package org.jolokia.handler;


import java.io.IOException;
import java.util.Stack;

import javax.management.*;

import org.jolokia.backend.executor.MBeanServerExecutor;
import org.jolokia.backend.executor.NotChangedException;
import org.jolokia.config.ConfigKey;
import org.jolokia.handler.list.MBeanInfoData;
import org.jolokia.request.JmxListRequest;
import org.jolokia.restrictor.Restrictor;
import org.jolokia.util.EscapeUtil;
import org.jolokia.util.RequestType;

/*
 * 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.
 */


/**
 * Handler for obtaining a list of all available MBeans and its attributes
 * and operations.
 *
 * @author roland
 * @since Jun 12, 2009
 */
public class ListHandler extends JsonRequestHandler<JmxListRequest> {


    /** {@inheritDoc} */
    public RequestType getType() {
        return RequestType.LIST;
    }

    /**
     * Constructor
     *
     * @param pRestrictor restrictor to apply
     */
    public ListHandler(Restrictor pRestrictor) {
        super(pRestrictor);
    }

    /**
     * Return true since a list handler needs to merge all information
     *
     * @return always true
     */
    @Override
    public boolean handleAllServersAtOnce(JmxListRequest pRequest) {
        return true;
    }

    /** {@inheritDoc} */
    @Override
    protected void checkForRestriction(JmxListRequest pRequest) {
        checkType();
    }

    /** {@inheritDoc} */
    @Override
    public Object doHandleRequest(MBeanServerExecutor pServerManager, JmxListRequest pRequest)
            throws IOException, NotChangedException {
        // Throw an exception if list has not changed
        checkForModifiedSince(pServerManager, pRequest);

        Stack<String> originalPathStack = EscapeUtil.reversePath(pRequest.getPathParts());
        int maxDepth = pRequest.getParameterAsInt(ConfigKey.MAX_DEPTH);
        boolean useCanonicalName = pRequest.getParameterAsBool(ConfigKey.CANONICAL_NAMING);

        ObjectName oName = null;
        try {
            Stack<String> pathStack = (Stack<String>) originalPathStack.clone();
            oName = objectNameFromPath(pathStack);

            ListMBeanEachAction action = new ListMBeanEachAction(maxDepth,pathStack,useCanonicalName);
            if (oName == null || oName.isPattern()) {
                pServerManager.each(oName, action);
            } else {
                pServerManager.call(oName,action);
            }

            return action.getResult();
        } catch (MalformedObjectNameException e) {
            throw new IllegalArgumentException("Invalid path within the MBean part given. (Path: " + pRequest.getPath() + ")",e);
        } catch (InstanceNotFoundException e) {
            throw new IllegalArgumentException("No MBean '" + oName + "' found",e);
        } catch (JMException e) {
            throw new IllegalStateException("Internal error while retrieving list: " + e, e);
        }
    }

    // will not be called

    /** {@inheritDoc} */
    @Override
    public Object doHandleRequest(MBeanServerConnection server, JmxListRequest request)
            throws InstanceNotFoundException, AttributeNotFoundException, ReflectionException, MBeanException {
        throw new UnsupportedOperationException("Internal: Method must not be called when all MBeanServers are handled at once");
    }
    @Override

    /**
     * Path handling is done directly within this handler to avoid
     * excessive memory consumption by building up the whole list
     * into memory only for extracting a part from it. So we return false here.
     *
     * @return always false
     */
    public boolean useReturnValueWithPath() {
        return false;
    }

    // ==========================================================================================================

    /**
     * Prepare an objectname patttern from a path (or "null" if no path is given)
     * @param pPathStack path
     * @return created object name (either plain or a pattern)
     */
    private ObjectName objectNameFromPath(Stack<String> pPathStack) throws MalformedObjectNameException {
        if (pPathStack.empty()) {
            return null;
        }
        Stack<String> path = (Stack<String>) pPathStack.clone();
        String domain = path.pop();
        if (path.empty()) {
            return new ObjectName(domain + ":*");
        }
        String props = path.pop();
        ObjectName mbean = new ObjectName(domain + ":" + props);
        if (mbean.isPattern()) {
            throw new IllegalArgumentException("Cannot use an MBean pattern as path (given MBean: " + mbean + ")");
        }
        return mbean;
    }

    // Class for handling list queries
    private static class ListMBeanEachAction implements MBeanServerExecutor.MBeanEachCallback, MBeanServerExecutor.MBeanAction<Void> {

        // Meta data which will get collected
        private final MBeanInfoData infoMap;

        /**
         * Handler used during iterations whe collecting MBean Meta data
         *
         * @param pMaxDepth max depth for the list tree to return
         * @param pPathStack optional stack for picking out a certain path from the list tree
         * @param pUseCanonicalName whether to use a canonical naming for the MBean property lists or the original
         *                          name
         */
        public ListMBeanEachAction(int pMaxDepth, Stack<String> pPathStack, boolean pUseCanonicalName) {
            infoMap = new MBeanInfoData(pMaxDepth,pPathStack,pUseCanonicalName);
        }

        /**
         * Add the given MBean to the collected Meta-Data for an iteration
         *
         * @param pConn connection from where to obtain the meta data
         * @param pName object name of the bean
         * @throws ReflectionException
         * @throws InstanceNotFoundException
         * @throws IOException
         */
        public void callback(MBeanServerConnection pConn, ObjectName pName)
                throws ReflectionException, InstanceNotFoundException, IOException, MBeanException {
            lookupMBeanInfo(pConn, pName);
        }

        /**
         * Add the MBeanInfo for a single MBean
         *
         * @param pConn MBeanServer on which the action should be performed
         * @param pName an objectname interpreted specifically by the action
         * @param extraArgs any extra args given as context from the outside
         * @return
         * @throws ReflectionException
         * @throws InstanceNotFoundException
         * @throws IOException
         * @throws MBeanException
         * @throws AttributeNotFoundException
         */
        public Void execute(MBeanServerConnection pConn, ObjectName pName, Object... extraArgs)
                throws ReflectionException, InstanceNotFoundException, IOException, MBeanException, AttributeNotFoundException {
            lookupMBeanInfo(pConn, pName);
            return null;
        }

        private void lookupMBeanInfo(MBeanServerConnection pConn, ObjectName pName) throws InstanceNotFoundException, ReflectionException, IOException {
            if (!infoMap.handleFirstOrSecondLevel(pName)) {
                try {
                    MBeanInfo mBeanInfo = pConn.getMBeanInfo(pName);
                    infoMap.addMBeanInfo(mBeanInfo, pName);
                } catch (IOException exp) {
                    infoMap.handleException(pName, exp);
                } catch (InstanceNotFoundException exp) {
                    infoMap.handleException(pName, exp);
                } catch (IllegalStateException exp) {
                    infoMap.handleException(pName, exp);
                } catch (IntrospectionException exp) {
                    throw new IllegalArgumentException("Cannot extra MBeanInfo for " + pName + ": " + exp,exp);
                }
            }
        }


        /**
         * Get the overall result
         *
         * @return the meta data suitable for JSON serialization
         */
        public Object getResult() {
            return infoMap.truncate();
        }

    }
}
