001 /* 002 * Cumulus4j - Securing your data in the cloud - http://cumulus4j.org 003 * Copyright (C) 2011 NightLabs Consulting GmbH 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018 package org.cumulus4j.store.model; 019 020 import java.util.ArrayList; 021 import java.util.Collection; 022 import java.util.HashMap; 023 import java.util.HashSet; 024 import java.util.Map; 025 import java.util.Set; 026 027 import javax.jdo.annotations.Column; 028 import javax.jdo.annotations.IdGeneratorStrategy; 029 import javax.jdo.annotations.IdentityType; 030 import javax.jdo.annotations.Key; 031 import javax.jdo.annotations.NotPersistent; 032 import javax.jdo.annotations.NullValue; 033 import javax.jdo.annotations.PersistenceCapable; 034 import javax.jdo.annotations.Persistent; 035 import javax.jdo.annotations.PrimaryKey; 036 import javax.jdo.annotations.Unique; 037 import javax.jdo.annotations.Uniques; 038 import javax.jdo.annotations.Version; 039 import javax.jdo.annotations.VersionStrategy; 040 import javax.jdo.listener.DetachCallback; 041 042 import org.cumulus4j.store.Cumulus4jStoreManager; 043 import org.datanucleus.metadata.AbstractClassMetaData; 044 import org.datanucleus.metadata.AbstractMemberMetaData; 045 import org.datanucleus.store.ExecutionContext; 046 047 /** 048 * Persistent meta-data for a field of a persistence-capable class. Since class- and field-names are very 049 * long we reference them indirectly via the long-identifiers of {@link ClassMeta} and {@link FieldMeta}, 050 * e.g. in the relation {@link IndexEntry#getFieldMeta() IndexEntry.fieldMeta}. 051 * 052 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de 053 */ 054 @PersistenceCapable(identityType=IdentityType.APPLICATION, detachable="true") 055 @Version(strategy=VersionStrategy.VERSION_NUMBER) 056 @Uniques({ 057 @Unique(name="FieldMeta_classMeta_ownerFieldMeta_fieldName_role", members={"classMeta", "ownerFieldMeta", "fieldName", "role"}) 058 }) 059 public class FieldMeta 060 implements DetachCallback 061 { 062 @PrimaryKey 063 @Persistent(valueStrategy=IdGeneratorStrategy.NATIVE) 064 private long fieldID = -1; 065 066 private ClassMeta classMeta; 067 068 private FieldMeta ownerFieldMeta; 069 070 @Persistent(nullValue=NullValue.EXCEPTION) 071 @Column(length=255) 072 private String fieldName; 073 074 @Persistent(nullValue=NullValue.EXCEPTION) 075 private FieldMetaRole role; 076 077 @NotPersistent 078 private int dataNucleusAbsoluteFieldNumber = -1; 079 080 @Persistent(mappedBy="ownerFieldMeta", dependentValue="true") 081 @Key(mappedBy="role") 082 private Map<FieldMetaRole, FieldMeta> role2subFieldMeta = new HashMap<FieldMetaRole, FieldMeta>(); 083 084 /** 085 * Internal constructor. This exists only for JDO and should not be used by application code! 086 */ 087 protected FieldMeta() { } 088 089 /** 090 * Create a <code>FieldMeta</code> referencing a real field. 091 * @param classMeta the class to which this field belongs. 092 * @param fieldName the field's name. 093 * @see #FieldMeta(FieldMeta, FieldMetaRole) 094 */ 095 public FieldMeta(ClassMeta classMeta, String fieldName) 096 { 097 this(classMeta, null, fieldName, FieldMetaRole.primary); 098 } 099 /** 100 * Create a <code>FieldMeta</code> referencing a part of a field. This is necessary to index keys and values of a 101 * <code>Map</code> field (i.e. 2 separate indexes for one field) as well as <code>Collection</code>-elements and similar. 102 * @param ownerFieldMeta the <code>FieldMeta</code> of the real field (to which the part belongs). 103 * @param role the role (aka type) of the sub-field (aka part). 104 * @see #FieldMeta(ClassMeta, String) 105 */ 106 public FieldMeta(FieldMeta ownerFieldMeta, FieldMetaRole role) 107 { 108 this(null, ownerFieldMeta, ownerFieldMeta.getFieldName(), role); 109 } 110 111 /** 112 * Internal constructor. This exists only for easier implementation of the other constructors and 113 * should not be used by application code! 114 */ 115 protected FieldMeta(ClassMeta classMeta, FieldMeta ownerFieldMeta, String fieldName, FieldMetaRole role) 116 { 117 if (classMeta == null && ownerFieldMeta == null) 118 throw new IllegalArgumentException("classMeta == null && ownerFieldMeta == null"); 119 120 if (classMeta != null && ownerFieldMeta != null) 121 throw new IllegalArgumentException("classMeta != null && ownerFieldMeta != null"); 122 123 if (fieldName == null) 124 throw new IllegalArgumentException("fieldName == null"); 125 126 if (role == null) 127 throw new IllegalArgumentException("role == null"); 128 129 this.classMeta = classMeta; 130 this.ownerFieldMeta = ownerFieldMeta; 131 this.fieldName = fieldName; 132 this.role = role; 133 } 134 135 public long getFieldID() { 136 return fieldID; 137 } 138 139 /** 140 * Get the {@link ClassMeta} to which this <code>FieldMeta</code> belongs. Every FieldMeta 141 * belongs to exactly one {@link ClassMeta} just like a field is declared in exactly one Java class. 142 * Note, that a {@link FieldMeta} might belong to another FieldMeta in order to reference sub-field-properties, 143 * e.g. a {@link Map}'s key. In this case, the direct property <code>classMeta</code> is <code>null</code>, but this method 144 * still returns the correct {@link ClassMeta} by resolving it indirectly via the {@link #getOwnerFieldMeta() ownerFieldMeta}. 145 * @return the {@link ClassMeta} to which this instance of <code>FieldMeta</code> belongs. 146 */ 147 public ClassMeta getClassMeta() { 148 if (ownerFieldMeta != null) 149 return ownerFieldMeta.getClassMeta(); 150 151 return classMeta; 152 } 153 154 /** 155 * Get the {@link FieldMetaRole#primary primary} {@link FieldMeta}, to which this sub-<code>FieldMeta</code> belongs 156 * or <code>null</code>, if this <code>FieldMeta</code> is primary. 157 * @return the owning primary field-meta or <code>null</code>. 158 */ 159 public FieldMeta getOwnerFieldMeta() { 160 return ownerFieldMeta; 161 } 162 163 /** 164 * Get the simple field name (no class prefix) of the field referenced by this meta-data-instance. 165 * @return the simple field name. 166 */ 167 public String getFieldName() { 168 return fieldName; 169 } 170 171 /** 172 * Get the role of the (sub-)field. If this is not a sub-field, but a primary field 173 * (i.e. directly meaning a real field of the class referenced by {@link #getClassMeta() classMeta}) 174 * it will be {@link FieldMetaRole#primary}, hence this method never returns <code>null</code>. 175 * @return the role of this <code>FieldMeta</code>; never <code>null</code>. 176 */ 177 public FieldMetaRole getRole() { 178 return role; 179 } 180 181 /** 182 * Get the non-persistent field-number in DataNucleus' meta-data. This is only a usable value, 183 * if this <code>FieldMeta</code> was obtained via 184 * {@link Cumulus4jStoreManager#getClassMeta(org.datanucleus.store.ExecutionContext, Class)}; otherwise 185 * it is -1. 186 * @return the non-persistent field-number in DataNucleus' meta-data or -1. 187 */ 188 public int getDataNucleusAbsoluteFieldNumber() { 189 return dataNucleusAbsoluteFieldNumber; 190 } 191 public void setDataNucleusAbsoluteFieldNumber(int dataNucleusAbsoluteFieldNumber) { 192 this.dataNucleusAbsoluteFieldNumber = dataNucleusAbsoluteFieldNumber; 193 this.dataNucleusMemberMetaData = null; 194 195 for (FieldMeta subFM : role2subFieldMeta.values()) 196 subFM.setDataNucleusAbsoluteFieldNumber(dataNucleusAbsoluteFieldNumber); 197 } 198 199 /** 200 * Get a sub-field of this field or <code>null</code>, if no such sub-field exists. 201 * @param role the role of the sub-field. Must not be <code>null</code>. 202 * @return the sub-<code>FieldMeta</code> or <code>null</code>. 203 */ 204 public FieldMeta getSubFieldMeta(FieldMetaRole role) 205 { 206 if (role == null) 207 throw new IllegalArgumentException("role == null"); 208 209 return role2subFieldMeta.get(role); 210 } 211 212 /** 213 * Get all sub-fields' meta-data of this field. If there are no sub-fields, this is an 214 * empty collection. 215 * @return all sub-<code>FieldMeta</code>s of this field; never <code>null</code>. 216 */ 217 public Collection<FieldMeta> getSubFieldMetas() 218 { 219 return role2subFieldMeta.values(); 220 } 221 222 public void addSubFieldMeta(FieldMeta subFieldMeta) 223 { 224 if (!this.equals(subFieldMeta.getOwnerFieldMeta())) 225 throw new IllegalArgumentException("this != subFieldMeta.ownerFieldMeta"); 226 227 if (!this.fieldName.equals(subFieldMeta.getFieldName())) 228 throw new IllegalArgumentException("this.fieldName != subFieldMeta.fieldName"); 229 230 if (getSubFieldMeta(subFieldMeta.getRole()) != null) 231 throw new IllegalArgumentException("There is already a subFieldMeta with role \"" + subFieldMeta.getRole() + "\"!"); 232 233 subFieldMeta.setDataNucleusAbsoluteFieldNumber(dataNucleusAbsoluteFieldNumber); 234 role2subFieldMeta.put(subFieldMeta.getRole(), subFieldMeta); 235 } 236 237 public void removeSubFieldMeta(FieldMeta fieldMeta) 238 { 239 role2subFieldMeta.remove(fieldMeta.getRole()); 240 } 241 242 public void removeAllSubFieldMetasExcept(FieldMetaRole ... roles) 243 { 244 if (roles == null) 245 roles = new FieldMetaRole[0]; 246 247 Set<FieldMetaRole> rolesToKeep = new HashSet<FieldMetaRole>(roles.length); 248 for (FieldMetaRole role : roles) 249 rolesToKeep.add(role); 250 251 Collection<FieldMetaRole> oldRoles = new ArrayList<FieldMetaRole>(role2subFieldMeta.keySet()); 252 for (FieldMetaRole role : oldRoles) { 253 if (!rolesToKeep.contains(role)) 254 role2subFieldMeta.remove(role); 255 } 256 } 257 258 @NotPersistent 259 private transient FieldMeta mappedByFieldMeta; 260 261 /** 262 * Used by {@link #getMappedByFieldMeta(ExecutionContext)} to mask <code>null</code> and thus 263 * prevent a second unnecessary resolve process if the first already resolved to <code>null</code>. 264 */ 265 private static final FieldMeta NULL_MAPPED_BY_FIELD_META = new FieldMeta(); 266 267 /** 268 * <p> 269 * Get the {@link FieldMeta} of the opposite end of the mapped-by-relation. If 270 * this is not a mapped-by field, this method returns <code>null</code>. 271 * </p> 272 * <p> 273 * Though, it returns always the mapped-by opposite side, the semantics of 274 * this method still depend on the {@link #getRole() role} of this <code>FieldMeta</code>: 275 * </p> 276 * <ul> 277 * <li>{@link FieldMetaRole#primary}: Returns the owner-field on the opposite side which is referenced by 278 * @Persistent(mappedBy="owner")</li> 279 * <li>{@link FieldMetaRole#mapKey}: Returns the key-field on the opposite side which is referenced by 280 * @Key(mappedBy="key")</li> 281 * <li>{@link FieldMetaRole#mapValue}: Returns the value-field on the opposite side which is referenced by 282 * @Value(mappedBy="value")</li> 283 * </ul> 284 * 285 * @return the {@link FieldMeta} of the other end of the mapped-by-relation. 286 */ 287 public FieldMeta getMappedByFieldMeta(ExecutionContext executionContext) 288 { 289 FieldMeta mbfm = mappedByFieldMeta; 290 291 if (NULL_MAPPED_BY_FIELD_META == mbfm) 292 return null; 293 294 if (mbfm != null) 295 return mbfm; 296 297 AbstractMemberMetaData mmd = getDataNucleusMemberMetaData(executionContext); 298 299 if (mmd.getMappedBy() != null) 300 { 301 Class<?> typeOppositeSide; 302 if (mmd.hasCollection()) 303 typeOppositeSide = executionContext.getClassLoaderResolver().classForName(mmd.getCollection().getElementType()); 304 else if (mmd.hasArray()) 305 typeOppositeSide = executionContext.getClassLoaderResolver().classForName(mmd.getArray().getElementType()); 306 else if (mmd.hasMap()) { 307 if (mmd.getMap().keyIsPersistent()) 308 typeOppositeSide = executionContext.getClassLoaderResolver().classForName(mmd.getMap().getKeyType()); 309 else if (mmd.getMap().valueIsPersistent()) 310 typeOppositeSide = executionContext.getClassLoaderResolver().classForName(mmd.getMap().getValueType()); 311 else 312 throw new IllegalStateException("How can a Map be mapped-by without key and value being persistent?! Exactly one of them should be persistent!"); 313 } 314 else 315 typeOppositeSide = mmd.getType(); 316 317 Cumulus4jStoreManager storeManager = (Cumulus4jStoreManager) executionContext.getStoreManager(); 318 ClassMeta classMetaOppositeSide = storeManager.getClassMeta(executionContext, typeOppositeSide); 319 String mappedBy = null; 320 321 switch (role) { 322 case primary: 323 mbfm = classMetaOppositeSide.getFieldMeta(mmd.getMappedBy()); 324 break; 325 326 case mapKey: 327 mappedBy = mmd.getKeyMetaData() == null ? null : mmd.getKeyMetaData().getMappedBy(); 328 if (mmd.getMap().valueIsPersistent() && mappedBy == null) 329 throw new IllegalStateException("The map's value is persistent via mappedBy (without @Join), but there is no @Key(mappedBy=\"...\")! This is invalid! " + mmd); 330 break; 331 332 case mapValue: 333 mappedBy = mmd.getValueMetaData() == null ? null : mmd.getValueMetaData().getMappedBy(); 334 if (mmd.getMap().keyIsPersistent() && mappedBy == null) 335 throw new IllegalStateException("The map's key is persistent via mappedBy (without @Join), but there is no @Value(mappedBy=\"...\")! This is invalid! " + mmd); 336 break; 337 } 338 339 if (mappedBy != null) { 340 mbfm = classMetaOppositeSide.getFieldMeta(mappedBy); 341 if (mbfm == null) 342 throw new IllegalStateException("Field \"" + mappedBy + "\" referenced in 'mappedBy' of " + this + " does not exist!"); 343 } 344 } 345 346 if (mbfm == null) 347 mappedByFieldMeta = NULL_MAPPED_BY_FIELD_META; 348 else 349 mappedByFieldMeta = mbfm; 350 351 return mbfm; 352 } 353 354 @Override 355 public void jdoPreDetach() { } 356 357 @Override 358 public void jdoPostDetach(Object o) { 359 FieldMeta attached = (FieldMeta) o; 360 FieldMeta detached = this; 361 detached.dataNucleusAbsoluteFieldNumber = attached.dataNucleusAbsoluteFieldNumber; 362 } 363 364 @Override 365 public int hashCode() 366 { 367 return (int) (fieldID ^ (fieldID >>> 32)); 368 } 369 370 @Override 371 public boolean equals(Object obj) 372 { 373 if (this == obj) return true; 374 if (obj == null) return false; 375 if (getClass() != obj.getClass()) return false; 376 FieldMeta other = (FieldMeta) obj; 377 return this.fieldID == other.fieldID; 378 } 379 380 @Override 381 public String toString() 382 { 383 ClassMeta cm = getClassMeta(); 384 return ( 385 this.getClass().getName() 386 + '@' 387 + Integer.toHexString(System.identityHashCode(this)) 388 + '[' 389 + fieldID + ',' + (cm == null ? null : cm.getClassName()) + '#' + getFieldName() + '[' + role + ']' 390 + ']' 391 ); 392 } 393 394 @NotPersistent 395 private AbstractMemberMetaData dataNucleusMemberMetaData; 396 397 public AbstractMemberMetaData getDataNucleusMemberMetaData(ExecutionContext executionContext) 398 { 399 if (dataNucleusMemberMetaData != null) 400 return dataNucleusMemberMetaData; 401 402 AbstractClassMetaData dnClassMetaData = getClassMeta().getDataNucleusClassMetaData(executionContext); 403 404 int dnFieldNumber = getDataNucleusAbsoluteFieldNumber(); 405 if (dnFieldNumber < 0) 406 throw new IllegalStateException("The method getDataNucleusMemberMetaData(...) can only be called on FieldMeta instances that were obtained via Cumulus4jStoreManager#getClassMeta(org.datanucleus.store.ExecutionContext, Class)!!!"); 407 408 AbstractMemberMetaData dnMemberMetaData = dnClassMetaData.getMetaDataForManagedMemberAtAbsolutePosition(dnFieldNumber); 409 if (dnMemberMetaData == null) 410 throw new IllegalStateException("DataNucleus has no meta-data for this field: fieldID=" + getFieldID() + " className=" + classMeta.getClassName() + " fieldName=" + getFieldName()); 411 412 dataNucleusMemberMetaData = dnMemberMetaData; 413 return dnMemberMetaData; 414 } 415 }