View Javadoc

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