001: /*--------------------------------------------------------------------------*
002: | Copyright (C) 2006 Christopher Kohlhaas |
003: | |
004: | This program is free software; you can redistribute it and/or modify |
005: | it under the terms of the GNU General Public License as published by the |
006: | Free Software Foundation. A copy of the license has been included with |
007: | these distribution in the COPYING file, if not go to www.fsf.org |
008: | |
009: | As a special exception, you are granted the permissions to link this |
010: | program with every library, which license fulfills the Open Source |
011: | Definition as published by the Open Source Initiative (OSI). |
012: *--------------------------------------------------------------------------*/
013: package org.rapla.storage.dbsql;
014:
015: import java.io.IOException;
016: import java.sql.Connection;
017: import java.sql.Driver;
018: import java.sql.ResultSet;
019: import java.sql.SQLException;
020: import java.sql.Statement;
021: import java.util.Iterator;
022: import java.util.Properties;
023:
024: import javax.naming.Context;
025: import javax.naming.InitialContext;
026: import javax.sql.DataSource;
027:
028: import org.apache.avalon.framework.activity.Disposable;
029: import org.apache.avalon.framework.configuration.Configuration;
030: import org.apache.avalon.framework.configuration.ConfigurationException;
031: import org.rapla.entities.RaplaType;
032: import org.rapla.entities.User;
033: import org.rapla.entities.storage.RefEntity;
034: import org.rapla.framework.RaplaContext;
035: import org.rapla.framework.RaplaDefaultContext;
036: import org.rapla.framework.RaplaException;
037: import org.rapla.framework.internal.ConfigTools;
038: import org.rapla.storage.CachableStorageOperator;
039: import org.rapla.storage.IOContext;
040: import org.rapla.storage.UpdateEvent;
041: import org.rapla.storage.UpdateResult;
042: import org.rapla.storage.impl.AbstractCachableOperator;
043: import org.rapla.storage.impl.EntityStore;
044: import org.rapla.storage.xml.RaplaInput;
045:
046: /** This Operator is used to store the data in a SQL-DBMS.*/
047: public class DBOperator extends AbstractCachableOperator implements
048: Disposable {
049: private String driverClassname;
050: protected String datasourceName;
051: protected String user;
052: protected String password;
053: protected String dbURL;
054: protected Driver dbDriver;
055: protected boolean isConnected;
056: Properties dbProperties = new Properties();
057: boolean bSupportsTransactions = false;
058: boolean hsqldb = false;
059: boolean oldResourceTableName;
060:
061: public DBOperator(RaplaContext context, Configuration config)
062: throws RaplaException {
063: super (context);
064: datasourceName = config.getChild("datasource").getValue(null);
065: // dont use datasource (we have to configure a driver )
066: if (datasourceName == null) {
067: try {
068: driverClassname = config.getChild("driver").getValue();
069: dbURL = ConfigTools.resolveContext(config.getChild(
070: "url").getValue(), serviceManager);
071: } catch (ConfigurationException e) {
072: throw new RaplaException(e);
073: }
074: dbProperties.setProperty("user", config.getChild("user")
075: .getValue(""));
076: dbProperties.setProperty("password", config.getChild(
077: "password").getValue(""));
078: hsqldb = config.getChild("hsqldb-shutdown")
079: .getValueAsBoolean(false);
080: try {
081: dbDriver = (Driver) getClass().getClassLoader()
082: .loadClass(driverClassname).newInstance();
083: } catch (ClassNotFoundException e) {
084: throw new RaplaException("DB-Driver not found: "
085: + driverClassname + "\nCheck classpath!");
086: } catch (Exception e) {
087: throw new RaplaException(
088: "Could not instantiate DB-Driver: "
089: + driverClassname, e);
090: }
091: }
092: }
093:
094: public boolean supportsActiveMonitoring() {
095: return false;
096: }
097:
098: public Connection createConnection() throws RaplaException {
099: try {
100: Connection connection;
101: //datasource lookup
102: if (datasourceName != null) {
103: Context cxt = new InitialContext();
104: DataSource ds = (DataSource) cxt
105: .lookup("java:/comp/env/jdbc/" + datasourceName);
106: if (ds == null) {
107: throw new RaplaDBException("Datasource not found");
108: }
109: connection = ds.getConnection();
110: }
111: // or driver initialization
112: else {
113: connection = dbDriver.connect(dbURL, dbProperties);
114: if (connection == null) {
115: throw new RaplaDBException("No driver found for: "
116: + dbURL + "\nCheck url!");
117: }
118: }
119:
120: bSupportsTransactions = connection.getMetaData()
121: .supportsTransactions();
122: if (bSupportsTransactions) {
123: connection.setAutoCommit(false);
124: } else {
125: getLogger().warn("No Transaction support");
126: }
127: return connection;
128: } catch (Throwable ex) {
129: if (ex instanceof RaplaDBException) {
130: throw (RaplaDBException) ex;
131: }
132: ex.printStackTrace();
133: throw new RaplaDBException("DB-Connection aborted", ex);
134: }
135: }
136:
137: public void connect() throws RaplaException {
138: if (isConnected()) {
139: return;
140: }
141: loadData();
142: isConnected = true;
143: }
144:
145: public void connect(String username, char[] password)
146: throws RaplaException {
147: connect();
148: }
149:
150: public boolean isConnected() {
151: return isConnected;
152: }
153:
154: final public void refresh() throws RaplaException {
155: getLogger().warn("Incremental refreshs are not supported");
156: }
157:
158: public void refreshFull() throws RaplaException {
159: try {
160: loadData();
161: } catch (Exception ex) {
162: cache.clearAll();
163: disconnect();
164: if (ex instanceof RaplaException) {
165: throw (RaplaException) ex;
166: } else {
167: throw new RaplaException(ex);
168: }
169: }
170: }
171:
172: public void forceDisconnect() {
173: try {
174: disconnect();
175: } catch (Exception ex) {
176: getLogger().error("Error during disconnect ", ex);
177: }
178: }
179:
180: public void disconnect() throws RaplaException {
181: cache.clearAll();
182: idTable.setCache(cache);
183: // HSQLDB Special
184: if (hsqldb) {
185: String sql = "SHUTDOWN COMPACT";
186: try {
187: Connection connection = createConnection();
188: Statement statement = connection.createStatement();
189: statement.executeQuery(sql);
190: } catch (SQLException ex) {
191: throw new RaplaException(ex);
192: }
193: }
194: isConnected = false;
195: fireStorageDisconnected();
196: }
197:
198: public void dispose() {
199: forceDisconnect();
200: }
201:
202: final protected void loadData() throws RaplaException {
203: Connection connection = createConnection();
204: try {
205: // Upgrade db if neccessary
206: {
207: ResultSet categoryTable = connection.prepareStatement(
208: "select * from CATEGORY").executeQuery();
209: if (categoryTable.getMetaData().getColumnCount() == 4) {
210: getLogger().warn(
211: "Patching Database for table CATEGORY");
212: try {
213: connection
214: .prepareStatement(
215: "ALTER TABLE CATEGORY ADD COLUMN DEFINITION TEXT")
216: .execute();
217: connection.commit();
218: } catch (SQLException ex) {
219: getLogger()
220: .warn(
221: "Category patch failed. Trying HDBSQL Syntax");
222: connection
223: .prepareStatement(
224: "ALTER TABLE CATEGORY ADD COLUMN DEFINITION VARCHAR")
225: .execute();
226: connection.commit();
227: }
228: getLogger().warn("CATEGORY patched!");
229: }
230: if (categoryTable.getMetaData().getColumnCount() == 5) {
231: getLogger()
232: .warn(
233: "Patching Database for table CATEGORY (Category Order)");
234: connection
235: .prepareStatement(
236: "ALTER TABLE CATEGORY ADD COLUMN PARENT_ORDER INTEGER")
237: .execute();
238: getLogger().warn("CATEGORY patched!");
239: }
240: ResultSet eventTable = connection.prepareStatement(
241: "select * from EVENT").executeQuery();
242: if (eventTable.getMetaData().getColumnCount() == 5) {
243: getLogger().warn(
244: "Patching Database for table EVENT");
245: connection
246: .prepareStatement(
247: "ALTER TABLE EVENT ADD COLUMN LAST_CHANGED_BY INTEGER")
248: .execute();
249: connection.commit();
250: getLogger().warn("EVENT patched");
251: }
252:
253: checkForOldResourceTableName(connection);
254:
255: }
256: ResultSet set = connection.prepareStatement(
257: "select * from DYNAMIC_TYPE").executeQuery();
258: if (!set.next()) {
259: getLogger()
260: .warn(
261: "No content in database! Creating new database");
262: CachableStorageOperator sourceOperator = (CachableStorageOperator) serviceManager
263: .lookup(CachableStorageOperator.ROLE + "/file");
264: sourceOperator.connect();
265: setCache(sourceOperator.getCache());
266: saveData();
267: getLogger().warn("Database created!");
268: } else {
269: cache.clearAll();
270: idTable.setCache(cache);
271: readEverythingIntoCache(connection);
272: idTable.setCache(cache);
273:
274: if (getLogger().isDebugEnabled())
275: getLogger().debug("Entities contextualized");
276:
277: if (getLogger().isDebugEnabled())
278: getLogger().debug(
279: "All ConfigurationReferences resolved");
280: }
281: } catch (RaplaException ex) {
282: throw ex;
283: } catch (Exception ex) {
284: throw new RaplaException(ex);
285: } finally {
286: close(connection);
287: }
288: }
289:
290: private void checkForOldResourceTableName(Connection connection) {
291: try {
292: ResultSet oldResourceTable = connection.getMetaData()
293: .getTables(null, null, "RESOURCE", null);
294: while (oldResourceTable.next()) {
295: oldResourceTableName = true;
296: }
297: } catch (SQLException ex) {
298: oldResourceTableName = false;
299: }
300: if (oldResourceTableName) {
301: getLogger()
302: .warn(
303: "Using old resource table name RESOURCE. Please rename to RAPLA_RESOURCE");
304: }
305: }
306:
307: public Object createIdentifier(RaplaType raplaType)
308: throws RaplaException {
309: return idTable.createId(raplaType);
310: }
311:
312: public void dispatch(UpdateEvent evt) throws RaplaException {
313: evt = createClosure(evt);
314: check(evt);
315: Connection connection = createConnection();
316: try {
317: executeEvent(connection, evt);
318: if (bSupportsTransactions) {
319: getLogger().debug("Commiting");
320: connection.commit();
321: }
322: UpdateResult result = super .update(evt, true);
323: fireStorageUpdated(result);
324: } catch (Exception ex) {
325: try {
326: if (bSupportsTransactions) {
327: connection.rollback();
328: getLogger().error("Doing rollback");
329: throw new RaplaDBException(getI18n().getString(
330: "error.rollback"), ex);
331: } else {
332: String message = getI18n().getString(
333: "error.no_rollback");
334: getLogger().fatalError(message);
335: forceDisconnect();
336: throw new RaplaDBException(message, ex);
337: }
338: } catch (SQLException sqlEx) {
339: String message = "Unrecoverable error while storing";
340: getLogger().fatalError(message, sqlEx);
341: forceDisconnect();
342: throw new RaplaDBException(message, sqlEx);
343: }
344: } finally {
345: close(connection);
346: }
347:
348: }
349:
350: /**
351: * @param evt
352: * @throws RaplaException
353: */
354: protected void executeEvent(Connection connection, UpdateEvent evt)
355: throws RaplaException, SQLException {
356: // create the writer
357: RaplaSQL raplaSQL = new RaplaSQL(createOutputContext(),
358: oldResourceTableName);
359: // execute updates
360: Iterator it = evt.getStoreObjects().iterator();
361: while (it.hasNext()) {
362: RefEntity entity = (RefEntity) it.next();
363: raplaSQL.store(connection, entity);
364: }
365:
366: // execute removes
367: it = evt.getRemoveObjects().iterator();
368: while (it.hasNext()) {
369: Object id = ((RefEntity) it.next()).getId();
370: RefEntity entity = (RefEntity) cache.get(id);
371: if (entity != null)
372: raplaSQL.remove(connection, entity);
373: }
374:
375: }
376:
377: public void removeAll() throws RaplaException {
378: Connection connection = createConnection();
379: try {
380: checkForOldResourceTableName(connection);
381: RaplaSQL raplaSQL = new RaplaSQL(createOutputContext(),
382: oldResourceTableName);
383: if (!isConnected())
384: createConnection();
385:
386: raplaSQL.removeAll(connection);
387: connection.commit();
388: // do something here
389: getLogger().info("DB cleared");
390: } catch (SQLException ex) {
391: throw new RaplaException(ex);
392: } finally {
393: close(connection);
394: }
395:
396: }
397:
398: public void saveData() throws RaplaException {
399: Connection connection = createConnection();
400: try {
401: checkForOldResourceTableName(connection);
402: RaplaSQL raplaSQL = new RaplaSQL(createOutputContext(),
403: oldResourceTableName);
404: getLogger().info("Creation of DB started");
405: if (!isConnected())
406: createConnection();
407:
408: raplaSQL.removeAll(connection);
409: raplaSQL.createAll(connection);
410: connection.commit();
411: // do something here
412: getLogger().info("DB Creation complete");
413: } catch (SQLException ex) {
414: throw new RaplaException(ex);
415: } finally {
416: close(connection);
417: }
418: }
419:
420: static private void close(Connection connection)
421: throws RaplaException {
422: try {
423: connection.close();
424: } catch (SQLException e) {
425: throw new RaplaException(
426: "Can't close connection to database ", e);
427: }
428: }
429:
430: protected boolean readEverythingIntoCache(Connection connection)
431: throws RaplaException, IOException, SQLException {
432: EntityStore entityStore = new EntityStore(null, cache
433: .getSuperCategory());
434:
435: RaplaSQL raplaSQL = new RaplaSQL(
436: createInputContext(entityStore), oldResourceTableName);
437: raplaSQL.loadAll(connection);
438: resolveEntities(entityStore.getList().iterator(), entityStore);
439: cache.putAll(entityStore.getList());
440: for (Iterator it = cache.getIterator(User.TYPE); it.hasNext();) {
441: RefEntity user = ((RefEntity) it.next());
442: String password = entityStore.getPassword(user.getId());
443: cache.putPassword(user.getId(), password);
444: }
445: return false;
446: }
447:
448: protected RaplaDefaultContext createInputContext(EntityStore store)
449: throws RaplaException {
450: RaplaDefaultContext inputContext = new IOContext()
451: .createInputContext(serviceManager, store, idTable);
452: RaplaInput xmlAdapter = new RaplaInput(getLogger()
453: .getChildLogger("reading"));
454: inputContext.put(RaplaInput.class.getName(), xmlAdapter);
455: return inputContext;
456:
457: }
458:
459: protected RaplaDefaultContext createOutputContext()
460: throws RaplaException {
461: RaplaDefaultContext outputContext = new IOContext()
462: .createOutputContext(serviceManager, cache, true, false);
463: return outputContext;
464:
465: }
466:
467: }
|