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.IdGeneratorStrategy;
031    import javax.jdo.annotations.IdentityType;
032    import javax.jdo.annotations.NotPersistent;
033    import javax.jdo.annotations.NullValue;
034    import javax.jdo.annotations.PersistenceCapable;
035    import javax.jdo.annotations.Persistent;
036    import javax.jdo.annotations.PrimaryKey;
037    import javax.jdo.annotations.Queries;
038    import javax.jdo.annotations.Query;
039    import javax.jdo.annotations.Unique;
040    import javax.jdo.annotations.Uniques;
041    import javax.jdo.annotations.Version;
042    import javax.jdo.annotations.VersionStrategy;
043    import javax.jdo.listener.DetachCallback;
044    
045    import org.cumulus4j.store.Cumulus4jStoreManager;
046    import org.datanucleus.metadata.AbstractClassMetaData;
047    import org.datanucleus.metadata.AbstractMemberMetaData;
048    import org.datanucleus.store.ExecutionContext;
049    import org.slf4j.Logger;
050    import org.slf4j.LoggerFactory;
051    
052    /**
053     * Persistent meta-data for a field of a persistence-capable class. Since class- and field-names are very
054     * long we reference them indirectly via the long-identifiers of {@link ClassMeta} and {@link FieldMeta},
055     * e.g. in the relation {@link IndexEntry#getFieldMeta() IndexEntry.fieldMeta}.
056     *
057     * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
058     */
059    @PersistenceCapable(identityType=IdentityType.APPLICATION, detachable="true")
060    @Version(strategy=VersionStrategy.VERSION_NUMBER)
061    @Uniques({
062            @Unique(name="FieldMeta_classMeta_ownerFieldMeta_fieldName_role", members={"classMeta", "ownerFieldMeta", "fieldName", "role"})
063    })
064    @Queries({
065            @Query(name=FieldMeta.NamedQueries.getFieldMetasForClassMeta, value="SELECT WHERE this.classMeta == :classMeta"),
066            @Query(name=FieldMeta.NamedQueries.getSubFieldMetasForFieldMeta, value="SELECT WHERE this.ownerFieldMeta == :ownerFieldMeta")
067    })
068    public class FieldMeta
069    implements DetachCallback
070    {
071            private static final Logger logger = LoggerFactory.getLogger(FieldMeta.class);
072    
073            protected static class NamedQueries {
074                    public static final String getFieldMetasForClassMeta = "getFieldMetasForClassMeta";
075                    public static final String getSubFieldMetasForFieldMeta = "getSubFieldMetasForFieldMeta";
076            }
077    
078            @PrimaryKey
079            @Persistent(valueStrategy=IdGeneratorStrategy.NATIVE, sequence="FieldMetaSequence")
080            private long fieldID = -1;
081    
082            private ClassMeta classMeta;
083    
084            private FieldMeta ownerFieldMeta;
085    
086            @Persistent(nullValue=NullValue.EXCEPTION)
087            @Column(length=255)
088            private String fieldName;
089    
090            @Persistent(nullValue=NullValue.EXCEPTION)
091            private FieldMetaRole role;
092    
093            @NotPersistent
094            private int dataNucleusAbsoluteFieldNumber = -1;
095    
096            /**
097             * Meta data for all sub-fields of this <code>FieldMeta</code>.
098             * <p>
099             * This map is manually managed (e.g. lazy-loaded by {@link #getRole2SubFieldMeta()} or manually detached
100             * in {@link #jdoPostDetach(Object)}) because of constraints in GAE. We simulate the behaviour of:
101             * <p>
102             * <pre>
103             * &#64;Persistent(mappedBy="ownerFieldMeta", dependentValue="true")
104             * &#64;Key(mappedBy="role")
105             * </pre>
106             */
107            @NotPersistent
108            private Map<FieldMetaRole, FieldMeta> role2SubFieldMeta;
109    
110            /**
111             * Internal constructor. This exists only for JDO and should not be used by application code!
112             */
113            protected FieldMeta() { }
114    
115            /**
116             * Create a <code>FieldMeta</code> referencing a real field.
117             * @param classMeta the class to which this field belongs.
118             * @param fieldName the field's name.
119             * @see #FieldMeta(FieldMeta, FieldMetaRole)
120             */
121            public FieldMeta(ClassMeta classMeta, String fieldName)
122            {
123                    this(classMeta, null, fieldName, FieldMetaRole.primary);
124            }
125            /**
126             * Create a <code>FieldMeta</code> referencing a part of a field. This is necessary to index keys and values of a
127             * <code>Map</code> field (i.e. 2 separate indexes for one field) as well as <code>Collection</code>-elements and similar.
128             * @param ownerFieldMeta the <code>FieldMeta</code> of the real field (to which the part belongs).
129             * @param role the role (aka type) of the sub-field (aka part).
130             * @see #FieldMeta(ClassMeta, String)
131             */
132            public FieldMeta(FieldMeta ownerFieldMeta, FieldMetaRole role)
133            {
134                    this(null, ownerFieldMeta, ownerFieldMeta.getFieldName(), role);
135            }
136    
137            /**
138             * Internal constructor. This exists only for easier implementation of the other constructors and
139             * should not be used by application code!
140             */
141            protected FieldMeta(ClassMeta classMeta, FieldMeta ownerFieldMeta, String fieldName, FieldMetaRole role)
142            {
143                    if (classMeta == null && ownerFieldMeta == null)
144                            throw new IllegalArgumentException("classMeta == null && ownerFieldMeta == null");
145    
146                    if (classMeta != null && ownerFieldMeta != null)
147                            throw new IllegalArgumentException("classMeta != null && ownerFieldMeta != null");
148    
149                    if (fieldName == null)
150                            throw new IllegalArgumentException("fieldName == null");
151    
152                    if (role == null)
153                            throw new IllegalArgumentException("role == null");
154    
155                    this.classMeta = classMeta;
156                    this.ownerFieldMeta = ownerFieldMeta;
157                    this.fieldName = fieldName;
158                    this.role = role;
159            }
160    
161            public long getFieldID() {
162                    return fieldID;
163            }
164    
165            /**
166             * Get the {@link ClassMeta} to which this <code>FieldMeta</code> belongs. Every FieldMeta
167             * belongs to exactly one {@link ClassMeta} just like a field is declared in exactly one Java class.
168             * Note, that a {@link FieldMeta} might belong to another FieldMeta in order to reference sub-field-properties,
169             * e.g. a {@link Map}'s key. In this case, the direct property <code>classMeta</code> is <code>null</code>, but this method
170             * still returns the correct {@link ClassMeta} by resolving it indirectly via the {@link #getOwnerFieldMeta() ownerFieldMeta}.
171             * @return the {@link ClassMeta} to which this instance of <code>FieldMeta</code> belongs.
172             */
173            public ClassMeta getClassMeta() {
174                    if (ownerFieldMeta != null)
175                            return ownerFieldMeta.getClassMeta();
176    
177                    return classMeta;
178            }
179    
180            protected void setClassMeta(ClassMeta classMeta) {
181                    // We allow only assignment of equal arguments (e.g. during detachment).
182                    if (this.classMeta != null && !this.classMeta.equals(classMeta))
183                            throw new IllegalStateException("Cannot modify this this.classMeta!");
184    
185                    this.classMeta = classMeta;
186            }
187    
188            /**
189             * Get the {@link FieldMetaRole#primary primary} {@link FieldMeta}, to which this sub-<code>FieldMeta</code> belongs
190             * or <code>null</code>, if this <code>FieldMeta</code> is primary.
191             * @return the owning primary field-meta or <code>null</code>.
192             */
193            public FieldMeta getOwnerFieldMeta() {
194                    return ownerFieldMeta;
195            }
196    
197            protected void setOwnerFieldMeta(FieldMeta ownerFieldMeta) {
198                    // We allow only assignment of equal arguments (e.g. during detachment).
199                    if (this.ownerFieldMeta != null && !this.ownerFieldMeta.equals(ownerFieldMeta))
200                            throw new IllegalStateException("Cannot modify this this.ownerFieldMeta!");
201    
202                    this.ownerFieldMeta = ownerFieldMeta;
203            }
204    
205            /**
206             * Get the simple field name (no class prefix) of the field referenced by this meta-data-instance.
207             * @return the simple field name.
208             */
209            public String getFieldName() {
210                    return fieldName;
211            }
212    
213            /**
214             * Get the role of the (sub-)field. If this is not a sub-field, but a primary field
215             * (i.e. directly meaning a real field of the class referenced by {@link #getClassMeta() classMeta})
216             * it will be {@link FieldMetaRole#primary}, hence this method never returns <code>null</code>.
217             * @return the role of this <code>FieldMeta</code>; never <code>null</code>.
218             */
219            public FieldMetaRole getRole() {
220                    return role;
221            }
222    
223            /**
224             * Get the {@link PersistenceManager} assigned to <code>this</code>. If there is none, this method checks, if
225             * <code>this</code> is new. If <code>this</code> was persisted before, it must have one or an {@link IllegalStateException}
226             * is thrown.
227             * @return the {@link PersistenceManager} assigned to this or <code>null</code>.
228             */
229            protected PersistenceManager getPersistenceManager() {
230                    PersistenceManager pm = JDOHelper.getPersistenceManager(this);
231                    if (pm == null) {
232                            if (JDOHelper.getObjectId(this) != null)
233                                    throw new IllegalStateException("This FieldMeta instance is not new, but JDOHelper.getPersistenceManager(this) returned null! " + this);
234                    }
235                    return pm;
236            }
237    
238            protected Map<FieldMetaRole, FieldMeta> getRole2SubFieldMeta() {
239                    Map<FieldMetaRole, FieldMeta> result = this.role2SubFieldMeta;
240    
241                    if (result == null) {
242                            logger.debug("getRole2SubFieldMeta: this.role2SubFieldMeta == null => populating. this={}", this);
243                            result = new HashMap<FieldMetaRole, FieldMeta>();
244                            PersistenceManager pm = getPersistenceManager();
245                            if (pm != null) {
246                                    Collection<FieldMeta> fieldMetas = new FieldMetaDAO(pm).getSubFieldMetasForFieldMeta(this);
247                                    for (FieldMeta fieldMeta : fieldMetas)
248                                            result.put(fieldMeta.getRole(), fieldMeta);
249                            }
250    
251                            this.role2SubFieldMeta = result;
252                    }
253                    else
254                            logger.trace("getRole2SubFieldMeta: this.role2SubFieldMeta != null (already populated). this={}", this);
255    
256                    return result;
257            }
258    
259            /**
260             * Get the non-persistent field-number in DataNucleus' meta-data. This is only a usable value,
261             * if this <code>FieldMeta</code> was obtained via
262             * {@link Cumulus4jStoreManager#getClassMeta(org.datanucleus.store.ExecutionContext, Class)}; otherwise
263             * it is -1.
264             * @return the non-persistent field-number in DataNucleus' meta-data or -1.
265             */
266            public int getDataNucleusAbsoluteFieldNumber() {
267                    return dataNucleusAbsoluteFieldNumber;
268            }
269            public void setDataNucleusAbsoluteFieldNumber(int dataNucleusAbsoluteFieldNumber) {
270                    this.dataNucleusAbsoluteFieldNumber = dataNucleusAbsoluteFieldNumber;
271                    this.dataNucleusMemberMetaData = null;
272    
273                    for (FieldMeta subFM : getRole2SubFieldMeta().values())
274                            subFM.setDataNucleusAbsoluteFieldNumber(dataNucleusAbsoluteFieldNumber);
275            }
276    
277            /**
278             * Get a sub-field of this field or <code>null</code>, if no such sub-field exists.
279             * @param role the role of the sub-field. Must not be <code>null</code>.
280             * @return the sub-<code>FieldMeta</code> or <code>null</code>.
281             */
282            public FieldMeta getSubFieldMeta(FieldMetaRole role)
283            {
284                    if (role == null)
285                            throw new IllegalArgumentException("role == null");
286    
287                    return getRole2SubFieldMeta().get(role);
288            }
289    
290            /**
291             * Get all sub-fields' meta-data of this field. If there are no sub-fields, this is an
292             * empty collection.
293             * @return all sub-<code>FieldMeta</code>s of this field; never <code>null</code>.
294             */
295            public Collection<FieldMeta> getSubFieldMetas()
296            {
297                    return getRole2SubFieldMeta().values();
298            }
299    
300            public void addSubFieldMeta(FieldMeta subFieldMeta)
301            {
302                    if (!this.equals(subFieldMeta.getOwnerFieldMeta()))
303                            throw new IllegalArgumentException("this != subFieldMeta.ownerFieldMeta");
304    
305                    if (!this.fieldName.equals(subFieldMeta.getFieldName()))
306                            throw new IllegalArgumentException("this.fieldName != subFieldMeta.fieldName");
307    
308                    if (getSubFieldMeta(subFieldMeta.getRole()) != null)
309                            throw new IllegalArgumentException("There is already a subFieldMeta with role \"" + subFieldMeta.getRole() + "\"!");
310    
311                    subFieldMeta.setDataNucleusAbsoluteFieldNumber(dataNucleusAbsoluteFieldNumber);
312    
313                    PersistenceManager pm = getPersistenceManager();
314                    if (pm != null)
315                            subFieldMeta = pm.makePersistent(subFieldMeta);
316    
317                    getRole2SubFieldMeta().put(subFieldMeta.getRole(), subFieldMeta);
318            }
319    
320            public void removeSubFieldMeta(FieldMeta subFieldMeta)
321            {
322                    if (!this.equals(subFieldMeta.getOwnerFieldMeta()))
323                            throw new IllegalArgumentException("subFieldMeta.ownerFieldMeta != this");
324    
325                    getRole2SubFieldMeta().remove(subFieldMeta.getRole());
326                    PersistenceManager pm = getPersistenceManager();
327                    if (pm != null)
328                            pm.deletePersistent(subFieldMeta);
329            }
330    
331            public void removeAllSubFieldMetasExcept(FieldMetaRole ... roles)
332            {
333                    if (roles == null)
334                            roles = new FieldMetaRole[0];
335    
336                    Set<FieldMetaRole> rolesToKeep = new HashSet<FieldMetaRole>(roles.length);
337                    for (FieldMetaRole role : roles)
338                            rolesToKeep.add(role);
339    
340                    PersistenceManager pm = getPersistenceManager();
341                    Collection<FieldMetaRole> oldRoles = new ArrayList<FieldMetaRole>(getRole2SubFieldMeta().keySet());
342                    for (FieldMetaRole role : oldRoles) {
343                            if (!rolesToKeep.contains(role)) {
344                                    FieldMeta subFieldMeta = getRole2SubFieldMeta().remove(role);
345    
346                                    if (pm != null && subFieldMeta != null)
347                                            pm.deletePersistent(subFieldMeta);
348                            }
349                    }
350            }
351    
352            @NotPersistent
353            private transient FieldMeta mappedByFieldMeta;
354    
355            /**
356             * Used by {@link #getMappedByFieldMeta(ExecutionContext)} to mask <code>null</code> and thus
357             * prevent a second unnecessary resolve process if the first already resolved to <code>null</code>.
358             */
359            private static final FieldMeta NULL_MAPPED_BY_FIELD_META = new FieldMeta();
360    
361            /**
362             * <p>
363             * Get the {@link FieldMeta} of the opposite end of the mapped-by-relation. If
364             * this is not a mapped-by field, this method returns <code>null</code>.
365             * </p>
366             * <p>
367             * Though, it returns always the mapped-by opposite side, the semantics of
368             * this method still depend on the {@link #getRole() role} of this <code>FieldMeta</code>:
369             * </p>
370             * <ul>
371             * <li>{@link FieldMetaRole#primary}: Returns the owner-field on the opposite side which is referenced by
372             * &#64;Persistent(mappedBy="owner")</li>
373             * <li>{@link FieldMetaRole#mapKey}: Returns the key-field on the opposite side which is referenced by
374             * &#64;Key(mappedBy="key")</li>
375             * <li>{@link FieldMetaRole#mapValue}: Returns the value-field on the opposite side which is referenced by
376             * &#64;Value(mappedBy="value")</li>
377             * </ul>
378             *
379             * @return the {@link FieldMeta} of the other end of the mapped-by-relation.
380             */
381            public FieldMeta getMappedByFieldMeta(ExecutionContext executionContext)
382            {
383                    FieldMeta mbfm = mappedByFieldMeta;
384    
385                    if (NULL_MAPPED_BY_FIELD_META == mbfm)
386                            return null;
387    
388                    if (mbfm != null)
389                            return mbfm;
390    
391                    AbstractMemberMetaData mmd = getDataNucleusMemberMetaData(executionContext);
392    
393                    if (mmd.getMappedBy() != null)
394                    {
395                            Class<?> typeOppositeSide;
396                            if (mmd.hasCollection())
397                                    typeOppositeSide = executionContext.getClassLoaderResolver().classForName(mmd.getCollection().getElementType());
398                            else if (mmd.hasArray())
399                                    typeOppositeSide = executionContext.getClassLoaderResolver().classForName(mmd.getArray().getElementType());
400                            else if (mmd.hasMap()) {
401                                    if (mmd.getMap().keyIsPersistent())
402                                            typeOppositeSide = executionContext.getClassLoaderResolver().classForName(mmd.getMap().getKeyType());
403                                    else if (mmd.getMap().valueIsPersistent())
404                                            typeOppositeSide = executionContext.getClassLoaderResolver().classForName(mmd.getMap().getValueType());
405                                    else
406                                            throw new IllegalStateException("How can a Map be mapped-by without key and value being persistent?! Exactly one of them should be persistent!");
407                            }
408                            else
409                                    typeOppositeSide = mmd.getType();
410    
411                            Cumulus4jStoreManager storeManager = (Cumulus4jStoreManager) executionContext.getStoreManager();
412                            ClassMeta classMetaOppositeSide = storeManager.getClassMeta(executionContext, typeOppositeSide);
413                            String mappedBy = null;
414    
415                            switch (role) {
416                                    case primary:
417                                            mbfm = classMetaOppositeSide.getFieldMeta(mmd.getMappedBy());
418                                            break;
419    
420                                    case mapKey:
421                                            mappedBy = mmd.getKeyMetaData() == null ? null : mmd.getKeyMetaData().getMappedBy();
422                                            if (mmd.getMap().valueIsPersistent() && mappedBy == null)
423                                                    throw new IllegalStateException("The map's value is persistent via mappedBy (without @Join), but there is no @Key(mappedBy=\"...\")! This is invalid! " + mmd);
424                                            break;
425    
426                                    case mapValue:
427                                            mappedBy = mmd.getValueMetaData() == null ? null : mmd.getValueMetaData().getMappedBy();
428                                            if (mmd.getMap().keyIsPersistent() && mappedBy == null)
429                                                    throw new IllegalStateException("The map's key is persistent via mappedBy (without @Join), but there is no @Value(mappedBy=\"...\")! This is invalid! " + mmd);
430                                            break;
431                            }
432    
433                            if (mappedBy != null) {
434                                    mbfm = classMetaOppositeSide.getFieldMeta(mappedBy);
435                                    if (mbfm == null)
436                                            throw new IllegalStateException("Field \"" + mappedBy + "\" referenced in 'mappedBy' of " + this + " does not exist!");
437                            }
438                    }
439    
440                    if (mbfm == null)
441                            mappedByFieldMeta = NULL_MAPPED_BY_FIELD_META;
442                    else
443                            mappedByFieldMeta = mbfm;
444    
445                    return mbfm;
446            }
447    
448            protected static final ThreadLocal<Set<FieldMeta>> attachedFieldMetasInPostDetachThreadLocal = new ThreadLocal<Set<FieldMeta>>() {
449                    @Override
450                    protected Set<FieldMeta> initialValue() {
451                            return new HashSet<FieldMeta>();
452                    }
453            };
454    
455            @Override
456            public void jdoPreDetach() { }
457    
458            @Override
459            public void jdoPostDetach(Object o) {
460                    final FieldMeta attached = (FieldMeta) o;
461                    final FieldMeta detached = this;
462                    logger.debug("jdoPostDetach: attached={}", attached);
463                    detached.dataNucleusAbsoluteFieldNumber = attached.dataNucleusAbsoluteFieldNumber;
464    
465                    Set<FieldMeta> attachedFieldMetasInPostDetach = attachedFieldMetasInPostDetachThreadLocal.get();
466                    if (!attachedFieldMetasInPostDetach.add(attached)) {
467                            logger.debug("jdoPostDetach: Already in detachment => Skipping detachment of this.role2SubFieldMeta! attached={}", attached);
468                            return;
469                    }
470                    try {
471    
472                            PersistenceManager pm = attached.getPersistenceManager();
473                            if (pm == null)
474                                    throw new IllegalStateException("attached.getPersistenceManager() returned null!");
475    
476                            // The following field should already be null, but we better ensure that we never
477                            // contain *AT*tached objects inside a *DE*tached container.
478                            detached.role2SubFieldMeta = null;
479    
480                            Set<?> fetchGroups = pm.getFetchPlan().getGroups();
481                            if (fetchGroups.contains(javax.jdo.FetchGroup.ALL)) {
482                                    logger.debug("jdoPostDetach: Detaching this.role2SubFieldMeta: attached={}", attached);
483    
484                                    // if the fetch-groups say we should detach the FieldMetas, we do it.
485                                    HashMap<FieldMetaRole, FieldMeta> map = new HashMap<FieldMetaRole, FieldMeta>();
486                                    Collection<FieldMeta> detachedSubFieldMetas = pm.detachCopyAll(attached.getRole2SubFieldMeta().values());
487                                    for (FieldMeta detachedSubFieldMeta : detachedSubFieldMetas) {
488                                            detachedSubFieldMeta.setOwnerFieldMeta(this); // ensure, it's the identical (not only equal) FieldMeta.
489                                            map.put(detachedSubFieldMeta.getRole(), detachedSubFieldMeta);
490                                    }
491                                    detached.role2SubFieldMeta = map;
492                            }
493    
494                    } finally {
495                            attachedFieldMetasInPostDetach.remove(attached);
496                    }
497            }
498    
499            @Override
500            public int hashCode()
501            {
502                    return (int) (fieldID ^ (fieldID >>> 32));
503            }
504    
505            @Override
506            public boolean equals(Object obj)
507            {
508                    if (this == obj) return true;
509                    if (obj == null) return false;
510                    if (getClass() != obj.getClass()) return false;
511                    FieldMeta other = (FieldMeta) obj;
512                    return this.fieldID == other.fieldID;
513            }
514    
515            @Override
516            public String toString()
517            {
518                    ClassMeta cm = getClassMeta();
519                    return (
520                                    this.getClass().getName()
521                                    + '@'
522                                    + Integer.toHexString(System.identityHashCode(this))
523                                    + '['
524                                    + fieldID + ',' + (cm == null ? null : cm.getClassName()) + '#' + getFieldName() + '[' + role + ']'
525                                    + ']'
526                    );
527            }
528    
529            @NotPersistent
530            private AbstractMemberMetaData dataNucleusMemberMetaData;
531    
532            public AbstractMemberMetaData getDataNucleusMemberMetaData(ExecutionContext executionContext)
533            {
534                    if (dataNucleusMemberMetaData != null)
535                            return dataNucleusMemberMetaData;
536    
537                    AbstractClassMetaData dnClassMetaData = getClassMeta().getDataNucleusClassMetaData(executionContext);
538    
539                    int dnFieldNumber = getDataNucleusAbsoluteFieldNumber();
540                    if (dnFieldNumber < 0)
541                            throw new IllegalStateException("The method getDataNucleusMemberMetaData(...) can only be called on FieldMeta instances that were obtained via Cumulus4jStoreManager#getClassMeta(org.datanucleus.store.ExecutionContext, Class)!!!");
542    
543                    AbstractMemberMetaData dnMemberMetaData = dnClassMetaData.getMetaDataForManagedMemberAtAbsolutePosition(dnFieldNumber);
544                    if (dnMemberMetaData == null)
545                            throw new IllegalStateException("DataNucleus has no meta-data for this field: fieldID=" + getFieldID() + " className=" + classMeta.getClassName() + " fieldName=" + getFieldName());
546    
547                    dataNucleusMemberMetaData = dnMemberMetaData;
548                    return dnMemberMetaData;
549            }
550    }