View Javadoc

1   /*
2    * $Id: JdbcMessageDispatcher.java 11728 2008-05-13 07:31:11Z dirk.olmes $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSource, Inc.  All rights reserved.  http://www.mulesource.com
5    *
6    * The software in this package is published under the terms of the CPAL v1.0
7    * license, a copy of which has been included with this distribution in the
8    * LICENSE.txt file.
9    */
10  
11  package org.mule.providers.jdbc;
12  
13  import org.mule.impl.MuleMessage;
14  import org.mule.providers.AbstractMessageDispatcher;
15  import org.mule.transaction.TransactionCoordination;
16  import org.mule.umo.UMOEvent;
17  import org.mule.umo.UMOMessage;
18  import org.mule.umo.UMOTransaction;
19  import org.mule.umo.endpoint.UMOImmutableEndpoint;
20  import org.mule.umo.provider.UMOMessageAdapter;
21  import org.mule.util.StringUtils;
22  
23  import java.sql.CallableStatement;
24  import java.sql.Connection;
25  import java.sql.Types;
26  import java.util.ArrayList;
27  import java.util.HashMap;
28  import java.util.List;
29  import java.util.Map;
30  
31  /**
32   * The Jdbc Message dispatcher is responsible for executing SQL queries against a
33   * database.
34   */
35  public class JdbcMessageDispatcher extends AbstractMessageDispatcher
36  {
37  
38      private JdbcConnector connector;
39      private static final String STORED_PROCEDURE_PREFIX = "{ ";
40      private static final String STORED_PROCEDURE_SUFFIX = " }";
41      private static final String IN = "in";
42      private static final String OUT = "out";
43      private static final String INOUT = "inout";
44  
45      private Map typesMap = new HashMap();
46  
47      public JdbcMessageDispatcher(UMOImmutableEndpoint endpoint)
48      {
49          super(endpoint);
50          this.connector = (JdbcConnector)endpoint.getConnector();
51          registerType("int", Types.INTEGER);
52          registerType("float", Types.FLOAT);
53          registerType("double", Types.DOUBLE);
54          registerType("string", Types.VARCHAR);
55      }
56  
57      protected int getType(String key)
58      {
59          Integer type = (Integer) typesMap.get(key);
60          return type != null ? type.intValue() : 0;
61      }
62  
63      protected void registerType(String type, int jdbcType)
64      {
65          typesMap.put(type, new Integer(jdbcType));
66      }
67  
68      /*
69       * (non-Javadoc)
70       * 
71       * @see org.mule.providers.AbstractMessageDispatcher#doDispose()
72       */
73      protected void doDispose()
74      {
75          // template method
76      }
77  
78      protected boolean isProcWithOutParams(Object[][] types)
79      {
80          for (int i = 0; i < types.length; i++)
81          {
82              String type = (String) types[i][2];
83              if (INOUT.equalsIgnoreCase(type) || OUT.equalsIgnoreCase(type))
84              {
85                  return true;
86              }
87          }
88          return false;
89      }
90  
91      protected UMOMessage executeWriteStatement(UMOEvent event, String writeStmt) throws Exception
92      {
93          List paramNames = new ArrayList();
94          writeStmt = connector.parseStatement(writeStmt, paramNames);
95  
96          Object[][] types = connector.getParamsTypes(paramNames);
97  
98          Object[] paramValues = connector.getParams(endpoint, paramNames, new MuleMessage(
99              event.getTransformedMessage()), this.endpoint.getEndpointURI().getAddress());
100 
101         UMOTransaction tx = TransactionCoordination.getInstance().getTransaction();
102         Connection con = null;
103         UMOMessage message = event.getMessage();
104         try
105         {
106             con = this.connector.getConnection();
107             boolean isCall = false;
108             if ("call".equalsIgnoreCase(writeStmt.substring(0, 4)))
109             {
110                 writeStmt = STORED_PROCEDURE_PREFIX + writeStmt + STORED_PROCEDURE_SUFFIX;
111                 isCall = true;
112             }
113 
114             if (isCall && isProcWithOutParams(types))
115             {
116                 CallableStatement statement = connector.getConnection().prepareCall(writeStmt);
117                 for (int i = 0; i < types.length; i++)
118                 {
119                     String dataType = (String) types[i][1];
120                     String type = (String) types[i][2];
121                     if (type == null || IN.equalsIgnoreCase(type))
122                     {
123                         statement.setObject(i + 1, paramValues[i]);
124                     }
125                     else
126                     {
127                         statement.registerOutParameter(i + 1, getType(dataType));
128                         if (INOUT.equalsIgnoreCase(type))
129                         {
130                             statement.setObject(i + 1, paramValues[i]);
131                         }
132                     }
133                 }
134                 statement.execute();
135                 Map result = new HashMap();
136                 for (int i = 0; i < types.length; i++)
137                 {
138                     Object name = types[i][0];
139                     String type = (String) types[i][2];
140                     if (INOUT.equalsIgnoreCase(type) || OUT.equalsIgnoreCase(type))
141                     {
142                         result.put(name, statement.getObject(i + 1));
143                     }
144                 }
145                 UMOMessageAdapter msgAdapter = this.connector.getMessageAdapter(result);
146                 message = new MuleMessage(msgAdapter);
147             }
148             else
149             {
150                 int nbRows = connector.createQueryRunner().update(con, writeStmt, paramValues);
151                 if (nbRows != 1)
152                 {
153                     logger.warn("Row count for write should be 1 and not " + nbRows);
154                 }
155             }
156             if (tx == null)
157             {
158                 JdbcUtils.commitAndClose(con);
159             }
160             logger.debug("Event dispatched succesfuly");
161         }
162         catch (Exception e)
163         {
164             logger.debug("Error dispatching event: " + e.getMessage(), e);
165             if (tx == null)
166             {
167                 JdbcUtils.rollbackAndClose(con);
168             }
169             throw e;
170         }
171         return message;
172     }
173     
174     protected String getStatement(UMOImmutableEndpoint endpoint)
175     {
176         String writeStmt = endpoint.getEndpointURI().getAddress();
177         String str;
178         if ((str = this.connector.getQuery(endpoint, writeStmt)) != null)
179         { 
180             writeStmt = str;
181         }
182         writeStmt = StringUtils.trimToEmpty(writeStmt);
183         if (StringUtils.isBlank(writeStmt))
184         {
185             throw new IllegalArgumentException("Missing statement");
186         }
187         
188         return writeStmt;
189     }
190     
191     protected boolean isWriteStatement(String writeStmt)
192     {
193         if (!"insert".equalsIgnoreCase(writeStmt.substring(0, 6))
194                         && !"update".equalsIgnoreCase(writeStmt.substring(0, 6))
195                         && !"delete".equalsIgnoreCase(writeStmt.substring(0, 6))
196                         && !"merge".equalsIgnoreCase(writeStmt.substring(0, 5))
197                         && !"call".equalsIgnoreCase(writeStmt.substring(0, 4)))
198         {
199             return false;
200         }
201         
202         return true;
203     }
204 
205     /*
206      * (non-Javadoc)
207      * 
208      * @see org.mule.providers.AbstractMessageDispatcher#doDispatch(org.mule.umo.UMOEvent)
209      */
210     protected void doDispatch(UMOEvent event) throws Exception
211     {
212         if (logger.isDebugEnabled())
213         {
214             logger.debug("Dispatch event: " + event);
215         }
216         
217         String writeStmt = getStatement(event.getEndpoint());
218         
219         if (!isWriteStatement(writeStmt))
220         {
221             throw new IllegalArgumentException(
222                 "Write statement should be an insert / update / delete / merge sql statement, or a stored-procedure call");
223         }
224         
225         this.executeWriteStatement(event, writeStmt);
226         
227     }
228 
229     /*
230      * (non-Javadoc)
231      * 
232      * @see org.mule.providers.AbstractMessageDispatcher#doSend(org.mule.umo.UMOEvent)
233      */
234     protected UMOMessage doSend(UMOEvent event) throws Exception
235     {
236         String statement = getStatement(event.getEndpoint());
237         
238         if (isWriteStatement(statement))
239         {
240             return executeWriteStatement(event, statement);
241         }
242         
243         return doReceive(event.getTimeout(),event);
244         
245     }
246 
247     /**
248      * Make a specific request to the underlying transport
249      * 
250      * @param timeout the maximum time the operation should block before returning.
251      *            The call should return immediately if there is data available. If
252      *            no data becomes available before the timeout elapses, null will be
253      *            returned
254      * @return the result of the request wrapped in a UMOMessage object. Null will be
255      *         returned if no data was available
256      * @throws Exception if the call to the underlying protocol causes an exception
257      */
258     protected UMOMessage doReceive(long timeout) throws Exception
259     {
260         return doReceive(timeout, null);
261     }
262 
263     /**
264      * Make a specific request to the underlying transport
265      * Special case: The event is need when doReceive was called from doSend
266      * @param timeout only for compatibility with doReceive(long timeout)
267      * @param event There is a need to get params from message
268      * @return the result of the request wrapped in a UMOMessage object. Null will be
269      *         returned if no data was available
270      * @throws Exception if the call to the underlying protocol causes an exception
271      */
272     protected UMOMessage doReceive(long timeout, UMOEvent event) throws Exception
273     {
274         if (logger.isDebugEnabled())
275         {
276             logger.debug("Trying to receive a message with a timeout of " + timeout);
277         }
278 
279         String[] stmts = this.connector.getReadAndAckStatements(endpoint);
280         String readStmt = stmts[0];
281         String ackStmt = stmts[1];
282         List readParams = new ArrayList();
283         List ackParams = new ArrayList();
284         readStmt = connector.parseStatement(readStmt, readParams);
285         ackStmt = connector.parseStatement(ackStmt, ackParams);
286 
287         Connection con = null;
288         long t0 = System.currentTimeMillis();
289         try
290         {
291             con = this.connector.getConnection();
292             if (timeout < 0)
293             {
294                 timeout = Long.MAX_VALUE;
295             }
296             Object result;
297             do
298             {
299                 result = connector.createQueryRunner().query(con, readStmt,
300                                                              connector.getParams(endpoint,
301                                                                                  readParams,
302                                                                                  event!=null ? event.getMessage() : null,
303                                                                                  this.endpoint.getEndpointURI().getAddress()),
304                                                              connector.createResultSetHandler());
305                 if (result != null)
306                 {
307                     if (logger.isDebugEnabled())
308                     {
309                         logger.debug("Received: " + result);
310                     }
311                     break;
312                 }
313                 long sleep = Math.min(this.connector.getPollingFrequency(),
314                                       timeout - (System.currentTimeMillis() - t0));
315                 if (sleep > 0)
316                 {
317                     if (logger.isDebugEnabled())
318                     {
319                         logger.debug("No results, sleeping for " + sleep);
320                     }
321                     Thread.sleep(sleep);
322                 }
323                 else
324                 {
325                     logger.debug("Timeout");
326                     JdbcUtils.rollbackAndClose(con);
327                     return null;
328                 }
329             }
330             while (true);
331             if (ackStmt != null)
332             {
333                 int nbRows = connector.createQueryRunner().update(con, ackStmt,
334                                                                   connector.getParams(endpoint, ackParams, result, ackStmt));
335                 if (nbRows != 1)
336                 {
337                     logger.warn("Row count for ack should be 1 and not " + nbRows);
338                 }
339             }
340             UMOMessageAdapter msgAdapter = this.connector.getMessageAdapter(result);
341             UMOMessage message = new MuleMessage(msgAdapter);
342             JdbcUtils.commitAndClose(con);
343             return message;
344         }
345         catch (Exception e)
346         {
347             JdbcUtils.rollbackAndClose(con);
348             throw e;
349         }
350 
351     }
352 
353 
354     protected void doConnect() throws Exception
355     {
356         // template method
357     }
358 
359     protected void doDisconnect() throws Exception
360     {
361         // template method
362     }
363 
364 }