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    }