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 }