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.model;
019    
020    import java.util.ArrayList;
021    import java.util.Collection;
022    import java.util.HashMap;
023    import java.util.HashSet;
024    import java.util.Map;
025    import java.util.Set;
026    
027    import javax.jdo.JDOHelper;
028    import javax.jdo.PersistenceManager;
029    import javax.jdo.annotations.Column;
030    import javax.jdo.annotations.Discriminator;
031    import javax.jdo.annotations.DiscriminatorStrategy;
032    import javax.jdo.annotations.IdGeneratorStrategy;
033    import javax.jdo.annotations.IdentityType;
034    import javax.jdo.annotations.NotPersistent;
035    import javax.jdo.annotations.NullValue;
036    import javax.jdo.annotations.PersistenceCapable;
037    import javax.jdo.annotations.Persistent;
038    import javax.jdo.annotations.PrimaryKey;
039    import javax.jdo.annotations.Queries;
040    import javax.jdo.annotations.Query;
041    import javax.jdo.annotations.Unique;
042    import javax.jdo.annotations.Uniques;
043    import javax.jdo.annotations.Version;
044    import javax.jdo.annotations.VersionStrategy;
045    import javax.jdo.listener.DetachCallback;
046    import javax.jdo.listener.StoreCallback;
047    
048    import org.cumulus4j.store.Cumulus4jStoreManager;
049    import org.datanucleus.ExecutionContext;
050    import org.datanucleus.metadata.AbstractClassMetaData;
051    import org.datanucleus.metadata.AbstractMemberMetaData;
052    import org.datanucleus.metadata.MetaDataManager;
053    import org.slf4j.Logger;
054    import org.slf4j.LoggerFactory;
055    
056    /**
057     * Persistent meta-data for a field of a persistence-capable class. Since class- and field-names are very
058     * long we reference them indirectly via the long-identifiers of {@link ClassMeta} and {@link FieldMeta},
059     * e.g. in the relation {@link IndexEntry#getFieldMeta() IndexEntry.fieldMeta}.
060     *
061     * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
062     */
063    @PersistenceCapable(identityType=IdentityType.APPLICATION, detachable="true")
064    @Discriminator(
065                    strategy=DiscriminatorStrategy.VALUE_MAP, value="FieldMeta",
066                    columns=@Column(name="discriminator", defaultValue="FieldMeta", length=100)
067    )
068    @Version(strategy=VersionStrategy.VERSION_NUMBER)
069    @Uniques({
070            @Unique(name="FieldMeta_classMeta_ownerFieldMeta_fieldName_role", members={"uniqueScope", "classMeta_classID", "ownerFieldMeta_fieldID", "fieldName", "role"})
071    })
072    @Queries({
073            @Query(name=FieldMeta.NamedQueries.getFieldMetasForClassMeta_classID, value="SELECT WHERE this.classMeta_classID == :classMeta_classID"),
074            @Query(name=FieldMeta.NamedQueries.getSubFieldMetasForFieldMeta_fieldID, value="SELECT WHERE this.ownerFieldMeta_fieldID == :ownerFieldMeta_fieldID")
075    })
076    public class FieldMeta
077    implements DetachCallback, StoreCallback
078    {
079            private static final Logger logger = LoggerFactory.getLogger(FieldMeta.class);
080    
081            protected static final String UNIQUE_SCOPE_FIELD_META = "FieldMeta";
082    
083            protected static class NamedQueries {
084                    public static final String getFieldMetasForClassMeta_classID = "getFieldMetasForClassMeta_classID";
085                    public static final String getSubFieldMetasForFieldMeta_fieldID = "getSubFieldMetasForFieldMeta_fieldID";
086            }
087    
088            @PrimaryKey
089            @Persistent(valueStrategy=IdGeneratorStrategy.NATIVE, sequence="FieldMetaSequence")
090            private Long fieldID;
091    
092    //      /**
093    //       * This is needed due to GAE compatibility. package.jdo is responsible
094    //       * for the correct usage if this field.
095    //       */
096    ////    @NotPersistent // not persistent for non-GAE-datastores
097    //      private String fieldIDString;
098    
099            @Persistent(nullValue=NullValue.EXCEPTION)
100            @Column(length=255, defaultValue=UNIQUE_SCOPE_FIELD_META)
101            private String uniqueScope;
102    
103            @Column(name="classMeta_classID_oid") // for downward-compatibility
104            private Long classMeta_classID;
105    
106            @NotPersistent
107            private ClassMeta classMeta;
108    
109            @Column(name="ownerFieldMeta_fieldID_oid") // for downward-compatibility
110            private Long ownerFieldMeta_fieldID;
111    
112            @NotPersistent
113            private FieldMeta ownerFieldMeta;
114    
115            @Persistent(nullValue=NullValue.EXCEPTION)
116            @Column(length=255)
117            private String fieldName;
118    
119            @Persistent(nullValue=NullValue.EXCEPTION)
120            private FieldMetaRole role;
121    
122            @NotPersistent
123            private int dataNucleusAbsoluteFieldNumber = -1;
124    
125            /**
126             * Meta data for all sub-fields of this <code>FieldMeta</code>.
127             * <p>
128             * This map is manually managed (e.g. lazy-loaded by {@link #getRole2SubFieldMeta()} or manually detached
129             * in {@link #jdoPostDetach(Object)}) because of constraints in GAE. We simulate the behaviour of:
130             * <p>
131             * <pre>
132             * &#64;Persistent(mappedBy="ownerFieldMeta", dependentValue="true")
133             * &#64;Key(mappedBy="role")
134             * </pre>
135             */
136            @NotPersistent
137            private Map<FieldMetaRole, FieldMeta> role2SubFieldMeta;
138    
139            @NotPersistent
140            private boolean embeddedClassMetaLoaded;
141    
142            @NotPersistent
143            private EmbeddedClassMeta embeddedClassMeta;
144    
145    //      @NotPersistent
146    //      private Set<EmbeddedClassMeta> embeddedClassMetasToBeDeleted;
147    
148            /**
149             * Internal constructor. This exists only for JDO and should not be used by application code!
150             */
151            protected FieldMeta() { }
152    
153            /**
154             * Create a <code>FieldMeta</code> referencing a real field.
155             * @param classMeta the class to which this field belongs.
156             * @param fieldName the field's name.
157             * @see #FieldMeta(FieldMeta, FieldMetaRole)
158             */
159            public FieldMeta(ClassMeta classMeta, String fieldName)
160            {
161                    this(classMeta, null, fieldName, FieldMetaRole.primary);
162            }
163            /**
164             * Create a <code>FieldMeta</code> referencing a part of a field. This is necessary to index keys and values of a
165             * <code>Map</code> field (i.e. 2 separate indexes for one field) as well as <code>Collection</code>-elements and similar.
166             * @param ownerFieldMeta the <code>FieldMeta</code> of the real field (to which the part belongs).
167             * @param role the role (aka type) of the sub-field (aka part).
168             * @see #FieldMeta(ClassMeta, String)
169             */
170            public FieldMeta(FieldMeta ownerFieldMeta, FieldMetaRole role)
171            {
172                    this(null, ownerFieldMeta, ownerFieldMeta.getFieldName(), role);
173            }
174    
175            /**
176             * Internal constructor. This exists only for easier implementation of the other constructors and
177             * should not be used by application code!
178             */
179            protected FieldMeta(ClassMeta classMeta, FieldMeta ownerFieldMeta, String fieldName, FieldMetaRole role)
180            {
181                    if (classMeta == null && ownerFieldMeta == null)
182                            throw new IllegalArgumentException("classMeta == null && ownerFieldMeta == null");
183    
184                    if (classMeta != null && ownerFieldMeta != null)
185                            throw new IllegalArgumentException("classMeta != null && ownerFieldMeta != null");
186    
187                    if (fieldName == null)
188                            throw new IllegalArgumentException("fieldName == null");
189    
190                    if (role == null)
191                            throw new IllegalArgumentException("role == null");
192    
193                    this.setClassMeta(classMeta);
194                    this.setOwnerFieldMeta(ownerFieldMeta);
195                    this.fieldName = fieldName;
196                    this.role = role;
197                    setUniqueScope(UNIQUE_SCOPE_FIELD_META);
198            }
199    
200            public long getFieldID() {
201    //              if(fieldIDString != null && fieldID == null){
202    //                      fieldID = KeyFactory.getInstance().stringToKey(fieldIDString).getId();
203    //              }
204                    return fieldID == null ? -1 : fieldID;
205            }
206    
207            protected String getUniqueScope() {
208                    return uniqueScope;
209            }
210    
211            protected void setUniqueScope(String uniqueScope) {
212                    this.uniqueScope = uniqueScope;
213            }
214    
215            /**
216             * Get the {@link ClassMeta} to which this <code>FieldMeta</code> belongs. Every FieldMeta
217             * belongs to exactly one {@link ClassMeta} just like a field is declared in exactly one Java class.
218             * Note, that a {@link FieldMeta} might belong to another FieldMeta in order to reference sub-field-properties,
219             * e.g. a {@link Map}'s key. In this case, the direct property <code>classMeta</code> is <code>null</code>, but this method
220             * still returns the correct {@link ClassMeta} by resolving it indirectly via the {@link #getOwnerFieldMeta() ownerFieldMeta}.
221             * @return the {@link ClassMeta} to which this instance of <code>FieldMeta</code> belongs.
222             */
223            public ClassMeta getClassMeta() {
224                    if (getOwnerFieldMeta() != null)
225                            return getOwnerFieldMeta().getClassMeta();
226    
227                    if (classMeta_classID == null) // should never happen but better check
228                            return null;
229    
230                    if (classMeta == null)
231                            classMeta = new ClassMetaDAO(getPersistenceManager()).getClassMeta(classMeta_classID, true);
232    
233                    return classMeta;
234            }
235    
236            protected void setClassMeta(ClassMeta classMeta) {
237                    // We allow only assignment of equal arguments (e.g. during detachment).
238                    if (this.classMeta != null && !this.classMeta.equals(classMeta))
239                            throw new IllegalStateException("Cannot modify this this.classMeta!");
240    
241                    if (this.classMeta_classID != null && this.classMeta_classID.longValue() != classMeta.getClassID())
242                            throw new IllegalStateException("Cannot modify this this.classMeta!");
243    
244                    this.classMeta = classMeta;
245                    this.classMeta_classID = classMeta == null ? null : classMeta.getClassID();
246                    if (this.classMeta_classID != null && this.classMeta_classID.longValue() < 0)
247                            throw new IllegalStateException("classMeta not persisted yet: " + classMeta);
248            }
249    
250            /**
251             * Get the {@link FieldMetaRole#primary primary} {@link FieldMeta}, to which this sub-<code>FieldMeta</code> belongs
252             * or <code>null</code>, if this <code>FieldMeta</code> is primary.
253             * @return the owning primary field-meta or <code>null</code>.
254             */
255            public FieldMeta getOwnerFieldMeta() {
256                    if (ownerFieldMeta_fieldID == null)
257                            return null;
258    
259                    if (ownerFieldMeta == null)
260                            ownerFieldMeta = new FieldMetaDAO(getPersistenceManager()).getFieldMeta(ownerFieldMeta_fieldID, true);
261    
262                    return ownerFieldMeta;
263            }
264    
265            protected void setOwnerFieldMeta(FieldMeta ownerFieldMeta) {
266                    // We allow only assignment of equal arguments (e.g. during detachment).
267                    if (this.ownerFieldMeta != null && !this.ownerFieldMeta.equals(ownerFieldMeta))
268                            throw new IllegalStateException("Cannot modify this this.ownerFieldMeta!");
269    
270                    if (this.ownerFieldMeta_fieldID != null && this.ownerFieldMeta_fieldID.longValue() != ownerFieldMeta.getFieldID())
271                            throw new IllegalStateException("Cannot modify this this.ownerFieldMeta!");
272    
273                    this.ownerFieldMeta = ownerFieldMeta;
274                    this.ownerFieldMeta_fieldID = ownerFieldMeta == null ? null : ownerFieldMeta.getFieldID();
275                    if (this.ownerFieldMeta_fieldID != null && this.ownerFieldMeta_fieldID.longValue() < 0)
276                            throw new IllegalStateException("ownerFieldMeta not persisted yet: " + ownerFieldMeta);
277            }
278    
279            /**
280             * Get the simple field name (no class prefix) of the field referenced by this meta-data-instance.
281             * @return the simple field name.
282             */
283            public String getFieldName() {
284                    return fieldName;
285            }
286    
287            /**
288             * Get the role of the (sub-)field. If this is not a sub-field, but a primary field
289             * (i.e. directly meaning a real field of the class referenced by {@link #getClassMeta() classMeta})
290             * it will be {@link FieldMetaRole#primary}, hence this method never returns <code>null</code>.
291             * @return the role of this <code>FieldMeta</code>; never <code>null</code>.
292             */
293            public FieldMetaRole getRole() {
294                    return role;
295            }
296    
297            /**
298             * Get the {@link PersistenceManager} assigned to <code>this</code>. If there is none, this method checks, if
299             * <code>this</code> is new. If <code>this</code> was persisted before, it must have one or an {@link IllegalStateException}
300             * is thrown.
301             * @return the {@link PersistenceManager} assigned to this or <code>null</code>.
302             */
303            protected PersistenceManager getPersistenceManager() {
304                    PersistenceManager pm = JDOHelper.getPersistenceManager(this);
305                    if (pm == null) {
306                            throw new IllegalStateException("JDOHelper.getPersistenceManager(this) returned null! " + this);
307    //                      if (JDOHelper.getObjectId(this) != null)
308    //                              throw new IllegalStateException("This FieldMeta instance is not new, but JDOHelper.getPersistenceManager(this) returned null! " + this);
309                    }
310                    return pm;
311            }
312    
313            protected Map<FieldMetaRole, FieldMeta> getRole2SubFieldMeta() {
314                    Map<FieldMetaRole, FieldMeta> result = this.role2SubFieldMeta;
315    
316                    if (result == null) {
317                            logger.debug("getRole2SubFieldMeta: this.role2SubFieldMeta == null => populating. this={}", this);
318                            result = new HashMap<FieldMetaRole, FieldMeta>();
319                            PersistenceManager pm = getPersistenceManager();
320                            if (pm != null) {
321                                    Collection<FieldMeta> fieldMetas = new FieldMetaDAO(pm).getSubFieldMetasForFieldMeta(this);
322                                    for (FieldMeta fieldMeta : fieldMetas)
323                                            result.put(fieldMeta.getRole(), fieldMeta);
324                            }
325    
326                            this.role2SubFieldMeta = result;
327                    }
328                    else
329                            logger.trace("getRole2SubFieldMeta: this.role2SubFieldMeta != null (already populated). this={}", this);
330    
331                    return result;
332            }
333    
334            public EmbeddedClassMeta getEmbeddedClassMeta() {
335                    if (!embeddedClassMetaLoaded) {
336                            logger.debug("getEmbeddedClassMeta: this.embeddedClassMetaLoaded == false => loading. this={}", this);
337                            PersistenceManager pm = getPersistenceManager();
338                            if (pm != null) {
339                                    embeddedClassMeta = new ClassMetaDAO(pm).getEmbeddedClassMeta(this, false);
340                            }
341                            embeddedClassMetaLoaded = true;
342                    }
343                    return embeddedClassMeta;
344            }
345    
346            public void setEmbeddedClassMeta(EmbeddedClassMeta embeddedClassMeta) {
347                    if (embeddedClassMeta != null && !JDOHelper.isDetached(embeddedClassMeta))
348                            embeddedClassMeta = getPersistenceManager().makePersistent(embeddedClassMeta);
349    
350                    EmbeddedClassMeta embeddedClassMetaOld = this.embeddedClassMeta;
351                    if (embeddedClassMetaOld != null && !embeddedClassMetaOld.equals(embeddedClassMeta)) {
352    //                      if (this.embeddedClassMetasToBeDeleted == null)
353    //                              this.embeddedClassMetasToBeDeleted = new HashSet<EmbeddedClassMeta>();
354    //
355    //                      this.embeddedClassMetasToBeDeleted.add(embeddedClassMetaOld);
356                            getPersistenceManager().deletePersistent(embeddedClassMetaOld);
357                    }
358    
359                    this.embeddedClassMeta = embeddedClassMeta;
360                    this.embeddedClassMetaLoaded = true;
361    
362    //              if (this.embeddedClassMetasToBeDeleted != null)
363    //                      this.embeddedClassMetasToBeDeleted.remove(embeddedClassMeta);
364            }
365    
366            public int getDataNucleusAbsoluteFieldNumber(ExecutionContext executionContext) {
367                    AbstractClassMetaData dnClassMetaData = getClassMeta().getDataNucleusClassMetaData(executionContext);
368                    int dnFieldNumber = getDataNucleusAbsoluteFieldNumber();
369                    if (dnFieldNumber < 0) {
370                            dnFieldNumber = dnClassMetaData.getAbsolutePositionOfMember(getClassMeta().getClassName(), getFieldName());
371                            if (dnFieldNumber < 0)
372                                    throw new IllegalStateException("The method dnClassMetaData.getAbsolutePositionOfMember(...) returned -1 for memberName='" + getFieldName() + "'!!!");
373    
374                            setDataNucleusAbsoluteFieldNumber(dnFieldNumber);
375                    }
376                    return dnFieldNumber;
377            }
378    
379            /**
380             * Get the non-persistent field-number in DataNucleus' meta-data. This is only a usable value,
381             * if this <code>FieldMeta</code> was obtained via
382             * {@link Cumulus4jStoreManager#getClassMeta(org.datanucleus.store.ExecutionContext, Class)}; otherwise
383             * it is -1.
384             * @return the non-persistent field-number in DataNucleus' meta-data or -1.
385             */
386            public int getDataNucleusAbsoluteFieldNumber() {
387                    return dataNucleusAbsoluteFieldNumber;
388            }
389            public void setDataNucleusAbsoluteFieldNumber(int dataNucleusAbsoluteFieldNumber) {
390                    this.dataNucleusAbsoluteFieldNumber = dataNucleusAbsoluteFieldNumber;
391                    this.dataNucleusMemberMetaData = null;
392    
393                    for (FieldMeta subFM : getRole2SubFieldMeta().values())
394                            subFM.setDataNucleusAbsoluteFieldNumber(dataNucleusAbsoluteFieldNumber);
395            }
396    
397            /**
398             * Get a sub-field of this field or <code>null</code>, if no such sub-field exists.
399             * @param role the role of the sub-field. Must not be <code>null</code>.
400             * @return the sub-<code>FieldMeta</code> or <code>null</code>.
401             */
402            public FieldMeta getSubFieldMeta(FieldMetaRole role)
403            {
404                    if (role == null)
405                            throw new IllegalArgumentException("role == null");
406    
407                    return getRole2SubFieldMeta().get(role);
408            }
409    
410            /**
411             * Get all sub-fields' meta-data of this field. If there are no sub-fields, this is an
412             * empty collection.
413             * @return all sub-<code>FieldMeta</code>s of this field; never <code>null</code>.
414             */
415            public Collection<FieldMeta> getSubFieldMetas()
416            {
417                    return getRole2SubFieldMeta().values();
418            }
419    
420            public void addSubFieldMeta(FieldMeta subFieldMeta)
421            {
422                    if (!this.equals(subFieldMeta.getOwnerFieldMeta()))
423                            throw new IllegalArgumentException("this != subFieldMeta.ownerFieldMeta");
424    
425                    if (!this.fieldName.equals(subFieldMeta.getFieldName()))
426                            throw new IllegalArgumentException("this.fieldName != subFieldMeta.fieldName");
427    
428                    if (getSubFieldMeta(subFieldMeta.getRole()) != null)
429                            throw new IllegalArgumentException("There is already a subFieldMeta with role \"" + subFieldMeta.getRole() + "\"!");
430    
431                    subFieldMeta = getPersistenceManager().makePersistent(subFieldMeta);
432    
433                    subFieldMeta.setDataNucleusAbsoluteFieldNumber(dataNucleusAbsoluteFieldNumber);
434    
435    //              PersistenceManager pm = getPersistenceManager();
436    //              if (pm != null) // If the pm is null, the subFieldMeta is persisted later (see jdoPreStore() below).
437    //                      subFieldMeta = pm.makePersistent(subFieldMeta);
438    
439                    getRole2SubFieldMeta().put(subFieldMeta.getRole(), subFieldMeta);
440            }
441    
442            public void removeSubFieldMeta(FieldMeta subFieldMeta)
443            {
444                    if (!this.equals(subFieldMeta.getOwnerFieldMeta()))
445                            throw new IllegalArgumentException("subFieldMeta.ownerFieldMeta != this");
446    
447                    getRole2SubFieldMeta().remove(subFieldMeta.getRole());
448    //              PersistenceManager pm = getPersistenceManager();
449    //              if (pm != null)
450    //                      pm.deletePersistent(subFieldMeta);
451                    getPersistenceManager().deletePersistent(subFieldMeta);
452            }
453    
454            public void removeAllSubFieldMetasExcept(FieldMetaRole ... roles)
455            {
456                    if (roles == null)
457                            roles = new FieldMetaRole[0];
458    
459                    Set<FieldMetaRole> rolesToKeep = new HashSet<FieldMetaRole>(roles.length);
460                    for (FieldMetaRole role : roles)
461                            rolesToKeep.add(role);
462    
463                    PersistenceManager pm = getPersistenceManager();
464                    Collection<FieldMetaRole> oldRoles = new ArrayList<FieldMetaRole>(getRole2SubFieldMeta().keySet());
465                    for (FieldMetaRole role : oldRoles) {
466                            if (!rolesToKeep.contains(role)) {
467                                    FieldMeta subFieldMeta = getRole2SubFieldMeta().remove(role);
468    
469    //                              if (pm != null && subFieldMeta != null)
470                                    if (subFieldMeta != null)
471                                            pm.deletePersistent(subFieldMeta);
472                            }
473                    }
474            }
475    
476            @NotPersistent
477            private transient FieldMeta mappedByFieldMeta;
478    
479            /**
480             * Used by {@link #getMappedByFieldMeta(ExecutionContext)} to mask <code>null</code> and thus
481             * prevent a second unnecessary resolve process if the first already resolved to <code>null</code>.
482             */
483            private static final FieldMeta NULL_MAPPED_BY_FIELD_META = new FieldMeta();
484    
485            public ClassMeta getFieldOrElementTypeClassMeta(ExecutionContext executionContext) {
486                    Class<?> clazz = getFieldOrElementType(executionContext);
487                    Cumulus4jStoreManager storeManager = (Cumulus4jStoreManager) executionContext.getStoreManager();
488                    if (!storeManager.getMetaDataManager().isClassPersistable(clazz.getName()))
489                            return null;
490    
491                    ClassMeta result;
492                    if (JDOHelper.isDetached(this))
493                            result = storeManager.getClassMeta(executionContext, clazz);
494                    else {
495                            PersistenceManager pm = getPersistenceManager();
496                            // We REQUIRE the pm to be present now. this.getPersistenceManager() sometimes returns null.
497                            if (pm == null)
498                                    throw new IllegalStateException("this.getPersistenceManager() == null! Probably this instance is new. Persist it first!");
499    
500                            result = storeManager.getAttachedClassMeta(executionContext, pm, clazz);
501                    }
502                    return result;
503            }
504    
505            public AbstractClassMetaData getFieldOrElementTypeDataNucleusClassMetaData(ExecutionContext executionContext)
506            {
507                    Class<?> clazz = getFieldOrElementType(executionContext);
508                    Cumulus4jStoreManager storeManager = (Cumulus4jStoreManager) executionContext.getStoreManager();
509                    MetaDataManager metaDataManager = storeManager.getMetaDataManager();
510                    AbstractClassMetaData metaDataForClass = metaDataManager.getMetaDataForClass(clazz, executionContext.getClassLoaderResolver());
511                    return metaDataForClass;
512            }
513    
514            public Class<?> getFieldOrElementType(ExecutionContext executionContext) {
515                    AbstractMemberMetaData mmd = getDataNucleusMemberMetaData(executionContext);
516                    Class<?> result;
517                    if (mmd.hasCollection()) {
518    //                      if (FieldMetaRole.primary == this.getRole())
519    //                              throw new IllegalStateException("this is a primary FieldMeta of a collection - use appropriate sub-FieldMeta instead!");
520    
521                            result = executionContext.getClassLoaderResolver().classForName(mmd.getCollection().getElementType());
522                    }
523                    else if (mmd.hasArray()) {
524    //                      if (FieldMetaRole.primary == this.getRole())
525    //                              throw new IllegalStateException("this is a primary FieldMeta of an array - use appropriate sub-FieldMeta instead!");
526    
527                            result = executionContext.getClassLoaderResolver().classForName(mmd.getArray().getElementType());
528                    }
529                    else if (mmd.hasMap()) {
530                            FieldMetaRole role = this.getRole();
531    
532                            // This method should work with mapped-by-relations, because there is only one
533                            // FCO related anyway. Marco :-)
534                            String mappedBy;
535                            mappedBy = mmd.getKeyMetaData() == null ? null : mmd.getKeyMetaData().getMappedBy();
536                            if(mappedBy != null)
537                                    role = FieldMetaRole.mapValue;
538    
539                            mappedBy = mmd.getValueMetaData() == null ? null : mmd.getValueMetaData().getMappedBy();
540                            if(mappedBy != null)
541                                    role = FieldMetaRole.mapKey;
542    
543                            if (FieldMetaRole.primary == role)
544                                    throw new IllegalStateException("this is a primary FieldMeta of a map - use appropriate sub-FieldMeta instead!");
545    
546                            switch (role) {
547                                    case mapKey:
548                                            result = executionContext.getClassLoaderResolver().classForName(mmd.getMap().getKeyType());
549                                            break;
550                                    case mapValue:
551                                            result = executionContext.getClassLoaderResolver().classForName(mmd.getMap().getValueType());
552                                            break;
553                                    default:
554                                            throw new IllegalStateException("DataNucleus-member-meta-data says this is a map, but this.role='" + this.getRole() + "': this=" + this);
555                            }
556    //                      if (mmd.getMap().keyIsPersistent())
557    //                              result = executionContext.getClassLoaderResolver().classForName(mmd.getMap().getKeyType());
558    //                      else if (mmd.getMap().valueIsPersistent())
559    //                              result = executionContext.getClassLoaderResolver().classForName(mmd.getMap().getValueType());
560    //                      else
561    //                              throw new IllegalStateException("How can a Map be mapped-by without key and value being persistent?! Exactly one of them should be persistent!");
562                    }
563                    else
564                            result = mmd.getType();
565    
566                    return result;
567            }
568    
569            /**
570             * <p>
571             * Get the {@link FieldMeta} of the opposite end of the mapped-by-relation. If
572             * this is not a mapped-by field, this method returns <code>null</code>.
573             * </p>
574             * <p>
575             * Though, it returns always the mapped-by opposite side, the semantics of
576             * this method still depend on the {@link #getRole() role} of this <code>FieldMeta</code>:
577             * </p>
578             * <ul>
579             * <li>{@link FieldMetaRole#primary}: Returns the owner-field on the opposite side which is referenced by
580             * &#64;Persistent(mappedBy="owner")</li>
581             * <li>{@link FieldMetaRole#mapKey}: Returns the key-field on the opposite side which is referenced by
582             * &#64;Key(mappedBy="key")</li>
583             * <li>{@link FieldMetaRole#mapValue}: Returns the value-field on the opposite side which is referenced by
584             * &#64;Value(mappedBy="value")</li>
585             * </ul>
586             *
587             * @return the {@link FieldMeta} of the other end of the mapped-by-relation.
588             */
589            public FieldMeta getMappedByFieldMeta(ExecutionContext executionContext)
590            {
591                    FieldMeta mbfm = mappedByFieldMeta;
592    
593                    if (NULL_MAPPED_BY_FIELD_META == mbfm)
594                            return null;
595    
596                    if (mbfm != null)
597                            return mbfm;
598    
599                    AbstractMemberMetaData mmd = getDataNucleusMemberMetaData(executionContext);
600    
601                    if (mmd.getMappedBy() != null)
602                    {
603                            Class<?> typeOppositeSide = getFieldOrElementType(executionContext);
604    //                      if (mmd.hasCollection())
605    //                              typeOppositeSide = executionContext.getClassLoaderResolver().classForName(mmd.getCollection().getElementType());
606    //                      else if (mmd.hasArray())
607    //                              typeOppositeSide = executionContext.getClassLoaderResolver().classForName(mmd.getArray().getElementType());
608    //                      else if (mmd.hasMap()) {
609    //                              if (mmd.getMap().keyIsPersistent())
610    //                                      typeOppositeSide = executionContext.getClassLoaderResolver().classForName(mmd.getMap().getKeyType());
611    //                              else if (mmd.getMap().valueIsPersistent())
612    //                                      typeOppositeSide = executionContext.getClassLoaderResolver().classForName(mmd.getMap().getValueType());
613    //                              else
614    //                                      throw new IllegalStateException("How can a Map be mapped-by without key and value being persistent?! Exactly one of them should be persistent!");
615    //                      }
616    //                      else
617    //                              typeOppositeSide = mmd.getType();
618    
619                            Cumulus4jStoreManager storeManager = (Cumulus4jStoreManager) executionContext.getStoreManager();
620                            ClassMeta classMetaOppositeSide = storeManager.getClassMeta(executionContext, typeOppositeSide);
621                            String mappedBy = null;
622    
623                            switch (role) {
624                                    case primary:
625    //                                      mbfm = classMetaOppositeSide.getFieldMeta(mmd.getMappedBy());
626                                            mappedBy = mmd.getMappedBy();
627                                            break;
628    
629                                    case mapKey:
630                                            mappedBy = mmd.getKeyMetaData() == null ? null : mmd.getKeyMetaData().getMappedBy();
631                                            if (mmd.getMap().valueIsPersistent() && mappedBy == null)
632                                                    throw new IllegalStateException("The map's value is persistent via mappedBy (without @Join), but there is no @Key(mappedBy=\"...\")! This is invalid! " + mmd);
633                                            break;
634    
635                                    case mapValue:
636                                            mappedBy = mmd.getValueMetaData() == null ? null : mmd.getValueMetaData().getMappedBy();
637                                            if (mmd.getMap().keyIsPersistent() && mappedBy == null)
638                                                    throw new IllegalStateException("The map's key is persistent via mappedBy (without @Join), but there is no @Value(mappedBy=\"...\")! This is invalid! " + mmd);
639                                            break;
640    
641                                    case arrayElement:
642                                    case collectionElement:
643                                            // TODO doesn't this need implementation?
644                                            // Seems to work this way, but why? Marco :-)
645                                            // 2012-11-10: added the following line.
646                                            mappedBy = mmd.getMappedBy(); // commented out again // FIXME - some queries break with htis, but IMHO it's correct!
647                                            break;
648    
649                                    default:
650                                            throw new IllegalStateException("Unexpected role: " + role);
651                            }
652    
653                            if (mappedBy != null) {
654                                    mbfm = classMetaOppositeSide.getFieldMeta(mappedBy);
655                                    if (mbfm == null)
656                                            throw new IllegalStateException("Field \"" + mappedBy + "\" referenced in 'mappedBy' of " + this + " does not exist!");
657                            }
658                    }
659    
660                    if (mbfm == null)
661                            mappedByFieldMeta = NULL_MAPPED_BY_FIELD_META;
662                    else
663                            mappedByFieldMeta = mbfm;
664    
665                    return mbfm;
666            }
667    
668            protected static final ThreadLocal<Set<FieldMeta>> attachedFieldMetasInPostDetachThreadLocal = new ThreadLocal<Set<FieldMeta>>() {
669                    @Override
670                    protected Set<FieldMeta> initialValue() {
671                            return new HashSet<FieldMeta>();
672                    }
673            };
674    
675            @Override
676            public void jdoPreDetach() { }
677    
678            @Override
679            public void jdoPostDetach(Object o) {
680                    final PostDetachRunnableManager postDetachRunnableManager = PostDetachRunnableManager.getInstance();
681                    postDetachRunnableManager.enterScope();
682                    try {
683                            final FieldMeta attached = (FieldMeta) o;
684                            final FieldMeta detached = this;
685                            logger.debug("jdoPostDetach: attached={}", attached);
686    
687                            if (!JDOHelper.isDetached(detached))
688                                    throw new IllegalStateException("detached ist not detached!");
689    
690                            if (JDOHelper.getPersistenceManager(detached) != null)
691                                    throw new IllegalStateException("detached has a PersistenceManager assigned!");
692    
693                            final DetachedClassMetaModel detachedClassMetaModel = DetachedClassMetaModel.getInstance();
694                            if (detachedClassMetaModel != null)
695                                    detachedClassMetaModel.registerFieldMetaCurrentlyDetaching(detached);
696    
697                            detached.dataNucleusAbsoluteFieldNumber = attached.dataNucleusAbsoluteFieldNumber;
698    
699                            final PersistenceManager pm = attached.getPersistenceManager();
700                            if (pm == null)
701                                    throw new IllegalStateException("attached.getPersistenceManager() returned null!");
702    
703                            Set<?> fetchGroups = pm.getFetchPlan().getGroups();
704    
705                            Set<FieldMeta> attachedFieldMetasInPostDetach = attachedFieldMetasInPostDetachThreadLocal.get();
706                            if (!attachedFieldMetasInPostDetach.add(attached)) {
707                                    logger.debug("jdoPostDetach: Already in detachment => Skipping detachment of this.role2SubFieldMeta! attached={}", attached);
708                                    return;
709                            }
710                            try {
711                                    // The following field should already be null, but we better ensure that we never
712                                    // contain *AT*tached objects inside a *DE*tached container.
713                                    detached.role2SubFieldMeta = null;
714    
715                                    if (fetchGroups.contains(javax.jdo.FetchGroup.ALL)) {
716                                            logger.debug("jdoPostDetach: Detaching this.role2SubFieldMeta: attached={}", attached);
717    
718                                            // if the fetch-groups say we should detach the FieldMetas, we do it.
719                                            HashMap<FieldMetaRole, FieldMeta> map = new HashMap<FieldMetaRole, FieldMeta>();
720                                            Collection<FieldMeta> detachedSubFieldMetas = pm.detachCopyAll(attached.getRole2SubFieldMeta().values());
721                                            for (final FieldMeta detachedSubFieldMeta : detachedSubFieldMetas) {
722    //                                              detachedSubFieldMeta.setOwnerFieldMeta(detached); // ensure, it's the identical (not only equal) FieldMeta.
723                                                    // The above is not necessary and might cause problems (because this callback might be called while the detached instance is currently
724                                                    // BEING detached, i.e. not yet finished detaching.
725    
726                                                    postDetachRunnableManager.addRunnable(new Runnable() {
727                                                            @Override
728                                                            public void run() {
729                                                                    detachedSubFieldMeta.setOwnerFieldMeta(detached); // ensure, it's the identical (not only equal) FieldMeta.
730                                                            }
731                                                    });
732    
733                                                    map.put(detachedSubFieldMeta.getRole(), detachedSubFieldMeta);
734                                            }
735                                            detached.role2SubFieldMeta = map;
736    
737    
738                                            postDetachRunnableManager.addRunnable(new Runnable() {
739                                                    @Override
740                                                    public void run() {
741                                                            try {
742                                                                    if (attached.classMeta_classID != null) {
743                                                                            detached.classMeta = detachedClassMetaModel == null ? null : detachedClassMetaModel.getClassMeta(attached.classMeta_classID, false);
744                                                                            if (detached.classMeta == null)
745                                                                                    detached.classMeta = pm.detachCopy(attached.getClassMeta());
746                                                                    }
747    
748                                                                    if (attached.ownerFieldMeta_fieldID != null) {
749                                                                            DetachedClassMetaModel detachedClassMetaModel = DetachedClassMetaModel.getInstance();
750                                                                            detached.ownerFieldMeta = detachedClassMetaModel == null ? null : detachedClassMetaModel.getFieldMeta(attached.ownerFieldMeta_fieldID, false);
751                                                                            if (detached.ownerFieldMeta == null)
752                                                                                    detached.ownerFieldMeta = pm.detachCopy(attached.getOwnerFieldMeta());
753                                                                    }
754                                                            } catch (Exception x) {
755                                                                    String className = classMeta != null ? classMeta.getClassName() : String.valueOf(classMeta_classID);
756                                                                    throw new RuntimeException("postDetachRunnable failed for class " + className + " and field " + fieldName + ": " + x, x);
757                                                            }
758                                                    }
759                                            });
760    
761                                    }
762    
763                            } finally {
764                                    attachedFieldMetasInPostDetach.remove(attached);
765                            }
766    
767                            if (fetchGroups.contains(javax.jdo.FetchGroup.ALL)) {
768                                    logger.debug("jdoPostDetach: Detaching this.embeddedClassMeta: attached={}", attached);
769                                    EmbeddedClassMeta embeddedClassMeta = attached.getEmbeddedClassMeta();
770                                    detached.setEmbeddedClassMeta(embeddedClassMeta == null ? null : pm.detachCopy(embeddedClassMeta));
771                            }
772                            else {
773                                    detached.embeddedClassMeta = null;
774                                    detached.embeddedClassMetaLoaded = false;
775                            }
776                    } finally {
777                            postDetachRunnableManager.exitScope();
778                    }
779            }
780    
781            @Override
782            public int hashCode()
783            {
784                    long fieldID = getFieldID();
785                    return (int) (fieldID ^ (fieldID >>> 32));
786            }
787    
788            @Override
789            public boolean equals(Object obj)
790            {
791                    if (this == obj) return true;
792                    if (obj == null) return false;
793                    if (getClass() != obj.getClass()) return false;
794                    FieldMeta other = (FieldMeta) obj;
795                    // if not yet persisted (id == null), it is only equal to the same instance (checked above, already).
796    //              return this.fieldID == null ? false : this.fieldID.equals(other.fieldID);
797                    return this.getFieldID() < 0 ? false : this.getFieldID() == other.getFieldID();
798            }
799    
800            @Override
801            public String toString()
802            {
803                    ClassMeta cm = getClassMeta();
804                    return (
805                                    this.getClass().getName()
806                                    + '@'
807                                    + Integer.toHexString(System.identityHashCode(this))
808                                    + '['
809                                    + fieldID + ',' + (cm == null ? null : cm.getClassName()) + '#' + getFieldName() + '[' + role + ']'
810                                    + ']'
811                    );
812            }
813    
814            @NotPersistent
815            private AbstractMemberMetaData dataNucleusMemberMetaData;
816    
817            public AbstractMemberMetaData getDataNucleusMemberMetaData(ExecutionContext executionContext)
818            {
819                    if (dataNucleusMemberMetaData != null)
820                            return dataNucleusMemberMetaData;
821    
822                    AbstractClassMetaData dnClassMetaData = getClassMeta().getDataNucleusClassMetaData(executionContext);
823    
824                    int dnFieldNumber = getDataNucleusAbsoluteFieldNumber(executionContext);
825    
826                    AbstractMemberMetaData dnMemberMetaData = dnClassMetaData.getMetaDataForManagedMemberAtAbsolutePosition(dnFieldNumber);
827                    if (dnMemberMetaData == null)
828                            throw new IllegalStateException("DataNucleus has no meta-data for this field: fieldID=" + getFieldID() + " className=" + classMeta.getClassName() + " fieldName=" + getFieldName());
829    
830                    dataNucleusMemberMetaData = dnMemberMetaData;
831                    return dnMemberMetaData;
832            }
833    
834            @Override
835            public void jdoPreStore() {
836                    logger.debug("jdoPreStore: {}", this);
837    //              final ClassMeta finalClassMeta = classMeta;
838    //              final FieldMeta finalOwnerFieldMeta = ownerFieldMeta;
839    //
840    //              PostStoreRunnableManager.getInstance().addRunnable(new Runnable() {
841    //                      @Override
842    //                      public void run() {
843    //                              logger.debug("postStore: {}", this);
844    //                              PersistenceManager pm = JDOHelper.getPersistenceManager(FieldMeta.this);
845    //
846    //                              if (finalClassMeta != null) {
847    //                                      setClassMeta(pm.makePersistent(finalClassMeta));
848    //                              }
849    //
850    //                              if (finalOwnerFieldMeta != null) {
851    //                                      setOwnerFieldMeta(pm.makePersistent(finalOwnerFieldMeta));
852    //                              }
853    //
854    //                              if (embeddedClassMeta != null) {
855    //                                      embeddedClassMeta = pm.makePersistent(embeddedClassMeta);
856    //                              }
857    //
858    //                              if (role2SubFieldMeta != null) {
859    //                                      Map<FieldMetaRole, FieldMeta> persistentRole2SubFieldMeta2 = new HashMap<FieldMetaRole, FieldMeta>(role2SubFieldMeta.size());
860    //                                      for (FieldMeta subFieldMeta : role2SubFieldMeta.values()) {
861    //                                              // Usually the persistentSubFieldMeta is the same instance as subFieldMeta, but this is dependent on the configuration.
862    //                                              // This code here should work with all possible configurations. Marco :-)
863    //                                              FieldMeta persistentSubFieldMeta = pm.makePersistent(subFieldMeta);
864    //                                              persistentRole2SubFieldMeta2.put(persistentSubFieldMeta.getRole(), persistentSubFieldMeta);
865    //                                      }
866    //                                      role2SubFieldMeta = persistentRole2SubFieldMeta2;
867    //                                      pm.flush();
868    //                              }
869    //
870    //                              if (embeddedClassMetasToBeDeleted != null) {
871    //                                      for (EmbeddedClassMeta embeddedClassMeta : embeddedClassMetasToBeDeleted) {
872    //                                              pm.deletePersistent(embeddedClassMeta);
873    //                                      }
874    //                                      pm.flush();
875    //                              }
876    //                      }
877    //              });
878            }
879    }