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 javax.jdo.JDOHelper; 021 import javax.jdo.PersistenceManager; 022 import javax.jdo.annotations.Column; 023 import javax.jdo.annotations.IdGeneratorStrategy; 024 import javax.jdo.annotations.IdentityType; 025 import javax.jdo.annotations.Inheritance; 026 import javax.jdo.annotations.InheritanceStrategy; 027 import javax.jdo.annotations.NotPersistent; 028 import javax.jdo.annotations.NullValue; 029 import javax.jdo.annotations.PersistenceCapable; 030 import javax.jdo.annotations.Persistent; 031 import javax.jdo.annotations.PrimaryKey; 032 import javax.jdo.annotations.Version; 033 import javax.jdo.annotations.VersionStrategy; 034 import javax.jdo.listener.StoreCallback; 035 036 /** 037 * <p> 038 * Persistent index information with <b>encrypted</b> pointers to {@link DataEntry}s. 039 * </p> 040 * <p> 041 * Since the index is type-specific, there are sub-classes for each data type. One 042 * {@link IndexEntry} instance is used for each distinct value of one certain field. 043 * Therefore, the field (represented by the property {@link #getFieldMeta() fieldMeta}) 044 * and the value together form a unique key of <code>IndexEntry</code> - thus the value 045 * is represented by the property {@link #getIndexKey() indexKey}. 046 * </p> 047 * <p> 048 * Example: 049 * </p> 050 * <pre> 051 * // persistent class: 052 * @PersistenceCapable 053 * class Person 054 * { 055 * public Person(String firstName, String lastName) { 056 * this.firstName = firstName; 057 * this.lastName = lastName; 058 * } 059 * 060 * private String firstName; 061 * private String lastName; 062 * 063 * // ... 064 * } 065 * 066 * class SomeTest 067 * { 068 * @Test 069 * public void persistPersons() 070 * { 071 * pm.makePersistent(new Person("Alice", "Müller")); 072 * pm.makePersistent(new Person("Alice", "Meier")); 073 * } 074 * } 075 * </pre> 076 * <p> 077 * After running this test, there would be three instances of {@link IndexEntryStringShort} in the database 078 * indexing the values "Alice", "Müller" and "Meier". Note, that "Alice" occurs only once in the index, even though 079 * there are two <code>Person</code> instances using it. The two persons would be referenced from the one index-entry 080 * via {@link #getIndexValue()}. 081 * </p> 082 * 083 * 084 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de 085 */ 086 @PersistenceCapable(identityType=IdentityType.APPLICATION) 087 @Inheritance(strategy=InheritanceStrategy.SUBCLASS_TABLE) 088 @Version(strategy=VersionStrategy.VERSION_NUMBER) 089 //@Unique(members={"keyStoreRefID", "fieldMeta_fieldID", "classMeta_classID", "indexKey"}) 090 public abstract class IndexEntry 091 implements StoreCallback 092 { 093 @PrimaryKey 094 @Persistent(valueStrategy=IdGeneratorStrategy.NATIVE, sequence="IndexEntrySequence") 095 private Long indexEntryID; 096 097 @Persistent(nullValue=NullValue.EXCEPTION) 098 @Column(defaultValue="0") 099 private int keyStoreRefID; 100 101 @NotPersistent 102 private FieldMeta fieldMeta; 103 104 @Column(name="fieldMeta_fieldID_oid") // for downward-compatibility 105 private long fieldMeta_fieldID = -1; 106 107 @NotPersistent 108 private ClassMeta classMeta; 109 110 @Column(name="classMeta_classID_oid", // not necessary for downward-compatibility (new field), but for the sake of consistency 111 defaultValue="-1") 112 private long classMeta_classID = -1; 113 114 private long keyID = -1; 115 116 /** DataEntryIDs for this indexed key. */ 117 private byte[] indexValue; 118 119 /** 120 * Get the single primary key field (= object-identifier) of this <code>IndexEntry</code>. 121 * 122 * @return the object-identifier (= primary key). 123 */ 124 public long getIndexEntryID() { 125 return indexEntryID == null ? -1 : indexEntryID; 126 } 127 128 /** 129 * <p> 130 * Get the descriptor of the indexed field. 131 * </p> 132 * <p> 133 * Every <code>IndexEntry</code> instance belongs to one field or a part of the field (e.g. a <code>Map</code>'s key). 134 * </p> 135 * @return the descriptor of the indexed field. 136 */ 137 public FieldMeta getFieldMeta() { 138 if (fieldMeta == null) { 139 if (fieldMeta_fieldID < 0) 140 return null; 141 142 fieldMeta = new FieldMetaDAO(getPersistenceManager()).getFieldMeta(fieldMeta_fieldID, true); 143 } 144 return fieldMeta; 145 } 146 147 protected void setFieldMeta(FieldMeta fieldMeta) { 148 if (this.fieldMeta != null && !this.fieldMeta.equals(fieldMeta)) 149 throw new IllegalStateException("The property fieldMeta cannot be modified after being set once!"); 150 151 this.fieldMeta = fieldMeta; 152 this.fieldMeta_fieldID = fieldMeta == null ? -1 : fieldMeta.getFieldID(); 153 if (this.fieldMeta_fieldID < 0) 154 throw new IllegalStateException("fieldMeta not persisted yet: " + fieldMeta); 155 } 156 157 /** 158 * Get the {@link ClassMeta} of the concrete type of the instance containing the field. 159 * <p> 160 * If a field is declared in a super-class, all sub-classes have it, too. But when querying 161 * instances of a sub-class (either as candidate-class or in a relation (as concrete type of 162 * the field/property), only this given sub-class and its sub-classes should be found. 163 * <p> 164 * The <code>ClassMeta</code> here is either the same as {@link FieldMeta#getClassMeta() fieldMeta.classMeta} 165 * (if it is an instance of the class declaring the field) or a <code>ClassMeta</code> of a sub-class of 166 * <code>fieldMeta.classMeta</code>. 167 * @return the {@link ClassMeta} of the concrete type of the instance containing the field. 168 */ 169 public ClassMeta getClassMeta() { 170 if (classMeta == null) { 171 if (classMeta_classID < 0) 172 return null; 173 174 classMeta = new ClassMetaDAO(getPersistenceManager()).getClassMeta(classMeta_classID, true); 175 } 176 return classMeta; 177 } 178 179 protected PersistenceManager getPersistenceManager() { 180 PersistenceManager pm = JDOHelper.getPersistenceManager(this); 181 if (pm == null) { 182 throw new IllegalStateException("JDOHelper.getPersistenceManager(this) returned null! " + this); 183 } 184 return pm; 185 } 186 187 public void setClassMeta(ClassMeta classMeta) { 188 if (this.classMeta != null && !this.classMeta.equals(classMeta)) 189 throw new IllegalStateException("The property classMeta cannot be modified after being set once!"); 190 191 this.classMeta = classMeta; 192 this.classMeta_classID = classMeta == null ? -1 : classMeta.getClassID(); 193 if (this.classMeta_classID < 0) 194 throw new IllegalStateException("classMeta not persisted yet: " + classMeta); 195 } 196 197 /** 198 * Get the numeric identifier of the key store. The key store's String-ID is mapped to this numeric ID 199 * via {@link KeyStoreRef} instances. 200 * @return the numeric identifier of the key store. 201 */ 202 public int getKeyStoreRefID() { 203 return keyStoreRefID; 204 } 205 206 /** 207 * Set the numeric identifier of the key store. 208 * @param keyStoreRefID the numeric identifier of the key store. 209 */ 210 public void setKeyStoreRefID(int keyStoreRefID) { 211 this.keyStoreRefID = keyStoreRefID; 212 } 213 214 /** 215 * Get the value which is indexed by this instance. It serves as 2nd part of the unique key together 216 * with the property {@link #getFieldMeta() fieldMeta}. 217 * @return the key. 218 */ 219 public abstract Object getIndexKey(); 220 221 protected abstract void setIndexKey(Object indexKey); 222 223 /** 224 * Get the identifier of the encryption-key used to encrypt the {@link #getIndexValue() indexValue}. 225 * @return the encryption-key used to encrypt this <code>IndexEntry</code>'s contents. 226 * @see #setKeyID(long) 227 */ 228 public long getKeyID() { 229 return keyID; 230 } 231 232 /** 233 * Set the identifier of the encryption-key used to encrypt the {@link #getIndexValue() indexValue}. 234 * @param keyID the encryption-key used to encrypt this <code>IndexEntry</code>'s contents. 235 * @see #getKeyID() 236 */ 237 public void setKeyID(long keyID) 238 { 239 if (keyID < 0) 240 throw new IllegalArgumentException("keyID < 0"); 241 242 this.keyID = keyID; 243 } 244 245 /** 246 * Get the <b>encrypted</b> pointers to {@link DataEntry}. After decrypting 247 * this byte array, you can pass it to {@link IndexValue#IndexValue(byte[])}. 248 * 249 * @return the <b>encrypted</b> pointers to {@link DataEntry}s. 250 */ 251 public byte[] getIndexValue() { 252 return indexValue; 253 } 254 255 public void setIndexValue(byte[] indexValue) { 256 this.indexValue = indexValue; 257 } 258 259 @Override 260 public int hashCode() { 261 long indexEntryID = getIndexEntryID(); 262 return (int) (indexEntryID ^ (indexEntryID >>> 32)); 263 } 264 265 @Override 266 public boolean equals(Object obj) { 267 if (this == obj) return true; 268 if (obj == null) return false; 269 if (getClass() != obj.getClass()) return false; 270 IndexEntry other = (IndexEntry) obj; 271 return this.getIndexEntryID() < 0 ? false : this.getIndexEntryID() == other.getIndexEntryID(); 272 } 273 274 @Override 275 public void jdoPreStore() 276 { 277 if (fieldMeta_fieldID < 0) 278 throw new IllegalStateException("fieldMeta_fieldID < 0"); 279 280 // Must allow this for updatability. At least temporarily. TODO find a better solution. 281 if (classMeta_classID < 0) 282 throw new IllegalStateException("classMeta_classID < 0"); 283 } 284 }