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 }