View Javadoc

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