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.crypto.CryptoContext; 027 import org.cumulus4j.store.model.ClassMeta; 028 import org.cumulus4j.store.model.DataEntry; 029 import org.cumulus4j.store.model.DataEntryDAO; 030 import org.cumulus4j.store.model.EmbeddedObjectContainer; 031 import org.cumulus4j.store.model.ObjectContainer; 032 import org.datanucleus.ExecutionContext; 033 import org.datanucleus.identity.IdentityUtils; 034 import org.datanucleus.metadata.AbstractClassMetaData; 035 import org.slf4j.Logger; 036 import org.slf4j.LoggerFactory; 037 038 /** 039 * Helper class for replacing object-references when storing a 1-1- or 1-n- or m-n-relationship 040 * inside an {@link ObjectContainer}. 041 * 042 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de 043 */ 044 public final class ObjectContainerHelper 045 { 046 private static final Logger logger = LoggerFactory.getLogger(ObjectContainerHelper.class); 047 048 /** 049 * If <code>false</code>, store object-ID in {@link ObjectContainer}. 050 * If <code>true</code>, store {@link DataEntry#getDataEntryID() dataEntryID} in {@link ObjectContainer}. 051 */ 052 private static final boolean USE_DATA_ENTRY_ID = true; 053 054 private ObjectContainerHelper() { } 055 056 private static final class TemporaryReferenceDataEntry { 057 public CryptoContext cryptoContext; 058 public String objectID; 059 public long dataEntryID = -1; 060 public ClassMeta classMeta; 061 } 062 063 private static void registerTemporaryReferenceDataEntry(CryptoContext cryptoContext, PersistenceManager pmData, DataEntry dataEntry) 064 { 065 assertTemporaryReferenceScopeEntered(cryptoContext, pmData); 066 067 Map<String, TemporaryReferenceDataEntry> objectID2tempRefMap = temporaryReferenceDataEntryMapThreadLocal.get(); 068 if (objectID2tempRefMap == null) { 069 objectID2tempRefMap = new HashMap<String, TemporaryReferenceDataEntry>(); 070 temporaryReferenceDataEntryMapThreadLocal.set(objectID2tempRefMap); 071 } 072 073 TemporaryReferenceDataEntry trde = new TemporaryReferenceDataEntry(); 074 trde.cryptoContext = cryptoContext; 075 trde.objectID = dataEntry.getObjectID(); 076 trde.dataEntryID = dataEntry.getDataEntryID(); 077 trde.classMeta = dataEntry.getClassMeta(); 078 079 if (trde.dataEntryID < 0) { 080 throw new IllegalStateException("dataEntry.dataEntryID < 0 :: trde.objectID = " + trde.objectID); 081 } 082 083 objectID2tempRefMap.put(trde.objectID, trde); 084 } 085 086 private static ThreadLocal<Map<String, TemporaryReferenceDataEntry>> temporaryReferenceDataEntryMapThreadLocal = new ThreadLocal<Map<String,TemporaryReferenceDataEntry>>(); 087 088 private static ThreadLocal<Integer> temporaryReferenceScopeCounterThreadLocal = new ThreadLocal<Integer>(); 089 090 public static void enterTemporaryReferenceScope() { 091 Integer temporaryReferenceScopeCounter = temporaryReferenceScopeCounterThreadLocal.get(); 092 093 if (temporaryReferenceScopeCounter == null) { 094 assertNoEmptyTemporaryReferenceDataEntry(); 095 temporaryReferenceScopeCounter = 1; 096 } 097 else 098 temporaryReferenceScopeCounter = temporaryReferenceScopeCounter + 1; 099 100 temporaryReferenceScopeCounterThreadLocal.set(temporaryReferenceScopeCounter); 101 } 102 103 public static void exitTemporaryReferenceScope(boolean error) { 104 Integer temporaryReferenceScopeCounter = temporaryReferenceScopeCounterThreadLocal.get(); 105 106 if (temporaryReferenceScopeCounter == null) 107 throw new IllegalStateException("temporaryReferenceScopeCounter == null"); 108 109 temporaryReferenceScopeCounter = temporaryReferenceScopeCounter - 1; 110 if (temporaryReferenceScopeCounter.intValue() == 0) { 111 temporaryReferenceScopeCounterThreadLocal.remove(); 112 if (error) 113 deleteTemporaryReferenceEmptyDataEntries(); 114 else { 115 assertNoEmptyTemporaryReferenceDataEntry(); 116 temporaryReferenceDataEntryMapThreadLocal.remove(); 117 } 118 } 119 else { 120 if (temporaryReferenceScopeCounter.intValue() < 0) 121 throw new IllegalStateException("temporaryReferenceScopeCounter < 0"); 122 123 temporaryReferenceScopeCounterThreadLocal.set(temporaryReferenceScopeCounter); 124 } 125 } 126 127 public static DataEntry getTemporaryReferenceDataEntry(CryptoContext cryptoContext, PersistenceManager pmData, String objectIDString) 128 { 129 assertTemporaryReferenceScopeEntered(cryptoContext, pmData); 130 131 Map<String, TemporaryReferenceDataEntry> objectID2tempRefMap = temporaryReferenceDataEntryMapThreadLocal.get(); 132 if (objectID2tempRefMap == null) 133 return null; 134 135 TemporaryReferenceDataEntry trde = objectID2tempRefMap.get(objectIDString); 136 if (trde == null) 137 return null; 138 139 DataEntry dataEntry = new DataEntryDAO(pmData, cryptoContext.getKeyStoreRefID()).getDataEntry(trde.dataEntryID); // .getDataEntry(trde.classMeta, objectIDString); 140 return dataEntry; 141 } 142 143 private static void deleteTemporaryReferenceEmptyDataEntries() { 144 Map<String, TemporaryReferenceDataEntry> objectID2tempRefMap = temporaryReferenceDataEntryMapThreadLocal.get(); 145 if (objectID2tempRefMap == null || objectID2tempRefMap.isEmpty()) 146 return; 147 148 for (TemporaryReferenceDataEntry trde : objectID2tempRefMap.values()) { 149 PersistenceManager pmData = trde.cryptoContext.getPersistenceManagerForData(); 150 DataEntryDAO dataEntryDAO = new DataEntryDAO(pmData, trde.cryptoContext.getKeyStoreRefID()); 151 DataEntry dataEntry = dataEntryDAO.getDataEntry(trde.dataEntryID); 152 if (dataEntry != null && (dataEntry.getValue() == null || dataEntry.getValue().length == 0)) 153 pmData.deletePersistent(dataEntry); 154 } 155 temporaryReferenceDataEntryMapThreadLocal.remove(); 156 } 157 158 private static void assertNoEmptyTemporaryReferenceDataEntry() { 159 Map<String, TemporaryReferenceDataEntry> objectID2tempRefMap = temporaryReferenceDataEntryMapThreadLocal.get(); 160 if (objectID2tempRefMap == null || objectID2tempRefMap.isEmpty()) 161 return; 162 163 for (TemporaryReferenceDataEntry trde : objectID2tempRefMap.values()) { 164 PersistenceManager pmData = trde.cryptoContext.getPersistenceManagerForData(); 165 DataEntryDAO dataEntryDAO = new DataEntryDAO(pmData, trde.cryptoContext.getKeyStoreRefID()); 166 DataEntry dataEntry = dataEntryDAO.getDataEntry(trde.dataEntryID); 167 if (dataEntry != null && (dataEntry.getValue() == null || dataEntry.getValue().length == 0)) 168 throw new IllegalStateException("Found empty TemporaryReferenceDataEntry! dataEntryID=" + trde.dataEntryID 169 + " classMeta.classID=" + (trde.classMeta == null ? null : trde.classMeta.getClassID()) 170 + " objectID=" + trde.objectID); 171 } 172 } 173 174 private static void assertTemporaryReferenceScopeEntered(CryptoContext cryptoContext, PersistenceManager pmData) { 175 Integer temporaryReferenceScopeCounter = temporaryReferenceScopeCounterThreadLocal.get(); 176 177 if (temporaryReferenceScopeCounter == null) 178 throw new IllegalStateException("temporaryReferenceScopeCounter == null"); 179 180 if (temporaryReferenceScopeCounter.intValue() < 1) 181 throw new IllegalStateException("temporaryReferenceScopeCounter < 1"); 182 } 183 184 public static Object entityToReference(CryptoContext cryptoContext, PersistenceManager pmData, Object entity) 185 { 186 if (entity == null) 187 return null; 188 189 ExecutionContext ec = cryptoContext.getExecutionContext(); 190 Cumulus4jStoreManager storeManager = (Cumulus4jStoreManager) ec.getStoreManager(); 191 Object objectID = ec.getApiAdapter().getIdForObject(entity); 192 if (objectID == null) 193 throw new IllegalStateException("executionContext.getApiAdapter().getIdForObject(entity) returned null for " + entity); 194 195 storeManager.setClassNameForObjectID(objectID, entity.getClass().getName()); 196 197 if (USE_DATA_ENTRY_ID) { 198 ClassMeta classMeta = storeManager.getClassMeta(ec, entity.getClass()); 199 String objectIDString = objectID.toString(); 200 Long dataEntryID = new DataEntryDAO(pmData, cryptoContext.getKeyStoreRefID()).getDataEntryID(classMeta, objectIDString); 201 if (dataEntryID == null) { 202 // Referenced entity not yet persisted => Create a temporarily empty DataEntry. It should be 203 // filled later when Cumulus4jPersistenceHandler.insertObject(...) is called for this entity. 204 // 205 // TODO If we ever stumble over empty DataEntry objects in the database, we should add a sanity check, 206 // which checks at the end of a flush(...) or commit(...) whether all of the DataEntry objects created here 207 // were actually post-processed by a call to Cumulus4jPersistenceHandler.insertObject(...). Marco :-) 208 DataEntry dataEntry = pmData.makePersistent(new DataEntry(classMeta, cryptoContext.getKeyStoreRefID(), objectIDString)); 209 dataEntryID = dataEntry.getDataEntryID(); 210 registerTemporaryReferenceDataEntry(cryptoContext, pmData, dataEntry); 211 logger.trace("entityToReference: Created temporary-reference-DataEntry for: {}", objectIDString); 212 // throw new IllegalStateException("DataEntry.getDataEntryID(...) returned null for entity=\"" + entity + "\" with objectID=\"" + objectID + "\""); 213 } 214 215 return dataEntryID; 216 } 217 218 return objectID; 219 } 220 221 public static Object referenceToEntity(CryptoContext cryptoContext, PersistenceManager pmData, Object reference) 222 { 223 if (reference == null) 224 return null; 225 226 ExecutionContext ec = cryptoContext.getExecutionContext(); 227 228 if (USE_DATA_ENTRY_ID) { 229 DataEntry dataEntry = new DataEntryDAO(pmData, cryptoContext.getKeyStoreRefID()).getDataEntry(((Long)reference).longValue()); 230 if (dataEntry != null && JDOHelper.isDeleted(dataEntry)) { 231 // Added check for deleted state because of https://sourceforge.net/tracker/?func=detail&aid=3515534&group_id=517465&atid=2102911 232 // Marco :-) 233 logger.warn("referenceToEntity: DataEntry.getDataEntry(...) returned deleted instance for dataEntryID=\"{}\"! Setting it to null.", reference); 234 dataEntry = null; 235 } 236 237 if (dataEntry == null) { 238 String message = String.format("DataEntry.getDataEntry(...) returned null for reference=\"%s\"!", reference); 239 if (((Cumulus4jPersistenceHandler)ec.getNucleusContext().getStoreManager().getPersistenceHandler()).useReferentialIntegrity()) 240 throw new IllegalStateException(message); 241 else { 242 // https://sourceforge.net/tracker/?func=detail&aid=3515529&group_id=517465&atid=2102914 243 logger.warn("referenceToEntity: {} Returning null, because reference is orphaned.", message); 244 return null; 245 } 246 } 247 248 AbstractClassMetaData cmd = dataEntry.getClassMeta().getDataNucleusClassMetaData(ec); 249 return IdentityUtils.getObjectFromIdString(dataEntry.getObjectID(), cmd, ec, true); 250 } 251 252 return ec.findObject(reference, true, true, null); 253 } 254 255 public static Long referenceToDataEntryID(CryptoContext cryptoContext, PersistenceManager pmData, Object reference) 256 { 257 if (reference == null) 258 return null; 259 260 if (USE_DATA_ENTRY_ID) { 261 if (reference instanceof EmbeddedObjectContainer) 262 return null; 263 264 return (Long)reference; 265 } 266 267 ExecutionContext ec = cryptoContext.getExecutionContext(); 268 Cumulus4jStoreManager storeManager = (Cumulus4jStoreManager) ec.getStoreManager(); 269 String clazzName = storeManager.getClassNameForObjectID(reference, ec.getClassLoaderResolver(), ec); 270 Class<?> clazz = ec.getClassLoaderResolver().classForName(clazzName); 271 ClassMeta classMeta = storeManager.getClassMeta(ec, clazz); 272 return new DataEntryDAO(pmData, cryptoContext.getKeyStoreRefID()).getDataEntryID(classMeta, reference.toString()); 273 } 274 }