All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.openjpa.jdbc.schema.XMLSchemaParser Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.openjpa.jdbc.schema;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;

import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.jdbc.sql.DBDictionary;
import org.apache.openjpa.lib.meta.SourceTracker;
import org.apache.openjpa.lib.meta.XMLMetaDataParser;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.Localizer.Message;
import org.apache.openjpa.util.UserException;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;


/**
 * Custom SAX parser used to parse {@link Schema} objects. The parser
 * will place all parsed schemas into the current {@link SchemaGroup}, set
 * via the {@link #setSchemaGroup} method. This allows parsing of
 * multiple files into a single schema group.
 *  The parser deserializes from the following XML format:
* <!ELEMENT schemas (schema)+>
* <!ELEMENT schema (table|sequence)+>
* <!ATTLIST schema name CDATA #IMPLIED>
* <!ELEMENT table (column|index|pk|fk|unique)+>
* <!ATTLIST table name CDATA #REQUIRED>
* <!ELEMENT column EMPTY>
* <!ATTLIST column name CDATA #REQUIRED>
* <!ATTLIST column type (array|bigint|binary|bit|blob|char|clob * |date|decimal|distinct|double|float|integer|java_object * |longvarbinary|longvarchar|null|numeric|other|real|ref|smallint|struct * |time|timstamp|tinyint|varbinary|varchar) #REQUIRED>
* <!ATTLIST column type-name CDATA #IMPLIED>
* <!ATTLIST column size CDATA #IMPLIED>
* <!ATTLIST column decimal-digits CDATA #IMPLIED>
* <!ATTLIST column not-null (true|false) "false">
* <!ATTLIST column default CDATA #IMPLIED>
* <!ATTLIST column auto-assign (true|false) "false">
* <!ELEMENT index (on)*>
* <!ATTLIST index name CDATA #REQUIRED>
* <!ATTLIST index column CDATA #IMPLIED>
* <!ATTLIST index unique (true|false) "false">
* <!ELEMENT on EMPTY>
* <!ATTLIST on column CDATA #REQUIRED>
* <!ELEMENT pk (on)*>
<!ATTLIST pk name CDATA #IMPLIED>
* <!ATTLIST pk column CDATA #IMPLIED>
* <!ELEMENT fk (join)*>
* <!ATTLIST fk name CDATA #IMPLIED>
* <!ATTLIST fk deferred (true|false) "false">
* <!ATTLIST fk column CDATA #IMPLIED>
* <!ATTLIST fk to-table CDATA #REQUIRED>
* <!ATTLIST fk delete-action (cascade|default|restrict|none|null) * "none">
* <!ATTLIST fk update-action (cascade|default|restrict|none|null) * "none">
<!ELEMENT unique (on)*>
* <!ATTLIST unique name CDATA #IMPLIED>
* <!ATTLIST unique column CDATA #IMPLIED>
* <!ATTLIST unique deferred (true|false) "false">
* <!ELEMENT join EMPTY>
* <!ATTLIST join column CDATA #IMPLIED>
* <!ATTLIST join value CDATA #IMPLIED>
* <!ATTLIST join to-column CDATA #REQUIRED>
* <!ELEMENT sequence EMPTY>
* <!ATTLIST sequence name CDATA #REQUIRED>
* <!ATTLIST sequence initial-value CDATA #IMPLIED>
* <!ATTLIST sequence increment CDATA #IMPLIED>
* <!ATTLIST sequence allocate CDATA #IMPLIED>
*
* Schema parsers are not threadsafe. * * @author Abe White */ public class XMLSchemaParser extends XMLMetaDataParser implements SchemaParser { private static final Localizer _loc = Localizer.forPackage (XMLSchemaParser.class); private final DBDictionary _dict; // state for current parse private SchemaGroup _group = null; private Schema _schema = null; private Table _table = null; private PrimaryKeyInfo _pk = null; private IndexInfo _index = null; private UniqueInfo _unq = null; private ForeignKeyInfo _fk = null; private boolean _delay = false; // used to collect info on schema elements before they're resolved private final Collection _pkInfos = new LinkedList<>(); private final Collection _indexInfos = new LinkedList<>(); private final Collection _unqInfos = new LinkedList<>(); private final Collection _fkInfos = new LinkedList<>(); /** * Constructor. Supply configuration. */ public XMLSchemaParser(JDBCConfiguration conf) { _dict = conf.getDBDictionaryInstance(); setLog(conf.getLog(JDBCConfiguration.LOG_SCHEMA)); setParseText(false); setSuffix(".schema"); } @Override public boolean getDelayConstraintResolve() { return _delay; } @Override public void setDelayConstraintResolve(boolean delay) { _delay = delay; } @Override public void resolveConstraints() { resolvePrimaryKeys(); resolveIndexes(); resolveForeignKeys(); resolveUniques(); clearConstraintInfo(); } /** * Clear constraint infos. */ private void clearConstraintInfo() { _pkInfos.clear(); _indexInfos.clear(); _fkInfos.clear(); _unqInfos.clear(); } @Override public SchemaGroup getSchemaGroup() { if (_group == null) _group = new SchemaGroup(); return _group; } @Override public void setSchemaGroup(SchemaGroup group) { _group = group; } /** * Parse the schema relating to the given class. The schemas will * be added to the current schema group. */ @Override protected void finish() { // now resolve pk, idx, fk info super.finish(); if (!_delay) resolveConstraints(); } /** * Transforms the collected primary key information into actual * primary keys on the schema tables. */ private void resolvePrimaryKeys() { PrimaryKeyInfo pkInfo; String colName; Column col; for (PrimaryKeyInfo info : _pkInfos) { pkInfo = info; for (String s : pkInfo.cols) { colName = s; col = pkInfo.pk.getTable().getColumn(colName); if (col == null) throwUserException(_loc.get("pk-resolve", new Object[] {colName, pkInfo.pk.getTable()})); pkInfo.pk.addColumn(col); } } } /** * Transforms the collected index information into actual * indexes on the schema tables. */ private void resolveIndexes() { IndexInfo indexInfo; String colName; Column col; for (IndexInfo info : _indexInfos) { indexInfo = info; for (String s : indexInfo.cols) { colName = s; col = indexInfo.index.getTable().getColumn(colName); if (col == null) throwUserException(_loc.get("index-resolve", new Object[] {indexInfo.index, colName, indexInfo.index.getTable()})); indexInfo.index.addColumn(col); } } } /** * Transforms the collected foreign key information into actual * foreign keys on the schema tables. */ private void resolveForeignKeys() { ForeignKeyInfo fkInfo; Table toTable; Column col; String colName; Column pkCol; String pkColName; PrimaryKey pk; Iterator pks; Iterator cols; for (ForeignKeyInfo info : _fkInfos) { fkInfo = info; toTable = _group.findTable(fkInfo.toTable); if (toTable == null || toTable.getPrimaryKey() == null) throwUserException(_loc.get("fk-totable", new Object[] {fkInfo.fk, fkInfo.toTable, fkInfo.fk.getTable()})); // check if only one fk column listed using shortcut pk = toTable.getPrimaryKey(); if (fkInfo.cols.size() == 1 && fkInfo.pks.size() == 0) fkInfo.pks.add(pk.getColumns()[0].getName()); // make joins pks = fkInfo.pks.iterator(); for (cols = fkInfo.cols.iterator(); cols.hasNext(); ) { colName = (String) cols.next(); col = fkInfo.fk.getTable().getColumn(colName); if (col == null) throwUserException(_loc.get("fk-nocol", fkInfo.fk, colName, fkInfo.fk.getTable())); pkColName = (String) pks.next(); pkCol = toTable.getColumn(pkColName); if (pkCol == null) throwUserException(_loc.get("fk-nopkcol", new Object[] {fkInfo.fk, pkColName, toTable, fkInfo.fk.getTable()})); fkInfo.fk.join(col, pkCol); } // make constant joins cols = fkInfo.constCols.iterator(); for (Object value : fkInfo.consts) { colName = cols.next(); col = fkInfo.fk.getTable().getColumn(colName); if (col == null) throwUserException(_loc.get("fk-nocol", fkInfo.fk, colName, fkInfo.fk.getTable())); fkInfo.fk.joinConstant(col, value); } pks = fkInfo.constColsPK.iterator(); for (Object o : fkInfo.constsPK) { pkColName = pks.next(); pkCol = toTable.getColumn(pkColName); if (pkCol == null) throwUserException(_loc.get("fk-nopkcol", new Object[] {fkInfo.fk, pkColName, toTable, fkInfo.fk.getTable()})); fkInfo.fk.joinConstant(o, pkCol); } } } /** * Transforms the collected unique constraint information into actual * constraints on the schema tables. */ private void resolveUniques() { UniqueInfo unqInfo; String colName; Column col; for (UniqueInfo info : _unqInfos) { unqInfo = info; for (String s : unqInfo.cols) { colName = s; col = unqInfo.unq.getTable().getColumn(colName); if (col == null) throwUserException(_loc.get("unq-resolve", new Object[] {unqInfo.unq, colName, unqInfo.unq.getTable()})); unqInfo.unq.addColumn(col); } } } @Override protected void reset() { _schema = null; _table = null; _pk = null; _index = null; _fk = null; _unq = null; if (!_delay) clearConstraintInfo(); } @Override protected Reader getDocType() throws IOException { return new InputStreamReader(XMLSchemaParser.class .getResourceAsStream("schemas-doctype.rsrc")); } @Override protected boolean startElement(String name, Attributes attrs) throws SAXException { switch (name.charAt(0)) { case's': if ("schema".equals(name)) startSchema(attrs); else if ("sequence".equals(name)) startSequence(attrs); return true; case't': startTable(attrs); return true; case'c': startColumn(attrs); return true; case'p': startPrimaryKey(attrs); return true; case'i': startIndex(attrs); return true; case'u': startUnique(attrs); return true; case'f': startForeignKey(attrs); return true; case'o': startOn(attrs); return true; case'j': startJoin(attrs); return true; default: return false; } } @Override protected void endElement(String name) { switch (name.charAt(0)) { case's': if ("schema".equals(name)) endSchema(); break; case't': endTable(); break; case'p': endPrimaryKey(); break; case'i': endIndex(); break; case'u': endUnique(); break; case'f': endForeignKey(); break; } } private void startSchema(Attributes attrs) { // creates group if not set SchemaGroup group = getSchemaGroup(); String name = attrs.getValue("name"); _schema = group.getSchema(name); if (_schema == null) _schema = group.addSchema(name); } private void endSchema() { _schema = null; } private void startSequence(Attributes attrs) { Sequence seq = _schema.addSequence(attrs.getValue("name")); Locator locator = getLocation().getLocator(); if (locator != null) { seq.setLineNumber(locator.getLineNumber()); seq.setColNumber(locator.getColumnNumber()); } seq.setSource(getSourceFile(), SourceTracker.SRC_XML); try { String val = attrs.getValue("initial-value"); if (val != null) seq.setInitialValue(Integer.parseInt(val)); val = attrs.getValue("increment"); if (val != null) seq.setIncrement(Integer.parseInt(val)); val = attrs.getValue("allocate"); if (val != null) seq.setAllocate(Integer.parseInt(val)); } catch (NumberFormatException nfe) { throwUserException(_loc.get("bad-seq-num", seq.getFullName())); } } private void startTable(Attributes attrs) { _table = _schema.addTable(attrs.getValue("name")); _table.setSource(getSourceFile(), SourceTracker.SRC_XML); Locator locator = getLocation().getLocator(); if (locator != null) { _table.setLineNumber(locator.getLineNumber()); _table.setColNumber(locator.getColumnNumber()); } } private void endTable() { _table = null; } private void startColumn(Attributes attrs) { Column col = _table.addColumn(attrs.getValue("name")); col.setType(_dict.getPreferredType(Schemas.getJDBCType (attrs.getValue("type")))); col.setTypeName(attrs.getValue("type-name")); String val = attrs.getValue("size"); if (val != null) col.setSize(Integer.parseInt(val)); val = attrs.getValue("decimal-digits"); if (val != null) col.setDecimalDigits(Integer.parseInt(val)); col.setNotNull("true".equals(attrs.getValue("not-null"))); col.setAutoAssigned("true".equals(attrs.getValue("auto-assign")) || "true".equals(attrs.getValue("auto-increment"))); // old attr col.setDefaultString(attrs.getValue("default")); } private void startPrimaryKey(Attributes attrs) { _pk = new PrimaryKeyInfo(); _pk.pk = _table.addPrimaryKey(attrs.getValue("name")); _pk.pk.setLogical("true".equals(attrs.getValue("logical"))); String val = attrs.getValue("column"); if (val != null) _pk.cols.add(val); } private void endPrimaryKey() { _pkInfos.add(_pk); _pk = null; } private void startIndex(Attributes attrs) { _index = new IndexInfo(); _index.index = _table.addIndex(attrs.getValue("name")); _index.index.setUnique("true".equals(attrs.getValue("unique"))); String val = attrs.getValue("column"); if (val != null) _index.cols.add(val); } private void endIndex() { _indexInfos.add(_index); _index = null; } private void startUnique(Attributes attrs) { _unq = new UniqueInfo(); _unq.unq = _table.addUnique(attrs.getValue("name")); _unq.unq.setDeferred("true".equals(attrs.getValue("deferred"))); String val = attrs.getValue("column"); if (val != null) _unq.cols.add(val); } private void endUnique() { _unqInfos.add(_unq); _unq = null; } private void startForeignKey(Attributes attrs) { _fk = new ForeignKeyInfo(); _fk.fk = _table.addForeignKey(attrs.getValue("name")); if ("true".equals(attrs.getValue("deferred"))) _fk.fk.setDeferred(true); // set update action before delete action in case user incorrectly // sets update-action to "none" when there is a delete-action; otherwise // setting the update-action to "none" will also automatically set the // delete-action to "none", since FKs cannot have one actio be none and // the other be non-none String action = attrs.getValue("update-action"); if (action != null) _fk.fk.setUpdateAction(ForeignKey.getAction(action)); action = attrs.getValue("delete-action"); if (action != null) _fk.fk.setDeleteAction(ForeignKey.getAction(action)); _fk.toTable = attrs.getValue("to-table"); String val = attrs.getValue("column"); if (val != null) _fk.cols.add(val); } private void endForeignKey() { _fkInfos.add(_fk); _fk = null; } private void startOn(Attributes attrs) { String col = attrs.getValue("column"); if (_pk != null) _pk.cols.add(col); else if (_index != null) _index.cols.add(col); else _unq.cols.add(col); } private void startJoin(Attributes attrs) { String col = attrs.getValue("column"); String toCol = attrs.getValue("to-column"); String val = attrs.getValue("value"); if (val == null) { _fk.cols.add(col); _fk.pks.add(toCol); } else if (col == null) { _fk.constsPK.add(convertConstant(val)); _fk.constColsPK.add(toCol); } else { _fk.consts.add(convertConstant(val)); _fk.constCols.add(col); } } private static Object convertConstant(String val) { if ("null".equals(val)) return null; if (val.startsWith("'")) return val.substring(1, val.length() - 1); if (val.indexOf('.') == -1) return new Long(val); return new Double(val); } private void throwUserException(Message msg) { throw new UserException(getSourceName() + ": " + msg.getMessage()); } /** * Used to hold primary key info before it is resolved. */ private static class PrimaryKeyInfo { public PrimaryKey pk = null; public Collection cols = new LinkedList<>(); } /** * Used to hold index info before it is resolved. */ private static class IndexInfo { public Index index = null; public Collection cols = new LinkedList<>(); } /** * Used to hold unique constraint info before it is resolved. */ public static class UniqueInfo { public Unique unq = null; public Collection cols = new LinkedList<>(); } /** * Used to hold foreign key info before it is resolved. */ private static class ForeignKeyInfo { public ForeignKey fk = null; public String toTable = null; public Collection cols = new LinkedList<>(); public Collection pks = new LinkedList<>(); public Collection consts = new LinkedList<>(); public Collection constCols = new LinkedList<>(); public Collection constsPK = new LinkedList<>(); public Collection constColsPK = new LinkedList<>(); } }