blob: bdce85e3066d6e0cb8db614eef5835f2322da09e [file] [log] [blame]
/*
* 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
* 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.client.runtime;
import static org.apache.chemistry.opencmis.commons.impl.CollectionsHelper.*;
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;
import org.apache.chemistry.opencmis.client.api.ItemIterable;
import org.apache.chemistry.opencmis.client.api.ObjectId;
import org.apache.chemistry.opencmis.client.api.ObjectType;
import org.apache.chemistry.opencmis.client.api.OperationContext;
import org.apache.chemistry.opencmis.client.api.QueryResult;
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.
*/
public class QueryStatementImpl implements QueryStatement, Cloneable {
private final Session session;
private final String statement;
private final Map<Integer, String> parametersMap = new HashMap<Integer, String>();
/**
* 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!");
}
if (statement == null) {
throw new IllegalArgumentException("Statement must be set!");
}
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<String> selectPropertyIds, Map<String, String> fromTypes,
String whereClause, List<String> orderByPropertyIds) {
if (session == null) {
throw new IllegalArgumentException("Session must be set!");
}
if (isNullOrEmpty(fromTypes)) {
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<String, ObjectType> types = new HashMap<String, ObjectType>();
for (Map.Entry<String, String> 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 (isNotEmpty(type.getPropertyDefinitions())) {
types.put(alias, type);
}
}
if (primaryType == null) {
throw new IllegalArgumentException("No primary type found!");
}
// SELECT
stmt.append("SELECT ");
StringListBuilder selectList = new StringListBuilder(",", stmt);
if (isNullOrEmpty(selectPropertyIds)) {
// 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<String, ObjectType> 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<String, ObjectType> 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 (isNotEmpty(orderByPropertyIds)) {
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<String, ObjectType> 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!");
}
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) {
setType(parameterIndex, session.getTypeDefinition(typeId));
}
public void setType(int parameterIndex, ObjectType type) {
if (type == null) {
throw new IllegalArgumentException("Type must be set!");
}
String queryName = type.getQueryName();
if (queryName == null) {
throw new IllegalArgumentException("Type has no query name!");
}
parametersMap.put(parameterIndex, queryName);
}
public void setProperty(int parameterIndex, String typeId, String propertyId) {
ObjectType type = session.getTypeDefinition(typeId);
PropertyDefinition<?> propertyDefinition = type.getPropertyDefinitions().get(propertyId);
if (propertyDefinition == null) {
throw new IllegalArgumentException("Property does not exist!");
}
setProperty(parameterIndex, propertyDefinition);
}
public void setProperty(int parameterIndex, PropertyDefinition<?> propertyDefinition) {
if (propertyDefinition == null) {
throw new IllegalArgumentException("Property must be set!");
}
String queryName = propertyDefinition.getQueryName();
if (queryName == null) {
throw new IllegalArgumentException("Property has no query name!");
}
parametersMap.put(parameterIndex, queryName);
}
public void setNumber(int parameterIndex, Number... num) {
if (num == null || num.length == 0) {
throw new IllegalArgumentException("Number must be set!");
}
StringListBuilder slb = new StringListBuilder(",");
for (Number n : num) {
if (n == null) {
throw new IllegalArgumentException("Number is null!");
}
slb.add(n.toString());
}
parametersMap.put(parameterIndex, slb.toString());
}
public void setString(int parameterIndex, String... str) {
if (str == null || str.length == 0) {
throw new IllegalArgumentException("String must be set!");
}
StringListBuilder slb = new StringListBuilder(",");
for (String s : str) {
if (s == null) {
throw new IllegalArgumentException("String is null!");
}
slb.add(escape(s));
}
parametersMap.put(parameterIndex, slb.toString());
}
public void setStringContains(int parameterIndex, String str) {
if (str == null) {
throw new IllegalArgumentException("String must be set!");
}
parametersMap.put(parameterIndex, escapeContains(str));
}
public void setStringLike(int parameterIndex, String str) {
if (str == null) {
throw new IllegalArgumentException("String must be set!");
}
parametersMap.put(parameterIndex, escapeLike(str));
}
public void setId(int parameterIndex, ObjectId... id) {
if (id == null || id.length == 0) {
throw new IllegalArgumentException("Id must be set!");
}
StringListBuilder slb = new StringListBuilder(",");
for (ObjectId oid : id) {
if (oid == null || oid.getId() == null) {
throw new IllegalArgumentException("Id is null!");
}
slb.add(escape(oid.getId()));
}
parametersMap.put(parameterIndex, slb.toString());
}
public void setUri(int parameterIndex, URI... uri) {
if (uri == null) {
throw new IllegalArgumentException("URI must be set!");
}
StringListBuilder slb = new StringListBuilder(",");
for (URI u : uri) {
if (u == null) {
throw new IllegalArgumentException("URI is null!");
}
slb.add(escape(u.toString()));
}
parametersMap.put(parameterIndex, slb.toString());
}
public void setUrl(int parameterIndex, URL... url) {
if (url == null) {
throw new IllegalArgumentException("URL must be set!");
}
StringListBuilder slb = new StringListBuilder(",");
for (URL u : url) {
if (u == null) {
throw new IllegalArgumentException("URI is null!");
}
slb.add(escape(u.toString()));
}
parametersMap.put(parameterIndex, slb.toString());
}
public void setBoolean(int parameterIndex, boolean... bool) {
if (bool == null || bool.length == 0) {
throw new IllegalArgumentException("Boolean must not be set!");
}
StringListBuilder slb = new StringListBuilder(",");
for (boolean b : bool) {
slb.add(b ? "TRUE" : "FALSE");
}
parametersMap.put(parameterIndex, slb.toString());
}
public void setDateTime(int parameterIndex, Calendar... cal) {
setDateTime(parameterIndex, false, cal);
}
public void setDateTimeTimestamp(int parameterIndex, Calendar... cal) {
setDateTime(parameterIndex, true, cal);
}
protected void setDateTime(int parameterIndex, boolean prefix, Calendar... cal) {
if (cal == null || cal.length == 0) {
throw new IllegalArgumentException("Calendar must be set!");
}
StringBuilder sb = new StringBuilder();
for (Calendar c : cal) {
if (c == null) {
throw new IllegalArgumentException("DateTime is null!");
}
if (sb.length() > 0) {
sb.append(',');
}
if (prefix) {
sb.append("TIMESTAMP ");
}
sb.append(convert(c.getTime()));
}
parametersMap.put(parameterIndex, sb.toString());
}
public void setDateTime(int parameterIndex, Date... date) {
setDateTime(parameterIndex, false, date);
}
public void setDateTimeTimestamp(int parameterIndex, Date... date) {
setDateTime(parameterIndex, true, date);
}
protected void setDateTime(int parameterIndex, boolean prefix, Date... date) {
if (date == null || date.length == 0) {
throw new IllegalArgumentException("Date must be set!");
}
StringListBuilder slb = new StringListBuilder(",");
for (Date d : date) {
if (d == null) {
throw new IllegalArgumentException("DateTime is null!");
}
slb.add((prefix ? "TIMESTAMP " : "") + convert(d));
}
parametersMap.put(parameterIndex, slb.toString());
}
public void setDateTime(int parameterIndex, long... ms) {
setDateTime(parameterIndex, false, ms);
}
public void setDateTimeTimestamp(int parameterIndex, long... ms) {
setDateTime(parameterIndex, true, ms);
}
protected void setDateTime(int parameterIndex, boolean prefix, long... ms) {
if (ms == null || ms.length == 0) {
throw new IllegalArgumentException("Timestamp must be set!");
}
StringListBuilder slb = new StringListBuilder(",");
for (long l : ms) {
slb.add((prefix ? "TIMESTAMP " : "") + convert(new Date(l)));
}
parametersMap.put(parameterIndex, slb.toString());
}
public String toQueryString() {
boolean inStr = false;
int parameterIndex = 0;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < statement.length(); i++) {
char c = statement.charAt(i);
if (c == '\'') {
if (inStr && statement.charAt(i - 1) == '\\') {
inStr = true;
} else {
inStr = !inStr;
}
sb.append(c);
} else if (c == '?' && !inStr) {
parameterIndex++;
String s = parametersMap.get(parameterIndex);
if (s == null) {
sb.append(c);
} else {
sb.append(s);
}
} else {
sb.append(c);
}
}
return sb.toString();
}
public ItemIterable<QueryResult> query(boolean searchAllVersions) {
return session.query(toQueryString(), searchAllVersions);
}
public ItemIterable<QueryResult> query(boolean searchAllVersions, OperationContext context) {
return session.query(toQueryString(), searchAllVersions, context);
}
@Override
protected QueryStatementImpl clone() throws CloneNotSupportedException {
QueryStatementImpl qs = new QueryStatementImpl(session, statement);
qs.parametersMap.putAll(parametersMap);
return qs;
}
@Override
public String toString() {
return toQueryString();
}
// --- internal ---
private static String escape(String str) {
StringBuilder sb = new StringBuilder("'");
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (c == '\'' || c == '\\') {
sb.append('\\');
}
sb.append(c);
}
sb.append('\'');
return sb.toString();
}
private static String escapeLike(String str) {
StringBuilder sb = new StringBuilder("'");
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (c == '\'') {
sb.append('\\');
} else if (c == '\\') {
if (i + 1 < str.length() && (str.charAt(i + 1) == '%' || str.charAt(i + 1) == '_')) {
// no additional back slash
} else {
sb.append('\\');
}
}
sb.append(c);
}
sb.append('\'');
return sb.toString();
}
private static String escapeContains(String str) {
StringBuilder sb = new StringBuilder("'");
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (c == '\\') {
sb.append('\\');
} else if (c == '\'' || c == '\"') {
sb.append("\\\\\\");
}
sb.append(c);
}
sb.append('\'');
return sb.toString();
}
private static String convert(Date date) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
return "'" + sdf.format(date) + "'";
}
}