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 }