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.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
123 if (!firstPass)
124 {
125 throw new Error("No more double pass");
126 }
127
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
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
246 ParseStackElement.pushState(this);
247
248 isExternalSchema = true;
249
250 parseFile(xmlFile);
251
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
348 isExternalSchema = parser.isExternalSchema;
349 currentPackage = parser.currentPackage;
350 currentXmlFile = parser.currentXmlFile;
351 firstPass = parser.firstPass;
352
353
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
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 }