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