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