001: /*
002: * Copyright 2003 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package velosurf.sql;
018:
019: import java.io.*;
020: import java.sql.DriverManager;
021: import java.sql.SQLException;
022: import java.util.*;
023:
024: import velosurf.cache.Cache;
025: import velosurf.context.RowIterator;
026: import velosurf.model.Attribute;
027: import velosurf.model.Entity;
028: import velosurf.model.Action;
029: import velosurf.util.Logger;
030: import velosurf.util.LineWriterOutputStream;
031: import velosurf.util.Cryptograph;
032: import velosurf.util.XIncludeResolver;
033: import velosurf.util.UserContext;
034:
035: /** This class encapsulates a connection to the database and contains all the stuff relative to it.
036: *
037: * <p>To get a new instance, client classes should call one of the getInstance static methods.</p>
038: *
039: * @author <a href=mailto:claude.brisson@gmail.com>Claude Brisson</a>
040: *
041: */
042: public class Database {
043:
044: /** Builds a new connection.
045: *
046: */
047: private Database() {
048: }
049:
050: /** Builds a new connection.
051: *
052: * @param user user name
053: * @param password password
054: * @param url database url
055: * @param driver driver java class name
056: * @param schema schema name to use
057: * @exception SQLException thrown by the database engine
058: */
059: private Database(String user, String password, String url,
060: String driver, String schema) throws SQLException {
061: open(user, password, url, driver, schema);
062: }
063:
064: /** Get a unique Database from connection params.
065: *
066: * @param user user name
067: * @param password password
068: * @param url database url
069: * @exception SQLException thrown by the database engine
070: * @return a new connection
071: */
072: public static Database getInstance(String user, String password,
073: String url) throws SQLException {
074: return getInstance(user, password, url, null, null);
075: }
076:
077: /** Get a unique Database from connection params.
078: *
079: * @param user user name
080: * @param password password
081: * @param url database url
082: * @param driver driver java class name
083: * @exception SQLException thrown by the database engine
084: * @return a new connection
085: */
086: public static Database getInstance(String user, String password,
087: String url, String driver) throws SQLException {
088: return getInstance(user, password, url, driver, null);
089: }
090:
091: /** Get a unique Database from connection params.
092: *
093: * @param user user name
094: * @param password password
095: * @param url database url
096: * @param driver driver java class name
097: * @param schema schema
098: * @exception SQLException thrown by the database engine
099: * @return a new connection
100: */
101: public static Database getInstance(String user, String password,
102: String url, String driver, String schema)
103: throws SQLException {
104: Integer hash = Integer.valueOf(user.hashCode()
105: ^ password.hashCode() ^ url.hashCode()
106: ^ (driver == null ? 0 : driver.hashCode())
107: ^ (schema == null ? 0 : schema.hashCode()));
108: Database instance = (Database) connectionsByParams.get(hash);
109: if (instance == null) {
110: instance = new Database(user, password, url, driver, schema);
111: connectionsByParams.put(hash, instance);
112: }
113: return instance;
114: }
115:
116: /** Get a unique Database from config filename.
117: *
118: * @param configFilename config filename
119: * @exception SQLException thrown by the database engine
120: * @return a new connection
121: */
122: public static Database getInstance(String configFilename)
123: throws SQLException, FileNotFoundException, IOException {
124: Integer hash = Integer.valueOf(configFilename.hashCode());
125: Database instance = (Database) connectionsByConfigFile
126: .get(hash);
127: if (instance == null) {
128: String base = null;
129: configFilename = configFilename.replace('\\', '/');
130: int i = configFilename.lastIndexOf('/');
131: if (i == -1) {
132: base = ".";
133: } else {
134: base = configFilename.substring(0, i);
135: }
136: instance = getInstance(new FileInputStream(configFilename),
137: new XIncludeResolver(base));
138: connectionsByConfigFile.put(hash, instance);
139: }
140: return instance;
141: }
142:
143: /** Get a new connection.
144: * @param config config filename
145: * @exception SQLException thrown by the database engine
146: * @return a new connection
147: */
148: public static Database getInstance(InputStream config)
149: throws SQLException {
150: return Database.getInstance(config, null);
151: }
152:
153: /** Get a new connection.
154: * @param config config filename
155: * @exception SQLException thrown by the database engine
156: * @return a new connection
157: */
158: public static Database getInstance(InputStream config,
159: XIncludeResolver xincludeResolver) throws SQLException {
160: Database instance = new Database();
161: instance.readConfigFile(config, xincludeResolver);
162: instance.initCryptograph();
163: instance.connect();
164: instance.getReverseEngineer().readMetaData();
165: return instance;
166: }
167:
168: /** Open the connection.
169: *
170: * @param user user name
171: * @param password password
172: * @param url database url
173: * @param driver driver java class name
174: * @param schema schema name
175: * @exception SQLException thrown by the database engine
176: */
177: private void open(String user, String password, String url,
178: String driver, String schema) throws SQLException {
179:
180: this .user = user;
181: this .password = password;
182: this .url = url;
183: this .schema = schema;
184: driverClass = driver;
185: initCryptograph();
186: connect();
187: }
188:
189: /** Connect the database.
190: *
191: * @throws SQLException
192: */
193: private void connect() throws SQLException {
194: Logger.info("opening database " + url + " for user " + user
195: + (schema == null ? "" : " using schema " + schema));
196:
197: loadDriver();
198:
199: connectionPool = new ConnectionPool(url, user, password,
200: schema, driverInfo, true, minConnections,
201: maxConnections);
202: transactionConnectionPool = new ConnectionPool(url, user,
203: password, schema, driverInfo, false, 1, maxConnections);
204:
205: statementPool = new StatementPool(connectionPool);
206: preparedStatementPool = new PreparedStatementPool(
207: connectionPool);
208:
209: transactionStatementPool = new StatementPool(
210: transactionConnectionPool);
211: transactionPreparedStatementPool = new PreparedStatementPool(
212: transactionConnectionPool);
213:
214: // startup action
215: Action startup = rootEntity.getAction("startup");
216: if (startup != null)
217: startup.perform(null);
218: }
219:
220: /**
221: * Set the read-only state.
222: * @param readOnly read-only state
223: */
224: public void setReadOnly(boolean readOnly) {
225: this .readOnly = readOnly;
226: }
227:
228: /**
229: * Set the caching method.
230: * @param cachingMethod caching method
231: */
232: public void setCaching(int cachingMethod) {
233: caching = cachingMethod;
234: }
235:
236: /** Set the database user.
237: *
238: * @param user user name.
239: */
240: public void setUser(String user) {
241: this .user = user;
242: }
243:
244: /**
245: * Set the database password.
246: * @param password password
247: */
248: public void setPassword(String password) {
249: this .password = password;
250: }
251:
252: /**
253: * Set the database URL.
254: * @param url database url
255: */
256: public void setURL(String url) {
257: this .url = url;
258: }
259:
260: /**
261: * Set driver class.
262: * @param driverClass driver class
263: */
264: public void setDriver(String driverClass) {
265: this .driverClass = driverClass;
266: }
267:
268: /**
269: * Set schema name.
270: * @param schema schema name
271: */
272: public void setSchema(String schema) {
273: this .schema = schema;
274: if (this .schema != null) {
275: // share entities
276: sharedCatalog.put(getMagicNumber(this .schema), entities);
277: }
278: }
279:
280: /**
281: * Set minimum number of connections.
282: * @param minConnections minimum number of connections
283: */
284: public void setMinConnections(int minConnections) {
285: this .minConnections = minConnections;
286: }
287:
288: /**
289: * Set the maximum number of connections.
290: * @param maxConnections maximum number of connections
291: */
292: public void setMaxConnections(int maxConnections) {
293: this .maxConnections = maxConnections;
294: }
295:
296: /**
297: * Set the encryption seed.
298: * @param seed encryption seed
299: */
300: public void setSeed(String seed) {
301: this .seed = seed;
302: }
303:
304: /**
305: * Set the case policy.
306: * Possible values are CASE_SENSITIVE, CASE_LOWERCASE and CASE_UPPERCASE.
307: * @param caseSensivity case policy
308: */
309: public void setCase(int caseSensivity) {
310: this .caseSensivity = caseSensivity;
311: }
312:
313: /** Load the appropriate driver.
314: */
315: @SuppressWarnings("deprecation")
316: protected void loadDriver() {
317:
318: if (driverLoaded)
319: return;
320: if (Logger.getLogLevel() == Logger.TRACE_ID) {
321: /* Initialize log
322: * DriverManager.setLogWriter(Logger.getWriter()); -> doesn't work with jdbc 1.0 drivers
323: * so use the deprecated form
324: * TODO: detect driver jdbc conformance
325: */
326: if (Logger.getLogLevel() <= Logger.DEBUG_ID) {
327: DriverManager
328: .setLogStream(new PrintStream(
329: new LineWriterOutputStream(Logger
330: .getWriter())));
331: }
332: }
333:
334: /* driver behaviour */
335: driverInfo = DriverInfo.getDriverInfo(url, driverClass);
336:
337: reverseEngineer.setDriverInfo(driverInfo);
338:
339: if (driverClass != null) {
340: try {
341: Class.forName(driverClass);
342: driverLoaded = true;
343: } catch (Exception e) {
344: Logger.log(e);
345: }
346: } else if (driverInfo != null) {
347: // try to load one of the known drivers
348: String[] drivers = driverInfo.getDrivers();
349: for (int i = 0; i < drivers.length; i++)
350: try {
351: Class.forName(drivers[i]);
352: driverLoaded = true;
353: break;
354: } catch (Exception e) {
355: }
356: }
357: }
358:
359: /** Init cryptograph.
360: *
361: */
362: protected void initCryptograph() {
363: if (cryptograph != null)
364: return;
365: // to initialize the cryptograph, we need a chunk of user-provided bytes
366: // they must be persistent, so that urls that use encrypted params remain valid
367: // => use the database url if null
368: if (seed == null) {
369: seed = url;
370: }
371: try {
372: cryptograph = (Cryptograph) Class.forName(
373: "velosurf.util.DESCryptograph")
374: .getDeclaredConstructor(new Class[] {})
375: .newInstance(new Object[] {});
376: cryptograph.init(seed);
377: } catch (Exception e) {
378: Logger.error("Cannot initialize the cryptograph");
379: Logger.log(e);
380: }
381: }
382:
383: /**
384: * Get reverse engineer.
385: * @return reverse engineer.
386: */
387: public ReverseEngineer getReverseEngineer() {
388: return reverseEngineer;
389: }
390:
391: /** Issue a query.
392: *
393: * @param query an SQL query
394: * @return the resulting RowIterator
395: */
396: public RowIterator query(String query) throws SQLException {
397: return query(query, null);
398: }
399:
400: /** Issue a query, knowing the resulting entity.
401: *
402: * @param query an SQL query
403: * @param entity the resulting entity
404: * @return return the resulting row iterator
405: */
406: public RowIterator query(String query, Entity entity)
407: throws SQLException {
408: PooledSimpleStatement statement = null;
409: statement = statementPool.getStatement();
410: return statement.query(query, entity);
411: }
412:
413: /** Evaluate a query to a scalar.
414: *
415: * @param query an sql query
416: * @return the resulting scalar
417: */
418: public Object evaluate(String query) {
419: PooledSimpleStatement statement = null;
420: try {
421: statement = statementPool.getStatement();
422: return statement.evaluate(query);
423: } catch (SQLException sqle) {
424: Logger.log(sqle);
425: return null;
426: }
427: }
428:
429: /** Prepare a query.
430: *
431: * @param query an sql query
432: * @return the pooled prepared statement corresponding to the query
433: */
434: public PooledPreparedStatement prepare(String query) {
435: PooledPreparedStatement statement = null;
436: try {
437: statement = preparedStatementPool
438: .getPreparedStatement(query);
439: return statement;
440: } catch (SQLException sqle) {
441: Logger.log(sqle);
442: return null;
443: }
444: }
445:
446: /** Prepare a query which is part of a transaction.
447: *
448: * @param query an sql query
449: * @return the prepared statemenet corresponding to the query
450: */
451: public PooledPreparedStatement transactionPrepare(String query) {
452: PooledPreparedStatement statement = null;
453: try {
454: statement = transactionPreparedStatementPool
455: .getPreparedStatement(query);
456: return statement;
457: } catch (SQLException sqle) {
458: Logger.log(sqle);
459: return null;
460: }
461: }
462:
463: /** Issue an update query.
464: *
465: * @param query an sql query
466: * @return the number of affected rows
467: */
468: public int update(String query) {
469: try {
470: PooledSimpleStatement statement = statementPool
471: .getStatement();
472: return statement.update(query);
473: } catch (SQLException sqle) {
474: Logger.log(sqle);
475: return -1;
476: }
477: }
478:
479: /** Issue an update query that is part of a transaction.
480: *
481: * @param query an sql query
482: * @return the number of affected rows
483: */
484: public int transactionUpdate(String query) {
485: try {
486: PooledSimpleStatement statement = transactionStatementPool
487: .getStatement();
488: return statement.update(query);
489: } catch (SQLException sqle) {
490: Logger.log(sqle);
491: return -1;
492: }
493: }
494:
495: /** Close the connection.
496: *
497: * @exception SQLException thrown by the database engine
498: */
499: public void close() throws SQLException {
500: connectionPool.clear();
501: connectionPool = null;
502: transactionConnectionPool.clear();
503: transactionConnectionPool = null;
504: statementPool.clear();
505: statementPool = null;
506: transactionStatementPool.clear();
507: transactionStatementPool = null;
508: preparedStatementPool.clear();
509: preparedStatementPool = null;
510: transactionPreparedStatementPool.clear();
511: transactionPreparedStatementPool = null;
512: }
513:
514: /** Display statistics about the statements pools.
515: */
516: public void displayStats() {
517: System.out.println("DB statistics:");
518: int[] normalStats = statementPool.getUsageStats();
519: int[] preparedStats = preparedStatementPool.getUsageStats();
520: System.out.println("\tsimple statements - " + normalStats[0]
521: + " free statements out of " + normalStats[1]);
522: System.out.println("\tprepared statements - "
523: + preparedStats[0] + " free statements out of "
524: + preparedStats[1]);
525: }
526:
527: /** Get a jdbc connection.
528: *
529: * @return a jdbc connection wrapper (which extends java.sql.Connection)
530: */
531: public ConnectionWrapper getConnection() throws SQLException {
532: ConnectionWrapper c = connectionPool.getConnection();
533: c.setReadOnly(readOnly);
534: return c;
535: }
536:
537: /** Get the underlying jdbc connection used for transactions, and mark it right away as busy.
538: *
539: * @return a jdbc connection wrapper (which extends java.sql.Connection)
540: */
541: public synchronized ConnectionWrapper getTransactionConnection()
542: throws SQLException {
543: ConnectionWrapper ret = transactionConnectionPool
544: .getConnection();
545: ret.setReadOnly(readOnly);
546: ret.enterBusyState();
547: return ret;
548: }
549:
550: /** Read configuration from the given input stream.
551: *
552: * @param config input stream on the config file
553: */
554: private void readConfigFile(InputStream config,
555: XIncludeResolver xincludeResolver) {
556: try {
557: new ConfigLoader(this , xincludeResolver).loadConfig(config);
558:
559: } catch (Exception e) {
560: Logger.error("could not load configuration!");
561: Logger.log(e);
562: }
563: }
564:
565: /** Changes to lowercase or uppercase if needed.
566: *
567: * @param identifier
568: * @return changed identifier
569: */
570: public String adaptCase(String identifier) {
571: if (identifier == null)
572: return null;
573: String ret;
574: switch (caseSensivity) {
575: case CASE_SENSITIVE:
576: ret = identifier;
577: break;
578: case UPPERCASE:
579: ret = identifier.toUpperCase();
580: break;
581: case LOWERCASE:
582: ret = identifier.toLowerCase();
583: break;
584: default:
585: Logger.error("bad case-sensivity!");
586: ret = identifier;
587: }
588: return ret;
589: }
590:
591: /** Add a new entity.
592: *
593: * @param entity entity to add
594: */
595: public void addEntity(Entity entity) {
596: String name = entity.getName();
597: Entity previous = entities.put(adaptCase(name), entity);
598: if (previous != null) {
599: Logger.warn("replacing an existing entity with a new one ("
600: + name + ")");
601: }
602: if (name.equals("velosurf.root")) {
603: /* this is the root entity */
604: rootEntity = entity;
605: }
606: }
607:
608: /**
609: * Get root entity.
610: * @return root entity
611: */
612: public Entity getRootEntity() {
613: return rootEntity;
614: }
615:
616: /** Get a named entity or creeate it if it doesn't exist
617: *
618: * @param name name of an entity
619: * @return the named entity
620: */
621: public Entity getEntityCreate(String name) {
622: Entity entity = getEntity(name);
623: if (entity == null) {
624: Logger.trace("Created entity: " + name);
625: entity = new Entity(this , name, readOnly, caching);
626: entities.put(adaptCase(name), entity);
627: }
628: return entity;
629: }
630:
631: /** Get an existing entity.
632: *
633: * @param name the name of an entity
634: * @return the named entity
635: */
636: public Entity getEntity(String name) {
637: int i;
638: Entity entity = (Entity) entities.get(adaptCase(name));
639: if (entity == null && name != null
640: && (i = name.indexOf('.')) != -1) {
641: // imported from another schema ?
642: String schema = name.substring(0, i);
643: name = name.substring(i + 1);
644: Map external = (Map) sharedCatalog
645: .get(getMagicNumber(schema));
646: if (external != null)
647: entity = (Entity) external.get(name);
648: }
649: return entity;
650: }
651:
652: /** Entities map getter.
653: *
654: * @return entities map
655: */
656: public Map<String, Entity> getEntities() {
657: return entities;
658: }
659:
660: /** Get a root attribute.
661: *
662: * @param name name of an attribute
663: * @return the named attribute
664: */
665: public Attribute getAttribute(String name) {
666: return rootEntity.getAttribute(adaptCase(name));
667: }
668:
669: /** Get a root action.
670: *
671: * @param name name of an attribute
672: * @return the named attribute
673: */
674: public Action getAction(String name) {
675: return rootEntity.getAction(adaptCase(name));
676: }
677:
678: /** Obfuscate the given value.
679: * @param value value to obfuscate
680: *
681: * @return obfuscated value
682: */
683: public String obfuscate(Object value) {
684: if (value == null)
685: return null;
686: String encoded = cryptograph.encrypt(value.toString());
687:
688: // we want to avoid some characters fot HTTP GET
689: encoded = encoded.replace('=', '$');
690: encoded = encoded.replace('/', '_');
691: encoded = encoded.replace('+', '-');
692:
693: return encoded;
694: }
695:
696: /** De-obfuscate the given value.
697: * @param value value to de-obfuscate
698: *
699: * @return obfuscated value
700: */
701: public String deobfuscate(Object value) {
702: if (value == null)
703: return null;
704:
705: String ret = value.toString();
706:
707: // recover exact encoded string
708: ret = ret.replace('$', '=');
709: ret = ret.replace('_', '/');
710: ret = ret.replace('-', '+');
711:
712: ret = cryptograph.decrypt(ret);
713:
714: if (ret == null) {
715: Logger.error("deobfuscation of value '" + value
716: + "' failed!");
717: return null;
718: }
719:
720: return ret;
721: }
722:
723: /** Get database driver infos.
724: * @return the database vendor
725: */
726: public DriverInfo getDriverInfo() {
727: return driverInfo;
728: }
729:
730: /** Get database case-sensivity policy.
731: *
732: * @return case-sensivity
733: */
734: public int getCaseSensivity() {
735: return caseSensivity;
736: }
737:
738: /** Get the integer key used to share schema entities among instances.
739: */
740: private Integer getMagicNumber(String schema) {
741: // url is not checked for now because for some databases, the schema is part of the url.
742: return Integer.valueOf((user/*+url*/+ schema).hashCode());
743: }
744:
745: /** Get the schema.
746: * @return the schema
747: */
748: public String getSchema() {
749: return schema;
750: }
751:
752: /** database user.
753: */
754: private String user = null;
755: /** database user's password.
756: */
757: private String password = null;
758: /** database url.
759: */
760: private String url = null;
761: /** schema.
762: */
763: private String schema = null;
764:
765: /** whether the JDBC driver has been loaded. */
766: private boolean driverLoaded = false;
767:
768: /** driver class name, if provided in the config file.
769: */
770: private String driverClass = null;
771:
772: /**
773: * Pool of connections.
774: */
775: private ConnectionPool connectionPool = null;
776: /** min connections. */
777: private int minConnections = 1; // applies to connectionPool (min connections is always 1 for transactionConnectionPool)
778: /** max connections. */
779: private int maxConnections = 50; // applies to connectionPool and transactionConnectionPool
780:
781: /**
782: * Pool of connections for transactions.
783: */
784: private ConnectionPool transactionConnectionPool = null;
785:
786: /** pool of statements.
787: */
788: private StatementPool statementPool = null;
789:
790: /** pool of statements for transactions.
791: */
792: private StatementPool transactionStatementPool = null;
793:
794: /** pool of prepared statements.
795: */
796: private PreparedStatementPool preparedStatementPool = null;
797:
798: /** pool of prepared statements for transactions.
799: */
800: private PreparedStatementPool transactionPreparedStatementPool = null;
801:
802: /** default access mode.
803: */
804: private boolean readOnly = true;
805: /** default caching mode.
806: */
807: private int caching = Cache.NO_CACHE;
808:
809: /** map name->entity.
810: */
811: private Map<String, Entity> entities = new HashMap<String, Entity>();
812:
813: /** root entity that contains all root attributes and actions.
814: */
815: private Entity rootEntity = null;
816:
817: /** driver infos (database vendor specific).
818: */
819: private DriverInfo driverInfo = null;
820:
821: /** random seed used to initialize the cryptograph.
822: */
823: private String seed = null;
824:
825: /** cryptograph used to encrypt/decrypt database ids.
826: */
827: private Cryptograph cryptograph = null;
828:
829: /** unknown case-sensitive policy. */
830: public static final int CASE_UNKNOWN = 0;
831: /** sensitive case-sensitive policy. */
832: public static final int CASE_SENSITIVE = 1;
833: /** uppercase case-sensitive policy. */
834: public static final int UPPERCASE = 2;
835: /** lowercase case-sensitive policy. */
836: public static final int LOWERCASE = 3;
837:
838: /** case-sensivity. */
839: private int caseSensivity = CASE_UNKNOWN;
840:
841: /** case-sensivity for context.
842: */
843: private static int contextCase = LOWERCASE;
844:
845: /* context case implemented as a system property for now...
846: *TODO: check also other configuration realms or use model.xml
847: */
848: static {
849: String contextCase = System.getProperty("velosurf.case");
850: if (contextCase != null) {
851: if ("uppercase".equals(contextCase)) {
852: Database.contextCase = UPPERCASE;
853: } else if ("lowercase".equals(contextCase)) {
854: Database.contextCase = LOWERCASE;
855: } else {
856: Logger
857: .error("system property 'velosurf.case' should be 'lowercase' or 'uppercase'");
858: }
859: }
860: }
861:
862: /** adapt a string to the context case.
863: *
864: * @param str string to adapt
865: * @return adapted string
866: */
867: public static String adaptContextCase(String str) {
868: if (str == null) {
869: return null;
870: }
871: switch (contextCase) {
872: case LOWERCASE:
873: return str.toLowerCase();
874: case UPPERCASE:
875: return str.toUpperCase();
876: default:
877: Logger.error("unknown context case policy!");
878: return str;
879: }
880: }
881:
882: /** Set this database user context (thread local)
883: * @param userContext user context
884: */
885: public void setUserContext(UserContext userContext) {
886: this .userContext.set(userContext);
887: }
888:
889: /** Get this database user context (thread local)
890: *
891: * @return the thread local user context
892: */
893: public UserContext getUserContext() {
894: UserContext ret = userContext.get();
895: if (ret == null) {
896: /* create one */
897: ret = new UserContext();
898: userContext.set(ret);
899: }
900: return ret;
901: }
902:
903: public void setError(String errormsg) {
904: getUserContext().setError(errormsg);
905: }
906:
907: /** map parameters -> instances. */
908: private static Map<Integer, Database> connectionsByParams = new HashMap<Integer, Database>();
909:
910: /** map config files -> instances. */
911: private static Map<Integer, Database> connectionsByConfigFile = new HashMap<Integer, Database>();
912:
913: /** <p>Shared catalog, to share entities among instances.</p>
914: *
915: * <p>Key is hashcode of (name+password+url+schema), value is an entities map.</p>
916: */
917: private static Map<Integer, Map<String, Entity>> sharedCatalog = new HashMap<Integer, Map<String, Entity>>();
918: /**
919: * Reverse engineer.
920: */
921: private ReverseEngineer reverseEngineer = new ReverseEngineer(this );
922:
923: /** Thread-local user context.
924: */
925: private ThreadLocal<UserContext> userContext = new ThreadLocal<UserContext>();
926:
927: }
|