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.FieldMetaRole;
032    import org.cumulus4j.store.model.IndexEntry;
033    import org.cumulus4j.store.model.IndexEntryFactory;
034    import org.cumulus4j.store.model.IndexValue;
035    import org.cumulus4j.store.query.QueryEvaluator;
036    import org.cumulus4j.store.query.eval.ExpressionHelper;
037    import org.cumulus4j.store.query.eval.InvokeExpressionEvaluator;
038    import org.cumulus4j.store.query.eval.PrimaryExpressionResolver;
039    import org.cumulus4j.store.query.eval.ResultDescriptor;
040    import org.datanucleus.ExecutionContext;
041    import org.datanucleus.query.QueryUtils;
042    import org.datanucleus.query.expression.Expression;
043    import org.datanucleus.query.expression.Literal;
044    import org.datanucleus.query.expression.ParameterExpression;
045    import org.datanucleus.query.expression.PrimaryExpression;
046    import org.datanucleus.query.expression.VariableExpression;
047    
048    /**
049     * Evaluator for "Collection.contains(element)".
050     */
051    public class CollectionContainsEvaluator extends AbstractMethodEvaluator
052    {
053            /* (non-Javadoc)
054             * @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)
055             */
056            @Override
057            public Set<Long> evaluate(QueryEvaluator queryEval, InvokeExpressionEvaluator invokeExprEval,
058                            Expression invokedExpr, ResultDescriptor resultDesc) {
059    
060                    if (invokeExprEval.getExpression().getArguments().size() != 1)
061                            throw new IllegalStateException("contains(...) expects exactly one argument, but there are " +
062                                            invokeExprEval.getExpression().getArguments().size());
063    
064                    if (invokedExpr instanceof PrimaryExpression) {
065                            // Evaluate the invoke argument
066                            Expression invokeArgExpr = invokeExprEval.getExpression().getArguments().get(0);
067                            Object invokeArgument;
068                            if (invokeArgExpr instanceof Literal)
069                                    invokeArgument = ((Literal)invokeArgExpr).getLiteral();
070                            else if (invokeArgExpr instanceof ParameterExpression)
071                                    invokeArgument = QueryUtils.getValueForParameterExpression(queryEval.getParameterValues(), (ParameterExpression)invokeArgExpr);
072                            else if (invokeArgExpr instanceof VariableExpression)
073                                    return new ExpressionHelper.ContainsVariableResolver(
074                                                    queryEval, (PrimaryExpression) invokedExpr, FieldMetaRole.collectionElement, (VariableExpression) invokeArgExpr,
075                                                    resultDesc.isNegated()
076                                    ).query();
077                            else
078                                    throw new UnsupportedOperationException("NYI");
079    
080                            return new ExpressionHelper.ContainsConstantResolver(
081                                            queryEval, (PrimaryExpression) invokedExpr, FieldMetaRole.collectionElement, invokeArgument,
082                                            resultDesc.isNegated()
083                            ).query();
084                    }
085                    else if (invokedExpr instanceof ParameterExpression) {
086                            Expression invokeArgExpr = invokeExprEval.getExpression().getArguments().get(0);
087                            Object paramValue = QueryUtils.getValueForParameterExpression(queryEval.getParameterValues(), (ParameterExpression)invokedExpr);
088    
089                            if (invokeArgExpr instanceof PrimaryExpression) {
090                                    return new ParameterContainsPrimaryEvaluator(queryEval, (PrimaryExpression) invokeArgExpr, (Collection)paramValue, resultDesc.isNegated()).query();
091                            }
092                            else {
093                                    throw new UnsupportedOperationException("NYI invocation of Collection.contains on a " + invokedExpr.getClass().getName());
094                            }
095                    }
096                    else {
097                            throw new UnsupportedOperationException("NYI invocation of Collection.contains on a " + invokedExpr.getClass().getName());
098                    }
099            }
100    
101            private class ParameterContainsPrimaryEvaluator extends PrimaryExpressionResolver
102            {
103                    private Collection invokeCollection;
104                    private boolean negate;
105    
106                    public ParameterContainsPrimaryEvaluator(
107                                    QueryEvaluator queryEvaluator, PrimaryExpression primaryExpression,
108                                    Collection invokeCollection,
109                                    boolean negate
110                    )
111                    {
112                            super(queryEvaluator, primaryExpression);
113                            this.invokeCollection = invokeCollection;
114                            this.negate = negate;
115                    }
116    
117                    @Override
118                    protected Set<Long> queryEnd(FieldMeta fieldMeta, ClassMeta classMeta) {
119                            CryptoContext cryptoContext = queryEvaluator.getCryptoContext();
120                            ExecutionContext executionContext = queryEvaluator.getExecutionContext();
121                            IndexEntryFactory indexEntryFactory = queryEvaluator.getStoreManager().getIndexFactoryRegistry().getIndexEntryFactory(
122                                            executionContext, fieldMeta, true
123                            );
124    
125                            Query q = queryEvaluator.getPersistenceManagerForIndex().newQuery(indexEntryFactory.getIndexEntryClass());
126                            StringBuilder str = new StringBuilder();
127                            str.append("this.keyStoreRefID == :keyStoreRefID && this.fieldMeta_fieldID == :fieldMeta_fieldID");
128                            if (!invokeCollection.isEmpty()) {
129                                    if (negate) {
130                                            str.append(" && !:paramColl.contains(this.indexKey)");
131                                    }
132                                    else {
133                                            str.append(" && :paramColl.contains(this.indexKey)");
134                                    }
135                            }
136    
137                            q.setFilter(str.toString());
138                            Map<String, Object> params = new HashMap<String, Object>(2);
139                            params.put("keyStoreRefID", cryptoContext.getKeyStoreRefID());
140                            params.put("fieldMeta_fieldID", fieldMeta.getFieldID());
141                            params.put("paramColl", invokeCollection);
142    
143                            @SuppressWarnings("unchecked")
144                            Collection<? extends IndexEntry> indexEntries = (Collection<? extends IndexEntry>) q.executeWithMap(params);
145    
146                            Set<Long> result = new HashSet<Long>();
147                            for (IndexEntry indexEntry : indexEntries) {
148                                    IndexValue indexValue = queryEvaluator.getEncryptionHandler().decryptIndexEntry(cryptoContext, indexEntry);
149                                    result.addAll(indexValue.getDataEntryIDs());
150                            }
151                            q.closeAll();
152                            return result;
153                    }
154            }
155    }