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 * @Persistent(mappedBy="ownerFieldMeta", dependentValue="true")
104 * @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 * @Persistent(mappedBy="owner")</li>
373 * <li>{@link FieldMetaRole#mapKey}: Returns the key-field on the opposite side which is referenced by
374 * @Key(mappedBy="key")</li>
375 * <li>{@link FieldMetaRole#mapValue}: Returns the value-field on the opposite side which is referenced by
376 * @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 }