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.crypto.keymanager.messagebroker.pmf; 019 020 import java.util.Collection; 021 import java.util.Date; 022 import java.util.Iterator; 023 024 import javax.jdo.JDOObjectNotFoundException; 025 import javax.jdo.PersistenceManager; 026 import javax.jdo.annotations.FetchGroup; 027 import javax.jdo.annotations.FetchGroups; 028 import javax.jdo.annotations.IdentityType; 029 import javax.jdo.annotations.Index; 030 import javax.jdo.annotations.Indices; 031 import javax.jdo.annotations.NullValue; 032 import javax.jdo.annotations.PersistenceCapable; 033 import javax.jdo.annotations.Persistent; 034 import javax.jdo.annotations.PrimaryKey; 035 import javax.jdo.annotations.Queries; 036 import javax.jdo.annotations.Query; 037 import javax.jdo.annotations.Version; 038 import javax.jdo.annotations.VersionStrategy; 039 import javax.jdo.identity.StringIdentity; 040 041 import org.cumulus4j.keymanager.back.shared.Request; 042 import org.cumulus4j.keymanager.back.shared.Response; 043 044 /** 045 * Persistent container holding a {@link Request} and optionally 046 * the corresponding {@link Response}. Used by {@link MessageBrokerPMF} 047 * to transmit messages via a backing-database. 048 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de 049 */ 050 @PersistenceCapable(identityType=IdentityType.APPLICATION) 051 @Indices({ 052 @Index(members={"cryptoSessionIDPrefix", "status"}), 053 @Index(members={"cryptoSessionIDPrefix", "status", "lastStatusChangeTimestamp"}), 054 @Index(members={"lastStatusChangeTimestamp"}) 055 }) 056 @Version(strategy=VersionStrategy.VERSION_NUMBER) 057 @FetchGroups({ 058 @FetchGroup(name=PendingRequest.FetchGroup.request, members=@Persistent(name="request")), 059 @FetchGroup(name=PendingRequest.FetchGroup.response, members=@Persistent(name="response")) 060 }) 061 @Queries({ 062 @Query( 063 name="getOldestPendingRequestWithStatus", 064 value="SELECT WHERE this.cryptoSessionIDPrefix == :cryptoSessionIDPrefix && this.status == :status ORDER BY this.lastStatusChangeTimestamp ASCENDING RANGE 0, 1" 065 ), 066 @Query( 067 name="getPendingRequestsWithLastStatusChangeTimestampOlderThanTimestamp", 068 value="SELECT WHERE this.lastStatusChangeTimestamp < :timestamp" 069 ) 070 }) 071 public class PendingRequest 072 { 073 /** 074 * <a target="_blank" href="http://www.datanucleus.org/products/accessplatform_3_0/jdo/fetchgroup.html">Fetch-groups</a> for 075 * {@link PendingRequest}. 076 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de 077 */ 078 public static final class FetchGroup { 079 /** 080 * Indicates fetching the {@link PendingRequest#getRequest() request} property of <code>PendingRequest</code>. 081 */ 082 public static final String request = "PendingRequest.request"; 083 /** 084 * Indicates fetching the {@link PendingRequest#getResponse() response} property of <code>PendingRequest</code>. 085 */ 086 public static final String response = "PendingRequest.response"; 087 } 088 089 public static Collection<PendingRequest> getPendingRequestsWithLastStatusChangeTimestampOlderThanTimestamp(PersistenceManager pm, Date timestamp) 090 { 091 if (pm == null) 092 throw new IllegalArgumentException("pm == null"); 093 094 if (timestamp == null) 095 throw new IllegalArgumentException("timestamp == null"); 096 097 javax.jdo.Query q = pm.newNamedQuery(PendingRequest.class, "getPendingRequestsWithLastStatusChangeTimestampOlderThanTimestamp"); 098 @SuppressWarnings("unchecked") 099 Collection<PendingRequest> c = (Collection<PendingRequest>) q.execute(timestamp); 100 // We return this directly and don't copy it (and thus do not close the query), because we delete all of them anyway 101 // and the tx is very short (no need to close the result-set, before tx-end). This way, the JDO impl has the chance 102 // to optimize (i.e. not to load anything from the DB, but really *directly* delete it). 103 return c; 104 } 105 106 /** 107 * Get the oldest <code>PendingRequest</code> matching the given criteria. 108 * @param pm the {@link PersistenceManager} for accessing the message-transfer-database. Must not be <code>null</code>. 109 * @param cryptoSessionIDPrefix the {@link #getCryptoSessionIDPrefix() cryptoSessionIDPrefix} used 110 * as criterion to filter the candidate-<code>PendingRequest</code>s. Must not be <code>null</code>. 111 * @param status the {@link #getStatus() status} used as criterion to filter the candidate-<code>PendingRequest</code>s. 112 * Must not be <code>null</code>. 113 * @return the oldest <code>PendingRequest</code> matching the given criteria or <code>null</code> if there is 114 * no <code>PendingRequest</code> in the datastore which matches the criteria. 115 */ 116 public static PendingRequest getOldestPendingRequest(PersistenceManager pm, String cryptoSessionIDPrefix, PendingRequestStatus status) 117 { 118 if (pm == null) 119 throw new IllegalArgumentException("pm == null"); 120 121 if (cryptoSessionIDPrefix == null) 122 throw new IllegalArgumentException("cryptoSessionIDPrefix == null"); 123 124 if (status == null) 125 throw new IllegalArgumentException("status == null"); 126 127 javax.jdo.Query q = pm.newNamedQuery(PendingRequest.class, "getOldestPendingRequestWithStatus"); 128 try { 129 @SuppressWarnings("unchecked") 130 Collection<PendingRequest> c = (Collection<PendingRequest>) q.execute(cryptoSessionIDPrefix, status); 131 Iterator<PendingRequest> it = c.iterator(); 132 if (it.hasNext()) 133 return it.next(); 134 else 135 return null; 136 } finally { 137 q.closeAll(); 138 } 139 } 140 141 /** 142 * Get the <code>PendingRequest</code> uniquely identified by the given <code>requestID</code>. 143 * If no such <code>PendingRequest</code> exists, return <code>null</code>. 144 * @param pm the {@link PersistenceManager} for accessing the message-transfer-database. Must not be <code>null</code>. 145 * @param requestID the unique identifier of the {@link PendingRequest} to obtain. Must not be <code>null</code>. 146 * @return the {@link PendingRequest} identified by the given <code>requestID</code> or <code>null</code>, if 147 * no such object exists in the datastore. 148 */ 149 public static PendingRequest getPendingRequest(PersistenceManager pm, String requestID) 150 { 151 if (pm == null) 152 throw new IllegalArgumentException("pm == null"); 153 154 if (requestID == null) 155 throw new IllegalArgumentException("requestID == null"); 156 157 StringIdentity identity = new StringIdentity(PendingRequest.class, requestID); 158 try { 159 return (PendingRequest) pm.getObjectById(identity); 160 } catch (JDOObjectNotFoundException x) { 161 return null; 162 } 163 } 164 165 @PrimaryKey 166 @Persistent(nullValue=NullValue.EXCEPTION) 167 private String requestID; 168 169 @Persistent(nullValue=NullValue.EXCEPTION) 170 private String cryptoSessionIDPrefix; 171 172 @Persistent(nullValue=NullValue.EXCEPTION) 173 private PendingRequestStatus status; 174 175 @Persistent(serialized="true", nullValue=NullValue.EXCEPTION) 176 private Request request; 177 178 @Persistent(serialized="true") 179 private Response response; 180 181 @Persistent(nullValue=NullValue.EXCEPTION) 182 private Date creationTimestamp; 183 184 @Persistent(nullValue=NullValue.EXCEPTION) 185 private Date lastStatusChangeTimestamp; 186 187 /** 188 * Internal constructor only used by JDO. Never call this constructor directly! 189 */ 190 protected PendingRequest() { } 191 192 /** 193 * Create an instance of <code>PendingRequest</code> for the given <code>request</code>. 194 * @param request the request to be processed and thus temporarily stored in the database; 195 * must not be <code>null</code>. 196 */ 197 public PendingRequest(Request request) 198 { 199 this.requestID = request.getRequestID(); 200 this.cryptoSessionIDPrefix = request.getCryptoSessionIDPrefix(); 201 this.request = request; 202 this.status = PendingRequestStatus.waitingForProcessing; 203 this.creationTimestamp = new Date(); 204 this.lastStatusChangeTimestamp = new Date(); 205 } 206 207 /** 208 * Get the {@link Request#getRequestID() requestID} of the <code>Request</code> that was passed to 209 * {@link #PendingRequest(Request)}. This property is the primary key of this class. 210 * @return the request's {@link Request#getRequestID() requestID}. 211 */ 212 public String getRequestID() { 213 return requestID; 214 } 215 216 /** 217 * Get the {@link Request#getCryptoSessionIDPrefix() cryptoSessionIDPrefix} of the <code>Request</code> that was passed to 218 * {@link #PendingRequest(Request)}. 219 * @return the request's {@link Request#getCryptoSessionIDPrefix() cryptoSessionIDPrefix}. 220 */ 221 public String getCryptoSessionIDPrefix() { 222 return cryptoSessionIDPrefix; 223 } 224 225 /** 226 * Get the current status. 227 * @return the current status. Can be <code>null</code>, if the instance was not yet 228 * persisted. 229 * @see #setStatus(PendingRequestStatus) 230 */ 231 public PendingRequestStatus getStatus() { 232 return status; 233 } 234 /** 235 * Set the current status. This method will automatically update the 236 * {@link #getLastStatusChangeTimestamp() lastStatusChangeTimestamp}. 237 * @param status the new status; must not be <code>null</code> (because of {@link NullValue#EXCEPTION}). 238 * @see #getStatus() 239 */ 240 public void setStatus(PendingRequestStatus status) { 241 this.status = status; 242 this.lastStatusChangeTimestamp = new Date(); 243 } 244 245 /** 246 * Get the {@link Request} that was passed to {@link #PendingRequest(Request)}. 247 * @return the request; never <code>null</code>. 248 */ 249 public Request getRequest() { 250 return request; 251 } 252 253 /** 254 * Get the {@link Response} previously {@link #setResponse(Response) set} or <code>null</code>, if none is set, yet. 255 * @return the response or <code>null</code>. 256 */ 257 public Response getResponse() { 258 return response; 259 } 260 261 /** 262 * Set the {@link Response}. 263 * @param response the response. 264 */ 265 public void setResponse(Response response) { 266 this.response = response; 267 } 268 269 /** 270 * Get the timestamp when this <code>PendingRequest</code> was instantiated. 271 * @return when was this <code>PendingRequest</code> created. 272 */ 273 public Date getCreationTimestamp() { 274 return creationTimestamp; 275 } 276 277 /** 278 * Get the timestamp when the {@link #getStatus() status} was changed 279 * the last time. 280 * @return the timestamp of the last {@link #getStatus() status}-change. 281 282 */ 283 public Date getLastStatusChangeTimestamp() { 284 return lastStatusChangeTimestamp; 285 } 286 287 @Override 288 public int hashCode() { 289 return (requestID == null) ? 0 : requestID.hashCode(); 290 } 291 292 @Override 293 public boolean equals(Object obj) { 294 if (this == obj) return true; 295 if (obj == null) return false; 296 if (getClass() != obj.getClass()) return false; 297 PendingRequest other = (PendingRequest) obj; 298 return ( 299 this.requestID == other.requestID || 300 (this.requestID != null && this.requestID.equals(other.requestID)) 301 ); 302 } 303 }