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.List; 026 import java.util.Map; 027 import java.util.Properties; 028 import java.util.Set; 029 import java.util.WeakHashMap; 030 031 import javax.jdo.FetchPlan; 032 import javax.jdo.PersistenceManager; 033 034 import org.cumulus4j.store.crypto.CryptoContext; 035 import org.cumulus4j.store.datastoreversion.DatastoreVersionManager; 036 import org.cumulus4j.store.model.ClassMeta; 037 import org.cumulus4j.store.model.ClassMetaDAO; 038 import org.cumulus4j.store.model.DataEntry; 039 import org.cumulus4j.store.model.DataEntryDAO; 040 import org.cumulus4j.store.model.DetachedClassMetaModel; 041 import org.cumulus4j.store.model.EmbeddedClassMeta; 042 import org.cumulus4j.store.model.EmbeddedFieldMeta; 043 import org.cumulus4j.store.model.FetchGroupsMetaData; 044 import org.cumulus4j.store.model.FieldMeta; 045 import org.cumulus4j.store.model.FieldMetaDAO; 046 import org.cumulus4j.store.model.FieldMetaRole; 047 import org.cumulus4j.store.model.IndexEntryFactoryRegistry; 048 import org.cumulus4j.store.model.PostDetachRunnableManager; 049 import org.datanucleus.ClassLoaderResolver; 050 import org.datanucleus.ExecutionContext; 051 import org.datanucleus.NucleusContext; 052 import org.datanucleus.api.jdo.JDOPersistenceManagerFactory; 053 import org.datanucleus.identity.OID; 054 import org.datanucleus.identity.SCOID; 055 import org.datanucleus.metadata.AbstractClassMetaData; 056 import org.datanucleus.metadata.AbstractMemberMetaData; 057 import org.datanucleus.metadata.MapMetaData.MapType; 058 import org.datanucleus.state.ObjectProvider; 059 import org.datanucleus.store.AbstractStoreManager; 060 import org.datanucleus.store.Extent; 061 import org.datanucleus.store.connection.ManagedConnection; 062 import org.datanucleus.store.schema.SchemaAwareStoreManager; 063 import org.slf4j.Logger; 064 import org.slf4j.LoggerFactory; 065 066 /** 067 * Store Manager for Cumulus4J operation. 068 * This StoreManager handles a backend StoreManager for the persistence to the chosen datastore, and optionally 069 * a second backend StoreManager for the persistence of index data to the chosen index datastore. 070 * The user will persist objects of their own classes, and these will be translated into the persistence of 071 * DataEntry, ClassMeta, FieldMeta for the data, as well as various IndexXXX types. 072 * 073 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de 074 */ 075 public class Cumulus4jStoreManager extends AbstractStoreManager implements SchemaAwareStoreManager 076 { 077 private static final Logger logger = LoggerFactory.getLogger(Cumulus4jStoreManager.class); 078 079 /** Extension key for marking field as not queryable */ 080 public static final String CUMULUS4J_QUERYABLE = "cumulus4j-queryable"; 081 082 // private static final SequenceMetaData SEQUENCE_META_DATA_DATA_ENTRY; 083 // static { 084 // SEQUENCE_META_DATA_DATA_ENTRY = new SequenceMetaData(DataEntry.class.getName(), SequenceStrategy.NONTRANSACTIONAL.toString()); 085 // SEQUENCE_META_DATA_DATA_ENTRY.setAllocationSize(100); 086 // SEQUENCE_META_DATA_DATA_ENTRY.setDatastoreSequence(DataEntry.class.getName()); 087 // } 088 089 private Map<Class<?>, ClassMeta> class2classMeta = Collections.synchronizedMap(new HashMap<Class<?>, ClassMeta>()); 090 private Map<Long, ClassMeta> classID2classMeta = Collections.synchronizedMap(new HashMap<Long, ClassMeta>()); 091 private Map<Long, FieldMeta> fieldID2fieldMeta = Collections.synchronizedMap(new HashMap<Long, FieldMeta>()); 092 093 /** 094 * For every class, we keep a set of all known sub-classes (all inheritance-levels down). Note, that the class in 095 * the map-key is contained in the Set (in the map-value). 096 */ 097 private Map<Class<?>, Set<Class<?>>> class2subclasses = Collections.synchronizedMap(new HashMap<Class<?>, Set<Class<?>>>()); 098 099 private EncryptionHandler encryptionHandler; 100 private EncryptionCoordinateSetManager encryptionCoordinateSetManager; 101 private KeyStoreRefManager keyStoreRefManager; 102 private DatastoreVersionManager datastoreVersionManager = new DatastoreVersionManager(this); 103 104 private IndexEntryFactoryRegistry indexFactoryRegistry; 105 106 public Cumulus4jStoreManager(ClassLoaderResolver clr, NucleusContext nucleusContext, Map<String, Object> props) 107 { 108 super("cumulus4j", clr, nucleusContext, props); 109 110 logger.info("====================== Cumulus4j ======================"); 111 String bundleName = "org.cumulus4j.store"; 112 String version = nucleusContext.getPluginManager().getVersionForBundle(bundleName); 113 logger.info("Bundle: " + bundleName + " - Version: " + version); 114 logger.info("======================================================="); 115 116 encryptionHandler = new EncryptionHandler(); 117 encryptionCoordinateSetManager = new EncryptionCoordinateSetManager(); 118 keyStoreRefManager = new KeyStoreRefManager(); 119 persistenceHandler = new Cumulus4jPersistenceHandler(this); 120 } 121 122 public EncryptionHandler getEncryptionHandler() { 123 return encryptionHandler; 124 } 125 126 public EncryptionCoordinateSetManager getEncryptionCoordinateSetManager() { 127 return encryptionCoordinateSetManager; 128 } 129 130 public KeyStoreRefManager getKeyStoreRefManager() { 131 return keyStoreRefManager; 132 } 133 134 public IndexEntryFactoryRegistry getIndexFactoryRegistry() { 135 if (indexFactoryRegistry == null) 136 indexFactoryRegistry = new IndexEntryFactoryRegistry(this); 137 138 return indexFactoryRegistry; 139 } 140 141 public DatastoreVersionManager getDatastoreVersionManager() { 142 return datastoreVersionManager; 143 } 144 145 // private ThreadLocal<Set<Long>> fieldIDsCurrentlyLoading = new ThreadLocal<Set<Long>>() { 146 // @Override 147 // protected Set<Long> initialValue() { 148 // return new HashSet<Long>(); 149 // } 150 // }; 151 152 public FieldMeta getFieldMeta(ExecutionContext ec, long fieldID, boolean throwExceptionIfNotFound) { 153 if (ec == null) 154 throw new IllegalArgumentException("ec == null"); 155 156 if (fieldID < 0) 157 throw new IllegalArgumentException("fieldID < 0"); 158 159 // if (!fieldIDsCurrentlyLoading.get().add(fieldID)) { 160 // if (throwExceptionIfNotFound) 161 // throw new IllegalStateException("Circular loading! This is only allowed, if throwExceptionIfNotFound == false and results in null being returned."); 162 // 163 // return null; 164 // } 165 // try { 166 FieldMeta result = fieldID2fieldMeta.get(fieldID); 167 if (result != null) { 168 logger.trace("getFieldMetaByFieldID: found cache entry. fieldID={}", fieldID); 169 return result; 170 } 171 172 long beginLoadingTimestamp = System.currentTimeMillis(); 173 long classID; 174 ManagedConnection mconn = this.getConnection(ec); 175 try { 176 PersistenceManagerConnection pmConn = (PersistenceManagerConnection)mconn.getConnection(); 177 PersistenceManager pm = pmConn.getDataPM(); 178 FieldMetaDAO dao = new FieldMetaDAO(pm); 179 FieldMeta fieldMeta = dao.getFieldMeta(fieldID, throwExceptionIfNotFound); 180 if (fieldMeta == null) 181 return null; 182 183 classID = fieldMeta.getClassMeta().getClassID(); 184 } finally { 185 mconn.release(); mconn = null; 186 } 187 188 getClassMeta(ec, classID, true); 189 190 result = fieldID2fieldMeta.get(fieldID); 191 if (result == null) 192 throw new IllegalStateException("Even after loading the class " + classID + " , the field " + fieldID + " is still not cached!"); 193 194 logger.debug("getFieldMetaByFieldID: end loading (took {} ms). fieldID={}", System.currentTimeMillis() - beginLoadingTimestamp, fieldID); 195 return result; 196 // } finally { 197 // Set<Long> set = fieldIDsCurrentlyLoading.get(); 198 // set.remove(fieldID); 199 // if (set.isEmpty()) 200 // fieldIDsCurrentlyLoading.remove(); 201 // } 202 } 203 204 public ClassMeta getClassMeta(ExecutionContext ec, long classID, boolean throwExceptionIfNotFound) { 205 if (ec == null) 206 throw new IllegalArgumentException("ec == null"); 207 208 if (classID < 0) 209 throw new IllegalArgumentException("classID < 0"); 210 211 ClassMeta result = classID2classMeta.get(classID); 212 if (result != null) { 213 logger.trace("getClassMetaByClassID: found cache entry. classID={}", classID); 214 return result; 215 } 216 217 logger.debug("getClassMetaByClassID: begin loading. classID={}", classID); 218 long beginLoadingTimestamp = System.currentTimeMillis(); 219 String className; 220 ManagedConnection mconn = this.getConnection(ec); 221 try { 222 PersistenceManagerConnection pmConn = (PersistenceManagerConnection)mconn.getConnection(); 223 PersistenceManager pm = pmConn.getDataPM(); 224 CryptoContext cryptoContext = new CryptoContext(encryptionCoordinateSetManager, keyStoreRefManager, ec, pmConn); 225 datastoreVersionManager.applyOnce(cryptoContext); 226 227 ClassMetaDAO dao = new ClassMetaDAO(pm); 228 ClassMeta classMeta = dao.getClassMeta(classID, throwExceptionIfNotFound); 229 if (classMeta == null) 230 return null; 231 232 className = classMeta.getClassName(); 233 } finally { 234 mconn.release(); mconn = null; 235 } 236 237 Class<?> clazz = ec.getClassLoaderResolver().classForName(className, true); 238 239 result = getClassMeta(ec, clazz); 240 241 // This is not necessarily the right result, because getClassMeta(ec, clazz) NEVER returns an EmbeddedClassMeta 242 // and the classID might belong to an embeddedClassMeta. 243 if (result.getClassID() != classID) { 244 result = null; 245 246 // DetachedClassMetaModel.setInstance(new DetachedClassMetaModel() { 247 // @Override 248 // public ClassMeta getClassMeta(long classID, boolean throwExceptionIfNotFound) { 249 // ClassMeta result = classID2classMeta.get(classID); 250 // if (result == null && throwExceptionIfNotFound) 251 // throw new IllegalArgumentException("No ClassMeta found for classID=" + classID); 252 // 253 // return result; 254 // } 255 // }); 256 // try { 257 mconn = this.getConnection(ec); 258 try { 259 PersistenceManagerConnection pmConn = (PersistenceManagerConnection)mconn.getConnection(); 260 PersistenceManager pm = pmConn.getDataPM(); 261 ClassMetaDAO dao = new ClassMetaDAO(pm); 262 ClassMeta classMeta = dao.getClassMeta(classID, throwExceptionIfNotFound); 263 result = detachClassMeta(ec, pm, classMeta); 264 } finally { 265 mconn.release(); mconn = null; 266 } 267 // } finally { 268 // DetachedClassMetaModel.setInstance(null); 269 // } 270 } 271 logger.debug("getClassMetaByClassID: end loading (took {} ms). classID={}", System.currentTimeMillis() - beginLoadingTimestamp, classID); 272 273 putClassMetaIntoCache(result); 274 return result; 275 } 276 277 protected void putClassMetaIntoCache(ClassMeta classMeta) { 278 if (classMeta == null) 279 return; 280 281 classID2classMeta.put(classMeta.getClassID(), classMeta); 282 putFieldMetasIntoCache(classMeta); 283 } 284 285 protected void putFieldMetasIntoCache(ClassMeta classMeta) { 286 if (classMeta == null) 287 return; 288 289 putFieldMetasIntoCache(classMeta.getFieldMetas()); 290 } 291 292 protected void putFieldMetasIntoCache(Collection<FieldMeta> fieldMetas) { 293 if (fieldMetas == null) 294 return; 295 296 for (FieldMeta fieldMeta : fieldMetas) { 297 if (fieldID2fieldMeta.put(fieldMeta.getFieldID(), fieldMeta) != null) 298 continue; // already added before => no recursion 299 300 putFieldMetasIntoCache(fieldMeta.getEmbeddedClassMeta()); 301 putFieldMetasIntoCache(fieldMeta.getSubFieldMetas()); 302 } 303 } 304 305 protected ClassMeta detachClassMeta(final ExecutionContext ec, PersistenceManager pm, ClassMeta classMeta) { 306 boolean clearDetachedClassMetaModel = false; 307 if (DetachedClassMetaModel.getInstance() == null) { 308 clearDetachedClassMetaModel = true; 309 DetachedClassMetaModel.setInstance(new DetachedClassMetaModel() { 310 private Set<Long> pendingClassIDs = new HashSet<Long>(); 311 private Set<Long> pendingFieldIDs = new HashSet<Long>(); 312 313 @Override 314 protected ClassMeta getClassMetaImpl(long classID, boolean throwExceptionIfNotFound) { 315 if (!pendingClassIDs.add(classID)) { 316 throw new IllegalStateException("Circular detachment of classID=" + classID); 317 } 318 try { 319 ClassMeta result = Cumulus4jStoreManager.this.getClassMeta(ec, classID, throwExceptionIfNotFound); 320 return result; 321 } finally { 322 pendingClassIDs.remove(classID); 323 } 324 } 325 @Override 326 protected FieldMeta getFieldMetaImpl(long fieldID, boolean throwExceptionIfNotFound) { 327 if (!pendingFieldIDs.add(fieldID)) { 328 throw new IllegalStateException("Circular detachment of fieldID=" + fieldID); 329 } 330 try { 331 FieldMeta result = Cumulus4jStoreManager.this.getFieldMeta(ec, fieldID, throwExceptionIfNotFound); 332 return result; 333 } finally { 334 pendingFieldIDs.remove(fieldID); 335 } 336 } 337 }); 338 } 339 try { 340 ClassMeta result; 341 pm.flush(); 342 pm.evictAll(); 343 pm.getFetchPlan().setGroups(FetchPlan.ALL, FetchGroupsMetaData.ALL); 344 pm.getFetchPlan().setMaxFetchDepth(-1); 345 final PostDetachRunnableManager postDetachRunnableManager = PostDetachRunnableManager.getInstance(); 346 postDetachRunnableManager.enterScope(); 347 try { 348 result = pm.detachCopy(classMeta); 349 } finally { 350 postDetachRunnableManager.exitScope(); 351 } 352 return result; 353 } finally { 354 if (clearDetachedClassMetaModel) 355 DetachedClassMetaModel.setInstance(null); 356 } 357 } 358 359 public List<ClassMeta> getClassMetaWithSubClassMetas(ExecutionContext ec, ClassMeta classMeta) { 360 final List<ClassMeta> result = getSubClassMetas(ec, classMeta, true); 361 // result.add(0, classMeta); 362 result.add(classMeta); // I think, the order does not matter ;-) 363 return result; 364 } 365 366 public List<ClassMeta> getSubClassMetas(ExecutionContext ec, ClassMeta classMeta, boolean includeDescendents) { 367 return getSubClassMetas(ec, classMeta.getClassName(), includeDescendents); 368 } 369 370 public List<ClassMeta> getSubClassMetas(ExecutionContext ec, Class<?> clazz, boolean includeDescendents) { 371 return getSubClassMetas(ec, clazz.getName(), includeDescendents); 372 } 373 374 public List<ClassMeta> getSubClassMetas(ExecutionContext ec, String className, boolean includeDescendents) { 375 ClassLoaderResolver clr = ec.getClassLoaderResolver(); 376 Collection<String> subClassesForClass = getSubClassesForClass(className, includeDescendents, clr); 377 List<ClassMeta> result = new ArrayList<ClassMeta>(subClassesForClass.size()); 378 for (String subClassName : subClassesForClass) { 379 Class<?> subClass = clr.classForName(subClassName); 380 ClassMeta subClassMeta = getClassMeta(ec, subClass); 381 result.add(subClassMeta); 382 } 383 return result; 384 } 385 386 /** 387 * Get the persistent meta-data of a certain class. This persistent meta-data is primarily used for efficient 388 * mapping using long-identifiers instead of fully qualified class names. 389 * 390 * @param ec 391 * @param clazz the {@link Class} for which to query the meta-data. Must not be <code>null</code>. 392 * @return the meta-data. Never returns <code>null</code>. 393 */ 394 public ClassMeta getClassMeta(ExecutionContext ec, Class<?> clazz) 395 { 396 if (clazz == null) 397 throw new IllegalArgumentException("clazz == null"); 398 399 ClassMeta result = class2classMeta.get(clazz); 400 if (result != null) { 401 logger.trace("getClassMetaByClass: found cache entry. class={}", clazz.getName()); 402 return result; 403 } 404 405 logger.debug("getClassMetaByClass: begin loading. class={}", clazz.getName()); 406 long beginLoadingTimestamp = System.currentTimeMillis(); 407 ManagedConnection mconn = this.getConnection(ec); 408 try { 409 PersistenceManagerConnection pmConn = (PersistenceManagerConnection)mconn.getConnection(); 410 PersistenceManager pm = pmConn.getDataPM(); 411 412 CryptoContext cryptoContext = new CryptoContext(encryptionCoordinateSetManager, keyStoreRefManager, ec, pmConn); 413 datastoreVersionManager.applyOnce(cryptoContext); 414 415 synchronized (this) { // Synchronise in case we have data and index backends // why? what about multiple instances? shouldn't the replication be safe? is this just for lower chance of exceptions (causing a rollback and being harmless)? 416 // Register the class 417 pm.getFetchPlan().setGroups(FetchPlan.ALL, FetchGroupsMetaData.ALL); 418 result = registerClass(ec, pm, clazz); 419 420 // Detach the class in order to cache only detached objects. Make sure fetch-plan detaches all 421 result = detachClassMeta(ec, pm, result); 422 423 if (pmConn.indexHasOwnPM()) { 424 // Replicate ClassMeta+FieldMeta to Index datastore 425 PersistenceManager pmIndex = pmConn.getIndexPM(); 426 pm.getFetchPlan().setGroups(FetchPlan.ALL, FetchGroupsMetaData.ALL); // not sure, if this is necessary before persisting, but don't have time to find it out - leaving it. 427 pmIndex.getFetchPlan().setMaxFetchDepth(-1); // not sure, if this is necessary before persisting, but don't have time to find it out - leaving it. 428 result = pmIndex.makePersistent(result); 429 result = detachClassMeta(ec, pmIndex, result); 430 } 431 } 432 433 class2classMeta.put(clazz, result); 434 putClassMetaIntoCache(result); 435 436 // register in class2subclasses-map 437 Set<Class<?>> currentSubclasses = new HashSet<Class<?>>(); 438 Class<?> c = clazz; 439 ClassMeta cm = result; 440 while (cm != null) { 441 currentSubclasses.add(c); 442 443 Set<Class<?>> subclasses; 444 synchronized (class2subclasses) { 445 subclasses = class2subclasses.get(c); 446 if (subclasses == null) { 447 subclasses = Collections.synchronizedSet(new HashSet<Class<?>>()); 448 class2subclasses.put(c, subclasses); 449 } 450 } 451 452 subclasses.addAll(currentSubclasses); 453 454 c = c.getSuperclass(); 455 cm = cm.getSuperClassMeta(); 456 if (cm != null) { 457 if (c == null) 458 throw new IllegalStateException("c == null && cm.className == " + cm.getClassName()); 459 460 if (!cm.getClassName().equals(c.getName())) 461 throw new IllegalStateException("cm.className != c.name :: cm.className=" + cm.getClassName() + " c.name=" + c.getName()); 462 463 // Store the super-class-meta-data for optimisation reasons (not necessary, but [hopefully] better). 464 class2classMeta.put(c, cm); 465 putClassMetaIntoCache(result); 466 } 467 } 468 } finally { 469 mconn.release(); 470 } 471 logger.debug("getClassMetaByClass: end loading (took {} ms). class={}", System.currentTimeMillis() - beginLoadingTimestamp, clazz.getName()); 472 473 return result; 474 } 475 476 public ClassMeta getAttachedClassMeta(ExecutionContext ec, PersistenceManager pm, Class<?> clazz) 477 { 478 ClassMeta classMeta = new ClassMetaDAO(pm).getClassMeta(clazz, false); 479 if (classMeta == null) { 480 classMeta = registerClass(ec, pm, clazz); 481 } 482 return classMeta; 483 } 484 485 private ClassMeta registerClass(ExecutionContext ec, PersistenceManager pm, Class<?> clazz) 486 { 487 logger.debug("registerClass: clazz={}", clazz == null ? null : clazz.getName()); 488 AbstractClassMetaData dnClassMetaData = getMetaDataManager().getMetaDataForClass(clazz, ec.getClassLoaderResolver()); 489 if (dnClassMetaData == null) 490 throw new IllegalArgumentException("The class " + clazz.getName() + " does not have persistence-meta-data! Is it persistence-capable? Is it enhanced?"); 491 492 ClassMeta classMeta = new ClassMetaDAO(pm).getClassMeta(clazz, false); 493 494 List<FieldMeta> primaryFieldMetas = new ArrayList<FieldMeta>(); 495 // final PostStoreRunnableManager postStoreRunnableManager = PostStoreRunnableManager.getInstance(); 496 // postStoreRunnableManager.enterScope(); 497 // try { 498 499 if (classMeta == null) { 500 // We need to find this class already, because embedded-handling might be recursive. 501 // Additionally, we have our IDs immediately this way and can store long-field-references 502 // without any problem. 503 classMeta = pm.makePersistent(new ClassMeta(clazz)); 504 } 505 506 Class<?> superclass = clazz.getSuperclass(); 507 if (superclass != null && getMetaDataManager().hasMetaDataForClass(superclass.getName())) { 508 ClassMeta superClassMeta = registerClass(ec, pm, superclass); 509 classMeta.setSuperClassMeta(superClassMeta); 510 } 511 512 Set<String> persistentMemberNames = new HashSet<String>(); 513 for (AbstractMemberMetaData memberMetaData : dnClassMetaData.getManagedMembers()) { 514 if (!memberMetaData.isFieldToBePersisted()) 515 continue; 516 517 persistentMemberNames.add(memberMetaData.getName()); 518 int dnAbsoluteFieldNumber = memberMetaData.getAbsoluteFieldNumber(); 519 520 // register primary field-meta 521 FieldMeta primaryFieldMeta = classMeta.getFieldMeta(memberMetaData.getName()); 522 if (primaryFieldMeta == null) { 523 // adding field that's so far unknown 524 primaryFieldMeta = new FieldMeta(classMeta, memberMetaData.getName()); 525 classMeta.addFieldMeta(primaryFieldMeta); 526 } 527 primaryFieldMeta.setDataNucleusAbsoluteFieldNumber(dnAbsoluteFieldNumber); 528 529 if (memberMetaData.hasCollection()) { 530 // register "collection" field-meta, if appropriate 531 primaryFieldMeta.removeAllSubFieldMetasExcept(FieldMetaRole.collectionElement); 532 FieldMeta subFieldMeta = primaryFieldMeta.getSubFieldMeta(FieldMetaRole.collectionElement); 533 if (subFieldMeta == null) { 534 // adding field that's so far unknown 535 subFieldMeta = new FieldMeta(primaryFieldMeta, FieldMetaRole.collectionElement); 536 primaryFieldMeta.addSubFieldMeta(subFieldMeta); 537 } 538 // setEmbeddedClassMeta(ec, subFieldMeta); 539 } 540 else if (memberMetaData.hasArray()) { 541 // register "array" field-meta, if appropriate 542 // TODO shouldn't we handle it exactly as a collection, including reusing 'FieldMetaRole.collectionElement' for this case? 543 primaryFieldMeta.removeAllSubFieldMetasExcept(FieldMetaRole.arrayElement); 544 FieldMeta subFieldMeta = primaryFieldMeta.getSubFieldMeta(FieldMetaRole.arrayElement); 545 if (subFieldMeta == null) { 546 // adding field that's so far unknown 547 subFieldMeta = new FieldMeta(primaryFieldMeta, FieldMetaRole.arrayElement); 548 primaryFieldMeta.addSubFieldMeta(subFieldMeta); 549 } 550 // setEmbeddedClassMeta(ec, subFieldMeta); 551 } 552 else if (memberMetaData.hasMap()) { 553 // register "map" field-meta, if appropriate 554 primaryFieldMeta.removeAllSubFieldMetasExcept(FieldMetaRole.mapKey, FieldMetaRole.mapValue); 555 556 // key 557 FieldMeta subFieldMeta = primaryFieldMeta.getSubFieldMeta(FieldMetaRole.mapKey); 558 if (subFieldMeta == null) { 559 // adding field that's so far unknown 560 subFieldMeta = new FieldMeta(primaryFieldMeta, FieldMetaRole.mapKey); 561 primaryFieldMeta.addSubFieldMeta(subFieldMeta); 562 } 563 // setEmbeddedClassMeta(ec, subFieldMeta); 564 565 // value 566 subFieldMeta = primaryFieldMeta.getSubFieldMeta(FieldMetaRole.mapValue); 567 if (subFieldMeta == null) { 568 // adding field that's so far unknown 569 subFieldMeta = new FieldMeta(primaryFieldMeta, FieldMetaRole.mapValue); 570 primaryFieldMeta.addSubFieldMeta(subFieldMeta); 571 } 572 // setEmbeddedClassMeta(ec, subFieldMeta); 573 } 574 else { 575 primaryFieldMeta.removeAllSubFieldMetasExcept(); 576 } 577 // setEmbeddedClassMeta(ec, primaryFieldMeta); // defer due to possible recursion to this method! 578 primaryFieldMetas.add(primaryFieldMeta); 579 } 580 581 for (FieldMeta fieldMeta : new ArrayList<FieldMeta>(classMeta.getFieldMetas())) { 582 if (persistentMemberNames.contains(fieldMeta.getFieldName())) 583 continue; 584 585 // The field is not in the class anymore => remove its persistent reference. 586 classMeta.removeFieldMeta(fieldMeta); 587 } 588 589 pm.flush(); // Get exceptions as soon as possible by forcing a flush here 590 591 // } finally { 592 // postStoreRunnableManager.exitScope(); 593 // pm.flush(); // Get exceptions as soon as possible by forcing a flush here 594 // } 595 596 // postStoreRunnableManager.enterScope(); 597 // try { 598 for (FieldMeta primaryFieldMeta : primaryFieldMetas) { 599 setEmbeddedClassMeta(ec, primaryFieldMeta); 600 pm.flush(); // Get exceptions as soon as possible by forcing a flush here 601 } 602 // } finally { 603 // postStoreRunnableManager.exitScope(); 604 // pm.flush(); // Get exceptions as soon as possible by forcing a flush here 605 // } 606 607 return classMeta; 608 } 609 610 private boolean isEmbedded(AbstractMemberMetaData memberMetaData) { 611 return isEmbeddedOneToOne(memberMetaData) 612 || isEmbeddedArray(memberMetaData) 613 || isEmbeddedCollection(memberMetaData) 614 || isEmbeddedMap(memberMetaData); 615 } 616 617 private boolean isEmbeddedOneToOne(AbstractMemberMetaData memberMetaData) { 618 return memberMetaData.isEmbedded(); 619 } 620 621 private boolean isEmbeddedCollection(AbstractMemberMetaData memberMetaData) { 622 return memberMetaData.hasCollection() && memberMetaData.getCollection().isEmbeddedElement(); 623 } 624 625 private boolean isEmbeddedArray(AbstractMemberMetaData memberMetaData) { 626 return memberMetaData.hasArray() && memberMetaData.getArray().isEmbeddedElement(); 627 } 628 629 private boolean isEmbeddedMap(AbstractMemberMetaData memberMetaData) { 630 return memberMetaData.hasMap() 631 && MapType.MAP_TYPE_JOIN.equals(memberMetaData.getMap().getMapType()) 632 && (memberMetaData.getMap().isEmbeddedKey() || memberMetaData.getMap().isEmbeddedValue()); 633 } 634 635 private void setEmbeddedClassMeta(ExecutionContext ec, FieldMeta fieldMeta) { 636 AbstractMemberMetaData memberMetaData = fieldMeta.getDataNucleusMemberMetaData(ec); 637 if (isEmbedded(memberMetaData)) { 638 if (fieldMeta.getSubFieldMetas().isEmpty()) { 639 // only assign this to the leafs (map-key, map-value, collection-element, etc.) 640 // if we have no sub-field-metas, our fieldMeta is a leaf. 641 if (fieldMeta.getEmbeddedClassMeta() == null) { 642 ClassMeta fieldOrElementTypeClassMeta = fieldMeta.getFieldOrElementTypeClassMeta(ec); 643 if (fieldOrElementTypeClassMeta != null) { 644 fieldMeta.setEmbeddedClassMeta(new EmbeddedClassMeta(ec, fieldOrElementTypeClassMeta, fieldMeta)); 645 } 646 } 647 648 if (fieldMeta.getEmbeddedClassMeta() != null) 649 updateEmbeddedFieldMetas(ec, fieldMeta); 650 } 651 else { 652 fieldMeta.setEmbeddedClassMeta(null); 653 for (FieldMeta subFieldMeta : fieldMeta.getSubFieldMetas()) { 654 boolean subEmbedded = true; 655 if (memberMetaData.hasMap()) { 656 switch (subFieldMeta.getRole()) { 657 case mapKey: 658 subEmbedded = memberMetaData.getMap().isEmbeddedKey(); 659 break; 660 case mapValue: 661 subEmbedded = memberMetaData.getMap().isEmbeddedValue(); 662 break; 663 default: 664 throw new IllegalStateException("Unexpected subFieldMeta.role=" + subFieldMeta.getRole()); 665 } 666 } 667 if (subEmbedded) 668 setEmbeddedClassMeta(ec, subFieldMeta); 669 else 670 subFieldMeta.setEmbeddedClassMeta(null); 671 } 672 } 673 } 674 else { 675 fieldMeta.setEmbeddedClassMeta(null); 676 for (FieldMeta subFieldMeta : fieldMeta.getSubFieldMetas()) { 677 subFieldMeta.setEmbeddedClassMeta(null); 678 } 679 } 680 } 681 682 private void updateEmbeddedFieldMetas(ExecutionContext ec, FieldMeta embeddingFieldMeta) 683 { 684 EmbeddedClassMeta embeddedClassMeta = embeddingFieldMeta.getEmbeddedClassMeta(); 685 686 for (FieldMeta fieldMeta : embeddedClassMeta.getNonEmbeddedClassMeta().getFieldMetas()) { 687 EmbeddedFieldMeta embeddedFieldMeta = embeddedClassMeta.getEmbeddedFieldMetaForNonEmbeddedFieldMeta(fieldMeta); 688 if (embeddedFieldMeta == null) { 689 embeddedFieldMeta = new EmbeddedFieldMeta(embeddedClassMeta, null, fieldMeta); 690 embeddedClassMeta.addFieldMeta(embeddedFieldMeta); 691 } 692 setEmbeddedClassMeta(ec, embeddedFieldMeta); 693 updateEmbeddedFieldMetas_subFieldMetas(embeddedClassMeta, fieldMeta, embeddedFieldMeta); 694 } 695 } 696 697 private void updateEmbeddedFieldMetas_subFieldMetas(EmbeddedClassMeta embeddedClassMeta, FieldMeta fieldMeta, EmbeddedFieldMeta embeddedFieldMeta) { 698 for (FieldMeta subFieldMeta : fieldMeta.getSubFieldMetas()) { 699 EmbeddedFieldMeta subEmbeddedFieldMeta = embeddedClassMeta.getEmbeddedFieldMetaForNonEmbeddedFieldMeta(subFieldMeta); 700 if (subEmbeddedFieldMeta == null) { 701 subEmbeddedFieldMeta = new EmbeddedFieldMeta(embeddedClassMeta, embeddedFieldMeta, subFieldMeta); 702 embeddedFieldMeta.addSubFieldMeta(subEmbeddedFieldMeta); 703 } 704 updateEmbeddedFieldMetas_subFieldMetas(embeddedClassMeta, subFieldMeta, subEmbeddedFieldMeta); 705 } 706 } 707 708 private Map<Object, String> objectID2className = Collections.synchronizedMap(new WeakHashMap<Object, String>()); 709 710 /** 711 * Store the association between an objectID and the class-name of the corresponding persistable object in 712 * a {@link WeakHashMap}. This is used for performance optimization of 713 * {@link #getClassNameForObjectID(Object, ClassLoaderResolver, ExecutionContext)}. 714 */ 715 public void setClassNameForObjectID(Object id, String className) 716 { 717 objectID2className.put(id, className); 718 } 719 720 @Override 721 public String getClassNameForObjectID(Object id, ClassLoaderResolver clr, ExecutionContext ec) 722 { 723 if (id == null) { 724 return null; 725 } 726 727 String className = objectID2className.get(id); 728 if (className != null) { 729 return className; 730 } 731 732 if (id instanceof SCOID) { 733 // Object is a SCOID 734 className = ((SCOID) id).getSCOClass(); 735 } 736 else if (id instanceof OID) { 737 // Object is an OID 738 className = ((OID)id).getPcClass(); 739 } 740 else if (getApiAdapter().isSingleFieldIdentity(id)) { 741 // Using SingleFieldIdentity so can assume that object is of the target class 742 className = getApiAdapter().getTargetClassNameForSingleFieldIdentity(id); 743 } 744 else { 745 // Application identity with user PK class, so find all using this PK 746 Collection<AbstractClassMetaData> cmds = getMetaDataManager().getClassMetaDataWithApplicationId(id.getClass().getName()); 747 if (cmds != null) { 748 if (cmds.size() == 1) { 749 className = cmds.iterator().next().getFullClassName(); 750 } 751 else { 752 ManagedConnection mconn = this.getConnection(ec); 753 try { 754 PersistenceManagerConnection pmConn = (PersistenceManagerConnection)mconn.getConnection(); 755 PersistenceManager pmData = pmConn.getDataPM(); 756 CryptoContext cryptoContext = new CryptoContext(encryptionCoordinateSetManager, keyStoreRefManager, ec, pmConn); 757 datastoreVersionManager.applyOnce(cryptoContext); 758 759 String objectIDString = id.toString(); 760 for (AbstractClassMetaData cmd : cmds) { 761 Class<?> clazz = clr.classForName(cmd.getFullClassName()); 762 ClassMeta classMeta = getClassMeta(ec, clazz); 763 DataEntry dataEntry = new DataEntryDAO(pmData, cryptoContext.getKeyStoreRefID()).getDataEntry(classMeta, objectIDString); 764 if (dataEntry != null) { 765 className = cmd.getFullClassName(); 766 } 767 } 768 } finally { 769 mconn.release(); 770 } 771 } 772 } 773 } 774 775 if (className != null) { 776 objectID2className.put(id, className); 777 } 778 779 return className; 780 } 781 782 // public long nextDataEntryID(ExecutionContext executionContext) 783 // { 784 // NucleusSequence nucleusSequence = getNucleusSequence(executionContext, SEQUENCE_META_DATA_DATA_ENTRY); 785 // return nucleusSequence.nextValue(); 786 // } 787 788 // @Override 789 // protected String getStrategyForNative(AbstractClassMetaData cmd, int absFieldNumber) { 790 // return "increment"; 791 //// AbstractMemberMetaData mmd = cmd.getMetaDataForManagedMemberAtAbsolutePosition(absFieldNumber); 792 //// if (String.class.isAssignableFrom(mmd.getType()) || UUID.class.isAssignableFrom(mmd.getType())) 793 //// return "uuid-hex"; 794 //// else 795 //// return "increment"; 796 // } 797 798 @Override 799 public void createSchema(Set<String> classNames, Properties props) { 800 Cumulus4jConnectionFactory cf = 801 (Cumulus4jConnectionFactory) connectionMgr.lookupConnectionFactory(primaryConnectionFactoryName); 802 JDOPersistenceManagerFactory pmfData = (JDOPersistenceManagerFactory) cf.getPMFData(); 803 JDOPersistenceManagerFactory pmfIndex = (JDOPersistenceManagerFactory) cf.getPMFIndex(); 804 if (pmfData.getNucleusContext().getStoreManager() instanceof SchemaAwareStoreManager) { 805 // Create Cumulus4J "Data" (plus "Index" if not separate) schema 806 SchemaAwareStoreManager schemaMgr = (SchemaAwareStoreManager) pmfData.getNucleusContext().getStoreManager(); 807 Set<String> cumulus4jClassNames = new HashSet<String>(); 808 Collection<Class> pmfClasses = pmfData.getManagedClasses(); 809 for (Class cls : pmfClasses) { 810 cumulus4jClassNames.add(cls.getName()); 811 } 812 schemaMgr.createSchema(cumulus4jClassNames, new Properties()); 813 } 814 if (pmfIndex != null && pmfIndex.getNucleusContext().getStoreManager() instanceof SchemaAwareStoreManager) { 815 // Create Cumulus4J "Index" schema 816 SchemaAwareStoreManager schemaMgr = (SchemaAwareStoreManager) pmfIndex.getNucleusContext().getStoreManager(); 817 Set<String> cumulus4jClassNames = new HashSet<String>(); 818 Collection<Class> pmfClasses = pmfIndex.getManagedClasses(); 819 for (Class cls : pmfClasses) { 820 cumulus4jClassNames.add(cls.getName()); 821 } 822 schemaMgr.createSchema(cumulus4jClassNames, new Properties()); 823 } 824 } 825 826 @Override 827 public void deleteSchema(Set<String> classNames, Properties props) { 828 Cumulus4jConnectionFactory cf = 829 (Cumulus4jConnectionFactory) connectionMgr.lookupConnectionFactory(primaryConnectionFactoryName); 830 JDOPersistenceManagerFactory pmfData = (JDOPersistenceManagerFactory) cf.getPMFData(); 831 JDOPersistenceManagerFactory pmfIndex = (JDOPersistenceManagerFactory) cf.getPMFIndex(); 832 if (pmfData.getNucleusContext().getStoreManager() instanceof SchemaAwareStoreManager) { 833 SchemaAwareStoreManager schemaMgr = (SchemaAwareStoreManager) pmfData.getNucleusContext().getStoreManager(); 834 Set<String> cumulus4jClassNames = new HashSet<String>(); 835 Collection<Class> pmfClasses = pmfData.getManagedClasses(); 836 for (Class cls : pmfClasses) { 837 cumulus4jClassNames.add(cls.getName()); 838 } 839 schemaMgr.deleteSchema(cumulus4jClassNames, new Properties()); 840 } 841 if (pmfIndex != null && pmfIndex.getNucleusContext().getStoreManager() instanceof SchemaAwareStoreManager) { 842 SchemaAwareStoreManager schemaMgr = (SchemaAwareStoreManager) pmfIndex.getNucleusContext().getStoreManager(); 843 Set<String> cumulus4jClassNames = new HashSet<String>(); 844 Collection<Class> pmfClasses = pmfIndex.getManagedClasses(); 845 for (Class cls : pmfClasses) { 846 cumulus4jClassNames.add(cls.getName()); 847 } 848 schemaMgr.deleteSchema(cumulus4jClassNames, new Properties()); 849 } 850 } 851 852 @Override 853 public void validateSchema(Set<String> classNames, Properties props) { 854 Cumulus4jConnectionFactory cf = 855 (Cumulus4jConnectionFactory) connectionMgr.lookupConnectionFactory(primaryConnectionFactoryName); 856 JDOPersistenceManagerFactory pmfData = (JDOPersistenceManagerFactory) cf.getPMFData(); 857 JDOPersistenceManagerFactory pmfIndex = (JDOPersistenceManagerFactory) cf.getPMFIndex(); 858 if (pmfData.getNucleusContext().getStoreManager() instanceof SchemaAwareStoreManager) { 859 SchemaAwareStoreManager schemaMgr = (SchemaAwareStoreManager) pmfData.getNucleusContext().getStoreManager(); 860 Set<String> cumulus4jClassNames = new HashSet<String>(); 861 Collection<Class> pmfClasses = pmfData.getManagedClasses(); 862 for (Class cls : pmfClasses) { 863 cumulus4jClassNames.add(cls.getName()); 864 } 865 schemaMgr.validateSchema(cumulus4jClassNames, new Properties()); 866 } 867 if (pmfIndex != null && pmfIndex.getNucleusContext().getStoreManager() instanceof SchemaAwareStoreManager) { 868 SchemaAwareStoreManager schemaMgr = (SchemaAwareStoreManager) pmfIndex.getNucleusContext().getStoreManager(); 869 Set<String> cumulus4jClassNames = new HashSet<String>(); 870 Collection<Class> pmfClasses = pmfIndex.getManagedClasses(); 871 for (Class cls : pmfClasses) { 872 cumulus4jClassNames.add(cls.getName()); 873 } 874 schemaMgr.validateSchema(cumulus4jClassNames, new Properties()); 875 } 876 } 877 878 @Override 879 public Cumulus4jPersistenceHandler getPersistenceHandler() { 880 return (Cumulus4jPersistenceHandler) super.getPersistenceHandler(); 881 } 882 883 @Override 884 public boolean isStrategyDatastoreAttributed(AbstractClassMetaData cmd, int absFieldNumber) { 885 // We emulate all strategies via our Cumulus4jIncrementGenerator - none is really datastore-attributed. 886 return false; 887 } 888 889 @Override 890 public Extent getExtent(ExecutionContext ec, @SuppressWarnings("rawtypes") Class c, boolean subclasses) { 891 getClassMeta(ec, c); // Ensure, we initialise our meta-data, too. 892 return super.getExtent(ec, c, subclasses); 893 } 894 895 public void assertReadOnlyForUpdateOfObject(ObjectProvider op) { 896 // TODO this method disappeared in DataNucleus 3.2 (it was still present in 3.1). Need to find out how to replace it! 897 } 898 }