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;
019    
020    import java.util.HashMap;
021    import java.util.Map;
022    
023    import javax.jdo.PersistenceManager;
024    
025    import org.cumulus4j.store.model.ClassMeta;
026    import org.cumulus4j.store.model.DataEntry;
027    import org.cumulus4j.store.model.ObjectContainer;
028    import org.datanucleus.identity.IdentityUtils;
029    import org.datanucleus.metadata.AbstractClassMetaData;
030    import org.datanucleus.store.ExecutionContext;
031    import org.slf4j.Logger;
032    import org.slf4j.LoggerFactory;
033    
034    /**
035     * Helper class for replacing object-references when storing a 1-1- or 1-n- or m-n-relationship
036     * inside an {@link ObjectContainer}.
037     *
038     * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
039     */
040    public final class ObjectContainerHelper
041    {
042            private static final Logger logger = LoggerFactory.getLogger(ObjectContainerHelper.class);
043    
044            /**
045             * If <code>false</code>, store object-ID in {@link ObjectContainer}.
046             * If <code>true</code>, store {@link DataEntry#getDataEntryID() dataEntryID} in {@link ObjectContainer}.
047             */
048            private static final boolean USE_DATA_ENTRY_ID = true;
049    
050            private ObjectContainerHelper() { }
051    
052            private static final class TemporaryReferenceDataEntry {
053                    public String objectID;
054                    public ClassMeta classMeta;
055            }
056    
057            private static final String PM_DATA_KEY_TEMPORARY_REFERENCE_DATA_ENTRY_MAP = "temporaryReferenceDataEntryMap";
058    
059            private static void registerTemporaryReferenceDataEntry(PersistenceManager pmData, DataEntry dataEntry)
060            {
061                    @SuppressWarnings("unchecked")
062                    Map<String, TemporaryReferenceDataEntry> objectID2tempRefMap = (Map<String, TemporaryReferenceDataEntry>) pmData.getUserObject(PM_DATA_KEY_TEMPORARY_REFERENCE_DATA_ENTRY_MAP);
063                    if (objectID2tempRefMap == null) {
064                            objectID2tempRefMap = new HashMap<String, TemporaryReferenceDataEntry>();
065                            pmData.putUserObject(PM_DATA_KEY_TEMPORARY_REFERENCE_DATA_ENTRY_MAP, objectID2tempRefMap);
066                    }
067    
068                    TemporaryReferenceDataEntry trde = new TemporaryReferenceDataEntry();
069                    trde.objectID = dataEntry.getObjectID();
070                    trde.classMeta = dataEntry.getClassMeta();
071                    objectID2tempRefMap.put(trde.objectID, trde);
072            }
073    
074            public static DataEntry popTemporaryReferenceDataEntry(PersistenceManager pmData, String objectIDString)
075            {
076                    @SuppressWarnings("unchecked")
077                    Map<String, TemporaryReferenceDataEntry> objectID2tempRefMap = (Map<String, TemporaryReferenceDataEntry>) pmData.getUserObject(PM_DATA_KEY_TEMPORARY_REFERENCE_DATA_ENTRY_MAP);
078                    if (objectID2tempRefMap == null)
079                            return null;
080    
081                    TemporaryReferenceDataEntry trde = objectID2tempRefMap.remove(objectIDString);
082                    if (trde == null)
083                            return null;
084    
085                    DataEntry dataEntry = DataEntry.getDataEntry(pmData, trde.classMeta, objectIDString);
086                    return dataEntry;
087            }
088    
089            @SuppressWarnings("unused")
090            public static Object entityToReference(ExecutionContext ec, PersistenceManager pmData, Object entity)
091            {
092                    if (entity == null)
093                            return null;
094    
095                    Cumulus4jStoreManager storeManager = (Cumulus4jStoreManager) ec.getStoreManager();
096                    Object objectID = ec.getApiAdapter().getIdForObject(entity);
097                    if (objectID == null)
098                            throw new IllegalStateException("executionContext.getApiAdapter().getIdForObject(entity) returned null for " + entity);
099    
100                    storeManager.setClassNameForObjectID(objectID, entity.getClass().getName());
101    
102                    if (USE_DATA_ENTRY_ID) {
103                            ClassMeta classMeta = storeManager.getClassMeta(ec, entity.getClass());
104                            String objectIDString = objectID.toString();
105                            Long dataEntryID = DataEntry.getDataEntryID(pmData, classMeta, objectIDString);
106                            if (dataEntryID == null) {
107                                    // Referenced entity not yet persisted => Create a temporarily empty DataEntry. It should be
108                                    // filled later when Cumulus4jPersistenceHandler.insertObject(...) is called for this entity.
109                                    //
110                                    // TODO If we ever stumble over empty DataEntry objects in the database, we should add a sanity check,
111                                    // which checks at the end of a flush(...) or commit(...) whether all of the DataEntry objects created here
112                                    // were actually post-processed by a call to Cumulus4jPersistenceHandler.insertObject(...). Marco :-)
113                                    DataEntry dataEntry = pmData.makePersistent(new DataEntry(classMeta, objectIDString));
114                                    dataEntryID = dataEntry.getDataEntryID();
115                                    registerTemporaryReferenceDataEntry(pmData, dataEntry);
116                                    logger.trace("entityToReference: Created temporary-reference-DataEntry for: {}", objectIDString);
117    //                              throw new IllegalStateException("DataEntry.getDataEntryID(...) returned null for entity=\"" + entity + "\" with objectID=\"" + objectID +  "\"");
118                            }
119    
120                            return dataEntryID;
121                    }
122    
123                    return objectID;
124            }
125    
126            @SuppressWarnings("unused")
127            public static Object referenceToEntity(ExecutionContext ec, PersistenceManager pmData, Object reference)
128            {
129                    if (reference == null)
130                            return null;
131    
132                    if (USE_DATA_ENTRY_ID) {
133                            DataEntry dataEntry = DataEntry.getDataEntry(pmData, ((Long)reference).longValue());
134                            if (dataEntry == null)
135                                    throw new IllegalStateException("DataEntry.getDataEntry(...) returned null for reference=\"" + reference + "\"!");
136    
137                            AbstractClassMetaData cmd = dataEntry.getClassMeta().getDataNucleusClassMetaData(ec);
138                            return IdentityUtils.getObjectFromIdString(dataEntry.getObjectID(), cmd, ec, true);
139                    }
140    
141                    return ec.findObject(reference, true, true, null);
142            }
143    
144            @SuppressWarnings("unused")
145            public static Long referenceToDataEntryID(ExecutionContext ec, PersistenceManager pmData, Object reference)
146            {
147                    if (reference == null)
148                            return null;
149    
150                    if (USE_DATA_ENTRY_ID)
151                            return (Long)reference;
152    
153                    Cumulus4jStoreManager storeManager = (Cumulus4jStoreManager) ec.getStoreManager();
154                    String clazzName = storeManager.getClassNameForObjectID(reference, ec.getClassLoaderResolver(), ec);
155                    Class<?> clazz = ec.getClassLoaderResolver().classForName(clazzName);
156                    ClassMeta classMeta = storeManager.getClassMeta(ec, clazz);
157                    return DataEntry.getDataEntryID(pmData, classMeta, reference.toString());
158            }
159    }