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.Iterator;
025    import java.util.Map;
026    import java.util.Set;
027    
028    import javax.jdo.PersistenceManager;
029    
030    import org.cumulus4j.store.Cumulus4jStoreManager;
031    import org.cumulus4j.store.EncryptionHandler;
032    import org.cumulus4j.store.ObjectContainerHelper;
033    import org.cumulus4j.store.crypto.CryptoContext;
034    import org.cumulus4j.store.model.ClassMeta;
035    import org.cumulus4j.store.model.DataEntry;
036    import org.cumulus4j.store.model.FieldMeta;
037    import org.cumulus4j.store.model.FieldMetaRole;
038    import org.cumulus4j.store.model.IndexEntry;
039    import org.cumulus4j.store.model.IndexEntryObjectRelationHelper;
040    import org.cumulus4j.store.model.IndexValue;
041    import org.cumulus4j.store.model.ObjectContainer;
042    import org.datanucleus.exceptions.NucleusDataStoreException;
043    import org.datanucleus.identity.IdentityUtils;
044    import org.datanucleus.metadata.AbstractClassMetaData;
045    import org.datanucleus.metadata.AbstractMemberMetaData;
046    import org.datanucleus.metadata.Relation;
047    import org.datanucleus.store.ExecutionContext;
048    import org.datanucleus.store.ObjectProvider;
049    import org.datanucleus.store.fieldmanager.AbstractFieldManager;
050    import org.datanucleus.store.types.sco.SCOUtils;
051    
052    /**
053     * Manager for the process of fetching a user object from the datastore, handling the translation from the
054     * DataEntry object into the users own object.
055     * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
056     */
057    public class FetchFieldManager extends AbstractFieldManager
058    {
059            private ObjectProvider op;
060            private CryptoContext cryptoContext;
061            private PersistenceManager pmData;
062            private PersistenceManager pmIndex;
063            private ExecutionContext ec;
064            private ClassMeta classMeta;
065            private AbstractClassMetaData dnClassMetaData;
066            private ObjectContainer objectContainer;
067    
068            public FetchFieldManager(
069                            ObjectProvider op,
070                            CryptoContext cryptoContext,
071                            ClassMeta classMeta,
072                            AbstractClassMetaData dnClassMetaData,
073                            ObjectContainer objectContainer
074            )
075            {
076                    this.op = op;
077                    this.cryptoContext = cryptoContext;
078                    this.pmData = cryptoContext.getPersistenceManagerForData();
079                    this.pmIndex = cryptoContext.getPersistenceManagerForIndex();
080                    this.ec = op.getExecutionContext();
081                    this.classMeta = classMeta;
082                    this.dnClassMetaData = dnClassMetaData;
083                    this.objectContainer = objectContainer;
084            }
085    
086            protected EncryptionHandler getEncryptionHandler()
087            {
088                    return ((Cumulus4jStoreManager) ec.getStoreManager()).getEncryptionHandler();
089            }
090    
091            private long getFieldID(int fieldNumber)
092            {
093                    AbstractMemberMetaData mmd = dnClassMetaData.getMetaDataForManagedMemberAtAbsolutePosition(fieldNumber);
094    
095                    FieldMeta fieldMeta = classMeta.getFieldMeta(mmd.getClassName(), mmd.getName());
096                    if (fieldMeta == null)
097                            throw new IllegalStateException("Unknown field! class=" + dnClassMetaData.getFullClassName() + " fieldNumber=" + fieldNumber + " fieldName=" + mmd.getName());
098    
099                    return fieldMeta.getFieldID();
100            }
101    
102            @Override
103            public boolean fetchBooleanField(int fieldNumber) {
104                    Object value = objectContainer.getValue(getFieldID(fieldNumber));
105                    return value == null ? false : (Boolean)value;
106            }
107    
108            @Override
109            public byte fetchByteField(int fieldNumber) {
110                    Object value = objectContainer.getValue(getFieldID(fieldNumber));
111                    return value == null ? 0 : (Byte)value;
112            }
113    
114            @Override
115            public char fetchCharField(int fieldNumber) {
116                    Object value = objectContainer.getValue(getFieldID(fieldNumber));
117                    return value == null ? 0 : (Character)value;
118            }
119    
120            @Override
121            public double fetchDoubleField(int fieldNumber) {
122                    Object value = objectContainer.getValue(getFieldID(fieldNumber));
123                    return value == null ? 0 : (Double)value;
124            }
125    
126            @Override
127            public float fetchFloatField(int fieldNumber) {
128                    Object value = objectContainer.getValue(getFieldID(fieldNumber));
129                    return value == null ? 0 : (Float)value;
130            }
131    
132            @Override
133            public int fetchIntField(int fieldNumber) {
134                    Object value = objectContainer.getValue(getFieldID(fieldNumber));
135                    return value == null ? 0 : (Integer)value;
136            }
137    
138            @Override
139            public long fetchLongField(int fieldNumber) {
140                    Object value = objectContainer.getValue(getFieldID(fieldNumber));
141                    return value == null ? 0 : (Long)value;
142            }
143    
144            @Override
145            public short fetchShortField(int fieldNumber) {
146                    Object value = objectContainer.getValue(getFieldID(fieldNumber));
147                    return value == null ? 0 : (Short)value;
148            }
149    
150            @Override
151            public String fetchStringField(int fieldNumber) {
152                    Object value = objectContainer.getValue(getFieldID(fieldNumber));
153                    return (String)value;
154            }
155    
156            private long thisDataEntryID = -1;
157    
158            protected long getThisDataEntryID()
159            {
160                    if (thisDataEntryID < 0)
161                            thisDataEntryID = DataEntry.getDataEntryID(pmData, classMeta, op.getObjectId().toString());
162    
163                    return thisDataEntryID;
164            }
165    
166            @Override
167            public Object fetchObjectField(int fieldNumber)
168            {
169                    AbstractMemberMetaData mmd = dnClassMetaData.getMetaDataForManagedMemberAtAbsolutePosition(fieldNumber);
170                    FieldMeta fieldMeta = classMeta.getFieldMeta(mmd.getClassName(), mmd.getName());
171                    if (fieldMeta == null)
172                            throw new IllegalStateException("Unknown field! class=" + dnClassMetaData.getFullClassName() + " fieldNumber=" + fieldNumber + " fieldName=" + mmd.getName());
173    
174                    Set<Long> mappedByDataEntryIDs = null;
175                    if (mmd.getMappedBy() != null) {
176                            IndexEntry indexEntry = IndexEntryObjectRelationHelper.getIndexEntry(pmIndex, fieldMeta.getMappedByFieldMeta(ec), getThisDataEntryID());
177                            if (indexEntry == null)
178                                    mappedByDataEntryIDs = Collections.emptySet();
179                            else {
180                                    IndexValue indexValue = getEncryptionHandler().decryptIndexEntry(cryptoContext, indexEntry);
181                                    mappedByDataEntryIDs = indexValue.getDataEntryIDs();
182                            }
183                    }
184    
185                    int relationType = mmd.getRelationType(ec.getClassLoaderResolver());
186    
187                    if (relationType == Relation.NONE)
188                    {
189                            if (mmd.hasCollection())
190                            {
191                                    Collection<Object> collection;
192                                    @SuppressWarnings("unchecked")
193                                    Class<? extends Collection<Object>> instanceType = SCOUtils.getContainerInstanceType(mmd.getType(), mmd.getOrderMetaData() != null);
194                                    try {
195                                            collection = instanceType.newInstance();
196                                    } catch (InstantiationException e) {
197                                            throw new NucleusDataStoreException(e.getMessage(), e);
198                                    } catch (IllegalAccessException e) {
199                                            throw new NucleusDataStoreException(e.getMessage(), e);
200                                    }
201    
202                                    Object array = objectContainer.getValue(fieldMeta.getFieldID());
203                                    if (array != null) {
204                                            for (int idx = 0; idx < Array.getLength(array); ++idx) {
205                                                    Object element = Array.get(array, idx);
206                                                    collection.add(element);
207                                            }
208                                    }
209                                    return op.wrapSCOField(fieldNumber, collection, false, false, true);
210                            }
211    
212                            if (mmd.hasMap())
213                            {
214                                    Map<?,?> map = (Map<?,?>) objectContainer.getValue(fieldMeta.getFieldID());
215                                    return op.wrapSCOField(fieldNumber, map, false, false, true);
216                            }
217    
218                            // Arrays are stored 'as is', thus no conversion necessary.
219                            return objectContainer.getValue(getFieldID(fieldNumber));
220                    }
221                    else if (Relation.isRelationSingleValued(relationType))
222                    {
223                            if (mmd.getMappedBy() != null) {
224                                    if (mappedByDataEntryIDs.isEmpty())
225                                            return null;
226    
227                                    if (mappedByDataEntryIDs.size() != 1)
228                                            throw new IllegalStateException("There are multiple objects referencing a 1-1-mapped-by-relationship! Expected 0 or 1! fieldMeta=" + fieldMeta + " dataEntryIDsForMappedBy=" + mappedByDataEntryIDs);
229    
230                                    long dataEntryID = mappedByDataEntryIDs.iterator().next();
231                                    return getObjectFromDataEntryID(dataEntryID);
232                            }
233    
234                            Object valueID = objectContainer.getValue(fieldMeta.getFieldID());
235                            return ObjectContainerHelper.referenceToEntity(ec, pmData, valueID);
236                    }
237                    else if (Relation.isRelationMultiValued(relationType))
238                    {
239                            // Collection/Map/Array
240                            if (mmd.hasCollection())
241                            {
242                                    Collection<Object> collection;
243                                    @SuppressWarnings("unchecked")
244                                    Class<? extends Collection<Object>> instanceType = SCOUtils.getContainerInstanceType(mmd.getType(), mmd.getOrderMetaData() != null);
245                                    try {
246                                            collection = instanceType.newInstance();
247                                    } catch (InstantiationException e) {
248                                            throw new NucleusDataStoreException(e.getMessage(), e);
249                                    } catch (IllegalAccessException e) {
250                                            throw new NucleusDataStoreException(e.getMessage(), e);
251                                    }
252    
253                                    if (mmd.getMappedBy() != null) {
254                                            for (Long mappedByDataEntryID : mappedByDataEntryIDs) {
255                                                    Object element = getObjectFromDataEntryID(mappedByDataEntryID);
256                                                    collection.add(element);
257                                            }
258                                    }
259                                    else {
260                                            Object ids = objectContainer.getValue(fieldMeta.getFieldID());
261                                            if (ids != null) {
262                                                    for (int idx = 0; idx < Array.getLength(ids); ++idx) {
263                                                            Object id = Array.get(ids, idx);
264                                                            Object element = ObjectContainerHelper.referenceToEntity(ec, pmData, id);
265                                                            if (element != null) // orphaned reference - https://sourceforge.net/tracker/?func=detail&aid=3515529&group_id=517465&atid=2102914
266                                                                    collection.add(element);
267                                                    }
268                                            }
269                                    }
270                                    return op.wrapSCOField(fieldNumber, collection, false, false, true);
271                            }
272                            else if (mmd.hasMap())
273                            {
274                                    Map<Object, Object> map;
275                                    @SuppressWarnings("unchecked")
276                                    Class<? extends Map<Object, Object>> instanceType = SCOUtils.getContainerInstanceType(mmd.getType(), mmd.getOrderMetaData() != null);
277                                    try {
278                                            map = instanceType.newInstance();
279                                    } catch (InstantiationException e) {
280                                            throw new NucleusDataStoreException(e.getMessage(), e);
281                                    } catch (IllegalAccessException e) {
282                                            throw new NucleusDataStoreException(e.getMessage(), e);
283                                    }
284    
285                                    boolean keyIsPersistent = mmd.getMap().keyIsPersistent();
286                                    boolean valueIsPersistent = mmd.getMap().valueIsPersistent();
287    
288                                    if (mmd.getMappedBy() != null) {
289                                            FieldMeta oppositeFieldMetaKey = fieldMeta.getSubFieldMeta(FieldMetaRole.mapKey).getMappedByFieldMeta(ec);
290                                            FieldMeta oppositeFieldMetaValue = fieldMeta.getSubFieldMeta(FieldMetaRole.mapValue).getMappedByFieldMeta(ec);
291    
292                                            for (Long mappedByDataEntryID : mappedByDataEntryIDs) {
293                                                    Object element = getObjectFromDataEntryID(mappedByDataEntryID);
294                                                    ObjectProvider elementOP = ec.findObjectProvider(element);
295                                                    if (elementOP == null)
296                                                            throw new IllegalStateException("executionContext.findObjectProvider(element) returned null for " + element);
297    
298                                                    Object key;
299                                                    if (keyIsPersistent)
300                                                            key = element;
301                                                    else
302                                                            key = elementOP.provideField(oppositeFieldMetaKey.getDataNucleusAbsoluteFieldNumber());
303    
304                                                    Object value;
305                                                    if (valueIsPersistent)
306                                                            value = element;
307                                                    else
308                                                            value = elementOP.provideField(oppositeFieldMetaValue.getDataNucleusAbsoluteFieldNumber());
309    
310                                                    map.put(key, value);
311                                            }
312                                    }
313                                    else {
314                                            Map<?,?> idMap = (Map<?,?>) objectContainer.getValue(fieldMeta.getFieldID());
315                                            if (idMap != null) {
316                                                    for (Map.Entry<?, ?> me : idMap.entrySet()) {
317                                                            Object k = me.getKey();
318                                                            Object v = me.getValue();
319    
320                                                            if (keyIsPersistent) {
321                                                                    k = ObjectContainerHelper.referenceToEntity(ec, pmData, k);
322                                                                    if (k == null) // orphaned reference - https://sourceforge.net/tracker/?func=detail&aid=3515529&group_id=517465&atid=2102914
323                                                                            continue;
324                                                            }
325    
326                                                            if (valueIsPersistent) {
327                                                                    v = ObjectContainerHelper.referenceToEntity(ec, pmData, v);
328                                                                    if (v == null) // orphaned reference - https://sourceforge.net/tracker/?func=detail&aid=3515529&group_id=517465&atid=2102914
329                                                                            continue;
330                                                            }
331    
332                                                            map.put(k, v);
333                                                    }
334                                            }
335                                    }
336    
337                                    return op.wrapSCOField(fieldNumber, map, false, false, true);
338                            }
339                            else if (mmd.hasArray())
340                            {
341                                    Class<?> elementType = ec.getClassLoaderResolver().classForName(mmd.getArray().getElementType());
342    
343                                    Object array;
344                                    if (mmd.getMappedBy() != null) {
345                                            int arrayLength = mappedByDataEntryIDs.size();
346                                            array = Array.newInstance(elementType, arrayLength);
347                                            Iterator<Long> it = mappedByDataEntryIDs.iterator();
348                                            for (int i = 0; i < arrayLength; ++i) {
349                                                    Long dataEntryID = it.next();
350                                                    Object element = getObjectFromDataEntryID(dataEntryID);
351                                                    Array.set(array, i, element);
352                                            }
353                                    }
354                                    else {
355                                            Object ids = objectContainer.getValue(fieldMeta.getFieldID());
356                                            if (ids == null)
357                                                    array = null;
358                                            else {
359                                                    if (ec.getStoreManager().getPersistenceHandler().useReferentialIntegrity()) {
360                                                            // Directly fill the array.
361                                                            int arrayLength = Array.getLength(ids);
362                                                            array = Array.newInstance(elementType, arrayLength);
363                                                            for (int i = 0; i < arrayLength; ++i) {
364                                                                    Object id = Array.get(ids, i);
365                                                                    Object element = ObjectContainerHelper.referenceToEntity(ec, pmData, id);
366                                                                    Array.set(array, i, element);
367                                                            }
368                                                    }
369                                                    else {
370                                                            // https://sourceforge.net/tracker/?func=detail&aid=3515529&group_id=517465&atid=2102914
371                                                            // First fill a list and then transfer everything into an array, because there might
372                                                            // be elements missing (orphaned references).
373                                                            int arrayLength = Array.getLength(ids);
374                                                            ArrayList<Object> tmpList = new ArrayList<Object>();
375                                                            for (int i = 0; i < arrayLength; ++i) {
376                                                                    Object id = Array.get(ids, i);
377                                                                    Object element = ObjectContainerHelper.referenceToEntity(ec, pmData, id);
378                                                                    if (element != null)
379                                                                            tmpList.add(element);
380                                                            }
381                                                            array = Array.newInstance(elementType, tmpList.size());
382                                                            array = tmpList.toArray((Object[]) array);
383                                                    }
384                                            }
385                                    }
386                                    return array;
387                            }
388                            else
389                                    throw new IllegalStateException("Unexpected multi-valued relationType: " + relationType);
390                    }
391                    else
392                            throw new IllegalStateException("Unexpected relationType: " + relationType);
393            }
394    
395            private Object getObjectFromDataEntryID(long dataEntryID)
396            {
397                    String idStr = DataEntry.getDataEntry(pmData, dataEntryID).getObjectID();
398                    return IdentityUtils.getObjectFromIdString(
399                                    idStr, classMeta.getDataNucleusClassMetaData(ec), ec, true
400                    );
401            }
402    }