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 }