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.model.ClassMeta;
029    import org.cumulus4j.store.model.FieldMeta;
030    import org.cumulus4j.store.model.ObjectContainer;
031    import org.datanucleus.exceptions.NucleusDataStoreException;
032    import org.datanucleus.metadata.AbstractClassMetaData;
033    import org.datanucleus.metadata.AbstractMemberMetaData;
034    import org.datanucleus.metadata.Relation;
035    import org.datanucleus.store.ExecutionContext;
036    import org.datanucleus.store.ObjectProvider;
037    import org.datanucleus.store.fieldmanager.AbstractFieldManager;
038    import org.datanucleus.store.types.sco.SCO;
039    import org.datanucleus.store.types.sco.SCOUtils;
040    import org.slf4j.Logger;
041    import org.slf4j.LoggerFactory;
042    
043    /**
044     * Manager for the process of persisting a user object into the datastore, handling the translation from the
045     * users own object into the DataEntry object.
046     * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
047     */
048    public class StoreFieldManager extends AbstractFieldManager
049    {
050            private static final Logger logger = LoggerFactory.getLogger(StoreFieldManager.class);
051    
052            private ObjectProvider op;
053            private PersistenceManager pmData;
054            private ExecutionContext ec;
055            private ClassMeta classMeta;
056            private AbstractClassMetaData dnClassMetaData;
057            private ObjectContainer objectContainer;
058    
059            public StoreFieldManager(
060                            ObjectProvider op,
061                            PersistenceManager pmData,
062                            ClassMeta classMeta,
063                            AbstractClassMetaData dnClassMetaData,
064                            ObjectContainer objectContainer // populated by this class
065            )
066            {
067                    this.op = op;
068                    this.pmData = pmData;
069                    this.ec = op.getExecutionContext();
070                    this.classMeta = classMeta;
071                    this.dnClassMetaData = dnClassMetaData;
072                    this.objectContainer = objectContainer;
073            }
074    
075            private long getFieldID(int fieldNumber)
076            {
077                    AbstractMemberMetaData mmd = dnClassMetaData.getMetaDataForManagedMemberAtAbsolutePosition(fieldNumber);
078    
079                    FieldMeta fieldMeta = classMeta.getFieldMeta(mmd.getClassName(), mmd.getName());
080                    if (fieldMeta == null)
081                            throw new IllegalStateException("Unknown field! class=" + dnClassMetaData.getFullClassName() + " fieldNumber=" + fieldNumber + " fieldName=" + mmd.getName());
082    
083                    return fieldMeta.getFieldID();
084            }
085    
086            @Override
087            public void storeBooleanField(int fieldNumber, boolean value) {
088                    objectContainer.setValue(getFieldID(fieldNumber), value);
089            }
090    
091            @Override
092            public void storeByteField(int fieldNumber, byte value) {
093                    objectContainer.setValue(getFieldID(fieldNumber), value);
094            }
095    
096            @Override
097            public void storeCharField(int fieldNumber, char value) {
098                    objectContainer.setValue(getFieldID(fieldNumber), value);
099            }
100    
101            @Override
102            public void storeDoubleField(int fieldNumber, double value) {
103                    objectContainer.setValue(getFieldID(fieldNumber), value);
104            }
105    
106            @Override
107            public void storeFloatField(int fieldNumber, float value) {
108                    objectContainer.setValue(getFieldID(fieldNumber), value);
109            }
110    
111            @Override
112            public void storeIntField(int fieldNumber, int value) {
113                    objectContainer.setValue(getFieldID(fieldNumber), value);
114            }
115    
116            @Override
117            public void storeLongField(int fieldNumber, long value) {
118                    objectContainer.setValue(getFieldID(fieldNumber), value);
119            }
120    
121            @Override
122            public void storeShortField(int fieldNumber, short value) {
123                    objectContainer.setValue(getFieldID(fieldNumber), value);
124            }
125    
126            @Override
127            public void storeStringField(int fieldNumber, String value) {
128                    objectContainer.setValue(getFieldID(fieldNumber), value);
129            }
130    
131            @Override
132            public void storeObjectField(int fieldNumber, Object value)
133            {
134                    if (logger.isTraceEnabled()) {
135                            logger.trace(
136                                            "storeObjectField: classMeta.className={} fieldNumber={} value={}",
137                                            new Object[] { classMeta.getClassName(), fieldNumber, value }
138                            );
139                    }
140    
141                    AbstractMemberMetaData mmd = dnClassMetaData.getMetaDataForManagedMemberAtAbsolutePosition(fieldNumber);
142    
143                    FieldMeta fieldMeta = classMeta.getFieldMeta(mmd.getClassName(), mmd.getName());
144                    if (fieldMeta == null)
145                            throw new IllegalStateException("Unknown field! class=" + dnClassMetaData.getFullClassName() + " fieldNumber=" + fieldNumber + " fieldName=" + mmd.getName());
146    
147                    if (value == null) {
148                            objectContainer.setValue(fieldMeta.getFieldID(), null);
149                            return;
150                    }
151    
152                    int relationType = mmd.getRelationType(ec.getClassLoaderResolver());
153    
154                    // Replace any SCO field that isn't already a wrapper, with its wrapper object
155                    boolean[] secondClassMutableFieldFlags = dnClassMetaData.getSCOMutableMemberFlags();
156                    if (secondClassMutableFieldFlags[fieldNumber] && !(value instanceof SCO))
157                            value = op.wrapSCOField(fieldNumber, value, false, true, true);
158    
159                    if (relationType == Relation.NONE)
160                    {
161                            if (mmd.hasCollection()) {
162                                    // Replace the special DN collection by a simple array.
163                                    Collection<?> collection = (Collection<?>)value;
164                                    Object[] values = collection.toArray(new Object[collection.size()]);
165                                    objectContainer.setValue(fieldMeta.getFieldID(), values);
166                            }
167                            else if (mmd.hasMap()) {
168                                    // replace the special DN Map by a simple HashMap.
169                                    Map<?,?> valueMap = (Map<?, ?>) value;
170    
171                                    Map<Object, Object> map;
172                                    @SuppressWarnings("unchecked")
173                                    Class<? extends Map<Object, Object>> instanceType = SCOUtils.getContainerInstanceType(mmd.getType(), mmd.getOrderMetaData() != null);
174                                    try {
175                                            map = instanceType.newInstance();
176                                    } catch (InstantiationException e) {
177                                            throw new NucleusDataStoreException(e.getMessage(), e);
178                                    } catch (IllegalAccessException e) {
179                                            throw new NucleusDataStoreException(e.getMessage(), e);
180                                    }
181    
182                                    map.putAll(valueMap);
183    
184                                    objectContainer.setValue(fieldMeta.getFieldID(), map);
185                            }
186                            else // arrays are not managed (no special DN instances) and thus stored 'as is'...
187                                    objectContainer.setValue(fieldMeta.getFieldID(), value);
188                    }
189                    else if (Relation.isRelationSingleValued(relationType))
190                    {
191                            // Persistable object - persist the related object and store the identity in the cell
192                            Object valuePC = ec.persistObjectInternal(value, op, fieldNumber, -1);
193                            ec.flushInternal(true);
194    
195                            if (mmd.getMappedBy() == null) {
196                                    Object valueID = ObjectContainerHelper.entityToReference(ec, pmData, valuePC);
197                                    objectContainer.setValue(fieldMeta.getFieldID(), valueID);
198                            }
199                    }
200                    else if (Relation.isRelationMultiValued(relationType))
201                    {
202                            // Collection/Map/Array
203                            if (mmd.hasCollection())
204                            {
205                                    Collection<?> collection = (Collection<?>)value;
206                                    Object[] ids = mmd.getMappedBy() != null ? null : new Object[collection.size()];
207                                    int idx = -1;
208                                    for (Object element : collection) {
209                                            Object elementPC = ec.persistObjectInternal(element, op, fieldNumber, -1);
210                                            ec.flushInternal(true);
211    
212                                            if (ids != null) {
213                                                    Object elementID = ObjectContainerHelper.entityToReference(ec, pmData, elementPC);
214                                                    ids[++idx] = elementID;
215                                            }
216                                    }
217    
218                                    if (ids != null)
219                                            objectContainer.setValue(fieldMeta.getFieldID(), ids);
220                            }
221                            else if (mmd.hasMap())
222                            {
223                                    boolean keyIsPersistent = mmd.getMap().keyIsPersistent();
224                                    boolean valueIsPersistent = mmd.getMap().valueIsPersistent();
225    
226                                    Map<?,?> valueMap = (Map<?, ?>) value;
227                                    Map<Object,Object> idMap = mmd.getMappedBy() != null ? null : new HashMap<Object, Object>(valueMap.size());
228                                    for (Map.Entry<?, ?> me : valueMap.entrySet()) {
229                                            Object k = me.getKey();
230                                            Object v = me.getValue();
231    
232                                            if (keyIsPersistent) {
233                                                    Object kpc = ec.persistObjectInternal(k, op, fieldNumber, -1);
234                                                    ec.flushInternal(true);
235    
236                                                    if (idMap != null)
237                                                            k = ObjectContainerHelper.entityToReference(ec, pmData, kpc);
238                                            }
239    
240                                            if (valueIsPersistent) {
241                                                    Object vpc = ec.persistObjectInternal(v, op, fieldNumber, -1);
242                                                    ec.flushInternal(true);
243    
244                                                    if (idMap != null)
245                                                            v = ObjectContainerHelper.entityToReference(ec, pmData, vpc);
246                                            }
247    
248                                            if (idMap != null)
249                                                    idMap.put(k, v);
250                                    }
251    
252                                    if (idMap != null)
253                                            objectContainer.setValue(fieldMeta.getFieldID(), idMap);
254                            }
255                            else if (mmd.hasArray())
256                            {
257                                    if (mmd.getMappedBy() != null)
258                                            throw new UnsupportedOperationException("NYI");
259    
260                                    Object[] ids = new Object[Array.getLength(value)];
261                                    for (int i=0;i<Array.getLength(value);i++)
262                                    {
263                                            Object element = Array.get(value, i);
264                                            Object elementPC = ec.persistObjectInternal(element, op, fieldNumber, -1);
265                                            ec.flushInternal(true);
266    
267                                            Object elementID = ObjectContainerHelper.entityToReference(ec, pmData, elementPC);
268                                            ids[i] = elementID;
269                                    }
270                                    objectContainer.setValue(fieldMeta.getFieldID(), ids);
271                            }
272                    }
273                    else
274                            throw new IllegalStateException("Unexpected relationType: " + relationType);
275            }
276    
277    }