001    /*
002     * Cumulus4j - Securing your data in the cloud - http://cumulus4j.org
003     * Copyright (C) 2011 NightLabs Consulting GmbH
004     *
005     * This program is free software: you can redistribute it and/or modify
006     * it under the terms of the GNU Affero General Public License as
007     * published by the Free Software Foundation, either version 3 of the
008     * License, or (at your option) any later version.
009     *
010     * This program is distributed in the hope that it will be useful,
011     * but WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013     * GNU Affero General Public License for more details.
014     *
015     * You should have received a copy of the GNU Affero General Public License
016     * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017     */
018    package org.cumulus4j.store.query.eval;
019    
020    import java.util.Collection;
021    import java.util.Collections;
022    import java.util.HashSet;
023    import java.util.Set;
024    
025    import javax.jdo.Query;
026    
027    import org.cumulus4j.store.crypto.CryptoContext;
028    import org.cumulus4j.store.model.ClassMeta;
029    import org.cumulus4j.store.model.DataEntry;
030    import org.cumulus4j.store.model.FieldMeta;
031    import org.cumulus4j.store.model.IndexEntry;
032    import org.cumulus4j.store.model.IndexEntryFactory;
033    import org.cumulus4j.store.model.IndexEntryObjectRelationHelper;
034    import org.cumulus4j.store.model.IndexValue;
035    import org.cumulus4j.store.query.QueryEvaluator;
036    import org.datanucleus.metadata.AbstractMemberMetaData;
037    import org.datanucleus.metadata.Relation;
038    import org.datanucleus.query.expression.DyadicExpression;
039    import org.datanucleus.query.expression.Expression;
040    import org.datanucleus.query.expression.Expression.Operator;
041    import org.datanucleus.query.expression.PrimaryExpression;
042    import org.datanucleus.store.ExecutionContext;
043    import org.slf4j.Logger;
044    import org.slf4j.LoggerFactory;
045    
046    /**
047     * Evaluator handling the comparisons ==, &lt;, &lt;=, &gt;, &gt;=.
048     *
049     * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
050     */
051    public class ComparisonExpressionEvaluator
052    extends AbstractExpressionEvaluator<DyadicExpression>
053    {
054            private static final Logger logger = LoggerFactory.getLogger(ComparisonExpressionEvaluator.class);
055    
056            public ComparisonExpressionEvaluator(QueryEvaluator queryEvaluator, AbstractExpressionEvaluator<?> parent, DyadicExpression expression) {
057                    super(queryEvaluator, parent, expression);
058            }
059    
060            @Override
061            protected Set<Long> _queryResultDataEntryIDs(ResultDescriptor resultDescriptor)
062            {
063                    ExecutionContext executionContext = getQueryEvaluator().getExecutionContext();
064    
065                    if (getLeft() instanceof InvokeExpressionEvaluator) {
066                            if (!getLeft().getResultSymbols().contains(resultDescriptor.getSymbol()))
067                                    return null;
068    
069                            return getLeft().queryResultDataEntryIDs(resultDescriptor);
070                    }
071    
072                    if (getLeft() instanceof PrimaryExpressionEvaluator) {
073                            if (!getLeft().getResultSymbols().contains(resultDescriptor.getSymbol()))
074                                    return null;
075    
076                            Object compareToArgument = getRightCompareToArgument();
077                            return new CompareWithConcreteValueResolver(getQueryEvaluator(), ((PrimaryExpressionEvaluator)getLeft()).getExpression(), compareToArgument, resultDescriptor.isNegated()).query();
078                    }
079    
080                    if (getRight() instanceof PrimaryExpressionEvaluator) {
081                            if (!getRight().getResultSymbols().contains(resultDescriptor.getSymbol()))
082                                    return null;
083    
084                            Object compareToArgument = getLeftCompareToArgument();
085                            return new CompareWithConcreteValueResolver(getQueryEvaluator(), ((PrimaryExpressionEvaluator)getRight()).getExpression(), compareToArgument, resultDescriptor.isNegated()).query();
086                    }
087    
088                    if (getLeft() instanceof VariableExpressionEvaluator) {
089                            if (!getLeft().getResultSymbols().contains(resultDescriptor.getSymbol()))
090                                    return null;
091    
092                            if (resultDescriptor.getFieldMeta() != null)
093                                    return queryCompareConcreteValue(resultDescriptor.getFieldMeta(), getRightCompareToArgument(), resultDescriptor.isNegated());
094                            else {
095                                    // The variable is an FCO and directly compared (otherwise it would be a PrimaryExpression - see above) or the FieldMeta would be specified.
096                                    ClassMeta classMeta = getQueryEvaluator().getStoreManager().getClassMeta(executionContext, resultDescriptor.getResultType());
097                                    return queryEqualsConcreteValue(classMeta, getRightCompareToArgument(), resultDescriptor.isNegated());
098                            }
099                    }
100    
101                    throw new UnsupportedOperationException("NYI");
102            }
103    
104            protected Object getLeftCompareToArgument() {
105                    Object compareToArgument;
106                    if (getLeft() instanceof LiteralEvaluator)
107                            compareToArgument = ((LiteralEvaluator)getLeft()).getLiteralValue();
108                    else if (getLeft() instanceof ParameterExpressionEvaluator)
109                            compareToArgument = ((ParameterExpressionEvaluator)getLeft()).getParameterValue();
110                    else
111                            throw new UnsupportedOperationException("NYI");
112                    return compareToArgument;
113            }
114    
115            protected Object getRightCompareToArgument() {
116                    Object compareToArgument;
117                    if (getRight() instanceof LiteralEvaluator)
118                            compareToArgument = ((LiteralEvaluator)getRight()).getLiteralValue();
119                    else if (getRight() instanceof ParameterExpressionEvaluator)
120                            compareToArgument = ((ParameterExpressionEvaluator)getRight()).getParameterValue();
121                    else
122                            throw new UnsupportedOperationException("NYI");
123                    return compareToArgument;
124            }
125    
126            private class CompareWithConcreteValueResolver extends PrimaryExpressionResolver
127            {
128                    private Object value;
129                    private boolean negate;
130    
131                    public CompareWithConcreteValueResolver(
132                                    QueryEvaluator queryEvaluator, PrimaryExpression primaryExpression,
133                                    Object value, boolean negate
134                    )
135                    {
136                            super(queryEvaluator, primaryExpression);
137                            this.value = value;
138                            this.negate = negate;
139                    }
140    
141                    @Override
142                    protected Set<Long> queryEnd(FieldMeta fieldMeta) {
143                            return queryCompareConcreteValue(fieldMeta, value, negate);
144                    }
145            }
146    
147            private Set<Long> queryCompareConcreteValue(FieldMeta fieldMeta, Object value, boolean negate)
148            {
149                    CryptoContext cryptoContext = getQueryEvaluator().getCryptoContext();
150                    ExecutionContext executionContext = getQueryEvaluator().getExecutionContext();
151                    AbstractMemberMetaData mmd = fieldMeta.getDataNucleusMemberMetaData(executionContext);
152                    int relationType = mmd.getRelationType(executionContext.getClassLoaderResolver());
153    
154                    Object queryParam;
155                    IndexEntryFactory indexEntryFactory;
156                    if (Relation.NONE == relationType)
157                    {
158                            indexEntryFactory = getQueryEvaluator().getStoreManager().getIndexFactoryRegistry().getIndexEntryFactory(
159                                            getQueryEvaluator().getExecutionContext(), fieldMeta, true
160                            );
161                            queryParam = value;
162                    }
163                    else if (Relation.isRelationSingleValued(relationType))
164                    {
165                            // Only "==" and "!=" are supported for object relations => check.
166                            Operator op = getExpression().getOperator();
167                            if (Expression.OP_EQ != op && Expression.OP_NOTEQ != op)
168                                    throw new UnsupportedOperationException("The operation \"" + getOperatorAsJDOQLSymbol(false) + "\" is not supported for object relations!");
169    
170                            indexEntryFactory = IndexEntryObjectRelationHelper.getIndexEntryFactory();
171                            Long valueDataEntryID = null;
172                            if (value != null) {
173                                    ClassMeta valueClassMeta = getQueryEvaluator().getStoreManager().getClassMeta(executionContext, value.getClass());
174                                    Object valueID = executionContext.getApiAdapter().getIdForObject(value);
175                                    if (valueID == null)
176                                            throw new IllegalStateException("The ApiAdapter returned null as object-ID for: " + value);
177    
178                                    valueDataEntryID = DataEntry.getDataEntryID(getQueryEvaluator().getPersistenceManagerForData(), valueClassMeta, valueID.toString());
179                            }
180                            queryParam = valueDataEntryID;
181                    }
182                    else
183                            throw new UnsupportedOperationException("NYI");
184    
185                    if (indexEntryFactory == null) {
186                            logger.warn("queryCompareConcreteValue: Returning empty result, because there is no index for this field: " + fieldMeta);
187                            return Collections.emptySet();
188                    }
189    
190                    Query q = getQueryEvaluator().getPersistenceManagerForIndex().newQuery(indexEntryFactory.getIndexEntryClass());
191                    q.setFilter(
192                                    "this.fieldMeta == :fieldMeta && " +
193                                    "this.indexKey " + getOperatorAsJDOQLSymbol(negate) + " :value"
194                    );
195    
196                    @SuppressWarnings("unchecked")
197                    Collection<? extends IndexEntry> indexEntries = (Collection<? extends IndexEntry>) q.execute(fieldMeta, queryParam);
198    
199                    Set<Long> result = new HashSet<Long>();
200                    for (IndexEntry indexEntry : indexEntries) {
201                            IndexValue indexValue = getQueryEvaluator().getEncryptionHandler().decryptIndexEntry(cryptoContext, indexEntry);
202                            result.addAll(indexValue.getDataEntryIDs());
203                    }
204                    q.closeAll();
205                    return result;
206            }
207    
208            private Set<Long> queryEqualsConcreteValue(ClassMeta classMeta, Object value, boolean negate)
209            {
210                    Operator op = getExpression().getOperator();
211                    if (Expression.OP_EQ != op && Expression.OP_NOTEQ != op)
212                            throw new UnsupportedOperationException("The operation \"" + getOperatorAsJDOQLSymbol(false) + "\" is not supported for object relations!");
213    
214                    ExecutionContext executionContext = getQueryEvaluator().getExecutionContext();
215                    Object valueID = executionContext.getApiAdapter().getIdForObject(value);
216                    if (valueID == null)
217                            throw new IllegalStateException("The ApiAdapter returned null as object-ID for: " + value);
218    
219                    if (Expression.OP_NOTEQ == op || negate) {
220                            // TODO IMHO this is incomplete - the sub-classes are probably missing. But before changing anything here,
221                            // we should design a test-case first and check if my assumption is correct.
222                            // Marco :-)
223                            return DataEntry.getDataEntryIDsNegated(getQueryEvaluator().getPersistenceManagerForData(), classMeta, valueID.toString());
224                    }
225                    else {
226                            Long dataEntryID = DataEntry.getDataEntryID(getQueryEvaluator().getPersistenceManagerForData(), classMeta, valueID.toString());
227                            return Collections.singleton(dataEntryID);
228                    }
229            }
230    
231            private String getOperatorAsJDOQLSymbol(boolean negate)
232            {
233                    Operator op = getExpression().getOperator();
234                    return ExpressionHelper.getOperatorAsJDOQLSymbol(op, negate);
235            }
236    }