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 "&&" (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<Long></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 && 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 }