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.Discriminator;
031 import javax.jdo.annotations.DiscriminatorStrategy;
032 import javax.jdo.annotations.IdGeneratorStrategy;
033 import javax.jdo.annotations.IdentityType;
034 import javax.jdo.annotations.NotPersistent;
035 import javax.jdo.annotations.NullValue;
036 import javax.jdo.annotations.PersistenceCapable;
037 import javax.jdo.annotations.Persistent;
038 import javax.jdo.annotations.PrimaryKey;
039 import javax.jdo.annotations.Queries;
040 import javax.jdo.annotations.Query;
041 import javax.jdo.annotations.Unique;
042 import javax.jdo.annotations.Uniques;
043 import javax.jdo.annotations.Version;
044 import javax.jdo.annotations.VersionStrategy;
045 import javax.jdo.listener.DetachCallback;
046 import javax.jdo.listener.StoreCallback;
047
048 import org.cumulus4j.store.Cumulus4jStoreManager;
049 import org.datanucleus.ExecutionContext;
050 import org.datanucleus.metadata.AbstractClassMetaData;
051 import org.datanucleus.metadata.AbstractMemberMetaData;
052 import org.datanucleus.metadata.MetaDataManager;
053 import org.slf4j.Logger;
054 import org.slf4j.LoggerFactory;
055
056 /**
057 * Persistent meta-data for a field of a persistence-capable class. Since class- and field-names are very
058 * long we reference them indirectly via the long-identifiers of {@link ClassMeta} and {@link FieldMeta},
059 * e.g. in the relation {@link IndexEntry#getFieldMeta() IndexEntry.fieldMeta}.
060 *
061 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
062 */
063 @PersistenceCapable(identityType=IdentityType.APPLICATION, detachable="true")
064 @Discriminator(
065 strategy=DiscriminatorStrategy.VALUE_MAP, value="FieldMeta",
066 columns=@Column(name="discriminator", defaultValue="FieldMeta", length=100)
067 )
068 @Version(strategy=VersionStrategy.VERSION_NUMBER)
069 @Uniques({
070 @Unique(name="FieldMeta_classMeta_ownerFieldMeta_fieldName_role", members={"uniqueScope", "classMeta_classID", "ownerFieldMeta_fieldID", "fieldName", "role"})
071 })
072 @Queries({
073 @Query(name=FieldMeta.NamedQueries.getFieldMetasForClassMeta_classID, value="SELECT WHERE this.classMeta_classID == :classMeta_classID"),
074 @Query(name=FieldMeta.NamedQueries.getSubFieldMetasForFieldMeta_fieldID, value="SELECT WHERE this.ownerFieldMeta_fieldID == :ownerFieldMeta_fieldID")
075 })
076 public class FieldMeta
077 implements DetachCallback, StoreCallback
078 {
079 private static final Logger logger = LoggerFactory.getLogger(FieldMeta.class);
080
081 protected static final String UNIQUE_SCOPE_FIELD_META = "FieldMeta";
082
083 protected static class NamedQueries {
084 public static final String getFieldMetasForClassMeta_classID = "getFieldMetasForClassMeta_classID";
085 public static final String getSubFieldMetasForFieldMeta_fieldID = "getSubFieldMetasForFieldMeta_fieldID";
086 }
087
088 @PrimaryKey
089 @Persistent(valueStrategy=IdGeneratorStrategy.NATIVE, sequence="FieldMetaSequence")
090 private Long fieldID;
091
092 // /**
093 // * This is needed due to GAE compatibility. package.jdo is responsible
094 // * for the correct usage if this field.
095 // */
096 //// @NotPersistent // not persistent for non-GAE-datastores
097 // private String fieldIDString;
098
099 @Persistent(nullValue=NullValue.EXCEPTION)
100 @Column(length=255, defaultValue=UNIQUE_SCOPE_FIELD_META)
101 private String uniqueScope;
102
103 @Column(name="classMeta_classID_oid") // for downward-compatibility
104 private Long classMeta_classID;
105
106 @NotPersistent
107 private ClassMeta classMeta;
108
109 @Column(name="ownerFieldMeta_fieldID_oid") // for downward-compatibility
110 private Long ownerFieldMeta_fieldID;
111
112 @NotPersistent
113 private FieldMeta ownerFieldMeta;
114
115 @Persistent(nullValue=NullValue.EXCEPTION)
116 @Column(length=255)
117 private String fieldName;
118
119 @Persistent(nullValue=NullValue.EXCEPTION)
120 private FieldMetaRole role;
121
122 @NotPersistent
123 private int dataNucleusAbsoluteFieldNumber = -1;
124
125 /**
126 * Meta data for all sub-fields of this <code>FieldMeta</code>.
127 * <p>
128 * This map is manually managed (e.g. lazy-loaded by {@link #getRole2SubFieldMeta()} or manually detached
129 * in {@link #jdoPostDetach(Object)}) because of constraints in GAE. We simulate the behaviour of:
130 * <p>
131 * <pre>
132 * @Persistent(mappedBy="ownerFieldMeta", dependentValue="true")
133 * @Key(mappedBy="role")
134 * </pre>
135 */
136 @NotPersistent
137 private Map<FieldMetaRole, FieldMeta> role2SubFieldMeta;
138
139 @NotPersistent
140 private boolean embeddedClassMetaLoaded;
141
142 @NotPersistent
143 private EmbeddedClassMeta embeddedClassMeta;
144
145 // @NotPersistent
146 // private Set<EmbeddedClassMeta> embeddedClassMetasToBeDeleted;
147
148 /**
149 * Internal constructor. This exists only for JDO and should not be used by application code!
150 */
151 protected FieldMeta() { }
152
153 /**
154 * Create a <code>FieldMeta</code> referencing a real field.
155 * @param classMeta the class to which this field belongs.
156 * @param fieldName the field's name.
157 * @see #FieldMeta(FieldMeta, FieldMetaRole)
158 */
159 public FieldMeta(ClassMeta classMeta, String fieldName)
160 {
161 this(classMeta, null, fieldName, FieldMetaRole.primary);
162 }
163 /**
164 * Create a <code>FieldMeta</code> referencing a part of a field. This is necessary to index keys and values of a
165 * <code>Map</code> field (i.e. 2 separate indexes for one field) as well as <code>Collection</code>-elements and similar.
166 * @param ownerFieldMeta the <code>FieldMeta</code> of the real field (to which the part belongs).
167 * @param role the role (aka type) of the sub-field (aka part).
168 * @see #FieldMeta(ClassMeta, String)
169 */
170 public FieldMeta(FieldMeta ownerFieldMeta, FieldMetaRole role)
171 {
172 this(null, ownerFieldMeta, ownerFieldMeta.getFieldName(), role);
173 }
174
175 /**
176 * Internal constructor. This exists only for easier implementation of the other constructors and
177 * should not be used by application code!
178 */
179 protected FieldMeta(ClassMeta classMeta, FieldMeta ownerFieldMeta, String fieldName, FieldMetaRole role)
180 {
181 if (classMeta == null && ownerFieldMeta == null)
182 throw new IllegalArgumentException("classMeta == null && ownerFieldMeta == null");
183
184 if (classMeta != null && ownerFieldMeta != null)
185 throw new IllegalArgumentException("classMeta != null && ownerFieldMeta != null");
186
187 if (fieldName == null)
188 throw new IllegalArgumentException("fieldName == null");
189
190 if (role == null)
191 throw new IllegalArgumentException("role == null");
192
193 this.setClassMeta(classMeta);
194 this.setOwnerFieldMeta(ownerFieldMeta);
195 this.fieldName = fieldName;
196 this.role = role;
197 setUniqueScope(UNIQUE_SCOPE_FIELD_META);
198 }
199
200 public long getFieldID() {
201 // if(fieldIDString != null && fieldID == null){
202 // fieldID = KeyFactory.getInstance().stringToKey(fieldIDString).getId();
203 // }
204 return fieldID == null ? -1 : fieldID;
205 }
206
207 protected String getUniqueScope() {
208 return uniqueScope;
209 }
210
211 protected void setUniqueScope(String uniqueScope) {
212 this.uniqueScope = uniqueScope;
213 }
214
215 /**
216 * Get the {@link ClassMeta} to which this <code>FieldMeta</code> belongs. Every FieldMeta
217 * belongs to exactly one {@link ClassMeta} just like a field is declared in exactly one Java class.
218 * Note, that a {@link FieldMeta} might belong to another FieldMeta in order to reference sub-field-properties,
219 * e.g. a {@link Map}'s key. In this case, the direct property <code>classMeta</code> is <code>null</code>, but this method
220 * still returns the correct {@link ClassMeta} by resolving it indirectly via the {@link #getOwnerFieldMeta() ownerFieldMeta}.
221 * @return the {@link ClassMeta} to which this instance of <code>FieldMeta</code> belongs.
222 */
223 public ClassMeta getClassMeta() {
224 if (getOwnerFieldMeta() != null)
225 return getOwnerFieldMeta().getClassMeta();
226
227 if (classMeta_classID == null) // should never happen but better check
228 return null;
229
230 if (classMeta == null)
231 classMeta = new ClassMetaDAO(getPersistenceManager()).getClassMeta(classMeta_classID, true);
232
233 return classMeta;
234 }
235
236 protected void setClassMeta(ClassMeta classMeta) {
237 // We allow only assignment of equal arguments (e.g. during detachment).
238 if (this.classMeta != null && !this.classMeta.equals(classMeta))
239 throw new IllegalStateException("Cannot modify this this.classMeta!");
240
241 if (this.classMeta_classID != null && this.classMeta_classID.longValue() != classMeta.getClassID())
242 throw new IllegalStateException("Cannot modify this this.classMeta!");
243
244 this.classMeta = classMeta;
245 this.classMeta_classID = classMeta == null ? null : classMeta.getClassID();
246 if (this.classMeta_classID != null && this.classMeta_classID.longValue() < 0)
247 throw new IllegalStateException("classMeta not persisted yet: " + classMeta);
248 }
249
250 /**
251 * Get the {@link FieldMetaRole#primary primary} {@link FieldMeta}, to which this sub-<code>FieldMeta</code> belongs
252 * or <code>null</code>, if this <code>FieldMeta</code> is primary.
253 * @return the owning primary field-meta or <code>null</code>.
254 */
255 public FieldMeta getOwnerFieldMeta() {
256 if (ownerFieldMeta_fieldID == null)
257 return null;
258
259 if (ownerFieldMeta == null)
260 ownerFieldMeta = new FieldMetaDAO(getPersistenceManager()).getFieldMeta(ownerFieldMeta_fieldID, true);
261
262 return ownerFieldMeta;
263 }
264
265 protected void setOwnerFieldMeta(FieldMeta ownerFieldMeta) {
266 // We allow only assignment of equal arguments (e.g. during detachment).
267 if (this.ownerFieldMeta != null && !this.ownerFieldMeta.equals(ownerFieldMeta))
268 throw new IllegalStateException("Cannot modify this this.ownerFieldMeta!");
269
270 if (this.ownerFieldMeta_fieldID != null && this.ownerFieldMeta_fieldID.longValue() != ownerFieldMeta.getFieldID())
271 throw new IllegalStateException("Cannot modify this this.ownerFieldMeta!");
272
273 this.ownerFieldMeta = ownerFieldMeta;
274 this.ownerFieldMeta_fieldID = ownerFieldMeta == null ? null : ownerFieldMeta.getFieldID();
275 if (this.ownerFieldMeta_fieldID != null && this.ownerFieldMeta_fieldID.longValue() < 0)
276 throw new IllegalStateException("ownerFieldMeta not persisted yet: " + ownerFieldMeta);
277 }
278
279 /**
280 * Get the simple field name (no class prefix) of the field referenced by this meta-data-instance.
281 * @return the simple field name.
282 */
283 public String getFieldName() {
284 return fieldName;
285 }
286
287 /**
288 * Get the role of the (sub-)field. If this is not a sub-field, but a primary field
289 * (i.e. directly meaning a real field of the class referenced by {@link #getClassMeta() classMeta})
290 * it will be {@link FieldMetaRole#primary}, hence this method never returns <code>null</code>.
291 * @return the role of this <code>FieldMeta</code>; never <code>null</code>.
292 */
293 public FieldMetaRole getRole() {
294 return role;
295 }
296
297 /**
298 * Get the {@link PersistenceManager} assigned to <code>this</code>. If there is none, this method checks, if
299 * <code>this</code> is new. If <code>this</code> was persisted before, it must have one or an {@link IllegalStateException}
300 * is thrown.
301 * @return the {@link PersistenceManager} assigned to this or <code>null</code>.
302 */
303 protected PersistenceManager getPersistenceManager() {
304 PersistenceManager pm = JDOHelper.getPersistenceManager(this);
305 if (pm == null) {
306 throw new IllegalStateException("JDOHelper.getPersistenceManager(this) returned null! " + this);
307 // if (JDOHelper.getObjectId(this) != null)
308 // throw new IllegalStateException("This FieldMeta instance is not new, but JDOHelper.getPersistenceManager(this) returned null! " + this);
309 }
310 return pm;
311 }
312
313 protected Map<FieldMetaRole, FieldMeta> getRole2SubFieldMeta() {
314 Map<FieldMetaRole, FieldMeta> result = this.role2SubFieldMeta;
315
316 if (result == null) {
317 logger.debug("getRole2SubFieldMeta: this.role2SubFieldMeta == null => populating. this={}", this);
318 result = new HashMap<FieldMetaRole, FieldMeta>();
319 PersistenceManager pm = getPersistenceManager();
320 if (pm != null) {
321 Collection<FieldMeta> fieldMetas = new FieldMetaDAO(pm).getSubFieldMetasForFieldMeta(this);
322 for (FieldMeta fieldMeta : fieldMetas)
323 result.put(fieldMeta.getRole(), fieldMeta);
324 }
325
326 this.role2SubFieldMeta = result;
327 }
328 else
329 logger.trace("getRole2SubFieldMeta: this.role2SubFieldMeta != null (already populated). this={}", this);
330
331 return result;
332 }
333
334 public EmbeddedClassMeta getEmbeddedClassMeta() {
335 if (!embeddedClassMetaLoaded) {
336 logger.debug("getEmbeddedClassMeta: this.embeddedClassMetaLoaded == false => loading. this={}", this);
337 PersistenceManager pm = getPersistenceManager();
338 if (pm != null) {
339 embeddedClassMeta = new ClassMetaDAO(pm).getEmbeddedClassMeta(this, false);
340 }
341 embeddedClassMetaLoaded = true;
342 }
343 return embeddedClassMeta;
344 }
345
346 public void setEmbeddedClassMeta(EmbeddedClassMeta embeddedClassMeta) {
347 if (embeddedClassMeta != null && !JDOHelper.isDetached(embeddedClassMeta))
348 embeddedClassMeta = getPersistenceManager().makePersistent(embeddedClassMeta);
349
350 EmbeddedClassMeta embeddedClassMetaOld = this.embeddedClassMeta;
351 if (embeddedClassMetaOld != null && !embeddedClassMetaOld.equals(embeddedClassMeta)) {
352 // if (this.embeddedClassMetasToBeDeleted == null)
353 // this.embeddedClassMetasToBeDeleted = new HashSet<EmbeddedClassMeta>();
354 //
355 // this.embeddedClassMetasToBeDeleted.add(embeddedClassMetaOld);
356 getPersistenceManager().deletePersistent(embeddedClassMetaOld);
357 }
358
359 this.embeddedClassMeta = embeddedClassMeta;
360 this.embeddedClassMetaLoaded = true;
361
362 // if (this.embeddedClassMetasToBeDeleted != null)
363 // this.embeddedClassMetasToBeDeleted.remove(embeddedClassMeta);
364 }
365
366 public int getDataNucleusAbsoluteFieldNumber(ExecutionContext executionContext) {
367 AbstractClassMetaData dnClassMetaData = getClassMeta().getDataNucleusClassMetaData(executionContext);
368 int dnFieldNumber = getDataNucleusAbsoluteFieldNumber();
369 if (dnFieldNumber < 0) {
370 dnFieldNumber = dnClassMetaData.getAbsolutePositionOfMember(getClassMeta().getClassName(), getFieldName());
371 if (dnFieldNumber < 0)
372 throw new IllegalStateException("The method dnClassMetaData.getAbsolutePositionOfMember(...) returned -1 for memberName='" + getFieldName() + "'!!!");
373
374 setDataNucleusAbsoluteFieldNumber(dnFieldNumber);
375 }
376 return dnFieldNumber;
377 }
378
379 /**
380 * Get the non-persistent field-number in DataNucleus' meta-data. This is only a usable value,
381 * if this <code>FieldMeta</code> was obtained via
382 * {@link Cumulus4jStoreManager#getClassMeta(org.datanucleus.store.ExecutionContext, Class)}; otherwise
383 * it is -1.
384 * @return the non-persistent field-number in DataNucleus' meta-data or -1.
385 */
386 public int getDataNucleusAbsoluteFieldNumber() {
387 return dataNucleusAbsoluteFieldNumber;
388 }
389 public void setDataNucleusAbsoluteFieldNumber(int dataNucleusAbsoluteFieldNumber) {
390 this.dataNucleusAbsoluteFieldNumber = dataNucleusAbsoluteFieldNumber;
391 this.dataNucleusMemberMetaData = null;
392
393 for (FieldMeta subFM : getRole2SubFieldMeta().values())
394 subFM.setDataNucleusAbsoluteFieldNumber(dataNucleusAbsoluteFieldNumber);
395 }
396
397 /**
398 * Get a sub-field of this field or <code>null</code>, if no such sub-field exists.
399 * @param role the role of the sub-field. Must not be <code>null</code>.
400 * @return the sub-<code>FieldMeta</code> or <code>null</code>.
401 */
402 public FieldMeta getSubFieldMeta(FieldMetaRole role)
403 {
404 if (role == null)
405 throw new IllegalArgumentException("role == null");
406
407 return getRole2SubFieldMeta().get(role);
408 }
409
410 /**
411 * Get all sub-fields' meta-data of this field. If there are no sub-fields, this is an
412 * empty collection.
413 * @return all sub-<code>FieldMeta</code>s of this field; never <code>null</code>.
414 */
415 public Collection<FieldMeta> getSubFieldMetas()
416 {
417 return getRole2SubFieldMeta().values();
418 }
419
420 public void addSubFieldMeta(FieldMeta subFieldMeta)
421 {
422 if (!this.equals(subFieldMeta.getOwnerFieldMeta()))
423 throw new IllegalArgumentException("this != subFieldMeta.ownerFieldMeta");
424
425 if (!this.fieldName.equals(subFieldMeta.getFieldName()))
426 throw new IllegalArgumentException("this.fieldName != subFieldMeta.fieldName");
427
428 if (getSubFieldMeta(subFieldMeta.getRole()) != null)
429 throw new IllegalArgumentException("There is already a subFieldMeta with role \"" + subFieldMeta.getRole() + "\"!");
430
431 subFieldMeta = getPersistenceManager().makePersistent(subFieldMeta);
432
433 subFieldMeta.setDataNucleusAbsoluteFieldNumber(dataNucleusAbsoluteFieldNumber);
434
435 // PersistenceManager pm = getPersistenceManager();
436 // if (pm != null) // If the pm is null, the subFieldMeta is persisted later (see jdoPreStore() below).
437 // subFieldMeta = pm.makePersistent(subFieldMeta);
438
439 getRole2SubFieldMeta().put(subFieldMeta.getRole(), subFieldMeta);
440 }
441
442 public void removeSubFieldMeta(FieldMeta subFieldMeta)
443 {
444 if (!this.equals(subFieldMeta.getOwnerFieldMeta()))
445 throw new IllegalArgumentException("subFieldMeta.ownerFieldMeta != this");
446
447 getRole2SubFieldMeta().remove(subFieldMeta.getRole());
448 // PersistenceManager pm = getPersistenceManager();
449 // if (pm != null)
450 // pm.deletePersistent(subFieldMeta);
451 getPersistenceManager().deletePersistent(subFieldMeta);
452 }
453
454 public void removeAllSubFieldMetasExcept(FieldMetaRole ... roles)
455 {
456 if (roles == null)
457 roles = new FieldMetaRole[0];
458
459 Set<FieldMetaRole> rolesToKeep = new HashSet<FieldMetaRole>(roles.length);
460 for (FieldMetaRole role : roles)
461 rolesToKeep.add(role);
462
463 PersistenceManager pm = getPersistenceManager();
464 Collection<FieldMetaRole> oldRoles = new ArrayList<FieldMetaRole>(getRole2SubFieldMeta().keySet());
465 for (FieldMetaRole role : oldRoles) {
466 if (!rolesToKeep.contains(role)) {
467 FieldMeta subFieldMeta = getRole2SubFieldMeta().remove(role);
468
469 // if (pm != null && subFieldMeta != null)
470 if (subFieldMeta != null)
471 pm.deletePersistent(subFieldMeta);
472 }
473 }
474 }
475
476 @NotPersistent
477 private transient FieldMeta mappedByFieldMeta;
478
479 /**
480 * Used by {@link #getMappedByFieldMeta(ExecutionContext)} to mask <code>null</code> and thus
481 * prevent a second unnecessary resolve process if the first already resolved to <code>null</code>.
482 */
483 private static final FieldMeta NULL_MAPPED_BY_FIELD_META = new FieldMeta();
484
485 public ClassMeta getFieldOrElementTypeClassMeta(ExecutionContext executionContext) {
486 Class<?> clazz = getFieldOrElementType(executionContext);
487 Cumulus4jStoreManager storeManager = (Cumulus4jStoreManager) executionContext.getStoreManager();
488 if (!storeManager.getMetaDataManager().isClassPersistable(clazz.getName()))
489 return null;
490
491 ClassMeta result;
492 if (JDOHelper.isDetached(this))
493 result = storeManager.getClassMeta(executionContext, clazz);
494 else {
495 PersistenceManager pm = getPersistenceManager();
496 // We REQUIRE the pm to be present now. this.getPersistenceManager() sometimes returns null.
497 if (pm == null)
498 throw new IllegalStateException("this.getPersistenceManager() == null! Probably this instance is new. Persist it first!");
499
500 result = storeManager.getAttachedClassMeta(executionContext, pm, clazz);
501 }
502 return result;
503 }
504
505 public AbstractClassMetaData getFieldOrElementTypeDataNucleusClassMetaData(ExecutionContext executionContext)
506 {
507 Class<?> clazz = getFieldOrElementType(executionContext);
508 Cumulus4jStoreManager storeManager = (Cumulus4jStoreManager) executionContext.getStoreManager();
509 MetaDataManager metaDataManager = storeManager.getMetaDataManager();
510 AbstractClassMetaData metaDataForClass = metaDataManager.getMetaDataForClass(clazz, executionContext.getClassLoaderResolver());
511 return metaDataForClass;
512 }
513
514 public Class<?> getFieldOrElementType(ExecutionContext executionContext) {
515 AbstractMemberMetaData mmd = getDataNucleusMemberMetaData(executionContext);
516 Class<?> result;
517 if (mmd.hasCollection()) {
518 // if (FieldMetaRole.primary == this.getRole())
519 // throw new IllegalStateException("this is a primary FieldMeta of a collection - use appropriate sub-FieldMeta instead!");
520
521 result = executionContext.getClassLoaderResolver().classForName(mmd.getCollection().getElementType());
522 }
523 else if (mmd.hasArray()) {
524 // if (FieldMetaRole.primary == this.getRole())
525 // throw new IllegalStateException("this is a primary FieldMeta of an array - use appropriate sub-FieldMeta instead!");
526
527 result = executionContext.getClassLoaderResolver().classForName(mmd.getArray().getElementType());
528 }
529 else if (mmd.hasMap()) {
530 FieldMetaRole role = this.getRole();
531
532 // This method should work with mapped-by-relations, because there is only one
533 // FCO related anyway. Marco :-)
534 String mappedBy;
535 mappedBy = mmd.getKeyMetaData() == null ? null : mmd.getKeyMetaData().getMappedBy();
536 if(mappedBy != null)
537 role = FieldMetaRole.mapValue;
538
539 mappedBy = mmd.getValueMetaData() == null ? null : mmd.getValueMetaData().getMappedBy();
540 if(mappedBy != null)
541 role = FieldMetaRole.mapKey;
542
543 if (FieldMetaRole.primary == role)
544 throw new IllegalStateException("this is a primary FieldMeta of a map - use appropriate sub-FieldMeta instead!");
545
546 switch (role) {
547 case mapKey:
548 result = executionContext.getClassLoaderResolver().classForName(mmd.getMap().getKeyType());
549 break;
550 case mapValue:
551 result = executionContext.getClassLoaderResolver().classForName(mmd.getMap().getValueType());
552 break;
553 default:
554 throw new IllegalStateException("DataNucleus-member-meta-data says this is a map, but this.role='" + this.getRole() + "': this=" + this);
555 }
556 // if (mmd.getMap().keyIsPersistent())
557 // result = executionContext.getClassLoaderResolver().classForName(mmd.getMap().getKeyType());
558 // else if (mmd.getMap().valueIsPersistent())
559 // result = executionContext.getClassLoaderResolver().classForName(mmd.getMap().getValueType());
560 // else
561 // throw new IllegalStateException("How can a Map be mapped-by without key and value being persistent?! Exactly one of them should be persistent!");
562 }
563 else
564 result = mmd.getType();
565
566 return result;
567 }
568
569 /**
570 * <p>
571 * Get the {@link FieldMeta} of the opposite end of the mapped-by-relation. If
572 * this is not a mapped-by field, this method returns <code>null</code>.
573 * </p>
574 * <p>
575 * Though, it returns always the mapped-by opposite side, the semantics of
576 * this method still depend on the {@link #getRole() role} of this <code>FieldMeta</code>:
577 * </p>
578 * <ul>
579 * <li>{@link FieldMetaRole#primary}: Returns the owner-field on the opposite side which is referenced by
580 * @Persistent(mappedBy="owner")</li>
581 * <li>{@link FieldMetaRole#mapKey}: Returns the key-field on the opposite side which is referenced by
582 * @Key(mappedBy="key")</li>
583 * <li>{@link FieldMetaRole#mapValue}: Returns the value-field on the opposite side which is referenced by
584 * @Value(mappedBy="value")</li>
585 * </ul>
586 *
587 * @return the {@link FieldMeta} of the other end of the mapped-by-relation.
588 */
589 public FieldMeta getMappedByFieldMeta(ExecutionContext executionContext)
590 {
591 FieldMeta mbfm = mappedByFieldMeta;
592
593 if (NULL_MAPPED_BY_FIELD_META == mbfm)
594 return null;
595
596 if (mbfm != null)
597 return mbfm;
598
599 AbstractMemberMetaData mmd = getDataNucleusMemberMetaData(executionContext);
600
601 if (mmd.getMappedBy() != null)
602 {
603 Class<?> typeOppositeSide = getFieldOrElementType(executionContext);
604 // if (mmd.hasCollection())
605 // typeOppositeSide = executionContext.getClassLoaderResolver().classForName(mmd.getCollection().getElementType());
606 // else if (mmd.hasArray())
607 // typeOppositeSide = executionContext.getClassLoaderResolver().classForName(mmd.getArray().getElementType());
608 // else if (mmd.hasMap()) {
609 // if (mmd.getMap().keyIsPersistent())
610 // typeOppositeSide = executionContext.getClassLoaderResolver().classForName(mmd.getMap().getKeyType());
611 // else if (mmd.getMap().valueIsPersistent())
612 // typeOppositeSide = executionContext.getClassLoaderResolver().classForName(mmd.getMap().getValueType());
613 // else
614 // throw new IllegalStateException("How can a Map be mapped-by without key and value being persistent?! Exactly one of them should be persistent!");
615 // }
616 // else
617 // typeOppositeSide = mmd.getType();
618
619 Cumulus4jStoreManager storeManager = (Cumulus4jStoreManager) executionContext.getStoreManager();
620 ClassMeta classMetaOppositeSide = storeManager.getClassMeta(executionContext, typeOppositeSide);
621 String mappedBy = null;
622
623 switch (role) {
624 case primary:
625 // mbfm = classMetaOppositeSide.getFieldMeta(mmd.getMappedBy());
626 mappedBy = mmd.getMappedBy();
627 break;
628
629 case mapKey:
630 mappedBy = mmd.getKeyMetaData() == null ? null : mmd.getKeyMetaData().getMappedBy();
631 if (mmd.getMap().valueIsPersistent() && mappedBy == null)
632 throw new IllegalStateException("The map's value is persistent via mappedBy (without @Join), but there is no @Key(mappedBy=\"...\")! This is invalid! " + mmd);
633 break;
634
635 case mapValue:
636 mappedBy = mmd.getValueMetaData() == null ? null : mmd.getValueMetaData().getMappedBy();
637 if (mmd.getMap().keyIsPersistent() && mappedBy == null)
638 throw new IllegalStateException("The map's key is persistent via mappedBy (without @Join), but there is no @Value(mappedBy=\"...\")! This is invalid! " + mmd);
639 break;
640
641 case arrayElement:
642 case collectionElement:
643 // TODO doesn't this need implementation?
644 // Seems to work this way, but why? Marco :-)
645 // 2012-11-10: added the following line.
646 mappedBy = mmd.getMappedBy(); // commented out again // FIXME - some queries break with htis, but IMHO it's correct!
647 break;
648
649 default:
650 throw new IllegalStateException("Unexpected role: " + role);
651 }
652
653 if (mappedBy != null) {
654 mbfm = classMetaOppositeSide.getFieldMeta(mappedBy);
655 if (mbfm == null)
656 throw new IllegalStateException("Field \"" + mappedBy + "\" referenced in 'mappedBy' of " + this + " does not exist!");
657 }
658 }
659
660 if (mbfm == null)
661 mappedByFieldMeta = NULL_MAPPED_BY_FIELD_META;
662 else
663 mappedByFieldMeta = mbfm;
664
665 return mbfm;
666 }
667
668 protected static final ThreadLocal<Set<FieldMeta>> attachedFieldMetasInPostDetachThreadLocal = new ThreadLocal<Set<FieldMeta>>() {
669 @Override
670 protected Set<FieldMeta> initialValue() {
671 return new HashSet<FieldMeta>();
672 }
673 };
674
675 @Override
676 public void jdoPreDetach() { }
677
678 @Override
679 public void jdoPostDetach(Object o) {
680 final PostDetachRunnableManager postDetachRunnableManager = PostDetachRunnableManager.getInstance();
681 postDetachRunnableManager.enterScope();
682 try {
683 final FieldMeta attached = (FieldMeta) o;
684 final FieldMeta detached = this;
685 logger.debug("jdoPostDetach: attached={}", attached);
686
687 if (!JDOHelper.isDetached(detached))
688 throw new IllegalStateException("detached ist not detached!");
689
690 if (JDOHelper.getPersistenceManager(detached) != null)
691 throw new IllegalStateException("detached has a PersistenceManager assigned!");
692
693 final DetachedClassMetaModel detachedClassMetaModel = DetachedClassMetaModel.getInstance();
694 if (detachedClassMetaModel != null)
695 detachedClassMetaModel.registerFieldMetaCurrentlyDetaching(detached);
696
697 detached.dataNucleusAbsoluteFieldNumber = attached.dataNucleusAbsoluteFieldNumber;
698
699 final PersistenceManager pm = attached.getPersistenceManager();
700 if (pm == null)
701 throw new IllegalStateException("attached.getPersistenceManager() returned null!");
702
703 Set<?> fetchGroups = pm.getFetchPlan().getGroups();
704
705 Set<FieldMeta> attachedFieldMetasInPostDetach = attachedFieldMetasInPostDetachThreadLocal.get();
706 if (!attachedFieldMetasInPostDetach.add(attached)) {
707 logger.debug("jdoPostDetach: Already in detachment => Skipping detachment of this.role2SubFieldMeta! attached={}", attached);
708 return;
709 }
710 try {
711 // The following field should already be null, but we better ensure that we never
712 // contain *AT*tached objects inside a *DE*tached container.
713 detached.role2SubFieldMeta = null;
714
715 if (fetchGroups.contains(javax.jdo.FetchGroup.ALL)) {
716 logger.debug("jdoPostDetach: Detaching this.role2SubFieldMeta: attached={}", attached);
717
718 // if the fetch-groups say we should detach the FieldMetas, we do it.
719 HashMap<FieldMetaRole, FieldMeta> map = new HashMap<FieldMetaRole, FieldMeta>();
720 Collection<FieldMeta> detachedSubFieldMetas = pm.detachCopyAll(attached.getRole2SubFieldMeta().values());
721 for (final FieldMeta detachedSubFieldMeta : detachedSubFieldMetas) {
722 // detachedSubFieldMeta.setOwnerFieldMeta(detached); // ensure, it's the identical (not only equal) FieldMeta.
723 // The above is not necessary and might cause problems (because this callback might be called while the detached instance is currently
724 // BEING detached, i.e. not yet finished detaching.
725
726 postDetachRunnableManager.addRunnable(new Runnable() {
727 @Override
728 public void run() {
729 detachedSubFieldMeta.setOwnerFieldMeta(detached); // ensure, it's the identical (not only equal) FieldMeta.
730 }
731 });
732
733 map.put(detachedSubFieldMeta.getRole(), detachedSubFieldMeta);
734 }
735 detached.role2SubFieldMeta = map;
736
737
738 postDetachRunnableManager.addRunnable(new Runnable() {
739 @Override
740 public void run() {
741 try {
742 if (attached.classMeta_classID != null) {
743 detached.classMeta = detachedClassMetaModel == null ? null : detachedClassMetaModel.getClassMeta(attached.classMeta_classID, false);
744 if (detached.classMeta == null)
745 detached.classMeta = pm.detachCopy(attached.getClassMeta());
746 }
747
748 if (attached.ownerFieldMeta_fieldID != null) {
749 DetachedClassMetaModel detachedClassMetaModel = DetachedClassMetaModel.getInstance();
750 detached.ownerFieldMeta = detachedClassMetaModel == null ? null : detachedClassMetaModel.getFieldMeta(attached.ownerFieldMeta_fieldID, false);
751 if (detached.ownerFieldMeta == null)
752 detached.ownerFieldMeta = pm.detachCopy(attached.getOwnerFieldMeta());
753 }
754 } catch (Exception x) {
755 String className = classMeta != null ? classMeta.getClassName() : String.valueOf(classMeta_classID);
756 throw new RuntimeException("postDetachRunnable failed for class " + className + " and field " + fieldName + ": " + x, x);
757 }
758 }
759 });
760
761 }
762
763 } finally {
764 attachedFieldMetasInPostDetach.remove(attached);
765 }
766
767 if (fetchGroups.contains(javax.jdo.FetchGroup.ALL)) {
768 logger.debug("jdoPostDetach: Detaching this.embeddedClassMeta: attached={}", attached);
769 EmbeddedClassMeta embeddedClassMeta = attached.getEmbeddedClassMeta();
770 detached.setEmbeddedClassMeta(embeddedClassMeta == null ? null : pm.detachCopy(embeddedClassMeta));
771 }
772 else {
773 detached.embeddedClassMeta = null;
774 detached.embeddedClassMetaLoaded = false;
775 }
776 } finally {
777 postDetachRunnableManager.exitScope();
778 }
779 }
780
781 @Override
782 public int hashCode()
783 {
784 long fieldID = getFieldID();
785 return (int) (fieldID ^ (fieldID >>> 32));
786 }
787
788 @Override
789 public boolean equals(Object obj)
790 {
791 if (this == obj) return true;
792 if (obj == null) return false;
793 if (getClass() != obj.getClass()) return false;
794 FieldMeta other = (FieldMeta) obj;
795 // if not yet persisted (id == null), it is only equal to the same instance (checked above, already).
796 // return this.fieldID == null ? false : this.fieldID.equals(other.fieldID);
797 return this.getFieldID() < 0 ? false : this.getFieldID() == other.getFieldID();
798 }
799
800 @Override
801 public String toString()
802 {
803 ClassMeta cm = getClassMeta();
804 return (
805 this.getClass().getName()
806 + '@'
807 + Integer.toHexString(System.identityHashCode(this))
808 + '['
809 + fieldID + ',' + (cm == null ? null : cm.getClassName()) + '#' + getFieldName() + '[' + role + ']'
810 + ']'
811 );
812 }
813
814 @NotPersistent
815 private AbstractMemberMetaData dataNucleusMemberMetaData;
816
817 public AbstractMemberMetaData getDataNucleusMemberMetaData(ExecutionContext executionContext)
818 {
819 if (dataNucleusMemberMetaData != null)
820 return dataNucleusMemberMetaData;
821
822 AbstractClassMetaData dnClassMetaData = getClassMeta().getDataNucleusClassMetaData(executionContext);
823
824 int dnFieldNumber = getDataNucleusAbsoluteFieldNumber(executionContext);
825
826 AbstractMemberMetaData dnMemberMetaData = dnClassMetaData.getMetaDataForManagedMemberAtAbsolutePosition(dnFieldNumber);
827 if (dnMemberMetaData == null)
828 throw new IllegalStateException("DataNucleus has no meta-data for this field: fieldID=" + getFieldID() + " className=" + classMeta.getClassName() + " fieldName=" + getFieldName());
829
830 dataNucleusMemberMetaData = dnMemberMetaData;
831 return dnMemberMetaData;
832 }
833
834 @Override
835 public void jdoPreStore() {
836 logger.debug("jdoPreStore: {}", this);
837 // final ClassMeta finalClassMeta = classMeta;
838 // final FieldMeta finalOwnerFieldMeta = ownerFieldMeta;
839 //
840 // PostStoreRunnableManager.getInstance().addRunnable(new Runnable() {
841 // @Override
842 // public void run() {
843 // logger.debug("postStore: {}", this);
844 // PersistenceManager pm = JDOHelper.getPersistenceManager(FieldMeta.this);
845 //
846 // if (finalClassMeta != null) {
847 // setClassMeta(pm.makePersistent(finalClassMeta));
848 // }
849 //
850 // if (finalOwnerFieldMeta != null) {
851 // setOwnerFieldMeta(pm.makePersistent(finalOwnerFieldMeta));
852 // }
853 //
854 // if (embeddedClassMeta != null) {
855 // embeddedClassMeta = pm.makePersistent(embeddedClassMeta);
856 // }
857 //
858 // if (role2SubFieldMeta != null) {
859 // Map<FieldMetaRole, FieldMeta> persistentRole2SubFieldMeta2 = new HashMap<FieldMetaRole, FieldMeta>(role2SubFieldMeta.size());
860 // for (FieldMeta subFieldMeta : role2SubFieldMeta.values()) {
861 // // Usually the persistentSubFieldMeta is the same instance as subFieldMeta, but this is dependent on the configuration.
862 // // This code here should work with all possible configurations. Marco :-)
863 // FieldMeta persistentSubFieldMeta = pm.makePersistent(subFieldMeta);
864 // persistentRole2SubFieldMeta2.put(persistentSubFieldMeta.getRole(), persistentSubFieldMeta);
865 // }
866 // role2SubFieldMeta = persistentRole2SubFieldMeta2;
867 // pm.flush();
868 // }
869 //
870 // if (embeddedClassMetasToBeDeleted != null) {
871 // for (EmbeddedClassMeta embeddedClassMeta : embeddedClassMetasToBeDeleted) {
872 // pm.deletePersistent(embeddedClassMeta);
873 // }
874 // pm.flush();
875 // }
876 // }
877 // });
878 }
879 }