View Javadoc

1   /*   Copyright (C) 2003 Finalist IT Group
2    *
3    *   This file is part of JAG - the Java J2EE Application Generator
4    *
5    *   JAG is free software; you can redistribute it and/or modify
6    *   it under the terms of the GNU General Public License as published by
7    *   the Free Software Foundation; either version 2 of the License, or
8    *   (at your option) any later version.
9    *   JAG is distributed in the hope that it will be useful,
10   *   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   *   GNU General Public License for more details.
13   *   You should have received a copy of the GNU General Public License
14   *   along with JAG; if not, write to the Free Software
15   *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16   */
17  package com.finalist.jaggenerator;
18  
19  import org.w3c.dom.Document;
20  import org.w3c.dom.Element;
21  import org.w3c.dom.NodeList;
22  
23  import javax.swing.*;
24  import java.io.ByteArrayOutputStream;
25  import java.io.File;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.util.*;
29  import java.util.jar.JarEntry;
30  import java.util.jar.JarFile;
31  
32  /***
33   * The DatabaseManager handles JAG's generic database support.
34   * This enables JAG to connect to any database, provided the database has a JDBC driver.
35   *
36   * @author Michael O'Connor - Finalist IT Group
37   */
38  public class DatabaseManager {
39     private static DatabaseManager ourInstance;
40     private static ArrayList databases = new ArrayList();
41  
42     private static final String DATABASE = "database";
43     private static final String SUPPORTED_DATABASES = "supported-databases";
44     public static final String NAME = "name";
45     private static final String DRIVER_CLASS = "driver-class";
46     private static final String APPSERVER_TYPEMAPPING = "appserver-typemapping";
47     private static final int READ_BUFFER_SIZE = 2048;
48     private static final String DOT_CLASS = ".class";
49     public static final String APPSERVER_TYPEMAPPINGS = "appserver-typemappings";
50  
51     private static String[] typeMappings;
52     private static final Database[] DATABASE_ARRAY = new Database[0];
53     private static final String FILE = "file";
54  
55     /***
56      * The DatabaseManager is a singleton - this method obtains the one and only instance.
57      * @return
58      */
59     public synchronized static DatabaseManager getInstance() {
60        if (ourInstance == null) {
61           ourInstance = new DatabaseManager();
62        }
63        return ourInstance;
64     }
65  
66     private DatabaseManager() {
67        load();
68     }
69  
70  
71     /***
72      * Adds JDBC driver(s) from the specified File.
73      *
74      * @param driverFile
75      * @return a List of Database objects, one for each driver found in the file.
76      */
77     public List addDrivers(final File driverFile) {
78        boolean driverFound = false;
79        List newDatabases = new ArrayList();
80  
81        try {
82           final JarFile jar = new JarFile(driverFile);
83           Enumeration e = jar.entries();
84  
85           while (e.hasMoreElements()) {
86              JarEntry entry = (JarEntry) e.nextElement();
87              if (entry.getName().endsWith(DOT_CLASS)) {
88                 String className = entry.getName().replace('/', '.').
89                       substring(0, entry.getName().indexOf(DOT_CLASS));
90                 try {
91                    Class clazz = new JarClassLoader(jar).loadClass(className);
92                    if (Arrays.asList(clazz.getInterfaces()).contains(java.sql.Driver.class)) {
93                       boolean alreadyKnown = false;
94                       driverFound = true;
95                       Iterator i = databases.iterator();
96                       while (i.hasNext()) {
97                          Database database = (Database) i.next();
98                          if (database.getDriverClass().equals(className)) {
99                             JOptionPane.showMessageDialog(null,
100                                  "The driver '" + className + "' will not be added, as it is already known!",
101                                  "Duplicate Driver", javax.swing.JOptionPane.INFORMATION_MESSAGE);
102                            alreadyKnown = true;
103                            break;
104                         }
105                      }
106 
107                      if (!alreadyKnown) {
108                         Database newDb = new Database();
109                         newDb.setDriverClass(className);
110                         newDb.setFilename(driverFile.getAbsolutePath());
111                         newDatabases.add(newDb);
112                      }
113                   }
114 
115                } catch (Throwable cnf) {
116                   System.out.println(cnf);
117                }
118             }
119          }
120 
121       } catch (IOException e) {
122          e.printStackTrace();  //To change body of catch statement use Options | File Templates.
123       }
124 
125       if (!driverFound) {
126          JOptionPane.showMessageDialog(null,
127                "JAG could not find any JDBC drivers in file: " + driverFile,
128                "No Drivers Found!", javax.swing.JOptionPane.INFORMATION_MESSAGE);
129       } else if (newDatabases.size() > 0) {
130          JOptionPane.showMessageDialog(null,
131                "Found " + newDatabases.size() + " JDBC driver" + (newDatabases.size() == 1 ? "" : "s") +
132                " in file: " + driverFile,
133                "JDBC Driver" + (newDatabases.size() == 1 ? "" : "s") + " Found!",
134                javax.swing.JOptionPane.INFORMATION_MESSAGE);
135       }
136 
137       return newDatabases;
138    }
139 
140    /***
141     * Gets the list of supported databases.
142     *
143     * @return an array of all currently supported databases.
144     */
145    public Database[] getSupportedDatabases() {
146       return (Database[]) databases.toArray(DATABASE_ARRAY);
147    }
148 
149    /***
150     * Resets the list of supported databases (and also resets the list in the Datasource panel).
151     * @param editedValues
152     */
153    public void setDatabases(ArrayList editedValues) {
154       databases = editedValues;
155       JagGenerator.jagGenerator.root.datasource.setSupportedDatabases(getSupportedDatabases());
156    }
157 
158    /***
159     * Gets the list of possible appserver typemappings.
160     * @return
161     */
162    public String[] getTypeMappings() {
163       return typeMappings;
164    }
165 
166    /***
167     * Persists the supported database information by appending an XML element to the config Document.
168     *
169     * @param root The XML Element under which we append the XML.
170     */
171    public Element appendXML(Element root) {
172       Document doc = root.getOwnerDocument();
173       Element dbRoot = doc.createElement(SUPPORTED_DATABASES);
174       Iterator i = databases.iterator();
175       while (i.hasNext()) {
176          Database dbInfo = (Database) i.next();
177          Element database = doc.createElement(DATABASE);
178          Element child = doc.createElement(NAME);
179          if (dbInfo.getDbName() != null) {
180             child.appendChild(doc.createTextNode(dbInfo.getDbName()));
181          }
182          database.appendChild(child);
183          child = doc.createElement(DRIVER_CLASS);
184          if (dbInfo.getDriverClass() != null) {
185             child.appendChild(doc.createTextNode(dbInfo.getDriverClass()));
186          }
187          database.appendChild(child);
188          child = doc.createElement(APPSERVER_TYPEMAPPING);
189          if (dbInfo.getTypeMapping() != null) {
190             child.appendChild(doc.createTextNode(dbInfo.getTypeMapping()));
191          }
192          database.appendChild(child);
193          child = doc.createElement(FILE);
194          if (dbInfo.getFilename() != null) {
195             child.appendChild(doc.createTextNode(dbInfo.getFilename()));
196          }
197          database.appendChild(child);
198          dbRoot.appendChild(database);
199       }
200       return dbRoot;
201    }
202 
203 
204    /***
205     * Reads in the supported database info from the config doc.
206     */
207    private void load() {
208       databases.clear();
209       Document doc = ConfigManager.getInstance().getDocument();
210       NodeList databaseNodes = doc.getElementsByTagName(DATABASE);
211       for (int i = 0; i < databaseNodes.getLength(); i++) {
212          Database dbInfo = new Database();
213          Element database = (Element) databaseNodes.item(i);
214          NodeList children = database.getChildNodes();
215          for (int j = 0; j < children.getLength(); j++) {
216             if (children.item(j) instanceof Element) {
217                Element child = (Element) children.item(j);
218                if (NAME.equals(child.getNodeName())) {
219                   dbInfo.setDbName(child.getFirstChild().getNodeValue());
220                } else if (DRIVER_CLASS.equals(child.getNodeName())) {
221                   dbInfo.setDriverClass(child.getFirstChild().getNodeValue());
222                } else if (APPSERVER_TYPEMAPPING.equals(child.getNodeName())) {
223                   dbInfo.setTypeMapping(child.getFirstChild().getNodeValue());
224                } else if (FILE.equals(child.getNodeName())) {
225                   dbInfo.setFilename(child.getFirstChild().getNodeValue());
226                }
227             }
228          }
229          //check validity of driver file
230          File driver = new File(dbInfo.getFilename());
231          if (!driver.exists()) {
232             JagGenerator.logToConsole("Removing missing driver reference: " + dbInfo.getFilename());
233             JOptionPane.showMessageDialog(JagGenerator.jagGenerator,
234                   "The previously specified JDBC driver for '" + dbInfo.getDbName() + "' databases can not be located.\n" +
235                   "(last known location was: " + dbInfo.getFilename() + ")\n" +
236                   "The entry for this driver will be deleted.",
237                   "Missing JDBC Driver!", javax.swing.JOptionPane.ERROR_MESSAGE);
238          } else {
239 
240             databases.add(dbInfo);
241          }
242       }
243 
244       Map typeMappingsMap = ConfigManager.getInstance().retrievePropertiesFromXML(APPSERVER_TYPEMAPPINGS);
245       String[] temp = (String[]) typeMappingsMap.get(NAME);
246       if (temp != null) {
247          typeMappings = temp;
248       }
249    }
250 
251    /***
252     * This ClassLoader is necessary for grabbing Class objects from a jar file.
253     */
254    private class JarClassLoader extends ClassLoader {
255       private HashMap alreadyLoaded = new HashMap();
256       private JarFile jar;
257 
258       /***
259        * Initialises the JarClassLoader with the jar file.
260        * @param jar
261        */
262       public JarClassLoader(JarFile jar) {
263          this.jar = jar;
264       }
265 
266       public synchronized Class loadClass(String name, boolean resolve)
267             throws ClassNotFoundException {
268          Class clazz = (Class) alreadyLoaded.get(name);
269          if (clazz == null) {
270             try {
271                //is it a system class?
272                return findSystemClass(name);
273             } catch (Exception e) {
274             }
275             try {
276                //is it already loaded?
277                return Class.forName(name);
278             } catch (Exception e) {
279             }
280 
281             byte[] bytes = loadClassBytes(name);
282             try {
283                clazz = defineClass(name, bytes, 0, bytes.length);
284             } catch (Throwable t) {
285                //System.out.println("!!defineClass threw " + t);
286             }
287                alreadyLoaded.put(name, clazz);
288 
289             if (clazz == null) {
290                throw new ClassNotFoundException(name);
291             }
292          }
293 
294          if (resolve) {
295             resolveClass(clazz);
296          }
297 
298          return clazz;
299       }
300 
301       private byte[] loadClassBytes(String name) {
302          ByteArrayOutputStream temp = new ByteArrayOutputStream();
303          byte[] buffer = new byte[READ_BUFFER_SIZE];
304          String entryName = name.replace('.', '/') + DOT_CLASS;
305          InputStream in = null;
306          try {
307 
308 //todo : the jars so far have not needed inflating.. is this always the case?
309 //               InflaterInputStream in = new InflaterInputStream(jar.getInputStream(entry));
310 
311             JarEntry entry = jar.getJarEntry(entryName);
312             if (entry != null) {
313                in = jar.getInputStream(entry);
314                int bytesRead = -1;
315                do {
316                   bytesRead = in.read(buffer, 0, READ_BUFFER_SIZE);
317                   if (bytesRead != -1) {
318                      temp.write(buffer, 0, bytesRead);
319                   }
320                } while (bytesRead != -1);
321             }
322 
323          } catch (IOException e1) {
324             e1.printStackTrace();
325          }
326 
327          return temp.toByteArray();
328       }
329    }
330 
331 }
332