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