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 }