View Javadoc

1   package org.apache.torque.engine.database.transform;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.BufferedInputStream;
23  import java.io.File;
24  import java.io.FileInputStream;
25  import java.io.FileNotFoundException;
26  import java.util.Stack;
27  import java.util.Vector;
28  
29  import javax.xml.parsers.SAXParser;
30  import javax.xml.parsers.SAXParserFactory;
31  
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  import org.apache.torque.engine.EngineException;
35  import org.apache.torque.engine.database.model.Column;
36  import org.apache.torque.engine.database.model.Database;
37  import org.apache.torque.engine.database.model.Domain;
38  import org.apache.torque.engine.database.model.ForeignKey;
39  import org.apache.torque.engine.database.model.Index;
40  import org.apache.torque.engine.database.model.Table;
41  import org.apache.torque.engine.database.model.Unique;
42  import org.kuali.core.db.torque.DatabaseParser;
43  import org.xml.sax.Attributes;
44  import org.xml.sax.InputSource;
45  import org.xml.sax.SAXException;
46  import org.xml.sax.SAXParseException;
47  import org.xml.sax.helpers.DefaultHandler;
48  
49  /**
50   * A Class that is used to parse an input xml schema file and creates a Database
51   * java structure.
52   *
53   * @author <a href="mailto:leon@opticode.co.za">Leon Messerschmidt</a>
54   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
55   * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
56   * @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
57   * @author <a href="mailto:fischer@seitenbau.de">Thomas Fischer</a>
58   * @author <a href="mailto:monroe@dukece.com>Greg Monroe</a>
59   * @version $Id: XmlToAppData.java,v 1.1 2007-10-21 07:57:26 abyrne Exp $
60   */
61  public class XmlToAppData extends DefaultHandler implements DatabaseParser
62  {
63      /** Logging class from commons.logging */
64      private static Log log = LogFactory.getLog(XmlToAppData.class);
65  
66      private Database database;
67      private Table currTable;
68      private Column currColumn;
69      private ForeignKey currFK;
70      private Index currIndex;
71      private Unique currUnique;
72  
73      private boolean firstPass;
74      private boolean isExternalSchema;
75      private String currentPackage;
76      private String currentXmlFile;
77      private String defaultPackage;
78  
79      private static SAXParserFactory saxFactory;
80  
81      /** remember all files we have already parsed to detect looping. */
82      private Vector alreadyReadFiles;
83  
84      /** this is the stack to store parsing data */
85      private Stack parsingStack = new Stack();
86  
87      static
88      {
89          saxFactory = SAXParserFactory.newInstance();
90          saxFactory.setValidating(true);
91      }
92  
93      /**
94       * Creates a new instance for the specified database type.
95       *
96       * @param databaseType The type of database for the application.
97       */
98      public XmlToAppData(String databaseType)
99      {
100         database = new Database(databaseType);
101         firstPass = true;
102     }
103 
104     /**
105      * Creates a new instance for the specified database type.
106      *
107      * @param databaseType The type of database for the application.
108      * @param defaultPackage the default java package used for the om
109      */
110     public XmlToAppData(String databaseType, String defaultPackage)
111     {
112         database = new Database(databaseType);
113         this.defaultPackage = defaultPackage;
114         firstPass = true;
115     }
116 
117     /**
118      * Parses a XML input file and returns a newly created and
119      * populated Database structure.
120      *
121      * @param xmlFile The input file to parse.
122      * @return Database populated by <code>xmlFile</code>.
123      */
124     public Database parseResource(String xmlFile)
125             throws EngineException
126     {
127         try
128         {
129             // in case I am missing something, make it obvious
130             if (!firstPass)
131             {
132                 throw new Error("No more double pass");
133             }
134             // check to see if we alread have parsed the file
135             if ((alreadyReadFiles != null)
136                     && alreadyReadFiles.contains(xmlFile))
137             {
138                 return database;
139             }
140             else if (alreadyReadFiles == null)
141             {
142                 alreadyReadFiles = new Vector(3, 1);
143             }
144 
145             // remember the file to avoid looping
146             alreadyReadFiles.add(xmlFile);
147 
148             currentXmlFile = xmlFile;
149 
150             SAXParser parser = saxFactory.newSAXParser();
151 
152             FileInputStream fileInputStream = null;
153             try
154             {
155                 fileInputStream = new FileInputStream(xmlFile);
156             }
157             catch (FileNotFoundException fnfe)
158             {
159                 throw new FileNotFoundException
160                     (new File(xmlFile).getAbsolutePath());
161             }
162             BufferedInputStream bufferedInputStream
163                     = new BufferedInputStream(fileInputStream);
164             try
165             {
166                 log.info("Parsing file: '"
167                         + (new File(xmlFile)).getName() + "'");
168                 InputSource is = new InputSource(bufferedInputStream);
169                 is.setSystemId( new File( xmlFile ).getAbsolutePath() );
170                 parser.parse(is, this);
171             }
172             finally
173             {
174                 bufferedInputStream.close();
175             }
176         }
177         catch (SAXParseException e)
178         {
179             throw new EngineException("Sax error on line "
180                         + e.getLineNumber()
181                         + " column "
182                         + e.getColumnNumber()
183                         + " : "
184                         + e.getMessage(),
185                     e);
186         }
187         catch (Exception e)
188         {
189             throw new EngineException(e);
190         }
191         if (!isExternalSchema)
192         {
193             firstPass = false;
194         }
195         database.doFinalInitialization();
196         return database;
197     }
198 
199     /**
200      * EntityResolver implementation. Called by the XML parser
201      *
202      * @param publicId The public identifier of the external entity
203      * @param systemId The system identifier of the external entity
204      * @return an InputSource for the database.dtd file
205      * @see DTDResolver#resolveEntity(String, String)
206      */
207     public InputSource resolveEntity(String publicId, String systemId)
208             throws SAXException
209     {
210         try
211         {
212             return new DTDResolver().resolveEntity(publicId, systemId);
213         }
214         catch (Exception e)
215         {
216             throw new SAXException(e);
217         }
218     }
219 
220     /**
221      * Handles opening elements of the xml file.
222      *
223      * @param uri
224      * @param localName The local name (without prefix), or the empty string if
225      *         Namespace processing is not being performed.
226      * @param rawName The qualified name (with prefix), or the empty string if
227      *         qualified names are not available.
228      * @param attributes The specified or defaulted attributes
229      */
230     public void startElement(String uri, String localName, String rawName,
231                              Attributes attributes)
232             throws SAXException
233     {
234         try
235         {
236             if (rawName.equals("database"))
237             {
238                 if (isExternalSchema)
239                 {
240                     currentPackage = attributes.getValue("package");
241                     if (currentPackage == null)
242                     {
243                         currentPackage = defaultPackage;
244                     }
245                 }
246                 else
247                 {
248                     database.loadFromXML(attributes);
249                     if (database.getPackage() == null)
250                     {
251                         database.setPackage(defaultPackage);
252                     }
253                 }
254             }
255             else if (rawName.equals("external-schema"))
256             {
257                 String xmlFile = attributes.getValue("filename");
258                 if (xmlFile.charAt(0) != '/')
259                 {
260                     File f = new File(currentXmlFile);
261                     xmlFile = new File(f.getParent(), xmlFile).getPath();
262                 }
263 
264                 // put current state onto the stack
265                 ParseStackElement.pushState(this);
266 
267                 isExternalSchema = true;
268 
269                 parseResource(xmlFile);
270                 // get the last state from the stack
271                 ParseStackElement.popState(this);
272             }
273             else if (rawName.equals("domain"))
274             {
275                 Domain domain = new Domain();
276                 domain.loadFromXML(attributes, database.getPlatform());
277                 database.addDomain(domain);
278             }
279             else if (rawName.equals("table"))
280             {
281                 currTable = database.addTable(attributes);
282                 if (isExternalSchema)
283                 {
284                     currTable.setForReferenceOnly(true);
285                     currTable.setPackage(currentPackage);
286                 }
287             }
288             else if (rawName.equals("column"))
289             {
290                 currColumn = currTable.addColumn(attributes);
291             }
292             else if (rawName.equals("inheritance"))
293             {
294                 currColumn.addInheritance(attributes);
295             }
296             else if (rawName.equals("foreign-key"))
297             {
298                 currFK = currTable.addForeignKey(attributes);
299             }
300             else if (rawName.equals("reference"))
301             {
302                 currFK.addReference(attributes);
303             }
304             else if (rawName.equals("index"))
305             {
306                 currIndex = currTable.addIndex(attributes);
307             }
308             else if (rawName.equals("index-column"))
309             {
310                 currIndex.addColumn(attributes);
311             }
312             else if (rawName.equals("unique"))
313             {
314                 currUnique = currTable.addUnique(attributes);
315             }
316             else if (rawName.equals("unique-column"))
317             {
318                 currUnique.addColumn(attributes);
319             }
320             else if (rawName.equals("id-method-parameter"))
321             {
322                 currTable.addIdMethodParameter(attributes);
323             }
324             else if (rawName.equals("option"))
325             {
326                 setOption(attributes);
327             }
328         }
329         catch (Exception e)
330         {
331             throw new SAXException(e);
332         }
333     }
334 
335     /**
336      * Handles closing elements of the xml file.
337      *
338      * @param uri
339      * @param localName The local name (without prefix), or the empty string if
340      *         Namespace processing is not being performed.
341      * @param rawName The qualified name (with prefix), or the empty string if
342      *         qualified names are not available.
343      */
344     public void endElement(String uri, String localName, String rawName)
345         throws SAXException
346     {
347         if (log.isDebugEnabled())
348         {
349             log.debug("endElement(" + uri + ", " + localName + ", "
350                     + rawName + ") called");
351         }
352         try
353         {
354             // Reset working objects to null to allow option to know
355             // which element it is associated with.
356             if (rawName.equals("table"))
357             {
358                 currTable = null;
359             }
360             else if (rawName.equals("column"))
361             {
362                 currColumn = null;
363             }
364             else if (rawName.equals("foreign-key"))
365             {
366                 currFK = null;
367             }
368             else if (rawName.equals("index"))
369             {
370                 currIndex = null;
371             }
372             else if (rawName.equals("unique"))
373             {
374                 currUnique = null;
375             }
376         }
377         catch (Exception e)
378         {
379             throw new SAXException(e);
380         }
381     }
382 
383     public void setOption(Attributes attributes)
384     {
385         // Look thru supported model elements in reverse order to
386         // find one that this option statement applies to.
387 
388         String key = attributes.getValue("key");
389         String value = attributes.getValue("value");
390         if (currUnique != null)
391         {
392             currUnique.addOption(key, value);
393         }
394         else if (currIndex != null)
395         {
396             currIndex.addOption(key, value);
397         }
398         else if (currFK != null)
399         {
400             currFK.addOption(key, value);
401         }
402         else if (currColumn != null)
403         {
404             currColumn.addOption(key, value);
405         }
406         else if (currTable != null)
407         {
408             currTable.addOption(key, value);
409         }
410         else
411         {                            // Must be a db level option.
412             database.addOption(key, value);
413         }
414     }
415 
416     /**
417      * Handles exception which occur when the xml file is parsed
418      * @param e the exception which occured while parsing
419      * @throws SAXException always
420      */
421     public void error(SAXParseException e) throws SAXException
422     {
423         log.error("Sax parser threw an Exception", e);
424         throw new SAXException(
425                 "Error while parsing "
426                 + currentXmlFile
427                 + " at line "
428                 + e.getLineNumber()
429                 + " column "
430                 + e.getColumnNumber()
431                 + " : "
432                 + e.getMessage());
433     }
434 
435     /**
436      * When parsing multiple files that use nested <external-schema> tags we
437      * need to use a stack to remember some values.
438      */
439     private static class ParseStackElement
440     {
441         private boolean isExternalSchema;
442         private String currentPackage;
443         private String currentXmlFile;
444         private boolean firstPass;
445 
446         /**
447          *
448          * @param parser
449          */
450         public ParseStackElement(XmlToAppData parser)
451         {
452             // remember current state of parent object
453             isExternalSchema = parser.isExternalSchema;
454             currentPackage = parser.currentPackage;
455             currentXmlFile = parser.currentXmlFile;
456             firstPass = parser.firstPass;
457 
458             // push the state onto the stack
459             parser.parsingStack.push(this);
460         }
461 
462         /**
463          * Removes the top element from the stack and activates the stored state
464          *
465          * @param parser
466          */
467         public static void popState(XmlToAppData parser)
468         {
469             if (!parser.parsingStack.isEmpty())
470             {
471                 ParseStackElement elem = (ParseStackElement)
472                         parser.parsingStack.pop();
473 
474                 // activate stored state
475                 parser.isExternalSchema = elem.isExternalSchema;
476                 parser.currentPackage = elem.currentPackage;
477                 parser.currentXmlFile = elem.currentXmlFile;
478                 parser.firstPass = elem.firstPass;
479             }
480         }
481 
482         /**
483          * Stores the current state on the top of the stack.
484          *
485          * @param parser
486          */
487         public static void pushState(XmlToAppData parser)
488         {
489             new ParseStackElement(parser);
490         }
491     }
492 }