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 }