All Downloads are FREE. Search and download functionalities are using the official Maven repository.

fish.payara.ejb.timer.hazelcast.HazelcastTimerStore Maven / Gradle / Ivy

There is a newer version: 4.1.2.181
Show newest version
/*

 DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.

 Copyright (c) 2015,2016 Payara Foundation and/or its affiliates.
 All rights reserved.

 The contents of this file are subject to the terms of 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.
 */
package fish.payara.ejb.timer.hazelcast;

import com.hazelcast.core.IMap;
import com.sun.ejb.containers.BaseContainer;
import com.sun.ejb.containers.EJBTimerSchedule;
import com.sun.ejb.containers.EJBTimerService;
import com.sun.ejb.containers.RuntimeTimerState;
import com.sun.ejb.containers.TimerPrimaryKey;
import com.sun.enterprise.deployment.MethodDescriptor;
import com.sun.logging.LogDomains;
import fish.payara.nucleus.hazelcast.HazelcastCore;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ejb.EJBException;
import javax.ejb.FinderException;
import javax.ejb.TimerConfig;
import javax.transaction.TransactionManager;
import org.glassfish.ejb.deployment.descriptor.EjbDescriptor;
import org.glassfish.ejb.deployment.descriptor.ScheduledTimerDescriptor;

/**
 *
 * @author steve
 */
public class HazelcastTimerStore extends EJBTimerService {

    private static final String EJB_TIMER_CACHE_NAME = "HZEjbTmerCache";
    private static final String EJB_TIMER_CONTAINER_CACHE_NAME = "HZEjbTmerContainerCache";
    private static final String EJB_TIMER_APPLICAION_CACHE_NAME = "HZEjbTmerApplicationCache";

    private final IMap pkCache;
    private final IMap containerCache;
    private final IMap applicationCache;
    private final String serverName;

    private static final Logger logger
            = LogDomains.getLogger(HazelcastTimerStore.class, LogDomains.EJB_LOGGER);

    static void init(HazelcastCore core) {
        try {
            EJBTimerService.setEJBTimerService(new HazelcastTimerStore(core));
        } catch (Exception ex) {
            Logger.getLogger(HazelcastTimerStore.class.getName()).log(Level.WARNING, "Problem when initialising Timer Store", ex);
        }
    }

    public HazelcastTimerStore(HazelcastCore core) throws Exception {
        pkCache = core.getInstance().getMap(EJB_TIMER_CACHE_NAME);
        containerCache = core.getInstance().getMap(EJB_TIMER_CONTAINER_CACHE_NAME);
        applicationCache = core.getInstance().getMap(EJB_TIMER_APPLICAION_CACHE_NAME);
        serverName = core.getInstance().getCluster().getLocalMember().getStringAttribute(HazelcastCore.INSTANCE_ATTRIBUTE);
        this.ownerIdOfThisServer_ = serverName;
        this.domainName_ = core.getInstance().getConfig().getGroupConfig().getName();
    }

    private void removeTimers(Set timerIdsToRemove) {
        for (TimerPrimaryKey timerPrimaryKey : timerIdsToRemove) {
            removeTimer((HZTimer) pkCache.get(timerPrimaryKey.timerId));
        }
    }

    @Override
    protected void _createTimer(TimerPrimaryKey timerId, long containerId, long applicationId, Object timedObjectPrimaryKey, String server_name, Date initialExpiration, long intervalDuration, EJBTimerSchedule schedule, TimerConfig timerConfig) throws Exception {
        if (timerConfig.isPersistent()) {
            
            pkCache.put(timerId.timerId, new HZTimer(timerId, containerId, applicationId, timedObjectPrimaryKey, this.serverName, ownerIdOfThisServer_, initialExpiration, intervalDuration, schedule, timerConfig));

            // add to container cache
            HashSet keysForContainer = (HashSet) containerCache.get(containerId);
            if (keysForContainer == null) {
                keysForContainer = new HashSet<>();
            }
            keysForContainer.add(timerId);
            containerCache.put(containerId, keysForContainer);

            // add to application cache
            HashSet keysForApp = (HashSet) applicationCache.get(applicationId);
            if (keysForApp == null) {
                keysForApp = new HashSet<>();
            }
            keysForApp.add(timerId);
            applicationCache.put(applicationId, keysForApp);

            TransactionManager tm = ejbContainerUtil.getTransactionManager();
            boolean localTx = tm.getTransaction() == null;

            if (localTx) {
                tm.begin();
            }

            addTimerSynchronization(null,
                    timerId.getTimerId(), initialExpiration,
                    containerId, ownerIdOfThisServer_, true);
            if (localTx) {
                tm.commit();
            }

        } else {
            addTimerSynchronization(null,
                    timerId.getTimerId(), initialExpiration,
                    containerId, ownerIdOfThisServer_, false);
        }
    }

    @Override
    public void destroyAllTimers(long applicationId) {

        // remove all timers
        Set timerIds = (Set) applicationCache.get(applicationId);

        if (timerIds == null || timerIds.isEmpty()) {
            if (logger.isLoggable(Level.INFO)) {
                logger.log(Level.INFO, "No timers to be deleted for id: " + applicationId);
            }
            return;
        }

        for (TimerPrimaryKey timerId : timerIds) {
            pkCache.remove(timerId.timerId);
        }
        logger.log(Level.INFO, "Destroyed {0} timers for application {1}", new Object[]{timerIds.size(), applicationId});
        timerIds.clear();
        applicationCache.remove(applicationId);

    }

    @Override
    public void destroyTimers(long containerId) {
        Set timerIds = (Set) containerCache.get(containerId);

        if (timerIds == null || timerIds.isEmpty()) {
            if (logger.isLoggable(Level.INFO)) {
                logger.log(Level.INFO, "No timers to be deleted for id: " + containerId);
            }
            return;
        }

        logger.log(Level.INFO, "Destroyed {0} timers for container {1}", new Object[]{timerIds.size(), containerId});
        timerIds.clear();
        applicationCache.put(containerId, timerIds);
    }

    @Override
    protected void cancelTimer(TimerPrimaryKey timerId) throws FinderException, Exception {
        // Check non-persistent timers first
        if (!cancelNonPersistentTimer(timerId)) {

            HZTimer timer = (HZTimer) pkCache.get(timerId.timerId);
            if (timer != null) {

                TransactionManager tm = ejbContainerUtil.getTransactionManager();
                boolean localTx = tm.getTransaction() == null;
                if (localTx) {
                    tm.begin();
                }
                EJBTimerService.getEJBTimerService().cancelTimerSynchronization(null, timerId,
                        timer.getContainerId(), timer.getOwnerId());
                if (localTx) {
                    tm.commit();
                }
            }
        }

    }

    @Override
    protected Serializable getInfo(TimerPrimaryKey timerId) throws FinderException {
        // Check non-persistent timers first
        if (!super.isPersistent(timerId)) {
            return super.getInfo(timerId);
        }

        HZTimer timer = (HZTimer) pkCache.get(timerId.timerId);

        if (timer == null) {
            throw new FinderException("Unable to find timer " + timerId);
        } else {
            return timer.getTimerConfig().getInfo();
        }
    }

    @Override
    protected Date getNextTimeout(TimerPrimaryKey timerId) throws FinderException {
        // Check non-persistent timers first
        RuntimeTimerState rt = getNonPersistentTimer(timerId);
        if (rt != null) {
            return _getNextTimeout(rt);
        }

        // It's a persistent timer
        HZTimer timer = getPersistentTimer(timerId);
        Date initialExpiration = timer.getInitialExpiration();
        long intervalDuration = timer.getIntervalDuration();
        EJBTimerSchedule ts = timer.getSchedule();

        Date nextTimeout = null;
        if (ts != null) {
            nextTimeout = getNextScheduledTimeout(ts);
            // The caller is responsible to return 0 or -1 for the time remaining....

        } else if (intervalDuration > 0) {
            nextTimeout = calcNextFixedRateExpiration(initialExpiration,
                    intervalDuration);
        } else {
            nextTimeout = initialExpiration;
        }

        return nextTimeout;
    }

    @Override
    protected void cancelTimersByKey(long containerId, Object primaryKey) {

        // Get *all* timers for this entity bean identity.  This includes
        // even timers *not* owned by this server instance, but that 
        // are associated with the same entity bean and primary key.
        Collection timers = (Collection) containerCache.get(containerId);

        if (timers != null) {
            HashSet timersToCancel = new HashSet<>();
            for (TimerPrimaryKey timer : timers) {
                HZTimer hzTimer = (HZTimer) pkCache.get(((TimerPrimaryKey)primaryKey).timerId);
                if (hzTimer != null && hzTimer.getTimedObjectPk().equals(primaryKey)) {
                    timersToCancel.add(hzTimer);
                }
            }

            for (HZTimer hZTimer : timersToCancel) {
                removeTimer(hZTimer);
            }
        }
    }

    @Override
    public void createSchedules(long containerId, long applicationId, Map> methodDescriptorSchedules, String server_name) {
        TransactionManager tm = ejbContainerUtil.getTransactionManager();
        try {
            tm.begin();
            Collection keys = (Collection) containerCache.get(containerId);

            if (keys == null || keys.isEmpty()) {
                // No timers owned by this EJB
                createSchedules(containerId, applicationId, methodDescriptorSchedules, null, server_name, false, true);
            }

            tm.commit();

        } catch (Exception e) {
            recoverAndCreateSchedulesError(e, tm);
        }
    }

    @Override
    public void createSchedulesOnServer(EjbDescriptor ejbDescriptor, String server_name) {
        Map> schedules
                = new HashMap>();
        for (ScheduledTimerDescriptor schd : ejbDescriptor.getScheduledTimerDescriptors()) {
            MethodDescriptor method = schd.getTimeoutMethod();
            if (method != null && schd.getPersistent()) {
                if (logger.isLoggable(Level.FINE)) {
                    logger.log(Level.FINE, "... processing " + method);
                }

                List list = schedules.get(method);
                if (list == null) {
                    list = new ArrayList();
                    schedules.put(method, list);
                }
                list.add(schd);
            }
        }

        if (logger.isLoggable(Level.FINE)) {
            logger.log(Level.FINE, "EJBTimerService - creating schedules for " + ejbDescriptor.getUniqueId());
        }
        createSchedules(ejbDescriptor.getUniqueId(), ejbDescriptor.getApplication().getUniqueId(), schedules, server_name);

        if (logger.isLoggable(Level.FINE)) {
            logger.log(Level.FINE, "EJBTimerService - finished processing schedules for BEAN ID: " + ejbDescriptor.getUniqueId());
        }
    }

    @Override
    protected void expungeTimer(TimerPrimaryKey timerId, boolean removeTimerBean) {

        HZTimer timer = (HZTimer) pkCache.get(timerId.timerId);
        if (timer != null) {
            removeTimer(timer);
        }

        // And finish in the superclass...
        super.expungeTimer(timerId, removeTimerBean);
    }

    @Override
    protected Collection getTimerIds(Collection containerIds) {
        Collection result = super.getTimerIds(containerIds);
        for (Long containerId : containerIds) {
            Collection colKeys = (Collection) containerCache.get(containerId);
            if (colKeys != null) {
                result.addAll(colKeys);
            }
        }
        return result;
    }

    @Override
    protected Collection getTimerIds(long containerId, Object timedObjectPrimaryKey) {
        // The results should include all timers for the given ejb
        // and/or primary key, including timers owned by other server instances.

        // @@@ Might want to consider cases where we can use 
        // timer cache to avoid some database access in PE/SE, or
        // even in EE with the appropriate consistency tradeoff.              
        Collection timerIdsForTimedObject
                = new HashSet();

        if (timedObjectPrimaryKey == null) {
            Collection contKeys = (Collection) containerCache.get(containerId);
            if (contKeys != null) {
                timerIdsForTimedObject.addAll(contKeys);
            }
        } else {

            Collection timersForTimedObject = (Collection) containerCache.get(containerId);
            if (timersForTimedObject != null) {
                for (TimerPrimaryKey timer : timersForTimedObject) {
                    HZTimer hzTimer = (HZTimer) pkCache.get(timer.timerId);
                    if (hzTimer != null && hzTimer.getTimedObjectPk().equals(timedObjectPrimaryKey)) {
                        timerIdsForTimedObject.add(timer);
                    }

                }
            }
        }

        // Add active non-persistent timer ids
        timerIdsForTimedObject.addAll(super.getTimerIds(containerId, null));

        return timerIdsForTimedObject;
    }

    @Override
    protected EJBTimerSchedule getTimerSchedule(TimerPrimaryKey timerId) throws FinderException {
        // Check non-persistent timers first
        EJBTimerSchedule ts = null;
        if (!super.isPersistent(timerId)) {
            ts = super.getTimerSchedule(timerId);
        } else {

            // @@@ We can't assume this server instance owns the persistent timer
            // so always ask the database.  Investigate possible use of
            // timer cache for optimization.
            HZTimer timer = getPersistentTimer(timerId);
            ts = timer.getSchedule();
        }
        return ts;
    }

    @Override
    public boolean isPersistent() {
        return true;
    }

    @Override
    protected boolean isCancelledByAnotherInstance(RuntimeTimerState timerState) {
        if (timerState.isPersistent() && !isDas) {

            if (logger.isLoggable(Level.FINE)) {
                logger.log(Level.FINE, "For Timer :" + timerState.getTimerId()
                        + ": check the database to ensure that the timer is still "
                        + " valid, before delivering the ejbTimeout call");
            }

            if (!checkForTimerValidity(timerState.getTimerId())) {
                // The timer for which a ejbTimeout is about to be delivered
                // is not present in the database. This could happen in the 
                // SE/EE case as other server instances (other than the owner)
                // could call a cancel on the timer - deleting the timer from 
                // the database.
                // Also it is possible that the timer is now owned by some other
                // server instance
                return true;
            }
        }

        return false;
    }

    @Override
    protected boolean isPersistent(TimerPrimaryKey timerId) throws FinderException {
        // Check non-persistent timers first
        if (!super.isPersistent(timerId)) {
            // Found and active
            return false;
        }

        // @@@ We can't assume this server instance owns the persistent timer
        // so always ask the database.  Investigate possible use of
        // timer cache for optimization.
        getPersistentTimer(timerId);
        // If we reached here, it means the timer is persistent
        return true;
    }

    @Override
    protected boolean isValidTimerForThisServer(TimerPrimaryKey timerId, RuntimeTimerState timerState) {
        boolean result = true;
        if (timerState.isPersistent()) {
            HZTimer timer = (HZTimer) pkCache.get(timerId.timerId);
            if (timer == null || !timer.getMemberName().equals(serverName)) {
                result = false;
            }
        }
        return result;
    }

    @Override
    public String[] listTimers(String[] serverIds) {
        String result[] = new String[serverIds.length];

        // count all server ids
        HashMap counts = new HashMap<>();
        for (Object timer : pkCache.values()) {
            HZTimer hzTimer = (HZTimer) timer;
            String serverName = hzTimer.getMemberName();
            Long val = counts.get(serverName);
            if (val == null) {
                val = new Long(0);
            }
            counts.put(serverName, new Long(val.intValue() + 1));
        }

        // copy counts into the strings
        for (int i = 0; i < serverIds.length; i++) {
            Long count = counts.get(serverIds[i]);
            if (count != null) {
                result[i] = counts.get(serverIds[i]).toString();
            } else {
                result[i] = "0";
            }
        }
        return result;
    }

    @Override
    public int migrateTimers(String fromOwnerId) {
        String ownerIdOfThisServer = getOwnerIdOfThisServer();

        if (fromOwnerId.equals(ownerIdOfThisServer)) {
            /// Error. The server from which timers are being
            // migrated should never be up and running OR receive this
            // notification.
            logger.log(Level.WARNING, "Attempt to migrate timers from "
                    + "an active server instance " + ownerIdOfThisServer);
            throw new IllegalStateException("Attempt to migrate timers from "
                    + " an active server instance "
                    + ownerIdOfThisServer);
        }

        logger.log(Level.INFO, "Beginning timer migration process from "
                + "owner " + fromOwnerId + " to " + ownerIdOfThisServer);

        TransactionManager tm = ejbContainerUtil.getTransactionManager();

        HashMap toRestore = new HashMap<>();
        int totalTimersMigrated = 0;

        for (String pk : (Collection) pkCache.keySet()) {
            HZTimer hZTimer = (HZTimer) pkCache.get(pk);
            if (hZTimer.getOwnerId().equals(fromOwnerId)) {
                toRestore.put(pk, hZTimer);
                hZTimer.setOwnerId(ownerIdOfThisServer);
                hZTimer.setMemberName(serverName);
            }
        }

        for (String pk : toRestore.keySet()) {
            pkCache.put(pk, toRestore.get(pk));
            totalTimersMigrated++;
        }

// XXX if( totalTimersMigrated  == toRestore.size() ) { XXX ???
        if (totalTimersMigrated > 0) {

            boolean success = false;
            try {

                logger.log(Level.INFO, "Timer migration phase 1 complete. "
                        + "Changed ownership of " + toRestore.size()
                        + " timers.  Now reactivating timers...");

                _notifyContainers(toRestore.values());

                tm.begin();
                _restoreTimers(toRestore.values());
                success = true;

            } catch (Exception e) {

                logger.log(Level.FINE, "timer restoration error", e);

                //Propogate any exceptions caught as part of the transaction 
                EJBException ejbEx = createEJBException(e);
                throw ejbEx;

            } finally {
                // We're not modifying any state in this tx so no harm in
                // always committing.
                try {
                    tm.commit();
                } catch (Exception re) {
                    logger.log(Level.FINE, "timer migration error", re);

                    if (success) {
                        //Propogate any exceptions caught when trying to commit
                        //the transaction
                        EJBException ejbEx = createEJBException(re);
                        throw ejbEx;
                    }
                }
            }
        } else {
            logger.log(Level.INFO, fromOwnerId + " has 0 timers in need "
                    + "of migration");
        }

        return totalTimersMigrated;
    }

    @Override
    protected Map recoverAndCreateSchedules(long containerId, long applicationId, Map> schedules, boolean deploy) {
        Map result = new HashMap();

        Set activeTimers = new HashSet<>();

        // get all timers for this container
        Collection containerKeys = (Collection) containerCache.get(containerId);
        Collection deadKeys = new HashSet<>();
        if (containerKeys != null) {
            for (TimerPrimaryKey containerKey : containerKeys) {
                HZTimer timer = (HZTimer) pkCache.get(containerKey.timerId);
                if (timer != null && timer.getMemberName().equals(this.serverName)) {
                    activeTimers.add(timer);
                } else if (timer == null) {
                    deadKeys.add(containerKey);
                }
            }
            if (!deadKeys.isEmpty()) {
                // clean out dead keys
                logger.info("Cleaning out " + deadKeys.size() + " dead timer ids from Container Cache ");
                for (TimerPrimaryKey deadKey : deadKeys) {
                    containerKeys.remove(deadKey);
                }
                containerCache.put(containerId, containerKeys);
            }
        } else if (containerKeys == null && deploy == false) {
            // we are in trouble as we are not deploying but our keys are null
            // looks like we lost the whole cluster storage
            // recreate timers
            deploy = true;
        }

        Set timers = _restoreTimers(activeTimers);

        if (timers.size() > 0) {
            logger.log(Level.FINE, "Found " + timers.size()
                    + " persistent timers for containerId: " + containerId);
        }

        boolean schedulesExist = (schedules.size() > 0);
        for (HZTimer timer : timers) {
            EJBTimerSchedule ts = timer.getSchedule();
            if (ts != null && ts.isAutomatic() && schedulesExist) {
                Iterator>> schedulesIterator = schedules.entrySet().iterator();
                while (schedulesIterator.hasNext()){
                    Map.Entry> entry = schedulesIterator.next();
                    Method m = entry.getKey();
                    if (m.getName().equals(ts.getTimerMethodName())
                            && m.getParameterTypes().length == ts.getMethodParamCount()) {
                        result.put(new TimerPrimaryKey(timer.getKey().getTimerId()), m);
                        if (logger.isLoggable(Level.FINE)) {
                            logger.log(Level.FINE, "@@@ FOUND existing schedule: "
                                    + ts.getScheduleAsString() + " FOR method: " + m);
                        }
                        schedulesIterator.remove();
                    }
                }
            }
        }

        
        
        try {
            if (!schedules.isEmpty()) {
                createSchedules(containerId, applicationId, schedules, result, serverName, true,
                    (deploy && isDas));
            }
        } catch (Exception ex) {
            Logger.getLogger(HazelcastTimerStore.class.getName()).log(Level.SEVERE, null, ex);
        }

        return result;
    }

    @Override
    protected boolean redeliverTimeout(RuntimeTimerState timerState) {
        return (timerState.getNumFailedDeliveries()
                < getMaxRedeliveries());
    }

    @Override
    protected void resetLastExpiration(TimerPrimaryKey timerId, RuntimeTimerState timerState
    ) {
        if (timerState.isPersistent()) {
            HZTimer timer = (HZTimer) pkCache.get(timerId.timerId);
            if (null == timer) {
                return;
            }

            Date now = new Date();
            timer.setLastExpiration(now);
            pkCache.put(timer.getKey().timerId, timer);

            // Since timer was successfully delivered, update
            // last delivery time in database if that option is
            // enabled. 
            // @@@ add configuration for update-db-on-delivery
            if (logger.isLoggable(Level.FINE)) {
                logger.log(Level.FINE,
                        "Setting last expiration "
                        + " for periodic timer " + timerState
                        + " to " + now);
            }
        }
    }

    @Override
    protected void resetEJBTimers(String target) {
        if (target == null) {
            // target is null when accessed from the BaseContainer on load, i.e. where timers are running
            logger.log(Level.INFO, "==> Restoring Timers ... ");
            if (restoreEJBTimers()) {
                logger.log(Level.INFO, "<== ... Timers Restored.");
            }
        }
    }

    @Override
    protected boolean stopOnFailure() {
        return false;
    }

    @Override
    protected boolean timerExists(TimerPrimaryKey timerId) {
        boolean exists = super.timerExists(timerId);

        // Check persistent timers only if non-persistent is not found
        if (!exists) {

            // @@@ We can't assume this server instance owns the persistent timer
            // so always ask the database.  Investigate possible use of
            // timer cache for optimization.
            HZTimer timer = (HZTimer) pkCache.get(timerId.timerId);
            if (timer != null) {
                // Make sure timer hasn't been cancelled within the current tx.
                exists = true;
            }
        }

        return exists;
    }

    @Override
    protected void stopTimers(long containerId
    ) {
        super.stopTimers(containerId);
        stopTimers((Set) containerCache.get(containerId));
    }

    private HZTimer getPersistentTimer(TimerPrimaryKey timerId) throws FinderException {
        HZTimer result = (HZTimer) pkCache.get(timerId.timerId);
        if (result == null) {
            throw new FinderException("Unable to find timer " + timerId);
        }
        return result;
    }

    private void removeTimer(HZTimer timer) {
        pkCache.remove(timer.getKey().timerId);

        Collection keys = (Collection) applicationCache.get(timer.getApplicationId());
        if (keys != null) {
            keys.remove(timer.getKey());
            applicationCache.put(timer.getApplicationId(), keys);
        }

        keys = (Collection) containerCache.get(timer.getContainerId());
        if (keys != null) {
            keys.remove(timer.getKey());
            containerCache.put(timer.getContainerId(), keys);
        }
    }

    /**
     * Common code for exception processing in recoverAndCreateSchedules
     */
    private void recoverAndCreateSchedulesError(Exception e, TransactionManager tm) {
        logger.log(Level.WARNING, "Timer restore or schedule creation error", e);

        try {
            tm.rollback();
        } catch (Exception re) {
            logger.log(Level.FINE, "Timer restore or schedule creation rollback error", re);
        }

        //Propagate the exception caught as an EJBException
        EJBException ejbEx = createEJBException(e);
        throw ejbEx;
    }

    private boolean checkForTimerValidity(TimerPrimaryKey timerId) {

        // check the timer still exists and is owned by me
        boolean result = false;

        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(HazelcastTimerStore.class.getClassLoader());
        HZTimer timer = (HZTimer) pkCache.get(timerId.timerId);
        if (timer != null && timer.getMemberName().equals(this.serverName)) {
            result = true;
        }
        Thread.currentThread().setContextClassLoader(cl);
        return result;
    }

    /**
     * The portion of timer migration that notifies containers about automatic
     * timers being migrated to this instance
     */
    private void _notifyContainers(Collection timers) {
        for (HZTimer timer : timers) {
            EJBTimerSchedule ts = timer.getSchedule();
            if (ts != null && ts.isAutomatic()) {
                addToSchedules(timer.getContainerId(), timer.getKey(), ts);
            }
        }
    }

    private void restoreTimers() throws Exception {

        // Optimization.  Skip timer restoration if there aren't any
        // applications with timed objects deployed.  
        if (totalTimedObjectsInitialized_ == 0) {
            return;
        }

        _restoreTimers(findActiveTimersOwnedByThisServer());
    }

    /**
     * The portion of timer restoration that deals with registering the JDK
     * timer tasks and checking for missed expirations.
     *
     * @return the Set of restored timers
     */
    private Collection _restoreTimers(Collection timersEligibleForRestoration) {

        // Do timer restoration in two passes.  The first pass updates
        // the timer cache with each timer.  The second pass schedules
        // the JDK timer tasks.  
        Map timersToRestore = new HashMap();
        Set timerIdsToRemove = new HashSet<>();
        Set result = new HashSet();

        for (HZTimer timer : timersEligibleForRestoration) {

            TimerPrimaryKey timerId = timer.getKey();
            if (getTimerState(timerId) != null) {
                // Already restored. Add it to the result but do nothing else.
                logger.log(Level.FINE, "@@@ Timer already restored: " + timer);
                result.add(timer);
                continue;
            }

            long containerId = timer.getContainerId();

            // Timer might refer to an obsolete container.
            BaseContainer container = getContainer(containerId);
            if (container != null) {

                // Update applicationId if it is null (from previous version)
                long appid = timer.getApplicationId();
                if (appid == 0) {
                    timer.setApplicationId(container.getApplicationId());
                }
                //  End update

                Date initialExpiration = timer.getInitialExpiration();

                // Create an instance of RuntimeTimerState. 
                // Only access timedObjectPrimaryKey if timed object is
                // an entity bean.  That allows us to lazily load the underlying
                // blob for stateless session and message-driven bean timers.
                Object timedObjectPrimaryKey = null;
                if (container.getContainerType() == BaseContainer.ContainerType.ENTITY) {
                    timedObjectPrimaryKey = timer.getTimedObjectPk();
                }

                RuntimeTimerState timerState = new RuntimeTimerState(timerId, initialExpiration,
                        timer.getIntervalDuration(), container,
                        timedObjectPrimaryKey,
                        timer.getSchedule(),
                        // Don't need to store the info ref for persistent timer
                        null, true);

                timerCache_.addTimer(timerId, timerState);

                // If a single-action timer is still in the database it never
                // successfully delivered, so always reschedule a timer task 
                // for it.  For periodic timers, we use the last known
                // expiration time to decide whether we need to fire one
                // ejbTimeout to make up for any missed ones.
                Date expirationTime = initialExpiration;
                Date now = new Date();

                if (timerState.isPeriodic()) {
                    // lastExpiration time, or null if we either aren't
                    // tracking last expiration or an expiration hasn't
                    // occurred yet for this timer.
                    Date lastExpiration = timer.getLastExpiration();
                    EJBTimerSchedule ts = timer.getSchedule();

                    // @@@ need to handle case where last expiration time
                    // is not stored in database.  This will be the case
                    // when we add configuration for update-db-on-delivery.
                    // However, for now assume we do update the db on each
                    // ejbTimeout.  Therefore, if (lastExpirationTime == null),
                    // it means the timer didn't successfully complete any
                    // timer expirations.                  
                    if ((lastExpiration == null)
                            && now.after(initialExpiration)) {

                        if (!timerState.isExpired()) {
                            // This timer didn't even expire one time.
                            logger.log(Level.INFO,
                                    "Rescheduling missed expiration for "
                                    + "periodic timer "
                                    + timerState + ". Timer expirations should "
                                    + " have been delivered starting at "
                                    + initialExpiration);
                        }

                        // keep expiration time at initialExpiration.  That
                        // will force an ejbTimeout almost immediately. After
                        // that the timer will return to fixed rate expiration.
                    } else if ((lastExpiration != null)
                            && ((ts != null && ts.getNextTimeout(lastExpiration).getTimeInMillis()
                            < now.getTime())
                            || ((ts == null) && now.getTime() - lastExpiration.getTime()
                            > timer.getIntervalDuration()))) {

                        // Schedule-based timer is periodic
                        logger.log(Level.INFO,
                                "Rescheduling missed expiration for "
                                + "periodic timer "
                                + timerState + ".  Last timer expiration "
                                + "occurred at " + lastExpiration);

                        // Timer expired at least once and at least one
                        // missed expiration has occurred.
                        // keep expiration time at initialExpiration.  That
                        // will force an ejbTimeout almost immediately. After
                        // that the timer will return to fixed rate expiration.
                    } else {

                        // In this case, at least one expiration has occurred
                        // but that was less than one period ago so there were
                        // no missed expirations.                     
                        expirationTime
                                = calcNextFixedRateExpiration(timerState);
                    }

                } else // single-action timer
                if (now.after(initialExpiration)) {
                    logger.log(Level.INFO,
                            "Rescheduling missed expiration for "
                            + "single-action timer "
                            + timerState + ". Timer expiration should "
                            + " have been delivered at "
                            + initialExpiration);
                }

                if (expirationTime == null) {
                    // Schedule-based timer will never expire again - remove it.
                    logger.log(Level.INFO,
                            "Removing schedule-based timer " + timerState
                            + " that will never expire again");
                    timerIdsToRemove.add(timerId);
                } else {
                    timersToRestore.put(timerState, expirationTime);
                    result.add(timer);
                }

            } else {
                // Timed object's container no longer exists - remember its id.
                logger.log(Level.FINE,
                        "Skipping timer " + timerId
                        + " for container that is not up: " + containerId);
            }
        } // End -- for each active timer

        if (timerIdsToRemove.size() > 0) {
            for (HZTimer hZTimer : result) {

            }
            removeTimers(timerIdsToRemove);
        }

        for (Iterator entries = timersToRestore.entrySet().iterator();
                entries.hasNext();) {
            Map.Entry next = (Map.Entry) entries.next();
            RuntimeTimerState nextTimer = (RuntimeTimerState) next.getKey();
            TimerPrimaryKey timerId = nextTimer.getTimerId();
            Date expiration = (Date) next.getValue();
            scheduleTask(timerId, expiration);
            logger.log(Level.FINE,
                    "EJBTimerService.restoreTimers(), scheduling timer "
                    + nextTimer);
        }

        logger.log(Level.FINE, "DONE EJBTimerService.restoreTimers()");
        return result;
    }

    /**
     * The portion of timer restoration that deals with registering the JDK
     * timer tasks and checking for missed expirations.
     *
     * @return the Set of restored timers
     */
    private Set _restoreTimers(Set timersEligibleForRestoration) {

        // Do timer restoration in two passes.  The first pass updates
        // the timer cache with each timer.  The second pass schedules
        // the JDK timer tasks.  
        Map timersToRestore = new HashMap();
        Set timerIdsToRemove = new HashSet<>();
        Set result = new HashSet();

        for (HZTimer timer : timersEligibleForRestoration) {

            TimerPrimaryKey timerId = timer.getKey();
            if (getTimerState(timerId) != null) {
                // Already restored. Add it to the result but do nothing else.
                logger.log(Level.FINE, "@@@ Timer already restored: " + timer);
                result.add(timer);
                continue;
            }

            long containerId = timer.getContainerId();

            // Timer might refer to an obsolete container.
            BaseContainer container = getContainer(containerId);
            if (container != null) {

                // Update applicationId if it is null (from previous version)
                long appid = timer.getApplicationId();
                if (appid == 0) {
                    timer.setApplicationId(container.getApplicationId());
                }
                //  End update

                Date initialExpiration = timer.getInitialExpiration();

                // Create an instance of RuntimeTimerState. 
                // Only access timedObjectPrimaryKey if timed object is
                // an entity bean.  That allows us to lazily load the underlying
                // blob for stateless session and message-driven bean timers.
                Object timedObjectPrimaryKey = null;
                if (container.getContainerType() == BaseContainer.ContainerType.ENTITY) {
                    timedObjectPrimaryKey = timer.getTimedObjectPk();
                }

                RuntimeTimerState timerState = new RuntimeTimerState(timerId, initialExpiration,
                        timer.getIntervalDuration(), container,
                        timedObjectPrimaryKey,
                        timer.getSchedule(),
                        // Don't need to store the info ref for persistent timer
                        null, true);

                timerCache_.addTimer(timerId, timerState);

                // If a single-action timer is still in the database it never
                // successfully delivered, so always reschedule a timer task 
                // for it.  For periodic timers, we use the last known
                // expiration time to decide whether we need to fire one
                // ejbTimeout to make up for any missed ones.
                Date expirationTime = initialExpiration;
                Date now = new Date();

                if (timerState.isPeriodic()) {
                    // lastExpiration time, or null if we either aren't
                    // tracking last expiration or an expiration hasn't
                    // occurred yet for this timer.
                    Date lastExpiration = timer.getLastExpiration();
                    EJBTimerSchedule ts = timer.getSchedule();

                    // @@@ need to handle case where last expiration time
                    // is not stored in database.  This will be the case
                    // when we add configuration for update-db-on-delivery.
                    // However, for now assume we do update the db on each
                    // ejbTimeout.  Therefore, if (lastExpirationTime == null),
                    // it means the timer didn't successfully complete any
                    // timer expirations.                  
                    if ((lastExpiration == null)
                            && now.after(initialExpiration)) {

                        if (!timerState.isExpired()) {
                            // This timer didn't even expire one time.
                            logger.log(Level.INFO,
                                    "Rescheduling missed expiration for "
                                    + "periodic timer "
                                    + timerState + ". Timer expirations should "
                                    + " have been delivered starting at "
                                    + initialExpiration);
                        }

                        // keep expiration time at initialExpiration.  That
                        // will force an ejbTimeout almost immediately. After
                        // that the timer will return to fixed rate expiration.
                    } else if ((lastExpiration != null)
                            && ((ts != null && ts.getNextTimeout(lastExpiration).getTimeInMillis()
                            < now.getTime())
                            || ((ts == null) && now.getTime() - lastExpiration.getTime()
                            > timer.getIntervalDuration()))) {

                        // Schedule-based timer is periodic
                        logger.log(Level.INFO,
                                "Rescheduling missed expiration for "
                                + "periodic timer "
                                + timerState + ".  Last timer expiration "
                                + "occurred at " + lastExpiration);

                        // Timer expired at least once and at least one
                        // missed expiration has occurred.
                        // keep expiration time at initialExpiration.  That
                        // will force an ejbTimeout almost immediately. After
                        // that the timer will return to fixed rate expiration.
                    } else {

                        // In this case, at least one expiration has occurred
                        // but that was less than one period ago so there were
                        // no missed expirations.                     
                        expirationTime
                                = calcNextFixedRateExpiration(timerState);
                    }

                } else // single-action timer
                if (now.after(initialExpiration)) {
                    logger.log(Level.INFO,
                            "Rescheduling missed expiration for "
                            + "single-action timer "
                            + timerState + ". Timer expiration should "
                            + " have been delivered at "
                            + initialExpiration);
                }

                if (expirationTime == null) {
                    // Schedule-based timer will never expire again - remove it.
                    logger.log(Level.INFO,
                            "Removing schedule-based timer " + timerState
                            + " that will never expire again");
                    timerIdsToRemove.add(timerId);
                } else {
                    timersToRestore.put(timerState, expirationTime);
                    result.add(timer);
                }

            } else {
                // Timed object's container no longer exists - remember its id.
                logger.log(Level.FINE,
                        "Skipping timer " + timerId
                        + " for container that is not up: " + containerId);
            }
        } // End -- for each active timer

        if (timerIdsToRemove.size() > 0) {
            removeTimers(timerIdsToRemove);
        }

        for (Iterator entries = timersToRestore.entrySet().iterator();
                entries.hasNext();) {
            Map.Entry next = (Map.Entry) entries.next();
            RuntimeTimerState nextTimer = (RuntimeTimerState) next.getKey();
            TimerPrimaryKey timerId = nextTimer.getTimerId();
            Date expiration = (Date) next.getValue();
            scheduleTask(timerId, expiration);
            logger.log(Level.FINE,
                    "EJBTimerService.restoreTimers(), scheduling timer "
                    + nextTimer);
        }

        logger.log(Level.FINE, "DONE EJBTimerService.restoreTimers()");
        return result;
    }

    private Collection findActiveTimersOwnedByThisServer() {
        HashSet result = new HashSet<>();
        for (HZTimer timer : (Collection) pkCache.values()) {
            if (timer.getMemberName().equals(this.serverName)) {
                result.add(timer);
            }
        }
        return result;
    }

    private boolean restoreEJBTimers() {
        boolean rc = false;
        try {
            if (totalTimedObjectsInitialized_ > 0) {
                restoreTimers();
                rc = true;
            } else {
                int s = (findActiveTimersOwnedByThisServer()).size();
                if (s > 0) {
                    logger.log(Level.INFO, "[" + s + "] EJB Timers owned by this server will be restored when timeout beans are loaded");
                } else {
                    logger.log(Level.INFO, "There are no EJB Timers owned by this server");
                }
                rc = true;
            }
        } catch (Exception ex) {
            // Problem accessing timer service so disable it.
            EJBTimerService.setEJBTimerService(null);

            logger.log(Level.WARNING, "ejb.timer_service_init_error", ex);

        }
        return rc;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy