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.indexOf(str [,from]) {oper} {compareTo}".
045     */
046    public class StringIndexOfEvaluator extends AbstractMethodEvaluator {
047    
048            /* (non-Javadoc)
049             * @see org.cumulus4j.store.query.method.AbstractMethodEvaluator#requiresComparisonArgument()
050             */
051            @Override
052            public boolean requiresComparisonArgument() {
053                    return true;
054            }
055    
056            /* (non-Javadoc)
057             * @see org.cumulus4j.store.query.method.MethodEvaluator#evaluate(org.cumulus4j.store.query.QueryEvaluator, org.cumulus4j.store.query.eval.InvokeExpressionEvaluator, org.datanucleus.query.expression.Expression, org.cumulus4j.store.query.eval.ResultDescriptor)
058             */
059            @Override
060            public Set<Long> evaluate(QueryEvaluator queryEval,
061                            InvokeExpressionEvaluator invokeExprEval, Expression invokedExpr,
062                            ResultDescriptor resultDesc) {
063                    if (invokeExprEval.getExpression().getArguments().size() < 1 || invokeExprEval.getExpression().getArguments().size() > 2)
064                            throw new IllegalStateException("String.indexOf(...) expects 1 or 2 arguments, but there are " +
065                                            invokeExprEval.getExpression().getArguments().size());
066    
067                    // Evaluate the invoke argument
068                    Object[] invokeArgs = ExpressionHelper.getEvaluatedInvokeArguments(queryEval, invokeExprEval.getExpression());
069    
070                    if (invokedExpr instanceof PrimaryExpression) {
071                            return new MethodResolver(invokeExprEval, queryEval, (PrimaryExpression) invokedExpr, invokeArgs[0],
072                                            (invokeArgs.length > 1 ? invokeArgs[1] : null), compareToArgument, resultDesc.isNegated()).query();
073                    }
074                    else {
075                            if (!invokeExprEval.getLeft().getResultSymbols().contains(resultDesc.getSymbol()))
076                                    return null;
077    
078                            return queryEvaluate(invokeExprEval, queryEval, resultDesc.getFieldMeta(), invokeArgs[0],
079                                            (invokeArgs.length > 1 ? invokeArgs[1] : null), compareToArgument, resultDesc.isNegated());
080                    }
081            }
082    
083            private Set<Long> queryEvaluate(
084                            InvokeExpressionEvaluator invokeExprEval,
085                            QueryEvaluator queryEval,
086                            FieldMeta fieldMeta,
087                            Object invokeArg1, // the xxx in 'indexOf(xxx)'
088                            Object invokeArg2, // the xxx2 in 'indexOf(xxx1, xxx2)'
089                            Object compareToArgument, // the yyy in 'indexOf(xxx) >= yyy'
090                            boolean negate
091            ) {
092                    CryptoContext cryptoContext = queryEval.getCryptoContext();
093                    ExecutionContext executionContext = queryEval.getExecutionContext();
094                    IndexEntryFactory indexEntryFactory = queryEval.getStoreManager().getIndexFactoryRegistry().getIndexEntryFactory(
095                                    executionContext, fieldMeta, true
096                    );
097    
098                    Query q = queryEval.getPersistenceManagerForIndex().newQuery(indexEntryFactory.getIndexEntryClass());
099                    q.setFilter(
100                                    "this.keyStoreRefID == :keyStoreRefID && this.fieldMeta_fieldID == :fieldMeta_fieldID && " +
101                                    (invokeArg2 != null ?
102                                                    "this.indexKey.indexOf(:invokeArg,:invokeFrom) " : "this.indexKey.indexOf(:invokeArg) ") +
103                                    ExpressionHelper.getOperatorAsJDOQLSymbol(invokeExprEval.getParent().getExpression().getOperator(), negate) +
104                                    " :compareToArgument"
105                    );
106                    Map<String, Object> params = new HashMap<String, Object>(3);
107                    params.put("keyStoreRefID", cryptoContext.getKeyStoreRefID());
108                    params.put("fieldMeta_fieldID", fieldMeta.getFieldID());
109                    params.put("invokeArg", invokeArg1);
110                    if (invokeArg2 != null)
111                    {
112                            params.put("invokeFrom", invokeArg2);
113                    }
114                    params.put("compareToArgument", compareToArgument);
115    
116                    @SuppressWarnings("unchecked")
117                    Collection<? extends IndexEntry> indexEntries = (Collection<? extends IndexEntry>) q.executeWithMap(params);
118    
119                    Set<Long> result = new HashSet<Long>();
120                    for (IndexEntry indexEntry : indexEntries) {
121                            IndexValue indexValue = queryEval.getEncryptionHandler().decryptIndexEntry(cryptoContext, indexEntry);
122                            result.addAll(indexValue.getDataEntryIDs());
123                    }
124                    q.closeAll();
125                    return result;
126            }
127    
128            private class MethodResolver extends PrimaryExpressionResolver
129            {
130                    private InvokeExpressionEvaluator invokeExprEval;
131                    private Object invokeArg;
132                    private Object invokeFrom;
133                    private Object compareToArgument;
134                    private boolean negate;
135    
136                    public MethodResolver(
137                                    InvokeExpressionEvaluator invokeExprEval,
138                                    QueryEvaluator queryEvaluator, PrimaryExpression primaryExpression,
139                                    Object invokeArg1, // the xxx1 in 'indexOf(xxx1) >= yyy'
140                                    Object invokeArg2, // the xxx2 in 'indexOf(xxx1, xxx2) >= yyy'
141                                    Object compareToArgument, // the yyy in 'indexOf(xxx) >= yyy'
142                                    boolean negate
143                    )
144                    {
145                            super(queryEvaluator, primaryExpression);
146                            this.invokeExprEval = invokeExprEval;
147                            this.invokeArg = invokeArg1;
148                            this.invokeFrom = invokeArg2;
149                            this.compareToArgument = compareToArgument;
150                            this.negate = negate;
151                    }
152    
153                    @Override
154                    protected Set<Long> queryEnd(FieldMeta fieldMeta, ClassMeta classMeta) {
155                            return queryEvaluate(invokeExprEval, queryEvaluator, fieldMeta, invokeArg, invokeFrom, compareToArgument, negate);
156                    }
157            }
158    }