| /* |
| * 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 |
| try { |
| 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; |
| |
| registerTimerDataSynchronization(); |
| |
| } catch (final TimerExpiredException e) { |
| setExpired(true); |
| log.warning("Timer " + trigger + " is expired and will never trigger."); |
| |
| } 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) { |
| log.warning(e.getLocalizedMessage(), 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(); |
| } |