001/*
002 * (C) Copyright 2010 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Anahide Tchertchian
018 */
019package org.nuxeo.ecm.platform.smart.query.jsf;
020
021import java.text.SimpleDateFormat;
022
023import org.apache.commons.lang.StringUtils;
024import org.nuxeo.ecm.core.query.QueryParseException;
025import org.nuxeo.ecm.core.query.sql.SQLQueryParser;
026import org.nuxeo.ecm.core.search.api.client.querymodel.Escaper;
027import org.nuxeo.ecm.core.search.api.client.querymodel.LuceneMinimalEscaper;
028import org.nuxeo.ecm.platform.smart.query.IncrementalSmartQuery;
029
030/**
031 * NXQL implementation of an incremental smart query
032 *
033 * @since 5.4
034 * @author Anahide Tchertchian
035 */
036public class IncrementalSmartNXQLQuery extends IncrementalSmartQuery {
037
038    private static final long serialVersionUID = 1L;
039
040    public static final String GENERIC_QUERY_SELECT = "SELECT * FROM DOCUMENT WHERE ";
041
042    public static enum SPECIAL_OPERATORS {
043
044        CONTAINS("CONTAINS"), BETWEEN("BETWEEN"), NOT_CONTAINS("NOT CONTAINS"), NOT_STARTSWITH("NOT STARTSWITH");
045
046        String stringValue;
047
048        SPECIAL_OPERATORS(String stringValue) {
049            this.stringValue = stringValue;
050        }
051
052        public String getStringValue() {
053            return stringValue;
054        }
055
056    }
057
058    // XXX: figure out when this is needed so that it is used
059    public static final Escaper escaper = new LuceneMinimalEscaper();
060
061    final SimpleDateFormat isoDate = new SimpleDateFormat("yyyy-MM-dd");
062
063    final SimpleDateFormat isoTimeStamp = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
064
065    public IncrementalSmartNXQLQuery(String existingQueryPart) {
066        super(existingQueryPart);
067    }
068
069    @Override
070    public String buildQuery() {
071        StringBuilder builder = new StringBuilder();
072        if (existingQueryPart != null) {
073            builder.append(existingQueryPart);
074            builder.append(" ");
075        }
076        // perform simple check before changing query
077        if (leftExpression != null && conditionalOperator != null) {
078            if (logicalOperator != null) {
079                builder.append(logicalOperator);
080                builder.append(" ");
081            }
082            if (Boolean.TRUE.equals(openParenthesis)) {
083                builder.append("(");
084            }
085            if (Boolean.TRUE.equals(addNotOperator)
086                    || SPECIAL_OPERATORS.NOT_STARTSWITH.getStringValue().equals(conditionalOperator)) {
087                builder.append("NOT ");
088            }
089            if (leftExpression != null) {
090                builder.append(leftExpression);
091                builder.append(" ");
092            }
093            if (conditionalOperator != null) {
094                if (SPECIAL_OPERATORS.CONTAINS.getStringValue().equals(conditionalOperator)) {
095                    builder.append("LIKE");
096                } else if (SPECIAL_OPERATORS.NOT_CONTAINS.getStringValue().equals(conditionalOperator)) {
097                    builder.append("NOT LIKE");
098                } else if (SPECIAL_OPERATORS.NOT_STARTSWITH.getStringValue().equals(conditionalOperator)) {
099                    // negation already added above
100                    builder.append("STARTSWITH");
101                } else {
102                    builder.append(conditionalOperator);
103                }
104                builder.append(" ");
105            }
106            if (value != null) {
107                if (booleanValue != null) {
108                    if (Boolean.TRUE.equals(booleanValue)) {
109                        builder.append(1);
110                    } else {
111                        builder.append(0);
112                    }
113                } else if (stringValue != null) {
114                    if (SPECIAL_OPERATORS.CONTAINS.getStringValue().equals(conditionalOperator)
115                            || SPECIAL_OPERATORS.NOT_CONTAINS.getStringValue().equals(conditionalOperator)) {
116                        builder.append("'%");
117                        if (Boolean.TRUE.equals(escapeValue)) {
118                            builder.append(String.format("%s", escaper.escape(stringValue)));
119                        } else {
120                            builder.append(stringValue);
121                        }
122                        builder.append("%'");
123                    } else {
124                        if (Boolean.TRUE.equals(escapeValue)) {
125                            builder.append(String.format("'%s'", escaper.escape(stringValue)));
126                        } else {
127                            builder.append(String.format("'%s'", stringValue));
128                        }
129                    }
130                } else if (stringListValue != null) {
131                    String[] values = new String[stringListValue.size()];
132                    values = stringListValue.toArray(values);
133                    if (Boolean.TRUE.equals(escapeValue)) {
134                        for (int i = 0; i < values.length; i++) {
135                            values[i] = String.format("'%s'", escaper.escape(values[i]));
136                        }
137                    } else {
138                        for (int i = 0; i < values.length; i++) {
139                            values[i] = String.format("'%s'", values[i]);
140                        }
141                    }
142                    builder.append(String.format("(%s)", StringUtils.join(values, ",")));
143                } else if (stringArrayValue != null) {
144                    String[] values = new String[stringArrayValue.length];
145                    if (Boolean.TRUE.equals(escapeValue)) {
146                        for (int i = 0; i < stringArrayValue.length; i++) {
147                            values[i] = String.format("'%s'", escaper.escape(stringArrayValue[i]));
148                        }
149                    } else {
150                        for (int i = 0; i < stringArrayValue.length; i++) {
151                            values[i] = String.format("'%s'", stringArrayValue[i]);
152                        }
153                    }
154                    builder.append(String.format("(%s)", StringUtils.join(values, ",")));
155                } else if (datetimeValue != null) {
156                    builder.append(String.format("TIMESTAMP '%s'",
157                            isoTimeStamp.format(Long.valueOf(datetimeValue.getTime()))));
158                    if (otherDatetimeValue != null) {
159                        builder.append(" AND ");
160                        builder.append(String.format("TIMESTAMP '%s'",
161                                isoTimeStamp.format(Long.valueOf(otherDatetimeValue.getTime()))));
162                    }
163                } else if (dateValue != null) {
164                    // TODO: handle other date
165                    builder.append(String.format("DATE '%s'", isoDate.format(Long.valueOf(dateValue.getTime()))));
166                    if (otherDateValue != null) {
167                        builder.append(" AND ");
168                        builder.append(String.format("DATE '%s'",
169                                isoDate.format(Long.valueOf(otherDateValue.getTime()))));
170                    }
171                } else if (integerValue != null) {
172                    builder.append(integerValue);
173                } else if (floatValue != null) {
174                    builder.append(floatValue);
175                } else {
176                    // value type not supported
177                    builder.append(value.toString());
178                }
179            }
180            if (Boolean.TRUE.equals(closeParenthesis)) {
181                builder.append(")");
182            }
183        }
184        String newValue = builder.toString().trim();
185        clear();
186        existingQueryPart = newValue;
187        return existingQueryPart;
188    }
189
190    @Override
191    public boolean isValid() {
192        return isValid(existingQueryPart);
193    }
194
195    public static boolean isValid(String queryPart) {
196        String query = GENERIC_QUERY_SELECT + queryPart;
197        try {
198            SQLQueryParser.parse(query);
199        } catch (QueryParseException e) {
200            return false;
201        }
202        return true;
203    }
204
205}