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;
019
020 import java.lang.reflect.Array;
021 import java.util.Map;
022
023 import javax.jdo.JDOHelper;
024 import javax.jdo.JDOUserException;
025 import javax.jdo.PersistenceManager;
026 import javax.jdo.Query;
027
028 import org.cumulus4j.store.crypto.CryptoContext;
029 import org.cumulus4j.store.model.ClassMeta;
030 import org.cumulus4j.store.model.FieldMeta;
031 import org.cumulus4j.store.model.FieldMetaRole;
032 import org.cumulus4j.store.model.IndexEntry;
033 import org.cumulus4j.store.model.IndexEntryFactory;
034 import org.cumulus4j.store.model.IndexEntryFactoryRegistry;
035 import org.cumulus4j.store.model.IndexEntryObjectRelationHelper;
036 import org.cumulus4j.store.model.IndexValue;
037 import org.datanucleus.ExecutionContext;
038 import org.datanucleus.metadata.AbstractMemberMetaData;
039 import org.datanucleus.metadata.RelationType;
040 import org.slf4j.Logger;
041 import org.slf4j.LoggerFactory;
042
043 /**
044 * Logic to add or remove an index entry.
045 * <p>
046 * This class is thread-safe. You should normally never need to instantiate this class.
047 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
048 */
049 public abstract class IndexEntryAction
050 {
051 protected Cumulus4jPersistenceHandler persistenceHandler;
052 protected Cumulus4jStoreManager storeManager;
053 protected EncryptionHandler encryptionHandler;
054 protected IndexEntryFactoryRegistry indexEntryFactoryRegistry;
055
056 public IndexEntryAction(Cumulus4jPersistenceHandler persistenceHandler) {
057 if (persistenceHandler == null)
058 throw new IllegalArgumentException("persistenceHandler == null");
059
060 this.persistenceHandler = persistenceHandler;
061 this.storeManager = persistenceHandler.getStoreManager();
062 this.encryptionHandler = storeManager.getEncryptionHandler();
063 indexEntryFactoryRegistry = storeManager.getIndexFactoryRegistry();
064 }
065
066 protected abstract IndexEntry getIndexEntry(
067 IndexEntryFactory indexEntryFactory, CryptoContext cryptoContext, PersistenceManager pmIndex, FieldMeta fieldMeta, ClassMeta classMeta, Object fieldValue
068 );
069
070 protected abstract IndexEntry getIndexEntryForObjectRelation(CryptoContext cryptoContext, PersistenceManager pmIndex, FieldMeta fieldMeta, ClassMeta classMeta, Long otherDataEntryID);
071
072 protected abstract void _perform(CryptoContext cryptoContext, IndexEntry indexEntry, long dataEntryID);
073
074 public void perform(CryptoContext cryptoContext, long dataEntryID, FieldMeta fieldMeta,
075 AbstractMemberMetaData dnMemberMetaData, ClassMeta classMeta, Object fieldValue
076 )
077 {
078 ExecutionContext ec = cryptoContext.getExecutionContext();
079 PersistenceManager pmData = cryptoContext.getPersistenceManagerForData();
080 PersistenceManager pmIndex = cryptoContext.getPersistenceManagerForIndex();
081 boolean hasQueryable = dnMemberMetaData.hasExtension(Cumulus4jStoreManager.CUMULUS4J_QUERYABLE);
082 if (hasQueryable) {
083 String val = dnMemberMetaData.getValueForExtension(Cumulus4jStoreManager.CUMULUS4J_QUERYABLE);
084 if (val.equalsIgnoreCase("false")) {
085 // Field marked as not queryable, so don't index it
086 return;
087 }
088 }
089
090 RelationType relationType = dnMemberMetaData.getRelationType(cryptoContext.getExecutionContext().getClassLoaderResolver());
091 if (RelationType.NONE == relationType) {
092 // The field contains no other persistent entity. It might contain a collection/array/map, though.
093
094 if (dnMemberMetaData.hasCollection() || dnMemberMetaData.hasArray()) {
095 FieldMetaRole role;
096 if (dnMemberMetaData.hasCollection())
097 role = FieldMetaRole.collectionElement;
098 else
099 role = FieldMetaRole.arrayElement;
100
101 FieldMeta subFieldMeta = fieldMeta.getSubFieldMeta(role);
102 IndexEntryFactory indexEntryFactory = indexEntryFactoryRegistry.getIndexEntryFactory(ec, subFieldMeta, false);
103 if (fieldValue != null) {
104 for (int idx = 0; idx < Array.getLength(fieldValue); ++idx) {
105 Object element = Array.get(fieldValue, idx);
106 IndexEntry indexEntry = getIndexEntry(indexEntryFactory, cryptoContext, pmIndex, subFieldMeta, classMeta, element);
107 _perform(cryptoContext, indexEntry, dataEntryID);
108 }
109 }
110
111 // Add entry for the collection/array size
112 int containerSize = fieldValue == null ? 0 : Array.getLength(fieldValue);
113 IndexEntry sizeIdxEntry =
114 indexEntryFactoryRegistry.getIndexEntryFactoryForContainerSize().createIndexEntry(cryptoContext, pmIndex, fieldMeta, classMeta, new Long(containerSize));
115 _perform(cryptoContext, sizeIdxEntry, dataEntryID);
116 }
117 else if (dnMemberMetaData.hasMap()) {
118 Map<?,?> fieldValueMap = (Map<?,?>) fieldValue;
119
120 FieldMeta subFieldMetaKey = fieldMeta.getSubFieldMeta(FieldMetaRole.mapKey);
121 FieldMeta subFieldMetaValue = fieldMeta.getSubFieldMeta(FieldMetaRole.mapValue);
122 IndexEntryFactory indexEntryFactoryKey = indexEntryFactoryRegistry.getIndexEntryFactory(ec, subFieldMetaKey, false);
123 IndexEntryFactory indexEntryFactoryValue = indexEntryFactoryRegistry.getIndexEntryFactory(ec, subFieldMetaValue, false);
124
125 if (fieldValueMap != null) {
126 for (Map.Entry<?, ?> me : fieldValueMap.entrySet()) {
127 IndexEntry indexEntryKey = getIndexEntry(indexEntryFactoryKey, cryptoContext, pmIndex, subFieldMetaKey, classMeta, me.getKey());
128 _perform(cryptoContext, indexEntryKey, dataEntryID);
129
130 IndexEntry indexEntryValue = getIndexEntry(indexEntryFactoryValue, cryptoContext, pmIndex, subFieldMetaValue, classMeta, me.getValue());
131 _perform(cryptoContext, indexEntryValue, dataEntryID);
132 }
133 }
134
135 // Add entry for the map size
136 int containerSize = (fieldValueMap != null ? fieldValueMap.size() : 0);
137 IndexEntry sizeIdxEntry =
138 indexEntryFactoryRegistry.getIndexEntryFactoryForContainerSize().createIndexEntry(cryptoContext, pmIndex, fieldMeta, classMeta, new Long(containerSize));
139 _perform(cryptoContext, sizeIdxEntry, dataEntryID);
140 }
141 else {
142 IndexEntryFactory indexEntryFactory = indexEntryFactoryRegistry.getIndexEntryFactory(ec, fieldMeta, false);
143 IndexEntry indexEntry = getIndexEntry(indexEntryFactory, cryptoContext, pmIndex, fieldMeta, classMeta, fieldValue);
144 _perform(cryptoContext, indexEntry, dataEntryID);
145 }
146 }
147 else if (RelationType.isRelationSingleValued(relationType)) {
148 // 1-1-relationship to another persistence-capable object.
149 Long otherDataEntryID = ObjectContainerHelper.referenceToDataEntryID(cryptoContext, pmData, fieldValue);
150 IndexEntry indexEntry = getIndexEntryForObjectRelation(cryptoContext, pmIndex, fieldMeta, classMeta, otherDataEntryID);
151 _perform(cryptoContext, indexEntry, dataEntryID);
152 }
153 else if (RelationType.isRelationMultiValued(relationType)) {
154 // map, collection, array
155
156 if (dnMemberMetaData.hasMap()) { // Map.class.isAssignableFrom(dnMemberMetaData.getType())) {
157 Map<?,?> fieldValueMap = (Map<?,?>) fieldValue;
158
159 boolean keyIsPersistent = dnMemberMetaData.getMap().keyIsPersistent();
160 boolean valueIsPersistent = dnMemberMetaData.getMap().valueIsPersistent();
161
162 FieldMeta subFieldMetaKey = fieldMeta.getSubFieldMeta(FieldMetaRole.mapKey);
163 FieldMeta subFieldMetaValue = fieldMeta.getSubFieldMeta(FieldMetaRole.mapValue);
164 IndexEntryFactory indexEntryFactoryKey = indexEntryFactoryRegistry.getIndexEntryFactory(ec, subFieldMetaKey, false);
165 IndexEntryFactory indexEntryFactoryValue = indexEntryFactoryRegistry.getIndexEntryFactory(ec, subFieldMetaValue, false);
166
167 if (fieldValueMap != null) {
168 for (Map.Entry<?, ?> me : fieldValueMap.entrySet()) {
169 if (keyIsPersistent) {
170 Long otherDataEntryID = ObjectContainerHelper.referenceToDataEntryID(cryptoContext, pmData, me.getKey());
171 IndexEntry indexEntry = getIndexEntryForObjectRelation(cryptoContext, pmIndex, subFieldMetaKey, classMeta, otherDataEntryID);
172 _perform(cryptoContext, indexEntry, dataEntryID);
173 }
174 else {
175 IndexEntry indexEntry = getIndexEntry(indexEntryFactoryKey, cryptoContext, pmIndex, subFieldMetaKey, classMeta, me.getKey());
176 _perform(cryptoContext, indexEntry, dataEntryID);
177 }
178
179 if (valueIsPersistent) {
180 Long otherDataEntryID = ObjectContainerHelper.referenceToDataEntryID(cryptoContext, pmData, me.getValue());
181 IndexEntry indexEntry = getIndexEntryForObjectRelation(cryptoContext, pmIndex, subFieldMetaValue, classMeta, otherDataEntryID);
182 _perform(cryptoContext, indexEntry, dataEntryID);
183 }
184 else {
185 IndexEntry indexEntry = getIndexEntry(indexEntryFactoryValue, cryptoContext, pmIndex, subFieldMetaValue, classMeta, me.getValue());
186 _perform(cryptoContext, indexEntry, dataEntryID);
187 }
188 }
189 }
190
191 // Add entry for the map size
192 int containerSize = (fieldValueMap != null ? fieldValueMap.size() : 0);
193 IndexEntry sizeIdxEntry =
194 indexEntryFactoryRegistry.getIndexEntryFactoryForContainerSize().createIndexEntry(cryptoContext, pmIndex, fieldMeta, classMeta, new Long(containerSize));
195 _perform(cryptoContext, sizeIdxEntry, dataEntryID);
196 }
197 else if (dnMemberMetaData.hasCollection() || dnMemberMetaData.hasArray()) {
198 FieldMetaRole role;
199 if (dnMemberMetaData.hasCollection()) // Collection.class.isAssignableFrom(dnMemberMetaData.getType()))
200 role = FieldMetaRole.collectionElement;
201 else
202 role = FieldMetaRole.arrayElement;
203
204 FieldMeta subFieldMeta = fieldMeta.getSubFieldMeta(role);
205 Object[] fieldValueArray = (Object[]) fieldValue;
206 if (fieldValueArray != null) {
207 for (Object element : fieldValueArray) {
208 Long otherDataEntryID = ObjectContainerHelper.referenceToDataEntryID(cryptoContext, pmData, element);
209 IndexEntry indexEntry = getIndexEntryForObjectRelation(cryptoContext, pmIndex, subFieldMeta, classMeta, otherDataEntryID);
210 _perform(cryptoContext, indexEntry, dataEntryID);
211 }
212 }
213
214 // Add entry for the collection/array size
215 int containerSize = (fieldValueArray != null ? fieldValueArray.length : 0);
216 IndexEntry sizeIdxEntry =
217 indexEntryFactoryRegistry.getIndexEntryFactoryForContainerSize().createIndexEntry(cryptoContext, pmIndex, fieldMeta, classMeta, new Long(containerSize));
218 _perform(cryptoContext, sizeIdxEntry, dataEntryID);
219 }
220 }
221 }
222
223 static class Add extends IndexEntryAction
224 {
225 public Add(Cumulus4jPersistenceHandler persistenceHandler) {
226 super(persistenceHandler);
227 }
228
229 @Override
230 public IndexEntry getIndexEntry(IndexEntryFactory indexEntryFactory, CryptoContext cryptoContext, PersistenceManager pmIndex, FieldMeta fieldMeta, ClassMeta classMeta, Object fieldValue)
231 {
232 return indexEntryFactory == null ? null : indexEntryFactory.createIndexEntry(cryptoContext, pmIndex, fieldMeta, classMeta, fieldValue);
233 }
234
235 @Override
236 public IndexEntry getIndexEntryForObjectRelation(CryptoContext cryptoContext, PersistenceManager pmIndex, FieldMeta fieldMeta, ClassMeta classMeta, Long otherDataEntryID) {
237 return IndexEntryObjectRelationHelper.createIndexEntry(cryptoContext, pmIndex, fieldMeta, classMeta, otherDataEntryID);
238 }
239
240 @Override
241 protected void _perform(CryptoContext cryptoContext, IndexEntry indexEntry, long dataEntryID)
242 {
243 if (indexEntry == null)
244 return;
245
246 IndexValue indexValue = encryptionHandler.decryptIndexEntry(cryptoContext, indexEntry);
247 indexValue.addDataEntryID(dataEntryID);
248 encryptionHandler.encryptIndexEntry(cryptoContext, indexEntry, indexValue);
249 cryptoContext.getPersistenceManagerForIndex().makePersistent(indexEntry); // We do not persist directly when creating anymore, thus we must persist here. This is a no-op if it's already persistent.
250 }
251 }
252
253 static class Remove extends IndexEntryAction
254 {
255 private static final Logger logger = LoggerFactory.getLogger(Remove.class);
256
257 public Remove(Cumulus4jPersistenceHandler persistenceHandler) {
258 super(persistenceHandler);
259 }
260
261 @Override
262 public IndexEntry getIndexEntry(IndexEntryFactory indexEntryFactory, CryptoContext cryptoContext, PersistenceManager pmIndex, FieldMeta fieldMeta, ClassMeta classMeta, Object fieldValue)
263 {
264 return indexEntryFactory == null ? null : indexEntryFactory.getIndexEntry(cryptoContext, pmIndex, fieldMeta, classMeta, fieldValue);
265 }
266
267 @Override
268 protected IndexEntry getIndexEntryForObjectRelation(CryptoContext cryptoContext, PersistenceManager pmIndex, FieldMeta fieldMeta, ClassMeta classMeta, Long otherDataEntryID) {
269 return IndexEntryObjectRelationHelper.getIndexEntry(cryptoContext, pmIndex, fieldMeta, classMeta, otherDataEntryID);
270 }
271
272 @Override
273 protected void _perform(CryptoContext cryptoContext, IndexEntry indexEntry, long dataEntryID)
274 {
275 if (indexEntry == null)
276 return;
277
278 IndexValue indexValue = encryptionHandler.decryptIndexEntry(cryptoContext, indexEntry);
279 indexValue.removeDataEntryID(dataEntryID);
280 if (indexValue.isDataEntryIDsEmpty()) {
281 PersistenceManager pmIndex = cryptoContext.getPersistenceManagerForIndex();
282 try {
283 pmIndex.deletePersistent(indexEntry);
284 // The above line sometimes (but not always) causes the following error:
285 // org.datanucleus.exceptions.NucleusUserException: Transient instances cant be deleted.
286 // at org.datanucleus.ObjectManagerImpl.deleteObjectInternal(ObjectManagerImpl.java:2068)
287 // at org.datanucleus.ObjectManagerImpl.deleteObjectWork(ObjectManagerImpl.java:2019)
288 // at org.datanucleus.ObjectManagerImpl.deleteObject(ObjectManagerImpl.java:1973)
289 // at org.datanucleus.api.jdo.JDOPersistenceManager.jdoDeletePersistent(JDOPersistenceManager.java:815)
290 // at org.datanucleus.api.jdo.JDOPersistenceManager.deletePersistent(JDOPersistenceManager.java:833)
291 // at org.cumulus4j.store.IndexEntryAction$Remove._perform(IndexEntryAction.java:268)
292 // at org.cumulus4j.store.IndexEntryAction.perform(IndexEntryAction.java:206)
293 // at org.cumulus4j.store.Cumulus4jPersistenceHandler.updateObject(Cumulus4jPersistenceHandler.java:343)
294 } catch (JDOUserException x) {
295 logger.warn("_perform: " + x, x);
296 // If we cannot delete it, we try to store an empty value. That's not nice, but at least a consistent DB state.
297 encryptionHandler.encryptIndexEntry(cryptoContext, indexEntry, indexValue);
298 // Make sure it is persisted or we get a new exception (because we just tried to delete it).
299 pmIndex.makePersistent(indexEntry);
300 pmIndex.flush();
301 Query query = pmIndex.newQuery(IndexEntry.class);
302 query.setFilter("JDOHelper.getObjectId(this) == :indexEntryID");
303 query.setUnique(true);
304 IndexEntry persistentIndexEntry = (IndexEntry) query.execute(JDOHelper.getObjectId(indexEntry));
305 if (persistentIndexEntry == null)
306 throw new IllegalStateException("Newly persisted IndexEntry did not end up in our database! indexEntryID=" + indexEntry.getIndexEntryID());
307 }
308
309 // BEGIN workaround - this works, but is not nice.
310 // Object indexEntryID = JDOHelper.getObjectId(indexEntry);
311 // if (indexEntryID != null) {
312 // PersistenceManager pmIdx = cryptoContext.getPersistenceManagerForIndex();
313 // pmIdx.flush();
314 // Query query = pmIdx.newQuery(IndexEntry.class);
315 // query.setFilter("JDOHelper.getObjectId(this) == :indexEntryID");
316 // query.setUnique(true);
317 // IndexEntry persistentIndexEntry = (IndexEntry) query.execute(indexEntryID);
318 // if (persistentIndexEntry != null)
319 // pmIdx.deletePersistent(persistentIndexEntry);
320 // }
321 // END workaround
322 }
323 else
324 encryptionHandler.encryptIndexEntry(cryptoContext, indexEntry, indexValue);
325 }
326 }
327 }