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.method; 019 020 import java.util.Collection; 021 import java.util.HashMap; 022 import java.util.HashSet; 023 import java.util.Map; 024 import java.util.Set; 025 026 import javax.jdo.Query; 027 028 import org.cumulus4j.store.crypto.CryptoContext; 029 import org.cumulus4j.store.model.ClassMeta; 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.IndexValue; 034 import org.cumulus4j.store.query.QueryEvaluator; 035 import org.cumulus4j.store.query.eval.ExpressionHelper; 036 import org.cumulus4j.store.query.eval.InvokeExpressionEvaluator; 037 import org.cumulus4j.store.query.eval.PrimaryExpressionResolver; 038 import org.cumulus4j.store.query.eval.ResultDescriptor; 039 import org.datanucleus.query.expression.Expression; 040 import org.datanucleus.query.expression.PrimaryExpression; 041 import org.datanucleus.store.ExecutionContext; 042 043 /** 044 * Evaluator for "{String}.startsWith(arg)". 045 */ 046 public class StringStartsWithEvaluator extends AbstractMethodEvaluator 047 { 048 /* (non-Javadoc) 049 * @see org.cumulus4j.store.query.method.MethodEvaluator#evaluate(org.cumulus4j.store.query.QueryEvaluator, org.datanucleus.query.expression.InvokeExpression, org.datanucleus.query.expression.Expression, org.cumulus4j.store.query.eval.ResultDescriptor) 050 */ 051 @Override 052 public Set<Long> evaluate(QueryEvaluator queryEval, InvokeExpressionEvaluator invokeExprEval, 053 Expression invokedExpr, ResultDescriptor resultDesc) { 054 if (invokeExprEval.getExpression().getArguments().size() != 1) 055 throw new IllegalStateException("startsWith(...) expects exactly one argument, but there are " + 056 invokeExprEval.getExpression().getArguments().size()); 057 058 // Evaluate the invoke argument 059 Object invokeArgument = ExpressionHelper.getEvaluatedInvokeArgument(queryEval, invokeExprEval.getExpression()); 060 061 if (invokedExpr instanceof PrimaryExpression) { 062 return new MethodResolver(queryEval, (PrimaryExpression) invokedExpr, invokeArgument, resultDesc.isNegated()).query(); 063 } 064 else { 065 if (!invokeExprEval.getLeft().getResultSymbols().contains(resultDesc.getSymbol())) 066 return null; 067 return queryEvaluate(queryEval, resultDesc.getFieldMeta(), invokeArgument, resultDesc.isNegated()); 068 } 069 } 070 071 private Set<Long> queryEvaluate( 072 QueryEvaluator queryEval, 073 FieldMeta fieldMeta, 074 Object invokeArgument, // the xxx in 'startsWith(xxx)' 075 boolean negate 076 ) { 077 CryptoContext cryptoContext = queryEval.getCryptoContext(); 078 ExecutionContext executionContext = queryEval.getExecutionContext(); 079 IndexEntryFactory indexEntryFactory = queryEval.getStoreManager().getIndexFactoryRegistry().getIndexEntryFactory( 080 executionContext, fieldMeta, true 081 ); 082 083 Query q = queryEval.getPersistenceManagerForIndex().newQuery(indexEntryFactory.getIndexEntryClass()); 084 q.setFilter( 085 "this.keyStoreRefID == :keyStoreRefID && this.fieldMeta_fieldID == :fieldMeta_fieldID && " + 086 (negate ? "!this.indexKey.startsWith(:invokeArg)" : "this.indexKey.startsWith(:invokeArg) ") 087 ); 088 Map<String, Object> params = new HashMap<String, Object>(3); 089 params.put("keyStoreRefID", cryptoContext.getKeyStoreRefID()); 090 params.put("fieldMeta_fieldID", fieldMeta.getFieldID()); 091 params.put("invokeArg", invokeArgument); 092 093 @SuppressWarnings("unchecked") 094 Collection<? extends IndexEntry> indexEntries = (Collection<? extends IndexEntry>) q.executeWithMap(params); 095 096 Set<Long> result = new HashSet<Long>(); 097 for (IndexEntry indexEntry : indexEntries) { 098 IndexValue indexValue = queryEval.getEncryptionHandler().decryptIndexEntry(cryptoContext, indexEntry); 099 result.addAll(indexValue.getDataEntryIDs()); 100 } 101 q.closeAll(); 102 return result; 103 } 104 105 private class MethodResolver extends PrimaryExpressionResolver 106 { 107 private Object invokeArgument; 108 private boolean negate; 109 110 public MethodResolver( 111 QueryEvaluator queryEvaluator, PrimaryExpression primaryExpression, 112 Object invokeArgument, // the xxx in 'startsWith(xxx)' 113 boolean negate 114 ) 115 { 116 super(queryEvaluator, primaryExpression); 117 this.invokeArgument = invokeArgument; 118 this.negate = negate; 119 } 120 121 @Override 122 protected Set<Long> queryEnd(FieldMeta fieldMeta, ClassMeta classMeta) { 123 return queryEvaluate(queryEvaluator, fieldMeta, invokeArgument, negate); 124 } 125 } 126 }