/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.openejb.core.timer; import org.apache.openejb.BeanContext; import org.apache.openejb.MethodContext; import org.apache.openejb.loader.SystemInstance; import org.apache.openejb.spi.ContainerSystem; import org.apache.openejb.util.LogCategory; import org.apache.openejb.util.Logger; import org.apache.openejb.quartz.Scheduler; import org.apache.openejb.quartz.SchedulerException; import org.apache.openejb.quartz.Trigger; import org.apache.openejb.quartz.TriggerKey; import org.apache.openejb.quartz.impl.triggers.AbstractTrigger; import javax.ejb.EJBException; import javax.ejb.Timer; import javax.ejb.TimerConfig; import javax.transaction.Status; import javax.transaction.Synchronization; import javax.transaction.Transaction; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.Method; import java.util.Date; import java.util.Iterator; import java.util.Map; public abstract class TimerData implements Serializable { private static final long serialVersionUID = 1L; public static final String OPEN_EJB_TIMEOUT_TRIGGER_NAME_PREFIX = "OPEN_EJB_TIMEOUT_TRIGGER_"; public static final String OPEN_EJB_TIMEOUT_TRIGGER_GROUP_NAME = "OPEN_EJB_TIMEOUT_TRIGGER_GROUP"; private static final Logger log = Logger.getInstance(LogCategory.TIMER, "org.apache.openejb.util.resources"); private long id; private EjbTimerServiceImpl timerService; private String deploymentId; private Object primaryKey; private Method timeoutMethod; private Object info; private boolean persistent; private boolean autoScheduled; protected AbstractTrigger<?> trigger; protected Scheduler scheduler; public void setScheduler(final Scheduler scheduler) { this.scheduler = scheduler; } // EJB Timer object given to user code private Timer timer; /** * Is this a new timer? A new timer must be scheduled with the java.util.Timer * when the transaction commits. */ private boolean newTimer; /** * Has this timer been cancelled? A canceled timer must be rescheduled with the * java.util.Timer if the transaction is rolled back */ private boolean cancelled; private boolean stopped; /** * Has this timer been registered with the transaction for callbacks? We remember * when we are registered to avoid multiple registrations. */ private boolean synchronizationRegistered; /** * Used to set timer to expired state after the timeout callback method has been successfully invoked. * only apply to * 1, Single action timer * 2, Calendar timer there are no future timeout. */ private boolean expired; public TimerData(final long id, final EjbTimerServiceImpl timerService, final String deploymentId, final Object primaryKey, final Method timeoutMethod, final TimerConfig timerConfig) { this.id = id; this.timerService = timerService; this.deploymentId = deploymentId; this.primaryKey = primaryKey; this.info = timerConfig == null ? null : timerConfig.getInfo(); this.persistent = timerConfig == null || timerConfig.isPersistent(); this.timer = new TimerImpl(this); this.timeoutMethod = timeoutMethod; } private void writeObject(final ObjectOutputStream out) throws IOException { doWriteObject(out); } protected void doWriteObject(final ObjectOutputStream out) throws IOException { out.writeLong(id); out.writeUTF(deploymentId); out.writeBoolean(persistent); out.writeBoolean(autoScheduled); out.writeObject(timer); out.writeObject(primaryKey); out.writeObject(timerService); out.writeObject(info); out.writeObject(trigger); out.writeUTF(timeoutMethod.getName()); } private void readObject(final ObjectInputStream in) throws IOException { doReadObject(in); } protected void doReadObject(final ObjectInputStream in) throws IOException { id = in.readLong(); deploymentId = in.readUTF(); persistent = in.readBoolean(); autoScheduled = in.readBoolean(); try { timer = (Timer) in.readObject(); primaryKey = in.readObject(); timerService = (EjbTimerServiceImpl) in.readObject(); info = in.readObject(); trigger = AbstractTrigger.class.cast(in.readObject()); } catch (final ClassNotFoundException e) { throw new IOException(e); } final String mtd = in.readUTF(); final BeanContext beanContext = SystemInstance.get().getComponent(ContainerSystem.class).getBeanContext(deploymentId); scheduler = timerService.getScheduler(); for (final Iterator<Map.Entry<Method, MethodContext>> it = beanContext.iteratorMethodContext(); it.hasNext(); ) { final MethodContext methodContext = it.next().getValue(); /* this doesn't work in all cases if (methodContext.getSchedules().isEmpty()) { continue; } */ final Method method = methodContext.getBeanMethod(); if (method != null && method.getName().equals(mtd)) { // maybe we should check parameters too setTimeoutMethod(method); break; } } } public void stop() { if (trigger != null) { try { final Scheduler s = timerService.getScheduler(); if (!s.isShutdown()) { if (!isPersistent()) { s.unscheduleJob(trigger.getKey()); } else { s.pauseTrigger(trigger.getKey()); } } } catch (final SchedulerException e) { throw new EJBException("fail to cancel the timer", e); } } cancelled = true; stopped = true; } public long getId() { return id; } public String getDeploymentId() { return deploymentId; } public Object getPrimaryKey() { return primaryKey; } public Object getInfo() { return info; } public Timer getTimer() { return timer; } public boolean isNewTimer() { return newTimer; } public void newTimer() { //Initialize the Quartz Trigger trigger = initializeTrigger(); trigger.computeFirstFireTime(null); trigger.setGroup(OPEN_EJB_TIMEOUT_TRIGGER_GROUP_NAME); trigger.setName(OPEN_EJB_TIMEOUT_TRIGGER_NAME_PREFIX + deploymentId + "_" + id); newTimer = true; try { registerTimerDataSynchronization(); } catch (final TimerStoreException e) { throw new EJBException("Failed to register new timer data synchronization", e); } } public boolean isCancelled() { return cancelled; } public void cancel() { if (stopped) { return; } timerService.cancelled(TimerData.this); if (trigger != null) { try { final Scheduler s = timerService.getScheduler(); if (!s.isShutdown()) { s.unscheduleJob(trigger.getKey()); } } catch (final SchedulerException e) { throw new EJBException("fail to cancel the timer", e); } } cancelled = true; try { registerTimerDataSynchronization(); } catch (final TimerStoreException e) { throw new EJBException("Failed to register timer data synchronization on cancel", e); } } private void setTimeoutMethod(final Method timeoutMethod) { this.timeoutMethod = timeoutMethod; } public Method getTimeoutMethod() { return timeoutMethod; } private void transactionComplete(final boolean committed) throws TimerStoreException { if (newTimer) { // you are only a new timer once no matter what newTimer = false; // if our new timer was not canceled and the transaction committed if (!isCancelled() && committed) { // schedule the timer with the java.util.Timer timerService.schedule(TimerData.this); } } else { // if the tx was rolled back, reschedule the timer with the java.util.Timer if (!committed) { cancelled = false; timerService.addTimerData(TimerData.this); timerService.schedule(TimerData.this); } } } private void registerTimerDataSynchronization() throws TimerStoreException { if (synchronizationRegistered) { return; } try { final Transaction transaction = timerService.getTransactionManager().getTransaction(); final int status = transaction == null ? Status.STATUS_NO_TRANSACTION : transaction.getStatus(); if (transaction != null && status == Status.STATUS_ACTIVE || status == Status.STATUS_MARKED_ROLLBACK) { transaction.registerSynchronization(new TimerDataSynchronization()); synchronizationRegistered = true; return; } } catch (final Exception e) { log.warning("Unable to register timer data transaction synchronization", e); } // there either wasn't a transaction or registration failed... call transactionComplete directly transactionComplete(true); } public boolean isStopped() { return stopped; } private class TimerDataSynchronization implements Synchronization { @Override public void beforeCompletion() { } @Override public void afterCompletion(final int status) { synchronizationRegistered = false; try { transactionComplete(status == Status.STATUS_COMMITTED); } catch (final TimerStoreException e) { throw new EJBException("Failed on afterCompletion", e); } } } public boolean isPersistent() { return persistent; } public Trigger getTrigger() { if (scheduler != null) { try { final TriggerKey key = new TriggerKey(trigger.getName(), trigger.getGroup()); if (scheduler.checkExists(key)) { return scheduler.getTrigger(key); } } catch (final SchedulerException e) { return null; } } return trigger; } public Date getNextTimeout() { try { // give the trigger 1 ms to init itself to set correct nextTimeout value. Thread.sleep(1); } catch (final InterruptedException e) { log.warning("Interrupted exception when waiting 1ms for the trigger to init", e); } Date nextTimeout = null; if (getTrigger() != null) { nextTimeout = getTrigger().getNextFireTime(); } return nextTimeout; } public long getTimeRemaining() { final Date nextTimeout = getNextTimeout(); return nextTimeout.getTime() - System.currentTimeMillis(); } public boolean isExpired() { return expired; } public void setExpired(final boolean expired) { this.expired = expired; } public abstract TimerType getType(); protected abstract AbstractTrigger<?> initializeTrigger(); }