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