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.util.concurrent.locks.ReentrantLock;
20  
21  import org.joda.time.DateTime;
22  import org.opensaml.xml.util.DatatypeHelper;
23  import org.slf4j.Logger;
24  import org.slf4j.LoggerFactory;
25  
26  /**
27   * Class that uses an underlying {@link StorageService} to track information associated with messages in order to detect
28   * message replays.
29   * 
30   * This class is thread-safe and uses a basic reentrant lock to avoid corruption of the underlying store, as well as to
31   * prevent race conditions with respect to replay checking.
32   */
33  public class ReplayCache {
34  
35      /** Logger. */
36      private final Logger log = LoggerFactory.getLogger(ReplayCache.class);
37  
38      /** Backing storage for the replay cache. */
39      private StorageService<String, ReplayCacheEntry> storage;
40  
41      /** Storage service partition used by this cache. default: replay */
42      private String partition;
43  
44      /** Time, in milliseconds, that message state is valid. */
45      private long entryDuration;
46  
47      /** Replay cache lock. */
48      private ReentrantLock cacheLock;
49  
50      /**
51       * Constructor.
52       * 
53       * @param storageService the StorageService which serves as the backing store for the cache
54       * @param duration default length of time that message state is valid
55       */
56      public ReplayCache(StorageService<String, ReplayCacheEntry> storageService, long duration) {
57          storage = storageService;
58          entryDuration = duration;
59          partition = "replay";
60          cacheLock = new ReentrantLock(true);
61      }
62  
63      /**
64       * Constructor.
65       * 
66       * @param storageService the StorageService which serves as the backing store for the cache
67       * @param storageParition name of storage service partition to use
68       * @param duration default length of time that message state is valid
69       */
70      public ReplayCache(StorageService<String, ReplayCacheEntry> storageService, String storageParition, long duration) {
71          storage = storageService;
72          entryDuration = duration;
73          if (!DatatypeHelper.isEmpty(storageParition)) {
74              partition = DatatypeHelper.safeTrim(storageParition);
75          } else {
76              partition = "replay";
77          }
78          cacheLock = new ReentrantLock(true);
79      }
80  
81      /**
82       * Checks if the message has been replayed. If the message has not been seen before then it is added to the list of
83       * seen of messages for the default duration.
84       * 
85       * @param issuerId unique ID of the message issuer
86       * @param messageId unique ID of the message
87       * 
88       * @return true if the given message ID has been seen before
89       */
90      public boolean isReplay(String issuerId, String messageId) {
91          log.debug("Attempting to acquire lock for replay cache check");
92          cacheLock.lock();
93          log.debug("Lock acquired");
94  
95          try {
96              boolean replayed = true;
97              String entryHash = issuerId + messageId;
98  
99              ReplayCacheEntry cacheEntry = storage.get(partition, entryHash);
100 
101             if (cacheEntry == null || cacheEntry.isExpired()) {
102                 if (log.isDebugEnabled()) {
103                     if (cacheEntry == null) {
104                         log.debug("Message ID {} was not a replay", messageId);
105                     } else if (cacheEntry.isExpired()) {
106                         log.debug("Message ID {} expired in replay cache at {}", messageId, cacheEntry
107                                 .getExpirationTime().toString());
108                         storage.remove(partition, entryHash);
109                     }
110                 }
111                 replayed = false;
112                 addMessageID(entryHash, new DateTime().plus(entryDuration));
113             } else {
114                 log.debug("Replay of message ID {} detected in replay cache, will expire at {}", messageId, cacheEntry
115                         .getExpirationTime().toString());
116             }
117 
118             return replayed;
119         } finally {
120             cacheLock.unlock();
121         }
122     }
123 
124     /**
125      * Accquires a write lock and adds the message state to the underlying storage service.
126      * 
127      * @param messageId unique ID of the message
128      * @param expiration time the message state expires
129      */
130     protected void addMessageID(String messageId, DateTime expiration) {
131         log.debug("Writing message ID {} to replay cache with expiration time {}", messageId, expiration.toString());
132         storage.put(partition, messageId, new ReplayCacheEntry(messageId, expiration));
133     }
134 }