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 }