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.HashMap;
023    import java.util.HashSet;
024    import java.util.List;
025    import java.util.Map;
026    import java.util.Set;
027    
028    import javax.jdo.Query;
029    
030    import org.cumulus4j.store.Cumulus4jStoreManager;
031    import org.cumulus4j.store.crypto.CryptoContext;
032    import org.cumulus4j.store.model.ClassMeta;
033    import org.cumulus4j.store.model.ClassMetaDAO;
034    import org.cumulus4j.store.model.DataEntryDAO;
035    import org.cumulus4j.store.model.FieldMeta;
036    import org.cumulus4j.store.model.IndexEntry;
037    import org.cumulus4j.store.model.IndexEntryFactory;
038    import org.cumulus4j.store.model.IndexEntryObjectRelationHelper;
039    import org.cumulus4j.store.model.IndexValue;
040    import org.cumulus4j.store.query.QueryEvaluator;
041    import org.datanucleus.metadata.AbstractMemberMetaData;
042    import org.datanucleus.metadata.Relation;
043    import org.datanucleus.query.expression.DyadicExpression;
044    import org.datanucleus.query.expression.Expression;
045    import org.datanucleus.query.expression.Expression.Operator;
046    import org.datanucleus.query.expression.PrimaryExpression;
047    import org.datanucleus.store.ExecutionContext;
048    import org.slf4j.Logger;
049    import org.slf4j.LoggerFactory;
050    
051    /**
052     * Evaluator handling the comparisons ==, &lt;, &lt;=, &gt;, &gt;=.
053     *
054     * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
055     */
056    public class ComparisonExpressionEvaluator
057    extends AbstractExpressionEvaluator<DyadicExpression>
058    {
059            private static final Logger logger = LoggerFactory.getLogger(ComparisonExpressionEvaluator.class);
060    
061            public ComparisonExpressionEvaluator(QueryEvaluator queryEvaluator, AbstractExpressionEvaluator<?> parent, DyadicExpression expression) {
062                    super(queryEvaluator, parent, expression);
063            }
064    
065            @Override
066            protected Set<Long> _queryResultDataEntryIDs(ResultDescriptor resultDescriptor)
067            {
068                    ExecutionContext executionContext = getQueryEvaluator().getExecutionContext();
069    
070                    if (getLeft() instanceof InvokeExpressionEvaluator) {
071                            if (!getLeft().getResultSymbols().contains(resultDescriptor.getSymbol()))
072                                    return null;
073    
074                            return getLeft().queryResultDataEntryIDs(resultDescriptor);
075                    }
076    
077                    if (getLeft() instanceof PrimaryExpressionEvaluator) {
078                            if (!getLeft().getResultSymbols().contains(resultDescriptor.getSymbol()))
079                                    return null;
080    
081                            Object compareToArgument = getRightCompareToArgument();
082                            return new CompareWithConcreteValueResolver(getQueryEvaluator(), ((PrimaryExpressionEvaluator)getLeft()).getExpression(), compareToArgument, resultDescriptor.isNegated()).query();
083                    }
084    
085                    if (getRight() instanceof PrimaryExpressionEvaluator) {
086                            if (!getRight().getResultSymbols().contains(resultDescriptor.getSymbol()))
087                                    return null;
088    
089                            Object compareToArgument = getLeftCompareToArgument();
090                            return new CompareWithConcreteValueResolver(getQueryEvaluator(), ((PrimaryExpressionEvaluator)getRight()).getExpression(), compareToArgument, resultDescriptor.isNegated()).query();
091                    }
092    
093                    if (getLeft() instanceof VariableExpressionEvaluator) {
094                            if (!getLeft().getResultSymbols().contains(resultDescriptor.getSymbol()))
095                                    return null;
096    
097                            String className = resultDescriptor.getResultType().getName();
098                            if (getQueryEvaluator().getStoreManager().getMetaDataManager().isClassPersistable(className)) {
099                                    ClassMeta classMeta = getQueryEvaluator().getStoreManager().getClassMeta(executionContext, resultDescriptor.getResultType());
100                                    return queryEqualsConcreteValue(classMeta, getRightCompareToArgument(), resultDescriptor.isNegated());
101                            }
102    
103                            return queryCompareConcreteValue(resultDescriptor.getFieldMeta(), resultDescriptor.getClassMeta(), getRightCompareToArgument(), resultDescriptor.isNegated());
104                    }
105    
106                    throw new UnsupportedOperationException("NYI");
107            }
108    
109            protected Object getLeftCompareToArgument() {
110                    Object compareToArgument;
111                    if (getLeft() instanceof LiteralEvaluator)
112                            compareToArgument = ((LiteralEvaluator)getLeft()).getLiteralValue();
113                    else if (getLeft() instanceof ParameterExpressionEvaluator)
114                            compareToArgument = ((ParameterExpressionEvaluator)getLeft()).getParameterValue();
115                    else
116                            throw new UnsupportedOperationException("NYI");
117                    return compareToArgument;
118            }
119    
120            protected Object getRightCompareToArgument() {
121                    Object compareToArgument;
122                    if (getRight() instanceof LiteralEvaluator)
123                            compareToArgument = ((LiteralEvaluator)getRight()).getLiteralValue();
124                    else if (getRight() instanceof ParameterExpressionEvaluator)
125                            compareToArgument = ((ParameterExpressionEvaluator)getRight()).getParameterValue();
126                    else
127                            throw new UnsupportedOperationException("NYI");
128                    return compareToArgument;
129            }
130    
131            private class CompareWithConcreteValueResolver extends PrimaryExpressionResolver
132            {
133                    private Object value;
134                    private boolean negate;
135    
136                    public CompareWithConcreteValueResolver(
137                                    QueryEvaluator queryEvaluator, PrimaryExpression primaryExpression,
138                                    Object value, boolean negate
139                    )
140                    {
141                            super(queryEvaluator, primaryExpression);
142                            this.value = value;
143                            this.negate = negate;
144                    }
145    
146                    @Override
147                    protected Set<Long> queryEnd(FieldMeta fieldMeta, ClassMeta classMeta) {
148                            return queryCompareConcreteValue(fieldMeta, classMeta, value, negate);
149                    }
150            }
151    
152            private Set<Long> queryCompareConcreteValue(FieldMeta fieldMeta, ClassMeta classMeta, Object value, boolean negate)
153            {
154                    CryptoContext cryptoContext = getQueryEvaluator().getCryptoContext();
155                    ExecutionContext executionContext = getQueryEvaluator().getExecutionContext();
156                    AbstractMemberMetaData mmd = fieldMeta.getDataNucleusMemberMetaData(executionContext);
157                    int relationType = mmd.getRelationType(executionContext.getClassLoaderResolver());
158    
159                    Object queryParam;
160                    IndexEntryFactory indexEntryFactory;
161                    if (Relation.NONE == relationType)
162                    {
163                            indexEntryFactory = getQueryEvaluator().getStoreManager().getIndexFactoryRegistry().getIndexEntryFactory(
164                                            getQueryEvaluator().getExecutionContext(), fieldMeta, true
165                            );
166                            queryParam = value;
167                    }
168                    else if (Relation.isRelationSingleValued(relationType))
169                    {
170                            // Only "==" and "!=" are supported for object relations => check.
171                            Operator op = getExpression().getOperator();
172                            if (Expression.OP_EQ != op && Expression.OP_NOTEQ != op)
173                                    throw new UnsupportedOperationException("The operation \"" + getOperatorAsJDOQLSymbol(false) + "\" is not supported for object relations!");
174    
175                            indexEntryFactory = IndexEntryObjectRelationHelper.getIndexEntryFactory();
176                            Long valueDataEntryID = null;
177                            if (value != null) {
178                                    ClassMeta valueClassMeta = getQueryEvaluator().getStoreManager().getClassMeta(executionContext, value.getClass());
179                                    Object valueID = executionContext.getApiAdapter().getIdForObject(value);
180                                    if (valueID == null)
181                                            throw new IllegalStateException("The ApiAdapter returned null as object-ID for: " + value);
182    
183                                    valueDataEntryID = new DataEntryDAO(
184                                                    getQueryEvaluator().getPersistenceManagerForData(), cryptoContext.getKeyStoreRefID()
185                                    ).getDataEntryID(valueClassMeta, valueID.toString());
186                            }
187                            queryParam = valueDataEntryID;
188                    }
189                    else
190                            throw new UnsupportedOperationException("NYI");
191    
192                    if (indexEntryFactory == null) {
193                            logger.warn("queryCompareConcreteValue: Returning empty result, because there is no index for this field: " + fieldMeta);
194                            return Collections.emptySet();
195                    }
196    
197                    Cumulus4jStoreManager storeManager = (Cumulus4jStoreManager) executionContext.getStoreManager();
198                    List<ClassMeta> classMetas = storeManager.getClassMetaWithSubClassMetas(executionContext, classMeta);
199    
200                    Query q = getQueryEvaluator().getPersistenceManagerForIndex().newQuery(indexEntryFactory.getIndexEntryClass());
201                    Map<String, Object> params = new HashMap<String, Object>(4);
202                    q.setFilter(
203                                    "this.keyStoreRefID == :keyStoreRefID && " +
204                                    "this.fieldMeta_fieldID == :fieldMeta_fieldID && " +
205    //                              ":classMetas.contains(this.classMeta) && " +
206                                    ClassMetaDAO.getMultiClassMetaOrFilterPart(params, classMetas) + " && " +
207                                    "this.indexKey " + getOperatorAsJDOQLSymbol(negate) + " :value"
208                    );
209    
210                    params.put("keyStoreRefID", cryptoContext.getKeyStoreRefID());
211                    params.put("fieldMeta_fieldID", fieldMeta.getFieldID());
212    //              params.put("classMetas", classMetas);
213                    params.put("value", queryParam);
214    
215    //              @SuppressWarnings("unchecked")
216    //              Collection<? extends IndexEntry> indexEntries = (Collection<? extends IndexEntry>) q.execute(
217    //                              cryptoContext.getKeyStoreRefID(), fieldMeta, queryParam
218    //              );
219                    @SuppressWarnings("unchecked")
220                    Collection<? extends IndexEntry> indexEntries = (Collection<? extends IndexEntry>) q.executeWithMap(params);
221    
222                    Set<Long> result = new HashSet<Long>();
223                    for (IndexEntry indexEntry : indexEntries) {
224                            IndexValue indexValue = getQueryEvaluator().getEncryptionHandler().decryptIndexEntry(cryptoContext, indexEntry);
225                            result.addAll(indexValue.getDataEntryIDs());
226                    }
227                    q.closeAll();
228                    return result;
229            }
230    
231            private Set<Long> queryEqualsConcreteValue(ClassMeta classMeta, Object value, boolean negate)
232            {
233                    CryptoContext cryptoContext = getQueryEvaluator().getCryptoContext();
234                    Operator op = getExpression().getOperator();
235                    if (Expression.OP_EQ != op && Expression.OP_NOTEQ != op)
236                            throw new UnsupportedOperationException("The operation \"" + getOperatorAsJDOQLSymbol(false) + "\" is not supported for object relations!");
237    
238                    ExecutionContext executionContext = getQueryEvaluator().getExecutionContext();
239                    Object valueID = executionContext.getApiAdapter().getIdForObject(value);
240                    if (valueID == null)
241                            throw new IllegalStateException("The ApiAdapter returned null as object-ID for: " + value);
242    
243                    if (Expression.OP_NOTEQ == op || negate) {
244                            // TODO IMHO this is incomplete - the sub-classes are probably missing. But before changing anything here,
245                            // we should design a test-case first and check if my assumption is correct.
246                            // Marco :-)
247                            return new DataEntryDAO(
248                                            getQueryEvaluator().getPersistenceManagerForData(), cryptoContext.getKeyStoreRefID()
249                            ).getDataEntryIDsNegated(classMeta, valueID.toString());
250                    }
251                    else {
252                            Long dataEntryID = new DataEntryDAO(
253                                            getQueryEvaluator().getPersistenceManagerForData(), cryptoContext.getKeyStoreRefID()
254                            ).getDataEntryID(classMeta, valueID.toString());
255                            return Collections.singleton(dataEntryID);
256                    }
257            }
258    
259            private String getOperatorAsJDOQLSymbol(boolean negate)
260            {
261                    Operator op = getExpression().getOperator();
262                    return ExpressionHelper.getOperatorAsJDOQLSymbol(op, negate);
263            }
264    }