org.eclipse.flux.service.common.ToolingServiceProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.flux.service.common.ToolingServiceProvider.java

Source

/*******************************************************************************
 * Copyright (c) 2014 Pivotal Software, Inc. and others.
 * All rights reserved. This program and the accompanying materials are made 
 * available under the terms of the Eclipse Public License v1.0 
 * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
 * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
 *
 * Contributors:
 *     Pivotal Software, Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.flux.service.common;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import org.eclipse.flux.client.IChannelListener;
import org.eclipse.flux.client.IMessageHandler;
import org.eclipse.flux.client.MessageConnector;
import org.eclipse.flux.client.MessageConstants;
import org.json.JSONException;
import org.json.JSONObject;

public class ToolingServiceProvider {

    private static final long EXPECTED_RESPONSE_TIMEOUT = 500;
    private static final long POOL_MAINTENANCE_PERIOD = 2 * 60 * 1000;
    private static final long POOL_MAINTENANCE_NOW_DELAY = 50;
    private static final long POOL_INITIALIZATION_WAIT_TIME_STEP = 1000;
    private static final long POOL_INITIALIZATION_TIMEOUT = 2 * 60 * 1000;

    private static final String DISCOVER_SERVICE_REQUEST = "discoverServiceRequest";
    private static final String DISCOVER_SERVICE_RESPONSE = "discoverServiceResponse";
    private static final String SERVICE_STATUS_CHANGE = "serviceStatusChange";
    private static final String SERVICE_REQUIRED_REQUEST = "serviceRequiredRequest";
    private static final String SERVICE_REQUIRED_RESPONSE = "serviceRequiredResponse";

    private static final String[] JSON_PROPERTIES = new String[] { "username", "service", "requestSenderID" };

    /**
     * Service Manager is active
     */
    private AtomicBoolean active = new AtomicBoolean(false);

    /**
     * Web socket connector
     */
    private MessageConnector messageConnector;

    private ScheduledExecutorService poolMaintenanceExecutor;

    private ExecutorService serviceLauncherExecutor;

    private String serviceId;

    /**
     * Starts and stops tooling services
     */
    private IServiceLauncher serviceLauncher = null;

    private int poolSize;

    private IMessageHandler[] messageHandlers;

    private ScheduledFuture<?> poolMaintenanceFuture;

    private Exception launchException;

    final private boolean autoMaintainServicePoolSize;

    private Runnable poolMaintenanceOperation = new PoolMaintenanceOperation();

    private final IChannelListener CONNECTION_LISTENER = new IChannelListener() {

        @Override
        public void connected(String userChannel) {
            if (MessageConstants.SUPER_USER.equals(userChannel)) {
                init();
            }
        }

        @Override
        public void disconnected(String userChannel) {
            if (MessageConstants.SUPER_USER.equals(userChannel)) {
                dispose();
            }
        }

    };

    /**
     * Constructs Tooling Services Manager
     * 
     * @param host Flux server URL
     * @param serviceLauncher The tooling service starter/stopper 
     */
    public ToolingServiceProvider(final MessageConnector messageConnector, final String serviceId,
            final IServiceLauncher serviceLauncher, int poolSize, boolean autoMaintainServicePoolSize) {
        super();
        this.messageConnector = messageConnector;
        this.serviceId = serviceId;
        this.poolSize = poolSize;
        this.autoMaintainServicePoolSize = autoMaintainServicePoolSize;
        serviceLauncher(serviceLauncher);

        messageHandlers = new IMessageHandler[] {

                new IMessageHandler() {

                    @Override
                    public boolean canHandle(String type, JSONObject message) {
                        try {
                            return message.getString("username").equals(MessageConstants.SUPER_USER)
                                    && message.getString("service").equals(serviceId);
                        } catch (JSONException e) {
                            e.printStackTrace();
                            return false;
                        }
                    }

                    @Override
                    public void handle(String type, JSONObject message) {
                        try {
                            messageConnector.send(SERVICE_REQUIRED_RESPONSE,
                                    new JSONObject(message, JSON_PROPERTIES));
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public String getMessageType() {
                        return SERVICE_REQUIRED_REQUEST;
                    }

                },

                new IMessageHandler() {

                    @Override
                    public boolean canHandle(String type, JSONObject message) {
                        try {
                            return message.getString("service").equals(serviceId);
                        } catch (JSONException e) {
                            e.printStackTrace();
                            return false;
                        }
                    }

                    @Override
                    public void handle(String type, final JSONObject message) {
                        try {
                            JSONObject statusMessage = new JSONObject(message, JSON_PROPERTIES);
                            statusMessage.put("status", "unavailable");
                            String error = getError();
                            if (error == null) {
                                statusMessage.put("info", "Starting up services, please wait...");
                            } else {
                                statusMessage.put("error", getError());
                            }
                            messageConnector.send(DISCOVER_SERVICE_RESPONSE, statusMessage);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public String getMessageType() {
                        return DISCOVER_SERVICE_REQUEST;
                    }

                },

                new IMessageHandler() {

                    @Override
                    public boolean canHandle(String type, JSONObject message) {
                        try {
                            return message.getString("service").equals(serviceId)
                                    && "ready".equals(message.getString("status"))
                                    && !MessageConstants.SUPER_USER.equals(message.get("username"));
                        } catch (JSONException e) {
                            e.printStackTrace();
                            return false;
                        }
                    }

                    @Override
                    public void handle(String type, JSONObject message) {
                        schedulePoolMaintenance();
                    }

                    @Override
                    public String getMessageType() {
                        return SERVICE_STATUS_CHANGE;
                    }

                } };
    }

    private synchronized String getError() {
        if (launchException != null) {
            return launchException.getMessage();
        }
        return null;
    }

    private void init() {
        /*
         * Get off the socket IO message listening thread
         */
        new Thread() {

            @Override
            public void run() {
                if (serviceLauncher != null) {
                    serviceLauncher.init();
                }

                serviceLauncherExecutor = Executors.newFixedThreadPool(5);

                poolMaintenanceExecutor = Executors.newScheduledThreadPool(1);
                new PoolInitilizationOperation().run();

                for (IMessageHandler messageHandler : messageHandlers) {
                    messageConnector.addMessageHandler(messageHandler);
                }
            }

        }.start();
    }

    private void validateState() {
        if (active.get()) {
            throw new IllegalArgumentException("Cannot set parameters when service manager is running!");
        }
    }

    public ToolingServiceProvider serviceLauncher(IServiceLauncher serviceLauncher) {
        validateState();
        if (serviceLauncher == null) {
            throw new IllegalArgumentException("Parameter must not be NULL!");
        }
        this.serviceLauncher = serviceLauncher;
        return this;
    }

    final public void stop() {
        if (!active.get()) {
            return;
        }

        dispose();
        messageConnector.removeChannelListener(CONNECTION_LISTENER);

        active.set(false);
    }

    private void dispose() {
        for (IMessageHandler messageHandler : messageHandlers) {
            messageConnector.removeMessageHandler(messageHandler);
        }

        if (poolMaintenanceFuture != null) {
            poolMaintenanceFuture.cancel(false);
        }

        poolMaintenanceExecutor.shutdown();

        serviceLauncherExecutor.shutdown();

        if (serviceLauncher != null) {
            serviceLauncher.dispose();
        }
    }

    final public void start() {
        if (active.get()) {
            return;
        }
        active.set(true);
        messageConnector.addChannelListener(CONNECTION_LISTENER);
        if (messageConnector.isConnected(MessageConstants.SUPER_USER)) {
            CONNECTION_LISTENER.connected(MessageConstants.SUPER_USER);
        } else {
            messageConnector.connectToChannel(MessageConstants.SUPER_USER);
        }
    }

    private void startService(final int n) {
        serviceLauncherExecutor.submit(new Runnable() {

            @Override
            public void run() {
                try {
                    serviceLauncher.startService(n);
                    setLaunchException(null);
                } catch (Exception e) {
                    setLaunchException(e);
                }
            }

        });
    }

    private synchronized void setLaunchException(Exception e) {
        launchException = e;
    }

    private void schedulePoolMaintenance() {
        if (poolMaintenanceFuture != null) {
            poolMaintenanceFuture.cancel(false);
        }
        if (autoMaintainServicePoolSize) {
            poolMaintenanceFuture = poolMaintenanceExecutor.scheduleWithFixedDelay(poolMaintenanceOperation,
                    POOL_MAINTENANCE_NOW_DELAY, POOL_MAINTENANCE_PERIOD, TimeUnit.MILLISECONDS);
        } else {
            poolMaintenanceFuture = poolMaintenanceExecutor.schedule(poolMaintenanceOperation,
                    POOL_MAINTENANCE_NOW_DELAY, TimeUnit.MILLISECONDS);
        }
    }

    private class PoolMaintenanceOperation implements Runnable {

        protected synchronized int getNumberOfServicesRunning() {

            final AtomicInteger counter = new AtomicInteger(0);

            IMessageHandler messageHandler = new IMessageHandler() {

                @Override
                public void handle(String type, JSONObject message) {
                    counter.incrementAndGet();
                }

                @Override
                public String getMessageType() {
                    return DISCOVER_SERVICE_RESPONSE;
                }

                @Override
                public boolean canHandle(String type, JSONObject message) {
                    try {
                        return message.getString("service").equals(serviceId)
                                && "ready".equals(message.getString("status"));
                    } catch (JSONException e) {
                        e.printStackTrace();
                        return false;
                    }
                }
            };

            messageConnector.addMessageHandler(messageHandler);

            try {
                JSONObject discoverMessage = new JSONObject();
                discoverMessage.put("service", serviceId);
                discoverMessage.put("username", MessageConstants.SUPER_USER);
                messageConnector.send(DISCOVER_SERVICE_REQUEST, discoverMessage);
            } catch (Exception e) {
                e.printStackTrace();
            }

            try {
                Thread.sleep(EXPECTED_RESPONSE_TIMEOUT);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            messageConnector.removeMessageHandler(messageHandler);

            return counter.get();
        }

        @Override
        public synchronized void run() {
            int numberOfServicesToStart = poolSize - getNumberOfServicesRunning();
            if (numberOfServicesToStart > 0) {
                startService(numberOfServicesToStart);
            }
        }
    }

    private class PoolInitilizationOperation extends PoolMaintenanceOperation {

        @Override
        public synchronized void run() {
            int numberOfServicesToStart = poolSize - getNumberOfServicesRunning();
            if (numberOfServicesToStart > 0) {
                System.out.println("Need to start " + numberOfServicesToStart + " services");
                final AtomicInteger counter = new AtomicInteger(numberOfServicesToStart);
                IMessageHandler readyServiceHanlder = new IMessageHandler() {

                    @Override
                    public boolean canHandle(String type, JSONObject message) {
                        try {
                            return message.getString("service").equals(serviceId)
                                    && "ready".equals(message.getString("status"))
                                    && MessageConstants.SUPER_USER.equals(message.get("username"));
                        } catch (JSONException e) {
                            e.printStackTrace();
                            return false;
                        }
                    }

                    @Override
                    public void handle(String type, JSONObject message) {
                        if (counter.decrementAndGet() <= 0) {
                            messageConnector.removeMessageHandler(this);
                        }
                    }

                    @Override
                    public String getMessageType() {
                        return SERVICE_STATUS_CHANGE;
                    }
                };
                messageConnector.addMessageHandler(readyServiceHanlder);
                startService(numberOfServicesToStart);
                System.out.println("Populating service pool.");
                for (long time = 0; counter.get() > 0
                        && time < POOL_INITIALIZATION_TIMEOUT; time += POOL_INITIALIZATION_WAIT_TIME_STEP) {
                    try {
                        Thread.sleep(POOL_INITIALIZATION_WAIT_TIME_STEP);
                        System.out.print(".");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                messageConnector.removeMessageHandler(readyServiceHanlder);
                System.out.println();
                int n = counter.get();
                if (n > 0) {
                    System.out.println("WARNING: Service pool hasn't been completely initialized. " + n
                            + " service(s) are missing.");
                } else {
                    System.out.println("Service pool has been successfully populated");
                }
            } else {
                System.out.println("Service pool is already filled up");
            }
        }

    }

}