/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/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 glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

/**
 * PROPRIETARY/CONFIDENTIAL.  Use of this product is subject to license terms.
 *
 * Copyright 2001-2002 by iPlanet/Sun Microsystems, Inc.,
 * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
 * All rights reserved.
 */
package com.sun.enterprise.admin.event;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.sun.enterprise.admin.common.constant.AdminConstants;
import com.sun.enterprise.admin.server.core.channel.AdminChannel;
import com.sun.enterprise.admin.server.core.channel.RMIClient;
import com.sun.enterprise.config.ConfigContext;
import com.sun.enterprise.config.ConfigAdd;
import com.sun.enterprise.config.ConfigBean;
import com.sun.enterprise.config.ConfigChange;
import com.sun.enterprise.config.ConfigDelete;
import com.sun.enterprise.config.ConfigException;
import com.sun.enterprise.config.ConfigSet;
import com.sun.enterprise.config.ConfigUpdate;
import com.sun.enterprise.config.impl.ConfigUpdateImpl;
import com.sun.enterprise.config.serverbeans.ServerTags;
import com.sun.enterprise.config.serverbeans.ServerXPathHelper;

//i18n import
import com.sun.enterprise.util.i18n.StringManager;

/**
 * AdminEventCache is used to cache events during "Apply". It also is the 
 * single point where ConfigChange objects are associated to the events. This
 * class has evolved from being a cache that tracked events between "Apply"
 * boundaries to its current form, so a lot of methods have been deprecated.
 * <p>
 * User interface allows several changes to configuration to be sent to the
 * instance by one operation -- Apply. The cache is used to store events 
 * generated by processing configuration changes obtained from
 * ConfigContext. The apply operation then sends all events one after
 * another to the administered instance.
 * </p>
 * <p>
 * A typical sequence of calls will be -- where <code>name</code> is a <code>
 * String</code> representing name of the server instance and <code>context
 * </code> is an instance of type <code>com.sun.enterprise.config.ConfigContext
 * </code> applicable to the server instance.
 * </p>
 * <pre>
    AdminEventCache eventCache = AdminEventCache.getInstance(name);
    ArrayList changeList = context.getConfigChangeList();
    context.resetConfigChangeList();
    eventCache.processConfigChangeList(changeList);
    ArrayList eventList = eventCache.getAndResetCachedEvents();
    Iterator iter = eventList.iterator();
    while (iter.hasNext()) {
        AdminEvent event = (AdminEvent)iter.next();
        AdminEventResult result = AdminEventMulticaster.multicastEvent(event);
        if (result.getResultCode() != AdminEventResult.SUCCESS) {
            // Handle error
        }
    }
 * </pre>
 * <p>
 * The method <code>processConfigChangeList</code> now handles all events,
 * including those related to deployment of applications and modules.
 * If Deployment action is accompanied with a implicit apply then it should
 * be handled by the method <code>populateConfigChange</code>.
 *</p>
 */
public class AdminEventCache {

    /**
     * A reference to logger object
     */
    static Logger logger = Logger.getLogger(AdminConstants.kLoggerName);

    /**
     * A map of instance and its cache
     */
    private static final transient HashMap instanceCacheMap = new HashMap();

    /**
     * Name of the server instance. The event cache is for this instance.
     */
    private String instanceName;

    /**
     * Event cache. The cache is used to keep events during "Apply" processing
     */
    private ArrayList cache;

    /**
     * Is restart needed.
     */
    private boolean restartNeeded = false;

    /**
     * Timestamp restart is needed since.
     */
    private long tsRestartNeededSince;

    /**
     * Admin config context. This is used to resolve application and resource
     * references. The references do not contain types and a search through
     * the config context is needed to determine the type. 
     */
    private ConfigContext adminConfigContext;

    /**
     * Create an instance of AdminEventCache. Every event cache is for a
     * specific instance.
     * @param instanceName name of the instance
     */
    AdminEventCache(String instanceName) {
        this.instanceName = instanceName;
        cache = new ArrayList();
        RMIClient channel = AdminChannel.getRMIClient(instanceName);
        if (channel.isRestartNeeded()) {
            setRestartNeededTrue(false);
        }
    }

    /**
     * Get event cache for specified instance.
     * @param instanceName name of the instance
     * @return event cache for the specified instance
     */
    public synchronized static AdminEventCache getInstance(
            String instanceName) {
        AdminEventCache aec =
                (AdminEventCache)instanceCacheMap.get(instanceName);
        if (aec == null) {
            aec = new AdminEventCache(instanceName);
            instanceCacheMap.put(instanceName, aec);
        }
        return aec;
    }

    /**
     * Set admin config context.
     */
    public void setAdminConfigContext(ConfigContext ctx) {
        adminConfigContext = ctx;
    }

    /**
     * Get list of cached events. This method returns the list object
     * being used inside this class, therefore, all changes to the list
     * should be carefully considered.
     * @return list of cached events.
     */
    ArrayList getCachedEvents() {
        return cache;
    }

    /**
     * Get list of cached events and re-initialize the cache. This method
     * should be called only after a call to processConfigChangeList(),
     * @return list of cached events
     */
    public synchronized ArrayList getAndResetCachedEvents() {
        ArrayList saved = cache;
        cache = new ArrayList();
        return saved;
    }

    /**
     * Add an event to the cache.
     * @param event Event to be added to cache
     * @deprecated with no substitution
     */
    public void addEvent(AdminEvent event) {
        cache.add(event);
    }

    /**
     * Add specified list of events to the cache.
     * @param eventList list of events to add to the cache
     * @deprecated with no substitution
     */
    public void addEvents(ArrayList eventList) {
        cache.addAll(eventList);
    }

    /**
     * Remove specified event from the cache.
     * @param event the event to remove from cache
     * @deprecated with no substitution
     */
    public void removeEvent(AdminEvent event) {
        cache.remove(event);
    }

    /**
     * Process config change list and update cache with appropriate events.
     * At the end of this method the cache will have zero or more events.
     * It will contain an instance of ConfigChangeEvent if the specified
     * parameter otherConfFilesChanges is true or if specified changeList is
     * not null and the change does not fit in any of following events --
     * MonitoringEvent with actionCode START_MONITOR or STOP_MONITOR or
     * ResourceDeployEvent. If the cache already contains an instance
     * ResourceDeployEvent for a given resource, then this method will not
     * add any new instance of ResourceDeployEvent for that resource.
     * @param changeList list of server.xml config changes
     * @param initOrObFilesChanged whether conf files init.conf or obj.conf
     *     were changed
     * @param mimeFileChanged whether mime type file(s) were changed
     */
    public synchronized void processConfigChangeList(ArrayList changeList,
            boolean initOrObjFilesChanged, boolean mimeFileChanged) {
        ConfigChangeEvent dfltEvent = null;
        boolean dfltEventCached = false;
        Iterator iter = cache.iterator();
        while (iter.hasNext()) {
            Object obj = iter.next();
            if (obj instanceof ConfigChangeEvent) {
                dfltEvent = (ConfigChangeEvent)obj;
                dfltEventCached = true;
                break;
            }
        }
        if (initOrObjFilesChanged || mimeFileChanged) {
            if (dfltEvent == null) {
                dfltEvent = new ConfigChangeEvent(instanceName, null);
            }
            dfltEvent.setWebCoreReconfigNeeded(true);
        }
        if (initOrObjFilesChanged) {
            if (dfltEvent == null) {
                dfltEvent = new ConfigChangeEvent(instanceName, null);
            }
            dfltEvent.setInitOrObjConfChanged(true);
        }
        if (changeList != null && !changeList.isEmpty()) {
            iter = changeList.iterator();
            while (iter.hasNext()) {
                ConfigChange change = (ConfigChange)iter.next();
                if (change != null) {
                    doChangeBucketing(change, dfltEvent, changeList);
                }
            }
        }
        if (( dfltEvent != null) && (!dfltEventCached) ){
            cache.add(dfltEvent);
        }
        purgeNopEvents();
    }

    /**
     * Process the specifid change and generate appropriate events.
     * @param change the config change
     * @param dflt the default config change event
     * @param changeList all changes being processed. This is needed to
     *     handle references because if a resource has been deleted its
     *     type can only be found from the change list.
     */
    private void doChangeBucketing(ConfigChange change,
            ConfigChangeEvent dflt, ArrayList changeList) {
        logger.log(Level.FINE, PROCESS_CHANGE, change);
        if (change == null || change.getXPath() == null) {
            logger.log(Level.FINE, CHANGE_NULL, change);
            return;
        }
        boolean processed = false;
        //processed = processLogLevelChangeEvent(change);
//        if (!processed) {
//            processed = processMonitoringLevelChangeEvent(change);
//        }
//        if (!processed) {
//            processed = processResourceChangeEvent(change);
//        }
//        if (!processed) {
//            processed = processResourceReference(change, changeList);
//        }
/*
        if (!processed) {
            processed = processOtherDeployEvent(change);
        }
        if (!processed) {
            processed = processApplicationReference(change, changeList);
        }
*/
        if (( dflt != null) && (!processed)) {
            dflt.addConfigChange(change);
            if (!dflt.isWebCoreReconfigNeeded()) {
                setWebCoreReconfig(change, dflt);
            }
        }
    }

    /**
     * Process changes to module log levels. One event is generated for every
     * module whose log level is changed. The method returns true if change is
     * for module log levels (excluding properties).
     * @param change the change to be processed
     * @return true if the change is for module log levels, false otherwise.
     */
    private boolean processLogLevelChangeEvent(ConfigChange change) {
        return processLevelChangeEvent(change,
                new LogLevelChangeProcessor(this));
    }

    /**
     * Process changes to module monitoring levels. One event is generated
     * for every module whose monitoring level is changed. The method returns
     * true if change is for module monitoring levels (excluding properties).
     * @param change the change to be processed
     * @return true if the change is for module monitoring levels, false
     *     otherwise.
     */
    private boolean processMonitoringLevelChangeEvent(ConfigChange change) {
        return processLevelChangeEvent(change,
                new MonitoringLevelChangeProcessor(this));
    }

    /**
     * Process changes to module level.
     * @param change the change to be processed
     * @param processor the handler for the change
     * @return true if the change is applicable and events are generated,
     *     false otherwise.
     */
    private boolean processLevelChangeEvent(ConfigChange change,
            LevelChangeProcessor processor) {
        // Only updates for level change can be handled dynamically, all
        // other changes will require restart.
        ConfigUpdate update = convertToConfigUpdate(change);
        if (update == null) {
            return false;
        }
        String xpath = cleanXPath(update.getXPath());
        if (!processor.isRelevant(xpath)) {
            return false;
        }
        Set attrs = update.getAttributeSet();
        if (attrs == null) {
            logger.log(Level.FINEST, "admin.event.null_updated_attrs",
                    update.getXPath());
            return false;
        }
        Iterator iter = attrs.iterator();
        while (iter.hasNext()) {
            String compName = (String)iter.next();
            String oldValue = update.getOldValue(compName);
            String newValue = update.getNewValue(compName);
            AdminEvent event = processor.createEvent(compName, oldValue,
                    newValue);
            cache.add(event);
            ConfigUpdate upd = new ConfigUpdateImpl(xpath, compName, oldValue,
                    newValue);
            event.addConfigChange(upd);
        }
        return true;
    }

    /**
     * Process the change and generate ResourceDeployEvent. If the change is
     * to a xpath that represents a resource, then a resource deploy event for
     * the resource represented by the xpath is added to cache, provided the
     * cache does not already contain a resource deploy event for the same
     * resource. If the attribute enabled is changed then the actionCode for
     * the event is ENABLE or DISABLE, otherwise it is REDEPLOY. The change is
     * considered fully processed if the xpath is for a resource.
     * @return true if the change is fully processed, false otherwise.
     */
    private boolean processResourceChangeEvent(ConfigChange change) {
        String xpath = cleanXPath(change.getXPath());
        boolean processed = false;
        if (isResourceXPath(xpath)) {
            String actionCode = null;
            if (change instanceof ConfigUpdate) {
                ConfigUpdate update = (ConfigUpdate)change;
                String enableStr = update.getNewValue(ServerTags.ENABLED);
                if (enableStr != null) {
                    if (getServerXmlBooleanValue(enableStr)) {
                        actionCode = ResourceDeployEvent.ENABLE;
                    } else {
                        actionCode = ResourceDeployEvent.DISABLE;
                    }
                } else {
                    actionCode = ResourceDeployEvent.REDEPLOY;
                }
            } else if (change instanceof ConfigAdd) {
                // In case of security-map, addition should fire 
                // redeploy event as it is part of connector-connection-pool
                // resource.
                boolean isMap = xpath.indexOf(ServerTags.SECURITY_MAP) != -1;
                if (isPropertyXPath(xpath) || isMap) {
                    actionCode = ResourceDeployEvent.REDEPLOY;
                } else {
                    actionCode = ResourceDeployEvent.DEPLOY;
                }
            } else if (change instanceof ConfigDelete) {
                // In case of security-map, delete should fire redeploy event
                // as it is part of connector-connection-pool resource.
                boolean isMap = xpath.indexOf(ServerTags.SECURITY_MAP) != -1;
                if (isPropertyXPath(xpath) || isMap) {
                    actionCode = ResourceDeployEvent.REDEPLOY;
                } else {
                     actionCode = ResourceDeployEvent.UNDEPLOY;
                }
            } else if (change instanceof ConfigSet) {
                // As of now set is used for existing properties in resources.
                // Existing properties imply that resource exists.
                actionCode = ResourceDeployEvent.REDEPLOY;
            } else {
                // Unknown ConfigChange cannot be handled
		String msg = localStrings.getString( "admin.event.unknown_configchange_for_resources");
                throw new IllegalStateException( msg );
            }
            String resType = getResourceType(xpath);
            String resName = getResourceName(xpath);
            ResourceDeployEvent resEvent =
                    findResourceDeployEvent(resType, resName);
            if (resEvent == null) {
                resEvent = new ResourceDeployEvent(instanceName, resName,
                        resType, actionCode);
                cache.add(resEvent);
            } else {
                if (isActionValidForCache(actionCode)) {
                    resEvent.setNewAction(actionCode);
                } else {
                    // As resEvent was not null, this is not the first change
                    // being processed for this xpath. Enable and Disable are
                    // not valid actions for second change of a resource xpath
                }
            }
            resEvent.addConfigChange(change);
            processed = true;
        }
        return processed;
    }

    /**
     * Is this a resource XPATH. The method returns true if the XPATH is for
     * a resource.
     * @param xpath the xpath
     * @return true if the xpath is for a resource, false otherwise.
     */
    private boolean isResourceXPath(String xpath) {
        String xpathLcase = xpath.toLowerCase();
        if (xpathLcase.startsWith(RES_PFX)) {
            return true;
        }
        return false;
    }

    /**
     * Is specified xpath for property.
     * @param xpath the xpath to be checked
     * @returns true if the xpath is for a property, false otherwise
     */
    private boolean isPropertyXPath(String xpath) {
        return AdminEventCache.matchRegex(PROPERTY_REGEX, xpath);
    }

    /**
     * Is specified xpath for module log levels.
     * @param xpath the xpath to be checked
     * @returns true if the xpath is for module log levels, false otherwise
     */
    private boolean isLogLevelXPath(String xpath) {
        return (LOG_LEVEL_XPATH.equalsIgnoreCase(xpath));
    }

    /**
     * Is specified xpath for module monitoring levels.
     * @param xpath the xpath to be checked
     * @returns true if the xpath is for module monitoring levels, false
     *     otherwise
     */
    private boolean isMonitoringLevelXPath(String xpath) {
        return (MONITORING_LEVEL_XPATH.equalsIgnoreCase(xpath));
    }

    /**
     * Is specified xpath for application reference
     * @param xpath the xpath to be checked
     * @returns true if the xpath is for application reference, false
     *     otherwise
     */
    private boolean isAppRefXPath(String xpath) {
        if (xpath == null) {
            return false;
        }
        String serverXPath = ServerXPathHelper.getServerIdXpath(instanceName);
        String appRefXPath = serverXPath + ServerXPathHelper.XPATH_SEPARATOR
                + ServerTags.APPLICATION_REF;
        return xpath.startsWith(appRefXPath);
    }

    /**
     * Is specified xpath for application reference
     * @param xpath the xpath to be checked
     * @returns true if the xpath is for resource reference, false
     *     otherwise
     */
    private boolean isResRefXPath(String xpath) {
        if (xpath == null) {
            return false;
        }
        String serverXPath = ServerXPathHelper.getServerIdXpath(instanceName);
        String resRefXPath = serverXPath + ServerXPathHelper.XPATH_SEPARATOR
                + ServerTags.RESOURCE_REF;
        return xpath.startsWith(resRefXPath);
    }

    /**
     * Get app ref xpath for specified app and server.
     * @param instance name of the server
     * @param appName name of the application
     * @return xpath corresponding to the application ref
     */
    private static String getAppRefXPath(String instance, String appName) {
        return ServerXPathHelper.getServerIdXpath(instance)
                + ServerXPathHelper.XPATH_SEPARATOR
                + ServerTags.APPLICATION_REF + "[@"
                + ServerTags.REF + "='" + appName + "']";
    }

    /**
     * Get resource type from the xpath.
     * @param xpath the xpath for a resource
     * @return the type of the resource, appropriate for use in
     *     constructor of ResourceDeployEvent.
     */
    private String getResourceType(String xpath) {
        String resType = "unknown";
        String xpathLcase = xpath.toLowerCase();
        if (xpathLcase.startsWith(ServerXPathHelper.XPATH_CUSTOM_RESOURCE)) {
            resType = ResourceDeployEvent.RES_TYPE_CUSTOM;
        } else if (xpathLcase.startsWith(ServerXPathHelper.XPATH_JNDI_RESOURCE)) {
            resType = ResourceDeployEvent.RES_TYPE_EXTERNAL_JNDI;
        } else if (xpathLcase.startsWith(ServerXPathHelper.XPATH_JDBC_CONNECTION_POOL)) {
            resType = ResourceDeployEvent.RES_TYPE_JCP;
        } else if (xpathLcase.startsWith(ServerXPathHelper.XPATH_JDBC_RESOURCE)) {
            resType = ResourceDeployEvent.RES_TYPE_JDBC;
        } else if (xpathLcase.startsWith(ServerXPathHelper.XPATH_MAIL_RESOURCE)) {
            resType = ResourceDeployEvent.RES_TYPE_MAIL;
        } else if (xpathLcase.startsWith(ServerXPathHelper.XPATH_PM_FACTORY_RESOURCE)) {
            resType = ResourceDeployEvent.RES_TYPE_PMF;
        } else if (xpathLcase.startsWith(ServerXPathHelper.XPATH_ADMIN_OBJECT_RESOURCE)) {
            resType = ResourceDeployEvent.RES_TYPE_AOR;
        }  else if (xpathLcase.startsWith(ServerXPathHelper.XPATH_CONNECTOR_CONNECTION_POOL)) {
            resType = ResourceDeployEvent.RES_TYPE_CCP;
        }  else if (xpathLcase.startsWith(ServerXPathHelper.XPATH_CONNECTOR_RESOURCE)) {
            resType = ResourceDeployEvent.RES_TYPE_CR;
        }  else if (xpathLcase.startsWith(ServerXPathHelper.XPATH_RESOURCE_ADAPTER_CONFIG)) {
             resType = ResourceDeployEvent.RES_TYPE_RAC;
        }

        return resType;
    }

    /**
     * Get name of the resource from a resource xpath.
     * @param xpath the xpath for a resource
     * @return the name of the resource
     */
    private String getResourceName(String xpath) {
        return getXPathToken(xpath, 1);
    }

    /**
     * Extract token separated by single-quote character (&#39;) after
     * discarding specified number of tokens. For example,
     * <pre>
       xpath                                  numToDiscard     Returns
       /a/b/c[@name='myName']                     1            myName 
       /a/b/c[@name='myName']/d[@id='myId']       3            myId
     * </pre>
     * Note that the value contains single-quote character, the xpath will
     * be formed with &amp;#39; and the caller has to convert to appropriate
     * character.
     * @param xpath the xpath from where token needs to be extracted
     * @param numToDiscard number of tokens to discard
     * @throws IllegalArgumentException if the xpath does not contain enough
     *      tokens.
     * @return the token after discarding numToDiscard tokens from xpath
     */
    private String getXPathToken(String xpath, int numToDiscard) {
        StringTokenizer tok = new StringTokenizer(xpath, "'");
        if (tok.countTokens() <= numToDiscard) {
            Object[] params = new Object[] {xpath,
                    new Integer(numToDiscard + 1),
                    new Integer(tok.countTokens())};
            logger.log(Level.FINE, INVALID_XPATH_TOKENIZATION, params);
            String msg = localStrings.getString(
                    "admin.event.invalid_xpath_tokenization", params );
            throw new IllegalArgumentException(msg);
        }
        for (int i = numToDiscard; i > 0; i--) {
            String discard = tok.nextToken();
        }
        return tok.nextToken();
    }

    /**
     * Process the change and generate ApplicationDeployEvent or
     * ModuleDeployEvent. If the change is to a xpath that represents an
     * application or module, then an application or module deploy event for
     * the resource represented by the xpath is added to cache, provided the
     * cache does not already contain a application or module deploy event for
     * the same resource. If the attribute enabled is changed then the
     * actionCode for the event is ENABLE or DISABLE, otherwise it is REDEPLOY.
     * The change is considered fully processed if the xpath is for an
     * application or a module.
     * @return true if the change is fully processed, false otherwise.
     */
    private boolean processOtherDeployEvent(ConfigChange change) {
        String xpath = cleanXPath(change.getXPath());
        String xpathLcase = xpath.toLowerCase();
        boolean processed = false;
        String componentType = null;
        String moduleType = null;
        if (xpathLcase.startsWith(APP_PFX)) {
            componentType = BaseDeployEvent.APPLICATION;
        } else if (xpathLcase.startsWith(EJB_PFX)) {
            componentType = BaseDeployEvent.MODULE;
            moduleType = ModuleDeployEvent.TYPE_EJBMODULE;
        } else if (xpathLcase.startsWith(WEB_PFX)) {
            componentType = BaseDeployEvent.MODULE;
            moduleType = ModuleDeployEvent.TYPE_WEBMODULE;
        } else if (xpathLcase.startsWith(RAR_PFX)) {
            componentType = BaseDeployEvent.MODULE;
            moduleType = ModuleDeployEvent.TYPE_CONNECTOR;
        } else if (xpathLcase.startsWith(ACC_PFX)) {
            componentType = BaseDeployEvent.MODULE;
            moduleType = ModuleDeployEvent.TYPE_APPCLIENT;
        } else {
            return false;
        }
        String componentName = getResourceName(xpath);
        BaseDeployEvent theEvent = null;
        if (BaseDeployEvent.APPLICATION.equals(componentType)) {
            theEvent = findApplicationDeployEvent(componentName);
        } else {
            theEvent = findModuleDeployEvent(moduleType, componentName);
        }
        if (theEvent == null) {
            String actionCode = null;
            if (change instanceof ConfigUpdate) {
/* krav
                ConfigUpdate update = (ConfigUpdate)change;
                String enableStr = update.getNewValue(ServerTags.ENABLED);
                if (enableStr != null) {
                    if (getServerXmlBooleanValue(enableStr)) {
                        actionCode = BaseDeployEvent.ENABLE;
                    } else {
                        actionCode = BaseDeployEvent.DISABLE;
                    }
                } else {
                    actionCode = BaseDeployEvent.REDEPLOY;
                }
 */
             } else if (change instanceof ConfigAdd) {
                actionCode = BaseDeployEvent.DEPLOY;
             } else if (change instanceof ConfigDelete) {
                actionCode = BaseDeployEvent.UNDEPLOY;
             } else {
		String msg = localStrings.getString( "admin.event.handle_add_delete_set_configchange" );
                throw new IllegalStateException( msg );
             }
             if (actionCode != null) {
                if (BaseDeployEvent.APPLICATION.equals(componentType)) {
                    theEvent = new ApplicationDeployEvent(instanceName,
                    componentName, actionCode);
                } else {
                    theEvent = new ModuleDeployEvent(instanceName, componentName,
                    moduleType, actionCode);
                }
                cache.add(theEvent);
                processed = true;
            }
        } else { // (theEvent != null)
            // Event is not null. Any update will not affect it. If the change
            // is not update (add, delete or set) -- assume that it already
            // affected the state of event when the change was made
            processed = true;
        }
        theEvent.addConfigChange(change);
        return processed;
    }

    /**
     * Process the change and create/update appropriate resource events, if the
     * change represents a resource reference. 
     * @param change the change to be processed
     * @param changeList all the changes being processed in the current batch
     * @return true if the change is for a resource reference and was added
     *      to an existing or a new event.
     */
    private boolean processResourceReference(ConfigChange change,
            ArrayList changeList) {
        return processReference(change, changeList, new ResRefProcessor(this));
    }

    /**
     * Process the change and create/update appropriate application or module
     * events, if the change represents a application reference. 
     * @param change the change to be processed
     * @param changeList all the changes being processed in the current batch
     * @return true if the change is for a application reference and was added
     *      to an existing or a new event.
     */
    private boolean processApplicationReference(ConfigChange change,
            ArrayList changeList) {
        return processReference(change, changeList, new AppRefProcessor(this));
    }

    /**
     * Process reference and create/update appropriate event in the cache.
     * @param change the change to be processed
     * @param changeList all changes being processed along with this change.
     *     The change list is needed to determine type of the config object
     *     that the ref is referring to, in cases when the actual config 
     *     object has been deleted and is no longer present in config context.
     * @param processor the processor for the reference. It is the abstraction
     *     that hides differences between application refs and resource refs.
     * @return true if the change can be handled by specified processor and
     *     is handled successfully, false otherwise.
     */
    private boolean processReference(ConfigChange change, ArrayList changeList,
            RefProcessor processor) {
        String xpath = cleanXPath(change.getXPath());
        if (!processor.isRelevant(xpath)) {
            return false;
        }
        String refName = processor.getRefName(xpath);
        processor.initRefTypeXPathMap(refName);
        String refType = processor.getRefType(refName, changeList);
        if (refType == null) {
            // The ref is not interesting, for example, lifecycle module ref
            return false;
        }
        return processor.process(refName, refType, change);
    }

    /**
     * Set web core reconfig status in specified event if the change warrants
     * that. Any config change to http-service or web container element require
     * a web core reconfig.
     * @param change the config change object
     * @param dflt the default config change event
     */
    private void setWebCoreReconfig(ConfigChange change,
            ConfigChangeEvent dflt) {
        String xpath = cleanXPath(change.getXPath());
        if (isWebCoreXPath(xpath)) {
            dflt.setWebCoreReconfigNeeded(true);
        }
        return;
    }

    /**
     * Is specified XPath handled only by web core. All elements under 
     * http-service and web-container are handled exclusively by web core.
     * @param xpath the xpath to process
     * @return true if the xpath is handled only by web core, false otherwise.
     */
    private boolean isWebCoreXPath(String xpath) {
        boolean webCoreXPath = false;
        if (xpath == null) {
            return webCoreXPath;
        } else {
            xpath = xpath.toLowerCase();
        }
        if (xpath.startsWith(HTTP_SERVICE) || xpath.startsWith(WEB_CONTAINER)) {
            webCoreXPath = true;
        }
        return webCoreXPath;
    }

    /**
     * Get a boolean value corresponding to the string value in xml.
     * @param xmlValue the xml string representing a boolean
     * @return true if the string is "true", "yes", "on" or "1"
     */
    public static boolean getServerXmlBooleanValue(String xmlValue) {
        boolean retval = false;
        if (xmlValue == null) {
            return retval;
        }
        if (xmlValue.equalsIgnoreCase("true")
                || xmlValue.equalsIgnoreCase("yes")
                || xmlValue.equalsIgnoreCase("on")
                || xmlValue.equalsIgnoreCase("1")) {
            retval = true;
        }
        return retval;
    }

    /**
     * Convert to config update, if possible. If specified change is not
     * an instanceof ConfigUpdate, the method returns null
     */
    private ConfigUpdate convertToConfigUpdate(ConfigChange change) {
        ConfigUpdate update = null;
        if (change instanceof ConfigUpdate) {
            update = (ConfigUpdate)change;
        }
        return update;
    }

    /**
     * Cache a resource change. This method inspects the cache and either
     * updates the existing event or creates a new resource event.
     * @param resourceType Type of the resource
     * @param resourceName Name of the resource
     * @param actionCode Action code, one of BaseDeployEvent.DEPLOY,
     *    BaseDeployEvent.REDEPLOY or BaseDeployEvent.UNDEPLOY
     * @deprecated with no substitution.
     */
    public void cacheResourceChange(String resourceType, String resourceName,
            String actionCode) {
        // Valid values for actionCode are DEPLOY, UNDEPLOY and REDEPLOY and
        // not ENABLE and DISABLE which can come only from analyzing config
        // changes. 
        if (!isActionValidForCache(actionCode)) {
			String msg = localStrings.getString( "admin.event.invalid_action", actionCode );
            throw new IllegalArgumentException( msg );
        }
        ResourceDeployEvent resEvent = findResourceDeployEvent(resourceType,
                resourceName);
        if (resEvent == null) {
            resEvent = new ResourceDeployEvent(instanceName, resourceName,
                    resourceType, actionCode);
            cache.add(resEvent);
        } else {
            resEvent.setNewAction(actionCode);
        }
    }

    /**
     * Check whether specified deploy action can be cached. Only deploy actions
     * - DEPLOY, UNDEPLOY and REDEPLOY can be cached. The other deploy actions
     * ENABLE, DISABLE are derived from the cache of changes.
     * @param deployAction the deployment action
     * @return true if deploy action is valid, false otherwise
     */
    private boolean isActionValidForCache(String deployAction) {
        boolean valid = false;
        if (deployAction != null) {
            if (deployAction.equals(BaseDeployEvent.DEPLOY)
                    || deployAction.equals(BaseDeployEvent.UNDEPLOY)
                    || deployAction.equals(BaseDeployEvent.REDEPLOY)) {
                valid = true;
            }
        }
        return valid;
    }

    /**
     * Populate config changes in the specified event. This method looks for
     * changes matching the event in the specified config context and if there
     * are any the changes are moved from config context to the event. This
     * method can only handle ApplicationDeployEvent and ModuleDeployEvent.
     * @param ctx The config context where the cache of changes is examined for
     *    match
     * @param event The event for which matching changes should be looked up
     * @throws UnsupportedOperationException if the event is not Application or
     *    Module deploy event.
     */
    public static void populateConfigChange(ConfigContext ctx, AdminEvent event) {
        if (event instanceof ApplicationDeployEvent) {
            populateApplicationConfigChange(ctx, (ApplicationDeployEvent)event);
        } else if (event instanceof ModuleDeployEvent) {
            populateModuleConfigChange(ctx, (ModuleDeployEvent)event);
        } else {
			String msg = localStrings.getString( "admin.event.unsupported_populateconfigchange", event.getClass().getName() );
            throw new UnsupportedOperationException( msg );
        }
    }

    /**
     * Populate config changes from specified config context into the specified
     * application deployment event.
     * @param ctx the config context
     * @param event the application deployment event
     */
    private static void populateApplicationConfigChange(ConfigContext ctx, 
            ApplicationDeployEvent event) {
        String xpath = ServerXPathHelper.getAppIdXpathExpression(
                event.getApplicationName());
        xpath = cleanXPath(xpath);
        event.addConfigChange(extractConfigChanges(ctx, xpath));
        // Now look for any changes to application ref
        String refXPath = getAppRefXPath(event.getInstanceName(),
                event.getApplicationName());
        event.addConfigChange(extractConfigChanges(ctx, refXPath));
    }

    /**
     * Populate config changes from specified config context into the specified
     * module deployment event.
     * @param ctx the config context
     * @param event the module deployment event
     * @throws IllegalArgumentException if module type as specified in the
     *    event is not one of EJB, WEB, CONNECTOR or APPCLIENT
     */
    private static void populateModuleConfigChange(ConfigContext ctx,
            ModuleDeployEvent event) {
        String moduleType = event.getModuleType();
        String moduleName = event.getModuleName();
        String xpath = null;
        if (event.TYPE_WEBMODULE.equals(moduleType)) {
            xpath = ServerXPathHelper.getWebModuleIdXpathExpression(moduleName);
        } else if (event.TYPE_EJBMODULE.equals(moduleType)) {
            xpath = ServerXPathHelper.getEjbModuleIdXpathExpression(moduleName);
        } else if (event.TYPE_CONNECTOR.equals(moduleType)) {
            xpath = ServerXPathHelper.getConnectorModuleIdXpathExpression(
                    moduleName);
        } else if (event.TYPE_APPCLIENT.equals(moduleType)) {
            xpath = ServerXPathHelper.getAppClientModuleIdXpathExpression(
                    moduleName);
        // XXX: Added for Extension Deployment SPI
        } else if (event.getModuleType().equals(moduleType)) {
            xpath = ServerXPathHelper.getExtensionModuleIdXpathExpression(
                    moduleName);
        } else {
	    String msg = localStrings.getString( "admin.event.invalid_module_type" , moduleType );
            throw new IllegalArgumentException( msg );
        }
        xpath = cleanXPath(xpath);
        event.addConfigChange(extractConfigChanges(ctx, xpath));
        // Now look for any changes to application ref
        String refXPath = getAppRefXPath(event.getInstanceName(), moduleName);
        event.addConfigChange(extractConfigChanges(ctx, refXPath));
    }

    /**
     * Extract config changes matching specified xpath from config context.
     * @param configContext the context from where changes are extracted
     * @param xpath the xpath for which all changes should be extracted
     * @throws IllegalArgumentException if specified config context is null
     * @throws IllegalStateException if list of changes maintained by config
     *     context is null.
     */
    private static ArrayList extractConfigChanges(ConfigContext configContext,
            String xpath) {
        if (configContext == null) {
			String msg = localStrings.getString( "admin.event.null_config_context" );
            throw new IllegalArgumentException( msg );
        }
        ArrayList myChanges = new ArrayList();
        ArrayList allChanges = configContext.getConfigChangeList();
        if (allChanges == null) {
			String msg = localStrings.getString( "admin.event.null_config_changes_in_configcontext" );
            throw new IllegalStateException( msg );
        }
        logger.log(Level.FINE, EXTRACT_CHANGE, xpath);
        Iterator iter = allChanges.iterator();
        while (iter.hasNext()) {
            ConfigChange change = (ConfigChange)iter.next();
            logger.log(Level.FINE, PROCESS_CHANGE, change);
            String changeXPath = null;
            if (change != null) {
                changeXPath = cleanXPath(change.getXPath());
                logger.log(Level.FINEST, CONFIG_CHANGE_XPATH, changeXPath);
            }
            if (change == null || changeXPath == null) {
                logger.log(Level.FINE, CHANGE_NULL, change);
                continue;
            }
            if (changeXPath.equals(xpath)) {
		//System.out.println("Xpath matches, adding change to the list");
                myChanges.add(change);
            } else {
		//System.out.println("No match, ignoring");
            }
        }
        iter = myChanges.iterator();
        while (iter.hasNext()) {
            configContext.removeConfigChange((ConfigChange)iter.next());
        }
        return myChanges;
    }

    /**
     * Clean XPath. This method converts all xpaths to lower case and replaces
     * two or more consecutive forward slash ('/') character by a single
     * forward slash.
     * @return modified XPath
     */
    private static String cleanXPath(String xpath) {
        if (xpath == null) {
            return xpath;
        }
        return xpath.replaceAll("/{2,}", "/");
    }

    /**
     * Match a string against a regular expression.
     * @param regex the regular expression to match
     * @param str the string to match
     * @returns true if the regular expression matches string
     */
    private static boolean matchRegex(String regex, String str) {
        Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(str);
        return matcher.matches();
    }

    /**
     * Find matching resource deploy event in the cache. This method iterates
     * through all cached events and tries to find a ResourceDeployEvent for
     * specified resource type and name. The method returns null if it doesn't
     * find a match.
     * @param resType type of the resource
     * @param resName name of the resource
     * @throws IllegalArgumentException if resource type or name is null
     * @return matching ResourceDeployEvent if found, null otherwise.
     */
    private ResourceDeployEvent findResourceDeployEvent(String resType,
            String resName) {
        if (resType == null || resName == null) {
			String msg = localStrings.getString( "admin.event.null_resource_type_or_name", resType, resName );
            throw new IllegalArgumentException( msg );
        }
        ResourceDeployEvent resEvent = null;
        Iterator iter = cache.iterator();
        while (iter.hasNext()) {
            AdminEvent event = (AdminEvent)iter.next();
            if (event instanceof ResourceDeployEvent) {
                resEvent = (ResourceDeployEvent)event;
                if (resType.equals(resEvent.getResourceType())
                        && resName.equals(resEvent.getResourceName())) {
                    break;
                } else {
                    resEvent = null;
                }
            }
        }
        return resEvent;
    }

    /**
     * Find matching application deploy event in the cache. This method iterates
     * through all cached events and tries to find an ApplicationDeployEvent for
     * specified name. The method returns null if it doesn't find a match.
     * @param appName name of the application
     * @throws IllegalArgumentException if specified application name is null
     * @return matching ApplicationDeployEvent if found, null otherwise.
     */
    private ApplicationDeployEvent findApplicationDeployEvent(String appName) {
        if (appName == null) {
			String msg = localStrings.getString( "admin.event.null_application_name" );
            throw new IllegalArgumentException( msg );
        }
        ApplicationDeployEvent appEvent = null;
        Iterator iter = cache.iterator();
        while (iter.hasNext()) {
            AdminEvent event = (AdminEvent)iter.next();
            if (event instanceof ApplicationDeployEvent) {
                appEvent = (ApplicationDeployEvent)event;
                if (appName.equals(appEvent.getApplicationName())) {
                    break;
                } else {
                    appEvent = null;
                }
            }
        }
        return appEvent;
    }

    /**
     * Find matching module deploy event in the cache. This method iterates
     * through all cached events and tries to find a ModuleDeployEvent for
     * specified module type and name. The method returns null if it doesn't
     * find a match.
     * @param modType type of the module
     * @param modName name of the module
     * @throws IllegalArgumentException if module type or name is null
     * @return matching ModuleDeployEvent if found, null otherwise.
     */
    private ModuleDeployEvent findModuleDeployEvent(String modType,
            String modName) {
        if (modType == null || modName == null) {
			String msg = localStrings.getString( "admin.event.null_module_type_or_name", modType, modName );
            throw new IllegalArgumentException( msg );
        }
        ModuleDeployEvent modEvent = null;
        Iterator iter = cache.iterator();
        while (iter.hasNext()) {
            AdminEvent event = (AdminEvent)iter.next();
            if (event instanceof ModuleDeployEvent) {
                modEvent = (ModuleDeployEvent)event;
                if (modEvent.getModuleType().equals(modType)
                        && modEvent.getModuleName().equals(modName)) {
                    break;
                } else {
                    modEvent = null;
                }
            }
        }
        return modEvent;
    }

    /**
     * Purge events that are NO-OP.
     */
    private void purgeNopEvents() {
        Iterator iter = cache.iterator();
        while (iter.hasNext()) {
            AdminEvent event = (AdminEvent)iter.next();
            if (event.isNoOp()) {
                logger.log(Level.FINE, PURGE_NOOP, event);
                iter.remove();
            }
        }
    }

    /**
     * Set whether the instance requires restart. Cache is invalid if instance
     * requires restart.
     */
    public void setRestartNeeded(boolean flag) {
        if (flag) {
            if (!restartNeeded) {
                setRestartNeededTrue(true);
            }
        } else {
            restartNeeded = false;
        }
    }

    /**
     * Set restart needed to true. This method also records the timestamp
     * when restart needed was set to true. If parameter notifyInstance is
     * true the instance is notified that it requires a restart.
     * @param notifyInstance if true the instance is notified of the restart
     *     required status.
     */
    private void setRestartNeededTrue(boolean notifyInstance) {
        restartNeeded = true;
        tsRestartNeededSince = System.currentTimeMillis();
        if (notifyInstance) {
            RMIClient channel = AdminChannel.getRMIClient(instanceName);
            channel.setRestartNeeded(true);
        }
    }

    /**
     * Is restart of instance required.
     */
    public boolean isInstanceRestartNeeded() {
        if (restartNeeded) {
            RMIClient channel = AdminChannel.getRMIClient(instanceName);
            boolean alreadyRestarted =
                    channel.hasRestartedSince(tsRestartNeededSince);
            if (alreadyRestarted) {
                restartNeeded = false;
            }
        }
        return restartNeeded;
    }

    private final static String RES_PFX = ServerXPathHelper.XPATH_RESOURCES;
    private final static String APP_PFX = ServerXPathHelper.XPATH_J2EE_APPLICATION;
    private final static String WEB_PFX = ServerXPathHelper.XPATH_WEB_MODULE;
    private final static String EJB_PFX = ServerXPathHelper.XPATH_EJB_MODULE;
    private final static String RAR_PFX = ServerXPathHelper.XPATH_CONNECTOR_MODULE;
    private final static String ACC_PFX = ServerXPathHelper.XPATH_APPCLIENT_MODULE;

    private final static String HTTP_SERVICE = ServerXPathHelper.XPATH_HTTP_SERVICE;
    private final static String WEB_CONTAINER = ServerXPathHelper.XPATH_WEB_CONTAINER;

    private final static String PROPERTY_REGEX = ServerXPathHelper.XPATH_DOMAIN
            + ServerXPathHelper.XPATH_SEPARATOR + "{1,}" + ".*"
            + ServerXPathHelper.XPATH_SEPARATOR + "{1,}"
            + ServerTags.ELEMENT_PROPERTY + ".*";

    private final static String LOG_LEVEL_XPATH =
            ServerXPathHelper.XPATH_CONFIG + ServerXPathHelper.XPATH_SEPARATOR
            + ServerTags.LOG_SERVICE + ServerXPathHelper.XPATH_SEPARATOR
            + ServerTags.MODULE_LOG_LEVELS;

    private final static String MONITORING_LEVEL_XPATH =
            ServerXPathHelper.XPATH_CONFIG + ServerXPathHelper.XPATH_SEPARATOR
            + ServerTags.MONITORING_SERVICE + ServerXPathHelper.XPATH_SEPARATOR
            + ServerTags.MODULE_MONITORING_LEVELS;

    private final static String PROCESS_CHANGE = "event.process_change";
    private final static String CHANGE_NULL = "event.change_null";
    private final static String EXTRACT_CHANGE = "event.extract_change";
    private final static String CONFIG_CHANGE_XPATH = "event.config_change_xpath";
    private final static String PURGE_NOOP = "event.purge_noop";
    private final static String NULL_UPDATED_ATTRS = "event.null_updated_attrs";
    private final static String INVALID_XPATH_TOKENIZATION =
            "event.invalid_xpath_tokenization";

	// i18n StringManager
	private static StringManager localStrings =
		StringManager.getManager( AdminEventCache.class );

    /**
     * An abstraction to allow processing of all level changes in same method.
     * The processing to generate events for log level changes and monitoring
     * level changes are very similar. This class abstracts out the differences
     * such that the code to generate event can be implemented in a single
     * method.
     */
    abstract class LevelChangeProcessor {
        AdminEventCache eventCache;

        LevelChangeProcessor(AdminEventCache cache) {
            eventCache = cache;
        }

        abstract boolean isRelevant(String xpath);

        abstract AdminEvent createEvent(String componentName, String oldValue,
                String newValue);
    }

    /**
     * Processor for changes to log level.
     */
    class LogLevelChangeProcessor extends LevelChangeProcessor {
        LogLevelChangeProcessor(AdminEventCache cache) {
            super(cache);
        }

        boolean isRelevant(String xpath) {
            return eventCache.isLogLevelXPath(xpath);
        }

        AdminEvent createEvent(String componentName, String oldValue,
                String newValue) {
            LogLevelChangeEvent event = new LogLevelChangeEvent(
                   eventCache.instanceName);
            event.setModuleName(componentName);
            event.setOldLogLevel(oldValue);
            event.setNewLogLevel(newValue);
            return event;
        }
    }

    /**
     * Processor for changes to monitoring level.
     */
    class MonitoringLevelChangeProcessor extends LevelChangeProcessor {
        MonitoringLevelChangeProcessor(AdminEventCache cache) {
            super(cache);
        }

        boolean isRelevant(String xpath) {
            return eventCache.isMonitoringLevelXPath(xpath);
        }

        AdminEvent createEvent(String componentName, String oldValue,
                String newValue) {
            MonitoringLevelChangeEvent event = new MonitoringLevelChangeEvent(
                   eventCache.instanceName);
            event.setComponentName(componentName);
            event.setOldMonitoringLevel(oldValue);
            event.setNewMonitoringLevel(newValue);
            return event;
        }
    }

    /**
     * RefProcessor is an abstraction for references. The configuration
     * element server contains references to applications and resources.
     * There is a lot of commonality between the events generated for
     * changes to all references. This class tries to abstract the common
     * behavior out.
     */
    abstract class RefProcessor {
        AdminEventCache adminEventCache;
        Map refTypeXPathMap = new HashMap();
        boolean resEnabled;

        RefProcessor(AdminEventCache cache) {
            adminEventCache = cache;
        }

        /**
         * Is the xpath relevant. Used to determine whether to continue
         * processing the config change. 
         */
        abstract boolean isRelevant(String xpath);

        /**
         * Process the config change and generate events for specified ref
         * name and ref type.
         * @param refName name of the reference. The reference name is unique
         *     for any given type. For example, no two resource refs can share
         *     the same name.
         * @param refType type of the reference. This represents the type 
         *     used in events. For example, ResourceDeployEvent.RES_TYPE_JCP or
         *     ApplicationDeployEvent.APPLICATION or
         *     ModuleDeployEvent.TYPE_WEBMODULE
         * @param change the change object for the reference
         * @return true if the change was added to an existing or new event,
         *     false otherwise.
         */
        abstract boolean process(String refName, String refType,
                ConfigChange change);

        /**
         * Get name of reference from xpath.
         */
        String getRefName(String xpath) {
            return adminEventCache.getXPathToken(xpath, 3);
        }

        /**
         * Get type of reference for specified refName.
         */
        String getRefType(String refName, ArrayList allChanges) {
            // Look for object in config first. It may not exist in config
            // if it has been deleted
            String refType = getRefTypeFromConfig(refName);
            if (refType == null) {
                // If not found, then look in the config changes
                refType = getRefTypeFromChanges(refName, allChanges);
            }
            return refType;
        }

        /**
         * Get type of reference from config.
         */
        String getRefTypeFromConfig(String refName) {
            ConfigContext ctx = adminEventCache.adminConfigContext;
            if (ctx == null) {
                return null;
            }
            String refType = null;
            Iterator iter = refTypeXPathMap.entrySet().iterator();
            while (iter.hasNext()) {
                Map.Entry entry = (Map.Entry)iter.next();
                String xpath = (String)entry.getValue();
                ConfigBean bean = null;
                try {
                    bean = ctx.exactLookup(xpath);
                } catch (ConfigException ce) {
                    // Ignore config exception, as it is not relevant for the
                    // purpose of finding the ref type.
                }
                if (bean != null) {
                    refType = (String)entry.getKey();
                    resEnabled = getEnabledState(bean);
                    break;
                }
            }
            return refType;
        }

        /**
         * Get ref type from config changes. This will be used only if the
         * object no longer exists in config context (can happen if it has
         * been deleted).
         */
        String getRefTypeFromChanges(String refName, ArrayList allChanges) {
            if (allChanges == null) {
                return null;
            }
            String refType = null;
            Iterator iter = allChanges.iterator();
            while (iter.hasNext()) {
                ConfigChange change = (ConfigChange)iter.next();
                String xpath = adminEventCache.cleanXPath(change.getXPath());
                Iterator iter2 = refTypeXPathMap.entrySet().iterator();
                while (iter2.hasNext()) {
                    Map.Entry entry = (Map.Entry)iter2.next();
                    String xpath2 = (String)entry.getValue();
                    if (xpath != null && xpath.equals(xpath2)) {
                        refType = (String)entry.getKey();
                        if (change instanceof ConfigAdd) {
                            ConfigBean b = ((ConfigAdd)change).getConfigBean();
                            resEnabled = getEnabledState(b);
                        } else {
                            // The change must be delete (otherwise we would
                            // have found the refType from config context
                            resEnabled = false;
                        }
                        break;
                    }
                }
                if (refType != null) {
                    break;
                }
            }
            return refType;
        }

        /**
         * Get enabled state for the specified config bean. If the bean does
         * not have an enabled attribute, the method returns true (objects
         * without an enabled attribute are enabled)
         */
        boolean getEnabledState(ConfigBean bean) {
            String enabledStr = null;
            try {
                enabledStr = bean.getAttributeValue(ServerTags.ENABLED);
            } catch (IllegalArgumentException iae) {
                // Some resources do not have enabled attribute.
                enabledStr = "true";
            }
            return adminEventCache.getServerXmlBooleanValue(enabledStr);
        }

        /**
         * Get action code from the change. The method determines the action
         * code for the event using the change object type (for the ref) and
         * enabled state of corresponding config object.
         */
        String getActionCode(ConfigChange change) {
            String actionCode = null;
            if (change instanceof ConfigUpdate) {
                ConfigUpdate update = (ConfigUpdate)change;
                String enableStr = update.getNewValue(ServerTags.ENABLED);
                if (enableStr != null) {
                    boolean enabled =
                            adminEventCache.getServerXmlBooleanValue(
                                enableStr);
                    if (enabled && resEnabled) {
                        actionCode = BaseDeployEvent.ENABLE;
                    } else if (enabled && !resEnabled) {
                        actionCode = BaseDeployEvent.REDEPLOY;
                    } else {
                        actionCode = BaseDeployEvent.DISABLE;
                    }
                } else {
                    actionCode = BaseDeployEvent.REDEPLOY;
                }
            } else if (change instanceof ConfigAdd) {
                actionCode = BaseDeployEvent.DEPLOY;
            } else if (change instanceof ConfigDelete) {
                actionCode = BaseDeployEvent.UNDEPLOY;
            } else if (change instanceof ConfigSet) {
                // Typically set is used for existing entries
                actionCode = BaseDeployEvent.REDEPLOY;
            } else {
                // Unknown ConfigChange cannot be handled
                String msg = localStrings.getString(
                        "admin.event.unknown_configchange_for_resources");
                throw new IllegalStateException( msg );
            }
            return actionCode;
        }

        /**
         * Initialize the map of ref types and config object xpath. This map
         * is used to determine the type of ref.
         */
        abstract void initRefTypeXPathMap(String refName);

    }

    /**
     * Processor for application references.
     */
    class AppRefProcessor extends RefProcessor {
        AppRefProcessor(AdminEventCache cache) {
            super(cache);
        }

        boolean isRelevant(String xpath) {
            return adminEventCache.isAppRefXPath(xpath);
        }

        /**
         * Create or update the event for application ref.
         */
        boolean process(String refName, String refType, ConfigChange change) {
            String compType = null;
            String modType = null;
            if (BaseDeployEvent.APPLICATION.equals(refType)) {
                compType = BaseDeployEvent.APPLICATION;
            } else if ((ModuleDeployEvent.TYPE_WEBMODULE.equals(refType))
                    || (ModuleDeployEvent.TYPE_EJBMODULE.equals(refType))
                    || (ModuleDeployEvent.TYPE_CONNECTOR.equals(refType))
                    || (ModuleDeployEvent.TYPE_APPCLIENT.equals(refType))) {
                compType = BaseDeployEvent.MODULE;
                modType = refType;
            } else {
                // Unhandled ref type
                return false;
            }
            AdminEvent theEvent = null;
            if (BaseDeployEvent.APPLICATION.equals(compType)) {
                theEvent = adminEventCache.findApplicationDeployEvent(refName);
            } else {
                theEvent = adminEventCache.findModuleDeployEvent(
                        modType, refName);
            }
            if (theEvent == null) {
                String actionCode = getActionCode(change);
                // enable - disable functionality is moved to
                // ElementChange event factory
                if(BaseDeployEvent.ENABLE.equals(actionCode) ||
                   BaseDeployEvent.DISABLE.equals(actionCode) ||
                   BaseDeployEvent.REDEPLOY.equals(actionCode))
                    return false;
                if (BaseDeployEvent.APPLICATION.equals(compType)) {
                    theEvent = new ApplicationDeployEvent(
                            adminEventCache.instanceName, refName,
                            actionCode);
                } else {
                    theEvent = new ModuleDeployEvent(
                            adminEventCache.instanceName, refName,
                            modType, actionCode);
                }
                cache.add(theEvent);
            }
            theEvent.addConfigChange(change);
            return true;
        }

        /**
         * Initialize map for application ref types. Dynamic reconfig of
         * lifecycle modules is not supported and hence the map only includes
         * entries for J2EE application, EJB Module, Web Module, Connector
         * module and appclient module.
         */
        void initRefTypeXPathMap(String refName) {
            refTypeXPathMap.put(
                    BaseDeployEvent.APPLICATION,
                    ServerXPathHelper.getAppIdXpathExpression(refName));
            refTypeXPathMap.put(
                    ModuleDeployEvent.TYPE_WEBMODULE,
                    ServerXPathHelper.getWebModuleIdXpathExpression(refName));
            refTypeXPathMap.put(
                    ModuleDeployEvent.TYPE_EJBMODULE,
                    ServerXPathHelper.getEjbModuleIdXpathExpression(refName));
            refTypeXPathMap.put(
                    ModuleDeployEvent.TYPE_CONNECTOR,
                    ServerXPathHelper.getConnectorModuleIdXpathExpression(
                            refName));
            refTypeXPathMap.put(
                    ModuleDeployEvent.TYPE_APPCLIENT,
                    ServerXPathHelper.getAppClientModuleIdXpathExpression(
                            refName));
        }
    }

    /**
     * Processor for resource references.
     */
    class ResRefProcessor extends RefProcessor {
        ResRefProcessor(AdminEventCache cache) {
            super(cache);
        }

        boolean isRelevant(String xpath) {
            return adminEventCache.isResRefXPath(xpath);
        }

        /**
         * Create or update the event for resource ref.
         */
        boolean process(String refName, String refType, ConfigChange change) {
            ResourceDeployEvent event =
                    findResourceDeployEvent(refType, refName);
            if (event == null) {
                String actionCode = getActionCode(change);
                event = new ResourceDeployEvent(adminEventCache.instanceName,
                        refName, refType, actionCode);
                cache.add(event);
            }
            event.addConfigChange(change);
            return true;
        }

        /**
         * Initialize map for resource types.
         */
        void initRefTypeXPathMap(String refName) {
            refTypeXPathMap.put(
                    ResourceDeployEvent.RES_TYPE_CUSTOM,
                    ServerXPathHelper.getCustomResourceIdXpath(refName));
            refTypeXPathMap.put(
                    ResourceDeployEvent.RES_TYPE_EXTERNAL_JNDI,
                    ServerXPathHelper.getJNDIResourceIdXpath(refName));
            refTypeXPathMap.put(
                    ResourceDeployEvent.RES_TYPE_JCP,
                    ServerXPathHelper.getJDBCConnectionPoolIdXpath(refName));
            refTypeXPathMap.put(
                    ResourceDeployEvent.RES_TYPE_JDBC,
                    ServerXPathHelper.getJDBCResourceIdXpath(refName));
            refTypeXPathMap.put(
                    ResourceDeployEvent.RES_TYPE_MAIL,
                    ServerXPathHelper.getMailResourceIdXpath(refName));
            refTypeXPathMap.put(
                    ResourceDeployEvent.RES_TYPE_PMF,
                    ServerXPathHelper.getPMFactoryResourceIdXpath(refName));
            refTypeXPathMap.put(
                    ResourceDeployEvent.RES_TYPE_AOR,
                    ServerXPathHelper.getAbsoluteIdXpathExpression(
                        ServerXPathHelper.XPATH_ADMIN_OBJECT_RESOURCE,
                        ServerTags.JNDI_NAME, refName));
            refTypeXPathMap.put(
                    ResourceDeployEvent.RES_TYPE_CCP,
                    ServerXPathHelper.getAbsoluteIdXpathExpression(
                        ServerXPathHelper.XPATH_CONNECTOR_CONNECTION_POOL,
                        ServerTags.NAME, refName));
            refTypeXPathMap.put(
                    ResourceDeployEvent.RES_TYPE_CR,
                    ServerXPathHelper.getAbsoluteIdXpathExpression(
                        ServerXPathHelper.XPATH_CONNECTOR_RESOURCE,
                        ServerTags.JNDI_NAME, refName));
            refTypeXPathMap.put(
                    ResourceDeployEvent.RES_TYPE_RAC,
                    ServerXPathHelper.getAbsoluteIdXpathExpression(
                        ServerXPathHelper.XPATH_RESOURCE_ADAPTER_CONFIG,
                        ServerTags.NAME, refName));
        }
    }
}
