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.ExecutionContext;
042 import org.datanucleus.metadata.AbstractMemberMetaData;
043 import org.datanucleus.metadata.RelationType;
044 import org.datanucleus.query.expression.DyadicExpression;
045 import org.datanucleus.query.expression.Expression;
046 import org.datanucleus.query.expression.Expression.Operator;
047 import org.datanucleus.query.expression.PrimaryExpression;
048 import org.slf4j.Logger;
049 import org.slf4j.LoggerFactory;
050
051 /**
052 * Evaluator handling the comparisons ==, <, <=, >, >=.
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 RelationType relationType = mmd.getRelationType(executionContext.getClassLoaderResolver());
158
159 Object queryParam;
160 IndexEntryFactory indexEntryFactory;
161 if (RelationType.NONE == relationType)
162 {
163 indexEntryFactory = getQueryEvaluator().getStoreManager().getIndexFactoryRegistry().getIndexEntryFactory(
164 getQueryEvaluator().getExecutionContext(), fieldMeta, true
165 );
166 queryParam = value;
167 }
168 else if (RelationType.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 }