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.LinkedList;
022 import java.util.List;
023 import java.util.Set;
024
025 import org.cumulus4j.store.Cumulus4jStoreManager;
026 import org.cumulus4j.store.ObjectContainerHelper;
027 import org.cumulus4j.store.crypto.CryptoContext;
028 import org.cumulus4j.store.model.ClassMeta;
029 import org.cumulus4j.store.model.DataEntry;
030 import org.cumulus4j.store.model.FieldMeta;
031 import org.cumulus4j.store.model.IndexEntry;
032 import org.cumulus4j.store.model.IndexEntryObjectRelationHelper;
033 import org.cumulus4j.store.model.IndexValue;
034 import org.cumulus4j.store.model.ObjectContainer;
035 import org.cumulus4j.store.query.MemberNotQueryableException;
036 import org.cumulus4j.store.query.QueryEvaluator;
037 import org.datanucleus.metadata.AbstractMemberMetaData;
038 import org.datanucleus.query.expression.PrimaryExpression;
039 import org.datanucleus.query.expression.VariableExpression;
040 import org.datanucleus.query.symbol.Symbol;
041 import org.datanucleus.store.ExecutionContext;
042 import org.slf4j.Logger;
043 import org.slf4j.LoggerFactory;
044
045 /**
046 * <p>
047 * Abstract base class for easy resolving of {@link PrimaryExpression}s. This class
048 * takes care of following one-to-one-relations inside the <code>PrimaryExpression</code>.
049 * </p>
050 * <p>
051 * For example, <code>this.aaa.bbb.ccc.ddd.someSet.contains(:param)</code> requires first to
052 * evaluate <code>DDD.someSet.contains(:param)</code> and then to follow the field chain back from
053 * <code>ddd</code> over <code>ccc</code> over <code>bbb</code> over <code>aaa</code> finally to <code>this</code>.
054 * The subclasses of <code>PrimaryExpressionResolver</code> only need to take care of the implementation
055 * of the last part in the chain (in our example <code>DDD.someSet.contains(:param)</code>) - the rest is done
056 * here.
057 * </p>
058 *
059 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
060 */
061 public abstract class PrimaryExpressionResolver
062 {
063 private static final Logger logger = LoggerFactory.getLogger(PrimaryExpressionResolver.class);
064
065 protected QueryEvaluator queryEvaluator;
066 protected PrimaryExpression primaryExpression;
067 protected CryptoContext cryptoContext;
068 protected ExecutionContext executionContext;
069
070 public PrimaryExpressionResolver(QueryEvaluator queryEvaluator, PrimaryExpression primaryExpression) {
071 if (queryEvaluator == null)
072 throw new IllegalArgumentException("queryEvaluator == null");
073
074 if (primaryExpression == null)
075 throw new IllegalArgumentException("primaryExpression == null");
076
077 this.queryEvaluator = queryEvaluator;
078 this.primaryExpression = primaryExpression;
079 this.cryptoContext = queryEvaluator.getCryptoContext();
080 this.executionContext = queryEvaluator.getExecutionContext();
081 }
082
083 public Set<Long> query()
084 {
085 List<String> tuples = new LinkedList<String>(primaryExpression.getTuples());
086 if (tuples.size() < 1)
087 throw new IllegalStateException("primaryExpression.tuples.size < 1");
088
089 Symbol symbol;
090 if (primaryExpression.getLeft() instanceof VariableExpression) {
091 symbol = ((VariableExpression)primaryExpression.getLeft()).getSymbol();
092 if (symbol == null)
093 throw new IllegalStateException("((VariableExpression)primaryExpression.getLeft()).getSymbol() returned null!");
094 }
095 else if (primaryExpression.getLeft() == null) {
096 if (queryEvaluator.getCandidateAlias().equals(tuples.get(0)))
097 tuples.remove(0);
098
099 symbol = queryEvaluator.getCompilation().getSymbolTable().getSymbol(queryEvaluator.getCandidateAlias());
100 if (symbol == null)
101 throw new IllegalStateException("getQueryEvaluator().getCompilation().getSymbolTable().getSymbol(getQueryEvaluator().getCandidateAlias()) returned null! candidateAlias=" + queryEvaluator.getCandidateAlias());
102 }
103 else
104 throw new UnsupportedOperationException("NYI");
105
106 Class<?> clazz = queryEvaluator.getValueType(symbol);
107 ClassMeta classMeta = queryEvaluator.getStoreManager().getClassMeta(executionContext, clazz);
108 return queryMiddle(classMeta, tuples);
109 }
110
111 protected Set<Long> queryMiddle(ClassMeta classMeta, List<String> tuples)
112 {
113 if (tuples.size() < 1)
114 throw new IllegalStateException("tuples.size < 1");
115
116 tuples = new LinkedList<String>(tuples);
117 String nextTuple = tuples.remove(0);
118 FieldMeta fieldMetaForNextTuple = classMeta.getFieldMeta(null, nextTuple);
119 if (fieldMetaForNextTuple == null)
120 throw new IllegalStateException("Neither the class " + classMeta.getClassName() + " nor one of its superclasses contain a field named \"" + nextTuple + "\"!");
121
122 AbstractMemberMetaData mmd = fieldMetaForNextTuple.getDataNucleusMemberMetaData(executionContext);
123 if (mmd.hasExtension(Cumulus4jStoreManager.CUMULUS4J_QUERYABLE) && mmd.getValueForExtension(Cumulus4jStoreManager.CUMULUS4J_QUERYABLE).equalsIgnoreCase("false")) {
124 throw new MemberNotQueryableException("Field/property " + mmd.getFullFieldName() + " is not queryable!");
125 }
126
127 if (tuples.isEmpty()) {
128 return queryEnd(fieldMetaForNextTuple);
129 }
130 else {
131 // join
132 Class<?> nextTupleType = mmd.getType();
133 ClassMeta classMetaForNextTupleType = queryEvaluator.getStoreManager().getClassMeta(executionContext, nextTupleType);
134 Set<Long> dataEntryIDsForNextTuple = queryMiddle(classMetaForNextTupleType, tuples);
135 Set<Long> result = new HashSet<Long>();
136 if (fieldMetaForNextTuple.getDataNucleusMemberMetaData(executionContext).getMappedBy() == null) {
137 for (Long dataEntryIDForNextTuple : dataEntryIDsForNextTuple) {
138 IndexEntry indexEntry = IndexEntryObjectRelationHelper.getIndexEntry(
139 queryEvaluator.getPersistenceManagerForIndex(), fieldMetaForNextTuple, dataEntryIDForNextTuple
140 );
141 if (indexEntry != null) {
142 IndexValue indexValue = queryEvaluator.getEncryptionHandler().decryptIndexEntry(
143 cryptoContext, indexEntry
144 );
145 result.addAll(indexValue.getDataEntryIDs());
146 }
147 }
148 }
149 else {
150 for (Long dataEntryIDForNextTuple : dataEntryIDsForNextTuple) {
151 DataEntry dataEntry = DataEntry.getDataEntry(queryEvaluator.getPersistenceManagerForData(), dataEntryIDForNextTuple);
152 if (dataEntry == null)
153 logger.warn("queryMiddle: There is no DataEntry with dataEntryID=" + dataEntryIDForNextTuple + "! " + fieldMetaForNextTuple);
154 else {
155 ObjectContainer objectContainer = queryEvaluator.getEncryptionHandler().decryptDataEntry(cryptoContext, dataEntry);
156 Object value = objectContainer.getValue(fieldMetaForNextTuple.getMappedByFieldMeta(executionContext).getFieldID());
157 if (value != null)
158 result.add(ObjectContainerHelper.referenceToDataEntryID(executionContext, queryEvaluator.getPersistenceManagerForData(), value));
159 }
160 }
161 }
162 return result;
163 }
164 }
165
166 protected abstract Set<Long> queryEnd(FieldMeta fieldMeta);
167 }