001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: */
019: package org.apache.openjpa.jdbc.schema;
020:
021: import java.io.IOException;
022: import java.io.InputStreamReader;
023: import java.io.Reader;
024: import java.util.Collection;
025: import java.util.Iterator;
026: import java.util.LinkedList;
027:
028: import org.xml.sax.Attributes;
029: import org.xml.sax.SAXException;
030: import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
031: import org.apache.openjpa.jdbc.sql.DBDictionary;
032: import org.apache.openjpa.lib.meta.XMLMetaDataParser;
033: import org.apache.openjpa.lib.util.Localizer;
034: import org.apache.openjpa.lib.util.Localizer.Message;
035: import org.apache.openjpa.util.UserException;
036:
037: /**
038: * Custom SAX parser used to parse {@link Schema} objects. The parser
039: * will place all parsed schemas into the current {@link SchemaGroup}, set
040: * via the {@link #setSchemaGroup} method. This allows parsing of
041: * multiple files into a single schema group.
042: * The parser deserializes from the following XML format:<br />
043: * <code> <!ELEMENT schemas (schema)+><br />
044: * <!ELEMENT schema (table|sequence)+><br />
045: * <!ATTLIST schema name CDATA #IMPLIED><br />
046: * <!ELEMENT table (column|index|pk|fk|unique)+><br />
047: * <!ATTLIST table name CDATA #REQUIRED><br />
048: * <!ELEMENT column EMPTY><br />
049: * <!ATTLIST column name CDATA #REQUIRED><br />
050: * <!ATTLIST column type (array|bigint|binary|bit|blob|char|clob
051: * |date|decimal|distinct|double|float|integer|java_object
052: * |longvarbinary|longvarchar|null|numeric|other|real|ref|smallint|struct
053: * |time|timstamp|tinyint|varbinary|varchar) #REQUIRED><br />
054: * <!ATTLIST column type-name CDATA #IMPLIED><br />
055: * <!ATTLIST column size CDATA #IMPLIED><br />
056: * <!ATTLIST column decimal-digits CDATA #IMPLIED><br />
057: * <!ATTLIST column not-null (true|false) "false"><br />
058: * <!ATTLIST column default CDATA #IMPLIED><br />
059: * <!ATTLIST column auto-assign (true|false) "false"><br />
060: * <!ELEMENT index (on)*><br />
061: * <!ATTLIST index name CDATA #REQUIRED><br />
062: * <!ATTLIST index column CDATA #IMPLIED><br />
063: * <!ATTLIST index unique (true|false) "false"><br />
064: * <!ELEMENT on EMPTY><br />
065: * <!ATTLIST on column CDATA #REQUIRED><br />
066: * <!ELEMENT pk (on)*><br /> <!ATTLIST pk name CDATA #IMPLIED><br />
067: * <!ATTLIST pk column CDATA #IMPLIED><br />
068: * <!ELEMENT fk (join)*><br />
069: * <!ATTLIST fk name CDATA #IMPLIED><br />
070: * <!ATTLIST fk deferred (true|false) "false"><br />
071: * <!ATTLIST fk column CDATA #IMPLIED><br />
072: * <!ATTLIST fk to-table CDATA #REQUIRED><br />
073: * <!ATTLIST fk delete-action (cascade|default|restrict|none|null)
074: * "none"><br />
075: * <!ATTLIST fk update-action (cascade|default|restrict|none|null)
076: * "none"><br /> <!ELEMENT unique (on)*><br />
077: * <!ATTLIST unique name CDATA #IMPLIED><br />
078: * <!ATTLIST unique column CDATA #IMPLIED><br />
079: * <!ATTLIST unique deferred (true|false) "false"><br />
080: * <!ELEMENT join EMPTY><br />
081: * <!ATTLIST join column CDATA #IMPLIED><br />
082: * <!ATTLIST join value CDATA #IMPLIED><br />
083: * <!ATTLIST join to-column CDATA #REQUIRED><br />
084: * <!ELEMENT sequence EMPTY><br />
085: * <!ATTLIST sequence name CDATA #REQUIRED><br />
086: * <!ATTLIST sequence initial-value CDATA #IMPLIED><br />
087: * <!ATTLIST sequence increment CDATA #IMPLIED><br />
088: * <!ATTLIST sequence allocate CDATA #IMPLIED><br />
089: * </code>
090: * Schema parsers are not threadsafe.
091: *
092: * @author Abe White
093: * @nojavadoc
094: */
095: public class XMLSchemaParser extends XMLMetaDataParser implements
096: SchemaParser {
097:
098: private static final Localizer _loc = Localizer
099: .forPackage(XMLSchemaParser.class);
100:
101: private final DBDictionary _dict;
102:
103: // state for current parse
104: private SchemaGroup _group = null;
105: private Schema _schema = null;
106: private Table _table = null;
107: private PrimaryKeyInfo _pk = null;
108: private IndexInfo _index = null;
109: private UniqueInfo _unq = null;
110: private ForeignKeyInfo _fk = null;
111: private boolean _delay = false;
112:
113: // used to collect info on schema elements before they're resolved
114: private final Collection _pkInfos = new LinkedList();
115: private final Collection _indexInfos = new LinkedList();
116: private final Collection _unqInfos = new LinkedList();
117: private final Collection _fkInfos = new LinkedList();
118:
119: /**
120: * Constructor. Supply configuration.
121: */
122: public XMLSchemaParser(JDBCConfiguration conf) {
123: _dict = conf.getDBDictionaryInstance();
124: setLog(conf.getLog(JDBCConfiguration.LOG_SCHEMA));
125: setParseText(false);
126: setSuffix(".schema");
127: }
128:
129: public boolean getDelayConstraintResolve() {
130: return _delay;
131: }
132:
133: public void setDelayConstraintResolve(boolean delay) {
134: _delay = delay;
135: }
136:
137: public void resolveConstraints() {
138: resolvePrimaryKeys();
139: resolveIndexes();
140: resolveForeignKeys();
141: resolveUniques();
142: clearConstraintInfo();
143: }
144:
145: /**
146: * Clear constraint infos.
147: */
148: private void clearConstraintInfo() {
149: _pkInfos.clear();
150: _indexInfos.clear();
151: _fkInfos.clear();
152: _unqInfos.clear();
153: }
154:
155: public SchemaGroup getSchemaGroup() {
156: if (_group == null)
157: _group = new SchemaGroup();
158: return _group;
159: }
160:
161: public void setSchemaGroup(SchemaGroup group) {
162: _group = group;
163: }
164:
165: /**
166: * Parse the schema relating to the given class. The schemas will
167: * be added to the current schema group.
168: */
169: protected void finish() {
170: // now resolve pk, idx, fk info
171: super .finish();
172: if (!_delay)
173: resolveConstraints();
174: }
175:
176: /**
177: * Transforms the collected primary key information into actual
178: * primary keys on the schema tables.
179: */
180: private void resolvePrimaryKeys() {
181: PrimaryKeyInfo pkInfo;
182: String colName;
183: Column col;
184: for (Iterator itr = _pkInfos.iterator(); itr.hasNext();) {
185: pkInfo = (PrimaryKeyInfo) itr.next();
186: for (Iterator cols = pkInfo.cols.iterator(); cols.hasNext();) {
187: colName = (String) cols.next();
188: col = pkInfo.pk.getTable().getColumn(colName);
189: if (col == null)
190: throwUserException(_loc.get("pk-resolve",
191: new Object[] { colName,
192: pkInfo.pk.getTable() }));
193: pkInfo.pk.addColumn(col);
194: }
195: }
196: }
197:
198: /**
199: * Transforms the collected index information into actual
200: * indexes on the schema tables.
201: */
202: private void resolveIndexes() {
203: IndexInfo indexInfo;
204: String colName;
205: Column col;
206: for (Iterator itr = _indexInfos.iterator(); itr.hasNext();) {
207: indexInfo = (IndexInfo) itr.next();
208: for (Iterator cols = indexInfo.cols.iterator(); cols
209: .hasNext();) {
210: colName = (String) cols.next();
211: col = indexInfo.index.getTable().getColumn(colName);
212: if (col == null)
213: throwUserException(_loc.get("index-resolve",
214: new Object[] { indexInfo.index, colName,
215: indexInfo.index.getTable() }));
216: indexInfo.index.addColumn(col);
217: }
218: }
219: }
220:
221: /**
222: * Transforms the collected foreign key information into actual
223: * foreign keys on the schema tables.
224: */
225: private void resolveForeignKeys() {
226: ForeignKeyInfo fkInfo;
227: Table toTable;
228: Column col;
229: String colName;
230: Column pkCol;
231: String pkColName;
232: PrimaryKey pk;
233: Iterator pks;
234: Iterator cols;
235: for (Iterator itr = _fkInfos.iterator(); itr.hasNext();) {
236: fkInfo = (ForeignKeyInfo) itr.next();
237: toTable = _group.findTable(fkInfo.toTable);
238: if (toTable == null || toTable.getPrimaryKey() == null)
239: throwUserException(_loc.get("fk-totable",
240: new Object[] { fkInfo.fk, fkInfo.toTable,
241: fkInfo.fk.getTable() }));
242:
243: // check if only one fk column listed using shortcut
244: pk = toTable.getPrimaryKey();
245: if (fkInfo.cols.size() == 1 && fkInfo.pks.size() == 0)
246: fkInfo.pks.add(pk.getColumns()[0].getName());
247:
248: // make joins
249: pks = fkInfo.pks.iterator();
250: for (cols = fkInfo.cols.iterator(); cols.hasNext();) {
251: colName = (String) cols.next();
252: col = fkInfo.fk.getTable().getColumn(colName);
253: if (col == null)
254: throwUserException(_loc.get("fk-nocol", fkInfo.fk,
255: colName, fkInfo.fk.getTable()));
256:
257: pkColName = (String) pks.next();
258: pkCol = toTable.getColumn(pkColName);
259: if (pkCol == null)
260: throwUserException(_loc.get("fk-nopkcol",
261: new Object[] { fkInfo.fk, pkColName,
262: toTable, fkInfo.fk.getTable() }));
263:
264: fkInfo.fk.join(col, pkCol);
265: }
266:
267: // make constant joins
268: cols = fkInfo.constCols.iterator();
269: for (Iterator vals = fkInfo.consts.iterator(); vals
270: .hasNext();) {
271: colName = (String) cols.next();
272: col = fkInfo.fk.getTable().getColumn(colName);
273: if (col == null)
274: throwUserException(_loc.get("fk-nocol", fkInfo.fk,
275: colName, fkInfo.fk.getTable()));
276:
277: fkInfo.fk.joinConstant(col, vals.next());
278: }
279:
280: pks = fkInfo.constColsPK.iterator();
281: for (Iterator vals = fkInfo.constsPK.iterator(); vals
282: .hasNext();) {
283: pkColName = (String) pks.next();
284: pkCol = toTable.getColumn(pkColName);
285: if (pkCol == null)
286: throwUserException(_loc.get("fk-nopkcol",
287: new Object[] { fkInfo.fk, pkColName,
288: toTable, fkInfo.fk.getTable() }));
289:
290: fkInfo.fk.joinConstant(vals.next(), pkCol);
291: }
292: }
293: }
294:
295: /**
296: * Transforms the collected unique constraint information into actual
297: * constraints on the schema tables.
298: */
299: private void resolveUniques() {
300: UniqueInfo unqInfo;
301: String colName;
302: Column col;
303: for (Iterator itr = _unqInfos.iterator(); itr.hasNext();) {
304: unqInfo = (UniqueInfo) itr.next();
305: for (Iterator cols = unqInfo.cols.iterator(); cols
306: .hasNext();) {
307: colName = (String) cols.next();
308: col = unqInfo.unq.getTable().getColumn(colName);
309: if (col == null)
310: throwUserException(_loc.get("unq-resolve",
311: new Object[] { unqInfo.unq, colName,
312: unqInfo.unq.getTable() }));
313: unqInfo.unq.addColumn(col);
314: }
315: }
316: }
317:
318: protected void reset() {
319: _schema = null;
320: _table = null;
321: _pk = null;
322: _index = null;
323: _fk = null;
324: _unq = null;
325: if (!_delay)
326: clearConstraintInfo();
327: }
328:
329: protected Reader getDocType() throws IOException {
330: return new InputStreamReader(XMLSchemaParser.class
331: .getResourceAsStream("schemas-doctype.rsrc"));
332: }
333:
334: protected boolean startElement(String name, Attributes attrs)
335: throws SAXException {
336: switch (name.charAt(0)) {
337: case 's':
338: if ("schema".equals(name))
339: startSchema(attrs);
340: else if ("sequence".equals(name))
341: startSequence(attrs);
342: return true;
343: case 't':
344: startTable(attrs);
345: return true;
346: case 'c':
347: startColumn(attrs);
348: return true;
349: case 'p':
350: startPrimaryKey(attrs);
351: return true;
352: case 'i':
353: startIndex(attrs);
354: return true;
355: case 'u':
356: startUnique(attrs);
357: return true;
358: case 'f':
359: startForeignKey(attrs);
360: return true;
361: case 'o':
362: startOn(attrs);
363: return true;
364: case 'j':
365: startJoin(attrs);
366: return true;
367: default:
368: return false;
369: }
370: }
371:
372: protected void endElement(String name) {
373: switch (name.charAt(0)) {
374: case 's':
375: if ("schema".equals(name))
376: endSchema();
377: break;
378: case 't':
379: endTable();
380: break;
381: case 'p':
382: endPrimaryKey();
383: break;
384: case 'i':
385: endIndex();
386: break;
387: case 'u':
388: endUnique();
389: break;
390: case 'f':
391: endForeignKey();
392: break;
393: }
394: }
395:
396: private void startSchema(Attributes attrs) {
397: // creates group if not set
398: SchemaGroup group = getSchemaGroup();
399:
400: String name = attrs.getValue("name");
401: _schema = group.getSchema(name);
402: if (_schema == null)
403: _schema = group.addSchema(name);
404: }
405:
406: private void endSchema() {
407: _schema = null;
408: }
409:
410: private void startSequence(Attributes attrs) {
411: Sequence seq = _schema.addSequence(attrs.getValue("name"));
412: seq.setSource(getSourceFile(), seq.SRC_XML);
413: try {
414: String val = attrs.getValue("initial-value");
415: if (val != null)
416: seq.setInitialValue(Integer.parseInt(val));
417: val = attrs.getValue("increment");
418: if (val != null)
419: seq.setIncrement(Integer.parseInt(val));
420: val = attrs.getValue("allocate");
421: if (val != null)
422: seq.setAllocate(Integer.parseInt(val));
423: } catch (NumberFormatException nfe) {
424: throwUserException(_loc.get("bad-seq-num", seq
425: .getFullName()));
426: }
427: }
428:
429: private void startTable(Attributes attrs) {
430: _table = _schema.addTable(attrs.getValue("name"));
431: _table.setSource(getSourceFile(), _table.SRC_XML);
432: }
433:
434: private void endTable() {
435: _table = null;
436: }
437:
438: private void startColumn(Attributes attrs) {
439: Column col = _table.addColumn(attrs.getValue("name"));
440: col.setType(_dict.getPreferredType(Schemas.getJDBCType(attrs
441: .getValue("type"))));
442: col.setTypeName(attrs.getValue("type-name"));
443: String val = attrs.getValue("size");
444: if (val != null)
445: col.setSize(Integer.parseInt(val));
446: val = attrs.getValue("decimal-digits");
447: if (val != null)
448: col.setDecimalDigits(Integer.parseInt(val));
449: col.setNotNull("true".equals(attrs.getValue("not-null")));
450: col.setAutoAssigned("true"
451: .equals(attrs.getValue("auto-assign"))
452: || "true".equals(attrs.getValue("auto-increment"))); // old attr
453: col.setDefaultString(attrs.getValue("default"));
454: }
455:
456: private void startPrimaryKey(Attributes attrs) {
457: _pk = new PrimaryKeyInfo();
458: _pk.pk = _table.addPrimaryKey(attrs.getValue("name"));
459: _pk.pk.setLogical("true".equals(attrs.getValue("logical")));
460:
461: String val = attrs.getValue("column");
462: if (val != null)
463: _pk.cols.add(val);
464: }
465:
466: private void endPrimaryKey() {
467: _pkInfos.add(_pk);
468: _pk = null;
469: }
470:
471: private void startIndex(Attributes attrs) {
472: _index = new IndexInfo();
473: _index.index = _table.addIndex(attrs.getValue("name"));
474: _index.index.setUnique("true".equals(attrs.getValue("unique")));
475:
476: String val = attrs.getValue("column");
477: if (val != null)
478: _index.cols.add(val);
479: }
480:
481: private void endIndex() {
482: _indexInfos.add(_index);
483: _index = null;
484: }
485:
486: private void startUnique(Attributes attrs) {
487: _unq = new UniqueInfo();
488: _unq.unq = _table.addUnique(attrs.getValue("name"));
489: _unq.unq.setDeferred("true".equals(attrs.getValue("deferred")));
490:
491: String val = attrs.getValue("column");
492: if (val != null)
493: _unq.cols.add(val);
494: }
495:
496: private void endUnique() {
497: _unqInfos.add(_unq);
498: _unq = null;
499: }
500:
501: private void startForeignKey(Attributes attrs) {
502: _fk = new ForeignKeyInfo();
503: _fk.fk = _table.addForeignKey(attrs.getValue("name"));
504:
505: if ("true".equals(attrs.getValue("deferred")))
506: _fk.fk.setDeferred(true);
507:
508: // set update action before delete action in case user incorrectly
509: // sets update-action to "none" when there is a delete-action; otherwise
510: // setting the update-action to "none" will also automatically set the
511: // delete-action to "none", since FKs cannot have one actio be none and
512: // the other be non-none
513: String action = attrs.getValue("update-action");
514: if (action != null)
515: _fk.fk.setUpdateAction(ForeignKey.getAction(action));
516: action = attrs.getValue("delete-action");
517: if (action != null)
518: _fk.fk.setDeleteAction(ForeignKey.getAction(action));
519:
520: _fk.toTable = attrs.getValue("to-table");
521: String val = attrs.getValue("column");
522: if (val != null)
523: _fk.cols.add(val);
524: }
525:
526: private void endForeignKey() {
527: _fkInfos.add(_fk);
528: _fk = null;
529: }
530:
531: private void startOn(Attributes attrs) {
532: String col = attrs.getValue("column");
533: if (_pk != null)
534: _pk.cols.add(col);
535: else if (_index != null)
536: _index.cols.add(col);
537: else
538: _unq.cols.add(col);
539: }
540:
541: private void startJoin(Attributes attrs) {
542: String col = attrs.getValue("column");
543: String toCol = attrs.getValue("to-column");
544: String val = attrs.getValue("value");
545: if (val == null) {
546: _fk.cols.add(col);
547: _fk.pks.add(toCol);
548: } else if (col == null) {
549: _fk.constsPK.add(convertConstant(val));
550: _fk.constColsPK.add(toCol);
551: } else {
552: _fk.consts.add(convertConstant(val));
553: _fk.constCols.add(col);
554: }
555: }
556:
557: private static Object convertConstant(String val) {
558: if ("null".equals(val))
559: return null;
560: if (val.startsWith("'"))
561: return val.substring(1, val.length() - 1);
562: if (val.indexOf('.') == -1)
563: return new Long(val);
564: return new Double(val);
565: }
566:
567: private void throwUserException(Message msg) {
568: throw new UserException(getSourceName() + ": "
569: + msg.getMessage());
570: }
571:
572: /**
573: * Used to hold primary key info before it is resolved.
574: */
575: private static class PrimaryKeyInfo {
576:
577: public PrimaryKey pk = null;
578: public Collection cols = new LinkedList();
579: }
580:
581: /**
582: * Used to hold index info before it is resolved.
583: */
584: private static class IndexInfo {
585:
586: public Index index = null;
587: public Collection cols = new LinkedList();
588: }
589:
590: /**
591: * Used to hold unique constraint info before it is resolved.
592: */
593: public static class UniqueInfo {
594:
595: public Unique unq = null;
596: public Collection cols = new LinkedList();
597: }
598:
599: /**
600: * Used to hold foreign key info before it is resolved.
601: */
602: private static class ForeignKeyInfo {
603:
604: public ForeignKey fk = null;
605: public String toTable = null;
606: public Collection cols = new LinkedList();
607: public Collection pks = new LinkedList();
608: public Collection consts = new LinkedList();
609: public Collection constCols = new LinkedList();
610: public Collection constsPK = new LinkedList();
611: public Collection constColsPK = new LinkedList();
612: }
613: }
|