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