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.ExecutionContext;
036    import org.datanucleus.metadata.AbstractClassMetaData;
037    import org.datanucleus.metadata.AbstractMemberMetaData;
038    import org.datanucleus.metadata.RelationType;
039    import org.datanucleus.state.ObjectProvider;
040    import org.datanucleus.store.fieldmanager.AbstractFieldManager;
041    import org.datanucleus.store.types.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                    RelationType 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 == RelationType.NONE)
184                            storeObjectFieldWithRelationTypeNone(fieldNumber, value, mmd, fieldMeta);
185                    else if (RelationType.isRelationSingleValued(relationType))
186                            storeObjectFieldWithRelationTypeSingleValue(fieldNumber, value, mmd, fieldMeta);
187                    else if (RelationType.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    }