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
44
45 public class RDBMSDataConnectorBeanDefinitionParser extends BaseDataConnectorBeanDefinitionParser {
46
47
48 public static final QName TYPE_NAME = new QName(DataConnectorNamespaceHandler.NAMESPACE, "RelationalDatabase");
49
50
51 public static final QName CONTAINER_MANAGED_CONNECTION_ELEMENT_NAME = new QName(
52 DataConnectorNamespaceHandler.NAMESPACE, "ContainerManagedConnection");
53
54
55 public static final QName APPLICATION_MANAGED_CONNECTION_ELEMENT_NAME = new QName(
56 DataConnectorNamespaceHandler.NAMESPACE, "ApplicationManagedConnection");
57
58
59 public static final QName QUERY_TEMPLATE_ELEMENT_NAME = new QName(DataConnectorNamespaceHandler.NAMESPACE,
60 "QueryTemplate");
61
62
63 public static final QName COLUMN_ELEMENT_NAME = new QName(DataConnectorNamespaceHandler.NAMESPACE, "Column");
64
65
66 private final Logger log = LoggerFactory.getLogger(RDBMSDataConnectorBeanDefinitionParser.class);
67
68
69 protected Class getBeanClass(Element element) {
70 return RDBMSDataConnectorFactoryBean.class;
71 }
72
73
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
127
128
129
130
131
132
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
147
148
149
150
151
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
180
181
182
183
184
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
272
273
274
275
276
277
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
289
290
291
292
293
294
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
326
327
328
329
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 }