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.IdGeneratorStrategy;
023    import javax.jdo.annotations.IdentityType;
024    import javax.jdo.annotations.Inheritance;
025    import javax.jdo.annotations.InheritanceStrategy;
026    import javax.jdo.annotations.NullValue;
027    import javax.jdo.annotations.PersistenceCapable;
028    import javax.jdo.annotations.Persistent;
029    import javax.jdo.annotations.PrimaryKey;
030    import javax.jdo.annotations.Version;
031    import javax.jdo.annotations.VersionStrategy;
032    import javax.jdo.listener.StoreCallback;
033    
034    /**
035     * <p>
036     * Persistent index information with <b>encrypted</b> pointers to {@link DataEntry}s.
037     * </p>
038     * <p>
039     * Since the index is type-specific, there are sub-classes for each data type. One
040     * {@link IndexEntry} instance is used for each distinct value of one certain field.
041     * Therefore, the field (represented by the property {@link #getFieldMeta() fieldMeta})
042     * and the value together form a unique key of <code>IndexEntry</code> - thus the value
043     * is represented by the property {@link #getIndexKey() indexKey}.
044     * </p>
045     * <p>
046     * Example:
047     * </p>
048     * <pre>
049     * // persistent class:
050     * &#64;PersistenceCapable
051     * class Person
052     * {
053     *      public Person(String firstName, String lastName) {
054     *              this.firstName = firstName;
055     *              this.lastName = lastName;
056     *      }
057     *
058     *      private String firstName;
059     *      private String lastName;
060     *
061     *      // ...
062     * }
063     *
064     * class SomeTest
065     * {
066     *      &#64;Test
067     *      public void persistPersons()
068     *      {
069     *              pm.makePersistent(new Person("Alice", "Müller"));
070     *              pm.makePersistent(new Person("Alice", "Meier"));
071     *      }
072     * }
073     * </pre>
074     * <p>
075     * After running this test, there would be three instances of {@link IndexEntryStringShort} in the database
076     * indexing the values "Alice", "Müller" and "Meier". Note, that "Alice" occurs only once in the index, even though
077     * there are two <code>Person</code> instances using it. The two persons would be referenced from the one index-entry
078     * via {@link #getIndexValue()}.
079     * </p>
080     *
081     *
082     * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
083     */
084    @PersistenceCapable(identityType=IdentityType.APPLICATION, detachable="true")
085    @Inheritance(strategy=InheritanceStrategy.SUBCLASS_TABLE)
086    @Version(strategy=VersionStrategy.VERSION_NUMBER)
087    //@Unique(members={"fieldMeta", "indexKeyDouble", "indexKeyLong", "indexKeyString"})
088    public abstract class IndexEntry
089    implements StoreCallback
090    {
091            @PrimaryKey
092            @Persistent(valueStrategy=IdGeneratorStrategy.NATIVE)
093            private long indexEntryID = -1;
094    
095            @Persistent(nullValue=NullValue.EXCEPTION)
096            private FieldMeta fieldMeta;
097    
098            private long keyID = -1;
099    
100            /** DataEntryIDs for this indexed key. */
101            private byte[] indexValue;
102    
103            /**
104             * Get the single primary key field (= object-identifier) of this <code>IndexEntry</code>.
105             *
106             * @return the object-identifier (= primary key).
107             */
108            public long getIndexEntryID() {
109                    return indexEntryID;
110            }
111    
112            /**
113             * <p>
114             * Get the descriptor of the indexed field.
115             * </p>
116             * <p>
117             * Every <code>IndexEntry</code> instance belongs to one field or a part of the field (e.g. a <code>Map</code>'s key).
118             * </p>
119             * @return the descriptor of the indexed field.
120             */
121            public FieldMeta getFieldMeta() {
122                    return fieldMeta;
123            }
124    
125            protected void setFieldMeta(FieldMeta fieldMeta) {
126                    if (this.fieldMeta != null && !this.fieldMeta.equals(fieldMeta))
127                            throw new IllegalStateException("The property fieldMeta cannot be modified after being set once!");
128    
129                    this.fieldMeta = fieldMeta;
130            }
131    
132            /**
133             * Get the value which is indexed by this instance. It serves as 2nd part of the unique key together
134             * with the property {@link #getFieldMeta() fieldMeta}.
135             * @return the key.
136             */
137            public abstract Object getIndexKey();
138    
139            protected abstract void setIndexKey(Object indexKey);
140    
141            /**
142             * Get the identifier of the encryption-key used to encrypt the {@link #getIndexValue() indexValue}.
143             * @return the encryption-key used to encrypt this <code>IndexEntry</code>'s contents.
144             * @see #setKeyID(long)
145             */
146            public long getKeyID() {
147                    return keyID;
148            }
149    
150            /**
151             * Set the identifier of the encryption-key used to encrypt the {@link #getIndexValue() indexValue}.
152             * @param keyID the encryption-key used to encrypt this <code>IndexEntry</code>'s contents.
153             * @see #getKeyID()
154             */
155            public void setKeyID(long keyID)
156            {
157                    if (keyID < 0)
158                            throw new IllegalArgumentException("keyID < 0");
159    
160                    this.keyID = keyID;
161            }
162    
163            /**
164             * Get the <b>encrypted</b> pointers to {@link DataEntry}. After decrypting
165             * this byte array, you can pass it to {@link IndexValue#IndexValue(byte[])}.
166             *
167             * @return the <b>encrypted</b> pointers to {@link DataEntry}s.
168             */
169            public byte[] getIndexValue() {
170                    return indexValue;
171            }
172    
173            public void setIndexValue(byte[] indexValue) {
174                    this.indexValue = indexValue;
175            }
176    
177            @Override
178            public int hashCode() {
179                    return (int) (indexEntryID ^ (indexEntryID >>> 32));
180            }
181    
182            @Override
183            public boolean equals(Object obj) {
184                    if (this == obj) return true;
185                    if (obj == null) return false;
186                    if (getClass() != obj.getClass()) return false;
187                    IndexEntry other = (IndexEntry) obj;
188                    return this.indexEntryID == other.indexEntryID;
189            }
190    
191            @Override
192            public void jdoPreStore()
193            {
194                    // See: DataEntry#jdoPreStore() - the same applies here to 'this.fieldMeta'.
195                    PersistenceManager pm = JDOHelper.getPersistenceManager(this);
196                    Object fieldMetaID = JDOHelper.getObjectId(fieldMeta);
197                    fieldMeta = (FieldMeta) pm.getObjectById(fieldMetaID);
198            }
199    }