1 package org.apache.torque.engine.database.transform;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
116 if (!firstPass)
117 {
118 throw new Error("No more double pass");
119 }
120
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
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
239 ParseStackElement.pushState(this);
240
241 isExternalSchema = true;
242
243 parseFile(xmlFile);
244
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
335 isExternalSchema = parser.isExternalSchema;
336 currentPackage = parser.currentPackage;
337 currentXmlFile = parser.currentXmlFile;
338 firstPass = parser.firstPass;
339
340
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
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 }