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