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.eval;
019    
020    import java.util.HashSet;
021    import java.util.Set;
022    
023    import org.cumulus4j.store.crypto.CryptoContext;
024    import org.cumulus4j.store.model.DataEntry;
025    import org.cumulus4j.store.query.QueryEvaluator;
026    import org.cumulus4j.store.query.QueryHelper;
027    import org.datanucleus.query.expression.DyadicExpression;
028    import org.datanucleus.query.expression.Expression;
029    import org.datanucleus.util.NucleusLogger;
030    
031    /**
032     * <p>
033     * Evaluator handling the boolean operation "&amp;&amp;" (AND).
034     * </p>
035     * <p>
036     * Cumulus4j encrypts as much as possible and keeps a minimum of plain-text indexes. The plain-text-indexes
037     * index each field separately. This is a compromise between security and searchability. As the index
038     * contains only plain-text field-values without any plain-text context (the context is encrypted),
039     * it provides the advantage of high security, but at the same time it is not possible to query an
040     * AND operation directly in the underlying database.
041     * </p>
042     * <p>
043     * Instead, the AND operation is performed by first querying all {@link DataEntry#getDataEntryID() dataEntryID}s
044     * of the {@link #getLeft() left} and the {@link #getRight() right} side and then intersecting these two
045     * <code>Set&lt;Long&gt;</code> in memory.
046     * </p>
047     * <p>
048     * If the {@link ResultDescriptor} indicates a {@link ResultDescriptor#isNegated() negation}, this evaluator
049     * delegates to the {@link OrExpressionEvaluator}, because a query like
050     * "!( a > 5 &amp;&amp; b <= 12 )" is internally converted to "a <= 5 || b > 12" for performance reasons.
051     * See {@link NotExpressionEvaluator} as well as
052     * <a target="_blank" href="http://en.wikipedia.org/wiki/De_Morgan%27s_laws">De Morgan's laws</a> in wikipedia for details.
053     * </p>
054     *
055     * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
056     * @see OrExpressionEvaluator
057     */
058    public class AndExpressionEvaluator
059    extends AbstractExpressionEvaluator<DyadicExpression>
060    {
061            private OrExpressionEvaluator negatedExpressionEvaluator;
062    
063            public AndExpressionEvaluator(QueryEvaluator queryEvaluator, AbstractExpressionEvaluator<?> parent, DyadicExpression expression) {
064                    super(queryEvaluator, parent, expression);
065            }
066    
067            public AndExpressionEvaluator(OrExpressionEvaluator negatedExpressionEvaluator)
068            {
069                    this(negatedExpressionEvaluator.getQueryEvaluator(), negatedExpressionEvaluator.getParent(), negatedExpressionEvaluator.getExpression());
070                    this.negatedExpressionEvaluator = negatedExpressionEvaluator;
071            }
072    
073            @Override
074            public AbstractExpressionEvaluator<? extends Expression> getLeft() {
075                    if (negatedExpressionEvaluator != null)
076                            return negatedExpressionEvaluator.getLeft();
077    
078                    return super.getLeft();
079            }
080    
081            @Override
082            public AbstractExpressionEvaluator<? extends Expression> getRight() {
083                    if (negatedExpressionEvaluator != null)
084                            return negatedExpressionEvaluator.getRight();
085    
086                    return super.getRight();
087            }
088    
089            @Override
090            protected Set<Long> _queryResultDataEntryIDs(ResultDescriptor resultDescriptor)
091            {
092                    if (resultDescriptor.isNegated())
093                            return new OrExpressionEvaluator(this)._queryResultDataEntryIDsIgnoringNegation(resultDescriptor);
094                    else
095                            return _queryResultDataEntryIDsIgnoringNegation(resultDescriptor);
096            }
097    
098            protected Set<Long> _queryResultDataEntryIDsIgnoringNegation(ResultDescriptor resultDescriptor)
099            {
100                    if (getLeft() == null)
101                            throw new IllegalStateException("getLeft() == null");
102    
103                    if (getRight() == null)
104                            throw new IllegalStateException("getRight() == null");
105    
106                    Set<Long> leftResult = null;
107                    boolean leftEvaluated = true;
108                    try {
109                            leftResult = getLeft().queryResultDataEntryIDs(resultDescriptor);
110                    }
111                    catch (UnsupportedOperationException uoe) {
112                            leftEvaluated = false;
113                            getQueryEvaluator().setIncomplete();
114                            NucleusLogger.QUERY.debug("Unsupported operation in LEFT : "+getLeft().getExpression() + " so deferring evaluation to in-memory");
115                    }
116    
117                    Set<Long> rightResult = null;
118                    boolean rightEvaluated = true;
119                    try {
120                            rightResult = getRight().queryResultDataEntryIDs(resultDescriptor);
121                    }
122                    catch (UnsupportedOperationException uoe) {
123                            rightEvaluated = false;
124                            getQueryEvaluator().setIncomplete();
125                            NucleusLogger.QUERY.debug("Unsupported operation in RIGHT : "+getRight().getExpression() + " so deferring evaluation to in-memory");
126                    }
127    
128                    if (!leftEvaluated && !rightEvaluated) {
129                            // Neither side evaluated so return all data entry ids
130                            CryptoContext cryptoContext = getQueryEvaluator().getCryptoContext();
131                            leftResult = QueryHelper.getAllDataEntryIdsForCandidate(cryptoContext,
132                                            getQueryEvaluator().getPersistenceManagerForData(), getQueryEvaluator().getQuery().getCandidateClass(),
133                                            getQueryEvaluator().getQuery().isSubclasses());
134                    }
135    
136                    if (leftResult != null && rightResult != null) {
137                            Set<Long> dataEntryIDs1;
138                            Set<Long> dataEntryIDs2;
139    
140                            // Swap them, if the first set is bigger than the 2nd (we want to always iterate the smaller set => faster).
141                            if (leftResult.size() > rightResult.size()) {
142                                    dataEntryIDs1 = rightResult;
143                                    dataEntryIDs2 = leftResult;
144                            }
145                            else {
146                                    dataEntryIDs1 = leftResult;
147                                    dataEntryIDs2 = rightResult;
148                            }
149    
150                            Set<Long> result = new HashSet<Long>(dataEntryIDs1.size());
151                            for (Long dataEntryID : dataEntryIDs1) {
152                                    if (dataEntryIDs2.contains(dataEntryID))
153                                            result.add(dataEntryID);
154                            }
155                            return result;
156                    }
157                    else if (leftResult != null)
158                            return leftResult;
159                    else
160                            return rightResult;
161            }
162    }