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.Collection; 021 import java.util.HashMap; 022 import java.util.Map; 023 024 import javax.jdo.FetchPlan; 025 import javax.jdo.JDOObjectNotFoundException; 026 import javax.jdo.PersistenceManager; 027 import javax.jdo.annotations.Column; 028 import javax.jdo.annotations.FetchGroup; 029 import javax.jdo.annotations.FetchGroups; 030 import javax.jdo.annotations.IdGeneratorStrategy; 031 import javax.jdo.annotations.IdentityType; 032 import javax.jdo.annotations.Key; 033 import javax.jdo.annotations.NotPersistent; 034 import javax.jdo.annotations.NullValue; 035 import javax.jdo.annotations.PersistenceCapable; 036 import javax.jdo.annotations.Persistent; 037 import javax.jdo.annotations.PrimaryKey; 038 import javax.jdo.annotations.Queries; 039 import javax.jdo.annotations.Query; 040 import javax.jdo.annotations.Unique; 041 import javax.jdo.annotations.Version; 042 import javax.jdo.annotations.VersionStrategy; 043 044 import org.datanucleus.metadata.AbstractClassMetaData; 045 import org.datanucleus.store.ExecutionContext; 046 047 /** 048 * Persistent meta-data for a persistence-capable {@link Class}. Since class names are very long, 049 * we use the {@link #getClassID() classID} instead in our index and data entities (e.g. in the relation 050 * {@link DataEntry#getClassMeta() DataEntry.classMeta}). 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 @Unique(name="ClassMeta_fullyQualifiedClassName", members={"packageName", "simpleClassName"}) 057 @FetchGroups({ 058 @FetchGroup(name=FetchPlan.ALL, members={ 059 @Persistent(name="superClassMeta", recursionDepth=-1) 060 }) 061 }) 062 @Queries({ 063 @Query( 064 name="getClassMetaByPackageNameAndSimpleClassName", 065 value="SELECT UNIQUE WHERE this.packageName == :packageName && this.simpleClassName == :simpleClassName" 066 ) 067 }) 068 public class ClassMeta 069 { 070 public static ClassMeta getClassMeta(PersistenceManager pm, String packageName, String simpleClassName, boolean throwExceptionIfNotFound) 071 { 072 javax.jdo.Query q = pm.newNamedQuery(ClassMeta.class, "getClassMetaByPackageNameAndSimpleClassName"); 073 ClassMeta result = (ClassMeta) q.execute(packageName, simpleClassName); 074 075 if (result == null && throwExceptionIfNotFound) 076 throw new JDOObjectNotFoundException( 077 "No ClassMeta found for packageName=\"" + packageName + "\" and simpleClassName=\"" + simpleClassName + "\"!" 078 ); 079 080 return result; 081 } 082 083 public static ClassMeta getClassMeta(PersistenceManager pm, Class<?> clazz, boolean throwExceptionIfNotFound) 084 { 085 String packageName = clazz.getPackage() == null ? "" : clazz.getPackage().getName(); 086 String simpleClassName = clazz.getSimpleName(); 087 return getClassMeta(pm, packageName, simpleClassName, throwExceptionIfNotFound); 088 } 089 090 @PrimaryKey 091 @Persistent(valueStrategy=IdGeneratorStrategy.NATIVE) 092 private long classID = -1; 093 094 @NotPersistent 095 private transient String className; 096 097 @Persistent(nullValue=NullValue.EXCEPTION) 098 @Column(length=255) 099 private String packageName; 100 101 @Persistent(nullValue=NullValue.EXCEPTION) 102 @Column(length=255) 103 private String simpleClassName; 104 105 private ClassMeta superClassMeta; 106 107 @Persistent(mappedBy="classMeta", dependentValue="true") 108 @Key(mappedBy="fieldName") 109 private Map<String, FieldMeta> fieldName2fieldMeta; 110 111 @NotPersistent 112 private Map<Long, FieldMeta> fieldID2fieldMeta; 113 114 protected ClassMeta() { } 115 116 public ClassMeta(Class<?> clazz) { 117 this.packageName = clazz.getPackage() == null ? "" : clazz.getPackage().getName(); 118 this.simpleClassName = clazz.getSimpleName(); 119 this.fieldName2fieldMeta = new HashMap<String, FieldMeta>(); 120 } 121 122 public long getClassID() { 123 return classID; 124 } 125 126 /** 127 * Get the package name or an empty <code>String</code> for the default package. 128 * @return the package name (maybe empty, but never <code>null</code>). 129 */ 130 public String getPackageName() { 131 return packageName; 132 } 133 134 public String getSimpleClassName() { 135 return simpleClassName; 136 } 137 138 /** 139 * Get the fully qualified class name (composed of {@link #getPackageName() packageName} and {@link #getSimpleClassName() simpleClassName}). 140 * @return the fully qualified class name. 141 */ 142 public String getClassName() 143 { 144 String cn = className; 145 if (cn == null) { 146 if (packageName.isEmpty()) 147 cn = simpleClassName; 148 else 149 cn = packageName + '.' + simpleClassName; 150 151 className = cn; 152 } 153 return cn; 154 } 155 156 /** 157 * The super-class' meta-data or <code>null</code>, if there is no <b>persistence-capable</b> super-class. 158 * @return the super-class' meta-data or <code>null</code>. 159 */ 160 public ClassMeta getSuperClassMeta() { 161 return superClassMeta; 162 } 163 164 public void setSuperClassMeta(ClassMeta superClassMeta) { 165 this.superClassMeta = superClassMeta; 166 } 167 168 /** 169 * Get all {@link FieldMeta} instances known to this instance. This is the meta-data for all fields 170 * <b>directly declared</b> in the class referenced by this <code>ClassMeta</code> <b>not 171 * including super-classes</b>. 172 * @return Collection of FieldMeta objects for this class 173 */ 174 public Collection<FieldMeta> getFieldMetas() { 175 return fieldName2fieldMeta.values(); 176 } 177 178 /** 179 * Get the {@link FieldMeta} for a field that is <b>directly declared</b> in the class referenced by 180 * this <code>ClassMeta</code>. This method thus does not take super-classes into account. 181 * 182 * @param fieldName the simple field name (no class prefix). 183 * @return the {@link FieldMeta} corresponding to the specified <code>fieldName</code> or <code>null</code>, if no such field 184 * exists. 185 * @see #getFieldMeta(long) 186 * @see #getFieldMeta(String, String) 187 */ 188 public FieldMeta getFieldMeta(String fieldName) { 189 return fieldName2fieldMeta.get(fieldName); 190 } 191 192 /** 193 * <p> 194 * Get the {@link FieldMeta} for a field that is either directly declared in the class referenced by this 195 * <code>ClassMeta</code> or in a super-class. 196 * </p> 197 * <p> 198 * If <code>className</code> is <code>null</code>, this method 199 * searches recursively in the inheritance hierarchy upwards (i.e. first this class then the super-class, 200 * then the next super-class etc.) until it finds a field matching the given <code>fieldName</code>. 201 * </p> 202 * <p> 203 * If <code>className</code> is not <code>null</code>, this method searches only in the specified class. 204 * If <code>className</code> is neither the current class nor any super-class, this method always returns 205 * <code>null</code>. 206 * </p> 207 * 208 * @param className the fully qualified class-name of the class referenced by this <code>ClassMeta</code> 209 * or any super-class. <code>null</code> to search the entire class hierarchy upwards (through all super-classes 210 * until the field is found or the last super-class was investigated). 211 * @param fieldName the simple field name (no class prefix). 212 * @return the {@link FieldMeta} matching the given criteria or <code>null</code> if no such field could be found. 213 */ 214 public FieldMeta getFieldMeta(String className, String fieldName) { 215 if (className == null) { 216 FieldMeta fieldMeta = getFieldMeta(fieldName); 217 if (fieldMeta != null) 218 return fieldMeta; 219 220 if (superClassMeta != null) 221 return superClassMeta.getFieldMeta(className, fieldName); 222 else 223 return null; 224 } 225 else { 226 if (getClassName().equals(className)) 227 return getFieldMeta(fieldName); 228 else if (superClassMeta != null) 229 return superClassMeta.getFieldMeta(className, fieldName); 230 else 231 return null; 232 } 233 } 234 235 /** 236 * Get the {@link FieldMeta} with the specified {@link FieldMeta#getFieldID() fieldID}. It does not matter, if 237 * this field is directly in the class referenced by this <code>ClassMeta</code> or in a super-class. 238 * @param fieldID the {@link FieldMeta#getFieldID() fieldID} of the <code>FieldMeta</code> to be found. 239 * @return the {@link FieldMeta} referenced by the given <code>fieldID</code> or <code>null</code>, if no such 240 * field exists in the class or any super-class. 241 */ 242 public FieldMeta getFieldMeta(long fieldID) 243 { 244 Map<Long, FieldMeta> m = fieldID2fieldMeta; 245 246 if (m == null) { 247 m = new HashMap<Long, FieldMeta>(fieldName2fieldMeta.size()); 248 for (FieldMeta fieldMeta : fieldName2fieldMeta.values()) 249 m.put(fieldMeta.getFieldID(), fieldMeta); 250 251 fieldID2fieldMeta = m; 252 } 253 254 FieldMeta fieldMeta = m.get(fieldID); 255 if (fieldMeta != null) 256 return fieldMeta; 257 258 if (superClassMeta != null) 259 return superClassMeta.getFieldMeta(fieldID); 260 else 261 return null; 262 } 263 264 public void addFieldMeta(FieldMeta fieldMeta) { 265 if (!this.equals(fieldMeta.getClassMeta())) 266 throw new IllegalArgumentException("fieldMeta.classMeta != this"); 267 268 fieldName2fieldMeta.put(fieldMeta.getFieldName(), fieldMeta); 269 fieldID2fieldMeta = null; 270 } 271 272 public void removeFieldMeta(FieldMeta fieldMeta) { 273 if (!this.equals(fieldMeta.getClassMeta())) 274 throw new IllegalArgumentException("fieldMeta.classMeta != this"); 275 276 fieldName2fieldMeta.remove(fieldMeta.getFieldName()); 277 fieldID2fieldMeta = null; 278 } 279 280 @Override 281 public int hashCode() { 282 return (int) (classID ^ (classID >>> 32)); 283 } 284 285 @Override 286 public boolean equals(Object obj) { 287 if (this == obj) return true; 288 if (obj == null) return false; 289 if (getClass() != obj.getClass()) return false; 290 ClassMeta other = (ClassMeta) obj; 291 return this.classID == other.classID; 292 } 293 294 @Override 295 public String toString() { 296 return ( 297 this.getClass().getName() 298 + '@' 299 + Integer.toHexString(System.identityHashCode(this)) 300 + '[' + classID + ',' + getClassName() + ']' 301 ); 302 } 303 304 @NotPersistent 305 private AbstractClassMetaData dataNucleusClassMetaData; 306 307 public AbstractClassMetaData getDataNucleusClassMetaData(ExecutionContext executionContext) 308 { 309 if (dataNucleusClassMetaData != null) 310 return dataNucleusClassMetaData; 311 312 AbstractClassMetaData dnClassMetaData = executionContext.getMetaDataManager().getMetaDataForClass(getClassName(), executionContext.getClassLoaderResolver()); 313 if (dnClassMetaData == null) 314 throw new IllegalStateException("DataNucleus does not know any meta-data for this class: classID=" + getClassID() + " className=" + getClassName()); 315 316 dataNucleusClassMetaData = dnClassMetaData; 317 return dnClassMetaData; 318 } 319 }