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    }