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