Return-Path: X-Original-To: apmail-chemistry-commits-archive@www.apache.org Delivered-To: apmail-chemistry-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id A371010FC6 for ; Fri, 21 Mar 2014 16:13:25 +0000 (UTC) Received: (qmail 88635 invoked by uid 500); 21 Mar 2014 16:13:24 -0000 Delivered-To: apmail-chemistry-commits-archive@chemistry.apache.org Received: (qmail 88607 invoked by uid 500); 21 Mar 2014 16:13:22 -0000 Mailing-List: contact commits-help@chemistry.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@chemistry.apache.org Delivered-To: mailing list commits@chemistry.apache.org Received: (qmail 88417 invoked by uid 99); 21 Mar 2014 16:13:20 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 21 Mar 2014 16:13:20 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 21 Mar 2014 16:13:16 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id 8A876238897A; Fri, 21 Mar 2014 16:12:53 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1579967 - in /chemistry/opencmis/trunk: chemistry-opencmis-client/chemistry-opencmis-client-api/src/main/java/org/apache/chemistry/opencmis/client/api/ chemistry-opencmis-client/chemistry-opencmis-client-impl/src/main/java/org/apache/chemi... Date: Fri, 21 Mar 2014 16:12:53 -0000 To: commits@chemistry.apache.org From: fmui@apache.org X-Mailer: svnmailer-1.0.9 Message-Id: <20140321161253.8A876238897A@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: fmui Date: Fri Mar 21 16:12:52 2014 New Revision: 1579967 URL: http://svn.apache.org/r1579967 Log: added a new Session.createQueryStatement() method that simplifies secondary type JOINs Added: chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/StringListBuilder.java (with props) Modified: chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-api/src/main/java/org/apache/chemistry/opencmis/client/api/Session.java chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-impl/src/main/java/org/apache/chemistry/opencmis/client/runtime/QueryStatementImpl.java chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-impl/src/main/java/org/apache/chemistry/opencmis/client/runtime/SessionImpl.java Modified: chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-api/src/main/java/org/apache/chemistry/opencmis/client/api/Session.java URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-api/src/main/java/org/apache/chemistry/opencmis/client/api/Session.java?rev=1579967&r1=1579966&r2=1579967&view=diff ============================================================================== --- chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-api/src/main/java/org/apache/chemistry/opencmis/client/api/Session.java (original) +++ chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-api/src/main/java/org/apache/chemistry/opencmis/client/api/Session.java Fri Mar 21 16:12:52 2014 @@ -20,6 +20,7 @@ package org.apache.chemistry.opencmis.cl import java.io.Serializable; import java.math.BigInteger; +import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.Map; @@ -621,6 +622,13 @@ public interface Session extends Seriali /** * Creates a query statement. + *

+ * Sample code: + * + *

+     * QueryStatement stmt = session
+     *         .createQueryStatement("SELECT ?, ? FROM ? WHERE ? > TIMESTAMP ? AND IN_FOLDER(?) OR ? IN (?)");
+     * 
* * @param statement * the query statement with placeholders ('?'), see @@ -635,6 +643,58 @@ public interface Session extends Seriali QueryStatement createQueryStatement(String statement); /** + * Creates a query statement for a query of one primary type joined by zero + * or more secondary types. + *

+ * Sample code: + * + *

+     * List<String> select = new ArrayList<String>();
+     * select.add("cmis:name");
+     * select.add("SecondaryStringProp");
+     * 
+     * Map<String, String> from = new HashMap<String, String>();
+     * from.put("d", "cmis:document");
+     * from.put("s", "MySecondaryType");
+     * 
+     * String where = "d.cmis:name LIKE ?";
+     * 
+     * List<String> orderBy = new ArrayList<String>();
+     * orderBy.add("cmis:name");
+     * orderBy.add("SecondaryIntegerProp");
+     * 
+     * QueryStatement stmt = session.createQueryStatement(select, from, where, orderBy);
+     * 
+ * + * Generates something like this: + * + *
+     * SELECT d.cmis:name,s.SecondaryStringProp FROM cmis:document AS d JOIN MySecondaryType AS s ON d.cmis:objectId=s.cmis:objectId WHERE d.cmis:name LIKE ? ORDER BY d.cmis:name,s.SecondaryIntegerProp
+     * 
+ * + * @param selectPropertyIds + * the property IDs in the SELECT statement, if {@code null} all + * properties are selected + * @param fromTypes + * a Map of type aliases (keys) and type IDs (values), the Map + * must contain exactly one primary type and zero or more + * secondary types + * @param whereClause + * an optional WHERE clause with placeholders ('?'), see + * {@link QueryStatement} for details + * @param orderByPropertyIds + * an optional list of properties IDs for the ORDER BY clause + * + * @return a new query statement object + * + * @see QueryStatement + * + * @cmis 1.0 + */ + QueryStatement createQueryStatement(Collection selectPropertyIds, Map fromTypes, + String whereClause, List orderByPropertyIds); + + /** * Returns the content changes. * * @param changeLogToken Modified: chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-impl/src/main/java/org/apache/chemistry/opencmis/client/runtime/QueryStatementImpl.java URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-impl/src/main/java/org/apache/chemistry/opencmis/client/runtime/QueryStatementImpl.java?rev=1579967&r1=1579966&r2=1579967&view=diff ============================================================================== --- chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-impl/src/main/java/org/apache/chemistry/opencmis/client/runtime/QueryStatementImpl.java (original) +++ chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-impl/src/main/java/org/apache/chemistry/opencmis/client/runtime/QueryStatementImpl.java Fri Mar 21 16:12:52 2014 @@ -21,8 +21,11 @@ import java.net.URI; import java.net.URL; import java.text.SimpleDateFormat; import java.util.Calendar; +import java.util.Collection; import java.util.Date; import java.util.HashMap; +import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.TimeZone; @@ -34,6 +37,8 @@ import org.apache.chemistry.opencmis.cli import org.apache.chemistry.opencmis.client.api.QueryStatement; import org.apache.chemistry.opencmis.client.api.Session; import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition; +import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; +import org.apache.chemistry.opencmis.commons.impl.StringListBuilder; /** * QueryStatement implementation. @@ -42,8 +47,17 @@ public class QueryStatementImpl implemen private final Session session; private final String statement; - private final Map parametersMap; + private final Map parametersMap = new HashMap(); + /** + * Creates a QueryStatement object with a given statement. + * + * @param session + * the Session object, must not be {@code null} + * @param statement + * the query statement with placeholders ('?'), see + * {@link QueryStatement} for details + */ public QueryStatementImpl(Session session, String statement) { if (session == null) { throw new IllegalArgumentException("Session must be set!"); @@ -55,8 +69,219 @@ public class QueryStatementImpl implemen this.session = session; this.statement = statement.trim(); + } + + /** + * Creates a QueryStatement object for a query of one primary type joined by + * zero or more secondary types. + * + * @param session + * the Session object, must not be {@code null} + * @param selectPropertyIds + * the property IDs in the SELECT statement, if {@code null} all + * properties are selected + * @param fromTypes + * a Map of type aliases (keys) and type IDs (values), the Map + * must contain exactly one primary type and zero or more + * secondary types + * @param whereClause + * an optional WHERE clause with placeholders ('?'), see + * {@link QueryStatement} for details + * @param orderByPropertyIds + * an optional list of properties IDs for the ORDER BY clause + */ + public QueryStatementImpl(Session session, Collection selectPropertyIds, Map fromTypes, + String whereClause, List orderByPropertyIds) { + if (session == null) { + throw new IllegalArgumentException("Session must be set!"); + } + + if (fromTypes == null || fromTypes.size() == 0) { + throw new IllegalArgumentException("Types must be set!"); + } + + this.session = session; + + StringBuilder stmt = new StringBuilder(); + + // find the primary type and check if all types are queryable + ObjectType primaryType = null; + String primaryAlias = null; + + Map types = new HashMap(); + for (Map.Entry fte : fromTypes.entrySet()) { + ObjectType type = session.getTypeDefinition(fte.getValue()); + + if (Boolean.FALSE.equals(type.isQueryable())) { + throw new IllegalArgumentException("Type '" + fte.getValue() + "' is not queryable!"); + } + + String alias = fte.getKey().trim(); + if (alias.length() < 1) { + throw new IllegalArgumentException("Invalid alias for type '" + fte.getValue() + "'!"); + } + + if (type.getBaseTypeId() != BaseTypeId.CMIS_SECONDARY) { + if (primaryType == null) { + primaryType = type; + primaryAlias = alias; + } else { + throw new IllegalArgumentException("Two primary types found: " + primaryType.getId() + " and " + + type.getId()); + } + } + + // exclude secondary types without properties + if (type.getPropertyDefinitions() != null && type.getPropertyDefinitions().size() > 0) { + types.put(alias, type); + } + } + + if (primaryType == null) { + throw new IllegalArgumentException("No primary type found!"); + } + + // SELECT + stmt.append("SELECT "); + + StringListBuilder selectList = new StringListBuilder(",", stmt); + + if (selectPropertyIds == null || selectPropertyIds.size() == 0) { + // select all properties + for (String alias : types.keySet()) { + selectList.add(alias + ".*"); + } + } else { + // select provided properties + for (String propertyId : selectPropertyIds) { + + propertyId = propertyId.trim(); + + if (propertyId.equals("*")) { + // found property "*" -> select all properties + for (String alias : types.keySet()) { + selectList.add(alias + ".*"); + } + continue; + } + + if (propertyId.endsWith(".*")) { + // found property "x.*" + // -> select all properties of the type with alias "x" + String starAlias = propertyId.substring(0, propertyId.length() - 2); + if (types.containsKey(starAlias)) { + selectList.add(starAlias + ".*"); + continue; + } else { + throw new IllegalArgumentException("Alias '" + starAlias + "' is not defined!"); + } + } + + PropertyDefinition propertyDef = null; + String alias = null; + + for (Map.Entry te : types.entrySet()) { + propertyDef = te.getValue().getPropertyDefinitions().get(propertyId); + if (propertyDef != null) { + alias = te.getKey(); + break; + } + } + + if (propertyDef == null) { + throw new IllegalArgumentException("Property '" + propertyId + + "' is not defined in the provided object types!"); + } + + if (propertyDef.getQueryName() == null) { + throw new IllegalArgumentException("Property '" + propertyId + "' has no query name!"); + } + + selectList.add(alias + "." + propertyDef.getQueryName()); + } + } + + // FROM + stmt.append(" FROM "); + + stmt.append(primaryType.getQueryName()); + stmt.append(" AS "); + stmt.append(primaryAlias); + + for (Map.Entry te : types.entrySet()) { + if (te.getKey().equals(primaryAlias)) { + continue; + } + + stmt.append(" JOIN "); + stmt.append(te.getValue().getQueryName()); + stmt.append(" AS "); + stmt.append(te.getKey()); + stmt.append(" ON "); + stmt.append(primaryAlias); + stmt.append(".cmis:objectId="); + stmt.append(te.getKey()); + stmt.append(".cmis:objectId"); + } + + // WHERE + if (whereClause != null && whereClause.trim().length() > 0) { + stmt.append(" WHERE "); + stmt.append(whereClause.trim()); + } + + // ORDER BY + if (orderByPropertyIds != null && orderByPropertyIds.size() > 0) { + stmt.append(" ORDER BY "); + + StringListBuilder orderByList = new StringListBuilder(",", stmt); + + for (String propertyId : orderByPropertyIds) { + String realPropertyId = propertyId.trim(); + String realPropertyIdLower = realPropertyId.toLowerCase(Locale.ENGLISH); + boolean desc = false; + + if (realPropertyIdLower.endsWith(" asc")) { + // property ends with " asc" -> remove it + realPropertyId = realPropertyId.substring(0, realPropertyId.length() - 4); + } + + if (realPropertyIdLower.endsWith(" desc")) { + // property ends with " desc" -> remove it and mark it as + // descending + realPropertyId = realPropertyId.substring(0, realPropertyId.length() - 5); + desc = true; + } + + PropertyDefinition propertyDef = null; + String alias = null; + + for (Map.Entry te : types.entrySet()) { + propertyDef = te.getValue().getPropertyDefinitions().get(realPropertyId); + if (propertyDef != null) { + alias = te.getKey(); + break; + } + } + + if (propertyDef == null) { + throw new IllegalArgumentException("Property '" + realPropertyId + + "' is not defined in the provided object types!"); + } - parametersMap = new HashMap(); + if (propertyDef.getQueryName() == null) { + throw new IllegalArgumentException("Property '" + realPropertyId + "' has no query name!"); + } + + if (Boolean.FALSE.equals(propertyDef.isOrderable())) { + throw new IllegalArgumentException("Property '" + realPropertyId + "' is not orderable!"); + } + + orderByList.add(alias + "." + propertyDef.getQueryName() + (desc ? " DESC" : "")); + } + } + + this.statement = stmt.toString(); } public void setType(int parameterIndex, String typeId) { @@ -105,20 +330,16 @@ public class QueryStatementImpl implemen throw new IllegalArgumentException("Number must be set!"); } - StringBuilder sb = new StringBuilder(); + StringListBuilder slb = new StringListBuilder(","); for (Number n : num) { if (n == null) { throw new IllegalArgumentException("Number is null!"); } - if (sb.length() > 0) { - sb.append(','); - } - - sb.append(n.toString()); + slb.add(n.toString()); } - parametersMap.put(parameterIndex, sb.toString()); + parametersMap.put(parameterIndex, slb.toString()); } public void setString(int parameterIndex, String... str) { @@ -126,20 +347,16 @@ public class QueryStatementImpl implemen throw new IllegalArgumentException("String must be set!"); } - StringBuilder sb = new StringBuilder(); + StringListBuilder slb = new StringListBuilder(","); for (String s : str) { if (s == null) { throw new IllegalArgumentException("String is null!"); } - if (sb.length() > 0) { - sb.append(','); - } - - sb.append(escape(s)); + slb.add(escape(s)); } - parametersMap.put(parameterIndex, sb.toString()); + parametersMap.put(parameterIndex, slb.toString()); } public void setStringContains(int parameterIndex, String str) { @@ -163,20 +380,16 @@ public class QueryStatementImpl implemen throw new IllegalArgumentException("Id must be set!"); } - StringBuilder sb = new StringBuilder(); + StringListBuilder slb = new StringListBuilder(","); for (ObjectId oid : id) { if (oid == null || oid.getId() == null) { throw new IllegalArgumentException("Id is null!"); } - if (sb.length() > 0) { - sb.append(','); - } - - sb.append(escape(oid.getId())); + slb.add(escape(oid.getId())); } - parametersMap.put(parameterIndex, sb.toString()); + parametersMap.put(parameterIndex, slb.toString()); } public void setUri(int parameterIndex, URI... uri) { @@ -184,20 +397,16 @@ public class QueryStatementImpl implemen throw new IllegalArgumentException("URI must be set!"); } - StringBuilder sb = new StringBuilder(); + StringListBuilder slb = new StringListBuilder(","); for (URI u : uri) { if (u == null) { throw new IllegalArgumentException("URI is null!"); } - if (sb.length() > 0) { - sb.append(','); - } - - sb.append(escape(u.toString())); + slb.add(escape(u.toString())); } - parametersMap.put(parameterIndex, sb.toString()); + parametersMap.put(parameterIndex, slb.toString()); } public void setUrl(int parameterIndex, URL... url) { @@ -205,20 +414,16 @@ public class QueryStatementImpl implemen throw new IllegalArgumentException("URL must be set!"); } - StringBuilder sb = new StringBuilder(); + StringListBuilder slb = new StringListBuilder(","); for (URL u : url) { if (u == null) { throw new IllegalArgumentException("URI is null!"); } - if (sb.length() > 0) { - sb.append(','); - } - - sb.append(escape(u.toString())); + slb.add(escape(u.toString())); } - parametersMap.put(parameterIndex, sb.toString()); + parametersMap.put(parameterIndex, slb.toString()); } public void setBoolean(int parameterIndex, boolean... bool) { @@ -226,16 +431,12 @@ public class QueryStatementImpl implemen throw new IllegalArgumentException("Boolean must not be set!"); } - StringBuilder sb = new StringBuilder(); + StringListBuilder slb = new StringListBuilder(","); for (boolean b : bool) { - if (sb.length() > 0) { - sb.append(','); - } - - sb.append(b ? "TRUE" : "FALSE"); + slb.add(b ? "TRUE" : "FALSE"); } - parametersMap.put(parameterIndex, sb.toString()); + parametersMap.put(parameterIndex, slb.toString()); } public void setDateTime(int parameterIndex, Calendar... cal) { @@ -284,24 +485,16 @@ public class QueryStatementImpl implemen throw new IllegalArgumentException("Date must be set!"); } - StringBuilder sb = new StringBuilder(); + StringListBuilder slb = new StringListBuilder(","); for (Date d : date) { if (d == null) { throw new IllegalArgumentException("DateTime is null!"); } - if (sb.length() > 0) { - sb.append(','); - } - - if (prefix) { - sb.append("TIMESTAMP "); - } - - sb.append(convert(d)); + slb.add((prefix ? "TIMESTAMP " : "") + convert(d)); } - parametersMap.put(parameterIndex, sb.toString()); + parametersMap.put(parameterIndex, slb.toString()); } public void setDateTime(int parameterIndex, long... ms) { @@ -317,20 +510,12 @@ public class QueryStatementImpl implemen throw new IllegalArgumentException("Timestamp must be set!"); } - StringBuilder sb = new StringBuilder(); + StringListBuilder slb = new StringListBuilder(","); for (long l : ms) { - if (sb.length() > 0) { - sb.append(','); - } - - if (prefix) { - sb.append("TIMESTAMP "); - } - - sb.append(convert(new Date(l))); + slb.add((prefix ? "TIMESTAMP " : "") + convert(new Date(l))); } - parametersMap.put(parameterIndex, sb.toString()); + parametersMap.put(parameterIndex, slb.toString()); } public String toQueryString() { Modified: chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-impl/src/main/java/org/apache/chemistry/opencmis/client/runtime/SessionImpl.java URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-impl/src/main/java/org/apache/chemistry/opencmis/client/runtime/SessionImpl.java?rev=1579967&r1=1579966&r2=1579967&view=diff ============================================================================== --- chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-impl/src/main/java/org/apache/chemistry/opencmis/client/runtime/SessionImpl.java (original) +++ chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-impl/src/main/java/org/apache/chemistry/opencmis/client/runtime/SessionImpl.java Fri Mar 21 16:12:52 2014 @@ -20,6 +20,7 @@ package org.apache.chemistry.opencmis.cl import java.math.BigInteger; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; @@ -961,6 +962,11 @@ public class SessionImpl implements Sess return new QueryStatementImpl(this, statement); } + public QueryStatement createQueryStatement(final Collection selectPropertyIds, + final Map fromTypes, final String whereClause, final List orderByPropertyIds) { + return new QueryStatementImpl(this, selectPropertyIds, fromTypes, whereClause, orderByPropertyIds); + } + /** * Connect session object to the provider. This is the very first call after * a session is created. Added: chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/StringListBuilder.java URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/StringListBuilder.java?rev=1579967&view=auto ============================================================================== --- chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/StringListBuilder.java (added) +++ chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/StringListBuilder.java Fri Mar 21 16:12:52 2014 @@ -0,0 +1,64 @@ +/* + * 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.commons.impl; + +public class StringListBuilder { + + private final String seperator; + private final StringBuilder stringBuilder; + private boolean first; + + public StringListBuilder() { + this(",", new StringBuilder()); + } + + public StringListBuilder(StringBuilder stringBuilder) { + this(",", stringBuilder); + } + + public StringListBuilder(String seperator) { + this(seperator, new StringBuilder()); + } + + public StringListBuilder(String seperator, StringBuilder stringBuilder) { + this.seperator = seperator; + this.stringBuilder = stringBuilder; + first = true; + } + + public void add(String s) { + if (!first) { + stringBuilder.append(seperator); + } else { + first = false; + } + + stringBuilder.append(s); + } + + public StringBuilder getStringBuilder() { + return stringBuilder; + } + + @Override + public String toString() { + return stringBuilder.toString(); + } + +} Propchange: chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/StringListBuilder.java ------------------------------------------------------------------------------ svn:eol-style = native