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 }