All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.chemistry.opencmis.client.runtime.QueryStatementImpl Maven / Gradle / Ivy

/*
 * 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.isNotEmpty;
import static org.apache.chemistry.opencmis.commons.impl.CollectionsHelper.isNullOrEmpty;

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 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.DateTimeHelper;
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 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!");
        }

        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 selectPropertyIds, Map fromTypes,
            String whereClause, List 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(1024);

        // 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 (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 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 (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 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();
    }

    @Override
    public void setType(int parameterIndex, String typeId) {
        setType(parameterIndex, session.getTypeDefinition(typeId));
    }

    @Override
    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);
    }

    @Override
    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);
    }

    @Override
    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);
    }

    @Override
    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());
    }

    @Override
    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());
    }

    @Override
    public void setStringContains(int parameterIndex, String str) {
        if (str == null) {
            throw new IllegalArgumentException("String must be set!");
        }

        parametersMap.put(parameterIndex, escapeContains(str));
    }

    @Override
    public void setStringLike(int parameterIndex, String str) {
        if (str == null) {
            throw new IllegalArgumentException("String must be set!");
        }

        parametersMap.put(parameterIndex, escapeLike(str));
    }

    @Override
    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());
    }

    @Override
    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());
    }

    @Override
    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());
    }

    @Override
    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());
    }

    @Override
    public void setDateTime(int parameterIndex, Calendar... cal) {
        setDateTime(parameterIndex, false, cal);
    }

    @Override
    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(64);
        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());
    }

    @Override
    public void setDateTime(int parameterIndex, Date... date) {
        setDateTime(parameterIndex, false, date);
    }

    @Override
    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());
    }

    @Override
    public void setDateTime(int parameterIndex, long... ms) {
        setDateTime(parameterIndex, false, ms);
    }

    @Override
    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());
    }

    @Override
    public String toQueryString() {
        boolean inStr = false;
        int parameterIndex = 0;

        StringBuilder sb = new StringBuilder(1024);
        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();
    }

    @Override
    public ItemIterable query() {
        return session.query(toQueryString(), false);
    }

    @Override
    public ItemIterable query(boolean searchAllVersions) {
        return session.query(toQueryString(), searchAllVersions);
    }

    @Override
    public ItemIterable 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(str.length() + 16);

        sb.append('\'');

        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(str.length() + 16);

        sb.append('\'');

        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(str.length() + 64);

        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);

            if (c == '\\') {
                sb.append('\\');
            } else if (c == '\'' || c == '\"') {
                sb.append('\\');
            } else if (c == '-') {
                if (i > 0) {
                    char cb = str.charAt(i - 1);
                    if (cb == '\\') {
                        sb.deleteCharAt(sb.length() - 1);
                    } else if (cb != ' ') {
                        sb.append('\\');
                    }
                }
            }

            sb.append(c);
        }

        return escape(sb.toString());
    }

    private static String convert(Date date) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
        sdf.setTimeZone(DateTimeHelper.GMT);

        return "'" + sdf.format(date) + "'";
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy