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 }