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 }