/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.ejb.containers;
import java.io.Serializable;
import java.io.IOException;
import java.io.ObjectStreamException;
import java.util.Date;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
import java.util.HashSet;
import javax.ejb.EJBContext;
import javax.ejb.EntityContext;
import javax.ejb.CreateException;
import javax.ejb.FinderException;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;
import javax.ejb.TimerConfig;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.PersistenceContext;
import java.util.logging.Logger;
import java.util.logging.Level;
import com.sun.logging.LogDomains;
import com.sun.ejb.containers.EjbContainerUtilImpl;
import com.sun.enterprise.deployment.EjbDescriptor;
import javax.naming.InitialContext;
import javax.sql.DataSource;
import java.sql.Connection;
/**
* TimerBean is a facade for the persistent state of an EJB Timer.
* It is part of the EJB container and is implemented using
* Java Persistence API. The standard JPA behavior is useful in
* implementing the transactional properties of EJB timers.
* When an EJB timer is created by an application, it is not
* eligible for expiration until the transaction commits.
* Likewise, if a timer is cancelled and the transaction rolls
* back, the timer must be reactivated.
* To accomplish this, TimerBean registers callbacks with the
* transaction manager and interacts with the EJBTimerService
* accordingly.
*
* @author Kenneth Saks
* @author Marina Vatkina
*/
@Stateless
public class TimerBean implements TimerLocal {
private static final Logger logger = LogDomains.getLogger(TimerBean.class, LogDomains.EJB_LOGGER);
private EJBContextImpl context_;
@PersistenceContext(unitName="__EJB__Timer__App")
private EntityManager em;
// Find Timer by Id
public TimerState findTimer(TimerPrimaryKey timerId) {
return em.find(TimerState.class, timerId);
}
//
// Query methods for timer ids
//
public Set findTimerIdsByContainer(long containerId) {
Query q = em.createNamedQuery("findTimerIdsByContainer");
q.setParameter(1, containerId);
return toPKeys(q.getResultList());
}
public Set findTimerIdsByContainerAndState
(long containerId, int state) {
Query q = em.createNamedQuery("findTimerIdsByContainerAndState");
q.setParameter(1, containerId);
q.setParameter(2, state);
return toPKeys(q.getResultList());
}
public Set findTimerIdsByContainerAndOwner
(long containerId, String ownerId) {
Query q = em.createNamedQuery("findTimerIdsByContainerAndOwner");
q.setParameter(1, containerId);
q.setParameter(2, ownerId);
return toPKeys(q.getResultList());
}
public Set findTimerIdsByContainerAndOwnerAndState
(long containerId, String ownerId, int state) {
Query q = em.createNamedQuery("findTimerIdsByContainerAndOwnerAndState");
q.setParameter(1, containerId);
q.setParameter(2, ownerId);
q.setParameter(3, state);
return toPKeys(q.getResultList());
}
public Set findTimerIdsByOwner(String ownerId) {
Query q = em.createNamedQuery("findTimerIdsByOwner");
q.setParameter(1, ownerId);
return toPKeys(q.getResultList());
}
public Set findTimerIdsByOwnerAndState
(String ownerId, int state) {
Query q = em.createNamedQuery("findTimerIdsByOwnerAndState");
q.setParameter(1, ownerId);
q.setParameter(2, state);
return toPKeys(q.getResultList());
}
//
// Query methods for timer beans
// XXX These methods return Sets XXX
//
public Set findTimersByContainer(long containerId) {
Query q = em.createNamedQuery("findTimersByContainer");
q.setParameter(1, containerId);
return new HashSet(q.getResultList());
}
public Set findTimersByContainerAndState
(long containerId, int state) {
Query q = em.createNamedQuery("findTimersByContainerAndState");
q.setParameter(1, containerId);
q.setParameter(2, state);
return new HashSet(q.getResultList());
}
public Set findTimersByContainerAndOwner
(long containerId, String ownerId) {
Query q = em.createNamedQuery("findTimersByContainerAndOwner");
q.setParameter(1, containerId);
q.setParameter(2, ownerId);
return new HashSet(q.getResultList());
}
public Set findTimersByContainerAndOwnerAndState
(long containerId, String ownerId, int state) {
Query q = em.createNamedQuery("findTimersByContainerAndOwnerAndState");
q.setParameter(1, containerId);
q.setParameter(2, ownerId);
q.setParameter(3, state);
return new HashSet(q.getResultList());
}
private Set findTimersByOwner(String ownerId) {
Query q = em.createNamedQuery("findTimersByOwner");
q.setParameter(1, ownerId);
return new HashSet(q.getResultList());
}
public Set findTimersByOwnerAndState
(String ownerId, int state) {
Query q = em.createNamedQuery("findTimersByOwnerAndState");
q.setParameter(1, ownerId);
q.setParameter(2, state);
return new HashSet(q.getResultList());
}
//
// Query methods for timer counts
//
public int countTimersByApplication(long applicationId) {
Query q = em.createNamedQuery("countTimersByApplication");
q.setParameter(1, applicationId);
return ((Number)q.getSingleResult()).intValue();
}
public int countTimersByContainer(long containerId) {
Query q = em.createNamedQuery("countTimersByContainer");
q.setParameter(1, containerId);
return ((Number)q.getSingleResult()).intValue();
}
public int countTimersByContainerAndState
(long containerId, int state) {
Query q = em.createNamedQuery("countTimersByContainerAndState");
q.setParameter(1, containerId);
q.setParameter(2, state);
return ((Number)q.getSingleResult()).intValue();
}
public int countTimersByContainerAndOwner
(long containerId, String ownerId) {
Query q = em.createNamedQuery("countTimersByContainerAndOwner");
q.setParameter(1, containerId);
q.setParameter(2, ownerId);
return ((Number)q.getSingleResult()).intValue();
}
public int countTimersByContainerAndOwnerAndState
(long containerId, String ownerId, int state) {
Query q = em.createNamedQuery("countTimersByContainerAndOwnerAndState");
q.setParameter(1, containerId);
q.setParameter(2, ownerId);
q.setParameter(3, state);
return ((Number)q.getSingleResult()).intValue();
}
public int countTimersByOwner(String ownerId) {
Query q = em.createNamedQuery("countTimersByOwner");
q.setParameter(1, ownerId);
return ((Number)q.getSingleResult()).intValue();
}
public int countTimersByOwnerAndState
(String ownerId, int state) {
Query q = em.createNamedQuery("countTimersByOwnerAndState");
q.setParameter(1, ownerId);
q.setParameter(2, state);
return ((Number)q.getSingleResult()).intValue();
}
//
// These data members contain derived state for
// some immutable fields.
//
// deserialized state from blob
private boolean blobLoaded_;
private Object timedObjectPrimaryKey_;
private transient Serializable info_;
public TimerState createTimer
(String timerId, long containerId, long applicationId, String ownerId,
Object timedObjectPrimaryKey,
Date initialExpiration, long intervalDuration,
TimerSchedule schedule, TimerConfig timerConfig)
throws CreateException {
TimerState timer = null;
try {
timer = new TimerState (timerId, containerId, applicationId, ownerId,
timedObjectPrimaryKey, initialExpiration,
intervalDuration, schedule, timerConfig.getInfo());
} catch(IOException ioe) {
CreateException ce = new CreateException();
ce.initCause(ioe);
throw ce;
}
if( logger.isLoggable(Level.FINE) ) {
logger.log(Level.FINE, "TimerBean.createTimer() ::timerId=" +
timer.getTimerId() + " ::containerId=" + timer.getContainerId() +
" ::applicationId=" + timer.getApplicationId() +
" ::timedObjectPK=" + timedObjectPrimaryKey +
" ::info=" + timerConfig.getInfo() +
" ::schedule=" + timer.getSchedule() +
" ::persistent=" + timerConfig.isPersistent() +
" ::initialExpiration=" + initialExpiration +
" ::intervalDuration=" + intervalDuration +
" :::state=" + timer.stateToString() +
" :::creationTime=" + timer.getCreationTime() +
" :::ownerId=" + timer.getOwnerId());
}
//
// Only proceed with transactional semantics if this timer
// is owned by the current server instance. NOTE that this
// will *ALWAYS* be the case for timers created from EJB
// applications via the javax.ejb.EJBTimerService.create methods.
//
// For testing purposes, ejbCreate takes an ownerId parameter,
// which allows us to easily simulate other server instances
// by creating timers for them. In those cases, we don't need
// the timer transaction semantics and ejbTimeout logic. Simulating
// the creation of timers for the same application and different
// server instances from a script is difficult since the
// containerId is not generated until after deployment.
//
try {
getEJBTimerService().addTimerSynchronization(context_,
timerId, initialExpiration, containerId, ownerId);
} catch(Exception e) {
CreateException ce = new CreateException();
ce.initCause(e);
throw ce;
}
em.persist(timer);
return timer;
}
private String getOwnerIdOfThisServer() {
return getEJBTimerService().getOwnerIdOfThisServer();
}
private static EJBTimerService getEJBTimerService() {
return EjbContainerUtilImpl.getInstance().getEJBTimerService();
}
public void setSessionContext(SessionContext context) {
context_ = (EJBContextImpl) context;
}
public void remove(TimerPrimaryKey timerId) {
TimerState timer = em.find(TimerState.class, timerId);
if (timer != null) {
em.remove(timer);
}
}
public void remove(Set<TimerPrimaryKey> timerIds) {
for(TimerPrimaryKey timerId: timerIds) {
try {
remove(timerId);
} catch(Exception e) {
logger.log(Level.FINE, "Cannot remove timer " + timerId +
" for unknown container ", e);
}
}
}
public void cancel(TimerPrimaryKey timerId)
throws FinderException, Exception {
TimerState timer = em.find(TimerState.class, timerId);
// If timer is null need to throw a FinderException so
// that the caller can handle it.
if( timer == null) {
throw new FinderException("timer " + timerId + " does not exist");
}
// First set the timer to the cancelled state. This step is
// performed whether or not the current server instance owns
// the timer.
if( timer.getState() == TimerState.CANCELLED ) {
// already cancelled or removed
return;
}
timer.setState(TimerState.CANCELLED);
getEJBTimerService().cancelTimerSynchronization(context_, timerId,
timer.getContainerId(), timer.getOwnerId());
// XXX ???? WHY WAS IT: NOTE that it's the caller's responsibility to call remove().
em.remove(timer);
return;
}
public void cancelTimers(Collection<TimerState> timers) {
for(TimerState timer : timers) {
try {
em.remove(timer);
} catch(Exception e) {
logger.log(Level.WARNING, "ejb.cancel_entity_timer",
new Object[] { timer.getTimerId() });
logger.log(Level.WARNING, "", e);
}
}
}
private Set toPKeys(Collection ids) {
Set pkeys = new HashSet();
for(Iterator iter = ids.iterator(); iter.hasNext();) {
pkeys.add(new TimerPrimaryKey((String) iter.next()));
}
return pkeys;
}
//
// Other query methods for timer ids
//
public Set findActiveTimerIdsByContainer(long containerId) {
return findTimerIdsByContainerAndState(containerId,
TimerState.ACTIVE);
}
public Set findCancelledTimerIdsByContainer(long containerId) {
return findTimerIdsByContainerAndState(containerId,
TimerState.CANCELLED);
}
public Set findTimerIdsOwnedByThisServerByContainer
(long containerId) {
return findTimerIdsByContainerAndOwner
(containerId, getOwnerIdOfThisServer());
}
public Set findActiveTimerIdsOwnedByThisServerByContainer
(long containerId) {
return findTimerIdsByContainerAndOwnerAndState
(containerId, getOwnerIdOfThisServer(),
TimerState.ACTIVE);
}
public Set findCancelledTimerIdsOwnedByThisServerByContainer
(long containerId) {
return findTimerIdsByContainerAndOwnerAndState
(containerId, getOwnerIdOfThisServer(),
TimerState.CANCELLED);
}
public Set findTimerIdsOwnedByThisServer() {
return findTimerIdsByOwner(getOwnerIdOfThisServer());
}
public Set findActiveTimerIdsOwnedByThisServer() {
return findTimerIdsByOwnerAndState
(getOwnerIdOfThisServer(),
TimerState.ACTIVE);
}
public Set findCancelledTimerIdsOwnedByThisServer() {
return findTimerIdsByOwnerAndState
(getOwnerIdOfThisServer(),
TimerState.CANCELLED);
}
public Set findTimerIdsOwnedBy(String ownerId) {
return findTimerIdsByOwner(ownerId);
}
public Set findActiveTimerIdsOwnedBy(String ownerId) {
return findTimerIdsByOwnerAndState(ownerId,
TimerState.ACTIVE);
}
public Set findCancelledTimerIdsOwnedBy(String ownerId) {
return findTimerIdsByOwnerAndState(ownerId,
TimerState.CANCELLED);
}
//
// Helper query methods for timer beans
//
public Set findActiveTimersByContainer(long containerId) {
return findTimersByContainerAndState(containerId,
TimerState.ACTIVE);
}
public Set findCancelledTimersByContainer(long containerId) {
return findTimersByContainerAndState
(containerId, TimerState.CANCELLED);
}
public Set findTimersOwnedByThisServerByContainer
(long containerId) {
return findTimersByContainerAndOwner
(containerId, getOwnerIdOfThisServer());
}
public Set findActiveTimersOwnedByThisServerByContainer
(long containerId) {
return findTimersByContainerAndOwnerAndState
(containerId, getOwnerIdOfThisServer(),
TimerState.ACTIVE);
}
public Set findCancelledTimersOwnedByThisServerByContainer
(long containerId) {
return findTimersByContainerAndOwnerAndState
(containerId, getOwnerIdOfThisServer(),
TimerState.CANCELLED);
}
public Set findTimersOwnedByThisServer() {
return findTimersByOwner(getOwnerIdOfThisServer());
}
public Set findActiveTimersOwnedByThisServer() {
return findTimersByOwnerAndState
(getOwnerIdOfThisServer(),
TimerState.ACTIVE);
}
public Set findCancelledTimersOwnedByThisServer() {
return findTimersByOwnerAndState
(getOwnerIdOfThisServer(),
TimerState.CANCELLED);
}
public Set findTimersOwnedBy(String ownerId) {
return findTimersByOwner(ownerId);
}
public Set findActiveTimersOwnedBy(String ownerId) {
return findTimersByOwnerAndState(ownerId,
TimerState.ACTIVE);
}
public Set findCancelledTimersOwnedBy(String ownerId) {
return findTimersByOwnerAndState(ownerId,
TimerState.CANCELLED);
}
//
// Helper query methods for timer counts
//
public int countActiveTimersByContainer(long containerId) {
return countTimersByContainerAndState(containerId,
TimerState.ACTIVE);
}
public int countCancelledTimersByContainer(long containerId) {
return countTimersByContainerAndState(containerId,
TimerState.CANCELLED);
}
public int countTimersOwnedByThisServerByContainer
(long containerId) {
return countTimersByContainerAndOwner
(containerId, getOwnerIdOfThisServer());
}
public int countActiveTimersOwnedByThisServerByContainer
(long containerId) {
return countTimersByContainerAndOwnerAndState
(containerId, getOwnerIdOfThisServer(),
TimerState.ACTIVE);
}
public int countCancelledTimersOwnedByThisServerByContainer
(long containerId) {
return countTimersByContainerAndOwnerAndState
(containerId, getOwnerIdOfThisServer(),
TimerState.CANCELLED);
}
public int countTimersOwnedByThisServer() {
return countTimersByOwner(getOwnerIdOfThisServer());
}
public String[] countTimersOwnedByServerIds(String[] serverIds) {
String[] totalTimers = new String[ serverIds.length ];
int i = 0;
for (String serverId : serverIds) {
totalTimers[i] = Integer.toString(
countTimersOwnedBy(serverId));
i++;
}
return totalTimers;
}
public int countActiveTimersOwnedByThisServer() {
return countTimersByOwnerAndState
(getOwnerIdOfThisServer(),
TimerState.ACTIVE);
}
public int countCancelledTimersOwnedByThisServer() {
return countTimersByOwnerAndState
(getOwnerIdOfThisServer(),
TimerState.CANCELLED);
}
public int countTimersOwnedBy(String ownerId) {
return countTimersByOwner(ownerId);
}
public int countActiveTimersOwnedBy(String ownerId) {
return countTimersByOwnerAndState(ownerId,
TimerState.ACTIVE);
}
public int countCancelledTimersOwnedBy(String ownerId) {
return countTimersByOwnerAndState(ownerId,
TimerState.CANCELLED);
}
public boolean checkStatus(String resourceJndiName,
boolean checkDatabase) {
boolean success = false;
Connection connection = null;
try {
InitialContext ic = new InitialContext();
DataSource dataSource = (DataSource) ic.lookup(resourceJndiName);
if( checkDatabase ) {
connection = dataSource.getConnection();
connection.close();
connection = null;
// Now try to a query that will access the timer table itself.
// Use a query that won't return a lot of data(even if the
// table is large) to reduce the overhead of this check.
countTimersByContainer(0);
}
success = true;
} catch(Exception e) {
logger.log(Level.WARNING, "ejb.timer_service_init_error",
"");
// Log exception itself at FINE level. The most likely cause
// is a connection error when the database is not started. This
// is already logged twice by the jdbc layer.
logger.log(Level.FINE, "ejb.timer_service_init_error", e);
} finally {
if( connection != null ) {
try {
connection.close();
} catch(Exception e) {
logger.log(Level.FINE, "timer connection close exception",
e);
}
}
}
return success;
}
public int migrateTimers(String fromOwnerId, String toOwnerId) {
Query q = em.createNamedQuery("updateTimersFromOwnerToNewOwner");
q.setParameter("fromOwner", fromOwnerId);
q.setParameter("toOwner", toOwnerId);
return q.executeUpdate();
}
public int deleteTimersByContainer(long containerId) {
Query q = em.createNamedQuery("deleteTimersByContainer");
q.setParameter("containerId", containerId);
return q.executeUpdate();
}
public int deleteTimersByApplication(long applicationId) {
Query q = em.createNamedQuery("deleteTimersByApplication");
q.setParameter("applicationId", applicationId);
return q.executeUpdate();
}
public static void testCreate(String timerId, EJBContext context,
String ownerId,
Date initialExpiration,
long intervalDuration,
Serializable info) throws CreateException {
TimerLocal timerLocal = getEJBTimerService().getTimerLocal();
EjbDescriptor ejbDesc = ((EJBContextImpl) context).getContainer().getEjbDescriptor();
long containerId = ejbDesc.getUniqueId();
long applicationId = ejbDesc.getApplication().getUniqueId();
Object timedObjectPrimaryKey = (context instanceof EntityContext) ?
((EntityContext)context).getPrimaryKey() : null;
TimerConfig timerConfig = new TimerConfig();
timerConfig.setInfo(info);
timerLocal.createTimer(timerId, containerId, applicationId, ownerId,
timedObjectPrimaryKey, initialExpiration,
intervalDuration, null, timerConfig);
return;
}
public static void testMigrate(String fromOwnerId) {
EJBTimerService ejbTimerService = getEJBTimerService();
ejbTimerService.migrateTimers(fromOwnerId);
}
// XXX Called by TimerState via a static call
public static BaseContainer getContainer(long containerId) {
return EjbContainerUtilImpl.getInstance().getContainer(containerId);
}
// Called by TimerWelcomeServlet
public Set findActiveNonPersistentTimersOwnedByThisServer() {
EJBTimerService ejbTimerService = getEJBTimerService();
return ejbTimerService.getActiveTimerIdsByThisServer();
}
/**
* To be used to read in TimerBean.Blob and replace with TimerState.Blob
* on v2.x upgrade
*/
public static class Blob implements Serializable {
private byte[] primaryKeyBytes_ = null;
private byte[] infoBytes_ = null;
private static final long serialVersionUID = 9167806434435988868L;
private Object readResolve() throws ObjectStreamException {
return new TimerState.Blob(primaryKeyBytes_, infoBytes_);
}
}
}