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 * @Persistent(mappedBy="owner")</li>
279 * <li>{@link FieldMetaRole#mapKey}: Returns the key-field on the opposite side which is referenced by
280 * @Key(mappedBy="key")</li>
281 * <li>{@link FieldMetaRole#mapValue}: Returns the value-field on the opposite side which is referenced by
282 * @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 }