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 }