View Javadoc

1   /*
2    * Copyright [2007] [University Corporation for Advanced Internet Development, Inc.]
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.opensaml.util.storage;
18  
19  import java.io.Serializable;
20  import java.util.concurrent.locks.ReentrantLock;
21  
22  import org.joda.time.DateTime;
23  import org.opensaml.xml.util.DatatypeHelper;
24  import org.slf4j.Logger;
25  import org.slf4j.LoggerFactory;
26  
27  /**
28   * Class that uses an underlying {@link StorageService} to track information associated with messages in order to detect
29   * message replays.
30   * 
31   * This class is thread-safe and uses a basic reentrant lock to avoid corruption of the underlying store, as well as to
32   * prevent race conditions with respect to replay checking.
33   */
34  public class ReplayCache {
35  
36      /** Logger. */
37      private final Logger log = LoggerFactory.getLogger(ReplayCache.class);
38  
39      /** Backing storage for the replay cache. */
40      private StorageService<String, ReplayCacheEntry> storage;
41  
42      /** Storage service partition used by this cache. default: replay */
43      private String partition;
44  
45      /** Time, in milliseconds, that message state is valid. */
46      private long entryDuration;
47  
48      /** Replay cache lock. */
49      private ReentrantLock cacheLock;
50  
51      /**
52       * Constructor.
53       * 
54       * @param storageService the StorageService which serves as the backing store for the cache
55       * @param duration default length of time that message state is valid
56       */
57      public ReplayCache(StorageService<String, ReplayCacheEntry> storageService, long duration) {
58          storage = storageService;
59          entryDuration = duration;
60          partition = "replay";
61          cacheLock = new ReentrantLock(true);
62      }
63  
64      /**
65       * Constructor.
66       * 
67       * @param storageService the StorageService which serves as the backing store for the cache
68       * @param storageParition name of storage service partition to use
69       * @param duration default length of time that message state is valid
70       */
71      public ReplayCache(StorageService<String, ReplayCacheEntry> storageService, String storageParition, long duration) {
72          storage = storageService;
73          entryDuration = duration;
74          if (!DatatypeHelper.isEmpty(storageParition)) {
75              partition = DatatypeHelper.safeTrim(storageParition);
76          } else {
77              partition = "replay";
78          }
79          cacheLock = new ReentrantLock(true);
80      }
81  
82      /**
83       * Checks if the message has been replayed. If the message has not been seen before then it is added to the list of
84       * seen of messages for the default duration.
85       * 
86       * @param issuerId unique ID of the message issuer
87       * @param messageId unique ID of the message
88       * 
89       * @return true if the given message ID has been seen before
90       */
91      public boolean isReplay(String issuerId, String messageId) {
92          log.debug("Attempting to acquire lock for replay cache check");
93          cacheLock.lock();
94          log.debug("Lock acquired");
95  
96          try {
97              boolean replayed = true;
98              String entryHash = issuerId + messageId;
99  
100             ReplayCacheEntry cacheEntry = storage.get(partition, entryHash);
101 
102             if (cacheEntry == null || cacheEntry.isExpired()) {
103                 if (log.isDebugEnabled()) {
104                     if (cacheEntry == null) {
105                         log.debug("Message ID {} was not a replay", messageId);
106                     } else if (cacheEntry.isExpired()) {
107                         log.debug("Message ID {} expired in replay cache at {}", messageId, cacheEntry
108                                 .getExpirationTime().toString());
109                         storage.remove(partition, entryHash);
110                     }
111                 }
112                 replayed = false;
113                 addMessageID(entryHash, new DateTime().plus(entryDuration));
114             } else {
115                 log.debug("Replay of message ID {} detected in replay cache, will expire at {}", messageId, cacheEntry
116                         .getExpirationTime().toString());
117             }
118 
119             return replayed;
120         } finally {
121             cacheLock.unlock();
122         }
123     }
124 
125     /**
126      * Accquires a write lock and adds the message state to the underlying storage service.
127      * 
128      * @param messageId unique ID of the message
129      * @param expiration time the message state expires
130      */
131     protected void addMessageID(String messageId, DateTime expiration) {
132         log.debug("Writing message ID {} to replay cache with expiration time {}", messageId, expiration.toString());
133         storage.put(partition, messageId, new ReplayCacheEntry(expiration));
134     }
135 
136     /** Replay cache storage service entry. */
137     public class ReplayCacheEntry implements ExpiringObject, Serializable {
138 
139         /** Serial version UID. */
140         private static final long serialVersionUID = 2398693920546938083L;
141 
142         /** Time when this entry expires. */
143         private DateTime expirationTime;
144 
145         /**
146          * Constructor.
147          * 
148          * @param expiration time when this entry expires
149          */
150         public ReplayCacheEntry(DateTime expiration) {
151             expirationTime = expiration;
152         }
153 
154         /** {@inheritDoc} */
155         public DateTime getExpirationTime() {
156             return expirationTime;
157         }
158 
159         /** {@inheritDoc} */
160         public boolean isExpired() {
161             return expirationTime.isBeforeNow();
162         }
163 
164         /** {@inheritDoc} */
165         public void onExpire() {
166 
167         }
168     }
169 }