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.modules.db.explorer.driver;
043:
044: import java.io.IOException;
045: import java.io.OutputStream;
046: import java.io.OutputStreamWriter;
047: import java.io.PrintWriter;
048: import java.lang.ref.Reference;
049: import java.lang.ref.WeakReference;
050: import java.net.MalformedURLException;
051: import java.net.URI;
052: import java.net.URISyntaxException;
053: import java.net.URL;
054: import java.util.Iterator;
055: import java.util.LinkedList;
056: import java.util.logging.Level;
057: import java.util.logging.Logger;
058: import org.openide.cookies.InstanceCookie;
059: import org.openide.filesystems.FileLock;
060: import org.openide.filesystems.FileObject;
061: import org.openide.filesystems.FileSystem;
062: import org.openide.filesystems.FileUtil;
063: import org.openide.loaders.Environment;
064: import org.openide.loaders.DataFolder;
065: import org.openide.loaders.DataObject;
066: import org.openide.loaders.MultiDataObject;
067: import org.openide.loaders.XMLDataObject;
068: import org.openide.util.Lookup;
069: import org.openide.util.lookup.AbstractLookup;
070: import org.openide.util.lookup.InstanceContent;
071: import org.openide.xml.EntityCatalog;
072: import org.openide.xml.XMLUtil;
073: import org.openide.filesystems.Repository;
074: import org.netbeans.api.db.explorer.JDBCDriver;
075: import org.openide.util.Exceptions;
076: import org.xml.sax.Attributes;
077: import org.xml.sax.InputSource;
078: import org.xml.sax.SAXException;
079: import org.xml.sax.XMLReader;
080: import org.xml.sax.helpers.DefaultHandler;
081:
082: /**
083: * Reads and writes the standard JDBC driver registration format.
084: *
085: * @author Radko Najman, Andrei Badea
086: */
087: public class JDBCDriverConvertor implements Environment.Provider,
088: InstanceCookie.Of {
089:
090: /**
091: * The reference to the instance of Environment.Provider
092: */
093: private static Reference/*<JDBCDriverConvertor>*/providerRef;
094:
095: /**
096: * The path where the drivers are registered in the SystemFileSystem.
097: */
098: public static final String DRIVERS_PATH = "Databases/JDBCDrivers"; // NOI18N
099:
100: /**
101: * The path where the drivers were registered in 4.1 and previous versions.
102: */
103: static final String OLD_DRIVERS_PATH = "Services/JDBCDrivers"; // NOI18N
104:
105: /**
106: * The delay by which the write of the changes is postponed.
107: */
108: private static final int DELAY = 2000;
109:
110: private static FileObject newlyCreated;
111: private static JDBCDriver newlyCreatedInstance;
112:
113: private final Reference holder;
114:
115: /**
116: * The lookup provided through Environment.Provider.
117: */
118: private Lookup lookup;
119:
120: Reference refDriver = new WeakReference(null);
121:
122: private static synchronized JDBCDriverConvertor createProvider() {
123: JDBCDriverConvertor provider = null;
124:
125: if (providerRef != null) {
126: provider = (JDBCDriverConvertor) providerRef.get();
127: }
128:
129: if (provider == null) {
130: provider = new JDBCDriverConvertor();
131: providerRef = new WeakReference(provider);
132: }
133:
134: return provider;
135: }
136:
137: private JDBCDriverConvertor() {
138: holder = new WeakReference(null);
139: }
140:
141: private JDBCDriverConvertor(XMLDataObject object) {
142: this .holder = new WeakReference(object);
143: InstanceContent cookies = new InstanceContent();
144: cookies.add(this );
145: lookup = new AbstractLookup(cookies);
146: }
147:
148: private JDBCDriverConvertor(XMLDataObject object,
149: JDBCDriver existingInstance) {
150: this (object);
151: refDriver = new WeakReference(existingInstance);
152: }
153:
154: // Environment.Provider methods
155:
156: public Lookup getEnvironment(DataObject obj) {
157: if (obj.getPrimaryFile() == newlyCreated) {
158: return new JDBCDriverConvertor((XMLDataObject) obj,
159: newlyCreatedInstance).getLookup();
160: } else {
161: return new JDBCDriverConvertor((XMLDataObject) obj)
162: .getLookup();
163: }
164: }
165:
166: // InstanceCookie.Of methods
167:
168: public String instanceName() {
169: XMLDataObject obj = getHolder();
170: return obj == null ? "" : obj.getName();
171: }
172:
173: public Class instanceClass() {
174: return JDBCDriver.class;
175: }
176:
177: public boolean instanceOf(Class type) {
178: return (type.isAssignableFrom(JDBCDriver.class));
179: }
180:
181: public Object instanceCreate() throws IOException,
182: ClassNotFoundException {
183: synchronized (this ) {
184: Object o = refDriver.get();
185: if (o != null) {
186: return o;
187: }
188:
189: XMLDataObject obj = getHolder();
190: if (obj == null) {
191: return null;
192: }
193: try {
194: JDBCDriver inst = readDriverFromFile(obj
195: .getPrimaryFile());
196: refDriver = new WeakReference(inst);
197: return inst;
198: } catch (MalformedURLException e) {
199: String message = "Ignoring " + obj.getPrimaryFile(); // NOI18N
200: Logger.getLogger(JDBCDriverConvertor.class.getName())
201: .log(Level.INFO, message, e);
202: return null;
203: }
204: }
205: }
206:
207: private XMLDataObject getHolder() {
208: return (XMLDataObject) holder.get();
209: }
210:
211: private static JDBCDriver readDriverFromFile(FileObject fo)
212: throws IOException, MalformedURLException {
213: Handler handler = new Handler();
214:
215: // parse the XM file
216: try {
217: XMLReader reader = XMLUtil.createXMLReader();
218: InputSource is = new InputSource(fo.getInputStream());
219: is.setSystemId(fo.getURL().toExternalForm());
220: reader.setContentHandler(handler);
221: reader.setErrorHandler(handler);
222: reader.setEntityResolver(EntityCatalog.getDefault());
223:
224: reader.parse(is);
225: } catch (SAXException ex) {
226: throw new IOException(ex.getMessage());
227: }
228:
229: // read the driver from the handler
230: URL[] urls = new URL[handler.urls.size()];
231: int j = 0;
232: for (Iterator i = handler.urls.iterator(); i.hasNext(); j++) {
233: urls[j] = new URL((String) i.next());
234: }
235: if (checkClassPathDrivers(handler.clazz, urls) == false) {
236: return null;
237: }
238:
239: if (handler.displayName == null) {
240: handler.displayName = handler.name;
241: }
242: return JDBCDriver.create(handler.name, handler.displayName,
243: handler.clazz, urls);
244: }
245:
246: // Other
247:
248: /**
249: * Creates the XML file describing the specified JDBC driver.
250: */
251: public static DataObject create(JDBCDriver drv) throws IOException {
252: FileObject fo = Repository.getDefault().getDefaultFileSystem()
253: .findResource(DRIVERS_PATH);
254: DataFolder df = DataFolder.findFolder(fo);
255:
256: String fileName = drv.getClassName().replace('.', '_'); //NOI18N
257: AtomicWriter writer = new AtomicWriter(drv, df, fileName);
258: df.getPrimaryFile().getFileSystem().runAtomicAction(writer);
259: return writer.holder;
260: }
261:
262: /**
263: * Moves the existing drivers from the old location (Services/JDBCDrivers)
264: * used in 4.1 and previous to the new one.
265: */
266: public static void importOldDrivers() {
267: FileSystem sfs = Repository.getDefault().getDefaultFileSystem();
268: FileObject oldRoot = sfs
269: .findResource(JDBCDriverConvertor.OLD_DRIVERS_PATH);
270: if (oldRoot == null) {
271: return;
272: }
273: FileObject newRoot = sfs
274: .findResource(JDBCDriverConvertor.DRIVERS_PATH);
275: FileObject[] children = oldRoot.getChildren();
276: for (int i = 0; i < children.length; i++) {
277: try {
278: JDBCDriver drv = readDriverFromFile(children[i]);
279: URL[] urls = drv.getURLs();
280: for (int j = 0; j < urls.length; j++) {
281: urls[j] = encodeURL(urls[j]);
282: }
283: create(drv);
284: } catch (Exception ex) {
285: Exceptions.printStackTrace(ex);
286: }
287: try {
288: children[i].delete();
289: } catch (IOException e) {
290: // what can we do?
291: }
292: }
293: }
294:
295: /**
296: * Encode an URL to be a valid URI. Be careful that this method will happily
297: * encode an already encoded URL! Use only on URLs which are not encoded.
298: */
299: static URL encodeURL(URL url) throws MalformedURLException,
300: URISyntaxException {
301: String urlString = url.toExternalForm();
302: int colon = urlString.indexOf(':');
303: int pound = urlString.indexOf('#');
304: String part = null;
305: String fragment = null;
306:
307: part = urlString.substring(colon + 1, pound != -1 ? pound
308: : urlString.length());
309: if (pound != -1) {
310: fragment = urlString.substring(pound + 1, urlString
311: .length());
312: }
313: return new URI(url.getProtocol(), part, fragment).toURL();
314: }
315:
316: /**
317: * Removes the file describing the specified JDBC driver.
318: */
319: public static void remove(JDBCDriver drv) throws IOException {
320: String name = drv.getName();
321: FileObject fo = Repository.getDefault().getDefaultFileSystem()
322: .findResource(DRIVERS_PATH); //NOI18N
323: DataFolder folder = DataFolder.findFolder(fo);
324: DataObject[] objects = folder.getChildren();
325:
326: for (int i = 0; i < objects.length; i++) {
327: InstanceCookie ic = (InstanceCookie) objects[i]
328: .getCookie(InstanceCookie.class);
329: if (ic != null) {
330: Object obj = null;
331: try {
332: obj = ic.instanceCreate();
333: } catch (ClassNotFoundException e) {
334: continue;
335: }
336: if (obj instanceof JDBCDriver) {
337: JDBCDriver driver = (JDBCDriver) obj;
338: if (driver.getName().equals(name)) {
339: objects[i].delete();
340: break;
341: }
342: }
343: }
344: }
345: }
346:
347: Lookup getLookup() {
348: return lookup;
349: }
350:
351: /**
352: * Atomic writer for writing a changed/new JDBCDriver.
353: */
354: private static final class AtomicWriter implements
355: FileSystem.AtomicAction {
356:
357: JDBCDriver instance;
358: MultiDataObject holder;
359: String fileName;
360: DataFolder parent;
361:
362: /**
363: * Constructor for writing to an existing file.
364: */
365: AtomicWriter(JDBCDriver instance, MultiDataObject holder) {
366: this .instance = instance;
367: this .holder = holder;
368: }
369:
370: /**
371: * Constructor for creating a new file.
372: */
373: AtomicWriter(JDBCDriver instance, DataFolder parent,
374: String fileName) {
375: this .instance = instance;
376: this .fileName = fileName;
377: this .parent = parent;
378: }
379:
380: public void run() throws java.io.IOException {
381: FileLock lck;
382: FileObject data;
383:
384: if (holder != null) {
385: data = holder.getPrimaryEntry().getFile();
386: lck = holder.getPrimaryEntry().takeLock();
387: } else {
388: FileObject folder = parent.getPrimaryFile();
389: String fn = FileUtil.findFreeFileName(folder, fileName,
390: "xml"); //NOI18N
391: data = folder.createData(fn, "xml"); //NOI18N
392: lck = data.lock();
393: }
394:
395: try {
396: OutputStream ostm = data.getOutputStream(lck);
397: PrintWriter writer = new PrintWriter(
398: new OutputStreamWriter(ostm, "UTF8")); //NOI18N
399: write(writer);
400: writer.flush();
401: writer.close();
402: ostm.close();
403: } finally {
404: lck.releaseLock();
405: }
406:
407: if (holder == null) {
408: newlyCreated = data;
409: newlyCreatedInstance = instance;
410: holder = (MultiDataObject) DataObject.find(data);
411: // ensure the Environment.Provider.getEnvironment() is called for the new DataObject
412: holder.getCookie(InstanceCookie.class);
413: newlyCreated = null;
414: newlyCreatedInstance = null;
415: }
416: }
417:
418: void write(PrintWriter pw) throws IOException {
419: pw.println("<?xml version='1.0'?>"); //NOI18N
420: pw
421: .println("<!DOCTYPE driver PUBLIC '-//NetBeans//DTD JDBC Driver 1.1//EN' 'http://www.netbeans.org/dtds/jdbc-driver-1_1.dtd'>"); //NOI18N
422: pw.println("<driver>"); //NOI18N
423: pw.println(" <name value='"
424: + XMLUtil.toAttributeValue(instance.getName())
425: + "'/>"); //NOI18N
426: pw.println(" <display-name value='"
427: + XMLUtil.toAttributeValue(instance
428: .getDisplayName()) + "'/>"); //NOI18N
429: pw.println(" <class value='"
430: + XMLUtil.toAttributeValue(instance.getClassName())
431: + "'/>"); //NOI18N
432: pw.println(" <urls>"); //NOI18N
433: URL[] urls = instance.getURLs();
434: for (int i = 0; i < urls.length; i++) {
435: pw.println(" <url value='"
436: + XMLUtil.toAttributeValue(urls[i].toString())
437: + "'/>"); //NOI18N
438: }
439: pw.println(" </urls>"); //NOI18N
440: pw.println("</driver>"); //NOI18N
441: }
442: }
443:
444: /**
445: * SAX handler for reading the XML file.
446: */
447: private static final class Handler extends DefaultHandler {
448:
449: private static final String ELEMENT_NAME = "name"; // NOI18N
450: private static final String ELEMENT_DISPLAY_NAME = "display-name"; // NOI18N
451: private static final String ELEMENT_CLASS = "class"; // NOI18N
452: private static final String ELEMENT_URL = "url"; // NOI18N
453: private static final String ATTR_PROPERTY_VALUE = "value"; // NOI18N
454:
455: String name;
456: String displayName;
457: String clazz;
458: LinkedList urls = new LinkedList();
459:
460: public void startDocument() throws SAXException {
461: }
462:
463: public void endDocument() throws SAXException {
464: }
465:
466: public void startElement(String uri, String localName,
467: String qName, Attributes attrs) throws SAXException {
468: if (ELEMENT_NAME.equals(qName)) {
469: name = attrs.getValue(ATTR_PROPERTY_VALUE);
470: } else if (ELEMENT_DISPLAY_NAME.equals(qName)) {
471: displayName = attrs.getValue(ATTR_PROPERTY_VALUE);
472: } else if (ELEMENT_CLASS.equals(qName)) {
473: clazz = attrs.getValue(ATTR_PROPERTY_VALUE);
474: } else if (ELEMENT_URL.equals(qName)) {
475: urls.add(attrs.getValue(ATTR_PROPERTY_VALUE));
476: }
477: }
478: }
479:
480: /**
481: * Checks if given class is on classpath.
482: *
483: * @param className fileName of class to be loaded
484: * @param urls file urls, checking classes only for 'file:/' URL.
485: * @return true if driver is available on classpath, otherwise false
486: */
487: private static boolean checkClassPathDrivers(String className,
488: URL[] urls) {
489: for (int i = 0; i < urls.length; i++) {
490: if ("file:/".equals(urls[i].toString())) { // NOI18N
491: try {
492: Class.forName(className);
493: } catch (ClassNotFoundException e) {
494: return false;
495: }
496: }
497: }
498: return true;
499: }
500: }
|