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.fieldmanager;
019    
020    import java.lang.reflect.Array;
021    import java.util.ArrayList;
022    import java.util.Collection;
023    import java.util.Collections;
024    import java.util.HashSet;
025    import java.util.Iterator;
026    import java.util.List;
027    import java.util.Map;
028    import java.util.Set;
029    
030    import javax.jdo.PersistenceManager;
031    import javax.jdo.spi.JDOImplHelper;
032    import javax.jdo.spi.PersistenceCapable;
033    
034    import org.cumulus4j.store.Cumulus4jPersistenceHandler;
035    import org.cumulus4j.store.Cumulus4jStoreManager;
036    import org.cumulus4j.store.EncryptionHandler;
037    import org.cumulus4j.store.ObjectContainerHelper;
038    import org.cumulus4j.store.crypto.CryptoContext;
039    import org.cumulus4j.store.model.ClassMeta;
040    import org.cumulus4j.store.model.DataEntryDAO;
041    import org.cumulus4j.store.model.EmbeddedObjectContainer;
042    import org.cumulus4j.store.model.FieldMeta;
043    import org.cumulus4j.store.model.FieldMetaRole;
044    import org.cumulus4j.store.model.IndexEntry;
045    import org.cumulus4j.store.model.IndexEntryObjectRelationHelper;
046    import org.cumulus4j.store.model.IndexValue;
047    import org.cumulus4j.store.model.ObjectContainer;
048    import org.datanucleus.ExecutionContext;
049    import org.datanucleus.exceptions.NucleusDataStoreException;
050    import org.datanucleus.identity.IdentityUtils;
051    import org.datanucleus.metadata.AbstractClassMetaData;
052    import org.datanucleus.metadata.AbstractMemberMetaData;
053    import org.datanucleus.metadata.RelationType;
054    import org.datanucleus.state.ObjectProvider;
055    import org.datanucleus.store.fieldmanager.AbstractFieldManager;
056    import org.datanucleus.store.types.SCOUtils;
057    
058    /**
059     * Manager for the process of fetching a user object from the datastore, handling the translation from the
060     * DataEntry object into the users own object.
061     * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
062     */
063    public class FetchFieldManager extends AbstractFieldManager
064    {
065            private ObjectProvider op;
066            private CryptoContext cryptoContext;
067            private PersistenceManager pmData;
068            private PersistenceManager pmIndex;
069            private ExecutionContext ec;
070            private ClassMeta classMeta;
071            private AbstractClassMetaData dnClassMetaData;
072            private ObjectContainer objectContainer;
073    
074            public FetchFieldManager(
075                            ObjectProvider op,
076                            CryptoContext cryptoContext,
077                            ClassMeta classMeta,
078                            AbstractClassMetaData dnClassMetaData,
079                            ObjectContainer objectContainer
080            )
081            {
082                    if (op == null)
083                            throw new IllegalArgumentException("op == null");
084                    if (cryptoContext == null)
085                            throw new IllegalArgumentException("cryptoContext == null");
086                    if (classMeta == null)
087                            throw new IllegalArgumentException("classMeta == null");
088                    if (dnClassMetaData == null)
089                            throw new IllegalArgumentException("dnClassMetaData == null");
090                    if (objectContainer == null)
091                            throw new IllegalArgumentException("objectContainer == null");
092    
093                    this.op = op;
094                    this.cryptoContext = cryptoContext;
095                    this.pmData = cryptoContext.getPersistenceManagerForData();
096                    this.pmIndex = cryptoContext.getPersistenceManagerForIndex();
097                    this.ec = op.getExecutionContext();
098                    this.classMeta = classMeta;
099                    this.dnClassMetaData = dnClassMetaData;
100                    this.objectContainer = objectContainer;
101            }
102    
103            protected EncryptionHandler getEncryptionHandler()
104            {
105                    return ((Cumulus4jStoreManager) ec.getStoreManager()).getEncryptionHandler();
106            }
107    
108            private long getFieldID(int fieldNumber)
109            {
110                    AbstractMemberMetaData mmd = dnClassMetaData.getMetaDataForManagedMemberAtAbsolutePosition(fieldNumber);
111    
112                    FieldMeta fieldMeta = classMeta.getFieldMeta(mmd.getClassName(), mmd.getName());
113                    if (fieldMeta == null)
114                            throw new IllegalStateException("Unknown field! class=" + dnClassMetaData.getFullClassName() + " fieldNumber=" + fieldNumber + " fieldName=" + mmd.getName());
115    
116                    return fieldMeta.getFieldID();
117            }
118    
119            @Override
120            public boolean fetchBooleanField(int fieldNumber) {
121                    Object value = objectContainer.getValue(getFieldID(fieldNumber));
122                    return value == null ? false : (Boolean)value;
123            }
124    
125            @Override
126            public byte fetchByteField(int fieldNumber) {
127                    Object value = objectContainer.getValue(getFieldID(fieldNumber));
128                    return value == null ? 0 : (Byte)value;
129            }
130    
131            @Override
132            public char fetchCharField(int fieldNumber) {
133                    Object value = objectContainer.getValue(getFieldID(fieldNumber));
134                    return value == null ? 0 : (Character)value;
135            }
136    
137            @Override
138            public double fetchDoubleField(int fieldNumber) {
139                    Object value = objectContainer.getValue(getFieldID(fieldNumber));
140                    return value == null ? 0 : (Double)value;
141            }
142    
143            @Override
144            public float fetchFloatField(int fieldNumber) {
145                    Object value = objectContainer.getValue(getFieldID(fieldNumber));
146                    return value == null ? 0 : (Float)value;
147            }
148    
149            @Override
150            public int fetchIntField(int fieldNumber) {
151                    Object value = objectContainer.getValue(getFieldID(fieldNumber));
152                    return value == null ? 0 : (Integer)value;
153            }
154    
155            @Override
156            public long fetchLongField(int fieldNumber) {
157                    Object value = objectContainer.getValue(getFieldID(fieldNumber));
158                    return value == null ? 0 : (Long)value;
159            }
160    
161            @Override
162            public short fetchShortField(int fieldNumber) {
163                    Object value = objectContainer.getValue(getFieldID(fieldNumber));
164                    return value == null ? 0 : (Short)value;
165            }
166    
167            @Override
168            public String fetchStringField(int fieldNumber) {
169                    Object value = objectContainer.getValue(getFieldID(fieldNumber));
170                    return (String)value;
171            }
172    
173            private long thisDataEntryID = -1;
174    
175            protected long getThisDataEntryID()
176            {
177                    if (thisDataEntryID < 0)
178                            thisDataEntryID = new DataEntryDAO(pmData, cryptoContext.getKeyStoreRefID()).getDataEntryID(classMeta, op.getExternalObjectId().toString());
179    
180                    return thisDataEntryID;
181            }
182    
183            @Override
184            public Object fetchObjectField(int fieldNumber)
185            {
186                    AbstractMemberMetaData mmd = dnClassMetaData.getMetaDataForManagedMemberAtAbsolutePosition(fieldNumber);
187                    FieldMeta fieldMeta = classMeta.getFieldMeta(mmd.getClassName(), mmd.getName());
188                    if (fieldMeta == null)
189                            throw new IllegalStateException("Unknown field! class=" + dnClassMetaData.getFullClassName() + " fieldNumber=" + fieldNumber + " fieldName=" + mmd.getName());
190    
191                    Set<Long> mappedByDataEntryIDs = null;
192                    if (mmd.getMappedBy() != null) {
193                            ClassMeta fieldOrElementTypeClassMeta = fieldMeta.getFieldOrElementTypeClassMeta(ec);
194    
195    //                      IndexEntry indexEntry = IndexEntryObjectRelationHelper.getIndexEntry(cryptoContext, pmIndex, fieldMeta.getMappedByFieldMeta(ec), classMeta, getThisDataEntryID());
196                            List<IndexEntry> indexEntries = IndexEntryObjectRelationHelper.getIndexEntriesIncludingSubClasses(cryptoContext, pmIndex, fieldMeta.getMappedByFieldMeta(ec), fieldOrElementTypeClassMeta, getThisDataEntryID());
197                            if (indexEntries.isEmpty())
198                                    mappedByDataEntryIDs = Collections.emptySet();
199                            else {
200                                    for (IndexEntry indexEntry : indexEntries) {
201                                            IndexValue indexValue = getEncryptionHandler().decryptIndexEntry(cryptoContext, indexEntry);
202                                            if (mappedByDataEntryIDs == null)
203                                                    mappedByDataEntryIDs = new HashSet<Long>(indexValue.getDataEntryIDs());
204                                            else
205                                                    mappedByDataEntryIDs.addAll(indexValue.getDataEntryIDs());
206                                    }
207                            }
208                    }
209    
210                    RelationType relationType = mmd.getRelationType(ec.getClassLoaderResolver());
211    
212                    if (relationType == RelationType.NONE)
213                            return fetchObjectFieldWithRelationTypeNone(fieldNumber, mmd, fieldMeta);
214                    else if (RelationType.isRelationSingleValued(relationType))
215                            return fetchObjectFieldWithRelationTypeSingleValue(fieldNumber, mmd, fieldMeta, mappedByDataEntryIDs);
216                    else if (RelationType.isRelationMultiValued(relationType))
217                    {
218                            // Collection/Map/Array
219                            if (mmd.hasCollection())
220                                    return fetchObjectFieldWithRelationTypeCollection(fieldNumber, mmd, fieldMeta, mappedByDataEntryIDs);
221                            else if (mmd.hasMap())
222                                    return fetchObjectFieldWithRelationTypeMap(fieldNumber, mmd, fieldMeta, mappedByDataEntryIDs);
223                            else if (mmd.hasArray())
224                                    return fetchObjectFieldWithRelationTypeArray(fieldNumber, mmd, fieldMeta, mappedByDataEntryIDs);
225                            else
226                                    throw new IllegalStateException("Unexpected multi-valued relationType: " + relationType);
227                    }
228                    else
229                            throw new IllegalStateException("Unexpected relationType: " + relationType);
230            }
231    
232            /**
233             * Fetch related objects that are not persistence-capable.
234             * The related objects might be single-valued, arrays, collections or maps.
235             */
236            protected Object fetchObjectFieldWithRelationTypeNone(int fieldNumber, AbstractMemberMetaData mmd, FieldMeta fieldMeta)
237            {
238                    if (mmd.hasCollection())
239                    {
240                            Collection<Object> collection;
241                            @SuppressWarnings("unchecked")
242                            Class<? extends Collection<Object>> instanceType = SCOUtils.getContainerInstanceType(mmd.getType(), mmd.getOrderMetaData() != null);
243                            try {
244                                    collection = instanceType.newInstance();
245                            } catch (InstantiationException e) {
246                                    throw new NucleusDataStoreException(e.getMessage(), e);
247                            } catch (IllegalAccessException e) {
248                                    throw new NucleusDataStoreException(e.getMessage(), e);
249                            }
250    
251                            Object array = objectContainer.getValue(fieldMeta.getFieldID());
252                            if (array != null) {
253                                    for (int idx = 0; idx < Array.getLength(array); ++idx) {
254                                            Object element = Array.get(array, idx);
255                                            collection.add(element);
256                                    }
257                            }
258                            return op.wrapSCOField(fieldNumber, collection, false, false, true);
259                    }
260    
261                    if (mmd.hasMap())
262                    {
263                            Map<?,?> map = (Map<?,?>) objectContainer.getValue(fieldMeta.getFieldID());
264                            return op.wrapSCOField(fieldNumber, map, false, false, true);
265                    }
266    
267                    // Arrays are stored 'as is', thus no conversion necessary.
268                    return objectContainer.getValue(getFieldID(fieldNumber));
269            }
270    
271            /**
272             * Fetch a single related object (1-1-relationship).
273             * The related object is persistence-capable.
274             */
275            protected Object fetchObjectFieldWithRelationTypeSingleValue(int fieldNumber, AbstractMemberMetaData mmd, FieldMeta fieldMeta, Set<Long> mappedByDataEntryIDs)
276            {
277                    if (mmd.isEmbedded()) {
278                            Object value = objectContainer.getValue(fieldMeta.getFieldID());
279                            if (value == null)
280                                    return null;
281    
282                            if (!(value instanceof EmbeddedObjectContainer))
283                                    throw new IllegalStateException("field claims to be embedded, but persistent field value is not an EmbeddedObjectContainer! fieldID=" + fieldMeta.getFieldID() + " fieldName=" + fieldMeta.getFieldName() + " value=" + value);
284    
285                            EmbeddedObjectContainer embeddedObjectContainer = (EmbeddedObjectContainer) value;
286                            ClassMeta embeddedClassMeta = fieldMeta.getEmbeddedClassMeta();
287                            return createPCFromEmbeddedObjectContainer(fieldNumber, fieldMeta, embeddedClassMeta, embeddedObjectContainer);
288                    }
289                    else {
290                            if (mmd.getMappedBy() != null) {
291                                    if (mappedByDataEntryIDs.isEmpty())
292                                            return null;
293    
294                                    if (mappedByDataEntryIDs.size() != 1)
295                                            throw new IllegalStateException("There are multiple objects referencing a 1-1-mapped-by-relationship! Expected 0 or 1! fieldMeta=" + fieldMeta + " dataEntryIDsForMappedBy=" + mappedByDataEntryIDs);
296    
297                                    long dataEntryID = mappedByDataEntryIDs.iterator().next();
298                                    return getObjectFromDataEntryID(dataEntryID);
299                            }
300    
301                            Object valueID = objectContainer.getValue(fieldMeta.getFieldID());
302                            return ObjectContainerHelper.referenceToEntity(cryptoContext, pmData, valueID);
303                    }
304            }
305    
306            protected Object createPCFromEmbeddedObjectContainer(int fieldNumber, FieldMeta fieldMeta, ClassMeta embeddedClassMeta, EmbeddedObjectContainer embeddedObjectContainer)
307            {
308                    if (fieldMeta == null)
309                            throw new IllegalArgumentException("fieldMeta == null");
310                    if (embeddedClassMeta == null)
311                            throw new IllegalArgumentException("embeddedClassMeta == null");
312    
313                    if (embeddedObjectContainer == null) // we allow null values in embedded lists - or shouldn't we?
314                            return null;
315    
316                    // TODO the newest DN version has a StateManagerFactory that makes the following more convenient (I think) - maybe switch later?!
317    //              ClassMeta embeddedClassMeta = ((Cumulus4jStoreManager)ec.getStoreManager()).getClassMeta(ec, embeddedObjectContainer.getClassID(), true);
318    //              ClassMeta embeddedClassMeta = fieldMeta.getEmbeddedClassMeta();
319                    Class<?> embeddedClass = ec.getClassLoaderResolver().classForName(embeddedClassMeta.getClassName());
320    
321                    AbstractClassMetaData embeddedDNClassMeta = embeddedClassMeta.getDataNucleusClassMetaData(ec);
322                    PersistenceCapable pc = JDOImplHelper.getInstance().newInstance(embeddedClass, null);
323    
324                    ObjectProvider embeddedOP = ec.getNucleusContext().getObjectProviderFactory().newForEmbedded(ec, pc, false, op, fieldNumber);
325                    embeddedOP.replaceFields(
326                                    embeddedDNClassMeta.getAllMemberPositions(),
327                                    new FetchFieldManager(embeddedOP, cryptoContext, embeddedClassMeta, embeddedDNClassMeta, embeddedObjectContainer)
328                    );
329                    return embeddedOP.getObject();
330            }
331    
332            /**
333             * Fetch an array of related objects (1-n-relationship).
334             * The related objects are persistence-capable.
335             */
336            protected Object fetchObjectFieldWithRelationTypeArray(int fieldNumber, AbstractMemberMetaData mmd, FieldMeta fieldMeta, Set<Long> mappedByDataEntryIDs)
337            {
338                    Class<?> elementType = ec.getClassLoaderResolver().classForName(mmd.getArray().getElementType());
339    
340                    Object array;
341    
342                    if (mmd.getArray().isEmbeddedElement()) {
343                            Object value = objectContainer.getValue(fieldMeta.getFieldID());
344                            if (!(value instanceof EmbeddedObjectContainer[]))
345                                    throw new IllegalStateException("field claims to be embedded, but persistent field value is not an array of EmbeddedObjectContainer! fieldID=" + fieldMeta.getFieldID() + " fieldName=" + fieldMeta.getFieldName() + " value=" + value);
346    
347                            EmbeddedObjectContainer[] embeddedObjectContainers = (EmbeddedObjectContainer[]) value;
348                            int arrayLength = embeddedObjectContainers.length;
349                            array = Array.newInstance(elementType, arrayLength);
350                            ClassMeta embeddedClassMeta = fieldMeta.getSubFieldMeta(FieldMetaRole.arrayElement).getEmbeddedClassMeta();
351                            for (int i = 0; i < arrayLength; ++i) {
352                                    Object pc = createPCFromEmbeddedObjectContainer(fieldNumber, fieldMeta, embeddedClassMeta, embeddedObjectContainers[i]);
353                                    Array.set(array, i, pc);
354                            }
355                    }
356                    else {
357                            if (mmd.getMappedBy() != null) {
358                                    int arrayLength = mappedByDataEntryIDs.size();
359                                    array = Array.newInstance(elementType, arrayLength);
360                                    Iterator<Long> it = mappedByDataEntryIDs.iterator();
361                                    for (int i = 0; i < arrayLength; ++i) {
362                                            Long dataEntryID = it.next();
363                                            Object element = getObjectFromDataEntryID(dataEntryID);
364                                            Array.set(array, i, element);
365                                    }
366                            }
367                            else {
368                                    Object ids = objectContainer.getValue(fieldMeta.getFieldID());
369                                    if (ids == null)
370                                            array = null;
371                                    else {
372                                            if (((Cumulus4jPersistenceHandler)ec.getStoreManager().getPersistenceHandler()).useReferentialIntegrity()) {
373                                                    // Directly fill the array.
374                                                    int arrayLength = Array.getLength(ids);
375                                                    array = Array.newInstance(elementType, arrayLength);
376                                                    for (int i = 0; i < arrayLength; ++i) {
377                                                            Object id = Array.get(ids, i);
378                                                            Object element = ObjectContainerHelper.referenceToEntity(cryptoContext, pmData, id);
379                                                            Array.set(array, i, element);
380                                                    }
381                                            }
382                                            else {
383                                                    // https://sourceforge.net/tracker/?func=detail&aid=3515529&group_id=517465&atid=2102914
384                                                    // First fill a list and then transfer everything into an array, because there might
385                                                    // be elements missing (orphaned references).
386                                                    int arrayLength = Array.getLength(ids);
387                                                    ArrayList<Object> tmpList = new ArrayList<Object>();
388                                                    for (int i = 0; i < arrayLength; ++i) {
389                                                            Object id = Array.get(ids, i);
390                                                            Object element = ObjectContainerHelper.referenceToEntity(cryptoContext, pmData, id);
391                                                            if (element != null)
392                                                                    tmpList.add(element);
393                                                    }
394                                                    array = Array.newInstance(elementType, tmpList.size());
395                                                    array = tmpList.toArray((Object[]) array);
396                                            }
397                                    }
398                            }
399                    }
400    
401                    return array;
402            }
403    
404            /**
405             * Fetch a {@link Collection} (<code>List</code>, <code>Set</code>, etc.) of
406             * related objects (1-n-relationship).
407             * The related objects are persistence-capable.
408             */
409            protected Object fetchObjectFieldWithRelationTypeCollection(int fieldNumber, AbstractMemberMetaData mmd, FieldMeta fieldMeta, Set<Long> mappedByDataEntryIDs) {
410                    Collection<Object> collection;
411                    @SuppressWarnings("unchecked")
412                    Class<? extends Collection<Object>> instanceType = SCOUtils.getContainerInstanceType(mmd.getType(), mmd.getOrderMetaData() != null);
413                    try {
414                            collection = instanceType.newInstance();
415                    } catch (InstantiationException e) {
416                            throw new NucleusDataStoreException(e.getMessage(), e);
417                    } catch (IllegalAccessException e) {
418                            throw new NucleusDataStoreException(e.getMessage(), e);
419                    }
420    
421                    if (mmd.getCollection().isEmbeddedElement()) {
422                            Object value = objectContainer.getValue(fieldMeta.getFieldID());
423                            if (!(value instanceof EmbeddedObjectContainer[]))
424                                    throw new IllegalStateException("field claims to be embedded, but persistent field value is not an array of EmbeddedObjectContainer! fieldID=" + fieldMeta.getFieldID() + " fieldName=" + fieldMeta.getFieldName() + " value=" + value);
425    
426                            EmbeddedObjectContainer[] embeddedObjectContainers = (EmbeddedObjectContainer[]) value;
427                            ClassMeta embeddedClassMeta = fieldMeta.getSubFieldMeta(FieldMetaRole.collectionElement).getEmbeddedClassMeta();
428                            for (EmbeddedObjectContainer embeddedObjectContainer : embeddedObjectContainers) {
429                                    Object pc = createPCFromEmbeddedObjectContainer(fieldNumber, fieldMeta, embeddedClassMeta, embeddedObjectContainer);
430                                    collection.add(pc);
431                            }
432                    }
433                    else {
434                            if (mmd.getMappedBy() != null) {
435                                    for (Long mappedByDataEntryID : mappedByDataEntryIDs) {
436                                            Object element = getObjectFromDataEntryID(mappedByDataEntryID);
437                                            collection.add(element);
438                                    }
439                            }
440                            else {
441                                    Object ids = objectContainer.getValue(fieldMeta.getFieldID());
442                                    if (ids != null) {
443                                            for (int idx = 0; idx < Array.getLength(ids); ++idx) {
444                                                    Object id = Array.get(ids, idx);
445                                                    Object element = ObjectContainerHelper.referenceToEntity(cryptoContext, pmData, id);
446                                                    if (element != null) // orphaned reference - https://sourceforge.net/tracker/?func=detail&aid=3515529&group_id=517465&atid=2102914
447                                                            collection.add(element);
448                                            }
449                                    }
450                            }
451                    }
452    
453                    return op.wrapSCOField(fieldNumber, collection, false, false, true);
454            }
455    
456            /**
457             * Fetch a {@link Map} of related objects (1-n-relationship).
458             * The related objects are persistence-capable.
459             */
460            protected Object fetchObjectFieldWithRelationTypeMap(int fieldNumber, AbstractMemberMetaData mmd, FieldMeta fieldMeta, Set<Long> mappedByDataEntryIDs)
461            {
462                    Map<Object, Object> map;
463                    @SuppressWarnings("unchecked")
464                    Class<? extends Map<Object, Object>> instanceType = SCOUtils.getContainerInstanceType(mmd.getType(), mmd.getOrderMetaData() != null);
465                    try {
466                            map = instanceType.newInstance();
467                    } catch (InstantiationException e) {
468                            throw new NucleusDataStoreException(e.getMessage(), e);
469                    } catch (IllegalAccessException e) {
470                            throw new NucleusDataStoreException(e.getMessage(), e);
471                    }
472    
473                    boolean keyIsPersistent = mmd.getMap().keyIsPersistent();
474                    boolean valueIsPersistent = mmd.getMap().valueIsPersistent();
475    
476                    if (mmd.getMappedBy() != null) {
477                            FieldMeta oppositeFieldMetaKey = fieldMeta.getSubFieldMeta(FieldMetaRole.mapKey).getMappedByFieldMeta(ec);
478                            FieldMeta oppositeFieldMetaValue = fieldMeta.getSubFieldMeta(FieldMetaRole.mapValue).getMappedByFieldMeta(ec);
479    
480                            for (Long mappedByDataEntryID : mappedByDataEntryIDs) {
481                                    Object element = getObjectFromDataEntryID(mappedByDataEntryID);
482                                    ObjectProvider elementOP = ec.findObjectProvider(element);
483                                    if (elementOP == null)
484                                            throw new IllegalStateException("executionContext.findObjectProvider(element) returned null for " + element);
485    
486                                    Object key;
487                                    if (keyIsPersistent)
488                                            key = element;
489                                    else
490                                            key = elementOP.provideField(oppositeFieldMetaKey.getDataNucleusAbsoluteFieldNumber(ec));
491    
492                                    Object value;
493                                    if (valueIsPersistent)
494                                            value = element;
495                                    else
496                                            value = elementOP.provideField(oppositeFieldMetaValue.getDataNucleusAbsoluteFieldNumber(ec));
497    
498                                    map.put(key, value);
499                            }
500                    }
501                    else {
502                            Map<?,?> idMap = (Map<?,?>) objectContainer.getValue(fieldMeta.getFieldID());
503                            if (idMap != null) {
504                                    for (Map.Entry<?, ?> me : idMap.entrySet()) {
505                                            Object k = me.getKey();
506                                            Object v = me.getValue();
507    
508                                            if (keyIsPersistent) {
509                                                    k = ObjectContainerHelper.referenceToEntity(cryptoContext, pmData, k);
510                                                    if (k == null) // orphaned reference - https://sourceforge.net/tracker/?func=detail&aid=3515529&group_id=517465&atid=2102914
511                                                            continue;
512                                            }
513    
514                                            if (valueIsPersistent) {
515                                                    v = ObjectContainerHelper.referenceToEntity(cryptoContext, pmData, v);
516                                                    if (v == null) // orphaned reference - https://sourceforge.net/tracker/?func=detail&aid=3515529&group_id=517465&atid=2102914
517                                                            continue;
518                                            }
519    
520                                            map.put(k, v);
521                                    }
522                            }
523                    }
524    
525                    return op.wrapSCOField(fieldNumber, map, false, false, true);
526            }
527    
528            protected Object getObjectFromDataEntryID(long dataEntryID)
529            {
530                    String idStr = new DataEntryDAO(pmData, cryptoContext.getKeyStoreRefID()).getDataEntry(dataEntryID).getObjectID();
531                    return IdentityUtils.getObjectFromIdString(
532                                    idStr, classMeta.getDataNucleusClassMetaData(ec), ec, true
533                    );
534            }
535    }