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.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
125 if (!firstPass)
126 {
127 throw new Error("No more double pass");
128 }
129
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
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
248 ParseStackElement.pushState(this);
249
250 isExternalSchema = true;
251
252 parseFile(xmlFile);
253
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
369 isExternalSchema = parser.isExternalSchema;
370 currentPackage = parser.currentPackage;
371 currentXmlFile = parser.currentXmlFile;
372 firstPass = parser.firstPass;
373
374
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
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 }