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    }