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.HashSet;
022 import java.util.Set;
023
024 import javax.jdo.JDOHelper;
025 import javax.jdo.JDOObjectNotFoundException;
026 import javax.jdo.PersistenceManager;
027 import javax.jdo.annotations.Column;
028 import javax.jdo.annotations.IdGeneratorStrategy;
029 import javax.jdo.annotations.IdentityType;
030 import javax.jdo.annotations.NullValue;
031 import javax.jdo.annotations.PersistenceCapable;
032 import javax.jdo.annotations.Persistent;
033 import javax.jdo.annotations.PrimaryKey;
034 import javax.jdo.annotations.Queries;
035 import javax.jdo.annotations.Query;
036 import javax.jdo.annotations.Unique;
037 import javax.jdo.annotations.Version;
038 import javax.jdo.annotations.VersionStrategy;
039 import javax.jdo.identity.LongIdentity;
040 import javax.jdo.listener.StoreCallback;
041
042 /**
043 * Persistent container holding an entity's data in <b>encrypted</b> form.
044 *
045 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
046 */
047 @PersistenceCapable(identityType=IdentityType.APPLICATION, detachable="true")
048 @Version(strategy=VersionStrategy.VERSION_NUMBER)
049 @Unique(name="DataEntry_classMeta_objectID", members={"classMeta", "objectID"})
050 @Queries({
051 @Query(
052 name="getDataEntryByClassMetaAndObjectID",
053 value="SELECT UNIQUE WHERE this.classMeta == :classMeta && this.objectID == :objectID"
054 ),
055 @Query(
056 name="getDataEntryIDByClassMetaAndObjectID",
057 value="SELECT UNIQUE this.dataEntryID WHERE this.classMeta == :classMeta && this.objectID == :objectID"
058 ),
059 @Query(
060 name="getDataEntryIDsByClassMetaAndObjectIDNegated",
061 value="SELECT this.dataEntryID WHERE this.classMeta == :classMeta && this.objectID != :notThisObjectID"
062 )
063 })
064 public class DataEntry
065 implements StoreCallback
066 {
067 /**
068 * Get the <code>DataEntry</code> identified by the specified {@link #getDataEntryID() dataEntryID} or
069 * <code>null</code> if no such instance exists.
070 * @param pmData the backend-<code>PersistenceManager</code>. Must not be <code>null</code>.
071 * @param dataEntryID the <code>DataEntry</code>'s {@link #getDataEntryID() identifier}.
072 * @return the <code>DataEntry</code> matching the given <code>dataEntryID</code> or <code>null</code>, if no such instance exists.
073 */
074 public static DataEntry getDataEntry(PersistenceManager pmData, long dataEntryID)
075 {
076 DataEntry dataEntry;
077 try {
078 dataEntry = (DataEntry) pmData.getObjectById(new LongIdentity(DataEntry.class, dataEntryID));
079 } catch (JDOObjectNotFoundException x) {
080 dataEntry = null;
081 }
082 return dataEntry;
083 }
084
085 /**
086 * Get the <code>DataEntry</code> identified by the given type and JDO/JPA-object-ID.
087 *
088 * @param pmData the backend-<code>PersistenceManager</code>. Must not be <code>null</code>.
089 * @param classMeta reference to the searched <code>DataEntry</code>'s {@link #getClassMeta() classMeta} (which must match
090 * the searched instance's concrete type - <b>not</b> the root-type of the inheritance tree!).
091 * @param objectID the <code>String</code>-representation of the JDO/JPA-object-ID.
092 * @return the <code>DataEntry</code> matching the given combination of <code>classMeta</code> and <code>objectID</code>;
093 * or <code>null</code>, if no such instance exists.
094 */
095 public static DataEntry getDataEntry(PersistenceManager pmData, ClassMeta classMeta, String objectID)
096 {
097 javax.jdo.Query q = pmData.newNamedQuery(DataEntry.class, "getDataEntryByClassMetaAndObjectID");
098 return (DataEntry) q.execute(classMeta, objectID);
099 // UNIQUE query does not need to be closed, because there is no result list lingering.
100 }
101
102 /**
103 * <p>
104 * Get the {@link #getDataEntryID() dataEntryID} of the <code>DataEntry</code> identified by the
105 * given type and JDO/JPA-object-ID.
106 * </p>
107 * <p>
108 * This method is equivalent to first calling
109 * </p>
110 * <pre>DataEntry e = {@link #getDataEntry(PersistenceManager, ClassMeta, String)}</pre>
111 * <p>
112 * and then
113 * </p>
114 * <pre>e == null ? null : Long.valueOf({@link #getDataEntryID() e.getDataEntryID()})</pre>
115 * <p>
116 * but faster, because it does not query unnecessary data from the underlying database.
117 * </p>
118 *
119 * @param pmData the backend-<code>PersistenceManager</code>. Must not be <code>null</code>.
120 * @param classMeta reference to the searched <code>DataEntry</code>'s {@link #getClassMeta() classMeta} (which must match
121 * the searched instance's concrete type - <b>not</b> the root-type of the inheritance tree!).
122 * @param objectID the <code>String</code>-representation of the JDO/JPA-object-ID.
123 * @return the {@link #getDataEntryID() dataEntryID} of the <code>DataEntry</code> matching the
124 * given combination of <code>classMeta</code> and <code>objectID</code>;
125 * or <code>null</code>, if no such instance exists.
126 */
127 public static Long getDataEntryID(PersistenceManager pmData, ClassMeta classMeta, String objectID)
128 {
129 javax.jdo.Query q = pmData.newNamedQuery(DataEntry.class, "getDataEntryIDByClassMetaAndObjectID");
130 return (Long) q.execute(classMeta, objectID);
131 // UNIQUE query does not need to be closed, because there is no result list lingering.
132 }
133
134 /**
135 * <p>
136 * Get the {@link #getDataEntryID() dataEntryID}s of all those <code>DataEntry</code> instances
137 * which do <b>not</b> match the given type and JDO/JPA-object-ID.
138 * </p>
139 * <p>
140 * This method is thus the negation of {@link #getDataEntryID(PersistenceManager, ClassMeta, String)}.
141 * </p>
142 *
143 * @param pmData the backend-<code>PersistenceManager</code>. Must not be <code>null</code>.
144 * @param classMeta reference to the searched <code>DataEntry</code>'s {@link #getClassMeta() classMeta} (which must match
145 * the searched instance's concrete type - <b>not</b> the root-type of the inheritance tree!).
146 * @param notThisObjectID the <code>String</code>-representation of the JDO/JPA-object-ID, which should be
147 * excluded.
148 * @return the {@link #getDataEntryID() dataEntryID}s of those <code>DataEntry</code>s which match the given
149 * <code>classMeta</code> but have an object-ID different from the one specified as <code>notThisObjectID</code>.
150 */
151 public static Set<Long> getDataEntryIDsNegated(PersistenceManager pmData, ClassMeta classMeta, String notThisObjectID)
152 {
153 javax.jdo.Query q = pmData.newNamedQuery(DataEntry.class, "getDataEntryIDsByClassMetaAndObjectIDNegated");
154 @SuppressWarnings("unchecked")
155 Collection<Long> dataEntryIDsColl = (Collection<Long>) q.execute(classMeta, notThisObjectID);
156 Set<Long> dataEntryIDsSet = new HashSet<Long>(dataEntryIDsColl);
157 q.closeAll();
158 return dataEntryIDsSet;
159 }
160
161 @PrimaryKey
162 @Persistent(valueStrategy=IdGeneratorStrategy.NATIVE)
163 private long dataEntryID = -1;
164
165 @Persistent(nullValue=NullValue.EXCEPTION)
166 private ClassMeta classMeta;
167
168 @Persistent(nullValue=NullValue.EXCEPTION)
169 @Column(length=255)
170 private String objectID;
171
172 private long keyID = -1;
173
174 private byte[] value;
175
176 /**
177 * Internal constructor. This exists only for JDO and should not be used by application code!
178 */
179 protected DataEntry() { }
180
181 /**
182 * Create an instance of <code>DataEntry</code>.
183 * @param classMeta the type of the entity persisted in this container (which must be the entity's concrete type -
184 * <b>not</b> the root-type of the inheritance tree!). See {@link #getClassMeta()} for further details.
185 * @param objectID the <code>String</code>-representation of the entity's identifier (aka OID or object-ID).
186 * See {@link #getObjectID()} for further details.
187 */
188 public DataEntry(ClassMeta classMeta, String objectID) {
189 this.classMeta = classMeta;
190 this.objectID = objectID;
191 }
192
193 /**
194 * Get the single primary key field (= object-identifier) of <code>DataEntry</code>.
195 * @return the object-identifier (= primary key).
196 */
197 public long getDataEntryID() {
198 return dataEntryID;
199 }
200
201 /**
202 * <p>
203 * Get the type of the entity persisted in this container.
204 * </p>
205 * <p>
206 * Note, that this is the concrete type of the persisted object and <b>not</b> the root-type of the
207 * persistable hierarchy. For example, if <code>bbb</code> is persisted and <code>bbb</code> is an instance of
208 * class <code>BBB</code> which extends <code>AAA</code>
209 * and both classes are persistable, this will point to class <code>BBB</code> (and <b>not</b> <code>AAA</code>).
210 * </p>
211 * <p>
212 * Therefore, if you want to query all instances of a certain type including subclasses, you have to
213 * ask for the sub-classes via {@link org.datanucleus.store.StoreManager#getSubClassesForClass(String, boolean, org.datanucleus.ClassLoaderResolver)}
214 * first and then query for all these classes individually.
215 * </p>
216 * @return the type of the entity.
217 */
218 public ClassMeta getClassMeta() {
219 return classMeta;
220 }
221
222 /**
223 * <p>
224 * Get the <code>String</code>-representation of the entity's identifier.
225 * </p>
226 * <p>
227 * For JDO, please read the following (and related) documentation:
228 * </p>
229 * <ul>
230 * <li><a target="_blank" href="http://www.datanucleus.org/products/accessplatform_3_0/jdo/application_identity.html">JDO Mapping / Identity / Application Identity</a></li>
231 * <li><a target="_blank" href="http://www.datanucleus.org/products/accessplatform_3_0/jdo/datastore_identity.html">JDO Mapping / Identity / Datastore Identity</a></li>
232 * </ul>
233 * <p>
234 * For JPA, please read the following (and related) documentation:
235 * </p>
236 * <ul>
237 * <li><a target="_blank" href="http://www.datanucleus.org/products/accessplatform_3_0/jpa/application_identity.html">JPA Mapping / Identity / Application Identity</a></li>
238 * <li><a target="_blank" href="http://www.datanucleus.org/products/accessplatform_3_0/jpa/datastore_identity.html">JPA Mapping / Identity / Datastore Identity</a></li>
239 * </ul>
240 *
241 * @return the OID in String-form
242 * (e.g. the result of <code><a target="_blank" href="http://db.apache.org/jdo/api30/apidocs/javax/jdo/JDOHelper.html#getObjectId%28java.lang.Object%29">JDOHelper.getObjectId</a>(entity).toString()</code>
243 * when using JDO).
244 */
245 public String getObjectID() {
246 return objectID;
247 }
248
249 /**
250 * Get the identifier of the encryption-key used to encrypt the {@link #getValue() value}.
251 * @return the encryption-key used to encrypt this <code>DataEntry</code>'s contents.
252 * @see #setKeyID(long)
253 */
254 public long getKeyID() {
255 return keyID;
256 }
257
258 /**
259 * Set the identifier of the encryption-key used to encrypt the {@link #getValue() value}.
260 * @param keyID the encryption-key used to encrypt this <code>DataEntry</code>'s contents.
261 * @see #getKeyID()
262 */
263 public void setKeyID(long keyID)
264 {
265 if (keyID < 0)
266 throw new IllegalArgumentException("keyID < 0");
267
268 this.keyID = keyID;
269 }
270
271 /**
272 * Get the <b>encrypted</b> data of an entity. The entity is transformed ("made flat") into an {@link ObjectContainer}
273 * which is then serialised using Java native serialisation and finally encrypted.
274 * @return the <b>encrypted</b> serialised data of an {@link ObjectContainer} holding the entity's data.
275 * @see #setValue(byte[])
276 */
277 public byte[] getValue() {
278 return value;
279 }
280
281 /**
282 * Set the <b>encrypted</b> data of an entity.
283 * @param value the <b>encrypted</b> serialised data of an {@link ObjectContainer} holding the entity's data.
284 * @see #getValue()
285 */
286 public void setValue(byte[] value) {
287 this.value = value;
288 }
289
290 @Override
291 public int hashCode() {
292 return (int) (dataEntryID ^ (dataEntryID >>> 32));
293 }
294
295 @Override
296 public boolean equals(Object obj) {
297 if (this == obj) return true;
298 if (obj == null) return false;
299 if (getClass() != obj.getClass()) return false;
300 DataEntry other = (DataEntry) obj;
301 return this.dataEntryID == other.dataEntryID;
302 }
303
304 @Override
305 public void jdoPreStore()
306 {
307 // We replace 'this.classMeta' by a persistent version, because it is a detached object
308 // which slows down the storing process immensely, as it is unnecessarily attached.
309 // Attaching an object is an expensive operation and we neither want nor need to
310 // update the ClassMeta object when persisting a new DataEntry.
311 PersistenceManager pm = JDOHelper.getPersistenceManager(this);
312 Object classMetaID = JDOHelper.getObjectId(classMeta);
313 classMeta = (ClassMeta) pm.getObjectById(classMetaID);
314 }
315 }