diff --git a/openjpa-jdbc-5/src/main/java/org/apache/openjpa/jdbc/meta/XMLMappingRepository.java b/openjpa-jdbc-5/src/main/java/org/apache/openjpa/jdbc/meta/XMLMappingRepository.java new file mode 100644 index 000000000..6352505bd --- /dev/null +++ b/openjpa-jdbc-5/src/main/java/org/apache/openjpa/jdbc/meta/XMLMappingRepository.java @@ -0,0 +1,82 @@ +/* + * 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.meta; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.openjpa.meta.FieldMetaData; +import org.apache.openjpa.meta.XMLClassMetaData; +import org.apache.openjpa.meta.XMLMapping; + +/** + * Repository of object/relational mapping information. + * (extended to include XML mapping metadata for XML columns) + * + * @author Catalina Wei + * @since 1.0.0 + */ +public class XMLMappingRepository extends MappingRepository { + // xml mapping + protected final XMLMapping[] EMPTY_XMLMETAS; + private final Map _xmlmetas = new HashMap(); + + public XMLMappingRepository() { + super(); + EMPTY_XMLMETAS = newXMLClassMetaDataArray(0); + } + + public synchronized XMLClassMetaData addXMLClassMetaData(FieldMetaData fmd, + String name) { + XMLClassMetaData meta = newXMLClassMetaData(fmd, name); + addXMLClassMetaData(fmd.getDeclaredType(), meta); + return meta; + } + + public XMLMapping getXMLClassMetaData(Class cls) { + synchronized(_xmlmetas) { + if (_xmlmetas.isEmpty()) + return null; + else + return (XMLClassMetaData) _xmlmetas.get(cls); + } + } + + public XMLMapping getXMLMetaData(FieldMetaData fmd) { + XMLMapping xmlmeta = null; + if (XMLClassMetaData.isXMLMapping(fmd.getDeclaredType())) { + xmlmeta = getXMLClassMetaData(fmd.getDeclaredType()); + if (xmlmeta == null) + xmlmeta = addXMLClassMetaData(fmd, fmd.getName()); + } + return xmlmeta; + } + + public synchronized void addXMLClassMetaData(Class cls, XMLMapping meta) { + _xmlmetas.put(cls, meta); + } + + protected XMLClassMetaData newXMLClassMetaData(FieldMetaData fmd, String name) { + return new XMLClassMetaData(fmd.getDeclaredType(), name, this); + } + + protected XMLMapping[] newXMLClassMetaDataArray(int length) { + return new XMLClassMetaData[length]; + } +} diff --git a/openjpa-jdbc-5/src/main/java/org/apache/openjpa/meta/XMLClassMetaData.java b/openjpa-jdbc-5/src/main/java/org/apache/openjpa/meta/XMLClassMetaData.java new file mode 100644 index 000000000..0037f24a1 --- /dev/null +++ b/openjpa-jdbc-5/src/main/java/org/apache/openjpa/meta/XMLClassMetaData.java @@ -0,0 +1,227 @@ +/* + * 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.meta; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Member; +import java.lang.reflect.Field; +import java.util.HashMap; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; + +import org.apache.openjpa.jdbc.meta.XMLMappingRepository; +import org.apache.openjpa.meta.JavaTypes; +import org.apache.openjpa.meta.XMLMapping; +import org.apache.openjpa.meta.XMLMetaData; +import org.apache.commons.lang.StringUtils; + +/** + * Contains metadata about a persistent field that maps to an xml column. + * This metadata is loaded at runtime when query involves predicates + * that navigate through xpath. + * + * @author Catalina Wei + * @since 1.0.0 + */ +public class XMLClassMetaData implements XMLMapping +{ + private Class _type; + private int _code = JavaTypes.OBJECT; + private int _xmltype = XMLTYPE; + private String _name = null; + private String _xmlname = null; + private String _xmlnamespace = null; + private boolean _isXMLRootElement = false; + private HashMap _fieldMap = new HashMap(); + + /** + * Constructor. + * + * @param type the class that contains XmlType annotation. + * @name the persistent field name that maps to xml column + * @param repo the meta repository. + */ + public XMLClassMetaData(Class type, String name, XMLMappingRepository repos) { + _type = type; + _isXMLRootElement = _type.getAnnotation(XmlRootElement.class) != null; + if (_isXMLRootElement) { + _xmlname = ((XmlRootElement) _type.getAnnotation + (XmlRootElement.class)).name(); + _xmlnamespace = ((XmlRootElement) _type.getAnnotation + (XmlRootElement.class)).namespace(); + } + else { + _xmlname = ((XmlType) _type.getAnnotation + (XmlType.class)).name(); + _xmlnamespace = ((XmlType) _type.getAnnotation + (XmlType.class)).namespace(); + _name = name; + } + populateFromReflection(_type, repos); + } + + /** + * Constructor. Supply described type and repository. + * + * @param type the class that contains XmlType annotation. + * @param repo the meta repository. + */ + protected XMLClassMetaData(Class type, XMLMappingRepository repos) { + _type = type; + _isXMLRootElement = _type.getAnnotation(XmlRootElement.class) != null; + if (_isXMLRootElement) { + _xmlname = ((XmlRootElement) _type.getAnnotation + (XmlRootElement.class)).name(); + _xmlnamespace = ((XmlRootElement) _type.getAnnotation + (XmlRootElement.class)).namespace(); + } + else { + _xmlname = ((XmlType) _type.getAnnotation + (XmlType.class)).name(); + _xmlnamespace = ((XmlType) _type.getAnnotation + (XmlType.class)).namespace(); + } + populateFromReflection(_type, repos); + repos.addXMLClassMetaData(type, this); + } + + /** + * Given a class type return true if XmlType annotation exists + * @param type + * @return true if XmlType annotation is present else false. + */ + public static boolean isXMLMapping(Class type) { + return type.isAnnotationPresent(XmlType.class); + } + + public void setName(String name) { + _name = name; + } + + public String getName() { + return _name; + } + + public void setXmlname(String name) { + _xmlname = name; + } + + public String getXmlname() { + return _isXMLRootElement ? null : _xmlname; + } + + public void setXmlnamespace(String name) { + // avoid JAXB XML bind default name + if (!StringUtils.equals(defaultName, name)) + _xmlnamespace = name; + } + + public String getXmlnamespace() { + return _xmlnamespace; + } + + public boolean isXmlRootElement() { + return _isXMLRootElement; + } + + public boolean isXmlElement() { + return false; + } + + public boolean isXmlAttribute() { + return false; + } + + public XMLMapping getFieldMapping(String name) { + return (XMLMapping) _fieldMap.get(name); + } + + public void setType(Class type) { + _type = type; + } + + public Class getType() { + return _type; + } + + public int getTypeCode() { + return _code; + } + + public void setXmltype(int type) { + _xmltype = type; + } + public int getXmltype() { + return _xmltype; + } + + private synchronized void populateFromReflection(Class cls, + XMLMappingRepository repos) { + Member[] members; + if (((XmlAccessorType)cls.getAnnotation(XmlAccessorType.class)).value() + == XmlAccessType.FIELD) + members = cls.getDeclaredFields(); + else + members = cls.getDeclaredMethods(); + for (int i = 0; i < members.length; i++) { + Member member = members[i]; + AnnotatedElement el = (AnnotatedElement) member; + XMLMapping field = null; + if (el.getAnnotation(XmlElement.class) != null) { + String xmlname = el.getAnnotation(XmlElement.class).name(); + // avoid JAXB XML bind default name + if (StringUtils.equals(defaultName, xmlname)) + xmlname = member.getName(); + if (((Field) member).getType(). + isAnnotationPresent(XmlType.class)) { + field = new XMLClassMetaData(((Field) member).getType(), + repos); + field.setXmltype(XMLTYPE); + field.setXmlname(xmlname); + } + else { + field = new XMLMetaData(); + field.setXmltype(ELEMENT); + field.setXmlname(xmlname); + field.setXmlnamespace(el.getAnnotation(XmlElement.class) + .namespace()); + } + } + else if (el.getAnnotation(XmlAttribute.class) != null) { + field = new XMLMetaData(); + field.setXmltype(XMLMetaData.ATTRIBUTE); + String xmlname = el.getAnnotation(XmlAttribute.class).name(); + // avoid JAXB XML bind default name + if (StringUtils.equals(defaultName, xmlname)) + xmlname = member.getName(); + field.setXmlname("@"+xmlname); + field.setXmlnamespace(el.getAnnotation(XmlAttribute.class) + .namespace()); + } + field.setName(member.getName()); + field.setType(((Field) member).getType()); + _fieldMap.put(member.getName(), field); + } + } +} diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/AbstractVal.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/AbstractVal.java index af6c2067b..7445b99ed 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/AbstractVal.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/AbstractVal.java @@ -41,6 +41,10 @@ abstract class AbstractVal return false; } + public boolean isXPath() { + return false; + } + public Object toDataStoreValue(Select sel, ExpContext ctx, ExpState state, Object val) { return val; diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/ConstPath.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/ConstPath.java index 549cf253b..a9caf53c2 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/ConstPath.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/ConstPath.java @@ -31,6 +31,7 @@ import org.apache.openjpa.kernel.OpenJPAStateManager; import org.apache.openjpa.kernel.exps.ExpressionVisitor; import org.apache.openjpa.meta.ClassMetaData; import org.apache.openjpa.meta.FieldMetaData; +import org.apache.openjpa.meta.XMLMapping; import org.apache.openjpa.util.InternalException; /** @@ -204,4 +205,14 @@ class ConstPath this.constantState = constantState; } } + + public void get(FieldMetaData fmd, XMLMapping meta) { + } + + public void get(XMLMapping meta, String name) { + } + + public XMLMapping getXmlMapping() { + return null; + } } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/EndsWithExpression.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/EndsWithExpression.java index 904f1eed2..76eea6413 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/EndsWithExpression.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/EndsWithExpression.java @@ -28,6 +28,7 @@ import org.apache.openjpa.jdbc.sql.DBDictionary; import org.apache.openjpa.jdbc.sql.SQLBuffer; import org.apache.openjpa.jdbc.sql.Select; import org.apache.openjpa.kernel.exps.ExpressionVisitor; +import org.apache.openjpa.meta.XMLMapping; /** * Test if one string ends with another. @@ -194,5 +195,13 @@ class EndsWithExpression public FieldMapping getFieldMapping() { return null; } + + public PCPath getXPath() { + return null; + } + + public XMLMapping getXmlMapping() { + return null; + } } } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/FilterValue.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/FilterValue.java index ab3ad6b39..a4e797edd 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/FilterValue.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/FilterValue.java @@ -23,6 +23,7 @@ import org.apache.openjpa.jdbc.meta.FieldMapping; import org.apache.openjpa.jdbc.schema.Column; import org.apache.openjpa.jdbc.schema.Table; import org.apache.openjpa.jdbc.sql.SQLBuffer; +import org.apache.openjpa.meta.XMLMapping; /** * The simplified public view of any non-operator in a query filter, @@ -103,4 +104,16 @@ public interface FilterValue { * return null. */ public FieldMapping getFieldMapping(); + + /** + * If this is an XPath, return it, + * else return null; + */ + public PCPath getXPath(); + + /** + * If this is an XPath, return XML mapping metadata, + * else return null; + */ + public XMLMapping getXmlMapping(); } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/FilterValueImpl.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/FilterValueImpl.java index 563d226e8..bdc84db9e 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/FilterValueImpl.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/FilterValueImpl.java @@ -24,6 +24,7 @@ import org.apache.openjpa.jdbc.schema.Column; import org.apache.openjpa.jdbc.schema.Table; import org.apache.openjpa.jdbc.sql.SQLBuffer; import org.apache.openjpa.jdbc.sql.Select; +import org.apache.openjpa.meta.XMLMapping; /** * Implementation of {@link FilterValue} that wraps a {@link Val}. @@ -98,4 +99,15 @@ class FilterValueImpl public FieldMapping getFieldMapping() { return (isPath()) ? ((PCPath) _val).getFieldMapping(_state) : null; } + + public PCPath getXPath() { + if (isPath() && ((PCPath) _val).isXPath()) + return (PCPath) _val; + else + return null; + } + + public XMLMapping getXmlMapping() { + return (getXPath() == null) ? null : getXPath().getXmlMapping(); + } } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/PCPath.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/PCPath.java index 2e8095198..47f721aef 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/PCPath.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/PCPath.java @@ -40,6 +40,7 @@ import org.apache.openjpa.lib.util.Localizer; import org.apache.openjpa.meta.ClassMetaData; import org.apache.openjpa.meta.FieldMetaData; import org.apache.openjpa.meta.JavaTypes; +import org.apache.openjpa.meta.XMLMapping; import org.apache.openjpa.util.UserException; /** @@ -55,6 +56,7 @@ class PCPath private static final int BOUND_VAR = 1; private static final int UNBOUND_VAR = 2; private static final int UNACCESSED_VAR = 3; + private static final int XPATH = 4; private static final Localizer _loc = Localizer.forPackage(PCPath.class); @@ -66,6 +68,7 @@ class PCPath private String _varName = null; private Class _cast = null; private boolean _cid = false; + private FieldMetaData _xmlfield = null; /** * Return a path starting with the 'this' ptr. @@ -168,7 +171,40 @@ class PCPath public boolean isKey() { return _key; } - + + public boolean isXPath() { + return _type == XPATH; + } + + public String getXPath() { + StringBuffer xpath = new StringBuffer(); + Action action; + Iterator itr = _actions.iterator(); + + // Skip variable actions since they are not part of the xpath + // until we reach the first xpath action. + // The first xpath action maps to the root of an xml document. + do + action = (Action) itr.next(); + while (action.op != Action.GET_XPATH); + + // Skip XmlRootElement: + // We can't rely on the accuracy of the name of the root element, + // because it could be set to some default by JAXB XML Binding. + // The caller(DBDictionary) should start with "/*" or "/*/", + // we build the remaining xpath that follows the root element. + while (itr.hasNext()) { + action = (Action) itr.next(); + if (((XMLMapping) action.data).getXmlname() != null) + xpath.append(((XMLMapping) action.data).getXmlname()); + else + xpath.append("*"); + if (itr.hasNext()) + xpath.append("/"); + } + return xpath.toString(); + } + public String getPath() { if (_actions == null) return (_varName == null) ? "" : _varName + "."; @@ -274,6 +310,36 @@ class PCPath _cast = null; _key = false; } + + public void get(FieldMetaData fmd, XMLMapping meta) { + if (_actions == null) + _actions = new LinkedList(); + Action action = new Action(); + action.op = Action.GET_XPATH; + action.data = meta; + _actions.add(action); + _cast = null; + _key = false;; + _type = XPATH; + _xmlfield = fmd; + } + + public void get(XMLMapping meta, String name) { + Action action = new Action(); + action.op = Action.GET_XPATH; + action.data = meta.getFieldMapping(name); + _actions.add(action); + _cast = null; + _key = false;; + _type = XPATH; + } + + public XMLMapping getXmlMapping() { + Action act = (Action) _actions.getLast(); + if (act != null) + return (XMLMapping) act.data; + return null; + } public synchronized void getKey() { if (_cid) @@ -288,7 +354,8 @@ class PCPath public FieldMetaData last() { Action act = lastFieldAction(); - return (act == null) ? null : (FieldMetaData) act.data; + return (act == null) ? null : isXPath() ? _xmlfield : + (FieldMetaData) act.data; } /** @@ -298,6 +365,9 @@ class PCPath if (_actions == null) return null; + if (isXPath()) + return (Action) _actions.getLast(); + ListIterator itr = _actions.listIterator(_actions.size()); Action prev; while (itr.hasPrevious()) { @@ -313,6 +383,9 @@ class PCPath if (_cast != null) return _cast; Action act = lastFieldAction(); + if (act != null && act.op == Action.GET_XPATH) + return ((XMLMapping) act.data).getType(); + FieldMetaData fld = (act == null) ? null : (FieldMetaData) act.data; boolean key = act != null && act.op == Action.GET_KEY; if (fld != null) { @@ -373,7 +446,8 @@ class PCPath rel.getTable()); } else { // move past the previous field, if any - field = (FieldMapping) action.data; + field = (action.op == Action.GET_XPATH) ? (FieldMapping) _xmlfield : + (FieldMapping) action.data; if (pstate.field != null) { // if this is the second-to-last field and the last is // the related field this field joins to, no need to @@ -416,6 +490,9 @@ class PCPath from = from.getJoinablePCSuperclassMapping()) pstate.joins = from.joinSuperclass(pstate.joins, false); } + // nothing more to do from here on as we encountered an xpath action + if (action.op == Action.GET_XPATH) + break; } } if (_varName != null) @@ -534,6 +611,8 @@ class PCPath PathExpState pstate = (PathExpState) state; FieldMapping field = (pstate.cmpfield != null) ? pstate.cmpfield : pstate.field; + if (isXPath()) + return val; if (field != null) { if (_key) return field.toKeyDataStoreValue(val, ctx.store); @@ -639,6 +718,9 @@ class PCPath // (e.g., during a bulk update) if (sel == null) sql.append(col.getName()); + else if (_type == XPATH) + // if this is an xpath, append xpath string + sql.append(getXPath()); else sql.append(sel.getColumnAlias(col, state.joins)); } @@ -716,6 +798,7 @@ class PCPath public static final int SUBQUERY = 4; public static final int UNBOUND_VAR = 5; public static final int CAST = 6; + public static final int GET_XPATH = 7; public int op = -1; public Object data = null; diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/StartsWithExpression.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/StartsWithExpression.java index 955fa61f7..bde553c96 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/StartsWithExpression.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/StartsWithExpression.java @@ -28,6 +28,7 @@ import org.apache.openjpa.jdbc.sql.DBDictionary; import org.apache.openjpa.jdbc.sql.SQLBuffer; import org.apache.openjpa.jdbc.sql.Select; import org.apache.openjpa.kernel.exps.ExpressionVisitor; +import org.apache.openjpa.meta.XMLMapping; import serp.util.Numbers; /** @@ -184,6 +185,15 @@ class StartsWithExpression public FieldMapping getFieldMapping() { return null; } + + public PCPath getXPath() { + return null; + } + + public XMLMapping getXmlMapping() { + return null; + } + } /** @@ -260,5 +270,13 @@ class StartsWithExpression public FieldMapping getFieldMapping() { return null; } + + public PCPath getXPath() { + return null; + } + + public XMLMapping getXmlMapping() { + return null; + } } } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DB2Dictionary.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DB2Dictionary.java index 036977cfe..f78f983e0 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DB2Dictionary.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DB2Dictionary.java @@ -22,13 +22,17 @@ import java.lang.reflect.Method; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.SQLException; +import java.sql.Types; import java.util.Arrays; import java.util.StringTokenizer; import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration; import org.apache.openjpa.jdbc.schema.Sequence; import org.apache.openjpa.lib.util.Localizer; +import org.apache.openjpa.meta.JavaTypes; import org.apache.openjpa.util.OpenJPAException; import org.apache.openjpa.util.UnsupportedException; +import org.apache.openjpa.kernel.Filters; +import org.apache.openjpa.jdbc.kernel.exps.FilterValue; /** * Dictionary for IBM DB2 database. @@ -492,4 +496,120 @@ public class DB2Dictionary public int getDb2ServerType() { return db2ServerType; } + + protected void appendLength(SQLBuffer buf, int type) { + if (type == Types.VARCHAR) + buf.append("(").append(Integer.toString(characterColumnSize)). + append(")"); + } + + /** + * If this dictionary supports XML type, + * use this method to append xml predicate. + * + * @param buf the SQL buffer to write the comparison + * @param op the comparison operation to perform + * @param lhs the left hand side of the comparison + * @param rhs the right hand side of the comparison + * @param lhsxml indicates whether the left operand maps to xml + * @param rhsxml indicates whether the right operand maps to xml + */ + public void appendXmlComparison(SQLBuffer buf, String op, FilterValue lhs, + FilterValue rhs, boolean lhsxml, boolean rhsxml) { + super.appendXmlComparison(buf, op, lhs, rhs, lhsxml, rhsxml); + if (lhsxml && rhsxml) + appendXmlComparison2(buf, op, lhs, rhs); + else if (lhsxml) + appendXmlComparison1(buf, op, lhs, rhs); + else + appendXmlComparison1(buf, op, rhs, lhs); + } + + /** + * Append an xml comparison predicate. + * + * @param buf the SQL buffer to write the comparison + * @param op the comparison operation to perform + * @param lhs the left hand side of the comparison (maps to xml column) + * @param rhs the right hand side of the comparison + */ + private void appendXmlComparison1(SQLBuffer buf, String op, + FilterValue lhs, FilterValue rhs) { + boolean castrhs = false; + Class rc = Filters.wrap(rhs.getType()); + int type = 0; + if (rhs.isConstant()) { + type = getJDBCType(JavaTypes.getTypeCode(rc), false); + castrhs = true; + } + + appendXmlExists(buf, lhs); + + buf.append(" ").append(op).append(" "); + + buf.append("$"); + if (castrhs) + buf.append("Parm"); + else + rhs.appendTo(buf); + + buf.append("]' PASSING "); + appendXmlVar(buf, lhs); + buf.append(", "); + + if (castrhs) + appendCast(buf, rhs, type); + else + rhs.appendTo(buf); + + buf.append(" AS \""); + if (castrhs) + buf.append("Parm"); + else + rhs.appendTo(buf); + buf.append("\")"); + } + + /** + * Append an xml comparison predicate. (both operands map to xml column) + * + * @param buf the SQL buffer to write the comparison + * @param op the comparison operation to perform + * @param lhs the left hand side of the comparison (maps to xml column) + * @param rhs the right hand side of the comparison (maps to xml column) + */ + private void appendXmlComparison2(SQLBuffer buf, String op, + FilterValue lhs, FilterValue rhs) { + appendXmlExists(buf, lhs); + + buf.append(" ").append(op).append(" "); + + buf.append("$").append(rhs.getColumnAlias( + rhs.getFieldMapping().getColumns()[0])). + append("/*/"); + rhs.appendTo(buf); + + buf.append("]' PASSING "); + appendXmlVar(buf, lhs); + buf.append(", "); + appendXmlVar(buf, rhs); + buf.append(")"); + } + + private void appendXmlVar(SQLBuffer buf, FilterValue val) { + buf.append(val.getColumnAlias( + val.getFieldMapping().getColumns()[0])). + append(" AS "). + append("\"").append(val.getColumnAlias( + val.getFieldMapping().getColumns()[0])). + append("\""); + } + + private void appendXmlExists(SQLBuffer buf, FilterValue val) { + buf.append("XMLEXISTS('"); + buf.append("$").append(val.getColumnAlias( + val.getFieldMapping().getColumns()[0])). + append("/*["); + val.appendTo(buf); + } } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java index 620c02bd8..fb8b9688e 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java @@ -84,6 +84,7 @@ import org.apache.openjpa.jdbc.schema.Sequence; import org.apache.openjpa.jdbc.schema.Table; import org.apache.openjpa.jdbc.schema.Unique; import org.apache.openjpa.kernel.Filters; +import org.apache.openjpa.kernel.exps.Path; import org.apache.openjpa.lib.conf.Configurable; import org.apache.openjpa.lib.conf.Configuration; import org.apache.openjpa.lib.jdbc.ConnectionDecorator; @@ -2456,6 +2457,12 @@ public class DBDictionary */ public void comparison(SQLBuffer buf, String op, FilterValue lhs, FilterValue rhs) { + boolean lhsxml = lhs.getXPath() != null; + boolean rhsxml = rhs.getXPath() != null; + if (lhsxml || rhsxml) { + appendXmlComparison(buf, op, lhs, rhs, lhsxml, rhsxml); + return; + } boolean castlhs = false; boolean castrhs = false; Class lc = Filters.wrap(lhs.getType()); @@ -2484,6 +2491,15 @@ public class DBDictionary rhs.appendTo(buf); } + /** + * If this dictionary supports XML type, + * use this method to append xml predicate. + */ + public void appendXmlComparison(SQLBuffer buf, String op, FilterValue lhs, + FilterValue rhs, boolean lhsxml, boolean rhsxml) { + assertSupport(supportsXMLColumn, "SupportsXMLColumn"); + } + /** * Append SQL for the given numeric value to the buffer, casting as needed. */ @@ -2518,9 +2534,13 @@ public class DBDictionary val.appendTo(buf); buf.append(mid); buf.append(getTypeName(type)); + appendLength(buf, type); buf.append(post); } + protected void appendLength(SQLBuffer buf, int type) { + } + /////////// // DDL SQL /////////// diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/OracleDictionary.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/OracleDictionary.java index 32a758e75..aa8ed1793 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/OracleDictionary.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/OracleDictionary.java @@ -1038,4 +1038,63 @@ public class OracleDictionary return false; } } + + /** + * If this dictionary supports XML type, + * use this method to append xml predicate. + * + * @param buf the SQL buffer to write the comparison + * @param op the comparison operation to perform + * @param lhs the left hand side of the comparison + * @param rhs the right hand side of the comparison + */ + public void appendXmlComparison(SQLBuffer buf, String op, FilterValue lhs, + FilterValue rhs, boolean lhsxml, boolean rhsxml) { + super.appendXmlComparison(buf, op, lhs, rhs, lhsxml, rhsxml); + if (lhsxml && rhsxml) + appendXmlComparison2(buf, op, lhs, rhs); + else if (lhsxml) + appendXmlComparison1(buf, op, lhs, rhs); + else + appendXmlComparison1(buf, op, rhs, lhs); + } + + /** + * Append an xml comparison predicate + * + * @param buf the SQL buffer to write the comparison + * @param op the comparison operation to perform + * @param lhs the left hand side of the comparison (maps to xml column) + * @param rhs the right hand side of the comparison + */ + private void appendXmlComparison1(SQLBuffer buf, String op, + FilterValue lhs, FilterValue rhs) { + appendXmlExtractValue(buf, lhs); + buf.append(" ").append(op).append(" "); + rhs.appendTo(buf); + } + + /** + * Append an xml comparison predicate (both operands map to xml column) + * + * @param buf the SQL buffer to write the comparison + * @param op the comparison operation to perform + * @param lhs the left hand side of the comparison (maps to xml column) + * @param rhs the right hand side of the comparison (maps to xml column) + */ + private void appendXmlComparison2(SQLBuffer buf, String op, + FilterValue lhs, FilterValue rhs) { + appendXmlExtractValue(buf, lhs); + buf.append(" ").append(op).append(" "); + appendXmlExtractValue(buf, rhs); + } + + private void appendXmlExtractValue(SQLBuffer buf, FilterValue val) { + buf.append("extractValue("). + append(val.getColumnAlias( + val.getFieldMapping().getColumns()[0])). + append(",'/*/"); + val.appendTo(buf); + buf.append("')"); + } } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/SQLServerDictionary.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/SQLServerDictionary.java index f02ee3068..baa0ee2e9 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/SQLServerDictionary.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/SQLServerDictionary.java @@ -23,8 +23,11 @@ import java.sql.DatabaseMetaData; import java.sql.SQLException; import java.sql.Types; +import org.apache.openjpa.jdbc.kernel.exps.FilterValue; +import org.apache.openjpa.kernel.Filters; import org.apache.openjpa.jdbc.schema.Column; import org.apache.openjpa.lib.util.Localizer; +import org.apache.openjpa.meta.JavaTypes; /** * Dictionary for MS SQLServer. @@ -134,4 +137,97 @@ public class SQLServerDictionary } return cols; } + + protected void appendLength(SQLBuffer buf, int type) { + if (type == Types.VARCHAR) + buf.append("(").append(Integer.toString(characterColumnSize)).append(")"); + } + + /** + * If this dictionary supports XML type, + * use this method to append xml predicate. + * + * @param buf the SQL buffer to write the comparison + * @param op the comparison operation to perform + * @param lhs the left hand side of the comparison + * @param rhs the right hand side of the comparison + * @param lhsxml indicates whether the left operand maps to xml + * @param rhsxml indicates whether the right operand maps to xml + */ + public void appendXmlComparison(SQLBuffer buf, String op, FilterValue lhs, + FilterValue rhs, boolean lhsxml, boolean rhsxml) { + super.appendXmlComparison(buf, op, lhs, rhs, lhsxml, rhsxml); + if (lhsxml && rhsxml) + appendXmlComparison2(buf, op, lhs, rhs); + else if (lhsxml) + appendXmlComparison1(buf, op, lhs, rhs); + else + appendXmlComparison1(buf, op, rhs, lhs); + } + /** + * Append an xml comparison predicate + * + * @param buf the SQL buffer to write the comparison + * @param op the comparison operation to perform + * @param lhs the left hand side of the comparison (maps to xml column) + * @param rhs the right hand side of the comparison + */ + private void appendXmlComparison1(SQLBuffer buf, String op, + FilterValue lhs, FilterValue rhs) { + boolean castrhs = rhs.isConstant(); + if (castrhs) + appendXmlValue(buf, lhs); + else + appendXmlExist(buf, lhs); + buf.append(" ").append(op).append(" "); + if (castrhs) + rhs.appendTo(buf); + else { + buf.append("sql:column(\""); + rhs.appendTo(buf); + buf.append("\")"). + append("]') = 1"); + } + } + + private void appendXmlExist(SQLBuffer buf, FilterValue lhs) { + buf.append(lhs.getColumnAlias( + lhs.getFieldMapping().getColumns()[0])). + append(".exist('"). + append("/*["); + lhs.appendTo(buf); + } + + /** + * Append an xml comparison predicate (both operands map to xml column) + * + * @param buf the SQL buffer to write the comparison + * @param op the comparison operation to perform + * @param lhs the left hand side of the comparison (maps to xml column) + * @param rhs the right hand side of the comparison (maps to xml column) + */ + private void appendXmlComparison2(SQLBuffer buf, String op, + FilterValue lhs, FilterValue rhs) { + appendXmlValue(buf, lhs); + buf.append(" ").append(op).append(" "); + appendXmlValue(buf, rhs); + } + + private void appendXmlValue(SQLBuffer buf, FilterValue val) { + Class rc = Filters.wrap(val.getType()); + int type = getJDBCType(JavaTypes.getTypeCode(rc), false); + boolean isXmlAttribute = (val.getXmlMapping() == null) ? false + : val.getXmlMapping().isXmlAttribute(); + buf.append(val.getColumnAlias( + val.getFieldMapping().getColumns()[0])). + append(".value("). + append("'(/*/"); + val.appendTo(buf); + if (!isXmlAttribute) + buf.append("/text()"); + buf.append(")[1]','"). + append(getTypeName(type)); + appendLength(buf, type); + buf.append("')"); + } } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/AbstractExpressionBuilder.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/AbstractExpressionBuilder.java index 77836ab21..023ee0247 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/AbstractExpressionBuilder.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/AbstractExpressionBuilder.java @@ -30,6 +30,8 @@ import org.apache.openjpa.lib.util.Localizer; import org.apache.openjpa.lib.util.Localizer.Message; import org.apache.openjpa.meta.ClassMetaData; import org.apache.openjpa.meta.FieldMetaData; +import org.apache.openjpa.meta.JavaTypes; +import org.apache.openjpa.meta.XMLMapping; import org.apache.openjpa.util.InternalException; import org.apache.openjpa.util.OpenJPAException; import org.apache.openjpa.util.UnsupportedException; @@ -243,6 +245,27 @@ public abstract class AbstractExpressionBuilder { protected Value traversePath(Path path, String field) { return traversePath(path, field, false, false); } + + protected Value traverseXPath(Path path, String field) { + XMLMapping meta = path.getXmlMapping(); + if (meta.getFieldMapping(field) == null) { + throw parseException(EX_USER, "no-field", + new Object[]{ meta.getType(), field }, null); + } + else { + // collection-valued xpath is not allowed + int type = meta.getFieldMapping(field).getTypeCode(); + switch (type) { + case JavaTypes.ARRAY: + case JavaTypes.COLLECTION: + case JavaTypes.MAP: + throw new UserException(_loc.get("collection-valued-path", + field)); + } + } + path.get(meta, field); + return path; + } /** * Traverse the given field in the given path. @@ -272,6 +295,14 @@ public abstract class AbstractExpressionBuilder { addAccessPath(meta); path.setMetaData(meta); } + else { + // xmlsupport xpath + XMLMapping xmlmeta = fmd.getRepository().getXMLMetaData(fmd); + if (xmlmeta != null) { + path.get(fmd, xmlmeta); + return path; + } + } if (meta != null || !pcOnly) path.get(fmd, allowNull); @@ -309,11 +340,11 @@ public abstract class AbstractExpressionBuilder { if (o1 && !o2) { val1.setImplicitType(c2); - if (val1.getMetaData() == null) + if (val1.getMetaData() == null && !val1.isXPath()) val1.setMetaData(val2.getMetaData()); } else if (!o1 && o2) { val2.setImplicitType(c1); - if (val2.getMetaData() == null) + if (val2.getMetaData() == null && !val1.isXPath()) val2.setMetaData(val1.getMetaData()); } else if (o1 && o2 && expected != null) { // we never expect a pc type, so don't bother with metadata diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/CandidatePath.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/CandidatePath.java index b33551b8e..0f7226b33 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/CandidatePath.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/CandidatePath.java @@ -30,6 +30,7 @@ import org.apache.openjpa.kernel.OpenJPAStateManager; import org.apache.openjpa.kernel.StoreContext; import org.apache.openjpa.meta.ClassMetaData; import org.apache.openjpa.meta.FieldMetaData; +import org.apache.openjpa.meta.XMLMapping; /** * A path represents a traversal into fields of a candidate object. @@ -184,4 +185,14 @@ class CandidatePath return ((Traversal) other).field.equals(field); } } + + public void get(FieldMetaData fmd, XMLMapping meta) { + } + + public void get(XMLMapping meta, String name) { + } + + public XMLMapping getXmlMapping() { + return null; + } } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Path.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Path.java index 9ba40e751..750f66393 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Path.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Path.java @@ -19,6 +19,7 @@ package org.apache.openjpa.kernel.exps; import org.apache.openjpa.meta.FieldMetaData; +import org.apache.openjpa.meta.XMLMapping; /** * A path represents a traversal into fields of a candidate object. @@ -42,4 +43,28 @@ public interface Path * not contain a final field. */ public FieldMetaData last(); + + /** + * Traverse into the given field that maps to xml column, and update + * the current object to that field value. + * + * @param fmd field maps to xml column + * @param meta associated xml mapping + */ + public void get(FieldMetaData fmd, XMLMapping meta); + + /** + * Traverse into the gevin xpath name of the current object, and update + * the current object to that xpath field. + * + * @param meta + * @param name + */ + public void get(XMLMapping meta, String name); + + /** + * Return the current XPath's xmlmapping metadata. + * @return Return xmlmapping + */ + public XMLMapping getXmlMapping(); } diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Val.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Val.java index 1e76ad3c6..da054c7d7 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Val.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Val.java @@ -109,6 +109,10 @@ public abstract class Val public boolean isAggregate() { return false; } + + public boolean isXPath() { + return false; + } public void acceptVisit(ExpressionVisitor visitor) { visitor.enter(this); diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Value.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Value.java index f80844ba3..b9135a1f0 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Value.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/exps/Value.java @@ -51,6 +51,11 @@ public interface Value { */ public boolean isAggregate(); + /** + * Return true if this value is an XML Path. + */ + public boolean isXPath(); + /** * Return any associated persistent type. */ diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java index c96e1b30e..06df6348c 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java @@ -1088,7 +1088,7 @@ public class JPQLExpressionBuilder if (fmd == null) return; - Class type = fmd.getType(); + Class type = path.isXPath() ? path.getType() : fmd.getType(); if (type == null) return; @@ -1298,6 +1298,11 @@ public class JPQLExpressionBuilder // walk through the children and assemble the path boolean allowNull = !inner; for (int i = 1; i < node.children.length; i++) { + if (path.isXPath()) { + for (int j = i; j