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