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    }