com.twinsoft.convertigo.engine.ContextManager.java Source code

Java tutorial

Introduction

Here is the source code for com.twinsoft.convertigo.engine.ContextManager.java

Source

/*
 * Copyright (c) 2001-2011 Convertigo SA.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see<http://www.gnu.org/licenses/>.
 *
 * $URL$
 * $Author$
 * $Revision$
 * $Date$
 */

package com.twinsoft.convertigo.engine;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.servlet.http.HttpSession;

import org.apache.commons.lang3.tuple.Pair;

import com.twinsoft.convertigo.beans.connectors.JavelinConnector;
import com.twinsoft.convertigo.beans.core.Connector;
import com.twinsoft.convertigo.beans.core.Pool;
import com.twinsoft.convertigo.beans.core.Project;
import com.twinsoft.convertigo.beans.core.ScreenClass;
import com.twinsoft.convertigo.beans.core.Transaction;
import com.twinsoft.convertigo.engine.EnginePropertiesManager.PropertyName;
import com.twinsoft.convertigo.engine.events.PropertyChangeEvent;
import com.twinsoft.convertigo.engine.events.PropertyChangeEventListener;
import com.twinsoft.convertigo.engine.requesters.DefaultRequester;
import com.twinsoft.convertigo.engine.requesters.HttpSessionListener;
import com.twinsoft.convertigo.engine.requesters.PoolRequester;
import com.twinsoft.convertigo.engine.requesters.Requester;
import com.twinsoft.convertigo.engine.util.GenericUtils;
import com.twinsoft.twinj.iJavelin;
import com.twinsoft.util.DevicePool;

public class ContextManager extends AbstractRunnableManager {
    public class MyPropertyChangeEventListener implements PropertyChangeEventListener {
        public void onEvent(PropertyChangeEvent event) {
            if (event.getKey() == PropertyName.POOL_MANAGER_TIMEOUT)
                loadParameters();
        }
    };

    MyPropertyChangeEventListener myPropertyChangeEventListener;

    public static final String POOL_CONTEXT_ID_PREFIX = "/";
    public static final String STUDIO_CONTEXT_PREFIX = "studio_";
    public static final String CONTEXT_TYPE_UNKNOWN = "";
    public static final String CONTEXT_TYPE_TRANSACTION = "C";
    public static final String CONTEXT_TYPE_SEQUENCE = "S";

    private Map<String, Context> contexts;
    private int currentContextNum;

    private Map<String, DevicePool> devicePools;
    private long manage_poll_timeout = -1;

    public void init() throws EngineException {
        Engine.logContextManager.info("ContextManager initialization...");

        try {
            contexts = new HashMap<String, Context>();
            currentContextNum = 0;

            devicePools = new HashMap<String, DevicePool>();
            Engine.theApp.eventManager.addListener(
                    myPropertyChangeEventListener = new MyPropertyChangeEventListener(),
                    PropertyChangeEventListener.class);

            loadParameters();
        } finally {
            Engine.logContextManager.debug("End of initialization");
        }
    }

    @Override
    public void destroy() throws EngineException {
        Engine.logContextManager.info("Destroying ContextManager...");

        super.destroy();

        // remove all contexts
        removeAll();
        contexts = null;

        // remove all devicePools
        removeDevicePools();
        devicePools = null;

        Engine.theApp.eventManager.removeListener(myPropertyChangeEventListener, PropertyChangeEventListener.class);
    }

    private void loadParameters() {
        try {
            manage_poll_timeout = -1;
            manage_poll_timeout = Integer
                    .parseInt(EnginePropertiesManager.getProperty(PropertyName.POOL_MANAGER_TIMEOUT));
            manage_poll_timeout = manage_poll_timeout <= 0 ? -1 : manage_poll_timeout * 1000;
        } catch (Exception e) {
        }
    }

    public void add(Context context) {
        synchronized (contexts) {
            contexts.put(context.contextID, context);
            Engine.logContextManager.debug("Context " + context.contextID + " has been added");
            Engine.logContext.debug("[" + context.contextID + "] Context created, project: " + context.projectName);
            Engine.logContextManager.info("Current in-use contexts: " + contexts.size());
            Engine.logUsageMonitor.info("[Contexts] Current in-use contexts: " + contexts.size());
        }
    }

    private void addDevicePool(String poolID, DevicePool devicePool) {
        synchronized (devicePools) {
            devicePools.put(poolID, devicePool);
            Engine.logContextManager.info("DevicePool for '" + poolID + "' has been added.");
        }
    }

    public String computeStudioContextName(String type, String projectName, String typeName) {
        return STUDIO_CONTEXT_PREFIX + projectName + ":" + type + ":" + typeName;
    }

    public Context get(Requester requester, String contextName, String projectName) throws Exception {
        return get(requester, contextName, null, null, projectName, null);
    }

    public Context get(Requester requester, String contextName, String contextIdPrefix, String poolName,
            String projectName, String connectorName) throws Exception {
        return get(requester, contextName, null, null, projectName, null, null);
    }

    public Context get(Requester requester, String contextName, String contextIdPrefix, String poolName,
            String projectName, String connectorName, String sequenceName) throws Exception {
        Context context = null;

        // Try to find the context in pool
        if ((poolName != null) && (poolName.length() > 0)) {
            context = findPoolContext(contextName, projectName, connectorName, poolName);
            if (context != null) {
                return context;
            }
        }

        // If not found, try the legacy contexts

        // Studio mode?
        if (Engine.isStudioMode()) {
            // Execution from the Studio : do nothing
            if ((contextName != null) && (contextName.startsWith(STUDIO_CONTEXT_PREFIX))) {
                Engine.logContextManager.info("Using studio given context name : " + contextName);
            }
            // Execution out of the Studio (e.g: test platform) or call step
            else {
                if ((sequenceName != null) && !(sequenceName.equals(""))) {
                    contextName = computeStudioContextName(CONTEXT_TYPE_SEQUENCE, projectName, sequenceName);
                } else if (connectorName != null && !(connectorName.equals(""))) {
                    contextName = computeStudioContextName(CONTEXT_TYPE_TRANSACTION, projectName, connectorName);
                } else {
                    try {
                        Project project = Engine.objectsProvider.getProject(projectName);
                        contextName = computeStudioContextName(CONTEXT_TYPE_TRANSACTION, projectName,
                                project.getDefaultConnector().getName());
                    } catch (EngineException ee) { // project not opened in studio
                        contextName = computeStudioContextName(CONTEXT_TYPE_UNKNOWN, projectName, "");
                    }
                }
                Engine.logContextManager.info("Dynamic studio context name computed: " + contextName);
            }
        }

        // Dynamic context name
        if (contextName.endsWith("*")) {
            String contextID;
            String prefix = contextName.substring(0, contextName.length() - 1);
            int i = 1;
            do {
                contextName = prefix + i;
                contextID = (contextIdPrefix == null ? "" : contextIdPrefix + "_") + contextName;
                i++;
            } while ((context = get(contextID)) != null);
            Engine.logContextManager.info("Dynamic context name computed: " + contextName);
            context = get(contextID, contextName, projectName);
        }
        // Studio context
        else if (contextName.startsWith(STUDIO_CONTEXT_PREFIX)) {
            String contextID = contextName;
            context = get(contextID, contextName, projectName);
        }
        // Classic context
        else {
            String contextID = contextIdPrefix + "_" + contextName;
            context = get(contextID, contextName, projectName);
        }

        return context;
    }

    private Context get(String contextID, String contextName, String projectName) throws EngineException {
        Context context = get(contextID);
        // Create a new context
        if (context == null) {
            // Studio mode
            //         if (Engine.isStudioMode()) {
            //            // Throws exception if studio context does not exist
            //            if (contextName.indexOf(":"+ CONTEXT_TYPE_SEQUENCE +":")>0)    // :S:
            //               throw new EngineException("Context \"" + contextName + "\" not found; Please verify that the corresponding sequence exists and check its editor is opened in Studio.");
            //            else
            //            if (contextName.indexOf(":"+ CONTEXT_TYPE_TRANSACTION +":")>0)   // :C:
            //               throw new EngineException("Context \"" + contextName + "\" not found; Please verify that the corresponding connector exists and check its editor is opened in Studio.");
            //            else
            //            if (contextName.indexOf(":"+ CONTEXT_TYPE_UNKNOWN +":")>0)      // ::
            //               throw new EngineException("Context \"" + contextName + "\" not found; Please verify that the corresponding project exists and is opened in Studio.");
            //         }
            if (Engine.isStudioMode()) {
                // Allows context creation even in Studio mode
                // for HTTP call through test platform, ...
                // for Call steps
            }
            synchronized (contexts) {
                long numberOfContext = contexts.size();
                long maxNumberOfContext = EnginePropertiesManager
                        .getPropertyAsLong(PropertyName.CONVERTIGO_MAX_CONTEXTS);
                if (numberOfContext >= maxNumberOfContext) {
                    Engine.logContextManager
                            .warn("Max number of contexts reached: " + numberOfContext + "/" + maxNumberOfContext);
                    throw new MaxNumberOfContextsException("Maximum number of contexts reached, please try later");
                } else {
                    Engine.logContextManager
                            .debug("Current number of contexts: " + numberOfContext + "/" + maxNumberOfContext);
                }

                Engine.logContextManager
                        .debug("Context \"" + contextName + "\" not found; creating the execution context");
                context = new Context(contextID);
                context.name = contextName;
                context.cacheEntry = null;
                currentContextNum++;
                context.contextNum = currentContextNum;
                long creationTime = System.currentTimeMillis();
                Engine.logContextManager
                        .debug("Setting the creation time for context " + contextID + ": " + creationTime);
                context.creationTime = creationTime;
                context.lastAccessTime = creationTime;
                context.projectName = projectName;
                add(context);
            }
        } else {
            Engine.logContextManager.debug("Context \"" + contextName + "\" found.");
        }
        return context;
    }

    public Context getContextByName(String contextName) {
        for (Context ctx : contexts.values()) {
            if (ctx.name.equals(contextName)) {
                return ctx;
            }
        }
        return null;
    }

    public Context get(String contextID) {
        synchronized (contexts) {
            return contexts.get(contextID);
        }
    }

    private DevicePool getDevicePool(String poolID) {
        synchronized (devicePools) {
            return devicePools.get(poolID);
        }
    }

    public synchronized DevicePool getDevicePool(String poolID, int iStart, int iStop, int iIncr, int iDigits) {
        DevicePool devicePool = getDevicePool(poolID);
        if (devicePool == null) {
            devicePool = new DevicePool();
            devicePool.init(iStart, iStop, iIncr, iDigits);
            addDevicePool(poolID, devicePool);
        }
        return devicePool;
    }

    public boolean isSessionEmtpy(String sessionID) {
        //      Engine.logContextManager.debug("Finding all contexts from the session " + sessionID + "...");
        //      try {
        //         for(String contextID : contexts.keySet()) {
        //            Engine.logContextManager.debug("Analyzing contextID " + contextID);
        //            if (contextID.startsWith(sessionID)) {
        //               return false;
        //            }
        //         }
        //      }
        //      catch(NullPointerException e) {
        //         // Nothing to do: the Engine object has yet been deleted
        //      }
        //      return true;

        /* Fix: #1754 - Slower transaction execution with many session */
        // HTTP session maintain its own context list in order to
        // improve context removal on session unbound process
        try {
            HttpSession httpSession = HttpSessionListener.getHttpSession(sessionID);
            synchronized (httpSession) {
                ArrayList<Context> contextList = GenericUtils.cast(httpSession.getAttribute("contexts"));
                int size = contextList.size();
                Engine.logContextManager
                        .debug("(ContextManager) Contexts from the session " + sessionID + ": " + size);
                return size > 0 ? false : true;
            }
        } catch (Exception e) {
        }
        return true;
    }

    @Deprecated
    public Enumeration<?> getAll() {
        return Collections.enumeration(contexts.values());
    }

    public Collection<String> getContextIds() {
        return contexts.keySet();
    }

    public Collection<Context> getContexts() {
        Collection<Context> res;
        synchronized (contexts) {
            res = new ArrayList<Context>(contexts.values());
        }
        return res;
    }

    public int getNumberOfContexts() {
        return contexts.size();
    }

    public void abort(String contextID) {
        synchronized (contexts) {
            abort(get(contextID));
        }
    }

    public void abort(Context context) {
        synchronized (contexts) {
            if (context == null) {
                // Silently ignore
                Engine.logContextManager
                        .warn("Requestable thread cannot be stopped because context does not exist any more!");
                return;
            }

            String contextID = context.contextID;
            if ((context.requestedObject != null) && (context.requestedObject.runningThread != null)) {
                Engine.logContextManager.info("Stopping requestable thread for context " + contextID);
                context.abortRequestable();
            }
        }
    }

    public void remove(String contextID) {
        Engine.logContextManager.info("Removing context '" + contextID + "'");
        Context context;
        synchronized (contexts) {
            context = contexts.remove(contextID);
        }
        if (context != null) {
            remove(context);
        }
    }

    public void remove(Context context) {
        if (context == null) {
            // Silently ignore
            Engine.logContextManager.warn("The context cannot be removed because it does not exist any more!");
            return;
        }

        // To prevent from deadlock, we must synchronize on the context itself (see #3048)
        // to avoid another request thread to try to use the context simultaneously.
        // This lock must occur BEFORE acquiring lock the the contexts table.
        synchronized (context) {
            String contextID = context.contextID;
            Engine.logContextManager.info("Removing context " + contextID);

            synchronized (contexts) {
                contexts.remove(contextID);
            }

            context.isDestroying = true;

            if ((context.requestedObject != null) && (context.requestedObject.runningThread != null)) {
                Engine.logContextManager.debug("Stopping requestable thread for context " + contextID);
                //context.requestedObject.runningThread.bContinue = false;
                context.abortRequestable();
            }

            // Trying to execute the end transaction (only in the engine mode)
            if ((Engine.isEngineMode()) && (context.getConnector() != null)) {
                // Execute the end transaction
                String endTransactionName = "n/a";
                try {
                    endTransactionName = context.getConnector().getEndTransactionName();
                    if ((endTransactionName != null) && (!endTransactionName.equals(""))) {
                        Engine.logContextManager
                                .debug("Trying to execute the end transaction: \"" + endTransactionName + "\"");
                        context.transactionName = endTransactionName;
                        DefaultRequester defaultRequester = new DefaultRequester();
                        // #4910 - prevent loop for destroying context renew
                        context.isDestroying = false;
                        defaultRequester.processRequest(context);
                        Engine.logContextManager.debug("End transaction successfull");
                    }
                } catch (Throwable e) {
                    Engine.logContextManager.error("Unable to execute the end transaction; " + "context: "
                            + context.contextID + ", " + "project: " + context.projectName + ", " + "connector: "
                            + context.connectorName + ", " + "end transaction: " + endTransactionName, e);
                } finally {
                    context.isDestroying = true;
                }
                // Unlocks device if any
                // WARNING: removing the device pool MUST BE DONE AFTER the end transaction!!!
                String connectorQName = context.getConnector().getQName();
                DevicePool devicePool = getDevicePool(connectorQName);
                if (devicePool != null) {
                    long contextNum = (Long.valueOf(Integer.toString(context.contextNum, 10))).longValue();
                    Engine.logContextManager.trace("DevicePool for '" + connectorQName
                            + "' exist: unlocking device for context number " + contextNum + ".");
                    devicePool.unlockDevice(contextNum);
                }
            }
            if (Engine.isEngineMode()) {
                for (final Connector connector : context.getOpenedConnectors()) {
                    Engine.logContextManager.trace("Releasing " + connector.getName() + " connector ("
                            + connector.getClass().getName() + ") for context id " + context.contextID);
                    Thread th = new Thread(new Runnable() {
                        public void run() {
                            connector.release();
                        }
                    });
                    th.setDaemon(true);
                    th.start();
                }
            }

            context.clearConnectors();

            // Set TwsCachedXPathAPI to null
            context.cleanXpathApi();

            Engine.theApp.sessionManager.removeSession(contextID);
            String projectName = (String) context.projectName;

            /* Fix: #1754 - Slower transaction execution with many session */
            // HTTP session maintain its own context list in order to
            // improve context removal on session unbound process
            // See also #4198 which fix a regression
            String sessionID = context.httpSession != null ? context.httpSession.getId()
                    : context.contextID.substring(0, context.contextID.indexOf("_"));
            HttpSession httpSession = HttpSessionListener.getHttpSession(sessionID);
            if (httpSession != null) {
                synchronized (httpSession) {
                    try {
                        ArrayList<Context> contextList = GenericUtils.cast(httpSession.getAttribute("contexts"));
                        if ((contextList != null) && contextList.contains(context)) {
                            contextList.remove(context);
                            Engine.logContextManager.debug("(ContextManager) context " + contextID
                                    + " has been removed from http session's context list");
                        }
                        httpSession.setAttribute("contexts", contextList);
                    } catch (Exception e) {
                        // Ignore: HTTP session may have already been invalidated
                    }
                }
            }

            Engine.logContextManager.debug("Context " + contextID + " has been removed");
            Engine.logContext.debug("[" + contextID + "] Context removed, project: " + projectName);
            Engine.logContextManager.info("Current in-use contexts: " + contexts.size());
            Engine.logUsageMonitor.info("[Contexts] Current in-use contexts: " + contexts.size());
        }
    }

    public void removeAll(String sessionID) {
        //      Engine.logContextManager.debug("Removing all contexts from the session " + sessionID + "...");
        //      try {
        //         for (String contextID : GenericUtils.clone(contexts).keySet()) {
        //            Engine.logContextManager.debug("Analyzing contextID " + contextID);
        //            if (contextID.startsWith(sessionID)) {
        //               remove(contextID);
        //            }
        //         }
        //      } catch(NullPointerException e) {
        //         // Nothing to do: the Engine object has yet been deleted
        //      }

        /* Fix: #1754 - Slower transaction execution with many session */
        // HTTP session maintain its own context list in order to
        // improve context removal on session unbound process
        try {
            HttpSession httpSession = HttpSessionListener.getHttpSession(sessionID);
            List<Context> contextList = GenericUtils.cast(httpSession.getAttribute("contexts"));

            for (Context context : contextList) {
                remove(context);
            }
        } catch (Exception e) {
            if (e instanceof IllegalStateException || e instanceof NullPointerException) {
                try {
                    for (Iterator<String> i = contexts.keySet().iterator(); i.hasNext();) {
                        String contextID = i.next();
                        if (contextID.startsWith(sessionID)) {
                            remove(contextID);
                        }
                    }
                } catch (Exception e2) {
                    // prevent exception propagation
                }
            }
        }
    }

    public void removeAll() {
        Engine.logContextManager.debug("Removing all contexts...");
        try {
            for (String contextID : GenericUtils.clone(contexts).keySet()) {
                remove(contextID);
            }
        } catch (NullPointerException e) {
            // Nothing to do: the Engine object has yet been deleted
        }
    }

    public void removeDevicePools() {
        Engine.logContextManager.debug("Removing all devicePools...");
        try {
            for (String poolID : GenericUtils.clone(devicePools).keySet()) {
                removeDevicePool(poolID);
            }
        } catch (NullPointerException e) {
            // Nothing to do: the Engine object has yet been deleted
        }
    }

    public void removeDevicePool(String poolID) {
        DevicePool devicePool = getDevicePool(poolID);
        if (devicePool != null) {
            synchronized (devicePool) {
                devicePool.clean("");
            }
        }
        synchronized (devicePools) {
            devicePools.remove(poolID);
        }
    }

    public void run() {
        Engine.logContextManager.info("Starting the vulture thread for context management");

        if (Engine.isStudioMode()) {
            Engine.logContextManager.warn("Studio context => pools won't be initialized!");
        }

        while (isRunning) {
            Engine.logContextManager.debug("Vulture task in progress");
            long sleepTime = System.currentTimeMillis() + 30000;
            try {
                Engine.theApp.usageMonitor.setUsageCounter("[Contexts] Number", contexts.size());
                int maxNbCurrentWorkerThreads = Integer.parseInt(
                        EnginePropertiesManager.getProperty(PropertyName.DOCUMENT_THREADING_MAX_WORKER_THREADS));
                Engine.theApp.usageMonitor.setUsageCounter("[Contexts] [Worker threads] In use",
                        com.twinsoft.convertigo.beans.core.RequestableObject.nbCurrentWorkerThreads + " ("
                                + 100 * com.twinsoft.convertigo.beans.core.RequestableObject.nbCurrentWorkerThreads
                                        / maxNbCurrentWorkerThreads
                                + "%)");
                Engine.theApp.usageMonitor.setUsageCounter("[Contexts] [Worker threads] Max",
                        maxNbCurrentWorkerThreads);

                removeExpiredContexts();
                managePoolContexts();
                Engine.logContextManager.debug("Vulture task done");
            } catch (Throwable e) {
                Engine.logContextManager.error("An unexpected error has occured in the ContextManager vulture.", e);
            } finally {
                if ((sleepTime -= System.currentTimeMillis()) > 0) {
                    try {
                        Thread.sleep(sleepTime);
                    } catch (InterruptedException e) {
                        // Ignore
                        Engine.logContextManager.debug(
                                "InterruptedException received: probably a request for stopping the vulture.");
                    }
                }
            }
        }

        Engine.logContextManager.info("The vulture thread has been stopped.");
    }

    private void removeExpiredContexts() {
        Engine.logContextManager.debug("Executing vulture thread for context expiration");

        for (Map.Entry<String, Context> entry : GenericUtils.clone(contexts).entrySet()) {
            if (!isRunning)
                return;
            String contextID = entry.getKey();
            long expirationTime;
            try {
                Context context = entry.getValue();

                context.checkXulRecorder();

                // Ignoring removing of pool contexts only if the pooled context has not been locked.
                // If the pooled context has been locked, it may be a zombie context and then must be
                // removed (it will be recreated after).
                if (contextID.startsWith(POOL_CONTEXT_ID_PREFIX) && !context.lockPooledContext) {
                    context.lastAccessTime = Calendar.getInstance().getTime().getTime();
                    continue;
                }

                if ((context.project == null) || (context.lastAccessTime == 0))
                    continue; // The context has not been completely created, so we ignore this context...
                else
                    expirationTime = context.lastAccessTime + context.project.getHttpSessionTimeout() * 1000;

                // Engine mode (studio contexts don't expire)
                if (Engine.isEngineMode()) {
                    Engine.logContextManager
                            .debug("Analyzing contextID " + contextID + ": expiration time = " + expirationTime);

                    long rightNow = Calendar.getInstance().getTime().getTime();
                    if (rightNow > expirationTime) {
                        Engine.logContextManager.info("The context " + contextID + " has expired!");
                        remove(contextID);
                    }
                }
            } catch (Exception e) {
                Engine.logContextManager.error(
                        "An unexpected error has occured in the ContextManager vulture while analyzing the context \""
                                + contextID + "\".",
                        e);
            }
        }
    }

    private int pooledContextsInUse = 0;
    private int pooledContextsLocked = 0;
    private int pooledContextsZombie = 0;
    private int pooledContextsToCreate = 0;
    private Set<Pair<Pool, Integer>> pooledContextsToCreateSet = new HashSet<Pair<Pool, Integer>>();

    private void managePoolContexts() {
        if (Engine.isStudioMode()) {
            return;
        }

        if (!Engine.isStarted) {
            Engine.logContextManager.debug("Engine is stopped => do not manage pools");
            return;
        }

        Engine.logContextManager.debug("Executing vulture thread for context pooling");

        try {
            long timeout = manage_poll_timeout;
            long now = System.currentTimeMillis();
            if (timeout != -1) {
                timeout += now;
            }

            pooledContextsToCreateSet.clear();
            Map<String, Integer> counters = new HashMap<String, Integer>();

            // Create the pooled contexts and initialize the pooled contexts
            // with the auto-start transaction
            for (String projectName : Engine.theApp.databaseObjectsManager.getAllProjectNamesList()) {
                if (!isRunning)
                    return;

                Engine.logContextManager.trace("Analyzing project " + projectName);
                Project project = null;
                try {
                    project = Engine.theApp.databaseObjectsManager.getProjectByName(projectName);
                } catch (Exception e) {
                    Engine.logContextManager.warn(
                            "Unable to load project '" + projectName + "'; avorting pool research for this project",
                            e);
                    continue;
                }

                Collection<Connector> vConnectors = project.getConnectorsList();
                Engine.logContextManager.trace("Connectors: " + vConnectors);

                for (Connector connector : vConnectors) {
                    if (!isRunning)
                        return;
                    Engine.logContextManager.trace("Connector: " + connector);

                    Collection<Pool> vPools = connector.getPoolsList();
                    Engine.logContextManager.trace("Pools: " + vPools);
                    String poolName;
                    for (Pool pool : vPools) {
                        if (!isRunning)
                            return;
                        poolName = pool.getName();
                        Engine.logContextManager.trace("Pool: " + poolName);
                        int pooledContexts = pool.getNumberOfContexts();
                        Engine.logContextManager.debug("Pool size: " + pooledContexts);
                        String poolNameWithPath = pool.getNameWithPath();

                        pooledContextsInUse = 0;
                        pooledContextsLocked = 0;
                        pooledContextsZombie = 0;
                        pooledContextsToCreate = 0;
                        counters.put(poolNameWithPath, 0);

                        if (pooledContexts > 0) {
                            for (int i = 1; i <= pool.getNumberOfContexts(); i++) {
                                if (!isRunning)
                                    return;
                                Project localProject = Engine.theApp.databaseObjectsManager
                                        .getProjectByName(projectName);
                                Connector localConnector = localProject.getConnectorByName(connector.getName());
                                Pool localPool = localConnector.getPoolByName(pool.getName());
                                String servCode = localPool.getServiceCode();
                                if (servCode != null && !servCode.equals("")) {
                                    if (localConnector instanceof JavelinConnector) {
                                        ((JavelinConnector) localConnector).setServiceCode(servCode);
                                        Engine.logContextManager
                                                .trace("Connector service code overridden to : " + servCode);
                                    }
                                    // TODO add code for each specific connector to use pools serviceCode property
                                }
                                managePoolContext(localProject, localConnector, localPool, i);
                            }

                            int pooledContextsInUsePercentage = 100 * pooledContextsInUse / pooledContexts;
                            int pooledContextsLockedPercentage = 100 * pooledContextsLocked / pooledContexts;

                            String poolStatistics = "Pool '" + poolNameWithPath + "' usage: pool size: "
                                    + pooledContexts + "; in use contexts: " + pooledContextsInUse + " ("
                                    + pooledContextsInUsePercentage + "%); zombie contexts: "
                                    + pooledContextsZombie;
                            ;

                            if (pooledContextsZombie > 0) {
                                Engine.logContextManager
                                        .warn("Pool '" + poolNameWithPath + "' had zombie contexts!");
                                Engine.logContextManager.warn(poolStatistics);
                            }

                            if (pooledContextsInUsePercentage > 80) {
                                Engine.logContextManager.warn("Pool '" + poolNameWithPath + "' is overloaded!");
                                Engine.logContextManager.warn(poolStatistics);
                            }

                            Engine.theApp.usageMonitor.setUsageCounter("[Pool] '" + poolNameWithPath + "' size",
                                    pooledContexts);
                            Engine.theApp.usageMonitor.setUsageCounter(
                                    "[Pool] '" + poolNameWithPath + "' in use contexts",
                                    pooledContextsInUse + " (" + pooledContextsInUsePercentage + "%)");
                            Engine.theApp.usageMonitor.setUsageCounter(
                                    "[Pool] '" + poolNameWithPath + "' locked contexts",
                                    pooledContextsLocked + " (" + pooledContextsLockedPercentage + "%)");
                            Engine.theApp.usageMonitor.setUsageCounter(
                                    "[Pool] '" + poolNameWithPath + "' zombie contexts", pooledContextsZombie);
                            Engine.theApp.usageMonitor.setUsageCounter(
                                    "[Pool] '" + poolNameWithPath + "' to be created contexts",
                                    pooledContextsToCreate);
                        }
                    }
                }
            }

            for (Pair<Pool, Integer> pooledContextToCreate : pooledContextsToCreateSet) {
                if (!isRunning)
                    return;
                String key = pooledContextToCreate.getKey().getNameWithPath();
                createPoolContext(pooledContextToCreate.getKey(), pooledContextToCreate.getValue());
                counters.put(key, counters.get(key) + 1);
                if (timeout != -1 && (now = System.currentTimeMillis()) > timeout)
                    break;
            }
            for (Entry<String, Integer> entry : counters.entrySet()) {
                if (!isRunning)
                    return;
                Engine.theApp.usageMonitor.setUsageCounter("[Pool] '" + entry.getKey() + "' (re)created contexts",
                        entry.getValue());
            }
        } catch (EngineException e) {
            Engine.logContextManager.error(
                    "An unexpected error has occured in the ContextManager vulture while managing the pool contexts.",
                    e);
        }

        Engine.logContextManager.debug("Pools creation successfully finished");
    }

    private void managePoolContext(Project project, Connector connector, Pool pool, int contextNumber) {
        String projectName = project.getName();
        String connectorName = connector.getName();
        String poolName = pool.getName();
        String poolContextID = getPoolContextID(projectName, connectorName, poolName, "" + contextNumber);
        Engine.logContextManager.trace("Managing the context " + poolContextID);

        Context context = contexts.get(poolContextID);
        if (context != null) { // Context already created
            if (context.waitingRequests == 0) { // Context not currently used
                // Ignore locked contexts
                if (context.lockPooledContext) {
                    pooledContextsLocked++;
                    Engine.logContextManager.debug("Context has been locked; ignoring possible zombie state");
                    return;
                }

                // Checking pool context state
                if (verifyPoolContext(context))
                    return;

                Engine.logContextManager.debug("Zombie context => destroying it!");
                remove(context);
                pooledContextsZombie++;
            } else {
                Engine.logContextManager
                        .debug("Aborting pool context analysis because the context is currently used");
                pooledContextsInUse++;
                return;
            }
        }

        // Context not yet created or removed (detected as a zombie context)
        pooledContextsToCreate++;
        pooledContextsToCreateSet.add(Pair.<Pool, Integer>of(pool, contextNumber));
    }

    private void createPoolContext(Pool pool, int contextNumber) {
        try {
            if (!isRunning)
                return;
            Connector connector = pool.getConnector();
            Project project = connector.getProject();

            String poolContextID = getPoolContextID(project.getName(), connector.getName(), pool.getName(),
                    "" + contextNumber);

            Engine.logContextManager.info("Creating context");
            Context context = get(poolContextID, contextNumber + "", project.getName());

            context.project = project;
            context.projectName = project.getName();
            //context.sequence = null;
            //context.sequenceName = null;
            context.setConnector(connector);
            context.pool = pool;
            context.poolContextNumber = contextNumber;
            context.transactionName = pool.getStartTransaction();
            if ((context.transactionName != null) && !context.transactionName.equals("")) {
                context.requestedObject = connector.getTransactionByName(context.transactionName);

                // For compatibility with older javelin projects, set the transaction context property
                context.transaction = (Transaction) context.requestedObject;

                Engine.logContextManager.debug("Launching the auto-start transaction \"" + context.transactionName
                        + "\" for the context " + context.contextID);

                context.remoteAddr = "127.0.0.1";
                context.remoteHost = "localhost";
                context.userAgent = "Convertigo ContextManager pools launcher";

                try {
                    if (!isRunning)
                        return;
                    PoolRequester poolRequester = new PoolRequester();
                    poolRequester.processRequest(context);
                } catch (Exception e) {
                    Engine.logContextManager.error("Unable to launch the context " + context.contextID, e);
                }
            }
        } catch (EngineException e) {
            Engine.logContextManager.error(
                    "An unexpected error has occured in the ContextManager vulture while creating the pool context.",
                    e);
        }
    }

    public static String getPoolContextID(String projectName, String connectorName, String poolName,
            String sessionName) {
        return POOL_CONTEXT_ID_PREFIX + projectName + "/" + connectorName + "/" + poolName + "_" + sessionName;
    }

    private Context findPoolContext(String contextName, String projectName, String connectorName, String poolName)
            throws EngineException {
        synchronized (contexts) {
            Engine.logContextManager.debug("Trying to find a pooled context");
            Engine.logContextManager.debug("   contextName=" + contextName);
            Engine.logContextManager.debug("   projectName=" + projectName);
            Engine.logContextManager.debug("   connectorName=" + connectorName);
            Engine.logContextManager.debug("   poolName=" + poolName);

            Project project = Engine.theApp.databaseObjectsManager.getProjectByName(projectName);
            Connector connector;
            if (connectorName == null) {
                connector = project.getDefaultConnector();
                connectorName = connector.getName();
            } else
                connector = project.getConnectorByName(connectorName);

            // If we cannot find the pool, abort the process
            Pool pool = connector.getPoolByName(poolName);
            if (pool == null) {
                Engine.logContextManager.debug("No pool named '" + poolName + "'; aborting pool management");
                return null;
            }
            Engine.logContextManager.debug("Found pool=" + pool);
            pool.checkSymbols();

            String contextIDPrefix = ContextManager.getPoolContextID(projectName, connectorName, poolName, "");

            if (contextName != null && !contextName.equals("default") && !contextName.equals("default*")) {
                Engine.logContextManager
                        .debug("Explicit pooled context '" + contextIDPrefix + contextName + "' has been required");
                Context context = get(contextIDPrefix + contextName);
                if (context == null)
                    throw new EngineException(
                            "Explicit pooled context '" + contextIDPrefix + contextName + "' does not exist!");
                Engine.logContextManager.debug("context.waitingRequests=" + context.waitingRequests);
                Engine.logContextManager.debug("context.lockPooledContext=" + context.lockPooledContext);
                if (!context.lockPooledContext)
                    throw new EngineException(
                            "Explicit pooled context '" + contextIDPrefix + contextName + "' has not been locked!");
                Engine.logContextManager
                        .debug("The context has been previously locked and has been explicitely requested");
                return context;
            } else {
                Engine.logContextManager.debug("Searching for good candidate");
                for (Map.Entry<String, Context> entry : contexts.entrySet()) {
                    Engine.logContextManager.debug("Analyzing context " + entry.getKey());
                    if (entry.getKey().startsWith(contextIDPrefix)) {
                        Context context = entry.getValue();

                        Engine.logContextManager.debug("context.waitingRequests=" + context.waitingRequests);
                        Engine.logContextManager.debug("context.lockPooledContext=" + context.lockPooledContext);

                        if ((context.waitingRequests == 0) && (!context.lockPooledContext)
                                && verifyPoolContext(context)) {
                            Engine.logContextManager.debug("Good candidate for election: " + context.contextID);
                            return entry.getValue();
                        }
                    }
                }
                throw new EngineException(
                        "No more available context on the pool " + poolName + "; please try again later.");
            }
        }
    }

    private boolean verifyPoolContext(Context context) {
        JavelinConnector javelinConnector = (JavelinConnector) context.getConnector();

        if (javelinConnector == null) {
            return true;
        }

        // TODO: find why the javelin is null sometimes with pools
        if (javelinConnector.javelin == null) {
            return true;
        }

        Engine.logContextManager.trace("verifyPoolContext() context=" + context.contextID);
        Engine.logContextManager
                .trace("verifyPoolContext() connector=" + Integer.toHexString(javelinConnector.hashCode()));
        Engine.logContextManager
                .trace("verifyPoolContext() javelin=" + Integer.toHexString(javelinConnector.javelin.hashCode()));

        boolean isConnected = ((iJavelin) javelinConnector.javelin).isConnected();
        Engine.logContextManager.trace("verifyPoolContext() isConnected=" + isConnected);

        boolean isInExpectedScreenClass = true;
        String initialScreenClass = context.pool.getInitialScreenClass();
        String currentScreenClassName = "none";
        if (initialScreenClass.length() > 0) {
            ScreenClass currentScreenClass = javelinConnector.getCurrentScreenClass();
            currentScreenClassName = currentScreenClass.getName();
            isInExpectedScreenClass = initialScreenClass.equals(currentScreenClass.getName());
        }

        Engine.logContextManager
                .trace("verifyPoolContext() expected screen class: " + context.pool.getInitialScreenClass());
        Engine.logContextManager.trace("verifyPoolContext() current screen class: " + currentScreenClassName);
        Engine.logContextManager.trace("verifyPoolContext() isInExpectedScreenClass=" + isInExpectedScreenClass);

        boolean b = isConnected && isInExpectedScreenClass;
        if (!b) {
            Engine.logContextManager.warn("Zombie context detected! context: " + context.contextID);
        }
        return b;
    }
}