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 }