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