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.Collection;
021    import java.util.HashMap;
022    import java.util.Map;
023    
024    import javax.jdo.FetchPlan;
025    import javax.jdo.JDOObjectNotFoundException;
026    import javax.jdo.PersistenceManager;
027    import javax.jdo.annotations.Column;
028    import javax.jdo.annotations.FetchGroup;
029    import javax.jdo.annotations.FetchGroups;
030    import javax.jdo.annotations.IdGeneratorStrategy;
031    import javax.jdo.annotations.IdentityType;
032    import javax.jdo.annotations.Key;
033    import javax.jdo.annotations.NotPersistent;
034    import javax.jdo.annotations.NullValue;
035    import javax.jdo.annotations.PersistenceCapable;
036    import javax.jdo.annotations.Persistent;
037    import javax.jdo.annotations.PrimaryKey;
038    import javax.jdo.annotations.Queries;
039    import javax.jdo.annotations.Query;
040    import javax.jdo.annotations.Unique;
041    import javax.jdo.annotations.Version;
042    import javax.jdo.annotations.VersionStrategy;
043    
044    import org.datanucleus.metadata.AbstractClassMetaData;
045    import org.datanucleus.store.ExecutionContext;
046    
047    /**
048     * Persistent meta-data for a persistence-capable {@link Class}. Since class names are very long,
049     * we use the {@link #getClassID() classID} instead in our index and data entities (e.g. in the relation
050     * {@link DataEntry#getClassMeta() DataEntry.classMeta}).
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    @Unique(name="ClassMeta_fullyQualifiedClassName", members={"packageName", "simpleClassName"})
057    @FetchGroups({
058            @FetchGroup(name=FetchPlan.ALL, members={
059                            @Persistent(name="superClassMeta", recursionDepth=-1)
060            })
061    })
062    @Queries({
063            @Query(
064                            name="getClassMetaByPackageNameAndSimpleClassName",
065                            value="SELECT UNIQUE WHERE this.packageName == :packageName && this.simpleClassName == :simpleClassName"
066            )
067    })
068    public class ClassMeta
069    {
070            public static ClassMeta getClassMeta(PersistenceManager pm, String packageName, String simpleClassName, boolean throwExceptionIfNotFound)
071            {
072                    javax.jdo.Query q = pm.newNamedQuery(ClassMeta.class, "getClassMetaByPackageNameAndSimpleClassName");
073                    ClassMeta result = (ClassMeta) q.execute(packageName, simpleClassName);
074    
075                    if (result == null && throwExceptionIfNotFound)
076                            throw new JDOObjectNotFoundException(
077                                            "No ClassMeta found for packageName=\"" + packageName + "\" and simpleClassName=\"" + simpleClassName + "\"!"
078                            );
079    
080                    return result;
081            }
082    
083            public static ClassMeta getClassMeta(PersistenceManager pm, Class<?> clazz, boolean throwExceptionIfNotFound)
084            {
085                    String packageName = clazz.getPackage() == null ? "" : clazz.getPackage().getName();
086                    String simpleClassName = clazz.getSimpleName();
087                    return getClassMeta(pm, packageName, simpleClassName, throwExceptionIfNotFound);
088            }
089    
090            @PrimaryKey
091            @Persistent(valueStrategy=IdGeneratorStrategy.NATIVE)
092            private long classID = -1;
093    
094            @NotPersistent
095            private transient String className;
096    
097            @Persistent(nullValue=NullValue.EXCEPTION)
098            @Column(length=255)
099            private String packageName;
100    
101            @Persistent(nullValue=NullValue.EXCEPTION)
102            @Column(length=255)
103            private String simpleClassName;
104    
105            private ClassMeta superClassMeta;
106    
107            @Persistent(mappedBy="classMeta", dependentValue="true")
108            @Key(mappedBy="fieldName")
109            private Map<String, FieldMeta> fieldName2fieldMeta;
110    
111            @NotPersistent
112            private Map<Long, FieldMeta> fieldID2fieldMeta;
113    
114            protected ClassMeta() { }
115    
116            public ClassMeta(Class<?> clazz) {
117                    this.packageName = clazz.getPackage() == null ? "" : clazz.getPackage().getName();
118                    this.simpleClassName = clazz.getSimpleName();
119                    this.fieldName2fieldMeta = new HashMap<String, FieldMeta>();
120            }
121    
122            public long getClassID() {
123                    return classID;
124            }
125    
126            /**
127             * Get the package name or an empty <code>String</code> for the default package.
128             * @return the package name (maybe empty, but never <code>null</code>).
129             */
130            public String getPackageName() {
131                    return packageName;
132            }
133    
134            public String getSimpleClassName() {
135                    return simpleClassName;
136            }
137    
138            /**
139             * Get the fully qualified class name (composed of {@link #getPackageName() packageName} and {@link #getSimpleClassName() simpleClassName}).
140             * @return the fully qualified class name.
141             */
142            public String getClassName()
143            {
144                    String cn = className;
145                    if (cn == null) {
146                            if (packageName.isEmpty())
147                                    cn = simpleClassName;
148                            else
149                                    cn = packageName + '.' + simpleClassName;
150    
151                            className = cn;
152                    }
153                    return cn;
154            }
155    
156            /**
157             * The super-class' meta-data or <code>null</code>, if there is no <b>persistence-capable</b> super-class.
158             * @return the super-class' meta-data or <code>null</code>.
159             */
160            public ClassMeta getSuperClassMeta() {
161                    return superClassMeta;
162            }
163    
164            public void setSuperClassMeta(ClassMeta superClassMeta) {
165                    this.superClassMeta = superClassMeta;
166            }
167    
168            /**
169             * Get all {@link FieldMeta} instances known to this instance. This is the meta-data for all fields
170             * <b>directly declared</b> in the class referenced by this <code>ClassMeta</code> <b>not
171             * including super-classes</b>.
172             * @return Collection of FieldMeta objects for this class
173             */
174            public Collection<FieldMeta> getFieldMetas() {
175                    return fieldName2fieldMeta.values();
176            }
177    
178            /**
179             * Get the {@link FieldMeta} for a field that is <b>directly declared</b> in the class referenced by
180             * this <code>ClassMeta</code>. This method thus does not take super-classes into account.
181             *
182             * @param fieldName the simple field name (no class prefix).
183             * @return the {@link FieldMeta} corresponding to the specified <code>fieldName</code> or <code>null</code>, if no such field
184             * exists.
185             * @see #getFieldMeta(long)
186             * @see #getFieldMeta(String, String)
187             */
188            public FieldMeta getFieldMeta(String fieldName) {
189                    return fieldName2fieldMeta.get(fieldName);
190            }
191    
192            /**
193             * <p>
194             * Get the {@link FieldMeta} for a field that is either directly declared in the class referenced by this
195             * <code>ClassMeta</code> or in a super-class.
196             * </p>
197             * <p>
198             * If <code>className</code> is <code>null</code>, this method
199             * searches recursively in the inheritance hierarchy upwards (i.e. first this class then the super-class,
200             * then the next super-class etc.) until it finds a field matching the given <code>fieldName</code>.
201             * </p>
202             * <p>
203             * If <code>className</code> is not <code>null</code>, this method searches only in the specified class.
204             * If <code>className</code> is neither the current class nor any super-class, this method always returns
205             * <code>null</code>.
206             * </p>
207             *
208             * @param className the fully qualified class-name of the class referenced by this <code>ClassMeta</code>
209             * or any super-class. <code>null</code> to search the entire class hierarchy upwards (through all super-classes
210             * until the field is found or the last super-class was investigated).
211             * @param fieldName the simple field name (no class prefix).
212             * @return the {@link FieldMeta} matching the given criteria or <code>null</code> if no such field could be found.
213             */
214            public FieldMeta getFieldMeta(String className, String fieldName) {
215                    if (className == null) {
216                            FieldMeta fieldMeta = getFieldMeta(fieldName);
217                            if (fieldMeta != null)
218                                    return fieldMeta;
219    
220                            if (superClassMeta != null)
221                                    return superClassMeta.getFieldMeta(className, fieldName);
222                            else
223                                    return null;
224                    }
225                    else {
226                            if (getClassName().equals(className))
227                                    return getFieldMeta(fieldName);
228                            else if (superClassMeta != null)
229                                    return superClassMeta.getFieldMeta(className, fieldName);
230                            else
231                                    return null;
232                    }
233            }
234    
235            /**
236             * Get the {@link FieldMeta} with the specified {@link FieldMeta#getFieldID() fieldID}. It does not matter, if
237             * this field is directly in the class referenced by this <code>ClassMeta</code> or in a super-class.
238             * @param fieldID the {@link FieldMeta#getFieldID() fieldID} of the <code>FieldMeta</code> to be found.
239             * @return the {@link FieldMeta} referenced by the given <code>fieldID</code> or <code>null</code>, if no such
240             * field exists in the class or any super-class.
241             */
242            public FieldMeta getFieldMeta(long fieldID)
243            {
244                    Map<Long, FieldMeta> m = fieldID2fieldMeta;
245    
246                    if (m == null) {
247                            m = new HashMap<Long, FieldMeta>(fieldName2fieldMeta.size());
248                            for (FieldMeta fieldMeta : fieldName2fieldMeta.values())
249                                    m.put(fieldMeta.getFieldID(), fieldMeta);
250    
251                            fieldID2fieldMeta = m;
252                    }
253    
254                    FieldMeta fieldMeta = m.get(fieldID);
255                    if (fieldMeta != null)
256                            return fieldMeta;
257    
258                    if (superClassMeta != null)
259                            return superClassMeta.getFieldMeta(fieldID);
260                    else
261                            return null;
262            }
263    
264            public void addFieldMeta(FieldMeta fieldMeta) {
265                    if (!this.equals(fieldMeta.getClassMeta()))
266                            throw new IllegalArgumentException("fieldMeta.classMeta != this");
267    
268                    fieldName2fieldMeta.put(fieldMeta.getFieldName(), fieldMeta);
269                    fieldID2fieldMeta = null;
270            }
271    
272            public void removeFieldMeta(FieldMeta fieldMeta) {
273                    if (!this.equals(fieldMeta.getClassMeta()))
274                            throw new IllegalArgumentException("fieldMeta.classMeta != this");
275    
276                    fieldName2fieldMeta.remove(fieldMeta.getFieldName());
277                    fieldID2fieldMeta = null;
278            }
279    
280            @Override
281            public int hashCode() {
282                    return (int) (classID ^ (classID >>> 32));
283            }
284    
285            @Override
286            public boolean equals(Object obj) {
287                    if (this == obj) return true;
288                    if (obj == null) return false;
289                    if (getClass() != obj.getClass()) return false;
290                    ClassMeta other = (ClassMeta) obj;
291                    return this.classID == other.classID;
292            }
293    
294            @Override
295            public String toString() {
296                    return (
297                                    this.getClass().getName()
298                                    + '@'
299                                    + Integer.toHexString(System.identityHashCode(this))
300                                    + '[' + classID + ',' + getClassName() + ']'
301                    );
302            }
303    
304            @NotPersistent
305            private AbstractClassMetaData dataNucleusClassMetaData;
306    
307            public AbstractClassMetaData getDataNucleusClassMetaData(ExecutionContext executionContext)
308            {
309                    if (dataNucleusClassMetaData != null)
310                            return dataNucleusClassMetaData;
311    
312                    AbstractClassMetaData dnClassMetaData = executionContext.getMetaDataManager().getMetaDataForClass(getClassName(), executionContext.getClassLoaderResolver());
313                    if (dnClassMetaData == null)
314                            throw new IllegalStateException("DataNucleus does not know any meta-data for this class: classID=" + getClassID() + " className=" + getClassName());
315    
316                    dataNucleusClassMetaData = dnClassMetaData;
317                    return dnClassMetaData;
318            }
319    }