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 }