View Javadoc

1   /*
2    * Copyright [2006] [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.io.File;
20  import java.io.FileInputStream;
21  import java.io.FileNotFoundException;
22  
23  import org.joda.time.DateTime;
24  import org.opensaml.saml2.common.SAML2Helper;
25  import org.opensaml.xml.XMLObject;
26  import org.opensaml.xml.io.UnmarshallingException;
27  import org.slf4j.Logger;
28  import org.slf4j.LoggerFactory;
29  
30  /**
31   * A metadata provider that pulls metadata from a file on the local filesystem. Metadata is cached and automatically
32   * refreshed when the file changes.
33   * 
34   * It is the responsibility of the caller to re-initialize, via {@link #initialize()}, if any properties of this
35   * provider are changed.
36   */
37  public class FilesystemMetadataProvider extends AbstractObservableMetadataProvider {
38  
39      /** Class logger. */
40      private final Logger log = LoggerFactory.getLogger(FilesystemMetadataProvider.class);
41  
42      /** The metadata file. */
43      private File metadataFile;
44  
45      /** Whether cached metadata should be discarded if it expires and can't be refreshed. */
46      private boolean maintainExpiredMetadata;
47  
48      /** Last time the cached metadata was updated. */
49      private long lastUpdate;
50  
51      /** Cached metadata. */
52      private XMLObject cachedMetadata;
53  
54      /**
55       * Constructor.
56       * 
57       * @param metadata the metadata file
58       * 
59       * @throws MetadataProviderException thrown if the given file path is null, does not exist, does not represent a
60       *             file, or if the metadata can not be parsed
61       */
62      public FilesystemMetadataProvider(File metadata) throws MetadataProviderException {
63          super();
64  
65          if (metadata == null) {
66              throw new MetadataProviderException("Give metadata file may not be null");
67          }
68  
69          if (!metadata.exists()) {
70              throw new MetadataProviderException("Give metadata file, " + metadata.getAbsolutePath() + " does not exist");
71          }
72  
73          if (!metadata.isFile()) {
74              throw new MetadataProviderException("Give metadata file, " + metadata.getAbsolutePath() + " is not a file");
75          }
76  
77          if (!metadata.canRead()) {
78              throw new MetadataProviderException("Give metadata file, " + metadata.getAbsolutePath()
79                      + " is not readable");
80          }
81  
82          metadataFile = metadata;
83          maintainExpiredMetadata = true;
84          lastUpdate = -1;
85      }
86  
87      /**
88       * Initializes the provider and prepares it for use.
89       * 
90       * @throws MetadataProviderException thrown if there is a problem reading, parsing, or validating the metadata
91       */
92      public void initialize() throws MetadataProviderException {
93          refreshMetadata();
94      }
95  
96      /**
97       * Gets whether cached metadata should be discarded if it expires and can not be refreshed.
98       * 
99       * @return whether cached metadata should be discarded if it expires and can not be refreshed
100      */
101     public boolean maintainExpiredMetadata() {
102         return maintainExpiredMetadata;
103     }
104 
105     /**
106      * Sets whether cached metadata should be discarded if it expires and can not be refreshed.
107      * 
108      * @param maintain whether cached metadata should be discarded if it expires and can not be refreshed
109      */
110     public void setMaintainExpiredMetadata(boolean maintain) {
111         maintainExpiredMetadata = maintain;
112     }
113 
114     /** {@inheritDoc} */
115     public XMLObject getMetadata() throws MetadataProviderException {
116         if (lastUpdate < metadataFile.lastModified()) {
117             refreshMetadata();
118         }
119 
120         return cachedMetadata;
121     }
122 
123     /**
124      * Retrieves, unmarshalls, and filters the metadata from the metadata file.
125      * 
126      * @throws MetadataProviderException thrown if there is a problem reading, parsing, or validating the metadata
127      */
128     private synchronized void refreshMetadata() throws MetadataProviderException {
129         // Only read the file last mod time once, store off for later use. See below.
130         long metadataFileLastModified = metadataFile.lastModified();
131         if (lastUpdate >= metadataFileLastModified) {
132             // In case other requests stacked up behind the synchronize lock
133             return;
134         }
135 
136         log.debug("Refreshing metadata from file {}", metadataFile);
137         try {
138             XMLObject metadata = unmarshallMetadata(new FileInputStream(metadataFile));
139             DateTime expirationTime = SAML2Helper.getEarliestExpiration(metadata);
140             if (expirationTime != null && !maintainExpiredMetadata() && expirationTime.isBeforeNow()) {
141                 log.debug(
142                         "Metadata from file {} is expired and provider is configured not to retain expired metadata.",
143                         metadataFile);
144                 cachedMetadata = null;
145             } else {
146                 filterMetadata(metadata);
147                 releaseMetadataDOM(metadata);
148                 cachedMetadata = metadata;
149             }
150 
151             // Note: this doesn't really avoid re-reading the metadata file unnecessarily on later refreshes
152             // (case where file changed between reading/storing the last mod time above and when the contents
153             // were read above).
154             // It does however avoid the greater evil of *missing* a newer file on a subsequent refresh
155             // (case where the file changed after the contents were read above, but before here).
156             // To do this exactly correctly, we need to make use of OS filesystem-level file locking.
157             lastUpdate = metadataFileLastModified;
158 
159             emitChangeEvent();
160         } catch (FileNotFoundException e) {
161             String errorMsg = "Unable to read metadata file";
162             log.error(errorMsg, e);
163             throw new MetadataProviderException(errorMsg, e);
164         } catch (UnmarshallingException e) {
165             String errorMsg = "Unable to unmarshall metadata";
166             log.error(errorMsg, e);
167             throw new MetadataProviderException(errorMsg, e);
168         } catch (FilterException e) {
169             String errorMsg = "Unable to filter metadata";
170             log.error(errorMsg, e);
171             throw new MetadataProviderException(errorMsg, e);
172         }
173     }
174 }