1 /*
2 * JBoss, Home of Professional Open Source.
3 * Copyright 2006, Red Hat Middleware LLC, and individual contributors
4 * as indicated by the @author tags. See the copyright.txt file in the
5 * distribution for a full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */
22 package org.jboss.ejb.txtimer;
23
24 // $Id: GeneralPurposeDatabasePersistencePlugin.java 62320 2007-04-13 10:56:45Z dimitris@jboss.org $
25
26 import java.io.ByteArrayInputStream;
27 import java.io.ByteArrayOutputStream;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.io.ObjectInputStream;
31 import java.io.ObjectOutputStream;
32 import java.io.Serializable;
33 import java.sql.Connection;
34 import java.sql.PreparedStatement;
35 import java.sql.ResultSet;
36 import java.sql.SQLException;
37 import java.sql.Statement;
38 import java.sql.Timestamp;
39 import java.util.ArrayList;
40 import java.util.Date;
41 import java.util.List;
42
43 import javax.management.MBeanServer;
44 import javax.management.ObjectName;
45 import javax.naming.InitialContext;
46 import javax.sql.DataSource;
47
48 import org.jboss.ejb.plugins.cmp.jdbc.JDBCUtil;
49 import org.jboss.ejb.plugins.cmp.jdbc.SQLUtil;
50 import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCFunctionMappingMetaData;
51 import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCTypeMappingMetaData;
52 import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCMappingMetaData;
53 import org.jboss.invocation.MarshalledValueInputStream;
54 import org.jboss.logging.Logger;
55 import org.jboss.mx.util.ObjectNameFactory;
56
57 /**
58 * This DatabasePersistencePlugin uses getBytes/setBytes to persist the
59 * serializable objects associated with the timer.
60 *
61 * @author Thomas.Diesler@jboss.org
62 * @author Dimitris.Andreadis@jboss.org
63 * @version $Revision: 62320 $
64 * @since 23-Sep-2004
65 */
66 public class GeneralPurposeDatabasePersistencePlugin implements DatabasePersistencePluginExt
67 {
68 /** logging support */
69 private static Logger log = Logger.getLogger(GeneralPurposeDatabasePersistencePlugin.class);
70
71 /** The mbean server */
72 protected MBeanServer server;
73
74 /** The service attributes */
75 protected ObjectName dataSourceName;
76
77 /** The timers table name */
78 protected String tableName;
79
80 /** The data source the timers will be persisted to */
81 protected DataSource ds;
82
83 /** datasource meta data */
84 protected ObjectName metaDataName;
85
86 // default JDBC type code for binary data
87 private int binarySqlType;
88
89 /**
90 * Initialize the plugin and set also the timers tablename
91 */
92 public void init(MBeanServer server, ObjectName dataSource, String tableName) throws SQLException
93 {
94 if (tableName == null)
95 throw new IllegalArgumentException("Timers tableName is null");
96 if (tableName.length() == 0)
97 throw new IllegalArgumentException("Timers tableName is empty");
98
99 this.tableName = tableName;
100 init(server, dataSource);
101 }
102
103 /** Initialize the plugin */
104 public void init(MBeanServer server, ObjectName dataSourceName) throws SQLException
105 {
106 this.server = server;
107 this.dataSourceName = dataSourceName;
108
109 // Get the DataSource from JNDI
110 try
111 {
112 String dsJndiTx = (String)server.getAttribute(dataSourceName, "BindName");
113 ds = (DataSource)new InitialContext().lookup(dsJndiTx);
114 }
115 catch (Exception e)
116 {
117 throw new SQLException("Failed to lookup data source: " + dataSourceName);
118 }
119
120 // Get the DataSource meta data
121 String dsName = dataSourceName.getKeyProperty("name");
122 metaDataName = ObjectNameFactory.create("jboss.jdbc:datasource=" + dsName + ",service=metadata");
123 if (this.server.isRegistered(metaDataName) == false)
124 throw new IllegalStateException("Cannot find datasource meta data: " + metaDataName);
125 }
126
127 /** Create the timer table if it does not exist already */
128 public void createTableIfNotExists()
129 throws SQLException
130 {
131 Connection con = null;
132 Statement st = null;
133 try
134 {
135 JDBCTypeMappingMetaData typeMapping = (JDBCTypeMappingMetaData)server.getAttribute(metaDataName, "TypeMappingMetaData");
136 if (typeMapping == null)
137 throw new IllegalStateException("Cannot obtain type mapping from: " + metaDataName);
138
139 JDBCMappingMetaData objectMetaData = typeMapping.getTypeMappingMetaData(Object.class);
140 binarySqlType = objectMetaData.getJdbcType();
141
142 if (!SQLUtil.tableExists(getTableName(), ds))
143 {
144 con = ds.getConnection();
145
146 String dateType = typeMapping.getTypeMappingMetaData(Timestamp.class).getSqlType();
147 String longType = typeMapping.getTypeMappingMetaData(Long.class).getSqlType();
148 String objectType = objectMetaData.getSqlType();
149
150 // The create table DDL
151 StringBuffer createTableDDL = new StringBuffer("create table " + getTableName() + " (" +
152 " " + getColumnTimerID() + " varchar(80) not null," +
153 " " + getColumnTargetID() + " varchar(250) not null," +
154 " " + getColumnInitialDate() + " " + dateType + " not null," +
155 " " + getColumnTimerInterval() + " " + longType + "," +
156 " " + getColumnInstancePK() + " " + objectType + "," +
157 " " + getColumnInfo() + " " + objectType + ", ");
158
159 // Add the primary key constraint using the pk-constraint-template
160 JDBCFunctionMappingMetaData pkConstraint = typeMapping.getPkConstraintTemplate();
161 String[] templateParams = new String[] {
162 getTableName() + "_PK",
163 getColumnTimerID() + ", " + getColumnTargetID()
164 };
165 pkConstraint.getFunctionSql(templateParams, createTableDDL);
166
167 // Complete the statement
168 createTableDDL.append(" )");
169
170 log.debug("Executing DDL: " + createTableDDL);
171
172 st = con.createStatement();
173 st.executeUpdate(createTableDDL.toString());
174 }
175 }
176 catch (SQLException e)
177 {
178 throw e;
179 }
180 catch (Exception e)
181 {
182 log.error("Cannot create timer table", e);
183 }
184 finally
185 {
186 JDBCUtil.safeClose(st);
187 JDBCUtil.safeClose(con);
188 }
189 }
190
191 /** Insert a timer object */
192 public void insertTimer(String timerId, TimedObjectId timedObjectId, Date initialExpiration, long intervalDuration, Serializable info)
193 throws SQLException
194 {
195 Connection con = null;
196 PreparedStatement st = null;
197 try
198 {
199 con = ds.getConnection();
200
201 String sql = "insert into " + getTableName() + " " +
202 "(" + getColumnTimerID() + "," + getColumnTargetID() + "," + getColumnInitialDate() + "," + getColumnTimerInterval() + "," + getColumnInstancePK() + "," + getColumnInfo() + ") " +
203 "values (?,?,?,?,?,?)";
204 st = con.prepareStatement(sql);
205
206 st.setString(1, timerId);
207 st.setString(2, timedObjectId.toString());
208 st.setTimestamp(3, new Timestamp(initialExpiration.getTime()));
209 st.setLong(4, intervalDuration);
210
211 byte[] bytes = serialize(timedObjectId.getInstancePk());
212 if(bytes == null)
213 {
214 st.setNull(5, binarySqlType);
215 }
216 else
217 {
218 st.setBytes(5, bytes);
219 }
220
221 bytes = serialize(info);
222 if(bytes == null)
223 {
224 st.setNull(6, binarySqlType);
225 }
226 else
227 {
228 st.setBytes(6, bytes);
229 }
230
231 int rows = st.executeUpdate();
232 if (rows != 1)
233 log.error("Unable to insert timer for: " + timedObjectId);
234 }
235 finally
236 {
237 JDBCUtil.safeClose(st);
238 JDBCUtil.safeClose(con);
239 }
240 }
241
242 /** Select a list of currently persisted timer handles
243 * @return List<TimerHandleImpl>
244 */
245 public List selectTimers(ObjectName containerId) throws SQLException
246 {
247 Connection con = null;
248 Statement st = null;
249 ResultSet rs = null;
250 try
251 {
252 con = ds.getConnection();
253
254 List list = new ArrayList();
255
256 st = con.createStatement();
257 rs = st.executeQuery("select * from " + getTableName());
258 while (rs.next())
259 {
260 String timerId = rs.getString(getColumnTimerID());
261 TimedObjectId targetId = TimedObjectId.parse(rs.getString(getColumnTargetID()));
262
263 // add this handle to the returned list, if a null containerId was used
264 // or the containerId filter matches
265 if (containerId == null || containerId.equals(targetId.getContainerId()))
266 {
267 Date initialDate = rs.getTimestamp(getColumnInitialDate());
268 long interval = rs.getLong(getColumnTimerInterval());
269 Serializable pKey = (Serializable)deserialize(rs.getBytes(getColumnInstancePK()));
270 Serializable info = null;
271 try
272 {
273 info = (Serializable)deserialize(rs.getBytes(getColumnInfo()));
274 }
275 catch (Exception e)
276 {
277 // may happen if listing all handles (containerId is null)
278 // with a stored custom info object coming from a scoped
279 // deployment.
280 log.warn("Cannot deserialize custom info object", e);
281 }
282 // is this really needed? targetId encapsulates pKey as well!
283 targetId = new TimedObjectId(targetId.getContainerId(), pKey);
284 TimerHandleImpl handle = new TimerHandleImpl(timerId, targetId, initialDate, interval, info);
285 list.add(handle);
286 }
287 }
288
289 return list;
290 }
291 finally
292 {
293 JDBCUtil.safeClose(rs);
294 JDBCUtil.safeClose(st);
295 JDBCUtil.safeClose(con);
296 }
297 }
298
299 /** Delete a timer. */
300 public void deleteTimer(String timerId, TimedObjectId timedObjectId)
301 throws SQLException
302 {
303 Connection con = null;
304 PreparedStatement st = null;
305 ResultSet rs = null;
306
307 try
308 {
309 con = ds.getConnection();
310
311 String sql = "delete from " + getTableName() + " where " + getColumnTimerID() + "=? and " + getColumnTargetID() + "=?";
312 st = con.prepareStatement(sql);
313
314 st.setString(1, timerId);
315 st.setString(2, timedObjectId.toString());
316
317 int rows = st.executeUpdate();
318
319 // This appears when a timer is created & persisted inside a tx,
320 // but then the tx is rolled back, at which point we go back
321 // to remove the entry, but no entry is found.
322 // Is this because we are "enlisting" the datasource in the tx, too?
323 if (rows != 1)
324 {
325 log.debug("Unable to remove timer for: " + timerId);
326 }
327 }
328 finally
329 {
330 JDBCUtil.safeClose(rs);
331 JDBCUtil.safeClose(st);
332 JDBCUtil.safeClose(con);
333 }
334 }
335
336 /** Clear all persisted timers */
337 public void clearTimers()
338 throws SQLException
339 {
340 Connection con = null;
341 PreparedStatement st = null;
342 ResultSet rs = null;
343 try
344 {
345 con = ds.getConnection();
346 st = con.prepareStatement("delete from " + getTableName());
347 st.executeUpdate();
348 }
349 finally
350 {
351 JDBCUtil.safeClose(rs);
352 JDBCUtil.safeClose(st);
353 JDBCUtil.safeClose(con);
354 }
355 }
356
357 /** Get the timer table name */
358 public String getTableName()
359 {
360 return tableName;
361 }
362
363 /** Get the timer ID column name */
364 public String getColumnTimerID()
365 {
366 return "TIMERID";
367 }
368
369 /** Get the target ID column name */
370 public String getColumnTargetID()
371 {
372 return "TARGETID";
373 }
374
375 /** Get the initial date column name */
376 public String getColumnInitialDate()
377 {
378 return "INITIALDATE";
379 }
380
381 /** Get the timer interval column name */
382 public String getColumnTimerInterval()
383 {
384 // Note 'INTERVAL' is a reserved word in MySQL
385 return "TIMERINTERVAL";
386 }
387
388 /** Get the instance PK column name */
389 public String getColumnInstancePK()
390 {
391 return "INSTANCEPK";
392 }
393
394 /** Get the info column name */
395 public String getColumnInfo()
396 {
397 return "INFO";
398 }
399
400 /** Serialize an object */
401 protected byte[] serialize(Object obj)
402 {
403 if (obj == null)
404 return null;
405
406 ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
407 try
408 {
409 ObjectOutputStream oos = new ObjectOutputStream(baos);
410 oos.writeObject(obj);
411 oos.close();
412 }
413 catch (IOException e)
414 {
415 log.error("Cannot serialize: " + obj, e);
416 }
417 return baos.toByteArray();
418 }
419
420 /** Deserialize an object */
421 protected Object deserialize(byte[] bytes)
422 {
423 if (bytes == null)
424 return null;
425
426 ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
427 try
428 {
429 // Use an ObjectInputStream that instantiates objects
430 // using the Thread Context ClassLoader (TCL)
431 ObjectInputStream oos = new MarshalledValueInputStream(bais);
432 return oos.readObject();
433 }
434 catch (Exception e)
435 {
436 log.error("Cannot deserialize", e);
437 return null;
438 }
439 }
440
441 /** Deserialize an object */
442 protected Object deserialize(InputStream input)
443 {
444
445 if (input == null)
446 return null;
447
448 byte[] barr = new byte[1024];
449 ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
450 try
451 {
452 for (int b = 0; (b = input.read(barr)) > 0;)
453 {
454 baos.write(barr, 0, b);
455 }
456 return deserialize(baos.toByteArray());
457 }
458 catch (Exception e)
459 {
460 log.error("Cannot deserialize", e);
461 return null;
462 }
463 }
464 }
465