1 /*
2  *  Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License, version 2.0,
6  *  as published by the Free Software Foundation.
7  *
8  *  This program is also distributed with certain software (including
9  *  but not limited to OpenSSL) that is licensed under separate terms,
10  *  as designated in a particular file or component or in included license
11  *  documentation.  The authors of MySQL hereby grant you an additional
12  *  permission to link the program and your derivative works with the
13  *  separately licensed software that they have included with MySQL.
14  *
15  *  This program is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *  GNU General Public License, version 2.0, for more details.
19  *
20  *  You should have received a copy of the GNU General Public License
21  *  along with this program; if not, write to the Free Software
22  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
23  */
24 
25 package com.mysql.clusterj.jdbc;
26 
27 import com.mysql.clusterj.ClusterJHelper;
28 import com.mysql.clusterj.ClusterJUserException;
29 import com.mysql.clusterj.SessionFactory;
30 import com.mysql.clusterj.core.query.QueryDomainTypeImpl;
31 import com.mysql.clusterj.core.spi.SessionSPI;
32 import com.mysql.clusterj.core.store.Dictionary;
33 import com.mysql.clusterj.core.util.I18NHelper;
34 import com.mysql.clusterj.core.util.Logger;
35 import com.mysql.clusterj.core.util.LoggerFactoryService;
36 import com.mysql.jdbc.Connection;
37 import com.mysql.jdbc.ResultSetInternalMethods;
38 import com.mysql.jdbc.Statement;
39 import com.mysql.clusterj.jdbc.antlr.ANTLRNoCaseStringStream;
40 import com.mysql.clusterj.jdbc.antlr.MySQL51Parser;
41 import com.mysql.clusterj.jdbc.antlr.MySQL51Lexer;
42 import com.mysql.clusterj.jdbc.antlr.QueuingErrorListener;
43 import com.mysql.clusterj.jdbc.antlr.node.Node;
44 import com.mysql.clusterj.jdbc.antlr.node.PlaceholderNode;
45 import com.mysql.clusterj.jdbc.antlr.node.SelectNode;
46 import com.mysql.clusterj.jdbc.antlr.node.WhereNode;
47 import com.mysql.clusterj.query.Predicate;
48 
49 import com.mysql.clusterj.jdbc.SQLExecutor.Executor;
50 import java.sql.SQLException;
51 import java.sql.Savepoint;
52 import java.util.ArrayList;
53 import java.util.IdentityHashMap;
54 import java.util.List;
55 import java.util.Map;
56 import java.util.Properties;
57 
58 import org.antlr.runtime.CommonTokenStream;
59 import org.antlr.runtime.RecognitionException;
60 import org.antlr.runtime.Token;
61 import org.antlr.runtime.TokenStream;
62 import org.antlr.runtime.tree.CommonErrorNode;
63 import org.antlr.runtime.tree.CommonTree;
64 import org.antlr.runtime.tree.CommonTreeAdaptor;
65 import org.antlr.runtime.tree.TreeAdaptor;
66 
67 /** This class implements the behavior associated with connection callbacks for statement execution
68  * and connection lifecycle. There is a clusterj session associated with the interceptor that
69  * is used to interact with the cluster. There is exactly one statement interceptor and one
70  * connection lifecycle interceptor associated with the interceptor.
71  * All of the SQL post-parsing behavior is contained here, and uses classes in the org.antlr.runtime and
72  * com.mysql.clusterj.jdbc.antlr packages to perform the parsing of the SQL statement. Analysis
73  * of the parsed SQL statement occurs here, and clusterj artifacts are constructed for use in
74  * other classes, in particular SQLExecutor and its command-specific subclasses.
75  */
76 public class InterceptorImpl {
77 
78     /** Register logger for JDBC stuff. */
79     static {
80         LoggerFactoryService.getFactory().registerLogger("com.mysql.clusterj.jdbc");
81     }
82 
83     /** My message translator */
84     static final I18NHelper local = I18NHelper.getInstance(InterceptorImpl.class);
85 
86     /** My logger */
87     static final Logger logger = LoggerFactoryService.getFactory().getInstance(InterceptorImpl.class);
88 
89     static Map<String, Executor> parsedSqlMap = new IdentityHashMap<String, Executor>();
90 
91     /** The map of connection to interceptor */
92     private static Map<Connection, InterceptorImpl> interceptorImplMap =
93             new IdentityHashMap<Connection, InterceptorImpl>();
94 
95     /** The connection properties */
96     private Properties properties;
97 
98     /** The connection being intercepted */
99     private Connection connection;
100 
101     /** The session factory for this connection */
102     SessionFactory sessionFactory;
103 
104     /** The current session (null if no session) */
105     private SessionSPI session;
106 
107     /** The statement interceptor (only used during initialization) */
108     private StatementInterceptor statementInterceptor;
109 
110     /** The connection lifecycle interceptor (only used during initialization) */
111     private ConnectionLifecycleInterceptor connectionLifecycleInterceptor;
112 
113     /** The interceptor is ready (both interceptors are registered) */
114     private boolean ready = false;
115 
116     private boolean autocommit;
117 
118     private static String LOTSOBLANKS = "                                                                          ";
119 
120     /** Create the interceptor.
121      *
122      * @param connection the connection being intercepted
123      * @param properties the connection properties
124      */
InterceptorImpl(Connection connection, Properties properties)125     public InterceptorImpl(Connection connection, Properties properties) {
126         if (logger.isDebugEnabled()) logger.debug("constructed with properties: " + properties);
127         this.properties = properties;
128         this.connection = connection;
129         // if database name is not specified, translate DBNAME to the required clusterj property
130         String dbname = properties.getProperty("com.mysql.clusterj.database",
131                 properties.getProperty("DBNAME"));
132         properties.put("com.mysql.clusterj.database", dbname);
133     }
134 
135     /** Return the interceptor for the connection lifecycle callbacks.
136      *
137      * @param connectionLifecycleInterceptor the connection lifecycle interceptor
138      * @param connection the connection
139      * @param properties the connection properties
140      * @return the interceptor delegate
141      */
getInterceptorImpl( ConnectionLifecycleInterceptor connectionLifecycleInterceptor, Connection connection, Properties properties)142     public static InterceptorImpl getInterceptorImpl(
143             ConnectionLifecycleInterceptor connectionLifecycleInterceptor,
144             Connection connection, Properties properties) {
145         InterceptorImpl result = getInterceptorImpl(connection, properties);
146         if (result.connectionLifecycleInterceptor != null) {
147             if (result.connectionLifecycleInterceptor != connectionLifecycleInterceptor) {
148                 throw new ClusterJUserException(
149                         local.message("ERR_Duplicate_Connection_Lifecycle_Interceptor"));
150             }
151         } else {
152             result.connectionLifecycleInterceptor = connectionLifecycleInterceptor;
153         }
154         if (result.statementInterceptor != null) {
155             result.ready = true;
156         }
157         return result;
158     }
159 
160     /** Return the interceptor for the statement interceptor callbacks.
161      *
162      * @param statementInterceptor the statement interceptor
163      * @param connection the connection
164      * @param properties the connection properties
165      * @return the interceptor delegate
166      */
getInterceptorImpl( StatementInterceptor statementInterceptor, Connection connection, Properties properties)167     public static InterceptorImpl getInterceptorImpl(
168             StatementInterceptor statementInterceptor, Connection connection,
169             Properties properties) {
170         InterceptorImpl result = getInterceptorImpl(connection, properties);
171         if (result.statementInterceptor != null) {
172             throw new ClusterJUserException(
173                     local.message("ERR_Duplicate_Statement_Interceptor"));
174         }
175         result.statementInterceptor = statementInterceptor;
176         if (result.connectionLifecycleInterceptor != null) {
177             result.ready = true;
178         }
179         return result;
180     }
181 
182     /** Create the interceptor to handle both connection lifecycle and statement interceptors.
183      *
184      * @param connection the connection
185      * @param properties the connection properties
186      * @return
187      */
getInterceptorImpl(Connection connection, Properties properties)188     public static InterceptorImpl getInterceptorImpl(Connection connection, Properties properties) {
189         InterceptorImpl result;
190         synchronized(interceptorImplMap) {
191             result = interceptorImplMap.get(connection);
192             if (result == null) {
193                 result = new InterceptorImpl(connection, properties);
194                 interceptorImplMap.put(connection, result);
195             }
196         }
197         return result;
198     }
199 
200     /** Return the interceptor assigned to the connection. If there is no interceptor, return null.
201      *
202      * @param connection the connection
203      * @return the interceptor for this connection or null if there is no interceptor
204      */
getInterceptorImpl(java.sql.Connection connection)205     public static InterceptorImpl getInterceptorImpl(java.sql.Connection connection) {
206         synchronized (interceptorImplMap) {
207             return interceptorImplMap.get(connection);
208         }
209     }
210 
211     @Override
toString()212     public String toString() {
213         return "InterceptorImpl "
214 //        + " properties: "+ properties.toString()
215         ;
216     }
217 
destroy()218     void destroy() {
219         if (sessionFactory != null) {
220             if (session != null) {
221                 session.close();
222             }
223             sessionFactory.close();
224             sessionFactory = null;
225             synchronized(interceptorImplMap) {
226                 interceptorImplMap.remove(connection);
227             }
228         }
229     }
230 
getSession()231     public SessionSPI getSession() {
232         if (session == null) {
233             session = (SessionSPI)sessionFactory.getSession();
234         }
235         return session;
236     }
237 
executeTopLevelOnly()238     public boolean executeTopLevelOnly() {
239 //        assertReady();
240         boolean result = true;
241         return result;
242     }
243 
postProcess(String sql, Statement statement, ResultSetInternalMethods result, Connection connection, int arg4, boolean arg5, boolean arg6, SQLException sqlException)244     public ResultSetInternalMethods postProcess(String sql, Statement statement,
245             ResultSetInternalMethods result, Connection connection, int arg4,
246             boolean arg5, boolean arg6, SQLException sqlException) throws SQLException {
247         assertReady();
248         return null;
249     }
250 
preProcess(String sql, Statement statement, Connection connection)251     public ResultSetInternalMethods preProcess(String sql, Statement statement,
252             Connection connection) throws SQLException {
253         assertReady();
254         if (statement instanceof com.mysql.jdbc.PreparedStatement) {
255             com.mysql.jdbc.PreparedStatement preparedStatement =
256                 (com.mysql.jdbc.PreparedStatement)statement;
257             // key must be interned because we are using IdentityHashMap
258             String preparedSql = preparedStatement.getPreparedSql().intern();
259             // see if we have a parsed version of this query
260             Executor sQLExecutor = null;
261             synchronized(parsedSqlMap) {
262                 sQLExecutor = parsedSqlMap.get(preparedSql);
263             }
264             // if no cached SQLExecutor, create it, which might take some time
265             if (sQLExecutor == null) {
266                 sQLExecutor = createSQLExecutor(preparedSql);
267                 if (sQLExecutor != null) {
268                     // multiple thread might have created a SQLExecutor but it's ok
269                     synchronized(parsedSqlMap) {
270                         parsedSqlMap.put(preparedSql, sQLExecutor);
271                     }
272                 }
273             }
274             return sQLExecutor.execute(this, preparedStatement.getParameterBindings());
275         }
276         return null;
277     }
278 
279     /**
280      * @param preparedSql
281      */
createSQLExecutor(String preparedSql)282     private Executor createSQLExecutor(String preparedSql) {
283         if (logger.isDetailEnabled()) logger.detail(preparedSql);
284         Executor result = null;
285         // parse the sql
286         CommonTree root = parse(preparedSql);
287         // get the root of the tree
288         int tokenType = root.getType();
289         // perform command-specific actions
290         String tableName = "";
291         CommonTree tableNode;
292         WhereNode whereNode;
293         List<String> columnNames = new ArrayList<String>();
294         Dictionary dictionary;
295         DomainTypeHandlerImpl<?> domainTypeHandler;
296         QueryDomainTypeImpl<?> queryDomainType = null;
297         switch (tokenType) {
298             case MySQL51Parser.INSERT:
299                 tableNode = (CommonTree)root.getFirstChildWithType(MySQL51Parser.TABLE);
300                 tableName = getTableName(tableNode);
301                 getSession();
302                 dictionary = session.getDictionary();
303                 domainTypeHandler = getDomainTypeHandler(tableName, dictionary);
304                 CommonTree insertValuesNode = (CommonTree)root.getFirstChildWithType(MySQL51Parser.INSERT_VALUES);
305                 CommonTree columnsNode = (CommonTree)insertValuesNode.getFirstChildWithType(MySQL51Parser.COLUMNS);
306                 List<CommonTree> fields = columnsNode.getChildren();
307                 for (CommonTree field: fields) {
308                     columnNames.add(getColumnName(field));
309                 }
310                 if (logger.isDetailEnabled()) logger.detail(
311                         "StatementInterceptorImpl.preProcess parse result INSERT INTO " + tableName
312                         + " COLUMNS " + columnNames);
313                 result = new SQLExecutor.Insert(domainTypeHandler, columnNames);
314                 break;
315             case MySQL51Parser.SELECT:
316                 CommonTree fromNode = (CommonTree)root.getFirstChildWithType(MySQL51Parser.FROM);
317                 if (fromNode == null) {
318                     // no from clause; cannot handle this case so return a do-nothing ParsedSQL
319                     result = new SQLExecutor.Noop();
320                     break;
321                 }
322                 try {
323                     // this currently handles only FROM clauses with a single table
324                     tableNode = (CommonTree) fromNode.getFirstChildWithType(MySQL51Parser.TABLE);
325                     tableName = getTableName(tableNode);
326                 } catch (Exception e) {
327                     // trouble with the FROM clause; log the SQL statement and the parser output
328                     logger.info("Problem with FROM clause in SQL statement: " + preparedSql);
329                     logger.info(walk(root));
330                     result = new SQLExecutor.Noop();
331                     break;
332                 }
333                 getSession();
334                 dictionary = session.getDictionary();
335                 domainTypeHandler = getDomainTypeHandler(tableName, dictionary);
336                 columnsNode = (CommonTree)root.getFirstChildWithType(MySQL51Parser.COLUMNS);
337                 List<CommonTree> selectExprNodes = columnsNode.getChildren();
338                 for (CommonTree selectExprNode: selectExprNodes) {
339                     columnNames.add(getColumnName(getFieldNode(selectExprNode)));
340                 }
341                 String whereType = "empty";
342                 if (logger.isDetailEnabled()) logger.detail(
343                         "SELECT FROM " + tableName
344                         + " COLUMNS " + columnNames);
345                 // we need to distinguish three cases:
346                 // - no where clause (select all rows)
347                 // - where clause that cannot be executed by clusterj
348                 // - where clause that can be executed by clusterj
349                 whereNode = ((SelectNode)root).getWhereNode();
350                 queryDomainType = (QueryDomainTypeImpl<?>) session.createQueryDomainType(domainTypeHandler);
351                 if (whereNode == null) {
352                     // no where clause (select all rows)
353                     result = new SQLExecutor.Select(domainTypeHandler, columnNames, queryDomainType);
354                 } else {
355                     // create a predicate from the tree
356                     Predicate predicate = whereNode.getPredicate(queryDomainType);
357                     if (predicate != null) {
358                         // where clause that can be executed by clusterj
359                         queryDomainType.where(predicate);
360                         result = new SQLExecutor.Select(domainTypeHandler, columnNames, queryDomainType);
361                         whereType = "clusterj";
362                     } else {
363                         // where clause that cannot be executed by clusterj
364                         result = new SQLExecutor.Noop();
365                         whereType = "non-clusterj";
366                     }
367                     if (logger.isDetailEnabled()) logger.detail(walk(root));
368                 }
369                 if (logger.isDetailEnabled()) {
370                     logger.detail(
371                         "SELECT FROM " + tableName
372                         + " COLUMNS " + columnNames + " whereType " + whereType);
373                     logger.detail(walk(root));
374                 }
375                 break;
376             case MySQL51Parser.DELETE:
377                 tableNode = (CommonTree)root.getFirstChildWithType(MySQL51Parser.TABLE);
378                 tableName = getTableName(tableNode);
379                 getSession();
380                 dictionary = session.getDictionary();
381                 domainTypeHandler = getDomainTypeHandler(tableName, dictionary);
382                 whereNode = ((WhereNode)root.getFirstChildWithType(MySQL51Parser.WHERE));
383                 int numberOfParameters = 0;
384                 if (whereNode == null) {
385                     // no where clause (delete all rows)
386                     result = new SQLExecutor.Delete(domainTypeHandler);
387                     whereType = "empty";
388                 } else {
389                     // create a predicate from the tree
390                     queryDomainType = (QueryDomainTypeImpl<?>) session.createQueryDomainType(domainTypeHandler);
391                     Predicate predicate = whereNode.getPredicate(queryDomainType);
392                     if (predicate != null) {
393                         // where clause that can be executed by clusterj
394                         queryDomainType.where(predicate);
395                         numberOfParameters = whereNode.getNumberOfParameters();
396                         result = new SQLExecutor.Delete(domainTypeHandler, queryDomainType, numberOfParameters);
397                         whereType = "clusterj";
398                     } else {
399                         // where clause that cannot be executed by clusterj
400                         result = new SQLExecutor.Noop();
401                         whereType = "non-clusterj";
402                     }
403                     if (logger.isDetailEnabled()) logger.detail(walk(root));
404                 }
405                 if (logger.isDetailEnabled()) logger.detail(
406                         "DELETE FROM " + tableName
407                         + " whereType " + whereType
408                         + " number of parameters " + numberOfParameters);
409                 break;
410             default:
411                 // return a do-nothing ParsedSQL
412                 if (logger.isDetailEnabled()) logger.detail("ClusterJ cannot process this SQL statement: unsupported statement type.");
413                 result = new SQLExecutor.Noop();
414         }
415         return result;
416     }
417 
getPrimaryKeyFieldName(CommonTree whereNode)418     private String getPrimaryKeyFieldName(CommonTree whereNode) {
419         String result = null;
420         CommonTree operation = (CommonTree) whereNode.getChild(0);
421         if (MySQL51Parser.EQUALS == operation.getType()) {
422             result = operation.getChild(0).getChild(0).getText();
423         } else {
424             throw new ClusterJUserException("Cannot find primary key in WHERE clause.");
425         }
426         return result;
427     }
428 
walk(CommonTree tree)429     private String walk(CommonTree tree) {
430         StringBuilder buffer = new StringBuilder();
431         walk(tree, buffer, 0);
432         return buffer.toString();
433     }
434 
435     @SuppressWarnings("unchecked") // tree.getChildren()
walk(CommonTree tree, StringBuilder buffer, int level)436     private void walk(CommonTree tree, StringBuilder buffer, int level) {
437             String indent = LOTSOBLANKS.substring(0, level);
438             Token token = tree.token;
439             int tokenType = token.getType();
440             String tokenText = token.getText();
441             int childCount = tree.getChildCount();
442             int childIndex = tree.getChildIndex();
443             buffer.append('\n');
444             buffer.append(indent);
445             buffer.append(tokenText);
446             buffer.append(" class: ");
447             buffer.append(tree.getClass().getName());
448             buffer.append(" tokenType ");
449             buffer.append(tokenType);
450             buffer.append(" child count ");
451             buffer.append(childCount);
452             buffer.append(" child index ");
453             buffer.append(childIndex);
454             List<CommonTree> children = tree.getChildren();
455             if (children == null) {
456                 return;
457             }
458             for (CommonTree child: children) {
459                 walk(child, buffer, level + 2);
460             }
461     }
462 
parse(String preparedSql)463     private CommonTree parse(String preparedSql) {
464         CommonTree result = null;
465         ANTLRNoCaseStringStream inputStream = new ANTLRNoCaseStringStream(preparedSql);
466         MySQL51Lexer lexer = new MySQL51Lexer(inputStream);
467         CommonTokenStream tokens = new CommonTokenStream(lexer);
468         lexer.setErrorListener(new QueuingErrorListener(lexer));
469         tokens.getTokens();
470         if (lexer.getErrorListener().hasErrors()) {
471             logger.warn(local.message("ERR_Lexing_SQ",preparedSql));
472             return result;
473         }
474         PlaceholderNode.resetId();
475         MySQL51Parser parser = new MySQL51Parser(tokens);
476         parser.setTreeAdaptor(mySQLTreeAdaptor);
477         parser.setErrorListener(new QueuingErrorListener(parser));
478         try {
479             CommonTree stmtTree = (CommonTree) parser.statement().getTree();
480             result = stmtTree;
481         } catch (RecognitionException e) {
482             logger.warn(local.message("ERR_Parsing_SQL", preparedSql));
483         }
484         if (parser.getErrorListener().hasErrors()) {
485             logger.warn(local.message("ERR_Parsing_SQL", preparedSql));
486         }
487         return result;
488     }
489 
490     private TreeAdaptor mySQLTreeAdaptor = new CommonTreeAdaptor() {
491         public Object create(Token token) { return new Node(token); }
492         public Object dupNode(Object t) {
493             if ( t==null ) return null;
494             return create(((Node)t).token);
495         }
496     };
497 
getTableName(CommonTree tableNode)498     private String getTableName(CommonTree tableNode) {
499         return tableNode.getChild(0).getText();
500     }
501 
getColumnName(CommonTree fieldNode)502     private String getColumnName(CommonTree fieldNode) {
503         return fieldNode.getChild(0).getText();
504     }
505 
getFieldNode(CommonTree selectExprNode)506     private CommonTree getFieldNode(CommonTree selectExprNode) {
507         return (CommonTree)selectExprNode.getChild(0);
508     }
509 
destroy(StatementInterceptor statementInterceptor)510     public void destroy(StatementInterceptor statementInterceptor) {
511     }
512 
destroy( ConnectionLifecycleInterceptor connectionLifecycleInterceptor)513     public void destroy(
514             ConnectionLifecycleInterceptor connectionLifecycleInterceptor) {
515     }
516 
assertReady()517     private void assertReady() {
518         if (!ready) {
519             if (statementInterceptor == null) {
520                 throw new ClusterJUserException(local.message("ERR_No_Statement_Interceptor"));
521             }
522             if (connectionLifecycleInterceptor == null) {
523                 throw new ClusterJUserException(local.message("ERR_No_Connection_Lifecycle_Interceptor"));
524             }
525         } else {
526             if (sessionFactory == null) {
527                 sessionFactory = ClusterJHelper.getSessionFactory(properties);
528             }
529         }
530     }
531 
532     /** TODO This needs to be rewritten with a proper state machine. */
setAutoCommit(boolean autocommit)533     public boolean setAutoCommit(boolean autocommit) throws SQLException {
534         assertReady();
535         logStatus("setAutoCommit(" + autocommit + ")");
536         this.autocommit = autocommit;
537         getSession();
538         if (!autocommit) {
539             // start a transaction
540             if (!session.currentTransaction().isActive()) {
541                 session.begin();
542             }
543         } else {
544             // roll back the previous transaction if active
545             if (session.currentTransaction().isActive()) {
546                 session.rollback();
547             }
548         }
549         return true; // let the driver perform its own autocommit behavior
550     }
551 
close()552     public void close() {
553     }
554 
commit()555     public boolean commit() throws SQLException {
556         logStatus("commit");
557         if (session.currentTransaction().isActive()) {
558             session.commit();
559         } else {
560             System.out.println("WARNING: commit called when session.transaction is not active");
561         }
562         session.begin();
563         return true;
564     }
565 
rollback()566     public boolean rollback() throws SQLException {
567         logStatus("rollback");
568         session.rollback();
569         session.begin();
570         return true;
571     }
572 
rollback(Savepoint savepoint)573     public boolean rollback(Savepoint savepoint) throws SQLException {
574         logStatus("rollback(Savepoint)");
575         return true;
576     }
577 
setCatalog(String catalog)578     public boolean setCatalog(String catalog) throws SQLException {
579         if (logger.isDebugEnabled()) logger.debug("catalog: " + catalog);
580         return true;
581     }
582 
transactionCompleted()583     public boolean transactionCompleted() throws SQLException {
584         logStatus("transactionCompleted");
585         return true;
586     }
587 
transactionBegun()588     public boolean transactionBegun() throws SQLException {
589         logStatus("transactionBegun");
590         return true;
591     }
592 
getDomainTypeHandler(String tableName, Dictionary dictionary)593     private DomainTypeHandlerImpl<?> getDomainTypeHandler(String tableName, Dictionary dictionary) {
594         DomainTypeHandlerImpl<?> domainTypeHandler =
595             DomainTypeHandlerImpl.getDomainTypeHandler(tableName, dictionary);
596         return domainTypeHandler;
597     }
598 
logStatus(String s)599     private void logStatus(String s) throws SQLException {
600         if (logger.isDetailEnabled()) {
601             StringBuilder builder = new StringBuilder("In ");
602             builder.append(s);
603             builder.append(" with");
604             if (connection != null) {
605                 builder.append(" connection.getAutocommit: " + connection.getAutoCommit());
606             }
607             if (session != null) {
608                 builder.append(" session.isActive: " + session.currentTransaction().isActive());
609             }
610             builder.append('\n');
611             String message = builder.toString();
612             logger.detail(message);
613         }
614     }
615 
616 }
617