View Javadoc
1   /*
2    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
3    * The software in this package is published under the terms of the CPAL v1.0
4    * license, a copy of which has been included with this distribution in the
5    * LICENSE.txt file.
6    */
7   package org.mule.transport.jdbc;
8   
9   import org.mule.DefaultMuleMessage;
10  import org.mule.api.MuleMessage;
11  import org.mule.api.construct.FlowConstruct;
12  import org.mule.api.endpoint.InboundEndpoint;
13  import org.mule.api.lifecycle.CreateException;
14  import org.mule.api.transaction.Transaction;
15  import org.mule.api.transport.Connector;
16  import org.mule.transaction.TransactionCoordination;
17  import org.mule.transaction.XaTransactionFactory;
18  import org.mule.transport.TransactedPollingMessageReceiver;
19  import org.mule.transport.jdbc.i18n.JdbcMessages;
20  import org.mule.util.ArrayUtils;
21  import org.mule.util.MapUtils;
22  
23  import java.sql.Connection;
24  import java.util.ArrayList;
25  import java.util.LinkedList;
26  import java.util.List;
27  
28  /**
29   * Implements {@link TransactedPollingMessageReceiver} reading data from a database.
30   * Provides a way to acknowledge each read data using a SQL statement.
31   */
32  public class JdbcMessageReceiver extends TransactedPollingMessageReceiver
33  {
34  
35      public static final String RECEIVE_MESSAGE_IN_TRANSCTION = "receiveMessageInTransaction";
36      public static final String RECEIVE_MESSAGES_IN_XA_TRANSCTION = "receiveMessagesInXaTransaction";
37  
38      protected JdbcConnector connector;
39      protected String readStmt;
40      protected String ackStmt;
41      protected List<?> readParams;
42      protected List<?> ackParams;
43      public boolean receiveMessagesInXaTransaction = false;
44      private volatile boolean aggregateResult;
45  
46      public JdbcMessageReceiver(Connector connector,
47                                 FlowConstruct flowConstruct,
48                                 InboundEndpoint endpoint,
49                                 String readStmt,
50                                 String ackStmt) throws CreateException
51      {
52          super(connector, flowConstruct, endpoint);
53          this.setFrequency(((JdbcConnector) connector).getPollingFrequency());
54  
55          boolean transactedEndpoint = endpoint.getTransactionConfig().isTransacted();
56          boolean xaTransactedEndpoint = (transactedEndpoint &&
57              endpoint.getTransactionConfig().getFactory() instanceof XaTransactionFactory);
58  
59          boolean receiveMessageInTransaction = MapUtils.getBooleanValue(endpoint.getProperties(),
60              RECEIVE_MESSAGE_IN_TRANSCTION, false);
61          this.setReceiveMessagesInTransaction(receiveMessageInTransaction && transactedEndpoint);
62          if (receiveMessageInTransaction && !transactedEndpoint)
63          {
64              logger.warn(JdbcMessages.forcePropertyNoTransaction(RECEIVE_MESSAGE_IN_TRANSCTION, "transaction"));
65              receiveMessageInTransaction = false;
66          }
67  
68          receiveMessagesInXaTransaction = MapUtils.getBooleanValue(endpoint.getProperties(),
69              RECEIVE_MESSAGES_IN_XA_TRANSCTION, false);
70          if (receiveMessagesInXaTransaction && !receiveMessageInTransaction)
71          {
72              logger.warn(JdbcMessages.forceProperty(RECEIVE_MESSAGES_IN_XA_TRANSCTION, RECEIVE_MESSAGE_IN_TRANSCTION));
73              receiveMessagesInXaTransaction = false;
74          }
75          else if (receiveMessagesInXaTransaction && isReceiveMessagesInTransaction() && !xaTransactedEndpoint)
76          {
77              logger.warn(JdbcMessages.forcePropertyNoTransaction(RECEIVE_MESSAGES_IN_XA_TRANSCTION, "XA transaction"));
78              receiveMessagesInXaTransaction = false;
79          }
80  
81          this.connector = (JdbcConnector) connector;
82          this.setReceiveMessagesInTransaction(endpoint.getTransactionConfig().isTransacted()
83              && !this.connector.isTransactionPerMessage());
84  
85          parseStatements(readStmt, ackStmt);
86      }
87  
88      /**
89       * Parses the read and acknowledge SQL statements
90       */
91      protected void parseStatements(String readStmt, String ackStmt)
92      {
93          this.readParams = new ArrayList<Object>();
94          this.readStmt = this.connector.parseStatement(readStmt, this.readParams);
95          this.ackParams = new ArrayList<Object>();
96          this.ackStmt = this.connector.parseStatement(ackStmt, this.ackParams);
97      }
98  
99      @Override
100     protected void doDispose()
101     {
102         // template method
103     }
104 
105     @Override
106     protected void doConnect() throws Exception
107     {
108         // template method
109     }
110 
111     @Override
112     protected void doDisconnect() throws Exception
113     {
114         // noop
115     }
116 
117     @Override
118     public void processMessage(Object message) throws Exception
119     {
120         Connection con = null;
121         Transaction tx = TransactionCoordination.getInstance().getTransaction();
122         try
123         {
124             MuleMessage muleMessage = createMuleMessage(message, endpoint.getEncoding());
125             if (hasAckStatement())
126             {
127                 con = this.connector.getConnection();
128 
129                 if (aggregateResult)
130                 {
131                     List<MuleMessage> messages = createMuleMessages((List) message);
132                     int[] nbRows = executeBatchAckStatement(con, messages);
133 
134                     if (nbRows[0] == 0)
135                     {
136                         logger.warn(".ack statement did not update any rows");
137                     }
138                     // Reset this flag
139                     aggregateResult = false;
140                 }
141                 else
142                 {
143                     int nbRows = executeAckStatement(con, muleMessage);
144                     if (nbRows == 0)
145                     {
146                         logger.warn(".ack statement did not update any rows");
147                     }
148                 }
149             }
150             routeMessage(muleMessage);
151 
152         }
153         catch (Exception ex)
154         {
155             if (tx != null)
156             {
157                 tx.setRollbackOnly();
158             }
159 
160             // rethrow
161             throw ex;
162         }
163         finally
164         {
165             if (tx == null)
166             {
167                 //Only close connection when there's no transaction.
168                 //If there's a transaction available, then the transaction
169                 //will be the one doing close after commit or rollback
170                 JdbcUtils.close(con);
171             }
172         }
173     }
174 
175     protected boolean hasAckStatement()
176     {
177         return this.ackStmt != null;
178     }
179 
180     /**
181      * Creates a mule message per each data record.
182      *
183      * @param records data records used to created the payload of the new messages.
184      * @return the created messages
185      */
186     protected List<MuleMessage> createMuleMessages(List<Object> records)
187     {
188         List<MuleMessage> messages = new LinkedList<MuleMessage>();
189         for (Object record : records)
190         {
191             messages.add(new DefaultMuleMessage(record, connector.getMuleContext()));
192         }
193 
194         return messages;
195     }
196 
197     /**
198      * Executes the acknowledge SQL statement for a given message.
199      *
200      * @param con         database connection to execute the statement
201      * @param muleMessage message to been acknowledge
202      * @return the number of updated rows by the SQL statement
203      * @throws Exception
204      */
205     protected int executeAckStatement(Connection con, MuleMessage muleMessage)
206             throws Exception
207     {
208         Object[] paramValues = connector.getParams(endpoint, this.ackParams, muleMessage, this.endpoint.getEndpointURI().getAddress());
209         if (logger.isDebugEnabled())
210         {
211             logger.debug("SQL UPDATE: " + ackStmt + ", params = " + ArrayUtils.toString(paramValues));
212         }
213         int nbRows = connector.getQueryRunnerFor(endpoint).update(con, this.ackStmt, paramValues);
214         return nbRows;
215     }
216 
217     /**
218      * Executes the acknowledge SQL statement for a list of messages.
219      *
220      * @param con      database connection to execute the statement
221      * @param messages messages to be acknowledge
222      * @return the number of updated rows by each batched execution
223      * @throws Exception
224      */
225     protected int[] executeBatchAckStatement(Connection con, List<MuleMessage> messages)
226             throws Exception
227     {
228         Object[][] paramValuesArray = new Object[messages.size()][];
229 
230         for (int i = 0; i < messages.size(); i++)
231         {
232             MuleMessage message = messages.get(i);
233             paramValuesArray[i] = connector.getParams(endpoint, this.ackParams, message, this.endpoint.getEndpointURI().getAddress());
234         }
235 
236         if (logger.isDebugEnabled())
237         {
238             logger.debug("SQL UPDATE: " + ackStmt + ", params = " + ArrayUtils.toString(ackParams));
239         }
240 
241         int[] nbRows = connector.getQueryRunnerFor(endpoint).batch(con, this.ackStmt, paramValuesArray);
242 
243         return nbRows;
244     }
245 
246     @Override
247     public List getMessages() throws Exception
248     {
249         Connection con = null;
250         try
251         {
252             con = this.connector.getConnection();
253 
254             List resultList = executeReadStatement(con);
255             if (resultList != null && resultList.size() > 1 && isReceiveMessagesInTransaction() && !receiveMessagesInXaTransaction)
256             {
257                 aggregateResult = true;
258                 logger.warn(JdbcMessages.moreThanOneMessageInTransaction(RECEIVE_MESSAGE_IN_TRANSCTION, RECEIVE_MESSAGES_IN_XA_TRANSCTION));
259                 List singleResultList = new ArrayList(1);
260                 singleResultList.add(resultList);
261                 return singleResultList;
262             }
263 
264             return resultList;
265         }
266         finally
267         {
268             if (TransactionCoordination.getInstance().getTransaction() == null)
269             {
270                 JdbcUtils.close(con);
271             }
272         }
273     }
274 
275     /**
276      * Executes the read SQL statement to get data from the database.
277      *
278      * @param con database connection to execute the statement
279      * @return the list of read records
280      * @throws Exception
281      */
282     protected List executeReadStatement(Connection con) throws Exception
283     {
284         Object[] readParams = connector.getParams(endpoint, this.readParams, null, this.endpoint.getEndpointURI().getAddress());
285         if (logger.isDebugEnabled())
286         {
287             logger.debug("SQL QUERY: " + readStmt + ", params = " + ArrayUtils.toString(readParams));
288         }
289         Object results = connector.getQueryRunnerFor(endpoint).query(con, this.readStmt, readParams,
290                 connector.getResultSetHandler());
291 
292         return (List) results;
293     }
294 }