1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
46 public class RDBMSDataConnectorBeanDefinitionParser extends BaseDataConnectorBeanDefinitionParser {
47
48
49 public static final QName TYPE_NAME = new QName(DataConnectorNamespaceHandler.NAMESPACE, "RelationalDatabase");
50
51
52 private final Logger log = LoggerFactory.getLogger(RDBMSDataConnectorBeanDefinitionParser.class);
53
54
55 protected Class getBeanClass(Element element) {
56 return RDBMSDataConnectorFactoryBean.class;
57 }
58
59
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
75
76
77
78
79
80
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
100
101
102
103
104
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
133
134
135
136
137
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
225
226
227
228
229
230
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
273
274
275
276
277
278
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
299
300
301
302
303
304
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
358
359
360
361
362
363
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
397
398
399
400
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 }