package org.jolokia.backend;

/*
 * Copyright 2009-2013 Roland Huss
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.util.UUID;

import javax.management.*;

import org.jolokia.backend.executor.NotChangedException;
import org.jolokia.config.ConfigKey;
import org.jolokia.config.Configuration;
import org.jolokia.converter.Converters;
import org.jolokia.detector.ServerHandle;
import org.jolokia.discovery.JolokiaDiscovery;
import org.jolokia.discovery.JolokiaDiscoveryMBean;
import org.jolokia.handler.JsonRequestHandler;
import org.jolokia.handler.RequestHandlerManager;
import org.jolokia.history.HistoryStore;
import org.jolokia.request.JmxRequest;
import org.jolokia.restrictor.Restrictor;
import org.jolokia.util.DebugStore;
import org.jolokia.util.LogHandler;
import org.jolokia.util.NetworkUtil;

/**
 * Dispatcher which dispatches to one or more local {@link javax.management.MBeanServer}.
 *
 * @author roland
 * @since Nov 11, 2009
 */
public class LocalRequestDispatcher implements RequestDispatcher {

    // Agent ID
    private final String agentId;

    // Handler for finding and merging the various MBeanHandler
    private MBeanServerHandler mBeanServerHandler;

    private RequestHandlerManager requestHandlerManager;

    // An (optional) qualifier for registering MBeans.
    private String qualifier;

    // Logger
    private LogHandler log;

    /**
     * Create a new local dispatcher which accesses local MBeans.
     *
     * @param pConverters object/string converters
     * @param pRestrictor restrictor which checks the access for various operations
     * @param pConfig agent configuration
     * @param pLogHandler local handler used for logging out errors and warnings
     */
    public LocalRequestDispatcher(Converters pConverters, Restrictor pRestrictor, Configuration pConfig, LogHandler pLogHandler) {
        // Get all MBean servers we can find. This is done by a dedicated
        // handler object
        mBeanServerHandler = new MBeanServerHandler(pConfig,pLogHandler);
        qualifier = pConfig.get(ConfigKey.MBEAN_QUALIFIER);
        log = pLogHandler;
        agentId = NetworkUtil.replaceExpression(pConfig.get(ConfigKey.AGENT_ID));

        // Request handling manager 
        requestHandlerManager =
                new RequestHandlerManager(pConfig,pConverters,mBeanServerHandler.getServerHandle(),pRestrictor);

    }

    // Can handle any request
    /** {@inheritDoc} */
    public boolean canHandle(JmxRequest pJmxRequest) {
        return true;
    }

    /** {@inheritDoc} */
    public boolean useReturnValueWithPath(JmxRequest pJmxRequest) {
        JsonRequestHandler handler = requestHandlerManager.getRequestHandler(pJmxRequest.getType());
        return handler.useReturnValueWithPath();
    }

    /** {@inheritDoc} */
    public Object dispatchRequest(JmxRequest pJmxReq)
            throws InstanceNotFoundException, AttributeNotFoundException, ReflectionException, MBeanException, NotChangedException {
        JsonRequestHandler handler = requestHandlerManager.getRequestHandler(pJmxReq.getType());
        return mBeanServerHandler.dispatchRequest(handler, pJmxReq);
    }

    /**
     * Initialise this request dispatcher, which will register a {@link ConfigMBean} for easy external
     * access to the {@link HistoryStore} and {@link DebugStore}. Also a {@link JolokiaDiscoveryMBean}
     * is registered
     *
     * @param pHistoryStore history store to be managed from within an MBean
     * @param pDebugStore managed debug store
     * @throws MalformedObjectNameException if our MBean's name is wrong (which cannot happen)
     * @throws MBeanRegistrationException if registration fails
     * @throws NotCompliantMBeanException if we have a non compliant MBean (cannot happen, too)
     */
    public void initMBeans(HistoryStore pHistoryStore, DebugStore pDebugStore)
            throws MalformedObjectNameException, MBeanRegistrationException, NotCompliantMBeanException {

        // Register the Config MBean
        String oName = createObjectNameWithQualifier(Config.OBJECT_NAME);
        try {
            Config config = new Config(pHistoryStore,pDebugStore,oName);
            mBeanServerHandler.registerMBean(config,oName);
        } catch (InstanceAlreadyExistsException exp) {
            String alternativeOName = oName + ",uuid=" + UUID.randomUUID();
            try {
                // Another instance has already started a Jolokia agent within the JVM. We are trying to add the MBean nevertheless with
                // a dynamically generated ObjectName. Of course, it would be good to have a more semantic meaning instead of
                // a random number, but this can already be performed with a qualifier
                log.info(oName + " is already registered. Adding it with " + alternativeOName + ", but you should revise your setup in " +
                         "order to either use a qualifier or ensure, that only a single agent gets registered (otherwise history functionality might not work)");
                Config config = new Config(pHistoryStore,pDebugStore,alternativeOName);
                mBeanServerHandler.registerMBean(config,alternativeOName);
            } catch (InstanceAlreadyExistsException e) {
                log.error("Cannot even register fallback MBean with name " + alternativeOName + ". Should never happen. Really.",e);
            }
        }

        // Register another Config MBean (which dispatched to the stores anyway) for access by
        // jmx4perl version < 0.80
        String legacyOName = createObjectNameWithQualifier(Config.LEGACY_OBJECT_NAME);
        try {
            Config legacyConfig = new Config(pHistoryStore,pDebugStore,legacyOName);
            mBeanServerHandler.registerMBean(legacyConfig,legacyOName);
        } catch (InstanceAlreadyExistsException exp) {
            log.info("Cannot register (legacy) MBean handler for config store with name " + legacyOName + " since it already exists. " +
                     "This is the case if another agent has been already started within the same JVM. The registration is skipped.");
        }

        try {
            mBeanServerHandler.registerMBean(new JolokiaDiscovery(agentId,log),JolokiaDiscoveryMBean.OBJECT_NAME);
        } catch (InstanceAlreadyExistsException e) {
            // Ignore since there is already one registered.
            log.info("Jolokia Discovery MBean registration is skipped because there is already one registered.");
        }
    }

    /**
     * Unregister the config MBean
     *
     * @throws JMException if unregistration fails
     */
    public void destroy() throws JMException {
        mBeanServerHandler.destroy();
    }

    /**
     * Get information about the current server
     * @return the server information
     */
    public ServerHandle getServerHandle() {
        return mBeanServerHandler.getServerHandle();
    }

    private String createObjectNameWithQualifier(String pOName) {
        return pOName + (qualifier != null ? "," + qualifier : "");
    }
}
