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    }