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