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