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