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 }