You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@chemistry.apache.org by ju...@apache.org on 2011/04/01 13:59:47 UTC
svn commit: r1087664 [2/3] - in /chemistry/opencmis/trunk:
chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/test/java/org/apache/chemistry/opencmis/client/bindings/framework/
chemistry-opencmis-server/chemistry-opencmis-server-jcr/ chem...
Added: chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/query/Evaluator.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/query/Evaluator.java?rev=1087664&view=auto
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/query/Evaluator.java (added)
+++ chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/query/Evaluator.java Fri Apr 1 11:59:46 2011
@@ -0,0 +1,119 @@
+/*
+ * 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.chemistry.opencmis.jcr.query;
+
+import java.util.GregorianCalendar;
+import java.util.List;
+
+/**
+ * Evaluator for CMIS query parse trees.
+ *
+ * @param <T> The result type of the evaluation of the parse tree.
+ * @see ParseTreeWalker
+ */
+public interface Evaluator<T> {
+
+ /** Create a new instance of this <code>Evaluator</code>. */
+ Evaluator<T> op();
+
+ /** Handle {@link org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer#NOT} nodes */
+ T not(T op);
+
+ /** Handle {@link org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer#AND} nodes */
+ T and(T op1, T op2);
+
+ /** Handle {@link org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer#OR} nodes */
+ T or(T op1, T op2);
+
+ /** Handle {@link org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer#EQ} nodes */
+ T eq(T op1, T op2);
+
+ /** Handle {@link org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer#NEQ} nodes */
+ T neq(T op1, T op2);
+
+ /** Handle {@link org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer#GT} nodes */
+ T gt(T op1, T op2);
+
+ /** Handle {@link org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer#GTEQ} nodes */
+ T gteq(T op1, T op2);
+
+ /** Handle {@link org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer#LT} nodes */
+ T lt(T op1, T op2);
+
+ /** Handle {@link org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer#LTEQ} nodes */
+ T lteq(T op1, T op2);
+
+ /** Handle {@link org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer#IN} nodes */
+ T in(T op1, T op2);
+
+ /** Handle {@link org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer#NOT_IN} nodes */
+ T notIn(T op1, T op2);
+
+ /** Handle {@link org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer#IN_ANY} nodes */
+ T inAny(T op1, T op2);
+
+ /** Handle {@link org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer#NOT_IN_ANY} nodes */
+ T notInAny(T op1, T op2);
+
+ /** Handle {@link org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer#EQ_ANY} nodes */
+ T eqAny(T op1, T op2);
+
+ /** Handle {@link org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer#IS_NULL} nodes */
+ T isNull(T op);
+
+ /** Handle {@link org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer#IS_NOT_NULL} nodes */
+ T notIsNull(T op);
+
+ /** Handle {@link org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer#LIKE} nodes */
+ T like(T op1, T op2);
+
+ /** Handle {@link org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer#NOT_LIKE} nodes */
+ T notLike(T op1, T op2);
+
+ /** Handle {@link org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer#CONTAINS} nodes */
+ T contains(T op1, T op2);
+
+ /** Handle {@link org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer#IN_FOLDER} nodes */
+ T inFolder(T op1, T op2);
+
+ /** Handle {@link org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer#IN_TREE} nodes */
+ T inTree(T op1, T op2);
+
+ /** Handle {@link org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer#IN_LIST} nodes */
+ T list(List<T> ops);
+
+ /** Handle {@link org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer#BOOL_LIT} nodes */
+ T value(boolean value);
+
+ /** Handle {@link org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer#NUM_LIT} nodes */
+ T value(double value);
+
+ /** Handle {@link org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer#NUM_LIT} nodes */
+ T value(long value);
+
+ /** Handle {@link org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer#STRING_LIT} nodes */
+ T value(String value);
+
+ /** Handle {@link org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer#TIME_LIT} nodes */
+ T value(GregorianCalendar value);
+
+ /** Handle {@link org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer#COL} nodes */
+ T col(String name);
+}
Added: chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/query/EvaluatorBase.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/query/EvaluatorBase.java?rev=1087664&view=auto
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/query/EvaluatorBase.java (added)
+++ chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/query/EvaluatorBase.java Fri Apr 1 11:59:46 2011
@@ -0,0 +1,148 @@
+/*
+ * 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.chemistry.opencmis.jcr.query;
+
+import org.apache.chemistry.opencmis.commons.exceptions.CmisNotSupportedException;
+
+import java.util.GregorianCalendar;
+import java.util.List;
+
+/**
+ * This abstract base class implements all methods of the {@link Evaluator} interface
+ * by throwing a {@link CmisNotSupportedException}.
+ */
+public abstract class EvaluatorBase<T> implements Evaluator<T> {
+ public Evaluator<T> op() {
+ throw new CmisNotSupportedException();
+ }
+
+ public T not(T op) {
+ throw new CmisNotSupportedException("Not supported in query: not");
+ }
+
+ public T and(T op1, T op2) {
+ throw new CmisNotSupportedException("Not supported in query: and");
+ }
+
+ public T or(T op1, T op2) {
+ throw new CmisNotSupportedException("Not supported in query: or");
+ }
+
+ public T eq(T op1, T op2) {
+ throw new CmisNotSupportedException("Not supported in query: =");
+ }
+
+ public T neq(T op1, T op2) {
+ throw new CmisNotSupportedException("Not supported in query: !=");
+ }
+
+ public T gt(T op1, T op2) {
+ throw new CmisNotSupportedException("Not supported in query: >");
+ }
+
+ public T gteq(T op1, T op2) {
+ throw new CmisNotSupportedException("Not supported in query: >=");
+ }
+
+ public T lt(T op1, T op2) {
+ throw new CmisNotSupportedException("Not supported in query: <");
+ }
+
+ public T lteq(T op1, T op2) {
+ throw new CmisNotSupportedException("Not supported in query: <=");
+ }
+
+ public T in(T op1, T op2) {
+ throw new CmisNotSupportedException("Not supported in query: in");
+ }
+
+ public T notIn(T op1, T op2) {
+ throw new CmisNotSupportedException("Not supported in query: not in");
+ }
+
+ public T inAny(T op1, T op2) {
+ throw new CmisNotSupportedException("Not supported in query: in");
+ }
+
+ public T notInAny(T op1, T op2) {
+ throw new CmisNotSupportedException("Not supported in query: not in");
+ }
+
+ public T eqAny(T op1, T op2) {
+ throw new CmisNotSupportedException("Not supported in query: = ANY");
+ }
+
+ public T isNull(T op) {
+ throw new CmisNotSupportedException("Not supported in query: is null");
+ }
+
+ public T notIsNull(T op) {
+ throw new CmisNotSupportedException("Not supported in query: is not null");
+ }
+
+ public T like(T op1, T op2) {
+ throw new CmisNotSupportedException("Not supported in query: like");
+ }
+
+ public T notLike(T op1, T op2) {
+ throw new CmisNotSupportedException("Not supported in query: not like");
+ }
+
+ public T contains(T op1, T op2) {
+ throw new CmisNotSupportedException("Not supported in query: contains");
+ }
+
+ public T inFolder(T op1, T op2) {
+ throw new CmisNotSupportedException("Not supported in query: in_folder");
+ }
+
+ public T inTree(T op1, T op2) {
+ throw new CmisNotSupportedException("Not supported in query: in_tree");
+ }
+
+ public T list(List<T> ops) {
+ throw new CmisNotSupportedException("Not supported in query: list");
+ }
+
+ public T value(boolean value) {
+ throw new CmisNotSupportedException("Not supported in query: boolean value " + value);
+ }
+
+ public T value(double value) {
+ throw new CmisNotSupportedException("Not supported in query: double value " + value);
+ }
+
+ public T value(long value) {
+ throw new CmisNotSupportedException("Not supported in query: long value " + value);
+ }
+
+ public T value(String value) {
+ throw new CmisNotSupportedException("Not supported in query: string value " + value);
+ }
+
+ public T value(GregorianCalendar value) {
+ throw new CmisNotSupportedException("Not supported in query: date value " + value);
+ }
+
+ public T col(String name) {
+ throw new CmisNotSupportedException("Not supported in query: column name " + name);
+ }
+
+}
Added: chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/query/EvaluatorXPath.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/query/EvaluatorXPath.java?rev=1087664&view=auto
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/query/EvaluatorXPath.java (added)
+++ chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/query/EvaluatorXPath.java Fri Apr 1 11:59:46 2011
@@ -0,0 +1,459 @@
+/*
+ * 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.chemistry.opencmis.jcr.query;
+
+import org.apache.chemistry.opencmis.jcr.util.ISO8601;
+import org.apache.chemistry.opencmis.jcr.util.Iterables;
+
+import java.util.GregorianCalendar;
+import java.util.List;
+
+/**
+ * This implementation of {@link Evaluator} results in an instance of a {@link XPathBuilder} which
+ * can be used to validated the where clause of the original CMIS query and translate it to a
+ * corresponding (i.e. semantically equal) XPath condition.
+ */
+public class EvaluatorXPath extends EvaluatorBase<XPathBuilder> {
+
+ @Override
+ public Evaluator<XPathBuilder> op() {
+ // New instance delegates these methods to this instance in order
+ // to account for the case where these methods are overridden.
+ return new EvaluatorXPath() {
+ @Override
+ protected String jcrPathFromId(String id) {
+ return EvaluatorXPath.this.jcrPathFromId(id);
+ }
+
+ @Override
+ protected String jcrPathFromCol(String name) {
+ return EvaluatorXPath.this.jcrPathFromCol(name);
+ }
+ };
+ }
+
+ @Override
+ public XPathBuilder not(final XPathBuilder op) {
+ return new XPathBuilder() {
+ public String xPath() {
+ if (eval(true) != null) {
+ return eval(true) ? "true()" : "false()";
+ }
+ else {
+ return "not(" + op.xPath() + ")";
+ }
+ }
+ public Boolean eval(Boolean folderPredicateValuation) {
+ return not(op.eval(folderPredicateValuation));
+ }
+
+ public Iterable<XPathBuilder> folderPredicates() {
+ return op.folderPredicates();
+ }
+ };
+ }
+
+ @Override
+ public XPathBuilder and(final XPathBuilder op1, final XPathBuilder op2) {
+ return new XPathBuilder() {
+ public String xPath() {
+ if (eval(true) != null) {
+ return eval(true) ? "true()" : "false()";
+ }
+ else if (op1.eval(true) != null) { // if not null, op1 must be true -> shortcut evaluation to op2
+ return op2.xPath();
+ }
+ else if (op2.eval(true) != null) { // if not null, op2 must be true -> shortcut evaluation to op1
+ return op1.xPath();
+ }
+ else {
+ return op1.xPath() + " and " + op2.xPath();
+ }
+ }
+ public Boolean eval(Boolean folderPredicateValuation) {
+ return and(op1.eval(folderPredicateValuation), op2.eval(folderPredicateValuation));
+ }
+
+ public Iterable<XPathBuilder> folderPredicates() {
+ return Iterables.concat(op1.folderPredicates(), op2.folderPredicates());
+ }
+ };
+ }
+
+ @Override
+ public XPathBuilder or(final XPathBuilder op1, final XPathBuilder op2) {
+ return new XPathBuilder() {
+ public String xPath() {
+ if (eval(true) != null) {
+ return eval(true) ? "true()" : "false()";
+ }
+ else if (op1.eval(true) != null) { // if not null, op1 must be false -> shortcut evaluation to op2
+ return op2.xPath();
+ }
+ else if (op2.eval(true) != null) { // if not null, op2 must be false -> shortcut evaluation to op2
+ return op1.xPath();
+ }
+ else {
+ return "(" + op1.xPath() + " or " + op2.xPath() + ")";
+ }
+ }
+ public Boolean eval(Boolean folderPredicateValuation) {
+ return or(op1.eval(folderPredicateValuation), op2.eval(folderPredicateValuation));
+ }
+
+ public Iterable<XPathBuilder> folderPredicates() {
+ return Iterables.concat(op1.folderPredicates(), op2.folderPredicates());
+ }
+ };
+ }
+
+ @Override
+ public XPathBuilder eq(XPathBuilder op1, XPathBuilder op2) {
+ return new RelOpBuilder(op1, " = ", op2);
+ }
+
+ @Override
+ public XPathBuilder neq(XPathBuilder op1, XPathBuilder op2) {
+ return new RelOpBuilder(op1, " != ", op2);
+ }
+
+ @Override
+ public XPathBuilder gt(XPathBuilder op1, XPathBuilder op2) {
+ return new RelOpBuilder(op1, " > ", op2);
+ }
+
+ @Override
+ public XPathBuilder gteq(XPathBuilder op1, XPathBuilder op2) {
+ return new RelOpBuilder(op1, " >= ", op2);
+ }
+
+ @Override
+ public XPathBuilder lt(XPathBuilder op1, XPathBuilder op2) {
+ return new RelOpBuilder(op1, " < ", op2);
+ }
+
+ @Override
+ public XPathBuilder lteq(XPathBuilder op1, XPathBuilder op2) {
+ return new RelOpBuilder(op1, " <= ", op2);
+ }
+
+ @Override
+ public XPathBuilder in(XPathBuilder op1, XPathBuilder op2) {
+ return super.in(op1, op2); // todo implement in
+ }
+
+ @Override
+ public XPathBuilder notIn(XPathBuilder op1, XPathBuilder op2) {
+ return super.notIn(op1, op2); // todo implement notIn
+ }
+
+ @Override
+ public XPathBuilder inAny(XPathBuilder op1, XPathBuilder op2) {
+ return super.inAny(op1, op2); // todo implement inAny
+ }
+
+ @Override
+ public XPathBuilder notInAny(XPathBuilder op1, XPathBuilder op2) {
+ return super.notInAny(op1, op2); // todo implement notInAny
+ }
+
+ @Override
+ public XPathBuilder eqAny(XPathBuilder op1, XPathBuilder op2) {
+ return super.eqAny(op1, op2); // todo implement eqAny
+ }
+
+ @Override
+ public XPathBuilder isNull(XPathBuilder op) {
+ return new FunctionBuilder(op);
+ }
+
+ @Override
+ public XPathBuilder notIsNull(XPathBuilder op) {
+ return new FunctionBuilder("not", op);
+ }
+
+ @Override
+ public XPathBuilder like(XPathBuilder op1, XPathBuilder op2) {
+ return new FunctionBuilder("jcr:like", op1, op2);
+ }
+
+ @Override
+ public XPathBuilder notLike(XPathBuilder op1, XPathBuilder op2) {
+ return new FunctionBuilder("jcr:like", op1, op2) {
+ @Override
+ public String xPath() {
+ return "not(" + super.xPath() + ")";
+ }
+ };
+ }
+
+ @Override
+ public XPathBuilder contains(XPathBuilder op1, XPathBuilder op2) {
+ return new FunctionBuilder("jcr:contains", ".", op2);
+ }
+
+ @Override
+ public XPathBuilder inFolder(XPathBuilder op1,XPathBuilder op2) {
+ return new FolderPredicateBuilder(op2.xPath(), false);
+ }
+
+ @Override
+ public XPathBuilder inTree(XPathBuilder op1, XPathBuilder op2) {
+ return new FolderPredicateBuilder(op2.xPath(), true);
+ }
+
+ @Override
+ public XPathBuilder list(List<XPathBuilder> ops) {
+ return super.list(ops); // todo implement list
+ }
+
+ @Override
+ public XPathBuilder value(boolean value) {
+ return new LiteralBuilder(value);
+ }
+
+ @Override
+ public XPathBuilder value(double value) {
+ return new LiteralBuilder(value);
+ }
+
+ @Override
+ public XPathBuilder value(long value) {
+ return new LiteralBuilder(value);
+ }
+
+ @Override
+ public XPathBuilder value(String value) {
+ return new LiteralBuilder(value);
+ }
+
+ @Override
+ public XPathBuilder value(GregorianCalendar value) {
+ return new LiteralBuilder(value);
+ }
+
+ @Override
+ public XPathBuilder col(String name) {
+ return new ColRefBuilder(name);
+ }
+
+ //------------------------------------------< protected >---
+
+ /**
+ * Resolve from a CMIS object id to the corresponding absolute JCR path.
+ * This default implementations simply returns <code>id</code>.
+ */
+ protected String jcrPathFromId(String id) {
+ return id;
+ }
+
+ /**
+ * Resolve from a column name in the query to the corresponding
+ * relative JCR path. The path must be relative to the context node.
+ * This default implementations simply returns <code>name</code>.
+ */
+ protected String jcrPathFromCol(String name) {
+ return name;
+ }
+
+ //------------------------------------------< private >---
+
+ /**
+ * @return <code>null</code> if <code>b</code> is <code>null</code>, <code>!b</code> otherwise.
+ */
+ private static Boolean not(Boolean b) {
+ return b == null ? null : !b;
+ }
+
+ /**
+ * @return
+ * <ul><li><code>true</code> if either of <code>b1</code> and <code>b2</code> is <code>true</code>,</li>
+ * <li><code>false</code> if both <code>b1</code> and <code>b2</code> are <code>false</code>,</li>
+ * <li><code>null</code> otherwise.</li></ul>
+ */
+ private static Boolean or(Boolean b1, Boolean b2) {
+ return Boolean.TRUE.equals(b1) || Boolean.TRUE.equals(b2)
+ ? Boolean.TRUE
+ : Boolean.FALSE.equals(b1) && Boolean.FALSE.equals(b2)
+ ? Boolean.FALSE
+ : null;
+ }
+
+ /**
+ * @return
+ * <ul><li><code>false</code> if either of <code>b1</code> and <code>b2</code> is <code>false</code>,</li>
+ * <li><code>true</code> if both <code>b1</code> and <code>b2</code> are <code>true</code>,</li>
+ * <li><code>null</code> otherwise.</li></ul>
+ */
+ private static Boolean and(Boolean b1, Boolean b2) {
+ return Boolean.FALSE.equals(b1) || Boolean.FALSE.equals(b2)
+ ? Boolean.FALSE
+ : Boolean.TRUE.equals(b1) && Boolean.TRUE.equals(b2)
+ ? Boolean.TRUE
+ : null;
+ }
+
+ private static class RelOpBuilder implements XPathBuilder {
+ private final String relOp;
+ private final XPathBuilder op1;
+ private final XPathBuilder op2;
+
+ public RelOpBuilder(XPathBuilder op1, String relOp, XPathBuilder op2) {
+ this.relOp = relOp;
+ this.op1 = op1;
+ this.op2 = op2;
+ }
+
+ public String xPath() {
+ return op1.xPath() + relOp + op2.xPath();
+ }
+
+ public Boolean eval(Boolean folderPredicateValuation) {
+ return null;
+ }
+
+ public Iterable<XPathBuilder> folderPredicates() {
+ return Iterables.concat(op1.folderPredicates(), op2.folderPredicates());
+ }
+ }
+
+ private class FolderPredicateBuilder implements XPathBuilder {
+ private final String folderId;
+ private final boolean includeDescendants;
+
+ public FolderPredicateBuilder(String folderId, boolean includeDescendants) {
+ this.folderId = stripQuotes(folderId);
+ this.includeDescendants = includeDescendants;
+ }
+
+ public String xPath() {
+ return jcrPathFromId(folderId) + (includeDescendants ? "//" : "/");
+ }
+
+ public Boolean eval(Boolean folderPredicateValuation) {
+ return folderPredicateValuation;
+ }
+
+ public Iterable<XPathBuilder> folderPredicates() {
+ return Iterables.singleton((XPathBuilder) this);
+ }
+
+ private String stripQuotes(String string) {
+ return (string.startsWith("'") || string.startsWith("\"")) && string.length() >= 2
+ ? string.substring(1, string.length() - 1)
+ : string;
+ }
+
+ }
+
+ private abstract static class PrimitiveBuilder implements XPathBuilder {
+ public Boolean eval(Boolean folderPredicateValuation) {
+ return null;
+ }
+
+ public Iterable<XPathBuilder> folderPredicates() {
+ return Iterables.empty();
+ }
+ }
+
+ private static class LiteralBuilder extends PrimitiveBuilder {
+ private final String xPath;
+
+ public LiteralBuilder(String value) {
+ xPath = "'" + value + "'";
+ }
+
+ public LiteralBuilder(boolean value) {
+ xPath = Boolean.toString(value);
+ }
+
+ public LiteralBuilder(long value) {
+ xPath = Long.toString(value);
+ }
+
+ public LiteralBuilder(double value) {
+ xPath = Double.toString(value);
+ }
+
+ public LiteralBuilder(GregorianCalendar value) {
+ xPath = "xs:dateTime('" + ISO8601.format(value) + "')";
+ }
+
+ public String xPath() {
+ return xPath;
+ }
+ }
+
+ private class ColRefBuilder extends PrimitiveBuilder {
+ private final String colRef;
+
+ public ColRefBuilder(String colRef) {
+ this.colRef = colRef;
+ }
+
+ public String xPath() {
+ return jcrPathFromCol(colRef);
+ }
+
+ }
+
+ private static class FunctionBuilder extends PrimitiveBuilder {
+ private final String function;
+ private String op1Str;
+ private final XPathBuilder op1;
+ private final XPathBuilder op2;
+
+ private FunctionBuilder(String function, String op1Str, XPathBuilder op1, XPathBuilder op2) {
+ this.function = function;
+ this.op1Str = op1Str;
+ this.op1 = op1;
+ this.op2 = op2;
+ }
+
+ public FunctionBuilder(String function, XPathBuilder op1, XPathBuilder op2) {
+ this(function, null, op1, op2);
+ }
+
+ public FunctionBuilder(String function, XPathBuilder op1) {
+ this(function, null, op1, null);
+ }
+
+ public FunctionBuilder(String function, String op1, XPathBuilder op2) {
+ this(function, op1, null, op2);
+
+ }
+
+ public FunctionBuilder(XPathBuilder op1) {
+ this(null, op1, null);
+ }
+
+ public String xPath() {
+ if (op1Str == null) {
+ op1Str = op1.xPath();
+ }
+
+ return function == null
+ ? op1Str
+ : function + "(" + op1Str + (op2 == null ? "" : ", " + op2.xPath()) + ")";
+ }
+
+ }
+
+}
Added: chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/query/IdentifierMap.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/query/IdentifierMap.java?rev=1087664&view=auto
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/query/IdentifierMap.java (added)
+++ chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/query/IdentifierMap.java Fri Apr 1 11:59:46 2011
@@ -0,0 +1,65 @@
+/*
+ * 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.chemistry.opencmis.jcr.query;
+
+/**
+ * The methods of this class map CMIS identifiers to JCR identifiers. Each implementation
+ * of this interface is bound to a specific CMIS object type. That is, it implements the
+ * identifier maps for that object type.
+ */
+public interface IdentifierMap {
+
+ /**
+ * Map a column name in the CMIS query to the corresponding relative JCR path.
+ * The path must be relative to the context node.
+ *
+ * @param name column name
+ * @return relative JCR path
+ */
+ String jcrPathFromCol(String name);
+
+ /**
+ * JCR type name corresponding to the CMIS type bound to this instance.
+ * @see #jcrTypeCondition()
+ *
+ * @return name of the JCR type
+ */
+ String jcrTypeName();
+
+ /**
+ * Create and additional condition in order for the query to only return nodes
+ * of the right type. This condition and-ed to the condition determined by the
+ * CMIS query's where clause.
+ * <p/>
+ * A CMIS query for non versionable documents should for example result in the
+ * following XPath query:
+ * <p/>
+ * <pre>
+ * element(*, nt:file)[not(@jcr:mixinTypes = 'mix:simpleVersionable')]
+ * </pre>
+ * Here the element test is covered by {@link #jcrTypeName()}
+ * while the predicate is covered by this method.
+ *
+ * @see #jcrTypeName()
+ *
+ * @return Additional condition or <code>null</code> if none.
+ */
+ String jcrTypeCondition();
+}
Added: chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/query/ParseTreeWalker.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/query/ParseTreeWalker.java?rev=1087664&view=auto
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/query/ParseTreeWalker.java (added)
+++ chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/query/ParseTreeWalker.java Fri Apr 1 11:59:46 2011
@@ -0,0 +1,271 @@
+/*
+ * 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.chemistry.opencmis.jcr.query;
+
+import org.antlr.runtime.tree.Tree;
+import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
+import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
+import org.apache.chemistry.opencmis.server.support.query.CalendarHelper;
+import org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer;
+import org.apache.chemistry.opencmis.server.support.query.PredicateWalkerBase;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This implementation of {@link PredicateWalkerBase} traverses the parse tree of a CMIS query.
+ * It uses an {@link Evaluator} to accumulate the result of the traversal. <code>Evaluator</code>
+ * has a corresponding method for each {@link Tree#getType() node type} in the parse tree.
+ * <code>ParseTreeWalker</code> calls these methods while traversing the parse tree passing an
+ * <code>Evaluator</code> for each of the corresponding operation's arguments.
+ * </br>
+ * The {@link #walkPredicate(Tree)} serves as entry point for traversing a parse tree. After
+ * successful traversal, the result is obtained from the {@link #getResult()} method.
+ *
+ * @param <T> type of the result determined by the <code>Evaluator</code> used.
+ */
+public class ParseTreeWalker<T> implements PredicateWalkerBase {
+
+ private final Evaluator<T> evaluator;
+ private T result;
+
+ /**
+ * Create a new instance for traversing CMIS query parse trees.
+ *
+ * @param evaluator <code>Evaluator</code> for evaluating the nodes of the parse tree
+ */
+ public ParseTreeWalker(Evaluator<T> evaluator) {
+ this.evaluator = evaluator;
+ }
+
+ /**
+ * Retrieve the result of a successful traversal.
+ *
+ * @return result of traversal or <code>null</code> if either not yet traversed, an error occurred
+ * on traversal or the query has an empty where clause.
+ */
+ public T getResult() {
+ return result;
+ }
+
+ //------------------------------------------< PredicateWalkerBase >---
+
+ public Boolean walkPredicate(Tree node) {
+ result = null;
+ result = walkPredicate(evaluator, node);
+ return false; // Return value is ignored by caller
+ }
+
+ //------------------------------------------< protected >---
+
+ /** For extensibility. */
+ protected T walkOtherExpr(Evaluator evaluator, Tree node) {
+ throw new CmisRuntimeException("Unknown node type: " + node.getType() + " (" + node.getText() + ")");
+ }
+
+ /** For extensibility. */
+ protected T walkOtherPredicate(Evaluator evaluator, Tree node) {
+ throw new CmisRuntimeException("Unknown node type: " + node.getType() + " (" + node.getText() + ")");
+ }
+
+ //------------------------------------------< private >---
+
+ private T walkPredicate(Evaluator<T> evaluator, Tree node) {
+ switch (node.getType()) {
+ case CmisQlStrictLexer.NOT:
+ return evaluator.not(walkPredicate(evaluator.op(), node.getChild(0)));
+ case CmisQlStrictLexer.AND:
+ return evaluator.and(
+ walkPredicate(evaluator.op(), node.getChild(0)),
+ walkPredicate(evaluator.op(), node.getChild(1)));
+ case CmisQlStrictLexer.OR:
+ return evaluator.or(
+ walkPredicate(evaluator.op(), node.getChild(0)),
+ walkPredicate(evaluator.op(), node.getChild(1)));
+ case CmisQlStrictLexer.EQ:
+ return evaluator.eq(
+ walkExpr(evaluator.op(), node.getChild(0)),
+ walkExpr(evaluator.op(), node.getChild(1)));
+ case CmisQlStrictLexer.NEQ:
+ return evaluator.neq(
+ walkExpr(evaluator.op(), node.getChild(0)),
+ walkExpr(evaluator.op(), node.getChild(1)));
+ case CmisQlStrictLexer.GT:
+ return evaluator.gt(
+ walkExpr(evaluator.op(), node.getChild(0)),
+ walkExpr(evaluator.op(), node.getChild(1)));
+ case CmisQlStrictLexer.GTEQ:
+ return evaluator.gteq(
+ walkExpr(evaluator.op(), node.getChild(0)),
+ walkExpr(evaluator.op(), node.getChild(1)));
+ case CmisQlStrictLexer.LT:
+ return evaluator.lt(
+ walkExpr(evaluator.op(), node.getChild(0)),
+ walkExpr(evaluator.op(), node.getChild(1)));
+ case CmisQlStrictLexer.LTEQ:
+ return evaluator.lteq(
+ walkExpr(evaluator.op(), node.getChild(0)),
+ walkExpr(evaluator.op(), node.getChild(1)));
+ case CmisQlStrictLexer.IN:
+ return evaluator.in(
+ walkExpr(evaluator.op(), node.getChild(0)),
+ walkExpr(evaluator.op(), node.getChild(1)));
+ case CmisQlStrictLexer.NOT_IN:
+ return evaluator.notIn(
+ walkExpr(evaluator.op(), node.getChild(0)),
+ walkExpr(evaluator.op(), node.getChild(1)));
+ case CmisQlStrictLexer.IN_ANY:
+ return evaluator.inAny(
+ walkExpr(evaluator.op(), node.getChild(0)),
+ walkExpr(evaluator.op(), node.getChild(1)));
+ case CmisQlStrictLexer.NOT_IN_ANY:
+ return evaluator.notInAny(
+ walkExpr(evaluator.op(), node.getChild(0)),
+ walkExpr(evaluator.op(), node.getChild(1)));
+ case CmisQlStrictLexer.EQ_ANY:
+ return evaluator.eqAny(
+ walkExpr(evaluator.op(), node.getChild(0)),
+ walkExpr(evaluator.op(), node.getChild(1)));
+ case CmisQlStrictLexer.IS_NULL:
+ return evaluator.isNull(walkExpr(evaluator.op(), node.getChild(0)));
+ case CmisQlStrictLexer.IS_NOT_NULL:
+ return evaluator.notIsNull(walkExpr(evaluator.op(), node.getChild(0)));
+ case CmisQlStrictLexer.LIKE:
+ return evaluator.like(
+ walkExpr(evaluator.op(), node.getChild(0)),
+ walkExpr(evaluator.op(), node.getChild(1)));
+ case CmisQlStrictLexer.NOT_LIKE:
+ return evaluator.notLike(
+ walkExpr(evaluator.op(), node.getChild(0)),
+ walkExpr(evaluator.op(), node.getChild(1)));
+ case CmisQlStrictLexer.CONTAINS:
+ if (node.getChildCount() == 1) {
+ return evaluator.contains(
+ null,
+ walkExpr(evaluator.op(), node.getChild(0)));
+ }
+ else {
+ return evaluator.contains(
+ walkExpr(evaluator.op(), node.getChild(0)),
+ walkExpr(evaluator.op(), node.getChild(1)));
+ }
+ case CmisQlStrictLexer.IN_FOLDER:
+ if (node.getChildCount() == 1) {
+ return evaluator.inFolder(
+ null,
+ walkExpr(evaluator.op(), node.getChild(0)));
+ }
+ else {
+ return evaluator.inFolder(
+ walkExpr(evaluator.op(), node.getChild(0)),
+ walkExpr(evaluator.op(), node.getChild(1)));
+ }
+ case CmisQlStrictLexer.IN_TREE:
+ if (node.getChildCount() == 1) {
+ return evaluator.inTree(
+ null,
+ walkExpr(evaluator.op(), node.getChild(0)));
+ }
+ else {
+ return evaluator.inTree(
+ walkExpr(evaluator.op(), node.getChild(0)),
+ walkExpr(evaluator.op(), node.getChild(1)));
+ }
+ default:
+ return walkOtherPredicate(evaluator, node);
+ }
+ }
+
+ private T walkExpr(Evaluator<T> evaluator, Tree node) {
+ switch (node.getType()) {
+ case CmisQlStrictLexer.BOOL_LIT:
+ return walkBoolean(evaluator, node);
+ case CmisQlStrictLexer.NUM_LIT:
+ return walkNumber(evaluator, node);
+ case CmisQlStrictLexer.STRING_LIT:
+ return walkString(evaluator, node);
+ case CmisQlStrictLexer.TIME_LIT:
+ return walkTimestamp(evaluator, node);
+ case CmisQlStrictLexer.IN_LIST:
+ return evaluator.list(walkList(evaluator, node));
+ case CmisQlStrictLexer.COL:
+ return walkCol(evaluator, node);
+ default:
+ return walkOtherExpr(evaluator, node);
+ }
+ }
+
+ private List<T> walkList(Evaluator<T> evaluator, Tree node) {
+ int n = node.getChildCount();
+ List<T> result = new ArrayList<T>(n);
+ for (int i = 0; i < n; i++) {
+ result.add(walkExpr(evaluator.op(), node.getChild(i)));
+ }
+ return result;
+ }
+
+ private T walkBoolean(Evaluator<T> evaluator, Tree node) {
+ String s = node.getText();
+
+ if ("true".equalsIgnoreCase(s)) {
+ return evaluator.value(true);
+ }
+ else if ("false".equalsIgnoreCase(s)) {
+ return evaluator.value(false);
+ }
+ else {
+ throw new CmisInvalidArgumentException("Not a boolean: " + s);
+ }
+ }
+
+ private T walkNumber(Evaluator<T> evaluator, Tree node) {
+ String s = node.getText();
+ try {
+ return s.contains(".") || s.contains("e") || s.contains("E")
+ ? evaluator.value(Double.valueOf(s))
+ : evaluator.value(Long.valueOf(s));
+ }
+ catch (NumberFormatException e) {
+ throw new CmisInvalidArgumentException("Not a number: " + s);
+ }
+ }
+
+ private T walkString(Evaluator<T> evaluator, Tree node) {
+ String s = node.getText();
+ s = s.substring(1, s.length() - 1);
+ return evaluator.value(s.replace("''", "'")); // un-escape quotes
+ }
+
+ private T walkTimestamp(Evaluator<T> evaluator, Tree node) {
+ String s = node.getText();
+ s = s.substring(s.indexOf('\'') + 1, s.length() - 1);
+ try {
+ return evaluator.value(CalendarHelper.fromString(s));
+ }
+ catch (IllegalArgumentException e) {
+ throw new CmisInvalidArgumentException("Not a date time value: " + s);
+ }
+ }
+
+ private T walkCol(Evaluator<T> evaluator, Tree node) {
+ return evaluator.col(node.getChild(0).getText());
+ }
+
+}
Added: chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/query/QueryTranslator.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/query/QueryTranslator.java?rev=1087664&view=auto
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/query/QueryTranslator.java (added)
+++ chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/query/QueryTranslator.java Fri Apr 1 11:59:46 2011
@@ -0,0 +1,259 @@
+/*
+ * 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.chemistry.opencmis.jcr.query;
+
+import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
+import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
+import org.apache.chemistry.opencmis.jcr.JcrTypeManager;
+import org.apache.chemistry.opencmis.server.support.query.QueryObject;
+import org.apache.chemistry.opencmis.server.support.query.QueryObject.SortSpec;
+import org.apache.chemistry.opencmis.server.support.query.QueryUtil;
+
+import java.util.List;
+
+/**
+ * Abstract base class for translating a CMIS query statement to a JCR XPath
+ * query statement.
+ * Overriding class need to implement methods for mapping CMIS ids to JCR paths,
+ * CMIS property names to JCR property names, CMIS type names to JCR type name and
+ * in addition a method for adding further constraints to a query based on a CMIS
+ * type.
+ */
+public abstract class QueryTranslator {
+ private final JcrTypeManager typeManager;
+ private final EvaluatorXPath evaluator;
+ private QueryObject queryObject;
+
+ /**
+ * Create a new query translator which uses the provided <code>typeManager</code>
+ * to resolve CMIS type names to CMIS types.
+ *
+ * @param typeManager
+ */
+ public QueryTranslator(JcrTypeManager typeManager) {
+ this.typeManager = typeManager;
+ evaluator = new EvaluatorXPath() {
+
+ @Override
+ protected String jcrPathFromId(String id) {
+ return QueryTranslator.this.jcrPathFromId(id);
+ }
+
+ @Override
+ protected String jcrPathFromCol(String name) {
+ return QueryTranslator.this.jcrPathFromCol(queryObject.getMainFromName(), name);
+ }
+ };
+ }
+
+ /**
+ * @return the {@link QueryObject} from the last translation performed through
+ * {@link QueryTranslator#translateToXPath(String)}.
+ */
+ public QueryObject getQueryObject() {
+ return queryObject;
+ }
+
+ /**
+ * Translate a CMIS query statement to a JCR XPath query statement.
+ *
+ * @param statement
+ * @return
+ */
+ public String translateToXPath(String statement) {
+ QueryUtil queryUtil = new QueryUtil();
+ queryObject = new QueryObject(typeManager);
+ ParseTreeWalker<XPathBuilder> parseTreeWalker = new ParseTreeWalker<XPathBuilder>(evaluator);
+ queryUtil.traverseStatementAndCatchExc(statement, queryObject, parseTreeWalker);
+
+ XPathBuilder parseResult = parseTreeWalker.getResult();
+ TypeDefinition fromType = getFromName(queryObject);
+
+ String pathExpression = buildPathExpression(fromType, getFolderPredicate(parseResult));
+ String elementTest = buildElementTest(fromType);
+ String predicates = buildPredicates(fromType, getCondition(parseResult));
+ String orderByClause = buildOrderByClause(fromType, queryObject.getOrderBys());
+ return "/jcr:root" + pathExpression + elementTest + predicates + orderByClause;
+ }
+
+ //------------------------------------------< protected >---
+
+ /**
+ * Map a CMIS objectId to an absolute JCR path. This method is called to
+ * resolve the folder if of folder predicates (i.e. IN_FOLDER, IN_TREE).
+ *
+ * @param id objectId
+ * @return absolute JCR path corresponding to <code>id</code>.
+ */
+ protected abstract String jcrPathFromId(String id);
+
+ /**
+ * Map a column name in the CMIS query to the corresponding relative JCR path.
+ * The path must be relative to the context node.
+ *
+ * @param fromType Type on which the CMIS query is performed
+ * @param name column name
+ * @return relative JCR path
+ */
+ protected abstract String jcrPathFromCol(TypeDefinition fromType, String name);
+
+ /**
+ * Map a CMIS type to the corresponding JCR type name.
+ * @see #jcrTypeCondition(TypeDefinition)
+ *
+ * @param fromType CMIS type
+ * @return name of the JCR type corresponding to <code>fromType</code>
+ */
+ protected abstract String jcrTypeName(TypeDefinition fromType);
+
+ /**
+ * Create and additional condition in order for the query to only return nodes
+ * of the right type. This condition and-ed to the condition determined by the
+ * CMIS query's where clause.
+ * <p/>
+ * A CMIS query for non versionable documents should for example result in the
+ * following XPath query:
+ * <p/>
+ * <pre>
+ * element(*, nt:file)[not(@jcr:mixinTypes = 'mix:simpleVersionable')]
+ * </pre>
+ * Here the element test is covered by {@link #jcrTypeName(TypeDefinition)}
+ * while the predicate is covered by this method.
+ *
+ * @see #jcrTypeName(TypeDefinition)
+ *
+ * @param fromType
+ * @return Additional condition or <code>null</code> if none.
+ */
+ protected abstract String jcrTypeCondition(TypeDefinition fromType);
+
+ /**
+ * Build a XPath path expression for the CMIS type queried for and a folder predicate.
+ *
+ * @param fromType CMIS type queried for
+ * @param folderPredicate folder predicate
+ * @return a valid XPath path expression or <code>null</code> if none.
+ */
+ protected String buildPathExpression(TypeDefinition fromType, String folderPredicate) {
+ return folderPredicate == null ? "//" : folderPredicate;
+ }
+
+ /**
+ * Build a XPath element test for the given CMIS type.
+ *
+ * @param fromType CMIS type queried for
+ * @return a valid XPath element test.
+ */
+ protected String buildElementTest(TypeDefinition fromType) {
+ return "element(*," + jcrTypeName(fromType) + ")";
+ }
+
+ /**
+ * Build a XPath predicate for the given CMIS type and an additional condition.
+ * The additional condition should be and-ed to the condition resulting from
+ * evaluating <code>fromType</code>.
+ *
+ * @param fromType CMIS type queried for
+ * @param condition additional condition.
+ * @return a valid XPath predicate or <code>null</code> if none.
+ */
+ protected String buildPredicates(TypeDefinition fromType, String condition) {
+ String typeCondition = jcrTypeCondition(fromType);
+
+ if (typeCondition == null) {
+ return condition == null ? "" : "[" + condition + "]";
+ }
+ else if (condition == null) {
+ return "[" + typeCondition + "]";
+ }
+ else {
+ return "[" + typeCondition + " and " + condition + "]";
+ }
+ }
+
+ /**
+ * Build a XPath order by clause for the given CMIS type and a list of {@link SortSpec}s.
+ *
+ * @param fromType CMIS type queried for
+ * @param orderBys <code>SortSpec</code>s
+ * @return a valid XPath order by clause
+ */
+ protected String buildOrderByClause(TypeDefinition fromType, List<SortSpec> orderBys) {
+ StringBuilder orderSpecs = new StringBuilder();
+
+ for (SortSpec orderBy : orderBys) {
+ String selector = jcrPathFromCol(fromType, orderBy.getSelector().getName());
+ boolean ascending = orderBy.isAscending();
+
+ if (orderSpecs.length() > 0) {
+ orderSpecs.append(',');
+ }
+
+ orderSpecs
+ .append(selector)
+ .append(' ')
+ .append(ascending ? "ascending" : "descending");
+ }
+
+ return orderSpecs.length() > 0
+ ? "order by " + orderSpecs
+ : "";
+ }
+
+ //------------------------------------------< private >---
+
+ private static String getFolderPredicate(XPathBuilder parseResult) {
+ if (parseResult == null) {
+ return null;
+ }
+
+ String folderPredicate = null;
+ for (XPathBuilder p : parseResult.folderPredicates()) {
+ if (folderPredicate == null) {
+ folderPredicate = p.xPath();
+ }
+ else {
+ throw new CmisInvalidArgumentException("Query may only contain a single folder predicate");
+ }
+ }
+
+ // See the class comment on XPathBuilder for details on affirmative literals
+ if (folderPredicate != null && // IF has single folder predicate
+ !Boolean.FALSE.equals(parseResult.eval(false))) { // AND folder predicate is not affirmative
+ throw new CmisInvalidArgumentException("Folder predicate " + folderPredicate + " is not affirmative.");
+ }
+
+ return folderPredicate;
+ }
+
+ private static TypeDefinition getFromName(QueryObject queryObject) {
+ if (queryObject.getTypes().size() != 1) {
+ throw new CmisInvalidArgumentException("From must contain one single reference");
+ }
+ return queryObject.getMainFromName();
+ }
+
+ private static String getCondition(XPathBuilder parseResult) {
+ // No condition if either parseResult is null or when it evaluates to true under
+ // the valuation which assigns true to the folder predicate.
+ return parseResult == null || Boolean.TRUE.equals(parseResult.eval(true)) ? null : parseResult.xPath();
+ }
+
+}
Added: chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/query/XPathBuilder.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/query/XPathBuilder.java?rev=1087664&view=auto
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/query/XPathBuilder.java (added)
+++ chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/query/XPathBuilder.java Fri Apr 1 11:59:46 2011
@@ -0,0 +1,61 @@
+/*
+ * 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.chemistry.opencmis.jcr.query;
+
+/**
+ * This result type of {@link EvaluatorXPath} provides means for partially evaluating
+ * the underlying query's condition. This allows to determine whether there is a semantically
+ * equivalent translation from the CMIS query's where clause to an XPath condition.
+ * <br/>
+ * Specifically <code>EvaluatorXPath</code> only supports a single folder predicate. That
+ * is the original CMIS query must not contain more than one IN_TREE or IN_FOLDER
+ * predicate respectively. Furthermore that single folder predicate must be affirmative.
+ * A literal <code>p</code> in a boolean expression <code>X</code> is affirmative if there
+ * exists a boolean expression <code>Y</code> such that <code>p ∧ Y = X</code>.
+ * <em>Note</em>: a single folder predicate is affirmative if any only if
+ * {@link #eval(Boolean) <code>eval(false)</code>} return <code>false</code>.
+ * <br/>
+ * Only if both conditions hold will the XPath translation provided the {@link #xPath()}
+ * method be valid.
+ */
+public interface XPathBuilder {
+
+ /**
+ * Translation of the underlying CMIS query's where clause to a XPath condition.
+ * The string is only valid if there is no more than one folder predicate and
+ * the folder predicate is in affirmative position.
+ */
+ String xPath();
+
+ /**
+ * Evaluate the query condition for a given valuation of the folder predicate terms.
+ *
+ * @param folderPredicateValuation valuation for the folder predicate terms. Use <code>null</code>
+ * for none.
+ * @return result of the partial evaluation. <code>null</code> means that the value of the
+ * query condition is not determined the value passed for <code>folderPredicateValuation</code>.
+ */
+ Boolean eval(Boolean folderPredicateValuation);
+
+ /**
+ * The folder predicates contained in this query's condition.
+ */
+ Iterable<XPathBuilder> folderPredicates();
+}
Added: chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/util/ISO8601.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/util/ISO8601.java?rev=1087664&view=auto
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/util/ISO8601.java (added)
+++ chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/util/ISO8601.java Fri Apr 1 11:59:46 2011
@@ -0,0 +1,320 @@
+/*
+ * 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.chemistry.opencmis.jcr.util;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+/**
+ * The <code>ISO8601</code> utility class provides helper methods
+ * to deal with date/time formatting using a specific ISO8601-compliant
+ * format (see <a href="http://www.w3.org/TR/NOTE-datetime">ISO 8601</a>).
+ * <p/>
+ * The currently supported format is:
+ * <pre>
+ * ±YYYY-MM-DDThh:mm:ss.SSSTZD
+ * </pre>
+ * where:
+ * <pre>
+ * ±YYYY = four-digit year with optional sign where values <= 0 are
+ * denoting years BCE and values > 0 are denoting years CE,
+ * e.g. -0001 denotes the year 2 BCE, 0000 denotes the year 1 BCE,
+ * 0001 denotes the year 1 CE, and so on...
+ * MM = two-digit month (01=January, etc.)
+ * DD = two-digit day of month (01 through 31)
+ * hh = two digits of hour (00 through 23) (am/pm NOT allowed)
+ * mm = two digits of minute (00 through 59)
+ * ss = two digits of second (00 through 59)
+ * SSS = three digits of milliseconds (000 through 999)
+ * TZD = time zone designator, Z for Zulu (i.e. UTC) or an offset from UTC
+ * in the form of +hh:mm or -hh:mm
+ * </pre>
+ * <p/>
+ * <em>Note:</em> This class is copied from org.apache.jackrabbit.util.ISO8601
+ */
+public final class ISO8601 {
+ private ISO8601() { }
+
+ /**
+ * Parses an ISO8601-compliant date/time string.
+ *
+ * @param text the date/time string to be parsed
+ * @return a <code>Calendar</code>, or <code>null</code> if the input could
+ * not be parsed
+ * @throws IllegalArgumentException if a <code>null</code> argument is passed
+ */
+ public static Calendar parse(String text) {
+ if (text == null) {
+ throw new IllegalArgumentException("argument can not be null");
+ }
+
+ // check optional leading sign
+ char sign;
+ int start;
+ if (text.startsWith("-")) {
+ sign = '-';
+ start = 1;
+ } else if (text.startsWith("+")) {
+ sign = '+';
+ start = 1;
+ } else {
+ sign = '+'; // no sign specified, implied '+'
+ start = 0;
+ }
+
+ /**
+ * the expected format of the remainder of the string is:
+ * YYYY-MM-DDThh:mm:ss.SSSTZD
+ *
+ * note that we cannot use java.text.SimpleDateFormat for
+ * parsing because it can't handle years <= 0 and TZD's
+ */
+
+ int year, month, day, hour, min, sec, ms;
+ String tzID;
+ try {
+ // year (YYYY)
+ year = Integer.parseInt(text.substring(start, start + 4));
+ start += 4;
+ // delimiter '-'
+ if (text.charAt(start) != '-') {
+ return null;
+ }
+ start++;
+ // month (MM)
+ month = Integer.parseInt(text.substring(start, start + 2));
+ start += 2;
+ // delimiter '-'
+ if (text.charAt(start) != '-') {
+ return null;
+ }
+ start++;
+ // day (DD)
+ day = Integer.parseInt(text.substring(start, start + 2));
+ start += 2;
+ // delimiter 'T'
+ if (text.charAt(start) != 'T') {
+ return null;
+ }
+ start++;
+ // hour (hh)
+ hour = Integer.parseInt(text.substring(start, start + 2));
+ start += 2;
+ // delimiter ':'
+ if (text.charAt(start) != ':') {
+ return null;
+ }
+ start++;
+ // minute (mm)
+ min = Integer.parseInt(text.substring(start, start + 2));
+ start += 2;
+ // delimiter ':'
+ if (text.charAt(start) != ':') {
+ return null;
+ }
+ start++;
+ // second (ss)
+ sec = Integer.parseInt(text.substring(start, start + 2));
+ start += 2;
+ // delimiter '.'
+ if (text.charAt(start) != '.') {
+ return null;
+ }
+ start++;
+ // millisecond (SSS)
+ ms = Integer.parseInt(text.substring(start, start + 3));
+ start += 3;
+ // time zone designator (Z or +00:00 or -00:00)
+ if (text.charAt(start) == '+' || text.charAt(start) == '-') {
+ // offset to UTC specified in the format +00:00/-00:00
+ tzID = "GMT" + text.substring(start);
+ } else if (text.substring(start).equals("Z")) {
+ tzID = "GMT";
+ } else {
+ // invalid time zone designator
+ return null;
+ }
+ } catch (IndexOutOfBoundsException e) {
+ return null;
+ } catch (NumberFormatException e) {
+ return null;
+ }
+
+ TimeZone tz = TimeZone.getTimeZone(tzID);
+ // verify id of returned time zone (getTimeZone defaults to "GMT")
+ if (!tz.getID().equals(tzID)) {
+ // invalid time zone
+ return null;
+ }
+
+ // initialize Calendar object
+ Calendar cal = Calendar.getInstance(tz);
+ cal.setLenient(false);
+ // year and era
+ if (sign == '-' || year == 0) {
+ // not CE, need to set era (BCE) and adjust year
+ cal.set(Calendar.YEAR, year + 1);
+ cal.set(Calendar.ERA, GregorianCalendar.BC);
+ } else {
+ cal.set(Calendar.YEAR, year);
+ cal.set(Calendar.ERA, GregorianCalendar.AD);
+ }
+ // month (0-based!)
+ cal.set(Calendar.MONTH, month - 1);
+ // day of month
+ cal.set(Calendar.DAY_OF_MONTH, day);
+ // hour
+ cal.set(Calendar.HOUR_OF_DAY, hour);
+ // minute
+ cal.set(Calendar.MINUTE, min);
+ // second
+ cal.set(Calendar.SECOND, sec);
+ // millisecond
+ cal.set(Calendar.MILLISECOND, ms);
+
+ try {
+ /**
+ * the following call will trigger an IllegalArgumentException
+ * if any of the set values are illegal or out of range
+ */
+ cal.getTime();
+ /**
+ * in addition check the validity of the year
+ */
+ getYear(cal);
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+
+ return cal;
+ }
+
+ /**
+ * Formats a <code>Calendar</code> value into an ISO8601-compliant
+ * date/time string.
+ *
+ * @param cal the time value to be formatted into a date/time string.
+ * @return the formatted date/time string.
+ * @throws IllegalArgumentException if a <code>null</code> argument is passed
+ * or the calendar cannot be represented as defined by ISO 8601 (i.e. year
+ * with more than four digits).
+ */
+ public static String format(Calendar cal) throws IllegalArgumentException {
+ if (cal == null) {
+ throw new IllegalArgumentException("argument can not be null");
+ }
+
+ /**
+ * the format of the date/time string is:
+ * YYYY-MM-DDThh:mm:ss.SSSTZD
+ *
+ * note that we cannot use java.text.SimpleDateFormat for
+ * formatting because it can't handle years <= 0 and TZD's
+ */
+ StringBuffer buf = new StringBuffer();
+ // year ([-]YYYY)
+ appendZeroPaddedInt(buf, getYear(cal), 4);
+ buf.append('-');
+ // month (MM)
+ appendZeroPaddedInt(buf, cal.get(Calendar.MONTH) + 1, 2);
+ buf.append('-');
+ // day (DD)
+ appendZeroPaddedInt(buf, cal.get(Calendar.DAY_OF_MONTH), 2);
+ buf.append('T');
+ // hour (hh)
+ appendZeroPaddedInt(buf, cal.get(Calendar.HOUR_OF_DAY), 2);
+ buf.append(':');
+ // minute (mm)
+ appendZeroPaddedInt(buf, cal.get(Calendar.MINUTE), 2);
+ buf.append(':');
+ // second (ss)
+ appendZeroPaddedInt(buf, cal.get(Calendar.SECOND), 2);
+ buf.append('.');
+ // millisecond (SSS)
+ appendZeroPaddedInt(buf, cal.get(Calendar.MILLISECOND), 3);
+ // time zone designator (Z or +00:00 or -00:00)
+ TimeZone tz = cal.getTimeZone();
+ // determine offset of timezone from UTC (incl. daylight saving)
+ int offset = tz.getOffset(cal.getTimeInMillis());
+ if (offset == 0) {
+ buf.append('Z');
+ }
+ else {
+ int hours = Math.abs(offset / (60 * 1000) / 60);
+ int minutes = Math.abs(offset / (60 * 1000) % 60);
+ buf.append(offset < 0 ? '-' : '+');
+ appendZeroPaddedInt(buf, hours, 2);
+ buf.append(':');
+ appendZeroPaddedInt(buf, minutes, 2);
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Returns the astronomical year of the given calendar.
+ *
+ * @param cal a calendar instance.
+ * @return the astronomical year.
+ * @throws IllegalArgumentException if calendar cannot be represented as
+ * defined by ISO 8601 (i.e. year with more
+ * than four digits).
+ */
+ public static int getYear(Calendar cal) throws IllegalArgumentException {
+ // determine era and adjust year if necessary
+ int year = cal.get(Calendar.YEAR);
+ if (cal.isSet(Calendar.ERA)
+ && cal.get(Calendar.ERA) == GregorianCalendar.BC) {
+
+ // calculate year using astronomical system:
+ // year n BCE => astronomical year -n + 1
+ year = 0 - year + 1;
+ }
+
+ if (year > 9999 || year < -9999) {
+ throw new IllegalArgumentException("Calendar has more than four " +
+ "year digits, cannot be formatted as ISO8601: " + year);
+ }
+ return year;
+ }
+
+ /**
+ * Appends a zero-padded number to the given string buffer.
+ * <p/>
+ * This is an internal helper method which doesn't perform any
+ * validation on the given arguments.
+ *
+ * @param buf String buffer to append to
+ * @param n number to append
+ * @param precision number of digits to append
+ */
+ private static void appendZeroPaddedInt(StringBuffer buf, int n, int precision) {
+ if (n < 0) {
+ buf.append('-');
+ n = -n;
+ }
+
+ for (int exp = precision - 1; exp > 0; exp--) {
+ if (n < Math.pow(10, exp)) {
+ buf.append('0');
+ } else {
+ break;
+ }
+ }
+ buf.append(n);
+ }
+}
Added: chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/util/Iterables.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/util/Iterables.java?rev=1087664&view=auto
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/util/Iterables.java (added)
+++ chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/util/Iterables.java Fri Apr 1 11:59:46 2011
@@ -0,0 +1,57 @@
+/*
+ * 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.chemistry.opencmis.jcr.util;
+
+import org.apache.commons.collections.iterators.EmptyIterator;
+import org.apache.commons.collections.iterators.IteratorChain;
+import org.apache.commons.collections.iterators.SingletonIterator;
+
+import java.util.Iterator;
+
+public class Iterables {
+ private Iterables() {}
+
+ public static <T> Iterable<T> concat(final Iterable<T> it1, final Iterable<T> it2) {
+ return new Iterable<T>() {
+ @SuppressWarnings("unchecked")
+ public Iterator<T> iterator() {
+ return new IteratorChain(it1.iterator(), it2.iterator());
+ }
+ };
+ }
+
+ public static <T> Iterable<T> singleton(final T element) {
+ return new Iterable<T>() {
+ @SuppressWarnings("unchecked")
+ public Iterator<T> iterator() {
+ return new SingletonIterator(element);
+ }
+ };
+ }
+
+ public static <T> Iterable<T> empty() {
+ return new Iterable<T>() {
+ @SuppressWarnings("unchecked")
+ public Iterator<T> iterator() {
+ return EmptyIterator.INSTANCE;
+ }
+ };
+ }
+}
Modified: chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/util/Util.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/util/Util.java?rev=1087664&r1=1087663&r2=1087664&view=diff
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/util/Util.java (original)
+++ chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/main/java/org/apache/chemistry/opencmis/jcr/util/Util.java Fri Apr 1 11:59:46 2011
@@ -82,4 +82,13 @@ public final class Util {
return result.toString();
}
+ /**
+ * Escapes a JCR path such that it can be used in a XPath query
+ * @param path
+ * @return escaped path
+ */
+ public static String escape(String path) {
+ return replace(path, " ", "_x0020_"); // fixme do more thorough escaping of path
+ }
+
}
Added: chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/test/java/org/apache/chemistry/opencmis/jcr/query/QueryTranslatorTest.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/test/java/org/apache/chemistry/opencmis/jcr/query/QueryTranslatorTest.java?rev=1087664&view=auto
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/test/java/org/apache/chemistry/opencmis/jcr/query/QueryTranslatorTest.java (added)
+++ chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-jcr/src/test/java/org/apache/chemistry/opencmis/jcr/query/QueryTranslatorTest.java Fri Apr 1 11:59:46 2011
@@ -0,0 +1,295 @@
+/*
+ * 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.chemistry.opencmis.jcr.query;
+
+import org.antlr.runtime.RecognitionException;
+import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
+import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
+import org.apache.chemistry.opencmis.commons.exceptions.CmisNotSupportedException;
+import org.apache.chemistry.opencmis.jcr.JcrTypeManager;
+import org.apache.chemistry.opencmis.jcr.util.ISO8601;
+import org.apache.chemistry.opencmis.server.support.query.CalendarHelper;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.GregorianCalendar;
+
+import static org.junit.Assert.*;
+
+public class QueryTranslatorTest {
+ private String jcrTypeCondition;
+
+ QueryTranslator queryTranslator = new QueryTranslator(new JcrTypeManager()) {
+ @Override
+ protected String jcrPathFromId(String id) {
+ assertNotNull(id);
+ return "/jcr:" + id;
+ }
+
+ @Override
+ protected String jcrPathFromCol(TypeDefinition fromType, String name) {
+ assertNotNull(fromType);
+ assertNotNull(name);
+ return name.replace("cmis:", "@jcr:");
+ }
+
+ @Override
+ protected String jcrTypeName(TypeDefinition fromType) {
+ assertNotNull(fromType);
+ return fromType.getQueryName().replace("cmis:", "jcr:");
+ }
+
+ @Override
+ protected String jcrTypeCondition(TypeDefinition fromType) {
+ assertNotNull(fromType);
+ return jcrTypeCondition;
+ }
+ };
+
+ @Test
+ public void testQueryTranslator() {
+ assertEquals(
+ "/jcr:root//element(*,jcr:document)",
+ queryTranslator.translateToXPath("select * from cmis:document"));
+
+ assertEquals(
+ "/jcr:root//element(*,jcr:document)[@jcr:isLatestVersion = 'foo']",
+ queryTranslator.translateToXPath("select * from cmis:document where cmis:isLatestVersion='foo'"));
+
+ assertEquals(
+ "/jcr:root//element(*,jcr:document)[jcr:like(@jcr:isLatestVersion, 'foo')]",
+ queryTranslator.translateToXPath("select * from cmis:document where cmis:isLatestVersion LIKE 'foo'"));
+
+ assertEquals(
+ "/jcr:root//element(*,jcr:document)[not(jcr:like(@jcr:isLatestVersion, 'foo'))]",
+ queryTranslator.translateToXPath(
+ "select * from cmis:document where cmis:isLatestVersion NOT LIKE 'foo'"));
+
+ assertEquals(
+ "/jcr:root//element(*,jcr:document)[@jcr:isLatestVersion = 'foo' and @jcr:name != 'baz']",
+ queryTranslator.translateToXPath(
+ "select * from cmis:document where cmis:isLatestVersion='foo' AND cmis:name<>'baz'"));
+
+ assertEquals(
+ "/jcr:root//element(*,jcr:document)[not((@jcr:isLatestVersion > 'foo' or @jcr:name < 1.0))]",
+ queryTranslator.translateToXPath(
+ "select * from cmis:document where NOT (cmis:isLatestVersion>'foo' OR cmis:name< 1.0)"));
+
+
+ assertEquals(
+ "/jcr:root//element(*,jcr:document)[(@jcr:name = 'foo' or @jcr:objectId = 'baz' and @jcr:createdBy = 'bar')]",
+ queryTranslator.translateToXPath(
+ "select * from cmis:document where cmis:name = 'foo' or cmis:objectId = 'baz' " +
+ "and cmis:createdBy = 'bar'"));
+
+ assertEquals(
+ "/jcr:root//element(*,jcr:document)[(@jcr:name = 'foo' and @jcr:objectId = 'baz' or @jcr:createdBy = 'bar')]",
+ queryTranslator.translateToXPath(
+ "select * from cmis:document where cmis:name = 'foo' and cmis:objectId = 'baz' " +
+ "or cmis:createdBy = 'bar'"));
+
+ assertEquals(
+ "/jcr:root//element(*,jcr:document)[@jcr:name = 'foo' and (@jcr:objectId = 'baz' or @jcr:createdBy = 'bar')]",
+ queryTranslator.translateToXPath(
+ "select * from cmis:document where cmis:name = 'foo' and (cmis:objectId = 'baz' " +
+ "or cmis:createdBy = 'bar')"));
+
+ assertEquals(
+ "/jcr:root/jcr:folderId/element(*,jcr:document)",
+ queryTranslator.translateToXPath(
+ "select * from cmis:document where IN_FOLDER('folderId')"));
+
+ assertEquals(
+ "/jcr:root/jcr:folderId/element(*,jcr:document)",
+ queryTranslator.translateToXPath("select * from cmis:document where not(not(IN_FOLDER('folderId')))"));
+
+ assertEquals(
+ "/jcr:root/jcr:folderId//element(*,jcr:document)",
+ queryTranslator.translateToXPath("select * from cmis:document where IN_TREE('folderId')"));
+
+ assertEquals(
+ "/jcr:root/jcr:folderId//element(*,jcr:document)",
+ queryTranslator.translateToXPath("select * from cmis:document where not(not(IN_TREE('folderId')))"));
+
+ assertEquals(
+ "/jcr:root/jcr:folderId/element(*,jcr:document)[@jcr:name <= 1]",
+ queryTranslator.translateToXPath(
+ "select * from cmis:document where IN_FOLDER('folderId') AND cmis:name <= 1"));
+
+ assertEquals(
+ "/jcr:root/jcr:folderId//element(*,jcr:document)[@jcr:name >= 'name' and @jcr:name = true]",
+ queryTranslator.translateToXPath(
+ "select * from cmis:document where IN_TREE('folderId') AND cmis:name >= 'name' " +
+ "AND cmis:name = TRUE"));
+
+ GregorianCalendar date = new GregorianCalendar();
+ assertEquals(
+ "/jcr:root/jcr:folderId/element(*,jcr:document)[not(@jcr:creationDate = xs:dateTime('" +
+ ISO8601.format(date) + "'))]",
+ queryTranslator.translateToXPath(
+ "select * from cmis:document where NOT(NOT IN_FOLDER('folderId') OR cmis:creationDate = TIMESTAMP '" +
+ CalendarHelper.toString(date) + "')"));
+
+ // fixme: need fix for CMIS-344
+ assertEquals(
+ "/jcr:root//element(*,jcr:document)[jcr:contains(., '\u4E2D\u6587')]",
+ queryTranslator.translateToXPath("select * from cmis:document where contains('\u4E2D\u6587')"));
+ }
+
+ @Test
+ public void testQueryWithOrderBy() {
+ assertEquals(
+ "/jcr:root//element(*,jcr:document)order by @jcr:name ascending",
+ queryTranslator.translateToXPath("select * from cmis:document order by cmis:name"));
+
+ assertEquals(
+ "/jcr:root//element(*,jcr:document)[@jcr:isLatestVersion = 'foo']order by @jcr:name descending",
+ queryTranslator.translateToXPath(
+ "select * from cmis:document where cmis:isLatestVersion='foo' order by cmis:name desc"));
+
+ assertEquals(
+ "/jcr:root//element(*,jcr:document)[jcr:like(@jcr:isLatestVersion, 'foo')]order by @jcr:name ascending," +
+ "@jcr:objectId descending",
+ queryTranslator.translateToXPath(
+ "select * from cmis:document where cmis:isLatestVersion LIKE 'foo' order by cmis:name asc, cmis:objectId desc"));
+ }
+
+ @Test
+ public void testQueryTranslatorWithTypeCondition() {
+ jcrTypeCondition = "@jcr:primaryType = nt:base";
+
+ assertEquals(
+ "/jcr:root//element(*,jcr:document)[@jcr:primaryType = nt:base]",
+ queryTranslator.translateToXPath("select * from cmis:document"));
+
+ assertEquals(
+ "/jcr:root//element(*,jcr:document)[@jcr:primaryType = nt:base and @jcr:isLatestVersion = 'foo']",
+ queryTranslator.translateToXPath("select * from cmis:document where cmis:isLatestVersion='foo'"));
+
+ assertEquals(
+ "/jcr:root/jcr:folderId/element(*,jcr:document)[@jcr:primaryType = nt:base]",
+ queryTranslator.translateToXPath("select * from cmis:document where IN_FOLDER('folderId')"));
+
+ assertEquals(
+ "/jcr:root/jcr:folderId/element(*,jcr:document)[@jcr:primaryType = nt:base]",
+ queryTranslator.translateToXPath("select * from cmis:document where not(not(IN_FOLDER('folderId')))"));
+
+ assertEquals(
+ "/jcr:root/jcr:folderId//element(*,jcr:document)[@jcr:primaryType = nt:base]",
+ queryTranslator.translateToXPath("select * from cmis:document where IN_TREE('folderId')"));
+
+ assertEquals(
+ "/jcr:root/jcr:folderId//element(*,jcr:document)[@jcr:primaryType = nt:base]",
+ queryTranslator.translateToXPath("select * from cmis:document where not(not(IN_TREE('folderId')))"));
+
+ assertEquals(
+ "/jcr:root/jcr:folderId/element(*,jcr:document)[@jcr:primaryType = nt:base and @jcr:name <= 1]",
+ queryTranslator.translateToXPath(
+ "select * from cmis:document where IN_FOLDER('folderId') AND cmis:name <= 1"));
+ }
+
+ @Test
+ public void testQueryTranslatorQueryTooSpecific() {
+ try {
+ queryTranslator.translateToXPath(
+ "select * from cmis:document where NOT IN_FOLDER('folderId')");
+ fail();
+ }
+ catch (CmisInvalidArgumentException expected) { }
+
+ try {
+ queryTranslator.translateToXPath(
+ "select * from cmis:document where NOT(NOT IN_FOLDER('folderId') AND cmis:name = 'name')");
+ fail();
+ }
+ catch (CmisInvalidArgumentException expected) { }
+
+ try {
+ queryTranslator.translateToXPath(
+ "select * from cmis:document where IN_FOLDER('folderId') OR cmis:name = 'name'");
+ fail();
+ }
+ catch (CmisInvalidArgumentException expected) { }
+
+ try {
+ queryTranslator.translateToXPath(
+ "select * from cmis:document where NOT(IN_FOLDER('folderId') AND cmis:name = 'name')");
+ fail();
+ }
+ catch (CmisInvalidArgumentException expected) { }
+
+ try {
+ queryTranslator.translateToXPath(
+ "select * from cmis:document where IN_FOLDER('folder1Id') OR IN_TREE('folder2Id')");
+ fail();
+ }
+ catch (CmisInvalidArgumentException expected) { }
+
+ try {
+ queryTranslator.translateToXPath(
+ "select * from cmis:document where IN_FOLDER('folder1Id') AND NOT IN_TREE('folder2Id')");
+ fail();
+ }
+ catch (CmisInvalidArgumentException expected) { }
+ }
+
+ @Test
+ public void testNotImplemented() throws IOException, RecognitionException {
+ try {
+ queryTranslator.translateToXPath("select * from cmis:document where cmis:name in (1,2,3)");
+ fail();
+ }
+ catch (CmisNotSupportedException expected) {}
+
+ try {
+ queryTranslator.translateToXPath("select * from cmis:document where 'foo' = ANY cmis:name");
+ fail();
+ }
+ catch (CmisNotSupportedException expected) {}
+ }
+
+ @Test
+ public void testInvalidQuery() throws IOException, RecognitionException {
+ try {
+ queryTranslator.translateToXPath("");
+ fail();
+ }
+ catch (CmisInvalidArgumentException expected) {}
+
+ try {
+ queryTranslator.translateToXPath("select * from cmis:something");
+ fail();
+ }
+ catch (CmisInvalidArgumentException expected) {}
+
+ try {
+ queryTranslator.translateToXPath("select * from cmis:document WHERE");
+ fail();
+ }
+ catch (CmisInvalidArgumentException expected) {}
+
+ try {
+ queryTranslator.translateToXPath("select * from cmis:document WHERE cmis:something = 'foo'");
+ fail();
+ }
+ catch (CmisInvalidArgumentException expected) {}
+ }
+
+}