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.ArrayList; 021 import java.util.Collection; 022 import java.util.Collections; 023 import java.util.HashMap; 024 import java.util.HashSet; 025 import java.util.Map; 026 import java.util.Properties; 027 import java.util.Set; 028 import java.util.WeakHashMap; 029 030 import javax.jdo.FetchPlan; 031 import javax.jdo.PersistenceManager; 032 033 import org.cumulus4j.store.model.ClassMeta; 034 import org.cumulus4j.store.model.DataEntry; 035 import org.cumulus4j.store.model.FieldMeta; 036 import org.cumulus4j.store.model.FieldMetaRole; 037 import org.cumulus4j.store.model.IndexEntryFactoryRegistry; 038 import org.datanucleus.ClassLoaderResolver; 039 import org.datanucleus.NucleusContext; 040 import org.datanucleus.api.jdo.JDOPersistenceManagerFactory; 041 import org.datanucleus.identity.OID; 042 import org.datanucleus.identity.SCOID; 043 import org.datanucleus.metadata.AbstractClassMetaData; 044 import org.datanucleus.metadata.AbstractMemberMetaData; 045 import org.datanucleus.store.AbstractStoreManager; 046 import org.datanucleus.store.ExecutionContext; 047 import org.datanucleus.store.connection.ManagedConnection; 048 import org.datanucleus.store.schema.SchemaAwareStoreManager; 049 import org.slf4j.Logger; 050 import org.slf4j.LoggerFactory; 051 052 /** 053 * Store Manager for Cumulus4J operation. 054 * This StoreManager handles a backend StoreManager for the persistence to the chosen datastore, and optionally 055 * a second backend StoreManager for the persistence of index data to the chosen index datastore. 056 * The user will persist objects of their own classes, and these will be translated into the persistence of 057 * DataEntry, ClassMeta, FieldMeta for the data, as well as various IndexXXX types. 058 * 059 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de 060 */ 061 public class Cumulus4jStoreManager extends AbstractStoreManager implements SchemaAwareStoreManager 062 { 063 private static final Logger logger = LoggerFactory.getLogger(Cumulus4jStoreManager.class); 064 065 /** Extension key for marking field as not queryable */ 066 public static final String CUMULUS4J_QUERYABLE = "cumulus4j-queryable"; 067 068 // private static final SequenceMetaData SEQUENCE_META_DATA_DATA_ENTRY; 069 // static { 070 // SEQUENCE_META_DATA_DATA_ENTRY = new SequenceMetaData(DataEntry.class.getName(), SequenceStrategy.NONTRANSACTIONAL.toString()); 071 // SEQUENCE_META_DATA_DATA_ENTRY.setAllocationSize(100); 072 // SEQUENCE_META_DATA_DATA_ENTRY.setDatastoreSequence(DataEntry.class.getName()); 073 // } 074 075 private Map<Class<?>, ClassMeta> class2classMeta = Collections.synchronizedMap(new HashMap<Class<?>, ClassMeta>()); 076 077 /** 078 * For every class, we keep a set of all known sub-classes (all inheritance-levels down). Note, that the class in 079 * the map-key is contained in the Set (in the map-value). 080 */ 081 private Map<Class<?>, Set<Class<?>>> class2subclasses = Collections.synchronizedMap(new HashMap<Class<?>, Set<Class<?>>>()); 082 083 private EncryptionHandler encryptionHandler; 084 private EncryptionCoordinateSetManager encryptionCoordinateSetManager; 085 086 private IndexEntryFactoryRegistry indexFactoryRegistry; 087 088 public Cumulus4jStoreManager(ClassLoaderResolver clr, NucleusContext nucleusContext, Map<String, Object> props) 089 { 090 super("cumulus4j", clr, nucleusContext, props); 091 092 logger.info("====================== Cumulus4j ======================"); 093 String bundleName = "org.cumulus4j.store"; 094 String version = nucleusContext.getPluginManager().getVersionForBundle(bundleName); 095 logger.info("Bundle: " + bundleName + " - Version: " + version); 096 logger.info("======================================================="); 097 098 indexFactoryRegistry = new IndexEntryFactoryRegistry(this); 099 encryptionHandler = new EncryptionHandler(); 100 encryptionCoordinateSetManager = new EncryptionCoordinateSetManager(); 101 persistenceHandler = new Cumulus4jPersistenceHandler(this); 102 } 103 104 public EncryptionHandler getEncryptionHandler() { 105 return encryptionHandler; 106 } 107 108 public EncryptionCoordinateSetManager getEncryptionCoordinateSetManager() { 109 return encryptionCoordinateSetManager; 110 } 111 112 public IndexEntryFactoryRegistry getIndexFactoryRegistry() { 113 return indexFactoryRegistry; 114 } 115 116 /** 117 * Get the persistent meta-data of a certain class. This persistent meta-data is primarily used for efficient 118 * mapping using long-identifiers instead of fully qualified class names. 119 * 120 * @param ec 121 * @param clazz the {@link Class} for which to query the meta-data. 122 * @return the meta-data. Never returns <code>null</code>. 123 */ 124 public ClassMeta getClassMeta(ExecutionContext ec, Class<?> clazz) 125 { 126 ClassMeta result = class2classMeta.get(clazz); 127 if (result != null) { 128 return result; 129 } 130 131 ManagedConnection mconn = this.getConnection(ec); 132 try { 133 PersistenceManagerConnection pmConn = (PersistenceManagerConnection)mconn.getConnection(); 134 PersistenceManager pm = pmConn.getDataPM(); 135 136 synchronized (this) { // Synchronise in case we have data and index backends 137 // Register the class 138 pm.getFetchPlan().setGroup(FetchPlan.ALL); 139 result = registerClass(ec, pm, clazz); 140 141 // Detach the class in order to cache only detached objects. Make sure fetch-plan detaches all 142 pm.getFetchPlan().setGroup(FetchPlan.ALL); 143 pm.getFetchPlan().setMaxFetchDepth(-1); 144 result = pm.detachCopy(result); 145 146 if (pmConn.indexHasOwnPM()) { 147 // Replicate ClassMeta+FieldMeta to Index datastore 148 PersistenceManager pmIndex = pmConn.getIndexPM(); 149 pmIndex.getFetchPlan().setGroup(FetchPlan.ALL); 150 pmIndex.getFetchPlan().setMaxFetchDepth(-1); 151 pmIndex.makePersistent(result); 152 } 153 } 154 155 class2classMeta.put(clazz, result); 156 157 // register in class2subclasses-map 158 Set<Class<?>> currentSubclasses = new HashSet<Class<?>>(); 159 Class<?> c = clazz; 160 ClassMeta cm = result; 161 while (cm != null) { 162 currentSubclasses.add(c); 163 164 Set<Class<?>> subclasses; 165 synchronized (class2subclasses) { 166 subclasses = class2subclasses.get(c); 167 if (subclasses == null) { 168 subclasses = Collections.synchronizedSet(new HashSet<Class<?>>()); 169 class2subclasses.put(c, subclasses); 170 } 171 } 172 173 subclasses.addAll(currentSubclasses); 174 175 c = c.getSuperclass(); 176 cm = cm.getSuperClassMeta(); 177 if (cm != null) { 178 if (c == null) 179 throw new IllegalStateException("c == null && cm.className == " + cm.getClassName()); 180 181 if (!cm.getClassName().equals(c.getName())) 182 throw new IllegalStateException("cm.className != c.name :: cm.className=" + cm.getClassName() + " c.name=" + c.getName()); 183 184 // Store the super-class-meta-data for optimisation reasons (not necessary, but [hopefully] better). 185 class2classMeta.put(c, cm); 186 } 187 } 188 } finally { 189 mconn.release(); 190 } 191 192 return result; 193 } 194 195 private ClassMeta registerClass(ExecutionContext ec, PersistenceManager pm, Class<?> clazz) 196 { 197 AbstractClassMetaData dnClassMetaData = getMetaDataManager().getMetaDataForClass(clazz, ec.getClassLoaderResolver()); 198 if (dnClassMetaData == null) 199 throw new IllegalArgumentException("The class " + clazz.getName() + " does not have persistence-meta-data! Is it persistence-capable? Is it enhanced?"); 200 201 ClassMeta classMeta = ClassMeta.getClassMeta(pm, clazz, false); 202 boolean classExists = (classMeta != null); 203 if (!classExists) { 204 classMeta = new ClassMeta(clazz); 205 } 206 207 Class<?> superclass = clazz.getSuperclass(); 208 if (superclass != null && getMetaDataManager().hasMetaDataForClass(superclass.getName())) { 209 ClassMeta superClassMeta = registerClass(ec, pm, superclass); 210 classMeta.setSuperClassMeta(superClassMeta); 211 } 212 213 Set<String> persistentMemberNames = new HashSet<String>(); 214 for (AbstractMemberMetaData memberMetaData : dnClassMetaData.getManagedMembers()) { 215 if (!memberMetaData.isFieldToBePersisted()) 216 continue; 217 218 persistentMemberNames.add(memberMetaData.getName()); 219 int dnAbsoluteFieldNumber = memberMetaData.getAbsoluteFieldNumber(); 220 221 // register primary field-meta 222 FieldMeta primaryFieldMeta = classMeta.getFieldMeta(memberMetaData.getName()); 223 if (primaryFieldMeta == null) { 224 // adding field that's so far unknown 225 primaryFieldMeta = new FieldMeta(classMeta, memberMetaData.getName()); 226 classMeta.addFieldMeta(primaryFieldMeta); 227 } 228 primaryFieldMeta.setDataNucleusAbsoluteFieldNumber(dnAbsoluteFieldNumber); 229 230 if (memberMetaData.hasCollection()) { 231 // register "collection" field-meta, if appropriate 232 primaryFieldMeta.removeAllSubFieldMetasExcept(FieldMetaRole.collectionElement); 233 FieldMeta subFieldMeta = primaryFieldMeta.getSubFieldMeta(FieldMetaRole.collectionElement); 234 if (subFieldMeta == null) { 235 // adding field that's so far unknown 236 subFieldMeta = new FieldMeta(primaryFieldMeta, FieldMetaRole.collectionElement); 237 primaryFieldMeta.addSubFieldMeta(subFieldMeta); 238 } 239 } 240 else if (memberMetaData.hasArray()) { 241 // register "array" field-meta, if appropriate 242 // TODO shouldn't we handle it exactly as a collection, including reusing 'FieldMetaRole.collectionElement' for this case? 243 primaryFieldMeta.removeAllSubFieldMetasExcept(FieldMetaRole.arrayElement); 244 FieldMeta subFieldMeta = primaryFieldMeta.getSubFieldMeta(FieldMetaRole.arrayElement); 245 if (subFieldMeta == null) { 246 // adding field that's so far unknown 247 subFieldMeta = new FieldMeta(primaryFieldMeta, FieldMetaRole.arrayElement); 248 primaryFieldMeta.addSubFieldMeta(subFieldMeta); 249 } 250 } 251 else if (memberMetaData.hasMap()) { 252 // register "map" field-meta, if appropriate 253 primaryFieldMeta.removeAllSubFieldMetasExcept(FieldMetaRole.mapKey, FieldMetaRole.mapValue); 254 255 FieldMeta subFieldMeta = primaryFieldMeta.getSubFieldMeta(FieldMetaRole.mapKey); 256 if (subFieldMeta == null) { 257 // adding field that's so far unknown 258 subFieldMeta = new FieldMeta(primaryFieldMeta, FieldMetaRole.mapKey); 259 primaryFieldMeta.addSubFieldMeta(subFieldMeta); 260 } 261 262 subFieldMeta = primaryFieldMeta.getSubFieldMeta(FieldMetaRole.mapValue); 263 if (subFieldMeta == null) { 264 // adding field that's so far unknown 265 subFieldMeta = new FieldMeta(primaryFieldMeta, FieldMetaRole.mapValue); 266 primaryFieldMeta.addSubFieldMeta(subFieldMeta); 267 } 268 } 269 else 270 primaryFieldMeta.removeAllSubFieldMetasExcept(); 271 } 272 273 for (FieldMeta fieldMeta : new ArrayList<FieldMeta>(classMeta.getFieldMetas())) { 274 if (persistentMemberNames.contains(fieldMeta.getFieldName())) 275 continue; 276 277 // The field is not in the class anymore => remove its persistent reference. 278 classMeta.removeFieldMeta(fieldMeta); 279 } 280 281 if (!classExists) { 282 // Persist the new class and its fields in one call, minimising updates 283 pm.makePersistent(classMeta); 284 } 285 pm.flush(); // Get exceptions as soon as possible by forcing a flush here 286 287 return classMeta; 288 } 289 290 private Map<Object, String> objectID2className = Collections.synchronizedMap(new WeakHashMap<Object, String>()); 291 292 /** 293 * Store the association between an objectID and the class-name of the corresponding persistable object in 294 * a {@link WeakHashMap}. This is used for performance optimization of 295 * {@link #getClassNameForObjectID(Object, ClassLoaderResolver, ExecutionContext)}. 296 */ 297 public void setClassNameForObjectID(Object id, String className) 298 { 299 objectID2className.put(id, className); 300 } 301 302 @Override 303 public String getClassNameForObjectID(Object id, ClassLoaderResolver clr, ExecutionContext ec) 304 { 305 if (id == null) { 306 return null; 307 } 308 309 String className = objectID2className.get(id); 310 if (className != null) { 311 return className; 312 } 313 314 if (id instanceof SCOID) { 315 // Object is a SCOID 316 className = ((SCOID) id).getSCOClass(); 317 } 318 else if (id instanceof OID) { 319 // Object is an OID 320 className = ((OID)id).getPcClass(); 321 } 322 else if (getApiAdapter().isSingleFieldIdentity(id)) { 323 // Using SingleFieldIdentity so can assume that object is of the target class 324 className = getApiAdapter().getTargetClassNameForSingleFieldIdentity(id); 325 } 326 else { 327 // Application identity with user PK class, so find all using this PK 328 Collection<AbstractClassMetaData> cmds = getMetaDataManager().getClassMetaDataWithApplicationId(id.getClass().getName()); 329 if (cmds != null) { 330 if (cmds.size() == 1) { 331 className = cmds.iterator().next().getFullClassName(); 332 } 333 else { 334 ManagedConnection mconn = this.getConnection(ec); 335 try { 336 PersistenceManagerConnection pmConn = (PersistenceManagerConnection)mconn.getConnection(); 337 PersistenceManager pmData = pmConn.getDataPM(); 338 String objectIDString = id.toString(); 339 for (AbstractClassMetaData cmd : cmds) { 340 Class<?> clazz = clr.classForName(cmd.getFullClassName()); 341 ClassMeta classMeta = getClassMeta(ec, clazz); 342 DataEntry dataEntry = DataEntry.getDataEntry(pmData, classMeta, objectIDString); 343 if (dataEntry != null) { 344 className = cmd.getFullClassName(); 345 } 346 } 347 } finally { 348 mconn.release(); 349 } 350 } 351 } 352 } 353 354 if (className != null) { 355 objectID2className.put(id, className); 356 } 357 358 return className; 359 } 360 361 // public long nextDataEntryID(ExecutionContext executionContext) 362 // { 363 // NucleusSequence nucleusSequence = getNucleusSequence(executionContext, SEQUENCE_META_DATA_DATA_ENTRY); 364 // return nucleusSequence.nextValue(); 365 // } 366 367 @Override 368 protected String getStrategyForNative(AbstractClassMetaData cmd, int absFieldNumber) { 369 return "increment"; 370 // AbstractMemberMetaData mmd = cmd.getMetaDataForManagedMemberAtAbsolutePosition(absFieldNumber); 371 // if (String.class.isAssignableFrom(mmd.getType()) || UUID.class.isAssignableFrom(mmd.getType())) 372 // return "uuid-hex"; 373 // else 374 // return "increment"; 375 } 376 377 @Override 378 public void createSchema(Set<String> classNames, Properties props) { 379 Cumulus4jConnectionFactory cf = 380 (Cumulus4jConnectionFactory) connectionMgr.lookupConnectionFactory(txConnectionFactoryName); 381 JDOPersistenceManagerFactory pmfData = (JDOPersistenceManagerFactory) cf.getPMFData(); 382 JDOPersistenceManagerFactory pmfIndex = (JDOPersistenceManagerFactory) cf.getPMFIndex(); 383 if (pmfData.getNucleusContext().getStoreManager() instanceof SchemaAwareStoreManager) { 384 // Create Cumulus4J "Data" (plus "Index" if not separate) schema 385 SchemaAwareStoreManager schemaMgr = (SchemaAwareStoreManager) pmfData.getNucleusContext().getStoreManager(); 386 Set<String> cumulus4jClassNames = new HashSet<String>(); 387 Collection<Class> pmfClasses = pmfData.getManagedClasses(); 388 for (Class cls : pmfClasses) { 389 cumulus4jClassNames.add(cls.getName()); 390 } 391 schemaMgr.createSchema(cumulus4jClassNames, new Properties()); 392 } 393 if (pmfIndex != null && pmfIndex.getNucleusContext().getStoreManager() instanceof SchemaAwareStoreManager) { 394 // Create Cumulus4J "Index" schema 395 SchemaAwareStoreManager schemaMgr = (SchemaAwareStoreManager) pmfIndex.getNucleusContext().getStoreManager(); 396 Set<String> cumulus4jClassNames = new HashSet<String>(); 397 Collection<Class> pmfClasses = pmfIndex.getManagedClasses(); 398 for (Class cls : pmfClasses) { 399 cumulus4jClassNames.add(cls.getName()); 400 } 401 schemaMgr.createSchema(cumulus4jClassNames, new Properties()); 402 } 403 } 404 405 @Override 406 public void deleteSchema(Set<String> classNames, Properties props) { 407 Cumulus4jConnectionFactory cf = 408 (Cumulus4jConnectionFactory) connectionMgr.lookupConnectionFactory(txConnectionFactoryName); 409 JDOPersistenceManagerFactory pmfData = (JDOPersistenceManagerFactory) cf.getPMFData(); 410 JDOPersistenceManagerFactory pmfIndex = (JDOPersistenceManagerFactory) cf.getPMFIndex(); 411 if (pmfData.getNucleusContext().getStoreManager() instanceof SchemaAwareStoreManager) { 412 SchemaAwareStoreManager schemaMgr = (SchemaAwareStoreManager) pmfData.getNucleusContext().getStoreManager(); 413 Set<String> cumulus4jClassNames = new HashSet<String>(); 414 Collection<Class> pmfClasses = pmfData.getManagedClasses(); 415 for (Class cls : pmfClasses) { 416 cumulus4jClassNames.add(cls.getName()); 417 } 418 schemaMgr.deleteSchema(cumulus4jClassNames, new Properties()); 419 } 420 if (pmfIndex != null && pmfIndex.getNucleusContext().getStoreManager() instanceof SchemaAwareStoreManager) { 421 SchemaAwareStoreManager schemaMgr = (SchemaAwareStoreManager) pmfIndex.getNucleusContext().getStoreManager(); 422 Set<String> cumulus4jClassNames = new HashSet<String>(); 423 Collection<Class> pmfClasses = pmfIndex.getManagedClasses(); 424 for (Class cls : pmfClasses) { 425 cumulus4jClassNames.add(cls.getName()); 426 } 427 schemaMgr.deleteSchema(cumulus4jClassNames, new Properties()); 428 } 429 } 430 431 @Override 432 public void validateSchema(Set<String> classNames, Properties props) { 433 Cumulus4jConnectionFactory cf = 434 (Cumulus4jConnectionFactory) connectionMgr.lookupConnectionFactory(txConnectionFactoryName); 435 JDOPersistenceManagerFactory pmfData = (JDOPersistenceManagerFactory) cf.getPMFData(); 436 JDOPersistenceManagerFactory pmfIndex = (JDOPersistenceManagerFactory) cf.getPMFIndex(); 437 if (pmfData.getNucleusContext().getStoreManager() instanceof SchemaAwareStoreManager) { 438 SchemaAwareStoreManager schemaMgr = (SchemaAwareStoreManager) pmfData.getNucleusContext().getStoreManager(); 439 Set<String> cumulus4jClassNames = new HashSet<String>(); 440 Collection<Class> pmfClasses = pmfData.getManagedClasses(); 441 for (Class cls : pmfClasses) { 442 cumulus4jClassNames.add(cls.getName()); 443 } 444 schemaMgr.validateSchema(cumulus4jClassNames, new Properties()); 445 } 446 if (pmfIndex != null && pmfIndex.getNucleusContext().getStoreManager() instanceof SchemaAwareStoreManager) { 447 SchemaAwareStoreManager schemaMgr = (SchemaAwareStoreManager) pmfIndex.getNucleusContext().getStoreManager(); 448 Set<String> cumulus4jClassNames = new HashSet<String>(); 449 Collection<Class> pmfClasses = pmfIndex.getManagedClasses(); 450 for (Class cls : pmfClasses) { 451 cumulus4jClassNames.add(cls.getName()); 452 } 453 schemaMgr.validateSchema(cumulus4jClassNames, new Properties()); 454 } 455 } 456 }