/* * The contents of this file are subject to the Mozilla Public License * Version 1.1 (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.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * The Original Code is the Kowari Metadata Store. * * The Initial Developer of the Original Code is Plugged In Software Pty * Ltd (http://www.pisoftware.com, mailto:info@pisoftware.com). Portions * created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002 * Northrop Grumman Corporation. All Rights Reserved. * * This file is an original work and contains no Original Code. It was * developed by Netymon Pty Ltd under contract to the Australian * Commonwealth Government, Defense Science and Technology Organisation * under contract #4500507038 and is contributed back to the Kowari/Mulgara * Project as per clauses 4.1.3 and 4.1.4 of the above contract. * * Contributor(s): N/A. * * Copyright: * The copyright on this file is held by: * The Australian Commonwealth Government * Department of Defense * Developed by Netymon Pty Ltd * Copyright (C) 2006 * The Australian Commonwealth Government * Department of Defense * * [NOTE: The text of this Exhibit A may differ slightly from the text * of the notices in the Source Code files of the Original Code. You * should use the text of this Exhibit A rather than the text found in the * Original Code Source Code for Your Modifications.] * */ package org.mulgara.resolver.relational; // Java 2 standard packages import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.Properties; import java.util.List; import java.util.Map; import java.util.Iterator; // Third party packages import org.apache.log4j.Logger; // Apache Log4J import org.jrdf.graph.URIReference; import org.jrdf.graph.Literal; // Locally written packages import org.mulgara.query.*; import org.mulgara.resolver.spi.EmptyResolution; import org.mulgara.resolver.spi.LocalizedTuples; import org.mulgara.resolver.spi.Resolution; import org.mulgara.resolver.spi.ResolverSession; import org.mulgara.store.tuples.Tuples; import org.mulgara.store.tuples.TuplesOperations; import org.mulgara.store.tuples.AbstractTuples; import org.mulgara.store.tuples.Annotation; import org.mulgara.store.tuples.RowComparator; import org.mulgara.resolver.relational.d2rq.Definition; import org.mulgara.resolver.relational.d2rq.AdditionalPropertyElem; import org.mulgara.resolver.relational.d2rq.ClassMapElem; import org.mulgara.resolver.relational.d2rq.PropertyBridgeElem; import org.mulgara.resolver.relational.d2rq.ObjectPropertyBridgeElem; import org.mulgara.resolver.relational.d2rq.DatatypePropertyBridgeElem; /** * Resolution of a query against a relational database. * * @author Andrae Muys * * @version $Revision: 1.1.1.1 $ * * @modified $Date: 2005/10/30 19:21:14 $ @maintenanceAuthor $Author: prototypo $ * * @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a> */ public class RelationalResolution extends AbstractTuples implements Resolution { /** Logger */ private static final Logger logger = Logger.getLogger(RelationalResolution.class); /** The constraint this instance resolves */ private final RelationalConstraint constraint; private Definition defn; private Tuples result; private Connection conn; private Variable[] variables; private int[] refCount; private ResolverSession resolverSession; private int[] columnMapping; private long cachedCount = -1; /** * @param constraint the constraint to resolver, never <code>null</code> * @param result the result of the resolution. * @throws IllegalArgumentException if the <var>constraint</var> or * <var>result</var> are <code>null</code> */ RelationalResolution(RelationalConstraint constraint, Definition defn, ResolverSession resolverSession) throws TuplesException { // Validate parameters if (constraint == null) { throw new IllegalArgumentException( "Null 'constraint' parameter"); } else if (defn == null) { throw new IllegalArgumentException("Null 'defn' parameter"); } if (logger.isInfoEnabled()) { logger.info("Resolving constraint: " + constraint); } try { this.defn = defn; this.constraint = constraint; this.conn = obtainConnection(defn); this.result = null; this.variables = (Variable[])constraint.getVariables().toArray(new Variable[] {}); this.columnMapping = new int[variables.length]; this.refCount = new int[] { 1 }; this.resolverSession = resolverSession; } catch (SQLException es) { throw new TuplesException("Unable to connect to relational database", es); } } public Variable[] getVariables() { return variables; } public long getRowCount() throws TuplesException { if (cachedCount == -1) { if (result == null) beforeFirst(); cachedCount = result.getRowCount(); } return cachedCount; } public long getRowUpperBound() throws TuplesException { // !!FIXME logger.info("Need to provide better estimate of upperbound for RelationalResolution"); // However this is a remote SQL query, so extremely expensive to reevalutate, so if we are // going to be rebinding variables we want to rebind as many as possible to minimise the amound of // data transferred. OTOH, we also would like to minimise the number of individual queries, I'm // not sure how we judge this tradeoff. On the left of a join, we have exactly one query returning // results that will be heavily filtered, on the right multiple queries returning pre-filtered results. // // My intuition tells me that we want to be as far right as possible, so maybe this is the optimal // solution.. return cachedCount == -1 ? Long.MAX_VALUE : cachedCount; } public long getRowExpectedCount() throws TuplesException { return cachedCount == -1 ? Long.MAX_VALUE : cachedCount; } public int getRowCardinality() throws TuplesException { return Cursor.MANY; } public int getColumnIndex(Variable variable) throws TuplesException { for (int i = 0; i < variables.length; i++) { if (variables[i].equals(variable)) { return i; } } throw new TuplesException("Variable " + variable + " not found in " + AbstractTuples.toString(variables)); } public boolean isColumnEverUnbound(int column) { return false; // No non-union compatible appends here. } public boolean isMaterialized() { if (result != null) { return result.isMaterialized(); } else { return false; } } public boolean isUnconstrained() throws TuplesException { if (result != null) { return result.isUnconstrained(); } else { return false; } } public boolean hasNoDuplicates() { return true; } public RowComparator getComparator() { if (result != null) { return result.getComparator(); } else { return null; // Should I be reporting sorted here, as it will be. } } public List<Tuples> getOperands() { return Collections.emptyList(); } public void beforeFirst() throws TuplesException { beforeFirst(NO_PREFIX, 0); } public long getColumnValue(int column) throws TuplesException { if (result == null) { throw new TuplesException("beforeFirst not called in RelationalResolution"); } return result.getColumnValue(columnMapping[column]); } public void renameVariables(Constraint constraint) { throw new IllegalStateException("Don't want to think about this yet"); } public Object clone() { RelationalResolution r = (RelationalResolution)super.clone(); r.refCount[0]++; if (r.result != null) { r.result = (Tuples)result.clone(); } return r; } public boolean equals(Object object) { if (object == this) { return true; } if (object != null && this.getClass() == object.getClass()) { RelationalResolution lhs = (RelationalResolution)object; return lhs.defn == this.defn && lhs.constraint == this.constraint; } return false; } public int hashCode() { return defn.hashCode() * 7; } public Constraint getConstraint() { return constraint; } public boolean isComplete() { return false; } public Annotation getAnnotation(Class<? extends Annotation> annotationClass) throws TuplesException { return null; } public void close() throws TuplesException { if (result != null) { result.close(); result = null; } refCount[0]--; try { if (refCount[0] == 0) { conn.close(); } } catch (SQLException es) { throw new TuplesException("Error closing connection", es); } } public void beforeFirst(long[] prefix, int suffix) throws TuplesException { if (suffix != 0) { throw new TuplesException("RelationalResolution does not support suffix != 0"); } Iterator<ConstraintExpression> i = constraint.getRdfTypeConstraints().iterator(); if (!i.hasNext()) { this.result = new EmptyResolution(constraint, true); } else { List<Tuples> result = new ArrayList<Tuples>(); try { while (i.hasNext()) { Constraint head = (Constraint)i.next(); result.add(resolveInstance(head, constraint, conn, defn)); } this.result = null; this.result = TuplesOperations.join(result); close(result.toArray(new Tuples[result.size()])); } catch (TuplesException e) { if (this.result == null) { // A non-null result means that the exception occured during the close try { close(result.toArray(new Tuples[result.size()])); } catch (TuplesException e2) { /* Already throwing an exception */ } } throw e; } } // It is possible that the join may have no variables but only if the query returns empty. if (!this.result.isEmpty()) { Variable[] local = this.getVariables(); Variable[] join = this.result.getVariables(); for (int a = 0; a < local.length; a++) { this.columnMapping[a] = -1; for (int b = 0; b < join.length; b++) { if (local[a].equals(join[b])) { this.columnMapping[a] = b; } } if (this.columnMapping[a] == -1) { throw new TuplesException("Error mapping variables to join"); } } } this.result.beforeFirst(prefix, suffix); } public boolean next() throws TuplesException { if (this.result == null) { throw new TuplesException("beforeFirst() not called on RelationalResolution"); } return this.result.next(); } private Connection obtainConnection(Definition defn) throws SQLException, TuplesException { if (defn.databaseDefn.jdbcDriver == null) { throw new IllegalArgumentException("defn.jdbcDriver cannot be null"); } else if (defn.databaseDefn.jdbcDSN == null) { throw new IllegalArgumentException("defn.jdbcDSN cannot be null"); } try { Class.forName(defn.databaseDefn.jdbcDriver); } catch (ClassNotFoundException ec) { throw new TuplesException("Couldn't find Driver", ec); } Properties properties = new Properties(); if (defn.databaseDefn.username != null) { properties.setProperty("user", defn.databaseDefn.username); } if (defn.databaseDefn.password != null) { properties.setProperty("password", defn.databaseDefn.password); } return DriverManager.getConnection(defn.databaseDefn.jdbcDSN, properties); } private Tuples resolveInstance(Constraint head, RelationalConstraint constraint, Connection conn, Definition defn) throws TuplesException { RelationalQuery query = new RelationalQuery(); ConstraintElement subj = head.getElement(0); ConstraintElement obj = head.getElement(2); if (!(obj instanceof URIReference)) { throw new TuplesException("rdf:type not URIReference:" + obj + " :: " + obj.getClass()); } // Basic query for instances of class. ClassMapElem classMap = (ClassMapElem)defn.classMaps.get(obj.toString()); if (classMap == null) { throw new TuplesException("ClassMap not found for type: " + obj.toString()); } includeInstanceQuery(query, subj, classMap); // Include properties List<Tuples> additionalProperties = new ArrayList<Tuples>(); Map<String,? extends PropertyBridgeElem> propBs = defn.objPropBridges.get(classMap.klass); Map<String,? extends PropertyBridgeElem> dataBs = defn.dataPropBridges.get(classMap.klass); List<Constraint> constraints = constraint.getConstraintsBySubject(subj); for (Constraint c: constraints) { ConstraintElement pred = c.getElement(1); if (pred instanceof Variable) { includeVariablePropertyBridge(query, (Variable)pred, c, propBs, dataBs); } else { includePropertyBridge(query, c, propBs); includePropertyBridge(query, c, dataBs); // If we find a matching additional property then save it for distribution over the result. for (AdditionalPropertyElem ape: classMap.additionalProperties) { if (ape.name.equals(c.getElement(1).toString())) { if (c.getElement(2) instanceof Variable) { additionalProperties.add(TuplesOperations.assign((Variable)c.getElement(2), ape.valueNode)); } else if (c.getElement(2) instanceof URIReference) { if (!ape.value.equals(c.getElement(2).toString())) { // Additional Property match failed, so entire constraint will fail. Short circuit. return TuplesOperations.empty(); } } else if (c.getElement(2) instanceof Literal) { if (!ape.value.equals(((Literal)c.getElement(2)).getLexicalForm())) { return TuplesOperations.empty(); } } else { // This shouldn't happen. logger.warn("Object in constraint not variable, uri, or literal: " + c); continue; } } } } } Answer answer; answer = new RelationalAnswer(query, conn); Tuples lt = null, st = null; try { lt = new LocalizedTuples(resolverSession, answer, false); } catch (IllegalArgumentException e) { try { answer.close(); } catch (TuplesException e2) { /* Already throwing an exception. Ignore. */ } throw e; } answer.close(); try { st = TuplesOperations.sort(lt); } catch (TuplesException e) { try { lt.close(); } catch (TuplesException e2) { /* Already throwing an exception. Ignore. */ } throw e; } lt.close(); // Combine result with additional properties via join. additionalProperties.add(st); Tuples jt = null; try { jt = TuplesOperations.join(additionalProperties); } catch (TuplesException e) { try { st.close(); } catch (TuplesException e2) { /* Already throwing an exception. Ignore. */ } throw e; } st.close(); return jt; } private void includeInstanceQuery(RelationalQuery query, ConstraintElement instance, ClassMapElem classMap) throws TuplesException { if (instance instanceof Variable) { VariableDesc desc; if (classMap.uriColumn != null) { desc = new ColumnDesc(classMap, defn.databaseDefn.dbType); } else if (classMap.uriPattern != null) { desc = new PatternDesc(classMap, defn.databaseDefn.dbType); } else if (classMap.bNodeIdColumns != null) { desc = new BNodeDesc(classMap); } else { throw new TuplesException("Unknown class map definition type (not column or uripattern)"); } if (desc.getTables().size() != 1) { throw new TuplesException("Error in PK definition: " + classMap + " - " + desc); } query.addTables(desc.getTables()); for (String c: desc.getColumns()) { desc.assignColumnIndex(c, query.addColumn(c)); } query.addVariable((Variable)instance, desc); for (String c: classMap.condition) { query.addRestriction(c); } } else { // subj !instanceof Variable VariableDesc desc; if (classMap.uriColumn != null) { desc = new ColumnDesc(classMap, defn.databaseDefn.dbType); } else if (classMap.uriPattern != null) { desc = new PatternDesc(classMap, defn.databaseDefn.dbType); } else { throw new TuplesException("Unknown class map definition type (not column or uripattern)"); } if (desc.getTables().size() != 1) { throw new TuplesException("Error in PK definition: " + classMap + " - " + desc); } query.addTables(desc.getTables()); query.addRestriction(desc.restrict(instance.toString())); for (String r: classMap.condition) { query.addRestriction(r); } } if(classMap.containsDuplicates) { query.makeDistinct(); } } private void includePropertyBridge(RelationalQuery query, Constraint c, Map<String,? extends PropertyBridgeElem> propBs) throws TuplesException { ConstraintElement pred = c.getElement(1); PropertyBridgeElem propB = propBs.get(pred.toString()); if (propB != null) { if (propB instanceof ObjectPropertyBridgeElem) { includeObjectPropertyBridge(query, c.getElement(2), (ObjectPropertyBridgeElem)propB); } else if (propB instanceof DatatypePropertyBridgeElem) { includeDatatypePropertyBridge(query, c.getElement(2), (DatatypePropertyBridgeElem)propB); } else { throw new TuplesException("Unknown propertybridge type"); } for (String r: propB.condition) { query.addRestriction(r); } for (String join: propB.join) { query.addTables(RelationalResolver.extractTablesFromJoin(join)); query.addRestriction(join); } } } private void includeVariablePropertyBridge(RelationalQuery query, Variable p, Constraint c, Map<String,? extends PropertyBridgeElem> propBs, Map<String,? extends PropertyBridgeElem> dataBs) throws TuplesException { PropertyBridgeElem defn = propBs.values().iterator().next(); LiteralDesc predDesc = new LiteralDesc(defn, p); query.addVariable(p, predDesc); ConstraintElement obj = c.getElement(2); for (Map.Entry<String,? extends PropertyBridgeElem> entry: propBs.entrySet()) { String predicate = entry.getKey(); PropertyBridgeElem propB = entry.getValue(); VariableDesc vdesc = obtainDescForVariableProperty(propB); if (vdesc != null) { query.addUnionCase(predDesc, new UnionCase(predDesc, predicate, vdesc, obj)); } } for (Map.Entry<String,? extends PropertyBridgeElem> entry: dataBs.entrySet()) { String predicate = entry.getKey(); PropertyBridgeElem pb = entry.getValue(); VariableDesc vdesc = obtainDescForVariableProperty(pb); if (vdesc != null) { query.addUnionCase(predDesc, new UnionCase(predDesc, predicate, vdesc, obj)); } } } private VariableDesc obtainDescForVariableProperty(PropertyBridgeElem propB) throws TuplesException { if (propB instanceof ObjectPropertyBridgeElem) { ObjectPropertyBridgeElem ob = (ObjectPropertyBridgeElem)propB; if (ob.column != null) { return new ColumnDesc(ob, defn.databaseDefn.dbType); } else if (ob.refersToClassMap != null) { VariableDesc cdesc; if (ob.refersToClassMap.uriColumn != null) { cdesc = new ColumnDesc(ob.refersToClassMap, defn.databaseDefn.dbType); } else if (ob.refersToClassMap.uriPattern != null) { cdesc = new PatternDesc(ob.refersToClassMap, defn.databaseDefn.dbType); } else if (ob.refersToClassMap.bNodeIdColumns != null) { return null; // ignore blanknodes until we figure out their interraction with restrict. } else { throw new TuplesException("Unknown class map definition type (not column or uripattern)"); } cdesc.addJoin(propB.join); cdesc.addCondition(propB.condition); return cdesc; } else if (ob.pattern != null) { return new PatternDesc(ob, defn.databaseDefn.dbType); } else { throw new TuplesException("Unknown object property bridge type: " + propB); } } else if (propB instanceof DatatypePropertyBridgeElem) { DatatypePropertyBridgeElem db = (DatatypePropertyBridgeElem)propB; if (db.column != null) { return new ColumnDesc(db, defn.databaseDefn.dbType); } else if (db.pattern != null) { return new PatternDesc(db, defn.databaseDefn.dbType); } else { throw new TuplesException("Unknown datatype property bridge type: " + propB); } } else { throw new TuplesException("Unknown propertybridge type"); } } private void includeObjectPropertyBridge(RelationalQuery query, ConstraintElement obj, ObjectPropertyBridgeElem propB) throws TuplesException { if (propB != null) { if (propB.column != null) { includeColumnBasedProperty(query, new ColumnDesc(propB, defn.databaseDefn.dbType), obj); } else if (propB.refersToClassMap != null) { includeInstanceQuery(query, obj, propB.refersToClassMap); } else if (propB.pattern != null) { includePatternBasedProperty(query, new PatternDesc(propB, defn.databaseDefn.dbType), obj); } else { throw new TuplesException("Unknown object property bridge type: " + propB); } } } private void includeDatatypePropertyBridge(RelationalQuery query, ConstraintElement obj, DatatypePropertyBridgeElem propB) throws TuplesException { if (propB != null) { if (propB.column != null) { includeColumnBasedProperty(query, new ColumnDesc(propB, defn.databaseDefn.dbType), obj); } else if (propB.pattern != null) { includePatternBasedProperty(query, new PatternDesc(propB, defn.databaseDefn.dbType), obj); } else { throw new TuplesException("Unknown datatype property bridge type: " + propB); } } } private void includeColumnBasedProperty(RelationalQuery query, ColumnDesc desc, ConstraintElement obj) throws TuplesException { query.addTable(desc.getTable()); if (obj instanceof Variable) { // * desc.assignColumnIndex(desc.getColumn(), query.addColumn(desc.getColumn())); query.addVariable((Variable)obj, desc); } else if (obj instanceof URIReference) { query.addRestriction(desc.restrict(obj.toString())); } else if (obj instanceof Literal) { // FIXME: handle datatypes by abstracting anticipatedClass, this can eliminate the above test for uri's as well. query.addRestriction(desc.restrict(((Literal)obj).getLexicalForm())); } else { throw new TuplesException("Unsupported object type: " + obj); } } private void includePatternBasedProperty(RelationalQuery query, PatternDesc desc, ConstraintElement obj) throws TuplesException { if (obj instanceof Variable) { Variable v = (Variable)obj; if (desc.getTables().size() != 1) { throw new TuplesException("Error in property definition: " + desc); } query.addTables(desc.getTables()); for (String c: desc.getColumns()) { desc.assignColumnIndex(c, query.addColumn(c)); } query.addVariable(v, desc); } else if (obj instanceof URIReference) { query.addRestriction(desc.restrict(obj.toString())); } else if (obj instanceof Literal) { query.addRestriction(desc.restrict(((Literal)obj).getLexicalForm())); } else { throw new TuplesException("Unsupported object type: " + obj); } } }