blob: 2a2c722f4f40e1e2f3fc449004e5254d492d767d [file] [log] [blame]
/*
* 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();
}