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