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 }