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.HashMap; 023 import java.util.Map; 024 025 import javax.jdo.PersistenceManager; 026 027 import org.cumulus4j.store.ObjectContainerHelper; 028 import org.cumulus4j.store.crypto.CryptoContext; 029 import org.cumulus4j.store.model.ClassMeta; 030 import org.cumulus4j.store.model.EmbeddedClassMeta; 031 import org.cumulus4j.store.model.EmbeddedObjectContainer; 032 import org.cumulus4j.store.model.FieldMeta; 033 import org.cumulus4j.store.model.FieldMetaRole; 034 import org.cumulus4j.store.model.ObjectContainer; 035 import org.datanucleus.metadata.AbstractClassMetaData; 036 import org.datanucleus.metadata.AbstractMemberMetaData; 037 import org.datanucleus.metadata.Relation; 038 import org.datanucleus.store.ExecutionContext; 039 import org.datanucleus.store.ObjectProvider; 040 import org.datanucleus.store.fieldmanager.AbstractFieldManager; 041 import org.datanucleus.store.types.sco.SCO; 042 import org.slf4j.Logger; 043 import org.slf4j.LoggerFactory; 044 045 /** 046 * Manager for the process of persisting a user object into the datastore, handling the translation from the 047 * users own object into the DataEntry object. 048 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de 049 */ 050 public class StoreFieldManager extends AbstractFieldManager 051 { 052 private static final Logger logger = LoggerFactory.getLogger(StoreFieldManager.class); 053 054 private ObjectProvider op; 055 private CryptoContext cryptoContext; 056 private PersistenceManager pmData; 057 private ExecutionContext ec; 058 private ClassMeta classMeta; 059 private AbstractClassMetaData dnClassMetaData; 060 private ObjectContainer objectContainer; 061 062 public StoreFieldManager( 063 ObjectProvider op, 064 CryptoContext cryptoContext, 065 PersistenceManager pmData, 066 ClassMeta classMeta, 067 AbstractClassMetaData dnClassMetaData, 068 int keyStoreRefID, 069 ObjectContainer objectContainer // populated by this class 070 ) 071 { 072 if (op == null) 073 throw new IllegalArgumentException("op == null"); 074 if (cryptoContext == null) 075 throw new IllegalArgumentException("cryptoContext == null"); 076 if (pmData == null) 077 throw new IllegalArgumentException("pmData == null"); 078 if (classMeta == null) 079 throw new IllegalArgumentException("classMeta == null"); 080 if (dnClassMetaData == null) 081 throw new IllegalArgumentException("dnClassMetaData == null"); 082 if (objectContainer == null) 083 throw new IllegalArgumentException("objectContainer == null"); 084 085 this.op = op; 086 this.cryptoContext = cryptoContext; 087 this.pmData = pmData; 088 this.ec = op.getExecutionContext(); 089 this.classMeta = classMeta; 090 this.dnClassMetaData = dnClassMetaData; 091 this.objectContainer = objectContainer; 092 } 093 094 private long getFieldID(int fieldNumber) 095 { 096 AbstractMemberMetaData mmd = dnClassMetaData.getMetaDataForManagedMemberAtAbsolutePosition(fieldNumber); 097 098 FieldMeta fieldMeta = classMeta.getFieldMeta(mmd.getClassName(), mmd.getName()); 099 if (fieldMeta == null) 100 throw new IllegalStateException("Unknown field! class=" + dnClassMetaData.getFullClassName() + " fieldNumber=" + fieldNumber + " fieldName=" + mmd.getName()); 101 102 return fieldMeta.getFieldID(); 103 } 104 105 @Override 106 public void storeBooleanField(int fieldNumber, boolean value) { 107 objectContainer.setValue(getFieldID(fieldNumber), value); 108 } 109 110 @Override 111 public void storeByteField(int fieldNumber, byte value) { 112 objectContainer.setValue(getFieldID(fieldNumber), value); 113 } 114 115 @Override 116 public void storeCharField(int fieldNumber, char value) { 117 objectContainer.setValue(getFieldID(fieldNumber), value); 118 } 119 120 @Override 121 public void storeDoubleField(int fieldNumber, double value) { 122 objectContainer.setValue(getFieldID(fieldNumber), value); 123 } 124 125 @Override 126 public void storeFloatField(int fieldNumber, float value) { 127 objectContainer.setValue(getFieldID(fieldNumber), value); 128 } 129 130 @Override 131 public void storeIntField(int fieldNumber, int value) { 132 objectContainer.setValue(getFieldID(fieldNumber), value); 133 } 134 135 @Override 136 public void storeLongField(int fieldNumber, long value) { 137 objectContainer.setValue(getFieldID(fieldNumber), value); 138 } 139 140 @Override 141 public void storeShortField(int fieldNumber, short value) { 142 objectContainer.setValue(getFieldID(fieldNumber), value); 143 } 144 145 @Override 146 public void storeStringField(int fieldNumber, String value) { 147 objectContainer.setValue(getFieldID(fieldNumber), value); 148 } 149 150 @Override 151 public void storeObjectField(int fieldNumber, Object value) 152 { 153 if (logger.isTraceEnabled()) { 154 logger.trace( 155 "storeObjectField: classMeta.className={} fieldNumber={} value={}", 156 new Object[] { classMeta.getClassName(), fieldNumber, value } 157 ); 158 } 159 160 AbstractMemberMetaData mmd = dnClassMetaData.getMetaDataForManagedMemberAtAbsolutePosition(fieldNumber); 161 162 FieldMeta fieldMeta = classMeta.getFieldMeta(mmd.getClassName(), mmd.getName()); 163 if (fieldMeta == null) 164 throw new IllegalStateException("Unknown field! class=" + dnClassMetaData.getFullClassName() + " fieldNumber=" + fieldNumber + " fieldName=" + mmd.getName()); 165 166 if (value == null) { 167 objectContainer.setValue(fieldMeta.getFieldID(), null); 168 return; 169 } 170 171 int relationType = mmd.getRelationType(ec.getClassLoaderResolver()); 172 173 // Replace any SCO field that isn't already a wrapper, with its wrapper object 174 // This is necessary to trigger persistence of related objects. We have to unwrap 175 // them later again to make sure we don't store SCOs in our object-container. 176 boolean[] secondClassMutableFieldFlags = dnClassMetaData.getSCOMutableMemberFlags(); 177 if (secondClassMutableFieldFlags[fieldNumber] && !(value instanceof SCO)) 178 value = op.wrapSCOField(fieldNumber, value, true, true, true); // 2012-12-27: switched forInsert to true, IMHO false was wrong before, but I'm not sure. Marco :-) 179 180 // We have to make sure we unwrap any SCO. 181 value = op.unwrapSCOField(fieldNumber, value, false); 182 183 if (relationType == Relation.NONE) 184 storeObjectFieldWithRelationTypeNone(fieldNumber, value, mmd, fieldMeta); 185 else if (Relation.isRelationSingleValued(relationType)) 186 storeObjectFieldWithRelationTypeSingleValue(fieldNumber, value, mmd, fieldMeta); 187 else if (Relation.isRelationMultiValued(relationType)) 188 { 189 // Collection/Map/Array 190 if (mmd.hasCollection()) 191 storeObjectFieldWithRelationTypeCollection(fieldNumber, value, mmd, fieldMeta); 192 else if (mmd.hasMap()) 193 storeObjectFieldWithRelationTypeMap(fieldNumber, value, mmd, fieldMeta); 194 else if (mmd.hasArray()) 195 storeObjectFieldWithRelationTypeArray(fieldNumber, value, mmd, fieldMeta); 196 else 197 throw new IllegalStateException("Unexpected 1-n-sub-type for relationType: " + relationType); 198 } 199 else 200 throw new IllegalStateException("Unexpected relationType: " + relationType); 201 } 202 203 /** 204 * Store related objects that are not persistence-capable. 205 * The related objects might be single-valued, arrays, collections or maps. 206 */ 207 protected void storeObjectFieldWithRelationTypeNone(int fieldNumber, Object value, AbstractMemberMetaData mmd, FieldMeta fieldMeta) { 208 if (mmd.hasCollection()) { 209 // Replace the special DN collection by a simple array. 210 Collection<?> collection = (Collection<?>)value; 211 Object[] values = collection.toArray(new Object[collection.size()]); 212 objectContainer.setValue(fieldMeta.getFieldID(), values); 213 } 214 // we unwrap now before, hence this is not needed anymore. 215 // else if (mmd.hasMap()) { 216 // // replace the special DN Map by a simple HashMap. 217 // Map<?,?> valueMap = (Map<?, ?>) value; 218 // 219 // Map<Object, Object> map; 220 // @SuppressWarnings("unchecked") 221 // Class<? extends Map<Object, Object>> instanceType = SCOUtils.getContainerInstanceType(mmd.getType(), mmd.getOrderMetaData() != null); 222 // try { 223 // map = instanceType.newInstance(); 224 // } catch (InstantiationException e) { 225 // throw new NucleusDataStoreException(e.getMessage(), e); 226 // } catch (IllegalAccessException e) { 227 // throw new NucleusDataStoreException(e.getMessage(), e); 228 // } 229 // 230 // map.putAll(valueMap); 231 // 232 // objectContainer.setValue(fieldMeta.getFieldID(), map); 233 // } 234 else // arrays are not managed (no special DN instances) and thus stored 'as is'... 235 objectContainer.setValue(fieldMeta.getFieldID(), value); 236 } 237 238 protected EmbeddedObjectContainer createEmbeddedObjectContainerFromPC(FieldMeta fieldMeta, EmbeddedClassMeta embeddedClassMeta, Object pc) { 239 if (pc == null) 240 return null; 241 242 ObjectProvider embeddedOP = ec.findObjectProvider(pc); 243 // EmbeddedObjectContainer embeddedObjectContainer = (EmbeddedObjectContainer) embeddedOP.getAssociatedValue(EmbeddedObjectContainer.ASSOCIATED_VALUE); 244 // if (embeddedObjectContainer == null) { // We must do this ALWAYS, because otherwise changes are ignored :-( 245 // embedded ONLY => not yet persisted 246 // Maybe we could omit the ec.persistObjectInternal(...) completely for embedded objects to prevent double handling, 247 // but I guess, we'd have to add other code, then, doing things that are also done by ec.persistObjectInternal(...) 248 // besides the actual persisting (which is skipped for embedded-ONLY PCs [but done for embedded PCs that are not 249 // @EmbeddedOnly]) - e.g. create & assign an ObjectProvider if none is assigned, yet. Marco :-) 250 251 // ClassMeta embeddedClassMeta = ((Cumulus4jStoreManager)ec.getStoreManager()).getClassMeta(ec, valuePC.getClass()); 252 AbstractClassMetaData embeddedDNClassMetaData = embeddedOP.getClassMetaData(); 253 EmbeddedObjectContainer embeddedObjectContainer = new EmbeddedObjectContainer(embeddedClassMeta.getClassID()); 254 embeddedOP.provideFields( 255 embeddedDNClassMetaData.getAllMemberPositions(), 256 new StoreFieldManager(embeddedOP, cryptoContext, pmData, embeddedClassMeta, embeddedDNClassMetaData, cryptoContext.getKeyStoreRefID(), embeddedObjectContainer)); 257 embeddedObjectContainer.setVersion(embeddedOP.getTransactionalVersion()); 258 // } 259 return embeddedObjectContainer; 260 } 261 262 /** 263 * Store a single related object (1-1-relationship). 264 * The related object is persistence-capable. 265 */ 266 protected void storeObjectFieldWithRelationTypeSingleValue(int fieldNumber, Object value, AbstractMemberMetaData mmd, FieldMeta fieldMeta) { 267 // Persistable object - persist the related object and store the identity in the cell 268 boolean embedded = mmd.isEmbedded(); 269 int objectType = embedded ? ObjectProvider.EMBEDDED_PC : ObjectProvider.PC; 270 271 Object valuePC = ec.persistObjectInternal(value, op, fieldNumber, objectType); 272 ec.flushInternal(true); 273 274 if (embedded) { 275 if (valuePC != null) { 276 EmbeddedClassMeta embeddedClassMeta = fieldMeta.getEmbeddedClassMeta(); 277 EmbeddedObjectContainer embeddedObjectContainer = createEmbeddedObjectContainerFromPC(fieldMeta, embeddedClassMeta, valuePC); 278 objectContainer.setValue(fieldMeta.getFieldID(), embeddedObjectContainer); 279 } 280 } 281 else { 282 if (mmd.getMappedBy() == null) { 283 Object valueID = ObjectContainerHelper.entityToReference(cryptoContext, pmData, valuePC); 284 objectContainer.setValue(fieldMeta.getFieldID(), valueID); 285 } 286 } 287 } 288 289 /** 290 * Store an array of related objects (1-n-relationship). 291 * The related objects are persistence-capable. 292 */ 293 protected void storeObjectFieldWithRelationTypeArray(int fieldNumber, Object value, AbstractMemberMetaData mmd, FieldMeta fieldMeta) { 294 // TODO for mapped-by-relations, the order is not yet maintained - AFAIK. 295 296 boolean embedded = mmd.getArray().isEmbeddedElement(); 297 int objectType = embedded ? ObjectProvider.EMBEDDED_COLLECTION_ELEMENT_PC : ObjectProvider.PC; 298 EmbeddedClassMeta embeddedClassMeta = fieldMeta.getSubFieldMeta(FieldMetaRole.arrayElement).getEmbeddedClassMeta(); 299 300 Object[] ids = (mmd.getMappedBy() != null || embedded) ? null : new Object[Array.getLength(value)]; 301 EmbeddedObjectContainer[] embeddedObjectContainers = (ids != null || !embedded) ? null : new EmbeddedObjectContainer[Array.getLength(value)]; 302 for (int i = 0; i < Array.getLength(value); i++) 303 { 304 Object element = Array.get(value, i); 305 Object elementPC = ec.persistObjectInternal(element, op, fieldNumber, objectType); 306 ec.flushInternal(true); 307 308 if (ids != null) { 309 Object elementID = ObjectContainerHelper.entityToReference(cryptoContext, pmData, elementPC); 310 ids[i] = elementID; 311 } 312 else if (embeddedObjectContainers != null) { 313 EmbeddedObjectContainer embeddedObjectContainer = createEmbeddedObjectContainerFromPC(fieldMeta, embeddedClassMeta, elementPC); 314 embeddedObjectContainers[i] = embeddedObjectContainer; 315 } 316 } 317 318 if (ids != null) 319 objectContainer.setValue(fieldMeta.getFieldID(), ids); 320 else if (embeddedObjectContainers != null) 321 objectContainer.setValue(fieldMeta.getFieldID(), embeddedObjectContainers); 322 } 323 324 /** 325 * Store a {@link Collection} (<code>List</code>, <code>Set</code>, etc.) of 326 * related objects (1-n-relationship). 327 * The related objects are persistence-capable. 328 */ 329 protected void storeObjectFieldWithRelationTypeCollection(int fieldNumber, Object value, AbstractMemberMetaData mmd, FieldMeta fieldMeta) { 330 // TODO for mapped-by-relations, the order is not yet maintained (=> Lists) - AFAIK. 331 332 boolean embedded = mmd.getCollection().isEmbeddedElement(); 333 int objectType = embedded ? ObjectProvider.EMBEDDED_COLLECTION_ELEMENT_PC : ObjectProvider.PC; 334 EmbeddedClassMeta embeddedClassMeta = fieldMeta.getSubFieldMeta(FieldMetaRole.collectionElement).getEmbeddedClassMeta(); 335 336 Collection<?> collection = (Collection<?>)value; 337 Object[] ids = (mmd.getMappedBy() != null || embedded) ? null : new Object[collection.size()]; 338 EmbeddedObjectContainer[] embeddedObjectContainers = (ids != null || !embedded) ? null : new EmbeddedObjectContainer[collection.size()]; 339 int idx = -1; 340 for (Object element : collection) { 341 Object elementPC = ec.persistObjectInternal(element, op, fieldNumber, objectType); 342 ec.flushInternal(true); 343 344 if (ids != null) { 345 Object elementID = ObjectContainerHelper.entityToReference(cryptoContext, pmData, elementPC); 346 ids[++idx] = elementID; 347 } 348 else if (embeddedObjectContainers != null) { 349 EmbeddedObjectContainer embeddedObjectContainer = createEmbeddedObjectContainerFromPC(fieldMeta, embeddedClassMeta, elementPC); 350 embeddedObjectContainers[++idx] = embeddedObjectContainer; 351 } 352 } 353 354 355 if (ids != null) 356 objectContainer.setValue(fieldMeta.getFieldID(), ids); 357 else if (embeddedObjectContainers != null) 358 objectContainer.setValue(fieldMeta.getFieldID(), embeddedObjectContainers); 359 } 360 361 /** 362 * Store a {@link Map} of related objects (1-n-relationship). 363 * The related objects are persistence-capable. 364 */ 365 protected void storeObjectFieldWithRelationTypeMap(int fieldNumber, Object value, AbstractMemberMetaData mmd, FieldMeta fieldMeta) { 366 boolean keyIsPersistent = mmd.getMap().keyIsPersistent(); 367 boolean valueIsPersistent = mmd.getMap().valueIsPersistent(); 368 boolean embeddedKey = mmd.getMap().isEmbeddedKey(); 369 boolean embeddedValue = mmd.getMap().isEmbeddedValue(); 370 371 int keyObjectType; 372 if (keyIsPersistent) 373 keyObjectType = embeddedKey ? ObjectProvider.EMBEDDED_MAP_KEY_PC : ObjectProvider.PC; 374 else 375 keyObjectType = -1; 376 377 int valueObjectType; 378 if (valueIsPersistent) 379 valueObjectType = embeddedValue ? ObjectProvider.EMBEDDED_MAP_VALUE_PC : ObjectProvider.PC; 380 else 381 valueObjectType = -1; 382 383 Map<?,?> valueMap = (Map<?, ?>) value; 384 Map<Object,Object> idMap = mmd.getMappedBy() != null ? null : new HashMap<Object, Object>(valueMap.size()); 385 for (Map.Entry<?, ?> me : valueMap.entrySet()) { 386 Object k = me.getKey(); 387 Object v = me.getValue(); 388 389 if (keyIsPersistent) { 390 Object kpc = ec.persistObjectInternal(k, op, fieldNumber, keyObjectType); 391 ec.flushInternal(true); 392 393 if (idMap != null) 394 k = ObjectContainerHelper.entityToReference(cryptoContext, pmData, kpc); 395 } 396 397 if (valueIsPersistent) { 398 Object vpc = ec.persistObjectInternal(v, op, fieldNumber, valueObjectType); 399 ec.flushInternal(true); 400 401 if (idMap != null) 402 v = ObjectContainerHelper.entityToReference(cryptoContext, pmData, vpc); 403 } 404 405 if (idMap != null) 406 idMap.put(k, v); 407 } 408 409 if (idMap != null) 410 objectContainer.setValue(fieldMeta.getFieldID(), idMap); 411 } 412 413 }