View Javadoc

1   /*
2    * Copyright 2008 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.saml2.metadata.provider;
18  
19  import java.util.Timer;
20  import java.util.TimerTask;
21  
22  import org.joda.time.DateTime;
23  import org.opensaml.saml2.common.SAML2Helper;
24  import org.opensaml.util.resource.Resource;
25  import org.opensaml.util.resource.ResourceException;
26  import org.opensaml.xml.XMLObject;
27  import org.opensaml.xml.io.UnmarshallingException;
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  
31  /**
32   * A metadata provider that reads metadata from a {#link {@link Resource}.
33   * 
34   * @since 2.2
35   */
36  public class ResourceBackedMetadataProvider extends AbstractObservableMetadataProvider {
37  
38      /** Class logger. */
39      private final Logger log = LoggerFactory.getLogger(ResourceBackedMetadataProvider.class);
40  
41      /** Timer used to execute metadata polling tasks. */
42      private Timer taskTimer;
43  
44      /** Maximum amount of time metadata will be cached, in milliseconds. */
45      private long maxCacheDuration;
46  
47      /** Resource from which metadata is read. */
48      private Resource metadataResource;
49  
50      /** The last time the metadata was updated as reported by the resource. */
51      private DateTime lastMetadataUpdate;
52  
53      /** Whether cached metadata should be discarded if it expires and can't be refreshed. */
54      private boolean maintainExpiredMetadata;
55  
56      /** Cached metadata. */
57      private XMLObject cachedMetadata;
58  
59      /**
60       * Constructor.
61       * 
62       * @param resource resource from which to read the metadata file.
63       * @param timer task timer used to schedule metadata refresh tasks
64       * @param maxMetadataCacheDuration maximum amount of time, in milliseconds, that metadata may be cached before being
65       *            re-read
66       * 
67       * @throws MetadataProviderException thrown if there is a problem retrieving information about the resource
68       */
69      public ResourceBackedMetadataProvider(Resource resource, Timer timer, long maxMetadataCacheDuration)
70              throws MetadataProviderException {
71          super();
72  
73          taskTimer = timer;
74  
75          if (maxMetadataCacheDuration < 1) {
76              throw new IllegalArgumentException("Max cache duration must be a positive number");
77          }
78          maxCacheDuration = maxMetadataCacheDuration;
79  
80          try {
81              if (!resource.exists()) {
82                  throw new MetadataProviderException("Resource " + resource.getLocation() + " does not exist.");
83              }
84              metadataResource = resource;
85              maintainExpiredMetadata = false;
86          } catch (ResourceException e) {
87              throw new MetadataProviderException("Unable to read resource", e);
88          }
89      }
90  
91      /**
92       * Initializes the provider and prepares it for use.
93       * 
94       * @throws MetadataProviderException thrown if there is a problem reading, parsing, or validating the metadata
95       */
96      public void initialize() throws MetadataProviderException {
97          refreshMetadata();
98      }
99  
100     /**
101      * Gets whether cached metadata should be discarded if it expires and can not be refreshed.
102      * 
103      * @return whether cached metadata should be discarded if it expires and can not be refreshed
104      */
105     public boolean maintainExpiredMetadata() {
106         return maintainExpiredMetadata;
107     }
108 
109     /**
110      * Sets whether cached metadata should be discarded if it expires and can not be refreshed.
111      * 
112      * @param maintain whether cached metadata should be discarded if it expires and can not be refreshed
113      */
114     public void setMaintainExpiredMetadata(boolean maintain) {
115         maintainExpiredMetadata = maintain;
116     }
117 
118     /** {@inheritDoc} */
119     public XMLObject getMetadata() throws MetadataProviderException {
120         return cachedMetadata;
121     }
122 
123     /**
124      * Retrieves, unmarshalls, and filters the metadata from the metadata resource.
125      * 
126      * @throws MetadataProviderException thrown if there is a problem reading, parsing, or validating the metadata
127      */
128     private void refreshMetadata() throws MetadataProviderException {
129         try {
130             boolean metadataChanged = false;
131             
132             XMLObject metadata = getLatestMetadata();
133             if(metadata != cachedMetadata){
134                 metadataChanged = true;
135             }
136 
137             // If the metadata has expired, and we're not configured to use expired metadata, discard it else use it
138             DateTime expirationTime = SAML2Helper.getEarliestExpiration(metadata);
139             if (expirationTime != null && !maintainExpiredMetadata() && expirationTime.isBeforeNow()) {
140                 log.debug("Metadata from resource {} is expired and this provider is configured not to retain expired metadata.",
141                                 metadataResource.getInputStream());
142                 cachedMetadata = null;
143                 metadataChanged = true;
144             } else {
145                 cachedMetadata = metadata;
146             }
147 
148             // Let everyone know metadata has changed
149             if (metadataChanged) {
150                 emitChangeEvent();
151             }
152 
153             // Determine the next time to check for a metadata change and schedule it
154             long nextUpdateDelay=0;
155             if (expirationTime != null && expirationTime.isBefore(System.currentTimeMillis() + maxCacheDuration)) {
156                 nextUpdateDelay = expirationTime.getMillis() - System.currentTimeMillis();
157             }
158             
159             if(nextUpdateDelay <= 0){
160                 nextUpdateDelay = maxCacheDuration;
161             }
162             
163             log.debug("Next refresh of metadata from resource {} scheduled in {}ms", metadataResource.getLocation(),
164                     nextUpdateDelay);
165             taskTimer.schedule(new MetadataPollTask(), nextUpdateDelay);
166         } catch (ResourceException e) {
167             String errorMsg = "Unable to read metadata file";
168             log.error(errorMsg, e);
169             throw new MetadataProviderException(errorMsg, e);
170         }
171     }
172 
173     /**
174      * Gets the latest metadata from the resource if the resource reports a modification time after the last update,
175      * otherwise uses the currently cached metadata.
176      * 
177      * @return latest metadata
178      * 
179      * @throws MetadataProviderException thrown if the metadata can not be loaded from the resource, unmarshalled, and
180      *             filtered
181      */
182     private XMLObject getLatestMetadata() throws MetadataProviderException {
183         XMLObject metadata;
184 
185         try {
186             DateTime metadataUpdateTime = metadataResource.getLastModifiedTime();
187             if (lastMetadataUpdate == null || metadataUpdateTime.isAfter(lastMetadataUpdate)) {
188                 lastMetadataUpdate = metadataUpdateTime;
189                 log.debug("Refreshing metadata from resource {}", metadataResource.getLocation());
190                 metadata = unmarshallMetadata(metadataResource.getInputStream());
191                 filterMetadata(metadata);
192                 releaseMetadataDOM(metadata);
193             } else {
194                 metadata = cachedMetadata;
195             }
196 
197             return metadata;
198         } catch (ResourceException e) {
199             String errorMsg = "Unable to read metadata file";
200             log.error(errorMsg, e);
201             throw new MetadataProviderException(errorMsg, e);
202         } catch (UnmarshallingException e) {
203             String errorMsg = "Unable to unmarshall metadata";
204             log.error(errorMsg, e);
205             throw new MetadataProviderException(errorMsg, e);
206         } catch (FilterException e) {
207             String errorMsg = "Unable to filter metadata";
208             log.error(errorMsg, e);
209             throw new MetadataProviderException(errorMsg, e);
210         }
211     }
212 
213     /** Timer task that periodically refreshes metadata. */
214     private class MetadataPollTask extends TimerTask {
215 
216         /** {@inheritDoc} */
217         public void run() {
218             try {
219                 log.debug("Checking if metadata from resource {} should be updated", metadataResource.getLocation());
220                 refreshMetadata();
221             } catch (MetadataProviderException e) {
222                 log.error("Unable to refresh metadata", e);
223             }
224         }
225     }
226 }