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     * &#64;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     *      &#64;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    }