001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.servicemix.jbi.audit.jdbc;
018
019 import java.io.IOException;
020 import java.net.URI;
021 import java.sql.Connection;
022 import java.sql.SQLException;
023
024 import javax.jbi.messaging.MessageExchange;
025 import javax.sql.DataSource;
026
027 import org.apache.servicemix.jbi.audit.AbstractAuditor;
028 import org.apache.servicemix.jbi.audit.AuditorException;
029 import org.apache.servicemix.jbi.event.ExchangeEvent;
030 import org.apache.servicemix.jbi.messaging.ExchangePacket;
031 import org.apache.servicemix.jbi.messaging.InOnlyImpl;
032 import org.apache.servicemix.jbi.messaging.InOptionalOutImpl;
033 import org.apache.servicemix.jbi.messaging.InOutImpl;
034 import org.apache.servicemix.jbi.messaging.MessageExchangeImpl;
035 import org.apache.servicemix.jbi.messaging.MessageExchangeSupport;
036 import org.apache.servicemix.jbi.messaging.RobustInOnlyImpl;
037 import org.apache.servicemix.jdbc.JDBCAdapter;
038 import org.apache.servicemix.jdbc.JDBCAdapterFactory;
039 import org.apache.servicemix.jdbc.Statements;
040 import org.springframework.beans.factory.InitializingBean;
041
042 /**
043 * Basic implementation of ServiceMix auditor on a jdbc store.
044 * This implementation, for performance purposes, only relies
045 * on one table SM_AUDIT with two columns:
046 * <ul>
047 * <li><b>ID</b> the exchange id (varchar)</li>
048 * <li><b>EXCHANGE</b> the serialized exchange (blob)</li>
049 * </ul>
050 * To minimize overhead, the exchange serialized is the undelying
051 * {@link org.apache.servicemix.jbi.messaging.ExchangePacket}.
052 *
053 * @org.apache.xbean.XBean element="jdbcAuditor" description="The Auditor of message exchanges to a JDBC database"
054 *
055 * @author Guillaume Nodet (gnt)
056 * @version $Revision: 34165 $
057 * @since 2.1
058 */
059 public class JdbcAuditor extends AbstractAuditor implements InitializingBean {
060
061 private DataSource dataSource;
062 private boolean autoStart = true;
063 private Statements statements;
064 private String tableName = "SM_AUDIT";
065 private JDBCAdapter adapter;
066 private boolean createDataBase = true;
067
068 public String getDescription() {
069 return "JDBC Auditing Service";
070 }
071
072 public void afterPropertiesSet() throws Exception {
073 if (this.container == null) {
074 throw new IllegalArgumentException("container should not be null");
075 }
076 if (this.dataSource == null) {
077 throw new IllegalArgumentException("dataSource should not be null");
078 }
079 if (statements == null) {
080 statements = new Statements();
081 statements.setStoreTableName(tableName);
082 }
083 Connection connection = null;
084 boolean restoreAutoCommit = false;
085 try {
086 connection = getDataSource().getConnection();
087 if (connection.getAutoCommit()) {
088 connection.setAutoCommit(false);
089 restoreAutoCommit = true;
090 }
091 adapter = JDBCAdapterFactory.getAdapter(connection);
092 if (statements == null) {
093 statements = new Statements();
094 statements.setStoreTableName(tableName);
095 }
096 adapter.setStatements(statements);
097 if (createDataBase) {
098 adapter.doCreateTables(connection);
099 }
100 connection.commit();
101 } catch (SQLException e) {
102 throw (IOException) new IOException("Exception while creating database").initCause(e);
103 } finally {
104 close(connection, restoreAutoCommit);
105 }
106 init(getContainer());
107 if (autoStart) {
108 start();
109 } else {
110 stop();
111 }
112 }
113
114 public void exchangeSent(ExchangeEvent event) {
115 MessageExchange exchange = event.getExchange();
116 if (!(exchange instanceof MessageExchangeImpl)) {
117 throw new IllegalArgumentException("exchange should be a MessageExchangeImpl");
118 }
119 try {
120 ExchangePacket packet = ((MessageExchangeImpl) exchange).getPacket();
121 String id = packet.getExchangeId();
122 byte[] data = packet.getData();
123 Connection connection = null;
124 boolean restoreAutoCommit = false;
125 try {
126 connection = dataSource.getConnection();
127 if (connection.getAutoCommit()) {
128 connection.setAutoCommit(false);
129 restoreAutoCommit = true;
130 }
131 store(connection, id, data);
132 connection.commit();
133 } finally {
134 close(connection, restoreAutoCommit);
135 }
136 } catch (Exception e) {
137 log.error("Could not persist exchange", e);
138 }
139 }
140
141 protected void store(Connection connection, String id, byte[] data) throws Exception {
142 if (adapter.doLoadData(connection, id) != null) {
143 adapter.doUpdateData(connection, id, data);
144 } else {
145 adapter.doStoreData(connection, id, data);
146 }
147 }
148
149 public DataSource getDataSource() {
150 return dataSource;
151 }
152
153 public void setDataSource(DataSource dataSource) {
154 this.dataSource = dataSource;
155 }
156
157 /* (non-Javadoc)
158 * @see org.apache.servicemix.jbi.audit.AuditorMBean#getExchangeCount()
159 */
160 public int getExchangeCount() throws AuditorException {
161 Connection connection = null;
162 try {
163 connection = dataSource.getConnection();
164 return adapter.doGetCount(connection);
165 } catch (Exception e) {
166 throw new AuditorException("Could not retrieve exchange count", e);
167 } finally {
168 close(connection, false);
169 }
170 }
171
172 /* (non-Javadoc)
173 * @see org.apache.servicemix.jbi.audit.AuditorMBean#getExchangeIds(int, int)
174 */
175 public String[] getExchangeIdsByRange(int fromIndex, int toIndex) throws AuditorException {
176 if (fromIndex < 0) {
177 throw new IllegalArgumentException("fromIndex should be greater or equal to zero");
178 }
179 if (toIndex < fromIndex) {
180 throw new IllegalArgumentException("toIndex should be greater or equal to fromIndex");
181 }
182 // Do not hit the database if no ids are requested
183 if (fromIndex == toIndex) {
184 return new String[0];
185 }
186 Connection connection = null;
187 try {
188 connection = dataSource.getConnection();
189 return adapter.doGetIds(connection, fromIndex, toIndex);
190 } catch (Exception e) {
191 throw new AuditorException("Could not retrieve exchange ids", e);
192 } finally {
193 close(connection, false);
194 }
195 }
196
197 /* (non-Javadoc)
198 * @see org.apache.servicemix.jbi.audit.AuditorMBean#getExchanges(java.lang.String[])
199 */
200 public MessageExchange[] getExchangesByIds(String[] ids) throws AuditorException {
201 MessageExchange[] exchanges = new MessageExchange[ids.length];
202 Connection connection = null;
203 try {
204 connection = dataSource.getConnection();
205 for (int row = 0; row < ids.length; row++) {
206 exchanges[row] = getExchange(adapter.doLoadData(connection, ids[row]));
207 }
208 return exchanges;
209 } catch (Exception e) {
210 throw new AuditorException("Could not retrieve exchanges", e);
211 } finally {
212 close(connection, false);
213 }
214 }
215
216 /* (non-Javadoc)
217 * @see org.apache.servicemix.jbi.audit.AuditorMBean#deleteExchanges(java.lang.String[])
218 */
219 public int deleteExchangesByIds(String[] ids) throws AuditorException {
220 Connection connection = null;
221 boolean restoreAutoCommit = false;
222 try {
223 connection = dataSource.getConnection();
224 if (connection.getAutoCommit()) {
225 connection.setAutoCommit(false);
226 restoreAutoCommit = true;
227 }
228 for (int row = 0; row < ids.length; row++) {
229 adapter.doRemoveData(connection, ids[row]);
230 }
231 connection.commit();
232 return -1;
233 } catch (Exception e) {
234 throw new AuditorException("Could not delete exchanges", e);
235 } finally {
236 close(connection, restoreAutoCommit);
237 }
238 }
239
240 // TODO: this should be somewhere in org.apache.servicemix.jbi.messaging
241 protected MessageExchange getExchange(byte[] data) throws AuditorException {
242 ExchangePacket packet = null;
243 try {
244 packet = ExchangePacket.readPacket(data);
245 } catch (Exception e) {
246 throw new AuditorException("Unable to reconstruct exchange", e);
247 }
248 URI mep = packet.getPattern();
249 if (MessageExchangeSupport.IN_ONLY.equals(mep)) {
250 return new InOnlyImpl(packet);
251 } else if (MessageExchangeSupport.IN_OPTIONAL_OUT.equals(mep)) {
252 return new InOptionalOutImpl(packet);
253 } else if (MessageExchangeSupport.IN_OUT.equals(mep)) {
254 return new InOutImpl(packet);
255 } else if (MessageExchangeSupport.ROBUST_IN_ONLY.equals(mep)) {
256 return new RobustInOnlyImpl(packet);
257 } else {
258 throw new AuditorException("Unhandled mep: " + mep);
259 }
260 }
261
262 public boolean isAutoStart() {
263 return autoStart;
264 }
265
266 public void setAutoStart(boolean autoStart) {
267 this.autoStart = autoStart;
268 }
269
270 private static void close(Connection connection, boolean restoreAutoCommit) {
271 if (connection != null) {
272 try {
273 if (restoreAutoCommit) {
274 connection.setAutoCommit(true);
275 }
276 connection.close();
277 } catch (SQLException e) {
278 // Do nothing
279 }
280 }
281 }
282
283 }