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.lang.reflect.Array; 021 import java.util.Map; 022 023 import javax.jdo.JDOHelper; 024 import javax.jdo.JDOUserException; 025 import javax.jdo.PersistenceManager; 026 import javax.jdo.Query; 027 028 import org.cumulus4j.store.crypto.CryptoContext; 029 import org.cumulus4j.store.model.ClassMeta; 030 import org.cumulus4j.store.model.FieldMeta; 031 import org.cumulus4j.store.model.FieldMetaRole; 032 import org.cumulus4j.store.model.IndexEntry; 033 import org.cumulus4j.store.model.IndexEntryFactory; 034 import org.cumulus4j.store.model.IndexEntryFactoryRegistry; 035 import org.cumulus4j.store.model.IndexEntryObjectRelationHelper; 036 import org.cumulus4j.store.model.IndexValue; 037 import org.datanucleus.ExecutionContext; 038 import org.datanucleus.metadata.AbstractMemberMetaData; 039 import org.datanucleus.metadata.RelationType; 040 import org.slf4j.Logger; 041 import org.slf4j.LoggerFactory; 042 043 /** 044 * Logic to add or remove an index entry. 045 * <p> 046 * This class is thread-safe. You should normally never need to instantiate this class. 047 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de 048 */ 049 public abstract class IndexEntryAction 050 { 051 protected Cumulus4jPersistenceHandler persistenceHandler; 052 protected Cumulus4jStoreManager storeManager; 053 protected EncryptionHandler encryptionHandler; 054 protected IndexEntryFactoryRegistry indexEntryFactoryRegistry; 055 056 public IndexEntryAction(Cumulus4jPersistenceHandler persistenceHandler) { 057 if (persistenceHandler == null) 058 throw new IllegalArgumentException("persistenceHandler == null"); 059 060 this.persistenceHandler = persistenceHandler; 061 this.storeManager = persistenceHandler.getStoreManager(); 062 this.encryptionHandler = storeManager.getEncryptionHandler(); 063 indexEntryFactoryRegistry = storeManager.getIndexFactoryRegistry(); 064 } 065 066 protected abstract IndexEntry getIndexEntry( 067 IndexEntryFactory indexEntryFactory, CryptoContext cryptoContext, PersistenceManager pmIndex, FieldMeta fieldMeta, ClassMeta classMeta, Object fieldValue 068 ); 069 070 protected abstract IndexEntry getIndexEntryForObjectRelation(CryptoContext cryptoContext, PersistenceManager pmIndex, FieldMeta fieldMeta, ClassMeta classMeta, Long otherDataEntryID); 071 072 protected abstract void _perform(CryptoContext cryptoContext, IndexEntry indexEntry, long dataEntryID); 073 074 public void perform(CryptoContext cryptoContext, long dataEntryID, FieldMeta fieldMeta, 075 AbstractMemberMetaData dnMemberMetaData, ClassMeta classMeta, Object fieldValue 076 ) 077 { 078 ExecutionContext ec = cryptoContext.getExecutionContext(); 079 PersistenceManager pmData = cryptoContext.getPersistenceManagerForData(); 080 PersistenceManager pmIndex = cryptoContext.getPersistenceManagerForIndex(); 081 boolean hasQueryable = dnMemberMetaData.hasExtension(Cumulus4jStoreManager.CUMULUS4J_QUERYABLE); 082 if (hasQueryable) { 083 String val = dnMemberMetaData.getValueForExtension(Cumulus4jStoreManager.CUMULUS4J_QUERYABLE); 084 if (val.equalsIgnoreCase("false")) { 085 // Field marked as not queryable, so don't index it 086 return; 087 } 088 } 089 090 RelationType relationType = dnMemberMetaData.getRelationType(cryptoContext.getExecutionContext().getClassLoaderResolver()); 091 if (RelationType.NONE == relationType) { 092 // The field contains no other persistent entity. It might contain a collection/array/map, though. 093 094 if (dnMemberMetaData.hasCollection() || dnMemberMetaData.hasArray()) { 095 FieldMetaRole role; 096 if (dnMemberMetaData.hasCollection()) 097 role = FieldMetaRole.collectionElement; 098 else 099 role = FieldMetaRole.arrayElement; 100 101 FieldMeta subFieldMeta = fieldMeta.getSubFieldMeta(role); 102 IndexEntryFactory indexEntryFactory = indexEntryFactoryRegistry.getIndexEntryFactory(ec, subFieldMeta, false); 103 if (fieldValue != null) { 104 for (int idx = 0; idx < Array.getLength(fieldValue); ++idx) { 105 Object element = Array.get(fieldValue, idx); 106 IndexEntry indexEntry = getIndexEntry(indexEntryFactory, cryptoContext, pmIndex, subFieldMeta, classMeta, element); 107 _perform(cryptoContext, indexEntry, dataEntryID); 108 } 109 } 110 111 // Add entry for the collection/array size 112 int containerSize = fieldValue == null ? 0 : Array.getLength(fieldValue); 113 IndexEntry sizeIdxEntry = 114 indexEntryFactoryRegistry.getIndexEntryFactoryForContainerSize().createIndexEntry(cryptoContext, pmIndex, fieldMeta, classMeta, new Long(containerSize)); 115 _perform(cryptoContext, sizeIdxEntry, dataEntryID); 116 } 117 else if (dnMemberMetaData.hasMap()) { 118 Map<?,?> fieldValueMap = (Map<?,?>) fieldValue; 119 120 FieldMeta subFieldMetaKey = fieldMeta.getSubFieldMeta(FieldMetaRole.mapKey); 121 FieldMeta subFieldMetaValue = fieldMeta.getSubFieldMeta(FieldMetaRole.mapValue); 122 IndexEntryFactory indexEntryFactoryKey = indexEntryFactoryRegistry.getIndexEntryFactory(ec, subFieldMetaKey, false); 123 IndexEntryFactory indexEntryFactoryValue = indexEntryFactoryRegistry.getIndexEntryFactory(ec, subFieldMetaValue, false); 124 125 if (fieldValueMap != null) { 126 for (Map.Entry<?, ?> me : fieldValueMap.entrySet()) { 127 IndexEntry indexEntryKey = getIndexEntry(indexEntryFactoryKey, cryptoContext, pmIndex, subFieldMetaKey, classMeta, me.getKey()); 128 _perform(cryptoContext, indexEntryKey, dataEntryID); 129 130 IndexEntry indexEntryValue = getIndexEntry(indexEntryFactoryValue, cryptoContext, pmIndex, subFieldMetaValue, classMeta, me.getValue()); 131 _perform(cryptoContext, indexEntryValue, dataEntryID); 132 } 133 } 134 135 // Add entry for the map size 136 int containerSize = (fieldValueMap != null ? fieldValueMap.size() : 0); 137 IndexEntry sizeIdxEntry = 138 indexEntryFactoryRegistry.getIndexEntryFactoryForContainerSize().createIndexEntry(cryptoContext, pmIndex, fieldMeta, classMeta, new Long(containerSize)); 139 _perform(cryptoContext, sizeIdxEntry, dataEntryID); 140 } 141 else { 142 IndexEntryFactory indexEntryFactory = indexEntryFactoryRegistry.getIndexEntryFactory(ec, fieldMeta, false); 143 IndexEntry indexEntry = getIndexEntry(indexEntryFactory, cryptoContext, pmIndex, fieldMeta, classMeta, fieldValue); 144 _perform(cryptoContext, indexEntry, dataEntryID); 145 } 146 } 147 else if (RelationType.isRelationSingleValued(relationType)) { 148 // 1-1-relationship to another persistence-capable object. 149 Long otherDataEntryID = ObjectContainerHelper.referenceToDataEntryID(cryptoContext, pmData, fieldValue); 150 IndexEntry indexEntry = getIndexEntryForObjectRelation(cryptoContext, pmIndex, fieldMeta, classMeta, otherDataEntryID); 151 _perform(cryptoContext, indexEntry, dataEntryID); 152 } 153 else if (RelationType.isRelationMultiValued(relationType)) { 154 // map, collection, array 155 156 if (dnMemberMetaData.hasMap()) { // Map.class.isAssignableFrom(dnMemberMetaData.getType())) { 157 Map<?,?> fieldValueMap = (Map<?,?>) fieldValue; 158 159 boolean keyIsPersistent = dnMemberMetaData.getMap().keyIsPersistent(); 160 boolean valueIsPersistent = dnMemberMetaData.getMap().valueIsPersistent(); 161 162 FieldMeta subFieldMetaKey = fieldMeta.getSubFieldMeta(FieldMetaRole.mapKey); 163 FieldMeta subFieldMetaValue = fieldMeta.getSubFieldMeta(FieldMetaRole.mapValue); 164 IndexEntryFactory indexEntryFactoryKey = indexEntryFactoryRegistry.getIndexEntryFactory(ec, subFieldMetaKey, false); 165 IndexEntryFactory indexEntryFactoryValue = indexEntryFactoryRegistry.getIndexEntryFactory(ec, subFieldMetaValue, false); 166 167 if (fieldValueMap != null) { 168 for (Map.Entry<?, ?> me : fieldValueMap.entrySet()) { 169 if (keyIsPersistent) { 170 Long otherDataEntryID = ObjectContainerHelper.referenceToDataEntryID(cryptoContext, pmData, me.getKey()); 171 IndexEntry indexEntry = getIndexEntryForObjectRelation(cryptoContext, pmIndex, subFieldMetaKey, classMeta, otherDataEntryID); 172 _perform(cryptoContext, indexEntry, dataEntryID); 173 } 174 else { 175 IndexEntry indexEntry = getIndexEntry(indexEntryFactoryKey, cryptoContext, pmIndex, subFieldMetaKey, classMeta, me.getKey()); 176 _perform(cryptoContext, indexEntry, dataEntryID); 177 } 178 179 if (valueIsPersistent) { 180 Long otherDataEntryID = ObjectContainerHelper.referenceToDataEntryID(cryptoContext, pmData, me.getValue()); 181 IndexEntry indexEntry = getIndexEntryForObjectRelation(cryptoContext, pmIndex, subFieldMetaValue, classMeta, otherDataEntryID); 182 _perform(cryptoContext, indexEntry, dataEntryID); 183 } 184 else { 185 IndexEntry indexEntry = getIndexEntry(indexEntryFactoryValue, cryptoContext, pmIndex, subFieldMetaValue, classMeta, me.getValue()); 186 _perform(cryptoContext, indexEntry, dataEntryID); 187 } 188 } 189 } 190 191 // Add entry for the map size 192 int containerSize = (fieldValueMap != null ? fieldValueMap.size() : 0); 193 IndexEntry sizeIdxEntry = 194 indexEntryFactoryRegistry.getIndexEntryFactoryForContainerSize().createIndexEntry(cryptoContext, pmIndex, fieldMeta, classMeta, new Long(containerSize)); 195 _perform(cryptoContext, sizeIdxEntry, dataEntryID); 196 } 197 else if (dnMemberMetaData.hasCollection() || dnMemberMetaData.hasArray()) { 198 FieldMetaRole role; 199 if (dnMemberMetaData.hasCollection()) // Collection.class.isAssignableFrom(dnMemberMetaData.getType())) 200 role = FieldMetaRole.collectionElement; 201 else 202 role = FieldMetaRole.arrayElement; 203 204 FieldMeta subFieldMeta = fieldMeta.getSubFieldMeta(role); 205 Object[] fieldValueArray = (Object[]) fieldValue; 206 if (fieldValueArray != null) { 207 for (Object element : fieldValueArray) { 208 Long otherDataEntryID = ObjectContainerHelper.referenceToDataEntryID(cryptoContext, pmData, element); 209 IndexEntry indexEntry = getIndexEntryForObjectRelation(cryptoContext, pmIndex, subFieldMeta, classMeta, otherDataEntryID); 210 _perform(cryptoContext, indexEntry, dataEntryID); 211 } 212 } 213 214 // Add entry for the collection/array size 215 int containerSize = (fieldValueArray != null ? fieldValueArray.length : 0); 216 IndexEntry sizeIdxEntry = 217 indexEntryFactoryRegistry.getIndexEntryFactoryForContainerSize().createIndexEntry(cryptoContext, pmIndex, fieldMeta, classMeta, new Long(containerSize)); 218 _perform(cryptoContext, sizeIdxEntry, dataEntryID); 219 } 220 } 221 } 222 223 static class Add extends IndexEntryAction 224 { 225 public Add(Cumulus4jPersistenceHandler persistenceHandler) { 226 super(persistenceHandler); 227 } 228 229 @Override 230 public IndexEntry getIndexEntry(IndexEntryFactory indexEntryFactory, CryptoContext cryptoContext, PersistenceManager pmIndex, FieldMeta fieldMeta, ClassMeta classMeta, Object fieldValue) 231 { 232 return indexEntryFactory == null ? null : indexEntryFactory.createIndexEntry(cryptoContext, pmIndex, fieldMeta, classMeta, fieldValue); 233 } 234 235 @Override 236 public IndexEntry getIndexEntryForObjectRelation(CryptoContext cryptoContext, PersistenceManager pmIndex, FieldMeta fieldMeta, ClassMeta classMeta, Long otherDataEntryID) { 237 return IndexEntryObjectRelationHelper.createIndexEntry(cryptoContext, pmIndex, fieldMeta, classMeta, otherDataEntryID); 238 } 239 240 @Override 241 protected void _perform(CryptoContext cryptoContext, IndexEntry indexEntry, long dataEntryID) 242 { 243 if (indexEntry == null) 244 return; 245 246 IndexValue indexValue = encryptionHandler.decryptIndexEntry(cryptoContext, indexEntry); 247 indexValue.addDataEntryID(dataEntryID); 248 encryptionHandler.encryptIndexEntry(cryptoContext, indexEntry, indexValue); 249 cryptoContext.getPersistenceManagerForIndex().makePersistent(indexEntry); // We do not persist directly when creating anymore, thus we must persist here. This is a no-op if it's already persistent. 250 } 251 } 252 253 static class Remove extends IndexEntryAction 254 { 255 private static final Logger logger = LoggerFactory.getLogger(Remove.class); 256 257 public Remove(Cumulus4jPersistenceHandler persistenceHandler) { 258 super(persistenceHandler); 259 } 260 261 @Override 262 public IndexEntry getIndexEntry(IndexEntryFactory indexEntryFactory, CryptoContext cryptoContext, PersistenceManager pmIndex, FieldMeta fieldMeta, ClassMeta classMeta, Object fieldValue) 263 { 264 return indexEntryFactory == null ? null : indexEntryFactory.getIndexEntry(cryptoContext, pmIndex, fieldMeta, classMeta, fieldValue); 265 } 266 267 @Override 268 protected IndexEntry getIndexEntryForObjectRelation(CryptoContext cryptoContext, PersistenceManager pmIndex, FieldMeta fieldMeta, ClassMeta classMeta, Long otherDataEntryID) { 269 return IndexEntryObjectRelationHelper.getIndexEntry(cryptoContext, pmIndex, fieldMeta, classMeta, otherDataEntryID); 270 } 271 272 @Override 273 protected void _perform(CryptoContext cryptoContext, IndexEntry indexEntry, long dataEntryID) 274 { 275 if (indexEntry == null) 276 return; 277 278 IndexValue indexValue = encryptionHandler.decryptIndexEntry(cryptoContext, indexEntry); 279 indexValue.removeDataEntryID(dataEntryID); 280 if (indexValue.isDataEntryIDsEmpty()) { 281 PersistenceManager pmIndex = cryptoContext.getPersistenceManagerForIndex(); 282 try { 283 pmIndex.deletePersistent(indexEntry); 284 // The above line sometimes (but not always) causes the following error: 285 // org.datanucleus.exceptions.NucleusUserException: Transient instances cant be deleted. 286 // at org.datanucleus.ObjectManagerImpl.deleteObjectInternal(ObjectManagerImpl.java:2068) 287 // at org.datanucleus.ObjectManagerImpl.deleteObjectWork(ObjectManagerImpl.java:2019) 288 // at org.datanucleus.ObjectManagerImpl.deleteObject(ObjectManagerImpl.java:1973) 289 // at org.datanucleus.api.jdo.JDOPersistenceManager.jdoDeletePersistent(JDOPersistenceManager.java:815) 290 // at org.datanucleus.api.jdo.JDOPersistenceManager.deletePersistent(JDOPersistenceManager.java:833) 291 // at org.cumulus4j.store.IndexEntryAction$Remove._perform(IndexEntryAction.java:268) 292 // at org.cumulus4j.store.IndexEntryAction.perform(IndexEntryAction.java:206) 293 // at org.cumulus4j.store.Cumulus4jPersistenceHandler.updateObject(Cumulus4jPersistenceHandler.java:343) 294 } catch (JDOUserException x) { 295 logger.warn("_perform: " + x, x); 296 // If we cannot delete it, we try to store an empty value. That's not nice, but at least a consistent DB state. 297 encryptionHandler.encryptIndexEntry(cryptoContext, indexEntry, indexValue); 298 // Make sure it is persisted or we get a new exception (because we just tried to delete it). 299 pmIndex.makePersistent(indexEntry); 300 pmIndex.flush(); 301 Query query = pmIndex.newQuery(IndexEntry.class); 302 query.setFilter("JDOHelper.getObjectId(this) == :indexEntryID"); 303 query.setUnique(true); 304 IndexEntry persistentIndexEntry = (IndexEntry) query.execute(JDOHelper.getObjectId(indexEntry)); 305 if (persistentIndexEntry == null) 306 throw new IllegalStateException("Newly persisted IndexEntry did not end up in our database! indexEntryID=" + indexEntry.getIndexEntryID()); 307 } 308 309 // BEGIN workaround - this works, but is not nice. 310 // Object indexEntryID = JDOHelper.getObjectId(indexEntry); 311 // if (indexEntryID != null) { 312 // PersistenceManager pmIdx = cryptoContext.getPersistenceManagerForIndex(); 313 // pmIdx.flush(); 314 // Query query = pmIdx.newQuery(IndexEntry.class); 315 // query.setFilter("JDOHelper.getObjectId(this) == :indexEntryID"); 316 // query.setUnique(true); 317 // IndexEntry persistentIndexEntry = (IndexEntry) query.execute(indexEntryID); 318 // if (persistentIndexEntry != null) 319 // pmIdx.deletePersistent(persistentIndexEntry); 320 // } 321 // END workaround 322 } 323 else 324 encryptionHandler.encryptIndexEntry(cryptoContext, indexEntry, indexValue); 325 } 326 } 327 }