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.util.Arrays;
021 import java.util.Map;
022
023 import javax.jdo.PersistenceManager;
024
025 import org.cumulus4j.store.crypto.CryptoContext;
026 import org.cumulus4j.store.fieldmanager.FetchFieldManager;
027 import org.cumulus4j.store.fieldmanager.StoreFieldManager;
028 import org.cumulus4j.store.model.ClassMeta;
029 import org.cumulus4j.store.model.DataEntry;
030 import org.cumulus4j.store.model.DataEntryDAO;
031 import org.cumulus4j.store.model.EmbeddedClassMeta;
032 import org.cumulus4j.store.model.EmbeddedFieldMeta;
033 import org.cumulus4j.store.model.EmbeddedObjectContainer;
034 import org.cumulus4j.store.model.FieldMeta;
035 import org.cumulus4j.store.model.ObjectContainer;
036 import org.datanucleus.ExecutionContext;
037 import org.datanucleus.exceptions.NucleusObjectNotFoundException;
038 import org.datanucleus.metadata.AbstractClassMetaData;
039 import org.datanucleus.metadata.AbstractMemberMetaData;
040 import org.datanucleus.state.ObjectProvider;
041 import org.datanucleus.store.AbstractPersistenceHandler;
042 import org.datanucleus.store.connection.ManagedConnection;
043 import org.slf4j.Logger;
044 import org.slf4j.LoggerFactory;
045
046 /**
047 * Handler for all persistence calls from the StoreManager, communicating with the backend datastore(s).
048 * Manages all inserts/updates/deletes/fetches/locates of the users own objects and translates them
049 * into inserts/updates/deletes/fetches/locates of Cumulus4J model objects.
050 */
051 public class Cumulus4jPersistenceHandler extends AbstractPersistenceHandler
052 {
053 private static final Logger logger = LoggerFactory.getLogger(Cumulus4jPersistenceHandler.class);
054
055 private Cumulus4jStoreManager storeManager;
056 private EncryptionCoordinateSetManager encryptionCoordinateSetManager;
057 private KeyStoreRefManager keyStoreRefManager;
058 private EncryptionHandler encryptionHandler;
059
060 private IndexEntryAction addIndexEntryAction;
061 private IndexEntryAction removeIndexEntryAction;
062
063 private static <T> T assertNotNull(String objectName, T object) {
064 if (object == null)
065 throw new IllegalArgumentException(objectName + " == null");
066
067 return object;
068 }
069
070 public Cumulus4jPersistenceHandler(Cumulus4jStoreManager storeManager) {
071 super(assertNotNull("storeManager", storeManager));
072 this.storeManager = storeManager;
073 this.encryptionCoordinateSetManager = storeManager.getEncryptionCoordinateSetManager();
074 this.keyStoreRefManager = storeManager.getKeyStoreRefManager();
075 this.encryptionHandler = storeManager.getEncryptionHandler();
076
077 this.addIndexEntryAction = new IndexEntryAction.Add(this);
078 this.removeIndexEntryAction = new IndexEntryAction.Remove(this);
079 }
080
081 public Cumulus4jStoreManager getStoreManager() {
082 return storeManager;
083 }
084
085 @Override
086 public void close() {
087 // No resources require to be closed here.
088 }
089
090 @Override
091 public void deleteObject(ObjectProvider op) {
092 // Check if read-only so update not permitted
093 storeManager.assertReadOnlyForUpdateOfObject(op);
094
095 ExecutionContext ec = op.getExecutionContext();
096 ManagedConnection mconn = storeManager.getConnection(ec);
097 try {
098 PersistenceManagerConnection pmConn = (PersistenceManagerConnection)mconn.getConnection();
099 PersistenceManager pmData = pmConn.getDataPM();
100 CryptoContext cryptoContext = new CryptoContext(encryptionCoordinateSetManager, keyStoreRefManager, ec, pmConn);
101 getStoreManager().getDatastoreVersionManager().applyOnce(cryptoContext);
102
103 Object object = op.getObject();
104 Object objectID = op.getExternalObjectId();
105 String objectIDString = objectID.toString();
106 final ClassMeta classMeta = storeManager.getClassMeta(ec, object.getClass());
107 DataEntry dataEntry = new DataEntryDAO(pmData, cryptoContext.getKeyStoreRefID()).getDataEntry(classMeta, objectIDString);
108 // if (dataEntry == null)
109 // throw new NucleusObjectNotFoundException("Object does not exist in datastore: class=" + classMeta.getClassName() + " oid=" + objectIDString);
110
111 if (dataEntry != null) {
112 // decrypt object-container in order to identify index entries for deletion
113 ObjectContainer objectContainer = encryptionHandler.decryptDataEntry(cryptoContext, dataEntry);
114 if (objectContainer != null) {
115 AbstractClassMetaData dnClassMetaData = storeManager.getMetaDataManager().getMetaDataForClass(object.getClass(), ec.getClassLoaderResolver());
116
117 deleteObjectIndex(cryptoContext, classMeta, dataEntry, objectContainer, dnClassMetaData);
118 }
119 pmData.deletePersistent(dataEntry);
120 }
121
122 } finally {
123 mconn.release();
124 }
125 }
126
127 protected void deleteObjectIndex(
128 CryptoContext cryptoContext, final ClassMeta classMeta, DataEntry dataEntry,
129 ObjectContainer objectContainer, AbstractClassMetaData dnClassMetaData
130 )
131 {
132 for (Map.Entry<Long, ?> me : objectContainer.getFieldID2value().entrySet()) {
133 long fieldID = me.getKey();
134 Object fieldValue = me.getValue();
135 FieldMeta fieldMeta = classMeta.getFieldMeta(fieldID);
136 deleteObjectIndex(cryptoContext, classMeta, dataEntry, fieldMeta, fieldValue);
137 }
138 }
139
140 protected void deleteObjectIndex(
141 CryptoContext cryptoContext, ClassMeta classMeta, DataEntry dataEntry,
142 FieldMeta fieldMeta, EmbeddedObjectContainer embeddedObjectContainer
143 )
144 {
145 ClassMeta embeddedClassMeta = storeManager.getClassMeta(cryptoContext.getExecutionContext(), embeddedObjectContainer.getClassID(), true);
146 EmbeddedClassMeta ecm = (EmbeddedClassMeta) embeddedClassMeta;
147 for (Map.Entry<Long, ?> me : embeddedObjectContainer.getFieldID2value().entrySet()) {
148 long embeddedFieldID = me.getKey();
149 Object embeddedFieldValue = me.getValue();
150 EmbeddedFieldMeta embeddedFieldMeta = (EmbeddedFieldMeta) ecm.getFieldMeta(embeddedFieldID);
151 deleteObjectIndex(cryptoContext, embeddedClassMeta, dataEntry, embeddedFieldMeta, embeddedFieldValue);
152 }
153 }
154
155 protected void deleteObjectIndex(
156 CryptoContext cryptoContext, ClassMeta classMeta, DataEntry dataEntry,
157 FieldMeta fieldMeta, Object fieldValue
158 )
159 {
160 if (fieldValue instanceof EmbeddedObjectContainer) {
161 EmbeddedObjectContainer embeddedObjectContainer = (EmbeddedObjectContainer) fieldValue;
162 deleteObjectIndex(cryptoContext, classMeta, dataEntry, fieldMeta, embeddedObjectContainer);
163 }
164 else if (fieldValue instanceof EmbeddedObjectContainer[]) {
165 EmbeddedObjectContainer[] embeddedObjectContainers = (EmbeddedObjectContainer[]) fieldValue;
166 for (EmbeddedObjectContainer embeddedObjectContainer : embeddedObjectContainers) {
167 if (embeddedObjectContainer != null)
168 deleteObjectIndex(cryptoContext, classMeta, dataEntry, fieldMeta, embeddedObjectContainer);
169 }
170 }
171 else {
172 // AbstractMemberMetaData dnMemberMetaData = dnClassMetaData.getMetaDataForManagedMemberAtAbsolutePosition(fieldMeta.getDataNucleusAbsoluteFieldNumber());
173 AbstractMemberMetaData dnMemberMetaData = fieldMeta.getDataNucleusMemberMetaData(cryptoContext.getExecutionContext());
174
175 // sanity checks
176 if (dnMemberMetaData == null)
177 throw new IllegalStateException("dnMemberMetaData == null!!! class == \"" + classMeta.getClassName() + "\" fieldMeta.dataNucleusAbsoluteFieldNumber == " + fieldMeta.getDataNucleusAbsoluteFieldNumber() + " fieldMeta.fieldName == \"" + fieldMeta.getFieldName() + "\"");
178
179 if (!fieldMeta.getFieldName().equals(dnMemberMetaData.getName()))
180 throw new IllegalStateException("Meta data inconsistency!!! class == \"" + classMeta.getClassName() + "\" fieldMeta.dataNucleusAbsoluteFieldNumber == " + fieldMeta.getDataNucleusAbsoluteFieldNumber() + " fieldMeta.fieldName == \"" + fieldMeta.getFieldName() + "\" != dnMemberMetaData.name == \"" + dnMemberMetaData.getName() + "\"");
181
182 removeIndexEntryAction.perform(cryptoContext, dataEntry.getDataEntryID(), fieldMeta, dnMemberMetaData, classMeta, fieldValue);
183 }
184 }
185
186 @Override
187 public void fetchObject(ObjectProvider op, int[] fieldNumbers)
188 {
189 ExecutionContext ec = op.getExecutionContext();
190 ManagedConnection mconn = storeManager.getConnection(ec);
191 try {
192 PersistenceManagerConnection pmConn = (PersistenceManagerConnection)mconn.getConnection();
193 PersistenceManager pmData = pmConn.getDataPM();
194 CryptoContext cryptoContext = new CryptoContext(encryptionCoordinateSetManager, keyStoreRefManager, ec, pmConn);
195 getStoreManager().getDatastoreVersionManager().applyOnce(cryptoContext);
196
197 Object object = op.getObject();
198 Object objectID = op.getExternalObjectId();
199 String objectIDString = objectID.toString();
200 final ClassMeta classMeta = storeManager.getClassMeta(ec, object.getClass());
201 AbstractClassMetaData dnClassMetaData = storeManager.getMetaDataManager().getMetaDataForClass(object.getClass(), ec.getClassLoaderResolver());
202
203 // TODO Maybe we should load ALL *SIMPLE* fields, because the decryption happens on a per-row-level and thus
204 // loading only some fields makes no sense performance-wise. However, maybe DataNucleus already optimizes
205 // calls to this method. It makes definitely no sense to load 1-n- or 1-1-fields and it makes no sense to
206 // optimize things that already are optimal. Hence we have to analyze first, how often this method is really
207 // called in normal operation.
208 // Marco.
209
210 DataEntry dataEntry = new DataEntryDAO(pmData, cryptoContext.getKeyStoreRefID()).getDataEntry(classMeta, objectIDString);
211 if (dataEntry == null)
212 throw new NucleusObjectNotFoundException("Object does not exist in datastore: class=" + classMeta.getClassName() + " oid=" + objectIDString);
213
214 ObjectContainer objectContainer = encryptionHandler.decryptDataEntry(cryptoContext, dataEntry);
215
216 op.replaceFields(fieldNumbers, new FetchFieldManager(op, cryptoContext, classMeta, dnClassMetaData, objectContainer));
217 if (op.getVersion() == null) // null-check prevents overwriting in case this method is called multiple times (for different field-numbers) - TODO necessary?
218 op.setVersion(objectContainer.getVersion());
219 } finally {
220 mconn.release();
221 }
222 }
223
224 @Override
225 public Object findObject(ExecutionContext ec, Object id) {
226 // Since we don't manage the memory instantiation of objects this just returns null.
227 return null;
228 }
229
230 @Override
231 public void insertObjects(ObjectProvider ... ops) {
232 boolean error = true;
233 ObjectContainerHelper.enterTemporaryReferenceScope();
234 try {
235 super.insertObjects(ops);
236
237 error = false;
238 } finally {
239 ObjectContainerHelper.exitTemporaryReferenceScope(error);
240 }
241 }
242
243 @Override
244 public void deleteObjects(ObjectProvider... ops) {
245 boolean error = true;
246 ObjectContainerHelper.enterTemporaryReferenceScope();
247 try {
248 super.deleteObjects(ops);
249
250 error = false;
251 } finally {
252 ObjectContainerHelper.exitTemporaryReferenceScope(error);
253 }
254 }
255
256
257 @Override
258 public void insertObject(ObjectProvider op)
259 {
260 // Check if read-only so update not permitted
261 storeManager.assertReadOnlyForUpdateOfObject(op);
262
263 if (op.getEmbeddedOwners() != null && op.getEmbeddedOwners().length > 0) {
264 return; // don't handle embedded objects here!
265 }
266
267 ExecutionContext ec = op.getExecutionContext();
268 ManagedConnection mconn = storeManager.getConnection(ec);
269 try {
270 PersistenceManagerConnection pmConn = (PersistenceManagerConnection)mconn.getConnection();
271 PersistenceManager pmData = pmConn.getDataPM();
272 CryptoContext cryptoContext = new CryptoContext(encryptionCoordinateSetManager, keyStoreRefManager, ec, pmConn);
273 getStoreManager().getDatastoreVersionManager().applyOnce(cryptoContext);
274
275 boolean error = true;
276 ObjectContainerHelper.enterTemporaryReferenceScope();
277 try {
278 Object object = op.getObject();
279 Object objectID = op.getExternalObjectId();
280 if (objectID == null) {
281 throw new IllegalStateException("op.getExternalObjectId() returned null! Maybe Cumulus4jStoreManager.isStrategyDatastoreAttributed(...) is incorrect?");
282 }
283 final ClassMeta classMeta = storeManager.getClassMeta(ec, object.getClass());
284
285 AbstractClassMetaData dnClassMetaData = storeManager.getMetaDataManager().getMetaDataForClass(object.getClass(), ec.getClassLoaderResolver());
286
287 ObjectContainer objectContainer = new ObjectContainer();
288 String objectIDString = objectID.toString();
289
290 // We have to persist the DataEntry before the call to provideFields(...), because the InsertFieldManager recursively
291 // persists other fields which might back-reference (=> mapped-by) and thus need this DataEntry to already exist.
292 // TO DO Try to make this persistent afterwards and solve the problem by only allocating the ID before [keeping it in memory] (see Cumulus4jStoreManager#nextDataEntryID(), which is commented out currently).
293 // Even though reducing the INSERT + UPDATE to one single INSERT in the handling of IndexEntry made
294 // things faster, it seems not to have a performance benefit here. But we should still look at this
295 // again later.
296 // Marco.
297 //
298 // 2012-02-02: Refactored this because of a Heisenbug with optimistic transactions. At the same time solved
299 // the above to do. Marco :-)
300
301 // This performs reachability on this input object so that all related objects are persisted.
302 op.provideFields(
303 dnClassMetaData.getAllMemberPositions(),
304 new StoreFieldManager(op, cryptoContext, pmData, classMeta, dnClassMetaData, cryptoContext.getKeyStoreRefID(), objectContainer));
305 objectContainer.setVersion(op.getTransactionalVersion());
306
307 // The DataEntry might already have been written by ObjectContainerHelper.entityToReference(...),
308 // if it was needed for a reference. We therefore check, if it already exists (and update it then instead of insert).
309 boolean persistDataEntry = false;
310 DataEntry dataEntry = ObjectContainerHelper.getTemporaryReferenceDataEntry(cryptoContext, pmData, objectIDString);
311 if (dataEntry != null)
312 logger.trace("insertObject: Found temporary-reference-DataEntry for: {}", objectIDString);
313 else {
314 persistDataEntry = true;
315 dataEntry = new DataEntry(classMeta, cryptoContext.getKeyStoreRefID(), objectIDString);
316 logger.trace("insertObject: Created new DataEntry for: {}", objectIDString);
317 }
318
319 encryptionHandler.encryptDataEntry(cryptoContext, dataEntry, objectContainer);
320
321 // persist data
322 if (persistDataEntry) {
323 dataEntry = pmData.makePersistent(dataEntry);
324 logger.trace("insertObject: Persisted new non-embedded DataEntry for: {}", objectIDString);
325 }
326
327 insertObjectIndex(op, cryptoContext, classMeta, dnClassMetaData, objectContainer, dataEntry);
328
329 error = false;
330 } finally {
331 ObjectContainerHelper.exitTemporaryReferenceScope(error);
332 }
333 } finally {
334 mconn.release();
335 }
336 }
337
338 protected void insertObjectIndex(
339 ObjectProvider op, CryptoContext cryptoContext,
340 ClassMeta classMeta, AbstractClassMetaData dnClassMetaData,
341 ObjectContainer objectContainer, DataEntry dataEntry
342 )
343 {
344 // persist index
345 for (Map.Entry<Long, ?> me : objectContainer.getFieldID2value().entrySet()) {
346 long fieldID = me.getKey();
347 Object fieldValue = me.getValue();
348 FieldMeta fieldMeta = classMeta.getFieldMeta(fieldID);
349 if (fieldMeta == null)
350 throw new IllegalStateException("fieldMeta not found: " + classMeta + ": fieldID=" + fieldID);
351
352 insertObjectIndex(cryptoContext, classMeta, dataEntry, fieldMeta, fieldValue);
353 }
354 }
355
356 protected void insertObjectIndex(
357 CryptoContext cryptoContext, ClassMeta classMeta, DataEntry dataEntry,
358 FieldMeta fieldMeta, EmbeddedObjectContainer embeddedObjectContainer
359 )
360 {
361 ClassMeta embeddedClassMeta = storeManager.getClassMeta(cryptoContext.getExecutionContext(), embeddedObjectContainer.getClassID(), true);
362 EmbeddedClassMeta ecm = (EmbeddedClassMeta) embeddedClassMeta;
363 for (Map.Entry<Long, ?> me : embeddedObjectContainer.getFieldID2value().entrySet()) {
364 long embeddedFieldID = me.getKey();
365 Object embeddedFieldValue = me.getValue();
366 EmbeddedFieldMeta embeddedFieldMeta = (EmbeddedFieldMeta) ecm.getFieldMeta(embeddedFieldID);
367 if (embeddedFieldMeta == null)
368 throw new IllegalStateException("fieldMeta not found: " + classMeta + ": embeddedFieldID=" + embeddedFieldID);
369
370 insertObjectIndex(cryptoContext, embeddedClassMeta, dataEntry, embeddedFieldMeta, embeddedFieldValue);
371 }
372 }
373
374 protected void insertObjectIndex(
375 CryptoContext cryptoContext, ClassMeta classMeta, DataEntry dataEntry,
376 FieldMeta fieldMeta, Object fieldValue
377 )
378 {
379 if (cryptoContext == null)
380 throw new IllegalArgumentException("cryptoContext == null");
381
382 if (classMeta == null)
383 throw new IllegalArgumentException("classMeta == null");
384
385 if (dataEntry == null)
386 throw new IllegalArgumentException("dataEntry == null");
387
388 if (fieldMeta == null)
389 throw new IllegalArgumentException("fieldMeta == null");
390
391 if (fieldValue instanceof EmbeddedObjectContainer) {
392 EmbeddedObjectContainer embeddedObjectContainer = (EmbeddedObjectContainer) fieldValue;
393 insertObjectIndex(cryptoContext, classMeta, dataEntry, fieldMeta, embeddedObjectContainer);
394 }
395 else if (fieldValue instanceof EmbeddedObjectContainer[]) {
396 EmbeddedObjectContainer[] embeddedObjectContainers = (EmbeddedObjectContainer[]) fieldValue;
397 for (EmbeddedObjectContainer embeddedObjectContainer : embeddedObjectContainers) {
398 if (embeddedObjectContainer != null)
399 insertObjectIndex(cryptoContext, classMeta, dataEntry, fieldMeta, embeddedObjectContainer);
400 }
401 }
402 else {
403 AbstractMemberMetaData dnMemberMetaData = fieldMeta.getDataNucleusMemberMetaData(cryptoContext.getExecutionContext());
404
405 // sanity checks
406 if (dnMemberMetaData == null)
407 throw new IllegalStateException("dnMemberMetaData == null!!! class == \"" + classMeta.getClassName() + "\" fieldMeta.dataNucleusAbsoluteFieldNumber == " + fieldMeta.getDataNucleusAbsoluteFieldNumber() + " fieldMeta.fieldName == \"" + fieldMeta.getFieldName() + "\"");
408
409 if (!fieldMeta.getFieldName().equals(dnMemberMetaData.getName()))
410 throw new IllegalStateException("Meta data inconsistency!!! class == \"" + classMeta.getClassName() + "\" fieldMeta.dataNucleusAbsoluteFieldNumber == " + fieldMeta.getDataNucleusAbsoluteFieldNumber() + " fieldMeta.fieldName == \"" + fieldMeta.getFieldName() + "\" != dnMemberMetaData.name == \"" + dnMemberMetaData.getName() + "\"");
411
412 addIndexEntryAction.perform(cryptoContext, dataEntry.getDataEntryID(), fieldMeta, dnMemberMetaData, classMeta, fieldValue);
413 }
414 }
415
416 @Override
417 public void locateObject(ObjectProvider op)
418 {
419 ExecutionContext ec = op.getExecutionContext();
420 ManagedConnection mconn = storeManager.getConnection(op.getExecutionContext());
421 try {
422 PersistenceManagerConnection pmConn = (PersistenceManagerConnection)mconn.getConnection();
423 PersistenceManager pmData = pmConn.getDataPM();
424 CryptoContext cryptoContext = new CryptoContext(encryptionCoordinateSetManager, keyStoreRefManager, ec, pmConn);
425 getStoreManager().getDatastoreVersionManager().applyOnce(cryptoContext);
426
427 ClassMeta classMeta = storeManager.getClassMeta(op.getExecutionContext(), op.getObject().getClass());
428 Object objectID = op.getExternalObjectId();
429 String objectIDString = objectID.toString();
430
431 DataEntry dataEntry = new DataEntryDAO(pmData, cryptoContext.getKeyStoreRefID()).getDataEntry(classMeta, objectIDString);
432 if (dataEntry == null)
433 throw new NucleusObjectNotFoundException("Object does not exist in datastore: class=" + classMeta.getClassName() + " oid=" + objectIDString);
434 } finally {
435 mconn.release();
436 }
437 }
438
439 @Override
440 public void updateObject(ObjectProvider op, int[] fieldNumbers)
441 {
442 // Check if read-only so update not permitted
443 storeManager.assertReadOnlyForUpdateOfObject(op);
444
445 if (op.getEmbeddedOwners() != null && op.getEmbeddedOwners().length > 0) {
446 return; // don't handle embedded objects here!
447 }
448
449 ExecutionContext ec = op.getExecutionContext();
450 ManagedConnection mconn = storeManager.getConnection(ec);
451 try {
452 PersistenceManagerConnection pmConn = (PersistenceManagerConnection)mconn.getConnection();
453 PersistenceManager pmData = pmConn.getDataPM();
454 CryptoContext cryptoContext = new CryptoContext(encryptionCoordinateSetManager, keyStoreRefManager, ec, pmConn);
455 getStoreManager().getDatastoreVersionManager().applyOnce(cryptoContext);
456
457 boolean error = true;
458 ObjectContainerHelper.enterTemporaryReferenceScope();
459 try {
460
461 Object object = op.getObject();
462 Object objectID = op.getExternalObjectId();
463 String objectIDString = objectID.toString();
464 final ClassMeta classMeta = storeManager.getClassMeta(ec, object.getClass());
465 AbstractClassMetaData dnClassMetaData = storeManager.getMetaDataManager().getMetaDataForClass(object.getClass(), ec.getClassLoaderResolver());
466
467 DataEntry dataEntry = new DataEntryDAO(pmData, cryptoContext.getKeyStoreRefID()).getDataEntry(classMeta, objectIDString);
468 if (dataEntry == null)
469 throw new NucleusObjectNotFoundException("Object does not exist in datastore: class=" + classMeta.getClassName() + " oid=" + objectIDString);
470
471 ObjectContainer objectContainerOld = encryptionHandler.decryptDataEntry(cryptoContext, dataEntry);
472 ObjectContainer objectContainerNew = objectContainerOld.clone();
473
474 // This performs reachability on this input object so that all related objects are persisted
475 op.provideFields(fieldNumbers, new StoreFieldManager(op, cryptoContext, pmData, classMeta, dnClassMetaData, cryptoContext.getKeyStoreRefID(), objectContainerNew));
476 objectContainerNew.setVersion(op.getTransactionalVersion());
477
478 // update persistent data
479 encryptionHandler.encryptDataEntry(cryptoContext, dataEntry, objectContainerNew);
480
481 // update persistent index
482 for (int fieldNumber : fieldNumbers) {
483 AbstractMemberMetaData dnMemberMetaData = dnClassMetaData.getMetaDataForManagedMemberAtAbsolutePosition(fieldNumber);
484 if (dnMemberMetaData == null)
485 throw new IllegalStateException("dnMemberMetaData == null!!! class == \"" + classMeta.getClassName() + "\" fieldNumber == " + fieldNumber);
486
487 if (dnMemberMetaData.getMappedBy() != null)
488 continue; // TODO is this sufficient to take 'mapped-by' into account?
489
490 FieldMeta fieldMeta = classMeta.getFieldMeta(dnMemberMetaData.getClassName(), dnMemberMetaData.getName());
491 if (fieldMeta == null)
492 throw new IllegalStateException("fieldMeta == null!!! class == \"" + classMeta.getClassName() + "\" dnMemberMetaData.className == \"" + dnMemberMetaData.getClassName() + "\" dnMemberMetaData.name == \"" + dnMemberMetaData.getName() + "\"");
493
494 Object fieldValueOld = objectContainerOld.getValue(fieldMeta.getFieldID());
495 Object fieldValueNew = objectContainerNew.getValue(fieldMeta.getFieldID());
496
497 if (!fieldsEqual(fieldValueOld, fieldValueNew)){
498
499 // removeIndexEntryAction.perform(cryptoContext, dataEntryID, fieldMeta, dnMemberMetaData, classMeta, fieldValueOld);
500 // addIndexEntryAction.perform( cryptoContext, dataEntryID, fieldMeta, dnMemberMetaData, classMeta, fieldValueNew);
501 deleteObjectIndex(cryptoContext, classMeta, dataEntry, fieldMeta, fieldValueOld);
502 insertObjectIndex(cryptoContext, classMeta, dataEntry, fieldMeta, fieldValueNew);
503 }
504 }
505
506 error = false;
507 } finally {
508 ObjectContainerHelper.exitTemporaryReferenceScope(error);
509 }
510 } finally {
511 mconn.release();
512 }
513 }
514
515 private static boolean fieldsEqual(Object obj0, Object obj1) {
516 if (obj0 instanceof Object[] && obj1 instanceof Object[])
517 return obj0 == obj1 || Arrays.equals((Object[])obj0, (Object[])obj1);
518 return obj0 == obj1 || (obj0 != null && obj0.equals(obj1));
519 }
520
521 // TODO what happened to this method? Was it moved or renamed? I don't find it.
522 // @Override
523 public boolean useReferentialIntegrity() {
524 // https://sourceforge.net/tracker/?func=detail&aid=3515527&group_id=517465&atid=2102914
525 // return super.useReferentialIntegrity();
526 return false;
527 }
528
529 /**
530 * Get the {@link IndexEntryAction} used to add an index-entry.
531 * @return the {@link IndexEntryAction} used to add an index-entry. Never <code>null</code>.
532 */
533 public IndexEntryAction getAddIndexEntryAction() {
534 return addIndexEntryAction;
535 }
536
537 /**
538 * Get the {@link IndexEntryAction} used to remove an index-entry.
539 * @return the {@link IndexEntryAction} used to remove an index-entry. Never <code>null</code>.
540 */
541 public IndexEntryAction getRemoveIndexEntryAction() {
542 return removeIndexEntryAction;
543 }
544 }