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 }