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.model;
019
020 import java.util.ArrayList;
021 import java.util.Collections;
022 import java.util.HashMap;
023 import java.util.List;
024 import java.util.Map;
025
026 import javax.jdo.PersistenceManager;
027
028 import org.cumulus4j.store.Cumulus4jStoreManager;
029 import org.cumulus4j.store.EncryptionHandler;
030 import org.cumulus4j.store.crypto.CryptoContext;
031 import org.datanucleus.store.ExecutionContext;
032
033 /**
034 * <p>
035 * Factory for creating (or looking up) specific {@link IndexEntry} implementations.
036 * </p><p>
037 * It is optional to implement a specific factory. For most use cases, it is sufficient to
038 * use the {@link DefaultIndexEntryFactory} (which is used, if the extension specifies the
039 * attribute <code>index-entry-type</code>), but you can alternatively specify a custom
040 * factory via the extension-attribute <code>index-entry-factory-type</code>.
041 * </p><p>
042 * If you specify a custom
043 * factory, you must omit (or leave empty) the <code>index-entry-type</code>!
044 * </p>
045 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
046 */
047 public abstract class IndexEntryFactory
048 {
049 /**
050 * Get the concrete implementation class (sub-class) of {@link IndexEntry} managed by this factory.
051 * @return the concrete implementation class of {@link IndexEntry} managed by this factory.
052 */
053 public abstract Class<? extends IndexEntry> getIndexEntryClass();
054
055 public List<IndexEntry> getIndexEntriesIncludingSubClasses(CryptoContext cryptoContext, PersistenceManager pmIndex, FieldMeta fieldMeta, ClassMeta classMeta, Object indexKey)
056 {
057 ExecutionContext ec = cryptoContext.getExecutionContext();
058 Cumulus4jStoreManager storeManager = (Cumulus4jStoreManager) ec.getStoreManager();
059 List<ClassMeta> classMetaWithSubClassMetas = storeManager.getClassMetaWithSubClassMetas(ec, classMeta);
060 return getIndexEntries(cryptoContext, pmIndex, fieldMeta, classMetaWithSubClassMetas, indexKey);
061 }
062
063 public List<IndexEntry> getIndexEntries(CryptoContext cryptoContext, PersistenceManager pmIndex, FieldMeta fieldMeta, List<ClassMeta> classMetas, Object indexKey)
064 {
065 if (pmIndex == null)
066 throw new IllegalArgumentException("pm == null");
067
068 if (fieldMeta == null)
069 throw new IllegalArgumentException("fieldMeta == null");
070
071 if (classMetas == null)
072 throw new IllegalArgumentException("classMetas == null");
073
074 if (classMetas.isEmpty()) {
075 throw new IllegalArgumentException("classMetas is empty"); // hmmm... I think this should never happen.
076 // return Collections.emptyList();
077 }
078
079 if (classMetas.size() == 1) {
080 IndexEntry indexEntry = getIndexEntry(cryptoContext, pmIndex, fieldMeta, classMetas.get(0), indexKey);
081 if (indexEntry == null)
082 return Collections.emptyList();
083 else
084 return Collections.singletonList(indexEntry);
085 }
086 // List<IndexEntry> result = new ArrayList<IndexEntry>(classMetas.size());
087 // for (ClassMeta classMeta : classMetas) {
088 // IndexEntry indexEntry = getIndexEntry(cryptoContext, pmIndex, fieldMeta, classMeta, indexKey);
089 // if (indexEntry != null)
090 // result.add(indexEntry);
091 // }
092 // return result;
093
094 Class<? extends IndexEntry> indexEntryClass = getIndexEntryClass();
095 javax.jdo.Query q = pmIndex.newQuery(indexEntryClass);
096 Map<String, Object> params = new HashMap<String, Object>();
097 q.setFilter(
098 "this.keyStoreRefID == :keyStoreRefID && " +
099 "this.fieldMeta_fieldID == :fieldMeta_fieldID && " +
100 // ":classMetas.contains(this.classMeta) && " +
101 ClassMetaDAO.getMultiClassMetaOrFilterPart(params, classMetas) + " && " +
102 "this.indexKey == :indexKey"
103 );
104 params.put("keyStoreRefID", cryptoContext.getKeyStoreRefID());
105 params.put("fieldMeta_fieldID", fieldMeta.getFieldID());
106 // params.put("classMetas", classMetas);
107 params.put("indexKey", indexKey);
108 @SuppressWarnings("unchecked")
109 List<IndexEntry> result = (List<IndexEntry>) q.executeWithMap(params);
110 result = Collections.unmodifiableList(new ArrayList<IndexEntry>(result)); // consistent with emptyList + singletonList above (both read-only)
111 q.closeAll();
112 return result;
113 }
114
115 /**
116 * Get an {@link IndexEntry} for the specified unique key fields or <code>null</code>, if no such instance
117 * exists.
118 * @param cryptoContext the crypto-context.
119 * @param pmIndex the backend-<code>PersistenceManager</code>. Must not be <code>null</code>.
120 * @param fieldMeta the meta-data of the field to query. Must not be <code>null</code>.
121 * @param classMeta TODO
122 * @param indexKey the indexed value to search for. Might be <code>null</code> (<code>null</code> can be indexed).
123 * @return the matching {@link IndexEntry} or <code>null</code>.
124 */
125 public IndexEntry getIndexEntry(CryptoContext cryptoContext, PersistenceManager pmIndex, FieldMeta fieldMeta, ClassMeta classMeta, Object indexKey)
126 {
127 if (pmIndex == null)
128 throw new IllegalArgumentException("pm == null");
129
130 if (fieldMeta == null)
131 throw new IllegalArgumentException("fieldMeta == null");
132
133 if (classMeta == null)
134 throw new IllegalArgumentException("classMeta == null");
135
136 Class<? extends IndexEntry> indexEntryClass = getIndexEntryClass();
137 javax.jdo.Query q = pmIndex.newQuery(indexEntryClass);
138 q.setUnique(true);
139 q.setFilter(
140 "this.keyStoreRefID == :keyStoreRefID && " +
141 "this.fieldMeta_fieldID == :fieldMeta_fieldID && " +
142 "this.classMeta_classID == :classMeta_classID && " +
143 "this.indexKey == :indexKey"
144 );
145 Map<String, Object> params = new HashMap<String, Object>();
146 params.put("keyStoreRefID", cryptoContext.getKeyStoreRefID());
147 params.put("fieldMeta_fieldID", fieldMeta.getFieldID());
148 params.put("classMeta_classID", classMeta.getClassID());
149 params.put("indexKey", indexKey);
150 return indexEntryClass.cast(q.executeWithMap(params));
151 }
152
153 /**
154 * Get an existing {@link IndexEntry} just like {@link #getIndexEntry(CryptoContext, PersistenceManager, FieldMeta, ClassMeta, Object)}
155 * or create one, if it does not yet exist.
156 * @param cryptoContext TODO
157 * @param pmIndex the backend-<code>PersistenceManager</code>. Must not be <code>null</code>.
158 * @param fieldMeta the meta-data of the field to query. Must not be <code>null</code>.
159 * @param classMeta TODO
160 * @param indexKey the indexed value to search for. Might be <code>null</code> (<code>null</code> can be indexed).
161 * @return the matching {@link IndexEntry} (never <code>null</code>).
162 */
163 public IndexEntry createIndexEntry(CryptoContext cryptoContext, PersistenceManager pmIndex, FieldMeta fieldMeta, ClassMeta classMeta, Object indexKey)
164 {
165 IndexEntry result = getIndexEntry(cryptoContext, pmIndex, fieldMeta, classMeta, indexKey);
166 if (result == null) {
167 try {
168 result = getIndexEntryClass().newInstance();
169 } catch (InstantiationException e) {
170 throw new RuntimeException(e);
171 } catch (IllegalAccessException e) {
172 throw new RuntimeException(e);
173 }
174 result.setFieldMeta(fieldMeta);
175 result.setClassMeta(classMeta);
176 result.setKeyStoreRefID(cryptoContext.getKeyStoreRefID());
177 result.setIndexKey(indexKey);
178
179 // We persist *after* setting all values, because that improves performance:
180 // This way, there is only one INSERT instead of one INSERT AND one UPDATE for each new
181 // index entry. The MovieQueryTest.importDataCsv() is around 10% faster when using MySQL
182 // (approximately 60 sec vs. 66 sec).
183 // However, when dumping the plaintexts for debugging, we need the indexEntryID already *before*
184 // encryption. Hence, we persist here, if the DEBUG_DUMP flag is set.
185 // Marco :-)
186 if (EncryptionHandler.DEBUG_DUMP)
187 result = pmIndex.makePersistent(result);
188 }
189
190 return result;
191 }
192 }