View Javadoc

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