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 }