/*
 * Copyright 2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

/*
 * @test
 * @bug 5108776
 * @summary Test that the various Executor parameters in an EventClient do
 * what they are supposed to.
 * @author Eamonn McManus
 */

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import javax.management.event.EventClient;
import javax.management.event.EventClientDelegate;
import javax.management.event.EventClientDelegateMBean;
import javax.management.event.FetchingEventRelay;
import javax.management.remote.MBeanServerForwarder;

public class EventClientExecutorTest {
    private static volatile String failure;
    private static final Set testedPrefixes = new HashSet();

    public static void main(String[] args) throws Exception {
        Executor fetchExecutor = Executors.newSingleThreadExecutor(
                new NamedThreadFactory("FETCH"));
        Executor listenerExecutor = Executors.newSingleThreadExecutor(
                new NamedThreadFactory("LISTENER"));
        ScheduledExecutorService leaseScheduler =
            Executors.newSingleThreadScheduledExecutor(
                new NamedThreadFactory("LEASE"));

        MBeanServer mbs = MBeanServerFactory.newMBeanServer();
        MBeanServerForwarder mbsf = EventClientDelegate.newForwarder();
        mbsf.setMBeanServer(mbs);
        mbs = mbsf;

        EventClientDelegateMBean ecd = EventClientDelegate.getProxy(mbs);
        ecd = (EventClientDelegateMBean) Proxy.newProxyInstance(
                EventClientDelegateMBean.class.getClassLoader(),
                new Class<?>[] {EventClientDelegateMBean.class},
                new DelegateCheckIH(ecd));

        ObjectName mbeanName = new ObjectName("d:type=Notifier");
        Notifier notifier = new Notifier();
        mbs.registerMBean(notifier, mbeanName);

        FetchingEventRelay eventRelay = new FetchingEventRelay(
                ecd, fetchExecutor);
        EventClient ec = new EventClient(
                ecd, eventRelay, listenerExecutor, leaseScheduler, 1000L);
        NotificationListener checkListener = new NotificationListener() {
            public void handleNotification(Notification notification,
                                           Object handback) {
                assertThreadName("listener dispatch", "LISTENER");
            }
        };
        ec.addNotificationListener(mbeanName, checkListener, null, null);

        mbs.invoke(mbeanName, "send", null, null);

        // Now wait until we have seen all three thread types.
        long deadline = System.currentTimeMillis() + 5000;
        synchronized (testedPrefixes) {
            while (testedPrefixes.size() < 3 && failure == null) {
                long remain = deadline - System.currentTimeMillis();
                if (remain <= 0) {
                    fail("Timed out waiting for all three thread types to show, " +
                            "saw only " + testedPrefixes);
                    break;
                }
                try {
                    testedPrefixes.wait(remain);
                } catch (InterruptedException e) {
                    fail("Unexpected InterruptedException");
                    break;
                }
            }
        }

        // We deliberately don't close the EventClient to check that it has
        // not created any non-daemon threads.

        if (failure != null)
            throw new Exception("TEST FAILED: " + failure);
        else
            System.out.println("TEST PASSED");
    }

    public static interface NotifierMBean {
        public void send();
    }

    public static class Notifier extends NotificationBroadcasterSupport
            implements NotifierMBean {
        public void send() {
            Notification n = new Notification("a.b.c", this, 0L);
            sendNotification(n);
        }
    }

    static void fail(String why) {
        System.out.println("FAIL: " + why);
        failure = why;
    }

    static void assertThreadName(String what, String prefix) {
        String name = Thread.currentThread().getName();
        if (!name.startsWith(prefix)) {
            fail("Wrong thread for " + what + ": " + name);
            return;
        }

        synchronized (testedPrefixes) {
            if (testedPrefixes.add(prefix))
                testedPrefixes.notify();
        }
    }

    private static class DelegateCheckIH implements InvocationHandler {
        private final EventClientDelegateMBean ecd;

        public DelegateCheckIH(EventClientDelegateMBean ecd) {
            this.ecd = ecd;
        }

        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            String methodName = method.getName();
            if (methodName.equals("fetchNotifications"))
                assertThreadName("fetchNotifications", "FETCH");
            else if (methodName.equals("lease"))
                assertThreadName("lease renewal", "LEASE");
            try {
                return method.invoke(ecd, args);
            } catch (InvocationTargetException e) {
                throw e.getCause();
            }
        }
    }

    private static class NamedThreadFactory implements ThreadFactory {
        private final String namePrefix;
        private int count;

        NamedThreadFactory(String namePrefix) {
            this.namePrefix = namePrefix;
        }

        public synchronized Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            t.setName(namePrefix + " " + ++count);
            t.setDaemon(true);
            return t;
        }
    }
}