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  
44  /** Spring bean definition parser for reading relational database data connector. */
45  public class RDBMSDataConnectorBeanDefinitionParser extends BaseDataConnectorBeanDefinitionParser {
46  
47      /** Schema type name. */
48      public static final QName TYPE_NAME = new QName(DataConnectorNamespaceHandler.NAMESPACE, "RelationalDatabase");
49  
50      /** ContainerManagedApplication element name. */
51      public static final QName CONTAINER_MANAGED_CONNECTION_ELEMENT_NAME = new QName(
52              DataConnectorNamespaceHandler.NAMESPACE, "ContainerManagedConnection");
53  
54      /** ApplicationManagedApplication element name. */
55      public static final QName APPLICATION_MANAGED_CONNECTION_ELEMENT_NAME = new QName(
56              DataConnectorNamespaceHandler.NAMESPACE, "ApplicationManagedConnection");
57  
58      /** QueryTemplate element name. */
59      public static final QName QUERY_TEMPLATE_ELEMENT_NAME = new QName(DataConnectorNamespaceHandler.NAMESPACE,
60              "QueryTemplate");
61  
62      /** Column element name. */
63      public static final QName COLUMN_ELEMENT_NAME = new QName(DataConnectorNamespaceHandler.NAMESPACE, "Column");
64  
65      /** Class logger. */
66      private final Logger log = LoggerFactory.getLogger(RDBMSDataConnectorBeanDefinitionParser.class);
67  
68      /** {@inheritDoc} */
69      protected Class getBeanClass(Element element) {
70          return RDBMSDataConnectorFactoryBean.class;
71      }
72  
73      /** {@inheritDoc} */
74      protected void doParse(String pluginId, Element pluginConfig, Map<QName, List<Element>> pluginConfigChildren,
75              BeanDefinitionBuilder pluginBuilder, ParserContext parserContext) {
76          super.doParse(pluginId, pluginConfig, pluginConfigChildren, pluginBuilder, parserContext);
77  
78          DataSource connectionSource = processConnectionManagement(pluginId, pluginConfigChildren, pluginBuilder);
79          pluginBuilder.addPropertyValue("connectionDataSource", connectionSource);
80  
81          String queryTemplate = processesQueryTemplate(pluginId, pluginConfigChildren, pluginBuilder);
82          queryTemplate = DatatypeHelper.safeTrimOrNullString(queryTemplate);
83          log.debug("Data connector {} database query template: {}", pluginId, queryTemplate);
84          pluginBuilder.addPropertyValue("queryTemplate", queryTemplate);
85  
86          List<RDBMSColumnDescriptor> descriptors = processColumnDescriptors(pluginId, pluginConfigChildren,
87                  pluginBuilder);
88          pluginBuilder.addPropertyValue("columnDescriptors", descriptors);
89  
90          boolean noResultsIsError = false;
91          if (pluginConfig.hasAttributeNS(null, "noResultIsError")) {
92              noResultsIsError = XMLHelper.getAttributeValueAsBoolean(pluginConfig.getAttributeNodeNS(null,
93                      "noResultIsError"));
94          }
95          log.debug("Data connector {} no results is error: {}", pluginId, noResultsIsError);
96          pluginBuilder.addPropertyValue("noResultIsError", noResultsIsError);
97  
98          boolean cacheResults = false;
99          if (pluginConfig.hasAttributeNS(null, "cacheResults")) {
100             cacheResults = XMLHelper.getAttributeValueAsBoolean(pluginConfig.getAttributeNodeNS(null, "cacheResults"));
101         }
102         log.debug("Data connector {} cache results: {}", pluginId, cacheResults);
103         pluginBuilder.addPropertyValue("cacheResults", cacheResults);
104 
105         boolean useSP = false;
106         if (pluginConfig.hasAttributeNS(null, "queryUsesStoredProcedure")) {
107             useSP = XMLHelper.getAttributeValueAsBoolean(pluginConfig.getAttributeNodeNS(null,
108                     "queryUsesStoredProcedure"));
109         }
110         log.debug("Data connector {} query uses stored procedures: {}", pluginId, useSP);
111         pluginBuilder.addPropertyValue("queryUsesStoredProcedures", useSP);
112 
113         boolean readOnlyCtx = true;
114         if (pluginConfig.hasAttributeNS(null, "readOnlyConnection")) {
115             readOnlyCtx = XMLHelper.getAttributeValueAsBoolean(pluginConfig.getAttributeNodeNS(null,
116                     "readOnlyConnection"));
117         }
118         log.debug("Data connector {} connections are read only: {}", pluginId, readOnlyCtx);
119         pluginBuilder.addPropertyValue("readOnlyConnections", readOnlyCtx);
120 
121         String templateEngineRef = pluginConfig.getAttributeNS(null, "templateEngine");
122         pluginBuilder.addPropertyReference("templateEngine", templateEngineRef);
123     }
124 
125     /**
126      * Processes the connection management configuration.
127      * 
128      * @param pluginId ID of this data connector
129      * @param pluginConfigChildren configuration elements for this connector
130      * @param pluginBuilder bean definition builder
131      * 
132      * @return data source built from configuration
133      */
134     protected DataSource processConnectionManagement(String pluginId, Map<QName, List<Element>> pluginConfigChildren,
135             BeanDefinitionBuilder pluginBuilder) {
136         List<Element> cmc = pluginConfigChildren.get(CONTAINER_MANAGED_CONNECTION_ELEMENT_NAME);
137         if (cmc != null && cmc.get(0) != null) {
138             return buildContainerManagedConnection(pluginId, cmc.get(0));
139         } else {
140             return buildApplicationManagedConnection(pluginId, pluginConfigChildren.get(
141                     APPLICATION_MANAGED_CONNECTION_ELEMENT_NAME).get(0));
142         }
143     }
144 
145     /**
146      * Builds a JDBC {@link DataSource} from a ContainerManagedConnection configuration element.
147      * 
148      * @param pluginId ID of this data connector
149      * @param cmc the container managed configuration element
150      * 
151      * @return the built data source
152      */
153     protected DataSource buildContainerManagedConnection(String pluginId, Element cmc) {
154         String jndiResource = cmc.getAttributeNS(null, "resourceName");
155         jndiResource = DatatypeHelper.safeTrim(jndiResource);
156 
157         Hashtable<String, String> initCtxProps = buildProperties(XMLHelper.getChildElementsByTagNameNS(cmc,
158                 DataConnectorNamespaceHandler.NAMESPACE, "JNDIConnectionProperty"));
159         try {
160             InitialContext initCtx = new InitialContext(initCtxProps);
161             DataSource dataSource = (DataSource) initCtx.lookup(jndiResource);
162             if (dataSource == null) {
163                 log.error("DataSource " + jndiResource + " did not exist in JNDI directory");
164                 throw new BeanCreationException("DataSource " + jndiResource + " did not exist in JNDI directory");
165             }
166             if (log.isDebugEnabled()) {
167                 log.debug("Retrieved data source for data connector {} from JNDI location {} using properties ",
168                         pluginId, initCtxProps);
169             }
170             return dataSource;
171         } catch (NamingException e) {
172             log.error("Unable to retrieve data source for data connector " + pluginId + " from JNDI location "
173                     + jndiResource + " using properties " + initCtxProps, e);
174             return null;
175         }
176     }
177 
178     /**
179      * Builds a JDBC {@link DataSource} from an ApplicationManagedConnection configuration element.
180      * 
181      * @param pluginId ID of this data connector
182      * @param amc the application managed configuration element
183      * 
184      * @return the built data source
185      */
186     protected DataSource buildApplicationManagedConnection(String pluginId, Element amc) {
187         ComboPooledDataSource datasource = new ComboPooledDataSource();
188 
189         String driverClass = DatatypeHelper.safeTrim(amc.getAttributeNS(null, "jdbcDriver"));
190         ClassLoader classLoader = this.getClass().getClassLoader();
191         try {
192             classLoader.loadClass(driverClass);
193         } catch (ClassNotFoundException e) {
194             log.error("Unable to create relational database connector, JDBC driver can not be found on the classpath");
195             throw new BeanCreationException(
196                     "Unable to create relational database connector, JDBC driver can not be found on the classpath");
197         }
198 
199         try {
200             datasource.setDriverClass(driverClass);
201             datasource.setJdbcUrl(DatatypeHelper.safeTrim(amc.getAttributeNS(null, "jdbcURL")));
202             datasource.setUser(DatatypeHelper.safeTrim(amc.getAttributeNS(null, "jdbcUserName")));
203             datasource.setPassword(DatatypeHelper.safeTrim(amc.getAttributeNS(null, "jdbcPassword")));
204 
205             if (amc.hasAttributeNS(null, "poolAcquireIncrement")) {
206                 datasource.setAcquireIncrement(Integer.parseInt(DatatypeHelper.safeTrim(amc.getAttributeNS(null,
207                         "poolAcquireIncrement"))));
208             } else {
209                 datasource.setAcquireIncrement(3);
210             }
211 
212             if (amc.hasAttributeNS(null, "poolAcquireRetryAttempts")) {
213                 datasource.setAcquireRetryAttempts(Integer.parseInt(DatatypeHelper.safeTrim(amc.getAttributeNS(null,
214                         "poolAcquireRetryAttempts"))));
215             } else {
216                 datasource.setAcquireRetryAttempts(36);
217             }
218 
219             if (amc.hasAttributeNS(null, "poolAcquireRetryDelay")) {
220                 datasource.setAcquireRetryDelay(Integer.parseInt(DatatypeHelper.safeTrim(amc.getAttributeNS(null,
221                         "poolAcquireRetryDelay"))));
222             } else {
223                 datasource.setAcquireRetryDelay(5000);
224             }
225 
226             if (amc.hasAttributeNS(null, "poolBreakAfterAcquireFailure")) {
227                 datasource.setBreakAfterAcquireFailure(XMLHelper.getAttributeValueAsBoolean(amc.getAttributeNodeNS(
228                         null, "poolBreakAfterAcquireFailure")));
229             } else {
230                 datasource.setBreakAfterAcquireFailure(true);
231             }
232 
233             if (amc.hasAttributeNS(null, "poolMinSize")) {
234                 datasource.setMinPoolSize(Integer.parseInt(DatatypeHelper.safeTrim(amc.getAttributeNS(null,
235                         "poolMinSize"))));
236             } else {
237                 datasource.setMinPoolSize(2);
238             }
239 
240             if (amc.hasAttributeNS(null, "poolMaxSize")) {
241                 datasource.setMaxPoolSize(Integer.parseInt(DatatypeHelper.safeTrim(amc.getAttributeNS(null,
242                         "poolMaxSize"))));
243             } else {
244                 datasource.setMaxPoolSize(50);
245             }
246 
247             if (amc.hasAttributeNS(null, "poolMaxIdleTime")) {
248                 datasource.setMaxIdleTime(Integer.parseInt(DatatypeHelper.safeTrim(amc.getAttributeNS(null,
249                         "poolMaxIdleTime"))));
250             } else {
251                 datasource.setMaxIdleTime(600);
252             }
253 
254             if (amc.hasAttributeNS(null, "poolIdleTestPeriod")) {
255                 datasource.setIdleConnectionTestPeriod(Integer.parseInt(DatatypeHelper.safeTrim(amc.getAttributeNS(
256                         null, "poolIdleTestPeriod"))));
257             } else {
258                 datasource.setIdleConnectionTestPeriod(180);
259             }
260 
261             log.debug("Created application managed data source for data connector {}", pluginId);
262             return datasource;
263         } catch (PropertyVetoException e) {
264             log.error("Unable to create data source for data connector {} with JDBC driver class {}", pluginId,
265                     driverClass);
266             return null;
267         }
268     }
269 
270     /**
271      * Processes the QueryTemplate configuration element.
272      * 
273      * @param pluginId ID of this data connector
274      * @param pluginConfigChildren configuration elements
275      * @param pluginBuilder the bean definition builder
276      * 
277      * @return SQL query template
278      */
279     protected String processesQueryTemplate(String pluginId, Map<QName, List<Element>> pluginConfigChildren,
280             BeanDefinitionBuilder pluginBuilder) {
281         List<Element> queryTemplateElems = pluginConfigChildren.get(QUERY_TEMPLATE_ELEMENT_NAME);
282         String queryTemplate = queryTemplateElems.get(0).getTextContent();
283         log.debug("Data connector {} query template: {}", pluginId, queryTemplate);
284         return queryTemplate;
285     }
286 
287     /**
288      * Processes the Column descriptor configuration elements.
289      * 
290      * @param pluginId ID of this data connector
291      * @param pluginConfigChildren configuration elements
292      * @param pluginBuilder the bean definition parser
293      * 
294      * @return result set column descriptors
295      */
296     protected List<RDBMSColumnDescriptor> processColumnDescriptors(String pluginId,
297             Map<QName, List<Element>> pluginConfigChildren, BeanDefinitionBuilder pluginBuilder) {
298         List<RDBMSColumnDescriptor> columnDescriptors = new ArrayList<RDBMSColumnDescriptor>();
299 
300         RDBMSColumnDescriptor columnDescriptor;
301         String columnName;
302         String attributeId;
303         String dataType;
304         if (pluginConfigChildren.containsKey(COLUMN_ELEMENT_NAME)) {
305             for (Element columnElem : pluginConfigChildren.get(COLUMN_ELEMENT_NAME)) {
306                 columnName = columnElem.getAttributeNS(null, "columnName");
307                 attributeId = columnElem.getAttributeNS(null, "attributeID");
308 
309                 if (columnElem.hasAttributeNS(null, "type")) {
310                     dataType = columnElem.getAttributeNS(null, "type");
311                 } else {
312                     dataType = DATA_TYPES.String.toString();
313                 }
314 
315                 columnDescriptor = new RDBMSColumnDescriptor(columnName, attributeId, DATA_TYPES.valueOf(dataType));
316                 columnDescriptors.add(columnDescriptor);
317             }
318             log.debug("Data connector {} column descriptors: {}", pluginId, columnDescriptors);
319         }
320 
321         return columnDescriptors;
322     }
323 
324     /**
325      * Builds a hash from PropertyType elements.
326      * 
327      * @param propertyElements properties elements
328      * 
329      * @return properties extracted from elements, key is the property name.
330      */
331     protected Hashtable<String, String> buildProperties(List<Element> propertyElements) {
332         if (propertyElements == null || propertyElements.size() < 1) {
333             return null;
334         }
335 
336         Hashtable<String, String> properties = new Hashtable<String, String>();
337 
338         String propName;
339         String propValue;
340         for (Element propertyElement : propertyElements) {
341             propName = DatatypeHelper.safeTrim(propertyElement.getAttributeNS(null, "name"));
342             propValue = DatatypeHelper.safeTrim(propertyElement.getAttributeNS(null, "value"));
343             properties.put(propName, propValue);
344         }
345 
346         return properties;
347     }
348 }