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