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 }