001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.lib.ddl.impl;
043:
044: import java.sql.Connection;
045: import java.sql.DatabaseMetaData;
046: import java.sql.SQLException;
047: import java.io.InputStream;
048: import java.text.MessageFormat;
049: import java.util.Iterator;
050: import java.util.HashMap;
051: import java.util.Set;
052: import java.util.Vector;
053:
054: import org.openide.util.NbBundle;
055:
056: import org.netbeans.lib.ddl.DatabaseProductNotFoundException;
057: import org.netbeans.lib.ddl.DatabaseSpecification;
058: import org.netbeans.lib.ddl.DatabaseSpecificationFactory;
059: import org.netbeans.lib.ddl.DBConnection;
060: import org.netbeans.lib.ddl.DDLException;
061: import org.netbeans.lib.ddl.DriverSpecificationFactory;
062:
063: /**
064: * The factory used for creating instances of Specification class.
065: * SpecificationFactory collects information about available database
066: * description files and is able to specify if system can control
067: * the database (specified by product name or live connection) or not.
068: * It also provides a list of supported databases. Information about databases
069: * reads from file org/netbeans/lib/ddl/DatabaseSpecification.plist. It's possible to replace it
070: * by setting db.specifications.file system property pointing to another one.
071: *
072: * @author Slavek Psenicka
073: */
074: public class SpecificationFactory implements
075: DatabaseSpecificationFactory, DriverSpecificationFactory {
076:
077: /** Database description file
078: * You should use PListReader to parse it.
079: */
080: private static final String dbFile = "org/netbeans/lib/ddl/resources/dbspec.plist";
081:
082: /** Driver description file
083: * You should use PListReader to parse it.
084: */
085: private static final String drvFile = "org/netbeans/lib/ddl/resources/driverspec.plist";
086:
087: /** Array of SpecificationFiles, found (but not read) files
088: * which describes database products.
089: */
090: private HashMap dbSpecs;
091:
092: /** Array of SpecificationFiles, found (but not read) files
093: * which describes driver products.
094: */
095: private HashMap drvSpecs;
096:
097: /** Debug information
098: */
099: private boolean debug = false;
100:
101: /** Constructor.
102: * Reads a bunch of specification files and prepares sfiles array. Files should
103: * be read from default place or from folder specified by system property named
104: * "db.specifications.folder".
105: */
106: public SpecificationFactory() throws DDLException {
107: String fileDB = System.getProperty("db.specifications.file");
108: String fileDrv = System
109: .getProperty("driver.specifications.file");
110: SpecificationParser parser;
111:
112: try {
113: if (fileDB == null) {
114: ClassLoader cl = getClass().getClassLoader();
115: InputStream stream = cl.getResourceAsStream(dbFile);
116: if (stream == null) {
117: String message = MessageFormat
118: .format(
119: NbBundle
120: .getBundle(
121: "org.netbeans.lib.ddl.resources.Bundle")
122: .getString(
123: "EXC_UnableToOpenStream"),
124: new String[] { dbFile }); // NOI18N
125: throw new Exception(message);
126: }
127: parser = new SpecificationParser(stream);
128: dbSpecs = parser.getData();
129: stream.close();
130: } else {
131: parser = new SpecificationParser(fileDB);
132: dbSpecs = parser.getData();
133: }
134: } catch (Exception e) {
135: if (fileDB != null)
136: throw new DDLException(
137: "unable to read specifications file " + fileDB
138: + ", " + e.getMessage());
139: else
140: throw new DDLException(
141: "unable to read default specifications file, "
142: + e.getMessage());
143: }
144:
145: try {
146: if (fileDrv == null) {
147: ClassLoader cl = getClass().getClassLoader();
148: InputStream stream = cl.getResourceAsStream(drvFile);
149: if (stream == null) {
150: String message = MessageFormat
151: .format(
152: NbBundle
153: .getBundle(
154: "org.netbeans.lib.ddl.resources.Bundle")
155: .getString(
156: "EXC_UnableToOpenStream"),
157: new String[] { drvFile }); // NOI18N
158: throw new Exception(message);
159: }
160: parser = new SpecificationParser(stream);
161: drvSpecs = parser.getData();
162: stream.close();
163: } else {
164: parser = new SpecificationParser(fileDrv);
165: drvSpecs = parser.getData();
166: }
167: } catch (Exception e) {
168: if (fileDrv != null)
169: throw new DDLException(
170: "unable to read specifications file " + fileDrv
171: + ", " + e.getMessage());
172: else
173: throw new DDLException(
174: "unable to read default specifications file, "
175: + e.getMessage());
176: }
177: }
178:
179: /** Returns array of database products supported by system.
180: * It returns string array only, if you need a Specification instance, use
181: * appropriate createSpecification method.
182: */
183: public Set supportedDatabases() {
184: return dbSpecs.keySet();
185: }
186:
187: /** Returns true if database (specified by databaseProductName) is
188: * supported by system. Does not throw exception if it doesn't.
189: */
190: public boolean isDatabaseSupported(String databaseProductName) {
191: return (dbSpecs.containsKey(databaseProductName));
192: }
193:
194: /** Creates instance of DatabaseSpecification class; a database-specification
195: * class. This object knows about used database and can be used as
196: * factory for db-manipulating commands. It connects to the database
197: * and reads database metadata. Throws DatabaseProductNotFoundException if database
198: * (obtained from database metadata) is not supported. Uses given Connection
199: */
200: public DatabaseSpecification createSpecification(
201: DBConnection dbcon, Connection jdbccon)
202: throws DatabaseProductNotFoundException, DDLException {
203: String pn = null;
204: try {
205: boolean close = (jdbccon != null ? false : true);
206: Connection con = (jdbccon != null ? jdbccon : dbcon
207: .createJDBCConnection());
208: DatabaseMetaData dmd = con.getMetaData();
209: pn = dmd.getDatabaseProductName().trim();
210:
211: DatabaseSpecification spec = createSpecification(dbcon, pn,
212: con);
213: if (close)
214: con.close();
215: return spec;
216: } catch (SQLException e) {
217: throw new DDLException("unable to connect to server");
218: } catch (Exception e) {
219: throw new DatabaseProductNotFoundException(pn,
220: "unable to create specification, " + e.getMessage());
221: }
222: }
223:
224: /** Creates instance of DatabaseSpecification class; a database-specification
225: * class. This object knows about used database and can be used as
226: * factory for db-manipulating commands. It connects to database and
227: * reads metadata as createSpecification(DBConnection connection), but always
228: * uses specified databaseProductName. This is not recommended technique.
229: */
230: public DatabaseSpecification createSpecification(
231: DBConnection connection, String databaseProductName,
232: Connection c) throws DatabaseProductNotFoundException {
233: //IBM DB2 hack
234: if (databaseProductName.toUpperCase().startsWith("DB2/")) //NOI18N
235: databaseProductName = "DB2/"; //NOI18N
236:
237: HashMap product = (HashMap) dbSpecs.get(databaseProductName);
238:
239: if (product == null)
240: throw new DatabaseProductNotFoundException(
241: databaseProductName);
242: HashMap specmap = deepUnion(product, (HashMap) dbSpecs
243: .get("GenericDatabaseSystem"), true);
244: specmap.put("connection", connection);
245: DatabaseSpecification spec = new Specification(specmap, c);
246: specmap.put("dbproduct", databaseProductName);
247: spec.setSpecificationFactory(this );
248:
249: return spec;
250: }
251:
252: /** Creates instance of DatabaseSpecification class; a database-specification
253: * class. This object knows about used database and can be used as
254: * factory for db-manipulating commands. It connects to database and
255: * reads metadata as createSpecification(DBConnection connection), but always
256: * uses specified databaseProductName. This is not recommended technique.
257: */
258: public DatabaseSpecification createSpecification(
259: String databaseProductName, Connection c)
260: throws DatabaseProductNotFoundException {
261: //IBM DB2 hack
262: if (databaseProductName.toUpperCase().startsWith("DB2/")) //NOI18N
263: databaseProductName = "DB2/"; //NOI18N
264:
265: HashMap product = (HashMap) dbSpecs.get(databaseProductName);
266: if (product == null)
267: throw new DatabaseProductNotFoundException(
268: databaseProductName);
269: HashMap specmap = deepUnion(product, (HashMap) dbSpecs
270: .get("GenericDatabaseSystem"), true);
271: specmap.put("dbproduct", databaseProductName);
272: return new Specification(specmap, c);
273: }
274:
275: public DatabaseSpecification createSpecification(Connection c)
276: throws DatabaseProductNotFoundException, SQLException {
277: return createSpecification(c, c.getMetaData()
278: .getDatabaseProductName().trim());
279: }
280:
281: public DatabaseSpecification createSpecification(Connection c,
282: String databaseProductName)
283: throws DatabaseProductNotFoundException {
284: //IBM DB2 hack
285: if (databaseProductName.toUpperCase().startsWith("DB2/")) //NOI18N
286: databaseProductName = "DB2/"; //NOI18N
287:
288: HashMap product = (HashMap) dbSpecs.get(databaseProductName);
289: if (product == null)
290: throw new DatabaseProductNotFoundException(
291: databaseProductName);
292: HashMap specmap = deepUnion(product, (HashMap) dbSpecs
293: .get("GenericDatabaseSystem"), true);
294: DatabaseSpecification spec = new Specification(specmap, c);
295: spec.setSpecificationFactory(this );
296: return spec;
297: }
298:
299: /** Returns debug-mode flag
300: */
301: public boolean isDebugMode() {
302: return debug;
303: }
304:
305: /** Sets debug-mode flag
306: */
307: public void setDebugMode(boolean mode) {
308: debug = mode;
309: }
310:
311: /** Returns array of driver products supported by system.
312: * It returns string array only, if you need a Specification instance, use
313: * appropriate createDriverSpecification method.
314: */
315: public Set supportedDrivers() {
316: return drvSpecs.keySet();
317: }
318:
319: /** Returns true if driver (specified by driverName) is
320: * supported by system. Does not throw exception if it doesn't.
321: */
322: public boolean isDriverSupported(String driverName) {
323: return (drvSpecs.containsKey(driverName));
324: }
325:
326: /** Creates instance of DriverSpecification class; a driver-specification
327: * class. This object knows about used driver.
328: */
329: public DriverSpecification createDriverSpecification(
330: String driverName) {
331: HashMap product = (HashMap) drvSpecs.get(driverName);
332: if (product == null)
333: product = (HashMap) drvSpecs.get("DefaultDriver");
334: HashMap specmap = deepUnion(product, (HashMap) drvSpecs
335: .get("DefaultDriver"), true);
336: DriverSpecification spec = new DriverSpecification(specmap);
337: spec.setDriverSpecificationFactory(this );
338:
339: return spec;
340: }
341:
342: /** Creates deep copy of Map.
343: * All items will be cloned. Used internally in this object.
344: */
345: private HashMap deepClone(HashMap map) {
346: HashMap newone = (HashMap) map.clone();
347: Iterator it = newone.keySet().iterator();
348: while (it.hasNext()) {
349: Object newkey = it.next();
350: Object deepobj = null, newobj = newone.get(newkey);
351: if (newobj instanceof HashMap)
352: deepobj = deepClone((HashMap) newobj);
353: else if (newobj instanceof String)
354: deepobj = (Object) new String((String) newobj);
355: else if (newobj instanceof Vector)
356: deepobj = ((Vector) newobj).clone();
357: newone.put(newkey, deepobj);
358: }
359:
360: return newone;
361: }
362:
363: /** Joins base map with additional one.
364: * Copies keys only if not present in base map. Used internally in this object.
365: */
366: private HashMap deepUnion(HashMap base, HashMap additional,
367: boolean deep) {
368: Iterator it = additional.keySet().iterator();
369: while (it.hasNext()) {
370: Object addkey = it.next();
371: Object addobj = additional.get(addkey);
372:
373: //SQL92 types will be not added into databese type list
374: if (addkey.equals("TypeMap"))
375: continue;
376:
377: if (base.containsKey(addkey)) {
378: Object baseobj = base.get(addkey);
379: if (deep && (baseobj instanceof HashMap)
380: && (addobj instanceof HashMap)) {
381: deepUnion((HashMap) baseobj, (HashMap) addobj, deep);
382: }
383: } else {
384: if (addobj instanceof HashMap)
385: addobj = deepClone((HashMap) addobj);
386: else if (addobj instanceof String)
387: addobj = (Object) new String((String) addobj);
388: else if (addobj instanceof Vector)
389: addobj = ((Vector) addobj).clone();
390: base.put(addkey, addobj);
391: }
392: }
393:
394: return base;
395: }
396:
397: }
|