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
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
129 if (!firstPass)
130 {
131 throw new Error("No more double pass");
132 }
133
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
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
263 ParseStackElement.pushState(this);
264
265 isExternalSchema = true;
266
267 parseFile(xmlFile);
268
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
353
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
384
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 {
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
451 isExternalSchema = parser.isExternalSchema;
452 currentPackage = parser.currentPackage;
453 currentXmlFile = parser.currentXmlFile;
454 firstPass = parser.firstPass;
455
456
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
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 }