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.attribute.resolver.dataConnector;
18  
19  import java.beans.PropertyVetoException;
20  import java.util.ArrayList;
21  import java.util.Hashtable;
22  import java.util.List;
23  import java.util.Map;
24  
25  import javax.naming.InitialContext;
26  import javax.naming.NamingException;
27  import javax.sql.DataSource;
28  import javax.xml.namespace.QName;
29  
30  import org.opensaml.xml.util.DatatypeHelper;
31  import org.opensaml.xml.util.XMLHelper;
32  import org.slf4j.Logger;
33  import org.slf4j.LoggerFactory;
34  import org.springframework.beans.factory.BeanCreationException;
35  import org.springframework.beans.factory.support.BeanDefinitionBuilder;
36  import org.springframework.beans.factory.xml.ParserContext;
37  import org.w3c.dom.Element;
38  
39  import com.mchange.v2.c3p0.ComboPooledDataSource;
40  
41  import edu.internet2.middleware.shibboleth.common.attribute.resolver.provider.dataConnector.RDBMSColumnDescriptor;
42  import edu.internet2.middleware.shibboleth.common.attribute.resolver.provider.dataConnector.RDBMSDataConnector.DATA_TYPES;
43  import edu.internet2.middleware.shibboleth.common.config.SpringConfigurationUtils;
44  
45  /** Spring bean definition parser for reading relational database data connector. */
46  public class RDBMSDataConnectorBeanDefinitionParser extends BaseDataConnectorBeanDefinitionParser {
47  
48      /** Schema type name. */
49      public static final QName TYPE_NAME = new QName(DataConnectorNamespaceHandler.NAMESPACE, "RelationalDatabase");
50  
51      /** Class logger. */
52      private final Logger log = LoggerFactory.getLogger(RDBMSDataConnectorBeanDefinitionParser.class);
53  
54      /** {@inheritDoc} */
55      protected Class getBeanClass(Element element) {
56          return RDBMSDataConnectorFactoryBean.class;
57      }
58  
59      /** {@inheritDoc} */
60      protected void doParse(String pluginId, Element pluginConfig, Map<QName, List<Element>> pluginConfigChildren,
61              BeanDefinitionBuilder pluginBuilder, ParserContext parserContext) {
62          super.doParse(pluginId, pluginConfig, pluginConfigChildren, pluginBuilder, parserContext);
63  
64          processConnectionManagement(pluginId, pluginConfig, pluginConfigChildren, pluginBuilder, parserContext);
65  
66          processQueryHandlingConfig(pluginId, pluginConfig, pluginConfigChildren, pluginBuilder, parserContext);
67  
68          processResultHandlingConfig(pluginId, pluginConfig, pluginConfigChildren, pluginBuilder, parserContext);
69  
70          processCacheConfig(pluginId, pluginConfig, pluginConfigChildren, pluginBuilder, parserContext);
71      }
72  
73      /**
74       * Processes the connection management configuration.
75       * 
76       * @param pluginId ID of the data connector
77       * @param pluginConfig configuration element for the data connector
78       * @param pluginConfigChildren child config elements for the data connect
79       * @param pluginBuilder builder of the data connector
80       * @param parserContext current configuration parsing context
81       */
82      protected void processConnectionManagement(String pluginId, Element pluginConfig,
83              Map<QName, List<Element>> pluginConfigChildren, BeanDefinitionBuilder pluginBuilder,
84              ParserContext parserContext) {
85          DataSource datasource;
86          List<Element> cmc = pluginConfigChildren.get(new QName(DataConnectorNamespaceHandler.NAMESPACE,
87                  "ContainerManagedConnection"));
88          if (cmc != null && cmc.get(0) != null) {
89              datasource = buildContainerManagedConnection(pluginId, cmc.get(0));
90          } else {
91              datasource = buildApplicationManagedConnection(pluginId, pluginConfigChildren.get(
92                      new QName(DataConnectorNamespaceHandler.NAMESPACE, "ApplicationManagedConnection")).get(0));
93          }
94  
95          pluginBuilder.addPropertyValue("connectionDataSource", datasource);
96      }
97  
98      /**
99       * Builds a JDBC {@link DataSource} from a ContainerManagedConnection configuration element.
100      * 
101      * @param pluginId ID of this data connector
102      * @param cmc the container managed configuration element
103      * 
104      * @return the built data source
105      */
106     protected DataSource buildContainerManagedConnection(String pluginId, Element cmc) {
107         String jndiResource = cmc.getAttributeNS(null, "resourceName");
108         jndiResource = DatatypeHelper.safeTrim(jndiResource);
109 
110         Hashtable<String, String> initCtxProps = buildProperties(XMLHelper.getChildElementsByTagNameNS(cmc,
111                 DataConnectorNamespaceHandler.NAMESPACE, "JNDIConnectionProperty"));
112         try {
113             InitialContext initCtx = new InitialContext(initCtxProps);
114             DataSource dataSource = (DataSource) initCtx.lookup(jndiResource);
115             if (dataSource == null) {
116                 log.error("DataSource " + jndiResource + " did not exist in JNDI directory");
117                 throw new BeanCreationException("DataSource " + jndiResource + " did not exist in JNDI directory");
118             }
119             if (log.isDebugEnabled()) {
120                 log.debug("Retrieved data source for data connector {} from JNDI location {} using properties ",
121                         pluginId, initCtxProps);
122             }
123             return dataSource;
124         } catch (NamingException e) {
125             log.error("Unable to retrieve data source for data connector " + pluginId + " from JNDI location "
126                     + jndiResource + " using properties " + initCtxProps, e);
127             return null;
128         }
129     }
130 
131     /**
132      * Builds a JDBC {@link DataSource} from an ApplicationManagedConnection configuration element.
133      * 
134      * @param pluginId ID of this data connector
135      * @param amc the application managed configuration element
136      * 
137      * @return the built data source
138      */
139     protected DataSource buildApplicationManagedConnection(String pluginId, Element amc) {
140         ComboPooledDataSource datasource = new ComboPooledDataSource();
141 
142         String driverClass = DatatypeHelper.safeTrim(amc.getAttributeNS(null, "jdbcDriver"));
143         ClassLoader classLoader = this.getClass().getClassLoader();
144         try {
145             classLoader.loadClass(driverClass);
146         } catch (ClassNotFoundException e) {
147             log.error("Unable to create relational database connector, JDBC driver can not be found on the classpath");
148             throw new BeanCreationException(
149                     "Unable to create relational database connector, JDBC driver can not be found on the classpath");
150         }
151 
152         try {
153             datasource.setDriverClass(driverClass);
154             datasource.setJdbcUrl(DatatypeHelper.safeTrim(amc.getAttributeNS(null, "jdbcURL")));
155             datasource.setUser(DatatypeHelper.safeTrim(amc.getAttributeNS(null, "jdbcUserName")));
156             datasource.setPassword(DatatypeHelper.safeTrim(amc.getAttributeNS(null, "jdbcPassword")));
157 
158             if (amc.hasAttributeNS(null, "poolAcquireIncrement")) {
159                 datasource.setAcquireIncrement(Integer.parseInt(DatatypeHelper.safeTrim(amc.getAttributeNS(null,
160                         "poolAcquireIncrement"))));
161             } else {
162                 datasource.setAcquireIncrement(3);
163             }
164 
165             if (amc.hasAttributeNS(null, "poolAcquireRetryAttempts")) {
166                 datasource.setAcquireRetryAttempts(Integer.parseInt(DatatypeHelper.safeTrim(amc.getAttributeNS(null,
167                         "poolAcquireRetryAttempts"))));
168             } else {
169                 datasource.setAcquireRetryAttempts(36);
170             }
171 
172             if (amc.hasAttributeNS(null, "poolAcquireRetryDelay")) {
173                 datasource.setAcquireRetryDelay(Integer.parseInt(DatatypeHelper.safeTrim(amc.getAttributeNS(null,
174                         "poolAcquireRetryDelay"))));
175             } else {
176                 datasource.setAcquireRetryDelay(5000);
177             }
178 
179             if (amc.hasAttributeNS(null, "poolBreakAfterAcquireFailure")) {
180                 datasource.setBreakAfterAcquireFailure(XMLHelper.getAttributeValueAsBoolean(amc.getAttributeNodeNS(
181                         null, "poolBreakAfterAcquireFailure")));
182             } else {
183                 datasource.setBreakAfterAcquireFailure(true);
184             }
185 
186             if (amc.hasAttributeNS(null, "poolMinSize")) {
187                 datasource.setMinPoolSize(Integer.parseInt(DatatypeHelper.safeTrim(amc.getAttributeNS(null,
188                         "poolMinSize"))));
189             } else {
190                 datasource.setMinPoolSize(2);
191             }
192 
193             if (amc.hasAttributeNS(null, "poolMaxSize")) {
194                 datasource.setMaxPoolSize(Integer.parseInt(DatatypeHelper.safeTrim(amc.getAttributeNS(null,
195                         "poolMaxSize"))));
196             } else {
197                 datasource.setMaxPoolSize(50);
198             }
199 
200             if (amc.hasAttributeNS(null, "poolMaxIdleTime")) {
201                 datasource.setMaxIdleTime(Integer.parseInt(DatatypeHelper.safeTrim(amc.getAttributeNS(null,
202                         "poolMaxIdleTime"))));
203             } else {
204                 datasource.setMaxIdleTime(600);
205             }
206 
207             if (amc.hasAttributeNS(null, "poolIdleTestPeriod")) {
208                 datasource.setIdleConnectionTestPeriod(Integer.parseInt(DatatypeHelper.safeTrim(amc.getAttributeNS(
209                         null, "poolIdleTestPeriod"))));
210             } else {
211                 datasource.setIdleConnectionTestPeriod(180);
212             }
213 
214             log.debug("Created application managed data source for data connector {}", pluginId);
215             return datasource;
216         } catch (PropertyVetoException e) {
217             log.error("Unable to create data source for data connector {} with JDBC driver class {}", pluginId,
218                     driverClass);
219             return null;
220         }
221     }
222 
223     /**
224      * Processes query handling related configuration options.
225      * 
226      * @param pluginId ID of the data connector
227      * @param pluginConfig configuration element for the data connector
228      * @param pluginConfigChildren child config elements for the data connect
229      * @param pluginBuilder builder of the data connector
230      * @param parserContext current configuration parsing context
231      */
232     protected void processQueryHandlingConfig(String pluginId, Element pluginConfig,
233             Map<QName, List<Element>> pluginConfigChildren, BeanDefinitionBuilder pluginBuilder,
234             ParserContext parserContext) {
235         String templateEngineRef = pluginConfig.getAttributeNS(null, "templateEngine");
236         pluginBuilder.addPropertyReference("templateEngine", templateEngineRef);
237 
238         List<Element> queryTemplateElems = pluginConfigChildren.get(new QName(DataConnectorNamespaceHandler.NAMESPACE,
239                 "QueryTemplate"));
240         String queryTemplate = queryTemplateElems.get(0).getTextContent();
241         log.debug("Data connector {} query template: {}", pluginId, queryTemplate);
242         pluginBuilder.addPropertyValue("queryTemplate", queryTemplate);
243 
244         long queryTimeout = 5 * 1000;
245         if (pluginConfig.hasAttributeNS(null, "queryTimeout")) {
246             queryTimeout = SpringConfigurationUtils.parseDurationToMillis(
247                     "queryTimeout on relational database connector " + pluginId, pluginConfig.getAttributeNS(null,
248                             "queryTimeout"), 0);
249         }
250         log.debug("Data connector {} SQL query timeout: {}ms", pluginId, queryTimeout);
251         pluginBuilder.addPropertyValue("queryTimeout", queryTimeout);
252 
253         boolean useSP = false;
254         if (pluginConfig.hasAttributeNS(null, "queryUsesStoredProcedure")) {
255             useSP = XMLHelper.getAttributeValueAsBoolean(pluginConfig.getAttributeNodeNS(null,
256                     "queryUsesStoredProcedure"));
257         }
258         log.debug("Data connector {} query uses stored procedures: {}", pluginId, useSP);
259         pluginBuilder.addPropertyValue("queryUsesStoredProcedures", useSP);
260 
261         boolean readOnlyCtx = true;
262         if (pluginConfig.hasAttributeNS(null, "readOnlyConnection")) {
263             readOnlyCtx = XMLHelper.getAttributeValueAsBoolean(pluginConfig.getAttributeNodeNS(null,
264                     "readOnlyConnection"));
265         }
266         log.debug("Data connector {} connections are read only: {}", pluginId, readOnlyCtx);
267         pluginBuilder.addPropertyValue("readOnlyConnections", readOnlyCtx);
268 
269     }
270 
271     /**
272      * Processes the result handling configuration options.
273      * 
274      * @param pluginId ID of the data connector
275      * @param pluginConfig configuration element for the data connector
276      * @param pluginConfigChildren child config elements for the data connect
277      * @param pluginBuilder builder of the data connector
278      * @param parserContext current configuration parsing context
279      */
280     protected void processResultHandlingConfig(String pluginId, Element pluginConfig,
281             Map<QName, List<Element>> pluginConfigChildren, BeanDefinitionBuilder pluginBuilder,
282             ParserContext parserContext) {
283 
284         List<RDBMSColumnDescriptor> descriptors = processColumnDescriptors(pluginId, pluginConfigChildren,
285                 pluginBuilder);
286         pluginBuilder.addPropertyValue("columnDescriptors", descriptors);
287 
288         boolean noResultsIsError = false;
289         if (pluginConfig.hasAttributeNS(null, "noResultIsError")) {
290             noResultsIsError = XMLHelper.getAttributeValueAsBoolean(pluginConfig.getAttributeNodeNS(null,
291                     "noResultIsError"));
292         }
293         log.debug("Data connector {} no results is error: {}", pluginId, noResultsIsError);
294         pluginBuilder.addPropertyValue("noResultIsError", noResultsIsError);
295     }
296 
297     /**
298      * Processes the cache configuration options.
299      * 
300      * @param pluginId ID of the data connector
301      * @param pluginConfig configuration element for the data connector
302      * @param pluginConfigChildren child config elements for the data connect
303      * @param pluginBuilder builder of the data connector
304      * @param parserContext current configuration parsing context
305      */
306     protected void processCacheConfig(String pluginId, Element pluginConfig,
307             Map<QName, List<Element>> pluginConfigChildren, BeanDefinitionBuilder pluginBuilder,
308             ParserContext parserContext) {
309         boolean cacheResults = false;
310         String cacheManagerId = "shibboleth.CacheManager";
311         long cacheElementTtl = 4 * 60 * 60 * 1000;
312         int maximumCachedElements = 500;
313 
314         List<Element> cacheConfigs = XMLHelper.getChildElementsByTagNameNS(pluginConfig,
315                 DataConnectorNamespaceHandler.NAMESPACE, "ResultCache");
316         if (cacheConfigs != null && !cacheConfigs.isEmpty()) {
317             Element cacheConfig = cacheConfigs.get(0);
318 
319             cacheResults = true;
320 
321             if (cacheConfig.hasAttributeNS(null, "cacheManagerRef")) {
322                 cacheManagerId = DatatypeHelper.safeTrim(cacheConfig.getAttributeNS(null, "cacheManagerRef"));
323             }
324 
325             if (cacheConfig.hasAttributeNS(null, "elementTimeToLive")) {
326                 cacheElementTtl = SpringConfigurationUtils.parseDurationToMillis("elementTimeToLive on data connector "
327                         + pluginId, cacheConfig.getAttributeNS(null, "elementTimeToLive"), 0);
328             }
329 
330             if (cacheConfig.hasAttributeNS(null, "maximumCachedElements")) {
331                 maximumCachedElements = Integer.parseInt(DatatypeHelper.safeTrim(cacheConfig.getAttributeNS(null,
332                         "maximumCachedElements")));
333             }
334         }
335 
336         if (pluginConfig.hasAttributeNS(null, "cacheResults")) {
337             log.warn("Data connection {}: use of 'cacheResults' attribute is deprecated.  Use <ResultCache> instead.",
338                     pluginId);
339             cacheResults = XMLHelper.getAttributeValueAsBoolean(pluginConfig.getAttributeNodeNS(null, "cacheResults"));
340         }
341 
342         if (cacheResults) {
343             log.debug("Data connector {} is caching results: {}", pluginId, cacheResults);
344 
345             pluginBuilder.addPropertyReference("cacheManager", cacheManagerId);
346 
347             log.debug("Data connector {} cache element time to live: {}ms", pluginId, cacheElementTtl);
348             pluginBuilder.addPropertyValue("cacheElementTimeToLive", cacheElementTtl);
349 
350             log.debug("Data connector {} maximum number of caches elements: {}", pluginId, maximumCachedElements);
351             pluginBuilder.addPropertyValue("maximumCachedElements", maximumCachedElements);
352         }
353 
354     }
355 
356     /**
357      * Processes the Column descriptor configuration elements.
358      * 
359      * @param pluginId ID of this data connector
360      * @param pluginConfigChildren configuration elements
361      * @param pluginBuilder the bean definition parser
362      * 
363      * @return result set column descriptors
364      */
365     protected List<RDBMSColumnDescriptor> processColumnDescriptors(String pluginId,
366             Map<QName, List<Element>> pluginConfigChildren, BeanDefinitionBuilder pluginBuilder) {
367         List<RDBMSColumnDescriptor> columnDescriptors = new ArrayList<RDBMSColumnDescriptor>();
368 
369         QName columnElementName = new QName(DataConnectorNamespaceHandler.NAMESPACE, "Column");
370 
371         RDBMSColumnDescriptor columnDescriptor;
372         String columnName;
373         String attributeId;
374         String dataType;
375         if (pluginConfigChildren.containsKey(columnElementName)) {
376             for (Element columnElem : pluginConfigChildren.get(columnElementName)) {
377                 columnName = columnElem.getAttributeNS(null, "columnName");
378                 attributeId = columnElem.getAttributeNS(null, "attributeID");
379 
380                 if (columnElem.hasAttributeNS(null, "type")) {
381                     dataType = columnElem.getAttributeNS(null, "type");
382                 } else {
383                     dataType = DATA_TYPES.String.toString();
384                 }
385 
386                 columnDescriptor = new RDBMSColumnDescriptor(columnName, attributeId, DATA_TYPES.valueOf(dataType));
387                 columnDescriptors.add(columnDescriptor);
388             }
389             log.debug("Data connector {} column descriptors: {}", pluginId, columnDescriptors);
390         }
391 
392         return columnDescriptors;
393     }
394 
395     /**
396      * Builds a hash from PropertyType elements.
397      * 
398      * @param propertyElements properties elements
399      * 
400      * @return properties extracted from elements, key is the property name.
401      */
402     protected Hashtable<String, String> buildProperties(List<Element> propertyElements) {
403         if (propertyElements == null || propertyElements.size() < 1) {
404             return null;
405         }
406 
407         Hashtable<String, String> properties = new Hashtable<String, String>();
408 
409         String propName;
410         String propValue;
411         for (Element propertyElement : propertyElements) {
412             propName = DatatypeHelper.safeTrim(propertyElement.getAttributeNS(null, "name"));
413             propValue = DatatypeHelper.safeTrim(propertyElement.getAttributeNS(null, "value"));
414             properties.put(propName, propValue);
415         }
416 
417         return properties;
418     }
419 }