package org.jolokia.handler;

/*
 * 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.io.IOException;
import java.lang.management.ManagementFactory;
import java.util.*;

import javax.management.*;

import org.easymock.EasyMock;
import org.jolokia.backend.executor.MBeanServerExecutor;
import org.jolokia.backend.MBeanServerExecutorLocal;
import org.jolokia.backend.executor.NotChangedException;
import org.jolokia.request.JmxListRequest;
import org.jolokia.request.JmxRequestBuilder;
import org.jolokia.restrictor.AllowAllRestrictor;
import org.jolokia.config.ConfigKey;
import org.jolokia.handler.list.DataKeys;
import org.jolokia.util.RequestType;
import org.testng.annotations.*;

import static org.easymock.EasyMock.*;
import static org.testng.Assert.*;

/**
 * @author roland
 * @since 11.04.11
 */
public class ListHandlerTest extends BaseHandlerTest {

    private ListHandler              handler;
    private MBeanServerExecutorLocal executor;

    @BeforeMethod
    private void createHandler() {
        handler = new ListHandler(new AllowAllRestrictor());
        executor = new MBeanServerExecutorLocal();
    }

    @AfterMethod
    private void destroy() {
        executor.destroy();
    }

    @Test
    public void singleSlashPath() throws Exception {
        for (String p : new String[]{null, "", "/"}) {
            JmxListRequest request = new JmxRequestBuilder(RequestType.LIST).path(p).build();
            Map res = execute(request);
            assertTrue(res.containsKey("java.lang"));
            assertTrue(res.get("java.lang") instanceof Map);
        }
    }

    @Test
    public void domainPath() throws Exception {
        JmxListRequest request = new JmxRequestBuilder(RequestType.LIST).pathParts("java.lang").build();
        Map res = execute(request);
        assertTrue(res.containsKey("type=Memory"));
        assertTrue(res.get("type=Memory") instanceof Map);
    }

    @Test
    public void propertiesPath() throws Exception {
        JmxListRequest request = new JmxRequestBuilder(RequestType.LIST).pathParts("java.lang", "type=Memory").build();
        Map res = execute(request);
        for (String k : new String[]{DataKeys.DESCRIPTION.getKey(), DataKeys.OPERATIONS.getKey(), DataKeys.ATTRIBUTES.getKey(), DataKeys.CLASSNAME.getKey()}) {
            assertTrue(res.containsKey(k));
        }
        assertEquals(res.size(), 4);
    }

    @Test
    public void attrPath() throws Exception {
        JmxListRequest request = new JmxRequestBuilder(RequestType.LIST).pathParts("java.lang","type=Memory","attr").build();
        Map res = execute(request);
        assertTrue(res.containsKey("HeapMemoryUsage"));
    }

    @Test
    public void descPath() throws Exception {
        JmxListRequest request = new JmxRequestBuilder(RequestType.LIST).pathParts("java.lang","type=Memory","desc").build();
        String res = (String) handler.handleRequest(executor, request);
        assertNotNull(res);
    }

    @Test
    public void descPathWithDepth() throws Exception {
        JmxListRequest request = new JmxRequestBuilder(RequestType.LIST)
                .pathParts("java.lang","type=Memory","desc")
                .option(ConfigKey.MAX_DEPTH,"4")
                .build();
        String res = (String) handler.handleRequest(executor, request);
        assertNotNull(res);
    }

    @Test
    public void opPath() throws Exception {
        JmxListRequest request = new JmxRequestBuilder(RequestType.LIST).pathParts("java.lang","type=Memory","op").build();
        Map res = execute(request);
        assertTrue(res.containsKey("gc"));
    }

    @Test
    public void maxDepth1() throws Exception {
        JmxListRequest request = new JmxRequestBuilder(RequestType.LIST).option(ConfigKey.MAX_DEPTH,"1").build();
        Map res = execute(request);
        assertTrue(res.containsKey("java.lang"));
        assertFalse(res.get("java.lang") instanceof Map);
    }

    @Test
    public void maxDepth2() throws Exception {
        JmxListRequest request = new JmxRequestBuilder(RequestType.LIST).option(ConfigKey.MAX_DEPTH,"2").build();
        Map res = execute(request);
        assertTrue(res.containsKey("java.lang"));
        Map inner = (Map) res.get("java.lang");
        assertTrue(inner.containsKey("type=Memory"));
        assertFalse(inner.get("type=Memory") instanceof Map);
    }


    @Test
    public void maxDepthAndPath() throws Exception {
        JmxListRequest request = new JmxRequestBuilder(RequestType.LIST).pathParts("java.lang","type=Memory")
                .option(ConfigKey.MAX_DEPTH, "3").build();
        Map res =  execute(request);
        assertEquals(res.size(), 4);
        Map ops = (Map) res.get("op");
        assertTrue(ops.containsKey("gc"));
        assertTrue(ops.get("gc") instanceof Map);
        Map attrs = (Map) res.get("attr");
        // Java 7 introduces a new attribute 'ObjectName' here
        assertEquals(attrs.size(),attrs.containsKey("ObjectName") ? 5 : 4);
        assertTrue(attrs.get("HeapMemoryUsage") instanceof Map);
        assertTrue(res.get("desc") instanceof String);
    }

    @Test
    public void keyOrder() throws Exception {
        JmxListRequest request = new JmxRequestBuilder(RequestType.LIST).option(ConfigKey.CANONICAL_NAMING,"true").build();
        Map res = execute(request);
        Map<String,?> mbeans = (Map<String,?>) res.get("java.lang");
        for (String key : mbeans.keySet()) {
            String parts[] = key.split(",");
            String partsSorted[] = parts.clone();
            Arrays.sort(partsSorted);
            assertEquals(parts,partsSorted);
        }
    }

    @Test
    public void truncatedList() throws Exception {
        JmxListRequest request = new JmxRequestBuilder(RequestType.LIST).pathParts("java.lang", "type=Runtime").build();
        Map res = execute(request);
        assertFalse(res.containsKey("op"));
        assertEquals(res.size(),3);
    }

    @Test(expectedExceptions = { IllegalArgumentException.class })
    public void invalidPath() throws Exception {
        JmxListRequest request = new JmxRequestBuilder(RequestType.LIST)
                .pathParts("java.lang", "type=Memory", "attr", "unknownAttribute")
                .build();
        execute(request);
    }

    @Test(expectedExceptions = { IllegalArgumentException.class })
    public void invalidPath2() throws Exception {
        JmxListRequest request = new JmxRequestBuilder(RequestType.LIST)
                .pathParts("java.lang", "type=Runtime", "op", "bla")
                .option(ConfigKey.MAX_DEPTH,"3")
                .build();
        execute(request);
    }

    @Test(expectedExceptions = { IllegalArgumentException.class })
    public void invalidPath3() throws Exception {
        JmxListRequest request = new JmxRequestBuilder(RequestType.LIST)
                .pathParts("java.lang", "type=Runtime", "bla")
                .option(ConfigKey.MAX_DEPTH,"3")
                .build();
        execute(request);
    }

    @Test(expectedExceptions = { IllegalArgumentException.class })
    public void invalidPath4() throws Exception {
        JmxListRequest request = new JmxRequestBuilder(RequestType.LIST)
                .pathParts("java.lang", "type=*")
                .build();
        execute(request);
    }

    @Test
    public void invalidPath5() throws Exception {
        for (String what : new String[] { "attr", "op", "not" }) {
            try {
                JmxListRequest request = new JmxRequestBuilder(RequestType.LIST)
                        .pathParts("java.lang", "type=Memory", what, "HeapMemoryUsage", "bla")
                        .build();
                execute(request);
                fail();
            } catch (IllegalArgumentException exp) {
                assertTrue(exp.getMessage().contains("bla"));
            }
        }
    }

    @Test(expectedExceptions = { IllegalArgumentException.class }, expectedExceptionsMessageRegExp = ".*bla.*")
    public void invalidPath8() throws Exception {
        JmxListRequest request = new JmxRequestBuilder(RequestType.LIST)
                .pathParts("java.lang", "type=Memory", "desc", "bla")
                .build();
        execute(request);
    }

    private Map execute(JmxListRequest pRequest) throws ReflectionException, InstanceNotFoundException, MBeanException, AttributeNotFoundException, IOException, NotChangedException {
        return (Map) handler.handleRequest(executor, pRequest);
    }


    @Test
    public void emptyMaps() throws Exception {
        JmxListRequest request = new JmxRequestBuilder(RequestType.LIST)
                .pathParts("java.lang", "type=Runtime", "op")
                .build();
        Map res = (Map) handler.handleRequest(executor,request);
        assertEquals(res.size(),0);

        request = new JmxRequestBuilder(RequestType.LIST)
                .pathParts("java.lang", "type=Runtime", "not")
                .build();
        res = (Map) handler.handleRequest(executor,request);
        assertEquals(res.size(),0);
    }

    @Test
    public void singleMBeanMultipleServers() throws MalformedObjectNameException, InstanceNotFoundException, IOException, AttributeNotFoundException, ReflectionException, MBeanException, IntrospectionException, NotChangedException {
        JmxListRequest request = new JmxRequestBuilder(RequestType.LIST)
                .pathParts("java.lang", "type=Memory", "attr")
                .build();
        MBeanServerConnection dummyConn = EasyMock.createMock(MBeanServerConnection.class);
        Set<MBeanServerConnection> conns = new LinkedHashSet<MBeanServerConnection>();
        conns.add(dummyConn);
        conns.add(ManagementFactory.getPlatformMBeanServer());

        expect(dummyConn.getMBeanInfo(new ObjectName("java.lang:type=Memory"))).andThrow(new InstanceNotFoundException());
        replay(dummyConn);
        Map res = (Map) handler.handleRequest(executor,request);
        assertEquals(((Map) res.get("Verbose")).get("type"),"boolean");
    }

    @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = ".*No MBean.*")
    public void noMBeanMultipleServers() throws MalformedObjectNameException, InstanceNotFoundException, IOException, AttributeNotFoundException, ReflectionException, MBeanException, IntrospectionException, NotChangedException {
        JmxListRequest request = new JmxRequestBuilder(RequestType.LIST)
                .pathParts("bullerbue", "country=sweden")
                .build();
        MBeanServer dummyConn = EasyMock.createMock(MBeanServer.class);

        MBeanServerExecutor servers = getMBeanServerManager(dummyConn, ManagementFactory.getPlatformMBeanServer());

        ObjectName name = new ObjectName("bullerbue:country=sweden");
        expect(dummyConn.getMBeanInfo(name)).andThrow(new InstanceNotFoundException());
        expect(dummyConn.isRegistered(name)).andReturn(false);
        replay(dummyConn);
        handler.handleRequest(servers,request);
    }


}
