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.annotations.Column;
028    import javax.jdo.annotations.IdGeneratorStrategy;
029    import javax.jdo.annotations.IdentityType;
030    import javax.jdo.annotations.Key;
031    import javax.jdo.annotations.NotPersistent;
032    import javax.jdo.annotations.NullValue;
033    import javax.jdo.annotations.PersistenceCapable;
034    import javax.jdo.annotations.Persistent;
035    import javax.jdo.annotations.PrimaryKey;
036    import javax.jdo.annotations.Unique;
037    import javax.jdo.annotations.Uniques;
038    import javax.jdo.annotations.Version;
039    import javax.jdo.annotations.VersionStrategy;
040    import javax.jdo.listener.DetachCallback;
041    
042    import org.cumulus4j.store.Cumulus4jStoreManager;
043    import org.datanucleus.metadata.AbstractClassMetaData;
044    import org.datanucleus.metadata.AbstractMemberMetaData;
045    import org.datanucleus.store.ExecutionContext;
046    
047    /**
048     * Persistent meta-data for a field of a persistence-capable class. Since class- and field-names are very
049     * long we reference them indirectly via the long-identifiers of {@link ClassMeta} and {@link FieldMeta},
050     * e.g. in the relation {@link IndexEntry#getFieldMeta() IndexEntry.fieldMeta}.
051     *
052     * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
053     */
054    @PersistenceCapable(identityType=IdentityType.APPLICATION, detachable="true")
055    @Version(strategy=VersionStrategy.VERSION_NUMBER)
056    @Uniques({
057            @Unique(name="FieldMeta_classMeta_ownerFieldMeta_fieldName_role", members={"classMeta", "ownerFieldMeta", "fieldName", "role"})
058    })
059    public class FieldMeta
060    implements DetachCallback
061    {
062            @PrimaryKey
063            @Persistent(valueStrategy=IdGeneratorStrategy.NATIVE)
064            private long fieldID = -1;
065    
066            private ClassMeta classMeta;
067    
068            private FieldMeta ownerFieldMeta;
069    
070            @Persistent(nullValue=NullValue.EXCEPTION)
071            @Column(length=255)
072            private String fieldName;
073    
074            @Persistent(nullValue=NullValue.EXCEPTION)
075            private FieldMetaRole role;
076    
077            @NotPersistent
078            private int dataNucleusAbsoluteFieldNumber = -1;
079    
080            @Persistent(mappedBy="ownerFieldMeta", dependentValue="true")
081            @Key(mappedBy="role")
082            private Map<FieldMetaRole, FieldMeta> role2subFieldMeta = new HashMap<FieldMetaRole, FieldMeta>();
083    
084            /**
085             * Internal constructor. This exists only for JDO and should not be used by application code!
086             */
087            protected FieldMeta() { }
088    
089            /**
090             * Create a <code>FieldMeta</code> referencing a real field.
091             * @param classMeta the class to which this field belongs.
092             * @param fieldName the field's name.
093             * @see #FieldMeta(FieldMeta, FieldMetaRole)
094             */
095            public FieldMeta(ClassMeta classMeta, String fieldName)
096            {
097                    this(classMeta, null, fieldName, FieldMetaRole.primary);
098            }
099            /**
100             * Create a <code>FieldMeta</code> referencing a part of a field. This is necessary to index keys and values of a
101             * <code>Map</code> field (i.e. 2 separate indexes for one field) as well as <code>Collection</code>-elements and similar.
102             * @param ownerFieldMeta the <code>FieldMeta</code> of the real field (to which the part belongs).
103             * @param role the role (aka type) of the sub-field (aka part).
104             * @see #FieldMeta(ClassMeta, String)
105             */
106            public FieldMeta(FieldMeta ownerFieldMeta, FieldMetaRole role)
107            {
108                    this(null, ownerFieldMeta, ownerFieldMeta.getFieldName(), role);
109            }
110    
111            /**
112             * Internal constructor. This exists only for easier implementation of the other constructors and
113             * should not be used by application code!
114             */
115            protected FieldMeta(ClassMeta classMeta, FieldMeta ownerFieldMeta, String fieldName, FieldMetaRole role)
116            {
117                    if (classMeta == null && ownerFieldMeta == null)
118                            throw new IllegalArgumentException("classMeta == null && ownerFieldMeta == null");
119    
120                    if (classMeta != null && ownerFieldMeta != null)
121                            throw new IllegalArgumentException("classMeta != null && ownerFieldMeta != null");
122    
123                    if (fieldName == null)
124                            throw new IllegalArgumentException("fieldName == null");
125    
126                    if (role == null)
127                            throw new IllegalArgumentException("role == null");
128    
129                    this.classMeta = classMeta;
130                    this.ownerFieldMeta = ownerFieldMeta;
131                    this.fieldName = fieldName;
132                    this.role = role;
133            }
134    
135            public long getFieldID() {
136                    return fieldID;
137            }
138    
139            /**
140             * Get the {@link ClassMeta} to which this <code>FieldMeta</code> belongs. Every FieldMeta
141             * belongs to exactly one {@link ClassMeta} just like a field is declared in exactly one Java class.
142             * Note, that a {@link FieldMeta} might belong to another FieldMeta in order to reference sub-field-properties,
143             * e.g. a {@link Map}'s key. In this case, the direct property <code>classMeta</code> is <code>null</code>, but this method
144             * still returns the correct {@link ClassMeta} by resolving it indirectly via the {@link #getOwnerFieldMeta() ownerFieldMeta}.
145             * @return the {@link ClassMeta} to which this instance of <code>FieldMeta</code> belongs.
146             */
147            public ClassMeta getClassMeta() {
148                    if (ownerFieldMeta != null)
149                            return ownerFieldMeta.getClassMeta();
150    
151                    return classMeta;
152            }
153    
154            /**
155             * Get the {@link FieldMetaRole#primary primary} {@link FieldMeta}, to which this sub-<code>FieldMeta</code> belongs
156             * or <code>null</code>, if this <code>FieldMeta</code> is primary.
157             * @return the owning primary field-meta or <code>null</code>.
158             */
159            public FieldMeta getOwnerFieldMeta() {
160                    return ownerFieldMeta;
161            }
162    
163            /**
164             * Get the simple field name (no class prefix) of the field referenced by this meta-data-instance.
165             * @return the simple field name.
166             */
167            public String getFieldName() {
168                    return fieldName;
169            }
170    
171            /**
172             * Get the role of the (sub-)field. If this is not a sub-field, but a primary field
173             * (i.e. directly meaning a real field of the class referenced by {@link #getClassMeta() classMeta})
174             * it will be {@link FieldMetaRole#primary}, hence this method never returns <code>null</code>.
175             * @return the role of this <code>FieldMeta</code>; never <code>null</code>.
176             */
177            public FieldMetaRole getRole() {
178                    return role;
179            }
180    
181            /**
182             * Get the non-persistent field-number in DataNucleus' meta-data. This is only a usable value,
183             * if this <code>FieldMeta</code> was obtained via
184             * {@link Cumulus4jStoreManager#getClassMeta(org.datanucleus.store.ExecutionContext, Class)}; otherwise
185             * it is -1.
186             * @return the non-persistent field-number in DataNucleus' meta-data or -1.
187             */
188            public int getDataNucleusAbsoluteFieldNumber() {
189                    return dataNucleusAbsoluteFieldNumber;
190            }
191            public void setDataNucleusAbsoluteFieldNumber(int dataNucleusAbsoluteFieldNumber) {
192                    this.dataNucleusAbsoluteFieldNumber = dataNucleusAbsoluteFieldNumber;
193                    this.dataNucleusMemberMetaData = null;
194    
195                    for (FieldMeta subFM : role2subFieldMeta.values())
196                            subFM.setDataNucleusAbsoluteFieldNumber(dataNucleusAbsoluteFieldNumber);
197            }
198    
199            /**
200             * Get a sub-field of this field or <code>null</code>, if no such sub-field exists.
201             * @param role the role of the sub-field. Must not be <code>null</code>.
202             * @return the sub-<code>FieldMeta</code> or <code>null</code>.
203             */
204            public FieldMeta getSubFieldMeta(FieldMetaRole role)
205            {
206                    if (role == null)
207                            throw new IllegalArgumentException("role == null");
208    
209                    return role2subFieldMeta.get(role);
210            }
211    
212            /**
213             * Get all sub-fields' meta-data of this field. If there are no sub-fields, this is an
214             * empty collection.
215             * @return all sub-<code>FieldMeta</code>s of this field; never <code>null</code>.
216             */
217            public Collection<FieldMeta> getSubFieldMetas()
218            {
219                    return role2subFieldMeta.values();
220            }
221    
222            public void addSubFieldMeta(FieldMeta subFieldMeta)
223            {
224                    if (!this.equals(subFieldMeta.getOwnerFieldMeta()))
225                            throw new IllegalArgumentException("this != subFieldMeta.ownerFieldMeta");
226    
227                    if (!this.fieldName.equals(subFieldMeta.getFieldName()))
228                            throw new IllegalArgumentException("this.fieldName != subFieldMeta.fieldName");
229    
230                    if (getSubFieldMeta(subFieldMeta.getRole()) != null)
231                            throw new IllegalArgumentException("There is already a subFieldMeta with role \"" + subFieldMeta.getRole() + "\"!");
232    
233                    subFieldMeta.setDataNucleusAbsoluteFieldNumber(dataNucleusAbsoluteFieldNumber);
234                    role2subFieldMeta.put(subFieldMeta.getRole(), subFieldMeta);
235            }
236    
237            public void removeSubFieldMeta(FieldMeta fieldMeta)
238            {
239                    role2subFieldMeta.remove(fieldMeta.getRole());
240            }
241    
242            public void removeAllSubFieldMetasExcept(FieldMetaRole ... roles)
243            {
244                    if (roles == null)
245                            roles = new FieldMetaRole[0];
246    
247                    Set<FieldMetaRole> rolesToKeep = new HashSet<FieldMetaRole>(roles.length);
248                    for (FieldMetaRole role : roles)
249                            rolesToKeep.add(role);
250    
251                    Collection<FieldMetaRole> oldRoles = new ArrayList<FieldMetaRole>(role2subFieldMeta.keySet());
252                    for (FieldMetaRole role : oldRoles) {
253                            if (!rolesToKeep.contains(role))
254                                    role2subFieldMeta.remove(role);
255                    }
256            }
257    
258            @NotPersistent
259            private transient FieldMeta mappedByFieldMeta;
260    
261            /**
262             * Used by {@link #getMappedByFieldMeta(ExecutionContext)} to mask <code>null</code> and thus
263             * prevent a second unnecessary resolve process if the first already resolved to <code>null</code>.
264             */
265            private static final FieldMeta NULL_MAPPED_BY_FIELD_META = new FieldMeta();
266    
267            /**
268             * <p>
269             * Get the {@link FieldMeta} of the opposite end of the mapped-by-relation. If
270             * this is not a mapped-by field, this method returns <code>null</code>.
271             * </p>
272             * <p>
273             * Though, it returns always the mapped-by opposite side, the semantics of
274             * this method still depend on the {@link #getRole() role} of this <code>FieldMeta</code>:
275             * </p>
276             * <ul>
277             * <li>{@link FieldMetaRole#primary}: Returns the owner-field on the opposite side which is referenced by
278             * &#64;Persistent(mappedBy="owner")</li>
279             * <li>{@link FieldMetaRole#mapKey}: Returns the key-field on the opposite side which is referenced by
280             * &#64;Key(mappedBy="key")</li>
281             * <li>{@link FieldMetaRole#mapValue}: Returns the value-field on the opposite side which is referenced by
282             * &#64;Value(mappedBy="value")</li>
283             * </ul>
284             *
285             * @return the {@link FieldMeta} of the other end of the mapped-by-relation.
286             */
287            public FieldMeta getMappedByFieldMeta(ExecutionContext executionContext)
288            {
289                    FieldMeta mbfm = mappedByFieldMeta;
290    
291                    if (NULL_MAPPED_BY_FIELD_META == mbfm)
292                            return null;
293    
294                    if (mbfm != null)
295                            return mbfm;
296    
297                    AbstractMemberMetaData mmd = getDataNucleusMemberMetaData(executionContext);
298    
299                    if (mmd.getMappedBy() != null)
300                    {
301                            Class<?> typeOppositeSide;
302                            if (mmd.hasCollection())
303                                    typeOppositeSide = executionContext.getClassLoaderResolver().classForName(mmd.getCollection().getElementType());
304                            else if (mmd.hasArray())
305                                    typeOppositeSide = executionContext.getClassLoaderResolver().classForName(mmd.getArray().getElementType());
306                            else if (mmd.hasMap()) {
307                                    if (mmd.getMap().keyIsPersistent())
308                                            typeOppositeSide = executionContext.getClassLoaderResolver().classForName(mmd.getMap().getKeyType());
309                                    else if (mmd.getMap().valueIsPersistent())
310                                            typeOppositeSide = executionContext.getClassLoaderResolver().classForName(mmd.getMap().getValueType());
311                                    else
312                                            throw new IllegalStateException("How can a Map be mapped-by without key and value being persistent?! Exactly one of them should be persistent!");
313                            }
314                            else
315                                    typeOppositeSide = mmd.getType();
316    
317                            Cumulus4jStoreManager storeManager = (Cumulus4jStoreManager) executionContext.getStoreManager();
318                            ClassMeta classMetaOppositeSide = storeManager.getClassMeta(executionContext, typeOppositeSide);
319                            String mappedBy = null;
320    
321                            switch (role) {
322                                    case primary:
323                                            mbfm = classMetaOppositeSide.getFieldMeta(mmd.getMappedBy());
324                                            break;
325    
326                                    case mapKey:
327                                            mappedBy = mmd.getKeyMetaData() == null ? null : mmd.getKeyMetaData().getMappedBy();
328                                            if (mmd.getMap().valueIsPersistent() && mappedBy == null)
329                                                    throw new IllegalStateException("The map's value is persistent via mappedBy (without @Join), but there is no @Key(mappedBy=\"...\")! This is invalid! " + mmd);
330                                            break;
331    
332                                    case mapValue:
333                                            mappedBy = mmd.getValueMetaData() == null ? null : mmd.getValueMetaData().getMappedBy();
334                                            if (mmd.getMap().keyIsPersistent() && mappedBy == null)
335                                                    throw new IllegalStateException("The map's key is persistent via mappedBy (without @Join), but there is no @Value(mappedBy=\"...\")! This is invalid! " + mmd);
336                                            break;
337                            }
338    
339                            if (mappedBy != null) {
340                                    mbfm = classMetaOppositeSide.getFieldMeta(mappedBy);
341                                    if (mbfm == null)
342                                            throw new IllegalStateException("Field \"" + mappedBy + "\" referenced in 'mappedBy' of " + this + " does not exist!");
343                            }
344                    }
345    
346                    if (mbfm == null)
347                            mappedByFieldMeta = NULL_MAPPED_BY_FIELD_META;
348                    else
349                            mappedByFieldMeta = mbfm;
350    
351                    return mbfm;
352            }
353    
354            @Override
355            public void jdoPreDetach() { }
356    
357            @Override
358            public void jdoPostDetach(Object o) {
359                    FieldMeta attached = (FieldMeta) o;
360                    FieldMeta detached = this;
361                    detached.dataNucleusAbsoluteFieldNumber = attached.dataNucleusAbsoluteFieldNumber;
362            }
363    
364            @Override
365            public int hashCode()
366            {
367                    return (int) (fieldID ^ (fieldID >>> 32));
368            }
369    
370            @Override
371            public boolean equals(Object obj)
372            {
373                    if (this == obj) return true;
374                    if (obj == null) return false;
375                    if (getClass() != obj.getClass()) return false;
376                    FieldMeta other = (FieldMeta) obj;
377                    return this.fieldID == other.fieldID;
378            }
379    
380            @Override
381            public String toString()
382            {
383                    ClassMeta cm = getClassMeta();
384                    return (
385                                    this.getClass().getName()
386                                    + '@'
387                                    + Integer.toHexString(System.identityHashCode(this))
388                                    + '['
389                                    + fieldID + ',' + (cm == null ? null : cm.getClassName()) + '#' + getFieldName() + '[' + role + ']'
390                                    + ']'
391                    );
392            }
393    
394            @NotPersistent
395            private AbstractMemberMetaData dataNucleusMemberMetaData;
396    
397            public AbstractMemberMetaData getDataNucleusMemberMetaData(ExecutionContext executionContext)
398            {
399                    if (dataNucleusMemberMetaData != null)
400                            return dataNucleusMemberMetaData;
401    
402                    AbstractClassMetaData dnClassMetaData = getClassMeta().getDataNucleusClassMetaData(executionContext);
403    
404                    int dnFieldNumber = getDataNucleusAbsoluteFieldNumber();
405                    if (dnFieldNumber < 0)
406                            throw new IllegalStateException("The method getDataNucleusMemberMetaData(...) can only be called on FieldMeta instances that were obtained via Cumulus4jStoreManager#getClassMeta(org.datanucleus.store.ExecutionContext, Class)!!!");
407    
408                    AbstractMemberMetaData dnMemberMetaData = dnClassMetaData.getMetaDataForManagedMemberAtAbsolutePosition(dnFieldNumber);
409                    if (dnMemberMetaData == null)
410                            throw new IllegalStateException("DataNucleus has no meta-data for this field: fieldID=" + getFieldID() + " className=" + classMeta.getClassName() + " fieldName=" + getFieldName());
411    
412                    dataNucleusMemberMetaData = dnMemberMetaData;
413                    return dnMemberMetaData;
414            }
415    }