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 }