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.Index;
026    import javax.jdo.annotations.Indices;
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.Queries;
033    import javax.jdo.annotations.Query;
034    import javax.jdo.annotations.Sequence;
035    import javax.jdo.annotations.SequenceStrategy;
036    import javax.jdo.annotations.Unique;
037    import javax.jdo.annotations.Version;
038    import javax.jdo.annotations.VersionStrategy;
039    import javax.jdo.listener.StoreCallback;
040    
041    import org.cumulus4j.store.datastoreversion.command.IntroduceKeyStoreRefID;
042    
043    /**
044     * Persistent container holding an entity's data in <b>encrypted</b> form.
045     *
046     * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
047     */
048    @PersistenceCapable(identityType=IdentityType.APPLICATION, detachable="true")
049    @Version(strategy=VersionStrategy.VERSION_NUMBER)
050    @Sequence(name="DataEntrySequence", datastoreSequence="DataEntrySequence", initialValue=0, strategy=SequenceStrategy.CONTIGUOUS)
051    @Unique(members={"keyStoreRefID", "classMeta_classID", "objectID"})
052    @Indices({
053            @Index(members={"keyStoreRefID", "classMeta_classID"}),
054            @Index(members={"classMeta_classID"})
055    })
056    @Queries({
057            @Query(
058                            name=DataEntry.NamedQueries.getDataEntryByClassMetaClassIDAndObjectID,
059                            value="SELECT UNIQUE WHERE this.keyStoreRefID == :keyStoreRefID && this.classMeta_classID == :classMeta_classID && this.objectID == :objectID"
060            ),
061            @Query(
062                            name=DataEntry.NamedQueries.getDataEntryIDByClassMetaClassIDAndObjectID,
063                            value="SELECT UNIQUE this.dataEntryID WHERE this.keyStoreRefID == :keyStoreRefID && this.classMeta_classID == :classMeta_classID && this.objectID == :objectID"
064            ),
065            @Query(
066                            name=DataEntry.NamedQueries.getDataEntryIDsByClassMetaClassIDAndObjectIDNegated,
067                            value="SELECT this.dataEntryID WHERE this.keyStoreRefID == :keyStoreRefID && this.classMeta_classID == :classMeta_classID && this.objectID != :notThisObjectID"
068            )
069    })
070    public class DataEntry
071    implements StoreCallback
072    {
073            protected static class NamedQueries {
074                    public static final String getDataEntryByClassMetaClassIDAndObjectID = "getDataEntryByClassMetaClassIDAndObjectID";
075                    public static final String getDataEntryIDByClassMetaClassIDAndObjectID = "getDataEntryIDByClassMetaClassIDAndObjectID";
076                    public static final String getDataEntryIDsByClassMetaClassIDAndObjectIDNegated = "getDataEntryIDsByClassMetaClassIDAndObjectIDNegated";
077            }
078    
079            @PrimaryKey
080            @Persistent(valueStrategy=IdGeneratorStrategy.NATIVE, sequence="DataEntrySequence")
081            private Long dataEntryID;
082    
083            @Persistent(nullValue=NullValue.EXCEPTION)
084            @Column(defaultValue="0")
085            private int keyStoreRefID;
086    
087    //      @Persistent(nullValue=NullValue.EXCEPTION)
088            @NotPersistent
089            private ClassMeta classMeta;
090    
091            @Column(name="classMeta_classID_oid") // for downward-compatibility
092            private long classMeta_classID;
093    
094            @Persistent(nullValue=NullValue.EXCEPTION)
095            @Column(length=255)
096            private String objectID;
097    
098            private long keyID = -1;
099    
100            private byte[] value;
101    
102            /**
103             * Internal constructor. This exists only for JDO and should not be used by application code!
104             */
105            protected DataEntry() { }
106    
107            /**
108             * Create an instance of <code>DataEntry</code>.
109             * @param classMeta the type of the entity persisted in this container (which must be the entity's concrete type -
110             * <b>not</b> the root-type of the inheritance tree!). See {@link #getClassMeta()} for further details.
111             * @param keyStoreRefID TODO
112             * @param objectID the <code>String</code>-representation of the entity's identifier (aka OID or object-ID).
113             * See {@link #getObjectID()} for further details.
114             */
115            public DataEntry(ClassMeta classMeta, int keyStoreRefID, String objectID) {
116                    this.classMeta = classMeta;
117                    this.classMeta_classID = classMeta.getClassID();
118                    this.keyStoreRefID = keyStoreRefID;
119                    this.objectID = objectID;
120    
121                    if (this.classMeta_classID < 0)
122                            throw new IllegalStateException("classMeta not persisted yet: " + classMeta);
123            }
124    
125            /**
126             * Get the single primary key field (= object-identifier) of <code>DataEntry</code>.
127             * @return the object-identifier (= primary key).
128             */
129            public long getDataEntryID() {
130                    return dataEntryID == null ? -1 : dataEntryID;
131            }
132    
133            /**
134             * <p>
135             * Get the type of the entity persisted in this container.
136             * </p>
137             * <p>
138             * Note, that this is the concrete type of the persisted object and <b>not</b> the root-type of the
139             * persistable hierarchy. For example, if <code>bbb</code> is persisted and <code>bbb</code> is an instance of
140             * class <code>BBB</code> which extends <code>AAA</code>
141             * and both classes are persistable, this will point to class <code>BBB</code> (and <b>not</b> <code>AAA</code>).
142             * </p>
143             * <p>
144             * Therefore, if you want to query all instances of a certain type including subclasses, you have to
145             * ask for the sub-classes via {@link org.datanucleus.store.StoreManager#getSubClassesForClass(String, boolean, org.datanucleus.ClassLoaderResolver)}
146             * first and then query for all these classes individually.
147             * </p>
148             * @return the type of the entity.
149             */
150            public ClassMeta getClassMeta() {
151                    if (classMeta == null) {
152                            if (classMeta_classID < 0)
153                                    return null;
154    
155                            classMeta = new ClassMetaDAO(getPersistenceManager()).getClassMeta(classMeta_classID, true);
156                    }
157                    return classMeta;
158            }
159    
160            protected PersistenceManager getPersistenceManager() {
161                    PersistenceManager pm = JDOHelper.getPersistenceManager(this);
162                    if (pm == null) {
163                            throw new IllegalStateException("JDOHelper.getPersistenceManager(this) returned null! " + this);
164                    }
165                    return pm;
166            }
167    
168            /**
169             * Get the numeric identifier of the key store. The key store's String-ID is mapped to this numeric ID
170             * via {@link KeyStoreRef} instances.
171             * @return the numeric identifier of the key store.
172             */
173            public int getKeyStoreRefID() {
174                    return keyStoreRefID;
175            }
176    
177            /**
178             * Set the numeric identifier of the key store.
179             * @param keyStoreRefID the numeric identifier of the key store.
180             * @deprecated Never call this method! It exists only for downward compatibility
181             * (needs to be accessible by {@link IntroduceKeyStoreRefID}).
182             */
183            @Deprecated
184            public void setKeyStoreRefID(int keyStoreRefID) {
185                    this.keyStoreRefID = keyStoreRefID;
186            }
187    
188            /**
189             * <p>
190             * Get the <code>String</code>-representation of the entity's identifier.
191             * </p>
192             * <p>
193             * For JDO, please read the following (and related) documentation:
194             * </p>
195             * <ul>
196             *      <li><a target="_blank" href="http://www.datanucleus.org/products/accessplatform_3_0/jdo/application_identity.html">JDO Mapping / Identity / Application Identity</a></li>
197             *      <li><a target="_blank" href="http://www.datanucleus.org/products/accessplatform_3_0/jdo/datastore_identity.html">JDO Mapping / Identity / Datastore Identity</a></li>
198             * </ul>
199             * <p>
200             * For JPA, please read the following (and related) documentation:
201             * </p>
202             * <ul>
203             *      <li><a target="_blank" href="http://www.datanucleus.org/products/accessplatform_3_0/jpa/application_identity.html">JPA Mapping / Identity / Application Identity</a></li>
204             *      <li><a target="_blank" href="http://www.datanucleus.org/products/accessplatform_3_0/jpa/datastore_identity.html">JPA Mapping / Identity / Datastore Identity</a></li>
205             * </ul>
206             *
207             * @return the OID in String-form
208             * (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>
209             * when using JDO).
210             */
211            public String getObjectID() {
212                    return objectID;
213            }
214    
215            /**
216             * Get the identifier of the encryption-key used to encrypt the {@link #getValue() value}.
217             * @return the encryption-key used to encrypt this <code>DataEntry</code>'s contents.
218             * @see #setKeyID(long)
219             */
220            public long getKeyID() {
221                    return keyID;
222            }
223    
224            /**
225             * Set the identifier of the encryption-key used to encrypt the {@link #getValue() value}.
226             * @param keyID the encryption-key used to encrypt this <code>DataEntry</code>'s contents.
227             * @see #getKeyID()
228             */
229            public void setKeyID(long keyID)
230            {
231                    if (keyID < 0)
232                            throw new IllegalArgumentException("keyID < 0");
233    
234                    this.keyID = keyID;
235            }
236    
237            /**
238             * Get the <b>encrypted</b> data of an entity. The entity is transformed ("made flat") into an {@link ObjectContainer}
239             * which is then serialised using Java native serialisation and finally encrypted.
240             * @return the <b>encrypted</b> serialised data of an {@link ObjectContainer} holding the entity's data.
241             * @see #setValue(byte[])
242             */
243            public byte[] getValue() {
244                    return value;
245            }
246    
247            /**
248             * Set the <b>encrypted</b> data of an entity.
249             * @param value the <b>encrypted</b> serialised data of an {@link ObjectContainer} holding the entity's data.
250             * @see #getValue()
251             */
252            public void setValue(byte[] value) {
253                    this.value = value;
254            }
255    
256            @Override
257            public int hashCode() {
258                    final long dataEntryID = getDataEntryID();
259                    return (int) (dataEntryID ^ (dataEntryID >>> 32));
260            }
261    
262            @Override
263            public boolean equals(Object obj) {
264                    if (this == obj) return true;
265                    if (obj == null) return false;
266                    if (getClass() != obj.getClass()) return false;
267                    DataEntry other = (DataEntry) obj;
268                    return this.getDataEntryID() < 0 ? false : this.getDataEntryID() == other.getDataEntryID();
269            }
270    
271            @Override
272            public void jdoPreStore()
273            {
274    //              // We replace 'this.classMeta' by a persistent version, because it is a detached object
275    //              // which slows down the storing process immensely, as it is unnecessarily attached.
276    //              // Attaching an object is an expensive operation and we neither want nor need to
277    //              // update the ClassMeta object when persisting a new DataEntry.
278    //              PersistenceManager pm = JDOHelper.getPersistenceManager(this);
279    //              Object classMetaID = JDOHelper.getObjectId(classMeta);
280    //              if (classMetaID == null)
281    //                      throw new IllegalStateException("JDOHelper.getObjectId(classMeta) returned null! this=" + this + " classMeta=" + classMeta);
282    //
283    //              classMeta = (ClassMeta) pm.getObjectById(classMetaID);
284            }
285    }