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    }