Make it possible to set pooling size more fully.
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / aa / attrresolv / provider / JDBCDataConnector.java
1 /*
2  * Copyright (c) 2003 National Research Council of Canada
3  *
4  * Permission is hereby granted, free of charge, to any person 
5  * obtaining a copy of this software and associated documentation 
6  * files (the "Software"), to deal in the Software without 
7  * restriction, including without limitation the rights to use, 
8  * copy, modify, merge, publish, distribute, sublicense, and/or 
9  * sell copies of the Software, and to permit persons to whom the 
10  * Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be 
13  * included in all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
16  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
17  * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
18  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
19  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
20  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
21  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 
22  * OTHER DEALINGS IN THE SOFTWARE.
23  *
24  */
25
26 package edu.internet2.middleware.shibboleth.aa.attrresolv.provider;
27
28 import java.io.PrintWriter;
29 import java.lang.reflect.Constructor;
30 import java.security.Principal;
31 import java.sql.Blob;
32 import java.sql.Clob;
33 import java.sql.Connection;
34 import java.sql.PreparedStatement;
35 import java.sql.ResultSet;
36 import java.sql.ResultSetMetaData;
37 import java.sql.SQLException;
38 import java.sql.Types;
39 import java.text.SimpleDateFormat;
40 import java.util.ArrayList;
41 import java.util.Iterator;
42 import java.util.Properties;
43
44 import javax.naming.NamingException;
45 import javax.naming.directory.Attribute;
46 import javax.naming.directory.Attributes;
47 import javax.naming.directory.BasicAttribute;
48 import javax.naming.directory.BasicAttributes;
49 import javax.sql.DataSource;
50
51 import org.apache.commons.dbcp.ConnectionFactory;
52 import org.apache.commons.dbcp.DriverManagerConnectionFactory;
53 import org.apache.commons.dbcp.PoolableConnectionFactory;
54 import org.apache.commons.dbcp.PoolingDataSource;
55 import org.apache.commons.pool.impl.GenericObjectPool;
56 import org.apache.commons.pool.impl.StackKeyedObjectPoolFactory;
57 import org.apache.log4j.Logger;
58 import org.apache.log4j.Priority;
59 import org.w3c.dom.Element;
60 import org.w3c.dom.Node;
61 import org.w3c.dom.NodeList;
62
63 import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeResolver;
64 import edu.internet2.middleware.shibboleth.aa.attrresolv.DataConnectorPlugIn;
65 import edu.internet2.middleware.shibboleth.aa.attrresolv.Dependencies;
66 import edu.internet2.middleware.shibboleth.aa.attrresolv.ResolutionPlugInException;
67 import edu.internet2.middleware.shibboleth.aa.attrresolv.ResolverAttribute;
68
69 /*
70  * Built at the Canada Institute for Scientific and Technical Information (CISTI 
71  * <ahref="http://www.cisti-icist.nrc-cnrc.gc.ca/">http://www.cisti-icist.nrc-cnrc.gc.ca/</a>, 
72  * the National Research Council Canada 
73  * (NRC <a href="http://www.nrc-cnrc.gc.ca/">http://www.nrc-cnrc.gc.ca/</a>)
74  * by David Dearman, COOP student from Dalhousie University,
75  * under the direction of Glen Newton, Head research (IT)
76  * <ahref="mailto:glen.newton@nrc-cnrc.gc.ca">glen.newton@nrc-cnrc.gc.ca</a>. 
77  */
78
79 /**
80  * Data Connector that uses JDBC to access user attributes stored in databases.
81  *
82  * @author David Dearman (dearman@cs.dal.ca)
83  * @author Walter Hoehn (wassa@columbia.edu)
84  * @author Scott Cantor
85  */
86
87 public class JDBCDataConnector extends BaseDataConnector implements DataConnectorPlugIn {
88
89         private static Logger log = Logger.getLogger(JDBCDataConnector.class.getName());
90         protected String searchVal;
91     protected int minResultSet=0,maxResultSet=0;
92         protected DataSource dataSource;
93         protected JDBCAttributeExtractor extractor;
94         protected JDBCStatementCreator statementCreator;
95
96         public JDBCDataConnector(Element e) throws ResolutionPlugInException {
97
98                 super(e);
99
100                 //Get the query string
101                 NodeList queryNodes = e.getElementsByTagNameNS(AttributeResolver.resolverNamespace, "Query");
102                 Node tnode = queryNodes.item(0).getFirstChild();
103                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
104                         searchVal = tnode.getNodeValue();
105                 }
106                 if (searchVal == null || searchVal.equals("")) {
107                         log.error("Database query must be specified.");
108                         throw new ResolutionPlugInException("Database query must be specified.");
109                 }
110
111                 //Load the supplied JDBC driver
112                 String dbDriverName = e.getAttribute("dbDriver");
113                 if (dbDriverName != null && (!dbDriverName.equals(""))) {
114                         loadDriver(dbDriverName);
115                 }
116
117                 String validationQuery = e.getAttribute("validationQuery");
118                 if (validationQuery == null || validationQuery.equals("")) {
119                         validationQuery = "select 1";
120                 }
121
122         try {
123             if (e.getAttributeNode("minResultSet") != null) {
124                 minResultSet = Integer.parseInt(e.getAttribute("minResultSet"));
125             }
126             if (e.getAttributeNode("maxResultSet") != null) {
127                 maxResultSet = Integer.parseInt(e.getAttribute("maxResultSet"));
128             }
129         } catch (NumberFormatException ex) {
130             log.error("Malformed result set limits: using defaults.");
131         }
132         
133                 //Load site-specific implementation classes     
134                 setupAttributeExtractor(
135                         (Element) e.getElementsByTagNameNS(AttributeResolver.resolverNamespace, "AttributeExtractor").item(
136                                 0));
137                 setupStatementCreator(
138                         (Element) e.getElementsByTagNameNS(AttributeResolver.resolverNamespace, "StatementCreator").item(0));
139         
140         //Load driver properties
141         Properties props = new Properties();
142         NodeList propertiesNode = e.getElementsByTagNameNS(AttributeResolver.resolverNamespace, "Property");
143         for (int i = 0; propertiesNode.getLength() > i; i++) {
144             Element property = (Element) propertiesNode.item(i);
145             String propertiesName = property.getAttribute("name");
146             String propertiesValue = property.getAttribute("value");
147
148             if (propertiesName != null
149                 && !propertiesName.equals("")
150                 && propertiesValue != null
151                 && !propertiesValue.equals("")) {
152                 props.setProperty(propertiesName, propertiesValue);
153                 log.debug("Property: (" + propertiesName + ")");
154                 log.debug("   Value: (" + propertiesValue + ")");
155             } else {
156                 log.error("Property is malformed.");
157                 throw new ResolutionPlugInException("Property is malformed.");
158             }
159         }
160         
161                 //Initialize a pooling Data Source
162                 int maxActive = 5;
163                 int maxIdle = 5;
164         int maxWait = 15;
165                 try {
166                         if (e.getAttributeNode("maxActive") != null) {
167                                 maxActive = Integer.parseInt(e.getAttribute("maxActive"));
168                         }
169                         if (e.getAttributeNode("maxIdle") != null) {
170                                 maxIdle = Integer.parseInt(e.getAttribute("maxIdle"));
171                         }
172             if (e.getAttributeNode("maxWait") != null) {
173                 maxWait = Integer.parseInt(e.getAttribute("maxWait"));
174             }
175                 } catch (NumberFormatException ex) {
176                         log.error("Malformed pooling limits: using defaults.");
177                 }
178                 if (e.getAttribute("dbURL") == null || e.getAttribute("dbURL").equals("")) {
179                         log.error("JDBC connection requires a dbURL property");
180                         throw new ResolutionPlugInException("JDBCDataConnection requires a \"dbURL\" property");
181                 }
182                 setupDataSource(e.getAttribute("dbURL"), props, maxActive, maxIdle, maxWait, validationQuery);
183         }
184
185         /**
186          * Initialize a Pooling Data Source
187          */
188         private void setupDataSource(String dbURL, Properties props, int maxActive, int maxIdle, int maxWait, String validationQuery) throws ResolutionPlugInException {
189
190                 GenericObjectPool objectPool = new GenericObjectPool(null);
191
192                 objectPool.setMaxActive(maxActive);
193                 objectPool.setMaxIdle(maxIdle);
194                 if (maxWait > 0)
195                         objectPool.setMaxWait(1000*maxWait);
196         else
197             objectPool.setMaxWait(maxWait);
198
199                 objectPool.setWhenExhaustedAction(GenericObjectPool.WHEN_EXHAUSTED_BLOCK);
200                 objectPool.setTestOnBorrow(true);
201
202                 ConnectionFactory connFactory = null;
203                 PoolableConnectionFactory poolConnFactory = null;
204
205                 try {
206                         connFactory = new DriverManagerConnectionFactory(dbURL, props);
207                         log.debug("Connection factory initialized.");
208                 } catch (Exception ex) {
209                         log.error(
210                                 "Connection factory couldn't be initialized, ensure database URL, username and password are correct.");
211                         throw new ResolutionPlugInException("Connection factory couldn't be initialized: " + ex.getMessage());
212                 }
213
214                 try {
215                         poolConnFactory =
216                         new PoolableConnectionFactory(
217                                 connFactory,
218                                 objectPool,
219                                 new StackKeyedObjectPoolFactory(),
220                                 validationQuery,
221                                 true,
222                                         false);
223                 } catch (Exception ex) {
224                         log.debug("Poolable connection factory error");
225                 }
226
227                 dataSource = new PoolingDataSource(objectPool);
228                 log.info("Data Source initialized.");
229                 try {
230                         dataSource.setLogWriter(
231                                 new Log4jPrintWriter(Logger.getLogger(JDBCDataConnector.class.getName() + ".Pool"), Priority.DEBUG));
232                 } catch (SQLException e) {
233                         log.error("Coudn't setup logger for database connection pool.");
234                 }
235         }
236
237         /**
238          * Instantiate an Attribute Extractor, using the default if none was configured
239          */
240         private void setupAttributeExtractor(Element config) throws ResolutionPlugInException {
241
242                 String className = null;
243                 if (config != null) {
244                         className = config.getAttribute("class");
245                 }
246                 if (className == null || className.equals("")) {
247                         log.debug("Using default Attribute Extractor.");
248                         className = DefaultAE.class.getName();
249                 }
250                 try {
251                         Class aeClass = Class.forName(className);
252                         extractor = (JDBCAttributeExtractor) aeClass.newInstance();
253                         log.debug("Attribute Extractor implementation loaded.");
254
255                 } catch (ClassNotFoundException e) {
256                         log.error("The supplied Attribute Extractor class could not be found: " + e);
257                         throw new ResolutionPlugInException(
258                                 "The supplied Attribute Extractor class could not be found: " + e.getMessage());
259                 } catch (Exception e) {
260                         log.error("Unable to instantiate Attribute Extractor implementation: " + e);
261                         throw new ResolutionPlugInException(
262                                 "Unable to instantiate Attribute Extractor implementation: " + e.getMessage());
263                 }
264         }
265
266         /**
267          * Instantiate a Statement Creator, using the default if none was configured
268          */
269         private void setupStatementCreator(Element config) throws ResolutionPlugInException {
270
271                 String scClassName = null;
272                 if (config != null) {
273                         scClassName = config.getAttribute("class");
274                 }
275                 if (scClassName == null || scClassName.equals("")) {
276                         log.debug("Using default Statement Creator.");
277                         scClassName = DefaultStatementCreator.class.getName();
278                 }
279                 try {
280                         Class scClass = Class.forName(scClassName);
281
282                         Class[] params = new Class[1];
283                         params[0] = Class.forName("org.w3c.dom.Element");
284                         try {
285                                 Constructor implementorConstructor = scClass.getConstructor(params);
286                                 Object[] args = new Object[1];
287                                 args[0] = config;
288                                 log.debug("Initializing Statement Creator of type (" + scClass.getName() + ").");
289                                 statementCreator = (JDBCStatementCreator) implementorConstructor.newInstance(args);
290                         } catch (NoSuchMethodException nsme) {
291                                 log.debug(
292                                         "Implementation constructor does have a parameterized constructor, attempting to load default.");
293                                 statementCreator = (JDBCStatementCreator) scClass.newInstance();
294                         }
295                         log.debug("Statement Creator implementation loaded.");
296
297                 } catch (ClassNotFoundException e) {
298                         log.error("The supplied Statement Creator class could not be found: " + e);
299                         throw new ResolutionPlugInException(
300                                 "The supplied Statement Creator class could not be found: " + e.getMessage());
301                 } catch (Exception e) {
302                         log.error("Unable to instantiate Statement Creator implementation: " + e);
303                         throw new ResolutionPlugInException(
304                                 "Unable to instantiate Statement Creator implementation: " + e.getMessage());
305                 }
306         }
307
308         public Attributes resolve(Principal principal, String requester, Dependencies depends)
309                 throws ResolutionPlugInException {
310
311                 log.debug("Resolving connector: (" + getId() + ")");
312
313                 //Retrieve a connection from the connection pool
314                 Connection conn = null;
315                 try {
316                         conn = dataSource.getConnection();
317                         log.debug("Connection retrieved from pool");
318                 } catch (Exception e) {
319                         log.error("JDBC Connector (" + getId() + ") unable to fetch a connection from the pool");
320                         throw new ResolutionPlugInException("Unable to fetch a connection from the pool: " + e.getMessage());
321                 }
322                 if (conn == null) {
323                         log.error("Pool didn't return a propertly initialized connection.");
324                         throw new ResolutionPlugInException("Pool didn't return a properly initialized connection.");
325                 }
326
327                 //Setup and execute a (pooled) prepared statement
328                 ResultSet rs = null;
329                 PreparedStatement preparedStatement = null;
330                 try {
331                         preparedStatement = conn.prepareStatement(searchVal);
332             preparedStatement.clearParameters();
333                         statementCreator.create(preparedStatement, principal, requester, depends);
334                         rs = preparedStatement.executeQuery();
335                         if (!rs.next()) {
336                 if (minResultSet == 0)
337                         return new BasicAttributes();
338                 else {
339                     log.error("Statement returned no rows, violating minResultSet of " + minResultSet);
340                     throw new ResolutionPlugInException("Statement didn't return any rows, violating minResultSet of " + minResultSet);
341                 }
342                         }
343             return extractor.extractAttributes(rs,minResultSet,maxResultSet);
344                 } catch (JDBCStatementCreatorException e) {
345                         log.error("An ERROR occured while constructing the query");
346                         throw new ResolutionPlugInException("An ERROR occured while constructing the query: " + e.getMessage());
347         } catch (JDBCAttributeExtractorException e) {
348             log.error("An ERROR occured while extracting attributes from result set");
349             throw new ResolutionPlugInException("An ERROR occured while extracting attributes from result set: " + e.getMessage());
350                 } catch (SQLException e) {
351                         log.error("An ERROR occured while executing the query");
352                         throw new ResolutionPlugInException("An ERROR occured while executing the query: " + e.getMessage());
353         } finally {
354             Exception e_save = null;
355             try {
356                 if (preparedStatement != null) {
357                     preparedStatement.close();
358                 }
359             } catch (SQLException e) {
360                 log.error("An error occured while closing the prepared statement: " + e.getMessage());
361                 e_save = e;
362             }
363             try {
364                 if (rs != null) {
365                     rs.close();
366                 }
367             } catch (SQLException e) {
368                 log.error("An error occured while closing the result set: " + e.getMessage());
369                 e_save = e;
370             }
371             try {
372                 conn.close();
373             } catch (SQLException e) {
374                 log.error("An error occured while closing the database connection: " + e.getMessage());
375                 e_save = e;
376             }
377             if (e_save != null) {
378                 throw new ResolutionPlugInException("An error occured while closing database objects:" + e_save.getMessage());
379             }
380         }
381         }
382
383         /** 
384          * Loads the driver used to access the database
385          * @param driver The driver used to access the database
386          * @throws ResolutionPlugInException If there is a failure to load the driver
387          */
388         public void loadDriver(String driver) throws ResolutionPlugInException {
389                 try {
390                         Class.forName(driver).newInstance();
391                         log.debug("Loading JDBC driver: " + driver);
392                 } catch (Exception e) {
393                         log.error("An error loading database driver: " + e);
394                         throw new ResolutionPlugInException(
395                                 "An IllegalAccessException occured while loading database driver: " + e.getMessage());
396                 }
397                 log.debug("Driver loaded.");
398         }
399
400         private class Log4jPrintWriter extends PrintWriter {
401
402                 private Priority level;
403                 private Logger logger;
404                 private StringBuffer text = new StringBuffer("");
405
406                 private Log4jPrintWriter(Logger logger, org.apache.log4j.Priority level) {
407                         super(System.err);
408                         this.level = level;
409                         this.logger = logger;
410                 }
411
412                 public void close() {
413                         flush();
414                 }
415
416                 public void flush() {
417                         if (!text.toString().equals("")) {
418                                 logger.log(level, text.toString());
419                                 text.setLength(0);
420                         }
421                 }
422
423                 public void print(boolean b) {
424                         text.append(b);
425                 }
426
427                 public void print(char c) {
428                         text.append(c);
429                 }
430
431                 public void print(char[] s) {
432                         text.append(s);
433                 }
434
435                 public void print(double d) {
436                         text.append(d);
437                 }
438
439                 public void print(float f) {
440                         text.append(f);
441                 }
442
443                 public void print(int i) {
444                         text.append(i);
445                 }
446
447                 public void print(long l) {
448                         text.append(l);
449                 }
450
451                 public void print(Object obj) {
452                         text.append(obj);
453                 }
454
455                 public void print(String s) {
456                         text.append(s);
457                 }
458
459                 public void println() {
460                         if (!text.toString().equals("")) {
461                                 logger.log(level, text.toString());
462                                 text.setLength(0);
463                         }
464                 }
465
466                 public void println(boolean x) {
467                         text.append(x);
468                         logger.log(level, text.toString());
469                         text.setLength(0);
470                 }
471
472                 public void println(char x) {
473                         text.append(x);
474                         logger.log(level, text.toString());
475                         text.setLength(0);
476                 }
477
478                 public void println(char[] x) {
479                         text.append(x);
480                         logger.log(level, text.toString());
481                         text.setLength(0);
482                 }
483
484                 public void println(double x) {
485                         text.append(x);
486                         logger.log(level, text.toString());
487                         text.setLength(0);
488                 }
489
490                 public void println(float x) {
491                         text.append(x);
492                         logger.log(level, text.toString());
493                         text.setLength(0);
494                 }
495
496                 public void println(int x) {
497                         text.append(x);
498                         logger.log(level, text.toString());
499                         text.setLength(0);
500                 }
501
502                 public void println(long x) {
503                         text.append(x);
504                         logger.log(level, text.toString());
505                         text.setLength(0);
506                 }
507
508                 public void println(Object x) {
509                         text.append(x);
510                         logger.log(level, text.toString());
511                         text.setLength(0);
512                 }
513
514                 public void println(String x) {
515                         text.append(x);
516                         logger.log(level, text.toString());
517                         text.setLength(0);
518                 }
519         }
520 }
521
522 /**
523  * The default attribute extractor. 
524  */
525 class DefaultAE implements JDBCAttributeExtractor {
526
527         private static Logger log = Logger.getLogger(DefaultAE.class.getName());
528
529         public BasicAttributes extractAttributes(ResultSet rs, int minResultSet, int maxResultSet) throws JDBCAttributeExtractorException {
530                 BasicAttributes attributes = new BasicAttributes();
531         int row = 0;
532         
533                 try {
534             // Get metadata about result set.
535             ResultSetMetaData rsmd = rs.getMetaData();
536             int numColumns = rsmd.getColumnCount();
537             log.debug("Number of returned columns: " + numColumns);
538
539             do {
540                 if (maxResultSet > 0 && row + 1 > maxResultSet) {
541                     log.error("Statement returned too many rows, violating maxResultSet of " + maxResultSet);
542                     throw new JDBCAttributeExtractorException("Statement returned too many rows, violating maxResultSet of " + maxResultSet);
543                 }
544
545                 for (int i = 1; i <= numColumns; i++) {
546                     String columnName = rsmd.getColumnName(i);
547                     Object columnValue = rs.getObject(columnName);
548                     if (log.isDebugEnabled()) {
549                         log.debug(
550                             "("
551                                 + i
552                                 + ". ColumnType = " + rsmd.getColumnTypeName(i)
553                                 + ") "
554                                 + columnName
555                                 + " -> "
556                                 + (columnValue != null ? columnValue.toString() : "(null)"));
557                     }
558                     if (row == 0) {
559                         BasicAttribute ba = new BasicAttribute(columnName, true);
560                         ba.add(row,columnValue);
561                         attributes.put(ba);
562                     }
563                     else {
564                         attributes.get(columnName).add(row,columnValue);
565                     }
566                 }
567                 row++;
568             } while (rs.next());
569                 } catch (SQLException e) {
570                         log.error("An ERROR occured while processing result set");
571                         throw new JDBCAttributeExtractorException(
572                                 "An ERROR occured while processing result set: " + e.getMessage());
573                 }
574
575         if (row < minResultSet) {
576             log.error("Statement returned " + row + " rows, violating minResultSet of " + minResultSet);
577             throw new JDBCAttributeExtractorException("Statement returned " + row + " rows, violating minResultSet of " + minResultSet);
578         }
579                 return attributes;
580         }
581 }
582
583 class DefaultStatementCreator implements JDBCStatementCreator {
584
585         private static Logger log = Logger.getLogger(DefaultStatementCreator.class.getName());
586
587         public void create(
588                 PreparedStatement preparedStatement,
589                 Principal principal,
590                 String requester,
591                 Dependencies depends)
592                 throws JDBCStatementCreatorException {
593
594                 try {
595                         log.debug("Creating prepared statement.  Substituting principal: (" + principal.getName() + ")");
596                         preparedStatement.setString(1, principal.getName());
597             //Tried using ParameterMetaData to determine param count, but it fails, so...
598             try {
599                 int i=2;
600                 while (true) {
601                     preparedStatement.setString(i++, principal.getName());
602                 }
603             } catch (SQLException e) {
604                 //Ignore any additional exceptions, assume parameters simply don't exist.
605             }
606                 } catch (SQLException e) {
607                         log.error("Encountered an error while creating prepared statement: " + e);
608                         throw new JDBCStatementCreatorException(
609                                 "Encountered an error while creating prepared statement: " + e.getMessage());
610                 }
611         }
612 }
613
614 class DependencyStatementCreator implements JDBCStatementCreator {
615
616         private static Logger log = Logger.getLogger(DependencyStatementCreator.class.getName());
617         private ArrayList parameters = new ArrayList();
618
619         public DependencyStatementCreator(Element conf) throws JDBCStatementCreatorException {
620
621                 NodeList nodes = conf.getElementsByTagName("Parameter");
622                 for (int i = 0; i < nodes.getLength(); i++) {
623                         Element parameter = (Element) nodes.item(i);
624                         String type = "String";
625                         if (parameter.getAttribute("type") != null && (!parameter.getAttribute("type").equals(""))) {
626                                 type = parameter.getAttribute("type");
627                         }
628
629                         if (parameter.getAttribute("attributeName") == null
630                                 || parameter.getAttribute("attributeName").equals("")) {
631                                 log.error("Statement Creator Parameter must reference an attribute by name.");
632                                 throw new JDBCStatementCreatorException("Statement Creator Parameter must reference an attribute by name.");
633                         }
634
635                         if (parameter.getAttribute("connectorId") != null && (!parameter.getAttribute("connectorId").equals(""))) {
636                                 parameters.add(
637                                         new Parameter(
638                                                 type,
639                                                 parameter.getAttribute("attributeName"),
640                                                 parameter.getAttribute("connectorId")));
641                         } else {
642                                 parameters.add(new Parameter(type, parameter.getAttribute("attributeName")));
643
644                         }
645
646                         if (parameter.getAttribute("nullMissing") != null && (!parameter.getAttribute("nullMissing").equals(""))) {
647                                 if (parameter.getAttribute("nullMissing").equalsIgnoreCase("FALSE")) {
648                                         ((Parameter) parameters.get(i)).setNullMissing(false);
649                                 }
650                         }
651                 }
652                 log.debug("Parameters configured: " + parameters.size());
653         }
654
655         public void create(
656                 PreparedStatement preparedStatement,
657                 Principal principal,
658                 String requester,
659                 Dependencies depends)
660                 throws JDBCStatementCreatorException {
661
662                 try {
663                         log.debug("Creating prepared statement.  Substituting values from dependencies.");
664                         for (int i = 0; i < parameters.size(); i++) {
665                                 ((Parameter) parameters.get(i)).setParameterValue(preparedStatement, i + 1, depends);
666                         }
667
668                 } catch (Exception e) {
669                         log.error("Encountered an error while creating prepared statement (principal=" + principal.getName() + "): " + e);
670                         throw new JDBCStatementCreatorException(
671                                 "Encountered an error while creating prepared statement: " + e.getMessage());
672                 }
673         }
674
675         protected class Parameter {
676                 private String type;
677                 private String attributeName;
678                 private boolean referencesConnector = false;
679                 private String connectorId;
680                 private boolean nullMissing = true;
681
682                 protected Parameter(String type, String attributeName) throws JDBCStatementCreatorException {
683                         if ((!type.equalsIgnoreCase("String"))
684                                 && (!type.equalsIgnoreCase("Integer"))
685                                 && (!type.equalsIgnoreCase("Byte"))
686                                 && (!type.equalsIgnoreCase("Double"))
687                                 && (!type.equalsIgnoreCase("Float"))
688                                 && (!type.equalsIgnoreCase("Long"))
689                                 && (!type.equalsIgnoreCase("Short"))
690                                 && (!type.equalsIgnoreCase("Boolean"))
691                                 && (!type.equalsIgnoreCase("Date"))
692                                 && (!type.equalsIgnoreCase("Blob"))
693                                 && (!type.equalsIgnoreCase("Clob"))) {
694                                 log.error("Unsupported type configured for Statement Creator Parameter.");
695                                 throw new JDBCStatementCreatorException("Unsupported type on Statement Creator Parameter.");
696                         }
697                         this.type = type;
698                         this.attributeName = attributeName;
699                 }
700
701                 protected Parameter(String type, String attributeName, String connectorId)
702                         throws JDBCStatementCreatorException {
703                         this(type, attributeName);
704                         referencesConnector = true;
705                         this.connectorId = connectorId;
706
707                 }
708         
709         protected int getSQLType() {
710             if (type.equalsIgnoreCase("String")) {
711                 return Types.VARCHAR;
712             } else if (type.equalsIgnoreCase("Integer")) {
713                 return Types.INTEGER;
714             } else if (type.equalsIgnoreCase("Byte")) {
715                 return Types.TINYINT;
716             } else if (type.equalsIgnoreCase("Double")) {
717                 return Types.DOUBLE;
718             } else if (type.equalsIgnoreCase("Float")) {
719                 return Types.FLOAT;
720             } else if (type.equalsIgnoreCase("Long")) {
721                 return Types.INTEGER;
722             } else if (type.equalsIgnoreCase("Short")) {
723                 return Types.SMALLINT;
724             } else if (type.equalsIgnoreCase("Boolean")) {
725                 return Types.BOOLEAN;
726             } else if (type.equalsIgnoreCase("Date")) {
727                 return Types.DATE;
728             } else if (type.equalsIgnoreCase("Blob")) {
729                 return Types.BLOB;
730             } else if (type.equalsIgnoreCase("Clob")) {
731                 return Types.CLOB;
732             } else {
733                 return Types.VARCHAR;
734             }
735         }
736
737                 protected void setParameterValue(PreparedStatement preparedStatement, int valueIndex, Dependencies depends)
738                         throws JDBCStatementCreatorException {
739
740                         //handle values from DataConnectors
741                         if (referencesConnector) {
742                                 Attributes attributes = depends.getConnectorResolution(connectorId);
743                                 if (attributes == null) {
744                                         log.error(
745                                                 "Statement Creator misconfiguration: Connector ("
746                                                         + connectorId
747                                                         + ") is not a dependency of this JDBCDataConnector.");
748                                         throw new JDBCStatementCreatorException(
749                                                 "Statement Creator misconfiguration: Connector ("
750                                                         + connectorId
751                                                         + ") is not a dependency of this JDBCDataConnector.");
752                                 }
753
754                                 Attribute attribute = attributes.get(attributeName);
755                                 if (attribute == null || attribute.size() < 1) {
756                                         if (nullMissing) {
757                                                 try {
758                                                         preparedStatement.setNull(valueIndex, getSQLType());
759                                                         return;
760                                                 } catch (SQLException e) {
761                                                         log.error(
762                                                                 "Encountered a problem while attempting to convert missing attribute value to null parameter.");
763                                                 }
764                                         }
765                                         log.error("Cannot parameterize prepared statement: missing dependency value.");
766                                         throw new JDBCStatementCreatorException("Cannot parameterize prepared statement: missing dependency value.");
767                                 }
768
769                                 if (attribute.size() > 1) {
770                                         log.error("Statement Creator encountered a multivalued dependent attribute.");
771                                         throw new JDBCStatementCreatorException("Statement Creator encountered a multivalued dependent attribute.");
772                                 }
773
774                                 try {
775                                         setSpecificParameter(preparedStatement, valueIndex, attribute.get());
776                                         return;
777                                 } catch (NamingException e) {
778                                         log.error(
779                                                 "Statement Creator encountered an error while extracting attributes from a Data Conector: "
780                                                         + e);
781                                         throw new JDBCStatementCreatorException(
782                                                 "Statement Creator encountered an error while extracting attributes from a Data Conector: "
783                                                         + e.getMessage());
784                                 }
785                         }
786
787                         //handle values from AttributeDefinitons
788                         ResolverAttribute attribute = depends.getAttributeResolution(attributeName);
789                         if (attribute != null) {
790                                 Iterator iterator = attribute.getValues();
791                                 if (iterator.hasNext()) {
792                                         setSpecificParameter(preparedStatement, valueIndex, iterator.next());
793                                         if (iterator.hasNext()) {
794                                                 log.error("Statement Creator encountered a multivalued dependent attribute.");
795                                                 throw new JDBCStatementCreatorException("Statement Creator encountered a multivalued dependent attribute.");
796                                         }
797                                         return;
798                                 }
799                         }
800                         if (nullMissing) {
801                                 try {
802                                         preparedStatement.setNull(valueIndex, getSQLType());
803                                         return;
804                                 } catch (SQLException e) {
805                                         log.error(
806                                                 "Encountered a problem while attempting to convert missing attribute value to null parameter.");
807                                 }
808                         }
809                         log.error("Cannot parameterize prepared statement: missing dependency value.");
810                         throw new JDBCStatementCreatorException("Cannot parameterize prepared statement: missing dependency value.");
811                 }
812
813                 protected void setNullMissing(boolean nullMissing) {
814                         this.nullMissing = nullMissing;
815                 }
816
817                 private void setSpecificParameter(PreparedStatement preparedStatement, int valueIndex, Object object)
818                         throws JDBCStatementCreatorException {
819
820                         if (object == null) {
821                                 try {
822                                         preparedStatement.setNull(valueIndex, getSQLType());
823                                         return;
824                                 } catch (SQLException e) {
825                                         log.error(
826                                                 "Encountered a problem while attempting to convert missing attribute value to null parameter.");
827                                         throw new JDBCStatementCreatorException("Encountered a problem while attempting to convert missing attribute value to null parameter.");
828                                 }
829                         } else if (type.equalsIgnoreCase("String")) {
830                                 setString(preparedStatement, valueIndex, object);
831                         } else if (type.equalsIgnoreCase("Integer")) {
832                                 setInteger(preparedStatement, valueIndex, object);
833                         } else if (type.equalsIgnoreCase("Byte")) {
834                                 setByte(preparedStatement, valueIndex, object);
835                         } else if (type.equalsIgnoreCase("Double")) {
836                                 setDouble(preparedStatement, valueIndex, object);
837                         } else if (type.equalsIgnoreCase("Float")) {
838                                 setFloat(preparedStatement, valueIndex, object);
839                         } else if (type.equalsIgnoreCase("Long")) {
840                                 setLong(preparedStatement, valueIndex, object);
841                         } else if (type.equalsIgnoreCase("Short")) {
842                                 setShort(preparedStatement, valueIndex, object);
843                         } else if (type.equalsIgnoreCase("Boolean")) {
844                                 setBoolean(preparedStatement, valueIndex, object);
845                         } else if (type.equalsIgnoreCase("Date")) {
846                                 setDate(preparedStatement, valueIndex, object);
847                         } else if (type.equalsIgnoreCase("Blob")) {
848                                 setBlob(preparedStatement, valueIndex, object);
849                         } else if (type.equalsIgnoreCase("Clob")) {
850                                 setClob(preparedStatement, valueIndex, object);
851                         } else {
852                                 setString(preparedStatement, valueIndex, object);
853                         }
854                 }
855
856                 private void setClob(PreparedStatement preparedStatement, int valueIndex, Object object)
857                         throws JDBCStatementCreatorException {
858                         if (object instanceof Clob) {
859                                 try {
860                                         preparedStatement.setClob(valueIndex, (Clob) object);
861                                         return;
862                                 } catch (SQLException e) {
863                                         log.error("Encountered an error while adding parameter to prepared statement: " + e);
864                                         throw new JDBCStatementCreatorException(
865                                                 "Encountered an error while adding parameter to prepared statement: " + e.getMessage());
866                                 }
867                         }
868                         log.error("Encountered a dependency with an invalid java type.");
869                         throw new JDBCStatementCreatorException("Encountered a dependency with an invalid java type.");
870                 }
871
872                 private void setBlob(PreparedStatement preparedStatement, int valueIndex, Object object)
873                         throws JDBCStatementCreatorException {
874                         if (object instanceof Blob) {
875                                 try {
876                                         preparedStatement.setBlob(valueIndex, (Blob) object);
877                                         return;
878                                 } catch (SQLException e) {
879                                         log.error("Encountered an error while adding parameter to prepared statement: " + e);
880                                         throw new JDBCStatementCreatorException(
881                                                 "Encountered an error while adding parameter to prepared statement: " + e.getMessage());
882                                 }
883                         }
884                         log.error("Encountered a dependency with an invalid java type.");
885                         throw new JDBCStatementCreatorException("Encountered a dependency with an invalid java type.");
886                 }
887
888                 private void setDate(PreparedStatement preparedStatement, int valueIndex, Object object)
889                         throws JDBCStatementCreatorException {
890
891                         if (object instanceof java.sql.Date) {
892                                 try {
893                                         preparedStatement.setDate(valueIndex, (java.sql.Date) object);
894                                         return;
895                                 } catch (SQLException e) {
896                                         log.error("Encountered an error while adding parameter to prepared statement: " + e);
897                                         throw new JDBCStatementCreatorException(
898                                                 "Encountered an error while adding parameter to prepared statement: " + e.getMessage());
899                                 }
900                         } else if (object instanceof java.util.Date) {
901                                 try {
902                                         //If you want to be frustrated by the java class library, look no further...
903                                         preparedStatement.setDate(valueIndex, new java.sql.Date(((java.util.Date) object).getTime()));
904                                         return;
905                                 } catch (SQLException e) {
906                                         log.error("Encountered an error while adding parameter to prepared statement: " + e);
907                                         throw new JDBCStatementCreatorException(
908                                                 "Encountered an error while adding parameter to prepared statement: " + e.getMessage());
909                                 }
910                         } else if (object instanceof Long) {
911                                 try {
912                                         preparedStatement.setDate(valueIndex, new java.sql.Date(((Long) object).longValue()));
913                                         return;
914                                 } catch (SQLException e) {
915                                         log.error("Encountered an error while adding parameter to prepared statement: " + e);
916                                         throw new JDBCStatementCreatorException(
917                                                 "Encountered an error while adding parameter to prepared statement: " + e.getMessage());
918                                 }
919                         } else if (object instanceof String) {
920                                 try {
921                                         preparedStatement.setDate(
922                                                 valueIndex,
923                                                 new java.sql.Date(new SimpleDateFormat().parse((String) object).getTime()));
924                                         return;
925                                 } catch (Exception e) {
926                                         log.error("Encountered an error while adding parameter to prepared statement: " + e);
927                                         throw new JDBCStatementCreatorException(
928                                                 "Encountered an error while adding parameter to prepared statement: " + e.getMessage());
929                                 }
930                         }
931                         log.error("Encountered a dependency with an invalid java type.");
932                         throw new JDBCStatementCreatorException("Encountered a dependency with an invalid java type.");
933                 }
934
935                 private void setBoolean(PreparedStatement preparedStatement, int valueIndex, Object object)
936                         throws JDBCStatementCreatorException {
937                         if (object instanceof Boolean) {
938                                 try {
939                                         preparedStatement.setBoolean(valueIndex, ((Boolean) object).booleanValue());
940                                         return;
941                                 } catch (SQLException e) {
942                                         log.error("Encountered an error while adding parameter to prepared statement: " + e);
943                                         throw new JDBCStatementCreatorException(
944                                                 "Encountered an error while adding parameter to prepared statement: " + e.getMessage());
945                                 }
946                         } else if (object instanceof String) {
947                                 try {
948                                         preparedStatement.setBoolean(valueIndex, new Boolean((String) object).booleanValue());
949                                         return;
950                                 } catch (Exception e) {
951                                         log.error("Encountered an error while adding parameter to prepared statement: " + e);
952                                         throw new JDBCStatementCreatorException(
953                                                 "Encountered an error while adding parameter to prepared statement: " + e.getMessage());
954                                 }
955                         }
956                         log.error("Encountered a dependency with an invalid java type.");
957                         throw new JDBCStatementCreatorException("Encountered a dependency with an invalid java type.");
958                 }
959
960                 private void setShort(PreparedStatement preparedStatement, int valueIndex, Object object)
961                         throws JDBCStatementCreatorException {
962                         if (object instanceof Boolean) {
963                                 try {
964                                         preparedStatement.setShort(valueIndex, ((Short) object).shortValue());
965                                         return;
966                                 } catch (SQLException e) {
967                                         log.error("Encountered an error while adding parameter to prepared statement: " + e);
968                                         throw new JDBCStatementCreatorException(
969                                                 "Encountered an error while adding parameter to prepared statement: " + e.getMessage());
970                                 }
971                         } else if (object instanceof String) {
972                                 try {
973                                         preparedStatement.setShort(valueIndex, new Short((String) object).shortValue());
974                                         return;
975                                 } catch (Exception e) {
976                                         log.error("Encountered an error while adding parameter to prepared statement: " + e);
977                                         throw new JDBCStatementCreatorException(
978                                                 "Encountered an error while adding parameter to prepared statement: " + e.getMessage());
979                                 }
980                         }
981                         log.error("Encountered a dependency with an invalid java type.");
982                         throw new JDBCStatementCreatorException("Encountered a dependency with an invalid java type.");
983                 }
984
985                 private void setLong(PreparedStatement preparedStatement, int valueIndex, Object object)
986                         throws JDBCStatementCreatorException {
987                         if (object instanceof Long || object instanceof Integer || object instanceof Short) {
988                                 try {
989                                         preparedStatement.setLong(valueIndex, ((Number) object).longValue());
990                                         return;
991                                 } catch (SQLException e) {
992                                         log.error("Encountered an error while adding parameter to prepared statement: " + e);
993                                         throw new JDBCStatementCreatorException(
994                                                 "Encountered an error while adding parameter to prepared statement: " + e.getMessage());
995                                 }
996                         } else if (object instanceof String) {
997                                 try {
998                                         preparedStatement.setLong(valueIndex, new Long((String) object).longValue());
999                                         return;
1000                                 } catch (Exception e) {
1001                                         log.error("Encountered an error while adding parameter to prepared statement: " + e);
1002                                         throw new JDBCStatementCreatorException(
1003                                                 "Encountered an error while adding parameter to prepared statement: " + e.getMessage());
1004                                 }
1005                         }
1006                         log.error("Encountered a dependency with an invalid java type.");
1007                         throw new JDBCStatementCreatorException("Encountered a dependency with an invalid java type.");
1008                 }
1009
1010                 private void setFloat(PreparedStatement preparedStatement, int valueIndex, Object object)
1011                         throws JDBCStatementCreatorException {
1012                         if (object instanceof Float) {
1013                                 try {
1014                                         preparedStatement.setFloat(valueIndex, ((Float) object).floatValue());
1015                                         return;
1016                                 } catch (SQLException e) {
1017                                         log.error("Encountered an error while adding parameter to prepared statement: " + e);
1018                                         throw new JDBCStatementCreatorException(
1019                                                 "Encountered an error while adding parameter to prepared statement: " + e.getMessage());
1020                                 }
1021                         } else if (object instanceof String) {
1022                                 try {
1023                                         preparedStatement.setFloat(valueIndex, new Float((String) object).floatValue());
1024                                         return;
1025                                 } catch (Exception e) {
1026                                         log.error("Encountered an error while adding parameter to prepared statement: " + e);
1027                                         throw new JDBCStatementCreatorException(
1028                                                 "Encountered an error while adding parameter to prepared statement: " + e.getMessage());
1029                                 }
1030                         }
1031                         log.error("Encountered a dependency with an invalid java type.");
1032                         throw new JDBCStatementCreatorException("Encountered a dependency with an invalid java type.");
1033                 }
1034
1035                 private void setDouble(PreparedStatement preparedStatement, int valueIndex, Object object)
1036                         throws JDBCStatementCreatorException {
1037                         if (object instanceof Double || object instanceof Float) {
1038                                 try {
1039                                         preparedStatement.setDouble(valueIndex, ((Number) object).doubleValue());
1040                                         return;
1041                                 } catch (SQLException e) {
1042                                         log.error("Encountered an error while adding parameter to prepared statement: " + e);
1043                                         throw new JDBCStatementCreatorException(
1044                                                 "Encountered an error while adding parameter to prepared statement: " + e.getMessage());
1045                                 }
1046                         } else if (object instanceof String) {
1047                                 try {
1048                                         preparedStatement.setDouble(valueIndex, new Double((String) object).doubleValue());
1049                                         return;
1050                                 } catch (Exception e) {
1051                                         log.error("Encountered an error while adding parameter to prepared statement: " + e);
1052                                         throw new JDBCStatementCreatorException(
1053                                                 "Encountered an error while adding parameter to prepared statement: " + e.getMessage());
1054                                 }
1055                         }
1056                         log.error("Encountered a dependency with an invalid java type.");
1057                         throw new JDBCStatementCreatorException("Encountered a dependency with an invalid java type.");
1058                 }
1059
1060                 private void setByte(PreparedStatement preparedStatement, int valueIndex, Object object)
1061                         throws JDBCStatementCreatorException {
1062                         if (object instanceof Byte) {
1063                                 try {
1064                                         preparedStatement.setByte(valueIndex, ((Byte) object).byteValue());
1065                                         return;
1066                                 } catch (SQLException e) {
1067                                         log.error("Encountered an error while adding parameter to prepared statement: " + e);
1068                                         throw new JDBCStatementCreatorException(
1069                                                 "Encountered an error while adding parameter to prepared statement: " + e.getMessage());
1070                                 }
1071                         } else if (object instanceof String) {
1072                                 try {
1073                                         preparedStatement.setByte(valueIndex, new Byte((String) object).byteValue());
1074                                         return;
1075                                 } catch (Exception e) {
1076                                         log.error("Encountered an error while adding parameter to prepared statement: " + e);
1077                                         throw new JDBCStatementCreatorException(
1078                                                 "Encountered an error while adding parameter to prepared statement: " + e.getMessage());
1079                                 }
1080                         }
1081                         log.error("Encountered a dependency with an invalid java type.");
1082                         throw new JDBCStatementCreatorException("Encountered a dependency with an invalid java type.");
1083                 }
1084
1085                 private void setInteger(PreparedStatement preparedStatement, int valueIndex, Object object)
1086                         throws JDBCStatementCreatorException {
1087                         if (object instanceof Integer || object instanceof Short) {
1088                                 try {
1089                                         preparedStatement.setInt(valueIndex, ((Number) object).intValue());
1090                                         return;
1091                                 } catch (SQLException e) {
1092                                         log.error("Encountered an error while adding parameter to prepared statement: " + e);
1093                                         throw new JDBCStatementCreatorException(
1094                                                 "Encountered an error while adding parameter to prepared statement: " + e.getMessage());
1095                                 }
1096                         } else if (object instanceof String) {
1097                                 try {
1098                                         preparedStatement.setInt(valueIndex, new Integer((String) object).intValue());
1099                                         return;
1100                                 } catch (Exception e) {
1101                                         log.error("Encountered an error while adding parameter to prepared statement: " + e);
1102                                         throw new JDBCStatementCreatorException(
1103                                                 "Encountered an error while adding parameter to prepared statement: " + e.getMessage());
1104                                 }
1105                         }
1106                         log.error("Encountered a dependency with an invalid java type.");
1107                         throw new JDBCStatementCreatorException("Encountered a dependency with an invalid java type.");
1108                 }
1109
1110                 private void setString(PreparedStatement preparedStatement, int valueIndex, Object object)
1111                         throws JDBCStatementCreatorException {
1112                         if (object instanceof String) {
1113                                 try {
1114                                         preparedStatement.setString(valueIndex, (String) object);
1115                                         return;
1116                                 } catch (SQLException e) {
1117                                         log.error("Encountered an error while adding parameter to prepared statement: " + e);
1118                                         throw new JDBCStatementCreatorException(
1119                                                 "Encountered an error while adding parameter to prepared statement: " + e.getMessage());
1120                                 }
1121                         }
1122                         log.error("Encountered a dependency with an invalid java type.");
1123                         throw new JDBCStatementCreatorException("Encountered a dependency with an invalid java type.");
1124                 }
1125         }
1126 }