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