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.FieldMeta;
030    import org.cumulus4j.store.model.IndexEntry;
031    import org.cumulus4j.store.model.IndexEntryFactory;
032    import org.cumulus4j.store.model.IndexValue;
033    import org.cumulus4j.store.query.QueryEvaluator;
034    import org.cumulus4j.store.query.eval.ExpressionHelper;
035    import org.cumulus4j.store.query.eval.InvokeExpressionEvaluator;
036    import org.cumulus4j.store.query.eval.PrimaryExpressionResolver;
037    import org.cumulus4j.store.query.eval.ResultDescriptor;
038    import org.datanucleus.query.expression.Expression;
039    import org.datanucleus.query.expression.PrimaryExpression;
040    import org.datanucleus.store.ExecutionContext;
041    
042    /**
043     * Evaluator for "{String}.matches(arg)".
044     */
045    public class StringMatchesEvaluator extends AbstractMethodEvaluator
046    {
047            /* (non-Javadoc)
048             * @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)
049             */
050            @Override
051            public Set<Long> evaluate(QueryEvaluator queryEval, InvokeExpressionEvaluator invokeExprEval,
052                            Expression invokedExpr, ResultDescriptor resultDesc) {
053                    // TODO Support escape argument
054                    if (invokeExprEval.getExpression().getArguments().size() != 1)
055                            throw new IllegalStateException("matches(...) 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 'matches(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.fieldMeta == :fieldMeta && " +
086                                    (negate ? "!this.indexKey.matches(:invokeArg)" : "this.indexKey.matches(:invokeArg) ")
087                    );
088                    Map<String, Object> params = new HashMap<String, Object>(3);
089                    params.put("fieldMeta", fieldMeta);
090                    params.put("invokeArg", invokeArgument);
091    
092                    @SuppressWarnings("unchecked")
093                    Collection<? extends IndexEntry> indexEntries = (Collection<? extends IndexEntry>) q.executeWithMap(params);
094    
095                    Set<Long> result = new HashSet<Long>();
096                    for (IndexEntry indexEntry : indexEntries) {
097                            IndexValue indexValue = queryEval.getEncryptionHandler().decryptIndexEntry(cryptoContext, indexEntry);
098                            result.addAll(indexValue.getDataEntryIDs());
099                    }
100                    q.closeAll();
101                    return result;
102            }
103    
104            private class MethodResolver extends PrimaryExpressionResolver
105            {
106                    private Object invokeArgument;
107                    private boolean negate;
108    
109                    public MethodResolver(
110                                    QueryEvaluator queryEvaluator, PrimaryExpression primaryExpression,
111                                    Object invokeArgument, // the xxx in 'matches(xxx)'
112                                    boolean negate
113                    )
114                    {
115                            super(queryEvaluator, primaryExpression);
116                            this.invokeArgument = invokeArgument;
117                            this.negate = negate;
118                    }
119    
120                    @Override
121                    protected Set<Long> queryEnd(FieldMeta fieldMeta) {
122                            return queryEvaluate(queryEvaluator, fieldMeta, invokeArgument, negate);
123                    }
124            }
125    }