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