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    }