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 edu.internet2.middleware.shibboleth.common.config;
18  
19  import java.util.Date;
20  import java.util.List;
21  
22  import javax.xml.datatype.Duration;
23  
24  import org.opensaml.util.resource.Resource;
25  import org.opensaml.util.resource.ResourceException;
26  import org.opensaml.xml.util.DatatypeHelper;
27  import org.opensaml.xml.util.XMLHelper;
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  import org.springframework.beans.factory.BeanDefinitionStoreException;
31  import org.springframework.beans.factory.config.BeanDefinition;
32  import org.springframework.beans.factory.config.RuntimeBeanReference;
33  import org.springframework.beans.factory.support.BeanDefinitionRegistry;
34  import org.springframework.beans.factory.support.ManagedList;
35  import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
36  import org.springframework.beans.factory.xml.NamespaceHandler;
37  import org.springframework.beans.factory.xml.ParserContext;
38  import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
39  import org.springframework.core.io.InputStreamResource;
40  import org.w3c.dom.Element;
41  
42  /**
43   * Utilities to help configure Spring beans.
44   */
45  public final class SpringConfigurationUtils {
46  
47      /** Log4j logger. */
48      private static Logger log = LoggerFactory.getLogger(SpringConfigurationUtils.class);
49  
50      /** Private Constructor. */
51      private SpringConfigurationUtils() {
52      }
53  
54      /**
55       * Loads a set of spring configuration resources into a given application context.
56       * 
57       * @param beanRegistry registry of spring beans to be populated with information from the given configurations
58       * @param configurationResources list of spring configuration resources
59       * 
60       * @throws ResourceException thrown if there is a problem reading the spring configuration resources into the
61       *             registry
62       */
63      public static void populateRegistry(BeanDefinitionRegistry beanRegistry, List<Resource> configurationResources)
64              throws ResourceException {
65          XmlBeanDefinitionReader configReader = new XmlBeanDefinitionReader(beanRegistry);
66          configReader.setValidationMode(XmlBeanDefinitionReader.VALIDATION_XSD);
67          configReader.setDocumentLoader(new SpringDocumentLoader());
68  
69          int numOfResources = configurationResources.size();
70          Resource configurationResource;
71          org.springframework.core.io.Resource[] configSources = new org.springframework.core.io.Resource[numOfResources];
72          for (int i = 0; i < numOfResources; i++) {
73              configurationResource = configurationResources.get(i);
74              if (configurationResource != null && configurationResource.exists()) {
75                  configSources[i] = new InputStreamResource(configurationResources.get(i).getInputStream(),
76                          configurationResource.getLocation());
77              } else {
78                  log.warn("Configuration resource not loaded because it does not exist: {}", configurationResource
79                          .getLocation());
80              }
81          }
82  
83          try {
84              configReader.loadBeanDefinitions(configSources);
85          } catch (BeanDefinitionStoreException e) {
86              throw new ResourceException("Unable to load Spring bean registry with configuration resources", e);
87          }
88      }
89  
90      /**
91       * Parses a bean definition using an xsi:type aware version of
92       * {@link BeanDefinitionParserDelegate#parseCustomElement(Element)}.
93       * 
94       * @param element configuration element
95       * @param parserContext current parser context
96       * 
97       * @return bean definition
98       */
99      public static BeanDefinition parseInnerCustomElement(Element element, ParserContext parserContext) {
100         return createBeanDefinition(element, parserContext);
101     }
102 
103     /**
104      * Parser a list of bean definitions using an xsi:type aware version of
105      * {@link BeanDefinitionParserDelegate#parseCustomElement(Element)}.
106      * 
107      * @param elements configuration elements
108      * @param parserContext current parser context
109      * 
110      * @return list of bean definition
111      */
112     public static ManagedList parseInnerCustomElements(List<Element> elements, ParserContext parserContext) {
113         ManagedList beans = new ManagedList();
114         if (elements != null) {
115             for (Element element : elements) {
116                 beans.add(parseInnerCustomElement(element, parserContext));
117             }
118         }
119 
120         return beans;
121     }
122 
123     /**
124      * Parses a bean definition using an xsi:type aware version of
125      * BeanDefinitionParserDelegate.parseCustomElement(Element). Assumes the element has an attribute 'id' that provides
126      * a unique identifier for the bean.
127      * 
128      * @param element element to parse
129      * @param parserContext current parser context
130      * 
131      * @return bean definition reference
132      */
133     public static RuntimeBeanReference parseCustomElement(Element element, ParserContext parserContext) {
134         return parseCustomElement(element, "id", parserContext);
135     }
136 
137     /**
138      * Parses a bean definition using an xsi:type aware version of
139      * BeanDefinitionParserDelegate.parseCustomElement(Element).
140      * 
141      * @param element element to parse
142      * @param idAttribute attribute that carries the unique ID for the bean
143      * @param parserContext current parser context
144      * 
145      * @return bean definition reference
146      */
147     public static RuntimeBeanReference parseCustomElement(Element element, String idAttribute,
148             ParserContext parserContext) {
149         createBeanDefinition(element, parserContext);
150         RuntimeBeanReference beanRef = new RuntimeBeanReference(element.getAttributeNS(null, idAttribute));
151         beanRef.setSource(element);
152         return beanRef;
153     }
154 
155     /**
156      * Creates a {@link BeanDefinition} from a custom element.
157      * 
158      * @param element configuration element
159      * @param parserContext currently parser context
160      * 
161      * @return the bean definition
162      */
163     private static BeanDefinition createBeanDefinition(Element element, ParserContext parserContext) {
164         BeanDefinitionParserDelegate delegate = parserContext.getDelegate();
165         String namespaceUri = element.getNamespaceURI();
166 
167         if (XMLHelper.hasXSIType(element)) {
168             namespaceUri = XMLHelper.getXSIType(element).getNamespaceURI();
169         }
170 
171         NamespaceHandler handler = delegate.getReaderContext().getNamespaceHandlerResolver().resolve(namespaceUri);
172         if (handler == null) {
173             log.error("Unable to locate NamespaceHandler for namespace [" + namespaceUri + "]");
174             return null;
175         }
176         return handler.parse(element, new ParserContext(delegate.getReaderContext(), delegate));
177     }
178 
179     /**
180      * Parses a custom element that is a reference to a bean declared elsewhere.
181      * 
182      * @param element the element that references the bean
183      * @param refAttribute the name of the attribute that contains the referenced bean's name
184      * @param parserContext current parsing context
185      * 
186      * @return reference to the bean or null if the element did not contain the reference attribute
187      */
188     public static RuntimeBeanReference parseCustomElementReference(Element element, String refAttribute,
189             ParserContext parserContext) {
190         String reference = DatatypeHelper.safeTrimOrNullString(element.getAttributeNS(null, refAttribute));
191         if (reference != null) {
192             return new RuntimeBeanReference(reference);
193         }
194 
195         return null;
196     }
197 
198     /**
199      * Parse list of elements into bean definitions. The list is populated with bean references. Each configuration
200      * element is expected to contain an 'id' attribute that provides a unique ID for each bean.
201      * 
202      * @param elements list of elements to parse
203      * @param parserContext current parsing context
204      * 
205      * @return list of bean references
206      */
207     public static ManagedList parseCustomElements(List<Element> elements, ParserContext parserContext) {
208         return parseCustomElements(elements, "id", parserContext);
209     }
210 
211     /**
212      * Parse list of elements into bean definitions.
213      * 
214      * @param elements list of elements to parse
215      * @param idAttribute attribute that carries the unique ID for the bean
216      * @param parserContext current parsing context
217      * 
218      * @return list of bean references
219      */
220     public static ManagedList parseCustomElements(List<Element> elements, String idAttribute,
221             ParserContext parserContext) {
222         if (elements == null) {
223             return null;
224         }
225 
226         ManagedList definitions = new ManagedList(elements.size());
227         for (Element e : elements) {
228             definitions.add(parseCustomElement(e, idAttribute, parserContext));
229         }
230 
231         return definitions;
232     }
233 
234     /**
235      * Converts a duration, either expressed as numerical time or or ISO8601 duration. If a numerical form is used a
236      * warning message indicating that the new IS08601 duration form should be used will be written to the logs.
237      * 
238      * This method will be removed once the deprecated numerical duration form is no longer allowed.
239      * 
240      * @param propertyName Name of the property carrying the duration. This is used in the warning log message if the
241      *            duration is in numerical form.
242      * @param duration the duration to be parsed
243      * @param toMillisFactor used to convert a numerical duration to milliseconds, 0 indicates no conversion
244      * 
245      * @return the duration in milliseconds
246      * 
247      * @throws IllegalArgumentException thrown if the given duration is either an invalid number or ISO8601 duration or
248      *             if the duration is negative
249      */
250     @Deprecated
251     public static long parseDurationToMillis(String propertyName, String duration, int toMillisFactor)
252             throws IllegalArgumentException {
253         if (duration.startsWith("-")) {
254             throw new IllegalArgumentException("Negative durations are not supported");
255         }
256 
257         long millis = 0;
258         if (duration.startsWith("P")) {
259             Duration xmlDuration = XMLHelper.getDataTypeFactory().newDuration(duration);
260             millis = xmlDuration.getTimeInMillis(new Date());
261         } else {
262             try {
263                 millis = Long.parseLong(duration);
264                 if (millis < 0) {
265                     throw new IllegalArgumentException("Negative durations are not supported");
266                 }
267                 if (toMillisFactor > 0) {
268                     millis *= toMillisFactor;
269                 }
270                 Duration xmlDuration = XMLHelper.getDataTypeFactory().newDuration(millis);
271                 log.warn("Numerical duration form is deprecated. The property {} should use the duration notation: {}",
272                         propertyName, xmlDuration.toString());
273             } catch (NumberFormatException e) {
274 
275             }
276         }
277 
278         return millis;
279     }
280 }