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.Arrays; 021 import java.util.Map; 022 023 import javax.jdo.PersistenceManager; 024 025 import org.cumulus4j.store.crypto.CryptoContext; 026 import org.cumulus4j.store.fieldmanager.FetchFieldManager; 027 import org.cumulus4j.store.fieldmanager.StoreFieldManager; 028 import org.cumulus4j.store.model.ClassMeta; 029 import org.cumulus4j.store.model.DataEntry; 030 import org.cumulus4j.store.model.FieldMeta; 031 import org.cumulus4j.store.model.ObjectContainer; 032 import org.datanucleus.exceptions.NucleusObjectNotFoundException; 033 import org.datanucleus.metadata.AbstractClassMetaData; 034 import org.datanucleus.metadata.AbstractMemberMetaData; 035 import org.datanucleus.store.AbstractPersistenceHandler; 036 import org.datanucleus.store.ExecutionContext; 037 import org.datanucleus.store.ObjectProvider; 038 import org.datanucleus.store.connection.ManagedConnection; 039 import org.slf4j.Logger; 040 import org.slf4j.LoggerFactory; 041 042 /** 043 * Handler for all persistence calls from the StoreManager, communicating with the backend datastore(s). 044 * Manages all inserts/updates/deletes/fetches/locates of the users own objects and translates them 045 * into inserts/updates/deletes/fetches/locates of Cumulus4J model objects. 046 */ 047 public class Cumulus4jPersistenceHandler extends AbstractPersistenceHandler 048 { 049 private static final Logger logger = LoggerFactory.getLogger(Cumulus4jPersistenceHandler.class); 050 051 private Cumulus4jStoreManager storeManager; 052 private EncryptionCoordinateSetManager encryptionCoordinateSetManager; 053 private EncryptionHandler encryptionHandler; 054 055 private IndexEntryAction addIndexEntry; 056 private IndexEntryAction removeIndexEntry; 057 058 public Cumulus4jPersistenceHandler(Cumulus4jStoreManager storeManager) { 059 if (storeManager == null) 060 throw new IllegalArgumentException("storeManager == null"); 061 062 this.storeManager = storeManager; 063 this.encryptionCoordinateSetManager = storeManager.getEncryptionCoordinateSetManager(); 064 this.encryptionHandler = storeManager.getEncryptionHandler(); 065 066 this.addIndexEntry = new IndexEntryAction.Add(this); 067 this.removeIndexEntry = new IndexEntryAction.Remove(this); 068 } 069 070 public Cumulus4jStoreManager getStoreManager() { 071 return storeManager; 072 } 073 074 @Override 075 public void close() { 076 // No resources require to be closed here. 077 } 078 079 @Override 080 public void deleteObject(ObjectProvider op) { 081 // Check if read-only so update not permitted 082 storeManager.assertReadOnlyForUpdateOfObject(op); 083 084 ExecutionContext ec = op.getExecutionContext(); 085 ManagedConnection mconn = storeManager.getConnection(ec); 086 try { 087 PersistenceManagerConnection pmConn = (PersistenceManagerConnection)mconn.getConnection(); 088 PersistenceManager pmData = pmConn.getDataPM(); 089 CryptoContext cryptoContext = new CryptoContext(encryptionCoordinateSetManager, ec, pmConn); 090 091 Object object = op.getObject(); 092 Object objectID = op.getExternalObjectId(); 093 String objectIDString = objectID.toString(); 094 ClassMeta classMeta = storeManager.getClassMeta(ec, object.getClass()); 095 DataEntry dataEntry = DataEntry.getDataEntry(pmData, classMeta, objectIDString); 096 // if (dataEntry == null) 097 // throw new NucleusObjectNotFoundException("Object does not exist in datastore: class=" + classMeta.getClassName() + " oid=" + objectIDString); 098 099 if (dataEntry != null) { 100 // decrypt object-container in order to identify index entries for deletion 101 ObjectContainer objectContainer = encryptionHandler.decryptDataEntry(cryptoContext, dataEntry); 102 AbstractClassMetaData dnClassMetaData = storeManager.getMetaDataManager().getMetaDataForClass(object.getClass(), ec.getClassLoaderResolver()); 103 104 for (Map.Entry<Long, ?> me : objectContainer.getFieldID2value().entrySet()) { 105 long fieldID = me.getKey(); 106 Object fieldValue = me.getValue(); 107 FieldMeta fieldMeta = classMeta.getFieldMeta(fieldID); 108 AbstractMemberMetaData dnMemberMetaData = dnClassMetaData.getMetaDataForManagedMemberAtAbsolutePosition(fieldMeta.getDataNucleusAbsoluteFieldNumber()); 109 110 // sanity checks 111 if (dnMemberMetaData == null) 112 throw new IllegalStateException("dnMemberMetaData == null!!! class == \"" + classMeta.getClassName() + "\" fieldMeta.dataNucleusAbsoluteFieldNumber == " + fieldMeta.getDataNucleusAbsoluteFieldNumber() + " fieldMeta.fieldName == \"" + fieldMeta.getFieldName() + "\""); 113 114 if (!fieldMeta.getFieldName().equals(dnMemberMetaData.getName())) 115 throw new IllegalStateException("Meta data inconsistency!!! class == \"" + classMeta.getClassName() + "\" fieldMeta.dataNucleusAbsoluteFieldNumber == " + fieldMeta.getDataNucleusAbsoluteFieldNumber() + " fieldMeta.fieldName == \"" + fieldMeta.getFieldName() + "\" != dnMemberMetaData.name == \"" + dnMemberMetaData.getName() + "\""); 116 117 removeIndexEntry.perform(cryptoContext, dataEntry.getDataEntryID(), fieldMeta, dnMemberMetaData, fieldValue); 118 } 119 pmData.deletePersistent(dataEntry); 120 } 121 122 } finally { 123 mconn.release(); 124 } 125 } 126 127 @Override 128 public void fetchObject(ObjectProvider op, int[] fieldNumbers) 129 { 130 ExecutionContext ec = op.getExecutionContext(); 131 ManagedConnection mconn = storeManager.getConnection(ec); 132 try { 133 PersistenceManagerConnection pmConn = (PersistenceManagerConnection)mconn.getConnection(); 134 PersistenceManager pmData = pmConn.getDataPM(); 135 CryptoContext cryptoContext = new CryptoContext(encryptionCoordinateSetManager, ec, pmConn); 136 137 Object object = op.getObject(); 138 Object objectID = op.getExternalObjectId(); 139 String objectIDString = objectID.toString(); 140 ClassMeta classMeta = storeManager.getClassMeta(ec, object.getClass()); 141 AbstractClassMetaData dnClassMetaData = storeManager.getMetaDataManager().getMetaDataForClass(object.getClass(), ec.getClassLoaderResolver()); 142 143 // TODO Maybe we should load ALL *SIMPLE* fields, because the decryption happens on a per-row-level and thus 144 // loading only some fields makes no sense performance-wise. However, maybe DataNucleus already optimizes 145 // calls to this method. It makes definitely no sense to load 1-n- or 1-1-fields and it makes no sense to 146 // optimize things that already are optimal. Hence we have to analyze first, how often this method is really 147 // called in normal operation. 148 // Marco. 149 150 DataEntry dataEntry = DataEntry.getDataEntry(pmData, classMeta, objectIDString); 151 if (dataEntry == null) 152 throw new NucleusObjectNotFoundException("Object does not exist in datastore: class=" + classMeta.getClassName() + " oid=" + objectIDString); 153 154 ObjectContainer objectContainer = encryptionHandler.decryptDataEntry(cryptoContext, dataEntry); 155 156 op.replaceFields(fieldNumbers, new FetchFieldManager(op, cryptoContext, classMeta, dnClassMetaData, objectContainer)); 157 if (op.getVersion() == null) // null-check prevents overwriting in case this method is called multiple times (for different field-numbers) - TODO necessary? 158 op.setVersion(objectContainer.getVersion()); 159 } finally { 160 mconn.release(); 161 } 162 } 163 164 @Override 165 public Object findObject(ExecutionContext ec, Object id) { 166 // Since we don't manage the memory instantiation of objects this just returns null. 167 return null; 168 } 169 170 @Override 171 public void insertObject(ObjectProvider op) 172 { 173 // Check if read-only so update not permitted 174 storeManager.assertReadOnlyForUpdateOfObject(op); 175 176 ExecutionContext ec = op.getExecutionContext(); 177 ManagedConnection mconn = storeManager.getConnection(ec); 178 try { 179 PersistenceManagerConnection pmConn = (PersistenceManagerConnection)mconn.getConnection(); 180 PersistenceManager pmData = pmConn.getDataPM(); 181 CryptoContext cryptoContext = new CryptoContext(encryptionCoordinateSetManager, ec, pmConn); 182 183 Object object = op.getObject(); 184 Object objectID = op.getExternalObjectId(); 185 ClassMeta classMeta = storeManager.getClassMeta(ec, object.getClass()); 186 187 AbstractClassMetaData dnClassMetaData = storeManager.getMetaDataManager().getMetaDataForClass(object.getClass(), ec.getClassLoaderResolver()); 188 189 int[] allFieldNumbers = dnClassMetaData.getAllMemberPositions(); 190 ObjectContainer objectContainer = new ObjectContainer(); 191 String objectIDString = objectID.toString(); 192 193 // We have to persist the DataEntry before the call to provideFields(...), because the InsertFieldManager recursively 194 // persists other fields which might back-reference (=> mapped-by) and thus need this DataEntry to already exist. 195 // TO DO Try to make this persistent afterwards and solve the problem by only allocating the ID before [keeping it in memory] (see Cumulus4jStoreManager#nextDataEntryID(), which is commented out currently). 196 // Even though reducing the INSERT + UPDATE to one single INSERT in the handling of IndexEntry made 197 // things faster, it seems not to have a performance benefit here. But we should still look at this 198 // again later. 199 // Marco. 200 // 201 // 2012-02-02: Refactored this because of a Heisenbug with optimistic transactions. At the same time solved 202 // the above to do. Marco :-) 203 204 // // In case we work with deferred datastore operations, the DataEntry might already have been written by 205 // // ObjectContainerHelper.entityToReference(...). We therefore, check, if it already exists (and update it then instead of insert). 206 // DataEntry dataEntry; 207 // dataEntry = DataEntry.getDataEntry(pmData, classMeta, objectIDString); 208 // if (dataEntry != null) 209 // logger.trace("insertObject: Found existing DataEntry for: {}", objectIDString); 210 // else { 211 // dataEntry = pmData.makePersistent(new DataEntry(classMeta, objectIDString)); 212 // logger.trace("insertObject: Persisted DataEntry for: {}", objectIDString); 213 // } 214 215 // This performs reachability on this input object so that all related objects are persisted. 216 op.provideFields(allFieldNumbers, new StoreFieldManager(op, pmData, classMeta, dnClassMetaData, objectContainer)); 217 objectContainer.setVersion(op.getTransactionalVersion()); 218 219 // The DataEntry might already have been written by ObjectContainerHelper.entityToReference(...), 220 // if it was needed for a reference. We therefore check, if it already exists (and update it then instead of insert). 221 boolean persistDataEntry = false; 222 DataEntry dataEntry = ObjectContainerHelper.popTemporaryReferenceDataEntry(pmData, objectIDString); 223 if (dataEntry != null) 224 logger.trace("insertObject: Found temporary-reference-DataEntry for: {}", objectIDString); 225 else { 226 persistDataEntry = true; 227 dataEntry = new DataEntry(classMeta, objectIDString); 228 logger.trace("insertObject: Created new DataEntry for: {}", objectIDString); 229 } 230 231 // persist data 232 encryptionHandler.encryptDataEntry(cryptoContext, dataEntry, objectContainer); 233 234 if (persistDataEntry) { 235 dataEntry = pmData.makePersistent(dataEntry); 236 logger.trace("insertObject: Persisted new DataEntry for: {}", objectIDString); 237 } 238 239 // persist index 240 for (Map.Entry<Long, ?> me : objectContainer.getFieldID2value().entrySet()) { 241 long fieldID = me.getKey(); 242 Object fieldValue = me.getValue(); 243 FieldMeta fieldMeta = classMeta.getFieldMeta(fieldID); 244 AbstractMemberMetaData dnMemberMetaData = dnClassMetaData.getMetaDataForManagedMemberAtAbsolutePosition(fieldMeta.getDataNucleusAbsoluteFieldNumber()); 245 246 // sanity checks 247 if (dnMemberMetaData == null) 248 throw new IllegalStateException("dnMemberMetaData == null!!! class == \"" + classMeta.getClassName() + "\" fieldMeta.dataNucleusAbsoluteFieldNumber == " + fieldMeta.getDataNucleusAbsoluteFieldNumber() + " fieldMeta.fieldName == \"" + fieldMeta.getFieldName() + "\""); 249 250 if (!fieldMeta.getFieldName().equals(dnMemberMetaData.getName())) 251 throw new IllegalStateException("Meta data inconsistency!!! class == \"" + classMeta.getClassName() + "\" fieldMeta.dataNucleusAbsoluteFieldNumber == " + fieldMeta.getDataNucleusAbsoluteFieldNumber() + " fieldMeta.fieldName == \"" + fieldMeta.getFieldName() + "\" != dnMemberMetaData.name == \"" + dnMemberMetaData.getName() + "\""); 252 253 addIndexEntry.perform(cryptoContext, dataEntry.getDataEntryID(), fieldMeta, dnMemberMetaData, fieldValue); 254 } 255 } finally { 256 mconn.release(); 257 } 258 } 259 260 @Override 261 public void locateObject(ObjectProvider op) 262 { 263 ManagedConnection mconn = storeManager.getConnection(op.getExecutionContext()); 264 try { 265 PersistenceManagerConnection pmConn = (PersistenceManagerConnection)mconn.getConnection(); 266 PersistenceManager pmData = pmConn.getDataPM(); 267 268 ClassMeta classMeta = storeManager.getClassMeta(op.getExecutionContext(), op.getObject().getClass()); 269 Object objectID = op.getExternalObjectId(); 270 String objectIDString = objectID.toString(); 271 272 DataEntry dataEntry = DataEntry.getDataEntry(pmData, classMeta, objectIDString); 273 if (dataEntry == null) 274 throw new NucleusObjectNotFoundException("Object does not exist in datastore: class=" + classMeta.getClassName() + " oid=" + objectIDString); 275 } finally { 276 mconn.release(); 277 } 278 } 279 280 @Override 281 public void updateObject(ObjectProvider op, int[] fieldNumbers) 282 { 283 // Check if read-only so update not permitted 284 storeManager.assertReadOnlyForUpdateOfObject(op); 285 286 ExecutionContext ec = op.getExecutionContext(); 287 ManagedConnection mconn = storeManager.getConnection(ec); 288 try { 289 PersistenceManagerConnection pmConn = (PersistenceManagerConnection)mconn.getConnection(); 290 PersistenceManager pmData = pmConn.getDataPM(); 291 CryptoContext cryptoContext = new CryptoContext(encryptionCoordinateSetManager, ec, pmConn); 292 293 Object object = op.getObject(); 294 Object objectID = op.getExternalObjectId(); 295 String objectIDString = objectID.toString(); 296 ClassMeta classMeta = storeManager.getClassMeta(ec, object.getClass()); 297 AbstractClassMetaData dnClassMetaData = storeManager.getMetaDataManager().getMetaDataForClass(object.getClass(), ec.getClassLoaderResolver()); 298 299 DataEntry dataEntry = DataEntry.getDataEntry(pmData, classMeta, objectIDString); 300 if (dataEntry == null) 301 throw new NucleusObjectNotFoundException("Object does not exist in datastore: class=" + classMeta.getClassName() + " oid=" + objectIDString); 302 303 long dataEntryID = dataEntry.getDataEntryID(); 304 305 ObjectContainer objectContainerOld = encryptionHandler.decryptDataEntry(cryptoContext, dataEntry); 306 ObjectContainer objectContainerNew = objectContainerOld.clone(); 307 308 // This performs reachability on this input object so that all related objects are persisted 309 op.provideFields(fieldNumbers, new StoreFieldManager(op, pmData, classMeta, dnClassMetaData, objectContainerNew)); 310 objectContainerNew.setVersion(op.getTransactionalVersion()); 311 312 // update persistent data 313 encryptionHandler.encryptDataEntry(cryptoContext, dataEntry, objectContainerNew); 314 315 // update persistent index 316 for (int fieldNumber : fieldNumbers) { 317 AbstractMemberMetaData dnMemberMetaData = dnClassMetaData.getMetaDataForManagedMemberAtAbsolutePosition(fieldNumber); 318 if (dnMemberMetaData == null) 319 throw new IllegalStateException("dnMemberMetaData == null!!! class == \"" + classMeta.getClassName() + "\" fieldNumber == " + fieldNumber); 320 321 if (dnMemberMetaData.getMappedBy() != null) 322 continue; // TODO is this sufficient to take 'mapped-by' into account? 323 324 FieldMeta fieldMeta = classMeta.getFieldMeta(dnMemberMetaData.getClassName(), dnMemberMetaData.getName()); 325 if (fieldMeta == null) 326 throw new IllegalStateException("fieldMeta == null!!! class == \"" + classMeta.getClassName() + "\" dnMemberMetaData.className == \"" + dnMemberMetaData.getClassName() + "\" dnMemberMetaData.name == \"" + dnMemberMetaData.getName() + "\""); 327 328 Object fieldValueOld = objectContainerOld.getValue(fieldMeta.getFieldID()); 329 Object fieldValueNew = objectContainerNew.getValue(fieldMeta.getFieldID()); 330 331 if (!fieldsEqual(fieldValueOld, fieldValueNew)){ 332 333 /* 334 * TODO: 335 * Cumulus4j throws a NullPointerException at this point when running the poleposition benchmark 336 * and using a list data type which has a null value to mark the end of the list. 337 * This null value check solves the problem but an problem when deleting the persisted 338 * data occurs. I have commented this out, because i have not fully analyzed this and so i am not sure 339 * is this is right. 340 * At the moment i have no more time to analyze this problem any more. :( Jan 341 * 342 if(fieldValueOld != null)*/ 343 removeIndexEntry.perform(cryptoContext, dataEntryID, fieldMeta, dnMemberMetaData, fieldValueOld); 344 addIndexEntry.perform(cryptoContext, dataEntryID, fieldMeta, dnMemberMetaData, fieldValueNew); 345 } 346 } 347 } finally { 348 mconn.release(); 349 } 350 } 351 352 private static boolean fieldsEqual(Object obj0, Object obj1) { 353 if (obj0 instanceof Object[] && obj1 instanceof Object[]) 354 return obj0 == obj1 || Arrays.equals((Object[])obj0, (Object[])obj1); 355 return obj0 == obj1 || (obj0 != null && obj0.equals(obj1)); 356 } 357 }