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 ==, <, <=, >, >=.
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 }