KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mmbase > storage > implementation > database > DatabaseStorageManagerFactory


1 /*
2
3 This software is OSI Certified Open Source Software.
4 OSI Certified is a certification mark of the Open Source Initiative.
5
6 The license (Mozilla version 1.0) can be read at the MMBase site.
7 See http://www.MMBase.org/license
8
9 */

10 package org.mmbase.storage.implementation.database;
11
12 import java.sql.*;
13 import java.util.StringTokenizer JavaDoc;
14
15 import javax.naming.*;
16 import javax.sql.DataSource JavaDoc;
17 import javax.servlet.ServletContext JavaDoc;
18 import java.io.*;
19
20 import org.mmbase.module.core.MMBaseContext;
21 import org.mmbase.storage.*;
22 import org.mmbase.storage.search.implementation.database.*;
23 import org.mmbase.storage.search.SearchQueryHandler;
24 import org.mmbase.storage.util.StorageReader;
25 import org.mmbase.util.logging.*;
26 import org.mmbase.util.ResourceLoader;
27 import org.xml.sax.InputSource JavaDoc;
28
29 /**
30  * A storage manager factory for database storages.
31  * This factory sets up a datasource for connecting to the database.
32  * If you specify the datasource URI in the 'datasource' property in mmbaseroot.xml configuration file,
33  * the factory attempts to obtain the datasource from the application server. If this fails, or no datasource URI is given,
34  * it attempts to use the connectivity offered by the JDBC Module, which is then wrapped in a datasource.
35  * Note that if you provide a datasource you should make the JDBC Module inactive to prevent the module from
36  * interfering with the storage layer.
37  * @todo backward compatibility with the old supportclasses should be done by creating a separate Factory
38  * (LegacyStorageManagerFactory ?).
39  *
40  * @author Pierre van Rooden
41  * @since MMBase-1.7
42  * @version $Id: DatabaseStorageManagerFactory.java,v 1.40.2.1 2006/10/03 14:40:40 michiel Exp $
43  */

44 public class DatabaseStorageManagerFactory extends StorageManagerFactory {
45
46     private static final Logger log = Logging.getLoggerInstance(DatabaseStorageManagerFactory.class);
47
48     // standard sql reserved words
49
private final static String JavaDoc[] STANDARD_SQL_KEYWORDS =
50     {"absolute","action","add","all","allocate","alter","and","any","are","as","asc","assertion","at","authorization","avg","begin","between","bit","bit_length",
51      "both","by","cascade","cascaded","case","cast","catalog","char","character","char_length","character_length","check","close","coalesce","collate","collation",
52      "column","commit","connect","connection","constraint","constraints","continue","convert","corresponding","count","create","cross","current","current_date",
53      "current_time","current_timestamp","current_user","cursor","date","day","deallocate","dec","decimal","declare","default","deferrable","deferred","delete",
54      "desc","describe","descriptor","diagnostics","disconnect","distinct","domain","double","drop","else","end","end-exec","escape","except","exception","exec",
55      "execute","exists","external","extract","false","fetch","first","float","for","foreign","found","from","full","get","global","go","goto","grant","group","having","hour",
56      "identity","immediate","in","indicator","initially","inner","input","insensitive","insert","int","integer","intersect","interval","into","is","isolation","join",
57      "key","language","last","leading","left","level","like","local","lower","match","max","min","minute","module","month","names","national","natural","nchar","next","no",
58      "not","null","nullif","numeric","octet_length","of","on","only","open","option","or","order","outer","output","overlaps","pad","partial","position","precision",
59      "prepare","preserve","primary","prior","privileges","procedure","public","read","real","references","relative","restrict","revoke","right","rollback","rows",
60      "schema","scroll","second","section","select","session","session_user","set","size","smallint","some","space","sql","sqlcode","sqlerror","sqlstate","substring",
61      "sum","system_user","table","temporary","then","time","timestamp","timezone_hour","timezone_minute","to","trailing","transaction","translate","translation",
62      "trim","true","union","unique","unknown","update","upper","usage","user","using","value","values","varchar","varying","view","when","whenever","where","with","work",
63      "write","year","zone"};
64
65
66     // Default query handler class.
67
private final static Class JavaDoc DEFAULT_QUERY_HANDLER_CLASS = org.mmbase.storage.search.implementation.database.BasicSqlHandler.class;
68
69     // Default storage manager class
70
private final static Class JavaDoc DEFAULT_STORAGE_MANAGER_CLASS = org.mmbase.storage.implementation.database.RelationalDatabaseStorageManager.class;
71
72     /**
73      * The catalog used by this storage.
74      */

75     protected String JavaDoc catalog = null;
76     private String JavaDoc databaseName = null;
77     /**
78      * The datasource in use by this factory.
79      * The datasource is retrieved either from the application server, or by wrapping the JDBC Module in a generic datasource.
80      */

81     protected DataSource JavaDoc dataSource;
82
83     /**
84      * The transaction isolation level available for this storage.
85      * Default TRANSACTION_NONE (no transaction support).
86      * The actual value is determined from the database metadata.
87      */

88     protected int transactionIsolation = Connection.TRANSACTION_NONE;
89
90     /**
91      * Whether transactions and rollback are supported by this database
92      */

93     protected boolean supportsTransactions = false;
94
95
96     private static final String JavaDoc BASE_PATH_UNSET = "UNSET";
97     /**
98      * Used by #getBinaryFileBasePath
99      */

100     private String JavaDoc basePath = BASE_PATH_UNSET;
101
102     public double getVersion() {
103         return 0.1;
104     }
105
106     public boolean supportsTransactions() {
107         return supportsTransactions;
108     }
109
110     public String JavaDoc getCatalog() {
111         return catalog;
112     }
113
114     // this is more or less common
115
private static final java.util.regex.Pattern JavaDoc JDBC_URL_DB = java.util.regex.Pattern.compile("(?i)jdbc:.*;.*DatabaseName=([^;]+?)");
116
117     // this too
118
private static final java.util.regex.Pattern JavaDoc JDBC_URL = java.util.regex.Pattern.compile("(?i)jdbc:.*:(?:.*[/@])?(.*?)(?:[;\\?].*)?");
119
120     private static String JavaDoc getDatabaseName(String JavaDoc url) {
121         if (url == null) return null;
122         java.util.regex.Matcher JavaDoc matcher = JDBC_URL_DB.matcher(url);
123         if (matcher.matches()) {
124             return matcher.group(1);
125         }
126         matcher = JDBC_URL.matcher(url);
127         if (matcher.matches()) {
128             return matcher.group(1);
129         }
130         return null;
131     }
132
133     /**
134      * Doing some best effort to get a 'database name'.
135      * @since MMBase-1.8
136      */

137     public String JavaDoc getDatabaseName() {
138         return databaseName;
139     }
140
141     /**
142      * Returns the DataSource associated with this factory.
143      * @since MMBase-1.8
144      */

145     public DataSource JavaDoc getDataSource() {
146         return dataSource;
147     }
148
149     /**
150      * @param binaryFileBasePath For some datasource a file base path may be needed (some configurations of hsql). It can be <code>null</code> during bootstrap. In lookup.xml an alternative URL may be configured then which does not need the file base path.
151      * @since MMBase-1.8
152      */

153     protected DataSource JavaDoc createDataSource(String JavaDoc binaryFileBasePath) {
154         DataSource JavaDoc ds = null;
155         // get the Datasource for the database to use
156
// the datasource uri (i.e. 'jdbc/xa/MMBase' )
157
// is stored in the mmbaseroot module configuration file
158
String JavaDoc dataSourceURI = mmbase.getInitParameter("datasource");
159         if (dataSourceURI != null) {
160             try {
161                 String JavaDoc contextName = mmbase.getInitParameter("datasource-context");
162                 if (contextName == null) {
163                     contextName = "java:comp/env";
164                 }
165                 log.service("Using configured datasource " + dataSourceURI);
166                 Context JavaDoc initialContext = new InitialContext();
167                 Context JavaDoc environmentContext = (Context JavaDoc) initialContext.lookup(contextName);
168                 ds = (DataSource JavaDoc)environmentContext.lookup(dataSourceURI);
169             } catch(NamingException ne) {
170                 log.warn("Datasource '" + dataSourceURI + "' not available. (" + ne.getMessage() + "). Attempt to use JDBC Module to access database.");
171             }
172         }
173         if (ds == null) {
174             log.service("No data-source configured, using Generic data source");
175             // if no datasource is provided, try to obtain the generic datasource (which uses JDBC Module)
176
// This datasource should only be needed in cases were MMBase runs without application server.
177
if (binaryFileBasePath == null) {
178                 ds = new GenericDataSource(mmbase); // one argument version triggers also 'meta' mode, which in case of hsql may give another mem-only URL (just for the meta-data).
179
} else {
180                 ds = new GenericDataSource(mmbase, binaryFileBasePath);
181             }
182         }
183         //ds.setLogWriter(new LoggerPrintWriter(Logging.getInstance("datasource"));
184
return ds;
185
186     }
187
188     /**
189      * Opens and reads the storage configuration document.
190      * Obtain a datasource to the storage, and load configuration attributes.
191      * @throws StorageException if the storage could not be accessed or necessary configuration data is missing or invalid
192      */

193     protected synchronized void load() throws StorageException {
194         // default storagemanager class
195
storageManagerClass = DEFAULT_STORAGE_MANAGER_CLASS;
196
197         // default searchquery handler class
198
queryHandlerClasses.add(DEFAULT_QUERY_HANDLER_CLASS);
199
200
201
202         dataSource = createDataSource(null);
203         // temporary source only used once, for the meta data.
204

205         String JavaDoc sqlKeywords;
206
207         // test the datasource and retrieves options,
208
// which are stored as options in the factory's attribute
209
// this allows for easy retrieval of database options
210
{
211             Connection con = null;
212             try {
213                 con = dataSource.getConnection();
214                 if (con == null) throw new StorageException("Did get 'null' connection from data source " + dataSource);
215                 catalog = con.getCatalog();
216                 log.service("Connecting to catalog with name " + catalog);
217
218                 DatabaseMetaData metaData = con.getMetaData();
219                 String JavaDoc url = metaData.getURL();
220                 String JavaDoc db = getDatabaseName(url);
221                 if (db != null) {
222                     databaseName = db;
223                 } else {
224                     log.service("No db found in database connection meta data URL '" + url + "'");
225                     databaseName = catalog;
226                 }
227                 log.service("Connecting to database with name " + getDatabaseName());
228
229                 // set transaction options
230
supportsTransactions = metaData.supportsTransactions() && metaData.supportsMultipleTransactions();
231
232                 // determine transactionlevels
233
if (metaData.supportsTransactionIsolationLevel(Connection.TRANSACTION_SERIALIZABLE)) {
234                     transactionIsolation = Connection.TRANSACTION_SERIALIZABLE;
235                 } else if (metaData.supportsTransactionIsolationLevel(Connection.TRANSACTION_REPEATABLE_READ)) {
236                     transactionIsolation = Connection.TRANSACTION_REPEATABLE_READ;
237                 } else if (metaData.supportsTransactionIsolationLevel(Connection.TRANSACTION_READ_COMMITTED)) {
238                     transactionIsolation = Connection.TRANSACTION_READ_COMMITTED;
239                 } else if (metaData.supportsTransactionIsolationLevel(Connection.TRANSACTION_READ_UNCOMMITTED)) {
240                     transactionIsolation = Connection.TRANSACTION_READ_UNCOMMITTED;
241                 } else {
242                     supportsTransactions = false;
243                 }
244                 sqlKeywords = ("" + metaData.getSQLKeywords()).toLowerCase();
245
246             } catch (SQLException se) {
247                 // log.fatal(se.getMessage() + Logging.stackTrace(se)); will be logged in StorageManagerFactory already
248
throw new StorageInaccessibleException(se);
249             } finally {
250                 if (con != null) {
251                     try {
252                         con.close();
253                     } catch (SQLException se) {
254                         log.error(se);
255                     }
256                 }
257             }
258         }
259
260
261         // why is this not stored in real properties?
262

263         setOption(Attributes.SUPPORTS_TRANSACTIONS, supportsTransactions);
264         setAttribute(Attributes.TRANSACTION_ISOLATION_LEVEL, new Integer JavaDoc(transactionIsolation));
265         setOption(Attributes.SUPPORTS_COMPOSITE_INDEX, true);
266         setOption(Attributes.SUPPORTS_DATA_DEFINITION, true);
267
268         // create a default disallowedfields list:
269
// get the standard sql keywords
270
for (int i = 0; i < STANDARD_SQL_KEYWORDS.length; i++) {
271             disallowedFields.put(STANDARD_SQL_KEYWORDS[i], null); // during super.load, the null values will be replaced by actual replace-values.
272
}
273
274         // get the extra reserved sql keywords (according to the JDBC driver)
275
// not sure what case these are in ???
276
StringTokenizer JavaDoc tokens = new StringTokenizer JavaDoc(sqlKeywords,", ");
277         while (tokens.hasMoreTokens()) {
278             String JavaDoc tok = tokens.nextToken();
279             disallowedFields.put(tok, null);
280         }
281
282
283         // load configuration data (is also needing the temprary datasource in getDocumentReader..)
284
super.load();
285         log.service("Now creating the real data source");
286         dataSource = createDataSource(getBinaryFileBasePath(false));
287
288         // store the datasource as an attribute
289
// mm: WTF. This seems to be a rather essential property, so why it is not wrapped by a normal, comprehensible getDataSource method or so.
290
setAttribute(Attributes.DATA_SOURCE, dataSource);
291
292         // determine transaction support again (may be manually switched off)
293
supportsTransactions = hasOption(Attributes.SUPPORTS_TRANSACTIONS);
294     }
295
296     /**
297      * {@inheritDoc}
298      * MMBase determine it using information gained from the datasource, and the lookup.xml file
299      * in the database configuration directory
300      * Storage configuration files should become resource files, and configurable using a storageresource property.
301      * The type of reader to return should be a StorageReader.
302      * @throws StorageException if the storage could not be accessed while determining the database type
303      * @return a StorageReader instance
304      */

305     public StorageReader getDocumentReader() throws StorageException {
306         StorageReader reader = super.getDocumentReader();
307         // if no storage reader configuration has been specified, auto-detect
308
if (reader == null) {
309             String JavaDoc databaseResourcePath;
310             // First, determine the database name from the parameter set in mmbaseroot
311
String JavaDoc databaseName = mmbase.getInitParameter("database");
312             if (databaseName != null) {
313                 // if databasename is specified, attempt to use the database resource of that name
314
if (databaseName.endsWith(".xml")) {
315                     databaseResourcePath = databaseName;
316                 } else {
317                     databaseResourcePath = "storage/databases/" + databaseName + ".xml";
318                 }
319             } else {
320                 // WTF to configure storage, we need a connection already?!
321

322                 // otherwise, search for supported drivers using the lookup xml
323
DatabaseStorageLookup lookup = new DatabaseStorageLookup();
324                 Connection con = null;
325                 try {
326                     con = dataSource.getConnection();
327                     DatabaseMetaData metaData = con.getMetaData();
328                     databaseResourcePath = lookup.getResourcePath(metaData);
329                     if(databaseResourcePath == null) {
330                         // TODO: ask the lookup for a string containing all information on which the lookup could verify and display this instead of the classname
331
throw new StorageConfigurationException("No filter found in " + lookup.getSystemId() + " for driver class:" + metaData.getConnection().getClass().getName() + "\n");
332                     }
333                 } catch (SQLException sqle) {
334                     throw new StorageInaccessibleException(sqle);
335                 } finally {
336                     // close connection
337
if (con != null) {
338                         try { con.close(); } catch (SQLException sqle) {}
339                     }
340                 }
341             }
342             // get configuration
343
log.service("Configuration used for database storage: " + databaseResourcePath);
344             try {
345                 InputSource JavaDoc in = ResourceLoader.getConfigurationRoot().getInputSource(databaseResourcePath);
346                 reader = new StorageReader(this, in);
347             } catch (java.io.IOException JavaDoc ioe) {
348                 throw new StorageConfigurationException(ioe);
349             }
350
351         }
352         return reader;
353     }
354
355     /**
356      * As {@link #getBinaryFileBasePath(boolean)} with <code>true</code>
357      */

358     public String JavaDoc getBinaryFileBasePath() {
359         return getBinaryFileBasePath(true);
360     }
361
362     /**
363      * Returns the base path for 'binary file'
364      * @param check If the path is only perhaps needed, you may want to provide 'false' here.
365      * @since MMBase-1.8.1
366      */

367     public String JavaDoc getBinaryFileBasePath(boolean check) {
368         if (basePath == BASE_PATH_UNSET) {
369             basePath = (String JavaDoc) getAttribute(Attributes.BINARY_FILE_PATH);
370             if (basePath == null || basePath.equals("")) {
371                 ServletContext JavaDoc sc = MMBaseContext.getServletContext();
372                 basePath = sc != null ? sc.getRealPath("/WEB-INF/data") : null;
373                 if (basePath == null) {
374                     basePath = System.getProperty("user.dir") + File.separator + "data";
375                 }
376
377             } else {
378                 java.io.File JavaDoc baseFile = new java.io.File JavaDoc(basePath);
379                 if (! baseFile.isAbsolute()) {
380                     ServletContext JavaDoc sc = MMBaseContext.getServletContext();
381                     String JavaDoc absolute = sc != null ? sc.getRealPath("/") + File.separator : null;
382                     if (absolute == null) absolute = System.getProperty("user.dir") + File.separator;
383                     basePath = absolute + basePath;
384                 }
385             }
386             if (basePath == null) {
387                 log.warn("Cannot determin a a Binary File Base Path");
388                 return null;
389             }
390             File baseDir = new File(basePath);
391             try {
392                 basePath = baseDir.getCanonicalPath();
393                 if (check) checkBinaryFileBasePath(basePath);
394             } catch (IOException ioe) {
395                 log.error(ioe);
396             }
397             if (! basePath.endsWith(File.separator)) {
398                 basePath += File.separator;
399             }
400         }
401         return basePath;
402     }
403
404     /**
405      * Tries to ensure that basePath existis and is writable. Logs error and returns false otherwise.
406      * @param basePath a Directory name
407      * @since MMBase-1.8.1
408      */

409     public static boolean checkBinaryFileBasePath(String JavaDoc basePath) {
410         File baseDir = new File(basePath);
411         if (! baseDir.mkdirs() && ! baseDir.exists()) {
412             log.error("Cannot create the binary file path " + basePath);
413         }
414         if (! baseDir.canWrite()) {
415             log.error("Cannot write in the binary file path " + basePath);
416             return false;
417         } else {
418             return true;
419         }
420     }
421
422     protected Object JavaDoc instantiateBasicHandler(Class JavaDoc handlerClass) {
423         // first handler
424
try {
425             java.lang.reflect.Constructor JavaDoc constructor = handlerClass.getConstructor(new Class JavaDoc[] {});
426             SqlHandler sqlHandler = (SqlHandler) constructor.newInstance( new Object JavaDoc[] {} );
427             log.service("Instantiated SqlHandler of type " + handlerClass.getName());
428             return sqlHandler;
429         } catch (NoSuchMethodException JavaDoc nsme) {
430             throw new StorageConfigurationException(nsme);
431         } catch (java.lang.reflect.InvocationTargetException JavaDoc ite) {
432             throw new StorageConfigurationException(ite);
433         } catch (IllegalAccessException JavaDoc iae) {
434             throw new StorageConfigurationException(iae);
435         } catch (InstantiationException JavaDoc ie) {
436             throw new StorageConfigurationException(ie);
437         }
438     }
439
440     protected Object JavaDoc instantiateChainedHandler(Class JavaDoc handlerClass, Object JavaDoc handler) {
441         // Chained handlers
442
try {
443             java.lang.reflect.Constructor JavaDoc constructor = handlerClass.getConstructor(new Class JavaDoc[] {SqlHandler.class});
444             ChainedSqlHandler sqlHandler = (ChainedSqlHandler) constructor.newInstance(new Object JavaDoc[] { handler });
445             log.service("Instantiated chained SQLHandler of type " + handlerClass.getName());
446             return sqlHandler;
447         } catch (NoSuchMethodException JavaDoc nsme) {
448             throw new StorageConfigurationException(nsme);
449         } catch (java.lang.reflect.InvocationTargetException JavaDoc ite) {
450             throw new StorageConfigurationException(ite);
451         } catch (IllegalAccessException JavaDoc iae) {
452             throw new StorageConfigurationException(iae);
453         } catch (InstantiationException JavaDoc ie) {
454             throw new StorageConfigurationException(ie);
455         }
456     }
457
458     protected SearchQueryHandler instantiateQueryHandler(Object JavaDoc data) {
459         return new BasicQueryHandler((SqlHandler)data);
460     }
461
462
463     public static void main(String JavaDoc[] args) {
464         String JavaDoc u = "jdbc:hsql:test;test=b";
465         if (args.length > 0) u = args[0];
466         System.out.println("Database " + getDatabaseName(u));
467     }
468 }
469
470
471
Popular Tags