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    }