1 /*
2 * JBoss, Home of Professional Open Source
3 * Copyright 2005, JBoss Inc., and individual contributors as indicated
4 * by the @authors tag. See the copyright.txt in the distribution for a
5 * 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: EJBTimerServiceImpl.java 66679 2007-11-02 14:29:49Z wolfc $
25
26 import java.lang.reflect.Constructor;
27 import java.util.Collection;
28 import java.util.Collections;
29 import java.util.HashMap;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Date;
34 import java.io.Serializable;
35
36 import javax.ejb.TimerService;
37 import javax.ejb.Timer;
38 import javax.ejb.EJBException;
39 import javax.management.ObjectName;
40 import javax.transaction.TransactionManager;
41
42 import org.jboss.ejb.Container;
43 import org.jboss.ejb.ContainerMBean;
44 import org.jboss.logging.Logger;
45 import org.jboss.mx.util.MBeanProxyExt;
46 import org.jboss.system.ServiceMBeanSupport;
47 import org.jboss.tm.TransactionManagerFactory;
48 import org.jboss.tm.TransactionManagerLocator;
49
50 /**
51 * A service that implements this interface provides an Tx aware EJBTimerService.
52 *
53 * @author Thomas.Diesler@jboss.org
54 * @author Dimitris.Andreadis@jboss.org
55 * @version $Revision: 66679 $
56 * @since 07-Apr-2004
57 */
58 public class EJBTimerServiceImpl extends ServiceMBeanSupport
59 implements EJBTimerServiceImplMBean
60 {
61 // Logging support
62 private static Logger log = Logger.getLogger(EJBTimerServiceImpl.class);
63
64 /**
65 * Used for objects that don't implement javax.ejb.TimedObject but still call getTimerService()
66 * According to the CTS, it's allowed (jbcts-381).
67 */
68 public static TimerService FOR_NON_TIMED_OBJECT = new TimerService()
69 {
70 public Timer createTimer(long duration, Serializable info) throws IllegalArgumentException,
71 IllegalStateException,
72 EJBException
73 {
74 throw new IllegalStateException("The object does not implement javax.ejb.TimedObject interface!");
75 }
76
77 public Timer createTimer(long initialDuration, long intervalDuration, Serializable info)
78 throws IllegalArgumentException, IllegalStateException, EJBException
79 {
80 throw new IllegalStateException("The object does not implement javax.ejb.TimedObject interface!");
81 }
82
83 public Timer createTimer(Date expiration, Serializable info) throws IllegalArgumentException,
84 IllegalStateException,
85 EJBException
86 {
87 throw new IllegalStateException("The object does not implement javax.ejb.TimedObject interface!");
88 }
89
90 public Timer createTimer(Date initialExpiration, long intervalDuration, Serializable info)
91 throws IllegalArgumentException, IllegalStateException, EJBException
92 {
93 throw new IllegalStateException("The object does not implement javax.ejb.TimedObject interface!");
94 }
95
96 public Collection getTimers() throws IllegalStateException, EJBException
97 {
98 return Collections.EMPTY_LIST;
99 }
100 };
101
102 // Attributes
103
104 // The object name of the retry policy
105 private ObjectName retryPolicyName;
106 // The object name of the persistence policy
107 private ObjectName persistencePolicyName;
108 // The TimerIdGenerator class name
109 private String timerIdGeneratorClassName;
110 // The TimedObjectInvoker class name
111 private String timedObjectInvokerClassName;
112 // The TransactionManagerFactory
113 private TransactionManagerFactory transactionManagerFactory;
114
115 // Plug-ins
116
117 // The tx manager plug-in
118 private TransactionManager transactionManager;
119 // The retry policy plug-in
120 private RetryPolicy retryPolicy;
121 // The persistence policy plug-in
122 private PersistencePolicy persistencePolicy;
123 // The timerId generator plug-in
124 private TimerIdGenerator timerIdGenerator;
125
126 // Maps the timedObjectId to TimerServiceImpl objects
127 private Map timerServiceMap = Collections.synchronizedMap(new HashMap());
128
129 // Attributes ----------------------------------------------------
130
131 /**
132 * Get the object name of the retry policy.
133 *
134 * @jmx.managed-attribute
135 */
136 public ObjectName getRetryPolicy()
137 {
138 return retryPolicyName;
139 }
140
141 /**
142 * Set the object name of the retry policy.
143 *
144 * @jmx.managed-attribute
145 */
146 public void setRetryPolicy(ObjectName retryPolicyName)
147 {
148 this.retryPolicyName = retryPolicyName;
149 }
150
151 /**
152 * Get the object name of the persistence policy.
153 *
154 * @jmx.managed-attribute
155 */
156 public ObjectName getPersistencePolicy()
157 {
158 return persistencePolicyName;
159 }
160
161 /**
162 * Set the object name of the persistence policy.
163 *
164 * @jmx.managed-attribute
165 */
166 public void setPersistencePolicy(ObjectName persistencePolicyName)
167 {
168 this.persistencePolicyName = persistencePolicyName;
169 }
170
171 /**
172 * Get the TimerIdGenerator class name
173 *
174 * @jmx.managed-attribute
175 */
176 public String getTimerIdGeneratorClassName()
177 {
178 return timerIdGeneratorClassName;
179 }
180
181 /**
182 * Get the TimerIdGenerator class name
183 *
184 * @jmx.managed-attribute
185 */
186 public void setTimerIdGeneratorClassName(String timerIdGeneratorClassName)
187 {
188 this.timerIdGeneratorClassName = timerIdGeneratorClassName;
189 }
190
191 /**
192 * Get the TimedObjectInvoker class name
193 *
194 * @jmx.managed-attribute
195 */
196 public String getTimedObjectInvokerClassName()
197 {
198 return timedObjectInvokerClassName;
199 }
200
201 /**
202 * Set the TimedObjectInvoker class name
203 *
204 * @jmx.managed-attribute
205 */
206 public void setTimedObjectInvokerClassName(String timedObjectInvokerClassName)
207 {
208 this.timedObjectInvokerClassName = timedObjectInvokerClassName;
209 }
210
211 /**
212 * Set the TransactionManagerFactory
213 */
214 public void setTransactionManagerFactory(TransactionManagerFactory factory)
215 {
216 this.transactionManagerFactory = factory;
217 }
218
219 // ServiceMBeanSupport Lifecycle ---------------------------------
220
221 protected void startService() throws Exception
222 {
223 // Setup plugins, fall back to safe defaults
224
225 // Get the TransactionManager from the factory, fall-back to the locator
226 if (transactionManagerFactory != null)
227 transactionManager = transactionManagerFactory.getTransactionManager();
228 else
229 transactionManager = TransactionManagerLocator.getInstance().locate();
230
231 // Get a proxy to the retry policy
232 try
233 {
234 retryPolicy = (RetryPolicy)MBeanProxyExt.create(RetryPolicy.class, getRetryPolicy(), server);
235 }
236 catch (Exception e)
237 {
238 log.error("Cannot obtain the implementation of a RetryPolicy", e);
239 }
240
241 // Get a proxy to the persistence policy
242 try
243 {
244 persistencePolicy = (PersistencePolicy)MBeanProxyExt.create(PersistencePolicy.class, persistencePolicyName, server);
245 }
246 catch (Exception e)
247 {
248 log.warn("Cannot obtain the implementation of a PersistencePolicy, using NoopPersistencePolicy: " + e.toString());
249 persistencePolicy = new NoopPersistencePolicy();
250 }
251
252 // Get the timerId generator
253 try
254 {
255 Class timerIdGeneratorClass = getClass().getClassLoader().loadClass(timerIdGeneratorClassName);
256 timerIdGenerator = (TimerIdGenerator)timerIdGeneratorClass.newInstance();
257 }
258 catch (Exception e)
259 {
260 log.warn("Cannot obtain the implementation of a TimerIdGenerator, using BigIntegerTimerIdGenerator: " + e.toString());
261 timerIdGenerator = new BigIntegerTimerIdGenerator();
262 }
263 }
264
265 protected void stopService()
266 {
267 // Cleanup plugins
268 transactionManager = null;
269 retryPolicy = null;
270 persistencePolicy = null;
271 timerIdGenerator = null;
272 }
273
274 // EJBTimerService Operations ------------------------------------
275
276 /**
277 * Create a TimerService for a given TimedObjectId that lives in a JBoss Container.
278 * The TimedObjectInvoker is constructed from the invokerClassName.
279 *
280 * @param containerId The string identifier for a class of TimedObjects
281 * @param instancePk The rimary key for an instance of a TimedObject, may be null
282 * @param container The Container that is associated with the TimerService
283 * @return the TimerService
284 */
285 public TimerService createTimerService(ObjectName containerId, Object instancePk, Container container)
286 {
287 TimedObjectInvoker invoker = null;
288 try
289 {
290 TimedObjectId timedObjectId = new TimedObjectId(containerId, instancePk);
291 Class invokerClass = getClass().getClassLoader().loadClass(timedObjectInvokerClassName);
292 Constructor constr = invokerClass.getConstructor(new Class[]{TimedObjectId.class, Container.class});
293 invoker = (TimedObjectInvoker)constr.newInstance(new Object[]{timedObjectId, container});
294 }
295 catch (Exception e)
296 {
297 log.error("Cannot create TimedObjectInvoker: " + timedObjectInvokerClassName, e);
298 return null;
299 }
300
301 return createTimerService(containerId, instancePk, invoker);
302 }
303
304 /**
305 * Create a TimerService for a given TimedObjectId that is invoked through the given invoker
306 *
307 * @param containerId The string identifier for a class of TimedObjects
308 * @param instancePk The rimary key for an instance of a TimedObject, may be null
309 * @param invoker The TimedObjectInvoker
310 * @return the TimerService
311 */
312 public TimerService createTimerService(ObjectName containerId, Object instancePk, TimedObjectInvoker invoker)
313 {
314 TimedObjectId timedObjectId = new TimedObjectId(containerId, instancePk);
315 TimerServiceImpl timerService = (TimerServiceImpl)timerServiceMap.get(timedObjectId);
316 if (timerService == null)
317 {
318 timerService = new TimerServiceImpl(timedObjectId, invoker,
319 transactionManager, persistencePolicy, retryPolicy, timerIdGenerator);
320 log.debug("createTimerService: " + timerService);
321 timerServiceMap.put(timedObjectId, timerService);
322 }
323 return timerService;
324 }
325
326 /**
327 * Get the TimerService for a given TimedObjectId
328 *
329 * @param containerId The string identifier for a class of TimedObjects
330 * @param instancePk The rimary key for an instance of a TimedObject, may be null
331 * @return The TimerService, or null if it does not exist
332 */
333 public TimerService getTimerService(ObjectName containerId, Object instancePk)
334 {
335 TimedObjectId timedObjectId = new TimedObjectId(containerId, instancePk);
336 return (TimerServiceImpl)timerServiceMap.get(timedObjectId);
337 }
338
339 /**
340 * Remove the TimerService for a given containerId/pKey (TimedObjectId),
341 * along with any persisted timer information.
342 *
343 * This should be used for removing the TimerService and Timers
344 * associated with a particular entity bean, when it gets removed.
345 *
346 * @param containerId The string identifier for a class of TimedObjects
347 * @param pKey The primary key for an instance of a TimedObject, may be null
348 */
349 public void removeTimerService(ObjectName containerId, Object instancePk)
350 {
351 TimedObjectId timedObjectId = new TimedObjectId(containerId, instancePk);
352 // remove a single timer service
353 if (timedObjectId.getInstancePk() != null)
354 {
355 TimerServiceImpl timerService = (TimerServiceImpl)getTimerService(containerId, instancePk);
356 if (timerService != null)
357 {
358 log.debug("removeTimerService: " + timerService);
359 // don't keep persistent state about the timer
360 // this is really an entity->remove()
361 timerService.shutdown(false);
362 timerServiceMap.remove(timedObjectId);
363 }
364 }
365 else
366 {
367 // assume we don't want to keep timer state when the container
368 // gets undeployed, this is the legacy behaviour
369 removeTimerService(containerId, false);
370 }
371 }
372
373 /**
374 * Remove the TimerService for a given containerId.
375 *
376 * This should be used to remove the timer service and timers for
377 * any type of container (session, entity, message) at the time of
378 * undeployment.
379 *
380 * @param containerId The string identifier for a class of TimedObjects
381 * @param keepState Flag indicating whether timer persistent state should be kept or removed
382 */
383 public void removeTimerService(ObjectName containerId, boolean keepState) throws IllegalStateException
384 {
385 // remove all timers with the given containerId
386 Iterator it = timerServiceMap.entrySet().iterator();
387 while (it.hasNext())
388 {
389 Map.Entry entry = (Map.Entry)it.next();
390 TimedObjectId key = (TimedObjectId)entry.getKey();
391 TimerServiceImpl timerService = (TimerServiceImpl)entry.getValue();
392 if (containerId.equals(key.getContainerId()))
393 {
394 log.debug("removeTimerService: " + timerService);
395 timerService.shutdown(keepState);
396 it.remove();
397 }
398 }
399 }
400
401 /**
402 * Remove the TimerService for a given containerId/pKey (TimedObjectId).
403 *
404 * @param containerId The string identifier for a class of TimedObjects
405 * @param pKey The primary key for an instance of a TimedObject, may be null
406 * @param keepState Flag indicating whether timer persistent state should be kept or removed
407 */
408 public void removeTimerService(ObjectName containerId, Object instancePk, boolean keepState) throws IllegalStateException
409 {
410 // remove a single timer service
411 TimedObjectId timedObjectId = new TimedObjectId(containerId, instancePk);
412 if (timedObjectId.getInstancePk() != null)
413 {
414 TimerServiceImpl timerService = (TimerServiceImpl)getTimerService(containerId, instancePk);
415 if (timerService != null)
416 {
417 log.debug("removeTimerService: " + timerService);
418 timerService.shutdown(false);
419 timerServiceMap.remove(timedObjectId);
420 }
421 }
422 // remove all timers with the given containerId
423 else
424 {
425 Iterator it = timerServiceMap.entrySet().iterator();
426 while (it.hasNext())
427 {
428 Map.Entry entry = (Map.Entry)it.next();
429 TimedObjectId key = (TimedObjectId)entry.getKey();
430 TimerServiceImpl timerService = (TimerServiceImpl)entry.getValue();
431 if (containerId.equals(key.getContainerId()))
432 {
433 log.debug("removeTimerService: " + timerService);
434 timerService.shutdown(keepState);
435 it.remove();
436 }
437 }
438 }
439 }
440
441 /**
442 * Restore the persisted timers for a given ejb container
443 *
444 * @param containerId The ejb container id
445 * @param loader The classloader to use for loading the timers
446 */
447 public void restoreTimers(ObjectName containerId, ClassLoader loader) throws IllegalStateException
448 {
449 assert persistencePolicy != null : "persistencePolicy is not set";
450
451 // find out all the persisted handles, for the specified container
452 List handles = persistencePolicy.listTimerHandles(containerId, loader);
453
454 if (handles.isEmpty() == false)
455 {
456 // first remove the persisted handles from the db
457 for (Iterator i = handles.iterator(); i.hasNext(); )
458 {
459 TimerHandleImpl handle = (TimerHandleImpl)i.next();
460 persistencePolicy.deleteTimer(handle.getTimerId(), handle.getTimedObjectId());
461 }
462
463 // make a second pass to re-create the timers; use the container
464 // itself to retrieve the correct TimerService/ for each handle,
465 // then use the standard ejb timer API to recreate the timer
466 for (Iterator i = handles.iterator(); i.hasNext(); )
467 {
468 TimerHandleImpl handle = (TimerHandleImpl)i.next();
469 try
470 {
471 TimedObjectId targetId = handle.getTimedObjectId();
472 ContainerMBean container = (ContainerMBean)MBeanProxyExt.create(ContainerMBean.class, containerId, server);
473 TimerService timerService = container.getTimerService(targetId.getInstancePk());
474 timerService.createTimer(handle.getFirstTime(), handle.getPeriode(), handle.getInfo());
475 }
476 catch (Exception e)
477 {
478 log.warn("Unable to restore timer record: " + handle, e);
479 }
480 }
481 }
482 }
483
484 // EJBTimerServiceImplMbean operations ---------------------------
485
486 /**
487 * List the timers registered with all TimerService objects
488 *
489 * @jmx.managed-operation
490 */
491 public String listTimers()
492 {
493 StringBuffer retBuffer = new StringBuffer();
494 Iterator it = timerServiceMap.entrySet().iterator();
495 while (it.hasNext())
496 {
497 Map.Entry entry = (Map.Entry)it.next();
498 TimedObjectId timedObjectId = (TimedObjectId)entry.getKey();
499 retBuffer.append(timedObjectId + "\n");
500
501 TimerServiceImpl timerService = (TimerServiceImpl)entry.getValue();
502 Collection col = timerService.getAllTimers();
503 for (Iterator iterator = col.iterator(); iterator.hasNext();)
504 {
505 TimerImpl timer = (TimerImpl)iterator.next();
506 TimerHandleImpl handle = new TimerHandleImpl(timer);
507 retBuffer.append(" handle: " + handle + "\n");
508 retBuffer.append(" " + timer + "\n");
509 }
510 }
511 return retBuffer.toString();
512 }
513
514 }