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