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 }