/* * #! * Ontopia DB2TM * #- * Copyright (C) 2001 - 2013 The Ontopia Project * #- * Licensed 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 net.ontopia.topicmaps.db2tm; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.Writer; import java.util.Collection; import java.util.HashMap; import java.util.Map; import net.ontopia.utils.OntopiaRuntimeException; import net.ontopia.utils.StringUtils; import net.ontopia.xml.DefaultXMLReaderFactory; import net.ontopia.xml.PrettyPrinter; import net.ontopia.xml.SAXTracker; import net.ontopia.xml.ValidatingContentHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.AttributesImpl; /** * INTERNAL: DB2TM relation mapping definition. Container for a set of * relations, entities and fields. The mapping can be instatiated by * calling the static read() methods, which will read its defintion * from an XML file. */ public class RelationMapping extends SAXTracker { // --- define a logging category. static Logger log = LoggerFactory.getLogger(RelationMapping.class); protected XMLReader reader; protected String name; protected String commitMode; protected File baseDirectory; protected final Map<String, DataSourceIF> datasources; protected final Map<String, Relation> relations; protected final Map<String, Prefix> iprefixes; RelationMapping() { this.datasources = new HashMap<String, DataSourceIF>(); this.relations = new HashMap<String, Relation>(); this.iprefixes = new HashMap<String, Prefix>(); // default commit mode, never commit this.commitMode = null; keepContentsOf("subject-locator"); keepContentsOf("subject-identifier"); keepContentsOf("item-identifier"); keepContentsOf("topic-name"); keepContentsOf("occurrence"); keepContentsOf("param"); keepContentsOf("condition"); keepContentsOf("expression-column"); } public void compile() { for (Relation rel : getRelations()) rel.compile(); } public void close() { for (DataSourceIF ds : getDataSources()) { try { ds.close(); } catch (Throwable t) { // ignore } } } protected static InputSource getRelaxNGSchema() throws IOException { ClassLoader cl = Thread.currentThread().getContextClassLoader(); InputStream i = cl.getResourceAsStream("net/ontopia/topicmaps/db2tm/db2tm.rnc"); return new InputSource(i); } public File getBaseDirectory() { return this.baseDirectory; } public void setBaseDirectory(File baseDirectory) { this.baseDirectory = baseDirectory; } public String getName() { return name; } public String getCommitMode() { return commitMode; } public Collection<DataSourceIF> getDataSources() { return datasources.values(); } public DataSourceIF getDataSource(String id) { return datasources.get(id); } public void addDataSource(String id, DataSourceIF datasource) { datasources.put(id, datasource); } public Collection<Relation> getRelations() { return relations.values(); } public Relation getRelation(String name) { return relations.get(name); } public void addRelation(Relation relation) { String name = relation.getName(); if (relations.containsKey(name)) throw new DB2TMException("Duplicate relation: " + name); else relations.put(name, relation); } public Prefix getPrefix(String prefix) { return iprefixes.get(prefix); } public String getQueryDeclarations() { // create prefix declaration string StringBuilder sb = new StringBuilder(); boolean first = true; for (Prefix prefix : iprefixes.values()) { if (!first) { sb.append("\n"); } first = false; sb.append("using "); sb.append(prefix.getId()); sb.append(" for "); switch (prefix.getType()) { case Prefix.TYPE_SUBJECT_IDENTIFIER: sb.append("i\""); break; case Prefix.TYPE_ITEM_IDENTIFIER: sb.append("s\""); break; case Prefix.TYPE_SUBJECT_LOCATOR: sb.append("a\""); break; } sb.append(prefix.getLocator()); sb.append("\""); } return sb.toString(); } public static RelationMapping read(File file) throws IOException { File parentFile = file.getParentFile(); return read(new FileInputStream(file), parentFile); } public static RelationMapping readFromClasspath(String resource) throws IOException { if (resource == null) throw new DB2TMConfigException("Parameter 'resource' must be specified."); ClassLoader cloader = RelationMapping.class.getClassLoader(); InputStream istream = cloader.getResourceAsStream(resource); if (istream != null) { log.debug("{}: loading from classpath", resource); return read(istream, null); } else { throw new DB2TMConfigException("Resource '" + resource + "' not found on classpath."); } } public static RelationMapping read(InputStream istream, File basedir) throws IOException { // Create new parser object XMLReader parser; try { parser = new DefaultXMLReaderFactory().createXMLReader(); } catch (SAXException e) { throw new IOException("Problems occurred when creating SAX2 XMLReader: " + e.getMessage()); } // Create content handler RelationMapping mapping = new RelationMapping(); mapping.setBaseDirectory(basedir); ContentHandler vhandler = new ValidatingContentHandler(mapping, getRelaxNGSchema(), true); parser.setContentHandler(vhandler); try { // Parse input source parser.parse(new InputSource(istream)); } catch (FileNotFoundException e) { log.error("Resource not found: {}", e.getMessage()); throw e; } catch (SAXParseException e) { throw new OntopiaRuntimeException("XML parsing problem: " + e.toString() + " at: "+ e.getSystemId() + ":" + e.getLineNumber() + ":" + e.getColumnNumber(), e); } catch (SAXException e) { if (e.getException() instanceof IOException) throw (IOException) e.getException(); throw new OntopiaRuntimeException(e); } // Compile mapping mapping.compile(); return mapping; } // -------------------------------------------------------------------------- // Content handler // -------------------------------------------------------------------------- protected Relation currel; protected Entity curent; protected Field curfield; protected ValueIF curvcol; protected Changelog cursync; protected ExpressionVirtualColumn curecol; // -------------------------------------------------------------------------- // Document events // -------------------------------------------------------------------------- public void startElement(String nsuri, String lname, String qname, Attributes attrs) throws SAXException { // Relations if (lname == "relation") { currel = new Relation(this); currel.setName(getValue(attrs, "name")); currel.setColumns(getValues(attrs, "columns", "column")); currel.setPrimaryKey(getValues(attrs, "primary-key")); currel.setCommitMode(getValue(attrs, "commit-mode")); String synctype = getValue(attrs, "synctype"); if (synctype == null) currel.setSynchronizationType(Relation.SYNCHRONIZATION_UNKNOWN); else if (synctype.equals("none")) currel.setSynchronizationType(Relation.SYNCHRONIZATION_NONE); else if (synctype.equals("rescan")) currel.setSynchronizationType(Relation.SYNCHRONIZATION_RESCAN); else if (synctype.equals("changelog")) currel.setSynchronizationType(Relation.SYNCHRONIZATION_CHANGELOG); addRelation(currel); } // Entities else if (lname == "topic") { curent = new Entity(Entity.TYPE_TOPIC, currel); String primary = getValue(attrs, "primary"); if (primary != null) curent.setPrimary(Boolean.valueOf(primary)); curent.setId(getValue(attrs, "id")); String condition = getValue(attrs, "condition"); if (condition != null) curent.setConditionValue(Values.getColumnValue(currel, condition)); curent.setTypes(getValues(attrs, "types", "type")); currel.addEntity(curent); } else if (lname == "association") { curent = new Entity(Entity.TYPE_ASSOCIATION, currel); String primary = getValue(attrs, "primary"); if (primary != null) curent.setPrimary(Boolean.valueOf(primary)); curent.setId(getValue(attrs, "id")); String condition = getValue(attrs, "condition"); if (condition != null) curent.setConditionValue(Values.getColumnValue(currel, condition)); curent.setAssociationType(getValue(attrs, "type")); curent.setScope(getValues(attrs, "scope")); currel.addEntity(curent); } // Identity Fields else if (lname == "subject-locator") { curfield = new Field(Field.TYPE_SUBJECT_LOCATOR, curent); curfield.setColumn(getValue(attrs, "column")); curent.addField(curfield); } else if (lname == "subject-identifier") { curfield = new Field(Field.TYPE_SUBJECT_IDENTIFIER, curent); curfield.setColumn(getValue(attrs, "column")); curent.addField(curfield); } else if (lname == "item-identifier") { curfield = new Field(Field.TYPE_ITEM_IDENTIFIER, curent); curfield.setColumn(getValue(attrs, "column")); curent.addField(curfield); } // Characteristics else if (lname == "occurrence") { curfield = new Field(Field.TYPE_OCCURRENCE, curent); curfield.setColumn(getValue(attrs, "column")); curfield.setType(getValue(attrs, "type")); curfield.setScope(getValues(attrs, "scope")); curfield.setDatatype(getValue(attrs, "datatype")); curent.addField(curfield); } else if (lname == "topic-name") { curfield = new Field(Field.TYPE_TOPIC_NAME, curent); curfield.setColumn(getValue(attrs, "column")); curfield.setType(getValue(attrs, "type")); curfield.setScope(getValues(attrs, "scope")); curent.addField(curfield); } else if (lname == "player") { curfield = new Field(Field.TYPE_PLAYER, curent); curfield.setRoleType(getValue(attrs, "rtype")); curfield.setAssociationType(getValue(attrs, "atype")); curfield.setScope(getValues(attrs, "scope")); curent.addField(curfield); } else if (lname == "other") { Field orole = new Field(Field.TYPE_ASSOCIATION_ROLE, curent); orole.setRoleType(getValue(attrs, "rtype")); orole.setPlayer(getValue(attrs, "player")); String optional = getValue(attrs, "optional"); if (optional != null) orole.setOptional(Boolean.valueOf(optional).booleanValue()); curfield.addOtherRoleField(orole); } else if (lname == "role") { curfield = new Field(Field.TYPE_ASSOCIATION_ROLE, curent); curfield.setColumn(getValue(attrs, "column")); curfield.setRoleType(getValue(attrs, "type")); curfield.setPlayer(getValue(attrs, "player")); String optional = getValue(attrs, "optional"); if (optional != null) curfield.setOptional(Boolean.valueOf(optional).booleanValue()); curent.addField(curfield); } // Virtual columns else if (lname == "mapping-column") { String colname = getValue(attrs, "name"); String inputName = getValue(attrs, "column"); curvcol = new MappingVirtualColumn(currel, colname, inputName); currel.addVirtualColumn(colname, curvcol); } else if (lname == "map") { ((MappingVirtualColumn)curvcol).addMapping(getValue(attrs, "from"), getValue(attrs, "to")); } else if (lname == "default") { ((MappingVirtualColumn)curvcol).setDefault(getValue(attrs, "to")); } // Function columns else if (lname == "function-column") { String colname = getValue(attrs, "name"); String method = getValue(attrs, "method"); curvcol = new FunctionVirtualColumn(currel, colname, method); currel.addVirtualColumn(colname, curvcol); } // Sync else if (lname == "changelog") { cursync = new Changelog(currel); cursync.setTable(getValue(attrs, "table")); cursync.setPrimaryKey(getValues(attrs, "primary-key")); cursync.setOrderColumn(getValue(attrs, "order-column")); cursync.setLocalOrderColumn(getValue(attrs, "local-order-column")); cursync.setCondition(getValue(attrs, "condition")); currel.addSync(cursync); if (currel.getSynchronizationType() == Relation.SYNCHRONIZATION_UNKNOWN) currel.setSynchronizationType(Relation.SYNCHRONIZATION_CHANGELOG); } else if (lname == "extent") { curent.addExtentQuery(getValue(attrs, "query")); } else if (lname == "expression-column") { curecol = new ExpressionVirtualColumn(getValue(attrs, "name")); cursync.addVirtualColumn(curecol); } // Prefixes else if (lname == "using") { String prefix = getValue(attrs, "prefix"); int type = Prefix.TYPE_SUBJECT_IDENTIFIER; String locator = getValue(attrs, "subject-identifier"); if (locator != null) type = Prefix.TYPE_SUBJECT_IDENTIFIER; if (locator == null) { locator = getValue(attrs, "item-identifier"); if (locator != null) type = Prefix.TYPE_ITEM_IDENTIFIER; } if (locator == null) { locator = getValue(attrs, "subject-locator"); if (locator != null) type = Prefix.TYPE_SUBJECT_LOCATOR; } iprefixes.put(prefix, new Prefix(prefix, locator, type)); } // Other else if (lname == "db2tm") { name = getValue(attrs, "name"); commitMode = getValue(attrs, "commit-mode"); } // Sources else if (lname == "sources") { } else if (lname == "csv") { String id = getValue(attrs, "id"); CSVDataSource datasource = new CSVDataSource(this); // - path datasource.setPath(getValue(attrs, "path")); // - encoding String encoding = getValue(attrs, "encoding"); if (encoding != null) datasource.setEncoding(getValue(attrs, "encoding")); // - separator String separator = getValue(attrs, "separator"); if (separator != null) datasource.setSeparator(separator.charAt(0)); // - quoting String quoting = getValue(attrs, "quoting"); if (quoting != null) datasource.setQuoteCharacter(quoting.charAt(0)); // - ignoreFirstLines String ignoreFirstLines = getValue(attrs, "ignoreFirstLines"); if (ignoreFirstLines != null) datasource.setIgnoreFirstLines(Integer.parseInt(ignoreFirstLines)); datasources.put(id, datasource); } else if (lname == "jdbc") { String id = getValue(attrs, "id"); JDBCDataSource datasource = new JDBCDataSource(this); datasource.setPropertyFile(getValue(attrs, "propfile")); datasources.put(id, datasource); } // call super super.startElement(nsuri, lname, qname, attrs); } public void endElement(String nsuri, String lname, String qname) throws SAXException { // call super super.endElement(nsuri, lname, qname); if (lname == "subject-locator") { curfield.setPattern(content.toString()); } else if (lname == "subject-identifier") { curfield.setPattern(content.toString()); } else if (lname == "item-identifier") { curfield.setPattern(content.toString()); } else if (lname == "topic-name") { curfield.setPattern(content.toString()); } else if (lname == "occurrence") { curfield.setPattern(content.toString()); } else if (lname == "relation") { currel = null; } else if (lname == "topic" || lname == "association") { curent = null; } else if (lname == "param") { ((FunctionVirtualColumn)curvcol).addParameter(content.toString()); } else if (lname == "condition") { currel.setCondition(content.toString()); } else if (lname == "mapping-column") { curvcol = null; } else if (lname == "function-column") { ((FunctionVirtualColumn)curvcol).compile(); curvcol = null; } else if (lname == "changelog") { cursync = null; } else if (lname == "expression-column") { curecol.setSQLExpression(content.toString()); curecol = null; } } // -------------------------------------------------------------------------- // Helpers // -------------------------------------------------------------------------- protected String getValue(Attributes attrs, String name) { return attrs.getValue("", name); } protected String getValue(Attributes attrs, String name, String _default) { String result = attrs.getValue("", name); return (result == null ? _default : result); } protected String[] getValues(Attributes attrs, String name) { String value = getValue(attrs, name); return (value == null) ? new String[] { } : StringUtils.tokenize(value, " \t\n\r,"); } protected String[] getValues(Attributes attrs, String plural, String singular) { String value = getValue(attrs, singular); return (value != null) ? new String[] { value } : getValues(attrs, plural); } protected void addAttribute(AttributesImpl atts, String name, String type, String value) { if (value != null) atts.addAttribute("", "", name, type, value); } protected void addAttribute(AttributesImpl atts, String name, String type, String[] values) { if (values != null) atts.addAttribute("", "", name, type, StringUtils.join(values, ",")); } // -------------------------------------------------------------------------- // Export // -------------------------------------------------------------------------- public void write(Writer writer) throws SAXException { write(writer, "utf-8"); } public void write(Writer writer, String encoding) throws SAXException { write(new PrettyPrinter(writer, encoding)); } protected void write(ContentHandler dh) throws SAXException { // initialize attributes AttributesImpl atts = new AttributesImpl(); // <db2tm name="..."> if (name != null) addAttribute(atts, "name", "CDATA", name); dh.startDocument(); dh.startElement("", "", "db2tm", atts); atts.clear(); // prefixes for (Prefix prefix : iprefixes.values()) { addAttribute(atts, "prefix", "CDATA", prefix.getId()); switch (prefix.getType()) { case Prefix.TYPE_SUBJECT_IDENTIFIER: addAttribute(atts, "subject-identifier", "CDATA", prefix.getLocator()); break; case Prefix.TYPE_ITEM_IDENTIFIER: addAttribute(atts, "item-identifier", "CDATA", prefix.getLocator()); break; case Prefix.TYPE_SUBJECT_LOCATOR: addAttribute(atts, "subject-locator", "CDATA", prefix.getLocator()); break; } dh.startElement("", "", "using", atts); atts.clear(); dh.endElement("", "", "using"); } // relations for (Relation rel : getRelations()) { // <relation> addAttribute(atts, "name", "CDATA", rel.getName()); addAttribute(atts, "columns", "CDATA", rel.getColumns()); dh.startElement("", "", "relation", atts); atts.clear(); outputEntities(rel, dh); // </relation> dh.endElement("", "", "relation"); } // </db2tm> dh.endElement("", "", "db2tm"); dh.endDocument(); } protected void outputEntities(Relation rel, ContentHandler dh) throws SAXException { AttributesImpl atts = new AttributesImpl(); for (Entity entity : rel.getEntities()) { if (entity.getEntityType() == Entity.TYPE_TOPIC) { // <topic> if (entity.getId() != null) addAttribute(atts, "id", "CDATA", entity.getId()); addAttribute(atts, "type", "CDATA", entity.getAssociationType()); dh.startElement("", "", "topic", atts); atts.clear(); outputFields(entity, dh); // </topic> dh.endElement("", "", "topic"); } else if (entity.getEntityType() == Entity.TYPE_ASSOCIATION) { // <association> if (entity.getId() != null) addAttribute(atts, "id", "CDATA", entity.getId()); addAttribute(atts, "type", "CDATA", entity.getAssociationType()); addAttribute(atts, "scope", "CDATA", entity.getScope()); dh.startElement("", "", "association", atts); atts.clear(); outputFields(entity, dh); // </association> dh.endElement("", "", "association"); } } } protected void outputFields(Entity entity, ContentHandler dh) throws SAXException { for (Field field : entity.getIdentityFields()) { outputField(field, dh); } for (Field field : entity.getRoleFields()) { outputField(field, dh); } for (Field field : entity.getCharacteristicFields()) { outputField(field, dh); } } protected void outputField(Field field, ContentHandler dh) throws SAXException { AttributesImpl atts = new AttributesImpl(); if (field.getFieldType() == Field.TYPE_SUBJECT_LOCATOR) { addAttribute(atts, "column", "CDATA", field.getColumn()); dh.startElement("", "", "subject-locator", atts); char[] c = field.getPattern().toCharArray(); dh.characters(c, 0, c.length); dh.endElement("", "", "subject-locator"); atts.clear(); } else if (field.getFieldType() == Field.TYPE_SUBJECT_IDENTIFIER) { addAttribute(atts, "column", "CDATA", field.getColumn()); dh.startElement("", "", "subject-identifier", atts); char[] c = field.getPattern().toCharArray(); dh.characters(c, 0, c.length); dh.endElement("", "", "subject-identifier"); atts.clear(); } else if (field.getFieldType() == Field.TYPE_ITEM_IDENTIFIER) { addAttribute(atts, "column", "CDATA", field.getColumn()); dh.startElement("", "", "item-identifier", atts); char[] c = field.getPattern().toCharArray(); dh.characters(c, 0, c.length); dh.endElement("", "", "item-identifier"); atts.clear(); } else if (field.getFieldType() == Field.TYPE_OCCURRENCE) { addAttribute(atts, "column", "CDATA", field.getColumn()); addAttribute(atts, "type", "CDATA", field.getType()); addAttribute(atts, "scope", "CDATA", field.getScope()); addAttribute(atts, "datatype", "CDATA", field.getDatatype()); dh.startElement("", "", "occurrence", atts); dh.endElement("", "", "occurrence"); atts.clear(); } else if (field.getFieldType() == Field.TYPE_TOPIC_NAME) { addAttribute(atts, "column", "CDATA", field.getColumn()); addAttribute(atts, "type", "CDATA", field.getType()); addAttribute(atts, "scope", "CDATA", field.getScope()); dh.startElement("", "", "topic-name", atts); dh.endElement("", "", "topic-name"); atts.clear(); } else if (field.getFieldType() == Field.TYPE_PLAYER) { addAttribute(atts, "rtype", "CDATA", field.getRoleType()); addAttribute(atts, "atype", "CDATA", field.getAssociationType()); addAttribute(atts, "scope", "CDATA", field.getScope()); dh.startElement("", "", "player", atts); atts.clear(); for (Field orole : field.getOtherRoleFields()) { addAttribute(atts, "rtype", "CDATA", orole.getRoleType()); addAttribute(atts, "player", "CDATA", orole.getPlayer()); dh.startElement("", "", "other", atts); dh.endElement("", "", "other"); atts.clear(); } dh.endElement("", "", "player"); atts.clear(); } else if (field.getFieldType() == Field.TYPE_ASSOCIATION_ROLE) { addAttribute(atts, "type", "CDATA", field.getRoleType()); addAttribute(atts, "player", "CDATA", field.getPlayer()); dh.startElement("", "", "role", atts); dh.endElement("", "", "role"); atts.clear(); } else throw new OntopiaRuntimeException("Unknown field type: " + field.getType()); } public String toString() { return "RelationMapping(" + getName() + ")"; } }