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