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 }