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