io.joynr.messaging.bounceproxy.monitoring.BounceProxyStartupReporter.java Source code

Java tutorial

Introduction

Here is the source code for io.joynr.messaging.bounceproxy.monitoring.BounceProxyStartupReporter.java

Source

package io.joynr.messaging.bounceproxy.monitoring;

/*
 * #%L
 * joynr::java::messaging::bounceproxy::controlled-bounceproxy
 * %%
 * Copyright (C) 2011 - 2013 BMW Car IT GmbH
 * %%
 * 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.
 * #L%
 */

import io.joynr.exceptions.JoynrHttpException;
import io.joynr.messaging.bounceproxy.BounceProxyControllerUrl;
import io.joynr.messaging.bounceproxy.BounceProxyPropertyKeys;
import io.joynr.messaging.bounceproxy.ControlledBounceProxyUrl;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.impl.client.CloseableHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.inject.Inject;
import com.google.inject.name.Named;

/**
 * Reporter for bounce proxy startup events.<br>
 * 
 * @author christina.strobel
 * 
 */
public class BounceProxyStartupReporter {

    private static final Logger logger = LoggerFactory.getLogger(BounceProxyStartupReporter.class);

    private boolean startupEventReported = false;

    private CloseableHttpClient httpclient;

    private long sendReportRetryIntervalMs;

    private Future<Boolean> startupReportFuture;

    private final BounceProxyControllerUrl bounceProxyControllerUrl;
    private final ControlledBounceProxyUrl controlledBounceProxyUrl;

    private ExecutorService execService;

    @Inject
    public BounceProxyStartupReporter(CloseableHttpClient httpclient,
            BounceProxyControllerUrl bounceProxyControllerUrl, ControlledBounceProxyUrl controlledBounceProxyUrl,
            ExecutorService execService,
            @Named(BounceProxyPropertyKeys.PROPERTY_BOUNCE_PROXY_SEND_LIFECYCLE_REPORT_RETRY_INTERVAL_MS) long sendReportRetryIntervalMs) {
        this.httpclient = httpclient;
        this.sendReportRetryIntervalMs = sendReportRetryIntervalMs;
        this.bounceProxyControllerUrl = bounceProxyControllerUrl;
        this.controlledBounceProxyUrl = controlledBounceProxyUrl;
        this.execService = execService;
    }

    /**
     * Notifies the monitoring service that a bounce proxy has changed its
     * lifecycle state.<br>
     * This call starts a loop that tries to send reports to the bounce proxy
     * controller until the bounce proxy is registered at the bounce proxy
     * controller.<br>
     * Startup reporting is done in a separate thread, so that a possibly
     * infinite loop won't block the whole servlet. Especially, this allows the
     * servlet to be stopped at any time.
     */
    public void startReporting() {

        // make sure we report the lifecycle event only once
        if (startupEventReported == true) {
            logger.error("startup event has already been reported or is being reported now");
            return;
        }

        startupEventReported = true;

        // the startup event has to be reported in its own thread, as this is
        // done forever. Otherwise a servlet will hang in startup mode forever
        // and can't be stopped by normal admin commands.
        Callable<Boolean> reportEventCallable = new Callable<Boolean>() {

            public Boolean call() {
                try {
                    // Notify BPC of BP startup event in a loop.
                    if (reportEventLoop()) {
                        // reporting successful within the allowed number of
                        // retries
                        return true;
                    }

                    // The BPC could not be reached, so return.
                    logger.error("Bounce Proxy lifecycle event notification failed. Exiting.");

                } catch (Exception e) {
                    logger.error("Uncaught exception in reporting lifecycle event.", e);
                }
                startupEventReported = false;
                return false;
            }
        };

        // don't wait for the callable to end so that shutting down is possible
        // at any time
        startupReportFuture = execService.submit(reportEventCallable);
    }

    /**
     * Gets whether the bounce proxy has been registered successfully at the
     * bounce proxy controller. Successful registration means that a startup
     * notification has been sent to the bounce proxy controller and the bounce
     * proxy controller responded that the bounce proxy was registered.<br>
     * This call is non-blocking, i.e. it won't wait for the bounce proxy to be
     * registered.
     * 
     * @return <code>true</code> if the bounce proxy has successfully been
     *         registered.<br>
     *         <code>false</code> if one of:
     *         <ul>
     *         <li>startup reporting hasn't started</li>
     *         <li>startup reporting is still ongoing</li>
     *         <li>startup reporting was cancelled before registration was
     *         successful</li>
     *         <li>the bounce proxy controller responds that the bounce proxy
     *         could not be registered for any reason</li>
     *         </ul>
     */
    public boolean hasBounceProxyBeenRegistered() {

        // startup reporting hasn't started
        if (!startupEventReported) {
            return false;
        }

        // startup reporting still ongoing - don't block this call!
        if (!startupReportFuture.isDone()) {
            return false;
        }

        // get result
        try {
            return startupReportFuture.get();
        } catch (InterruptedException e) {
            return false;
        } catch (ExecutionException e) {
            return false;
        } catch (CancellationException e) {
            // reporting was cancelled before startup could be reported
            return false;
        }
    }

    /**
     * Cancels startup reporting.
     */
    public void cancelReporting() {
        if (startupReportFuture != null) {
            startupReportFuture.cancel(true);
        }
        if (execService != null) {
            // interrupt reporting loop
            execService.shutdownNow();
        }
    }

    /**
     * Starts a loop to send a startup report to the monitoring service.<br>
     * The loop is executed forever, if startup reporting isn't successful.
     * 
     * @return <code>true</code> if a startup report could be sent successfully
     *         and the bounce proxy controller responded that registration was
     *         successful. If <code>false</code> is returned, then sending a
     *         report to the service failed.
     */
    private boolean reportEventLoop() {

        // try forever to reach the bounce proxy, as otherwise the bounce proxy
        // isn't useful anyway
        while (!startupReportFuture.isCancelled()) {
            try {
                reportEventAsHttpRequest();
                // if no exception is thrown, reporting was successful and we
                // can return here
                return true;
            } catch (Exception e) {
                // TODO we might have to intercept the JoynrHttpMessage if the
                // bounce proxy responds that the client could not be
                // registered. It could be that two clients try to register with
                // the same ID. Then we won't have to try to register forever.
                // Do a more detailed error handling here!
                logger.error("error notifying of Bounce Proxy startup: exception: {}, message: {}", e.getClass(),
                        e.getMessage());
            }
            try {
                Thread.sleep(sendReportRetryIntervalMs);
            } catch (InterruptedException e) {
                return false;
            }
        }
        return false;
    }

    /**
     * Reports the lifecycle event to the monitoring service as HTTP request.
     * 
     * @throws IOException
     *             if the connection with the bounce proxy controller could not
     *             be established
     * @throws JoynrHttpException
     *             if the bounce proxy responded that registering the bounce
     *             proxy did not succeed
     */
    private void reportEventAsHttpRequest() throws IOException {

        final String url = bounceProxyControllerUrl.buildReportStartupUrl(controlledBounceProxyUrl);
        logger.debug("Using monitoring service URL: {}", url);

        HttpPut putReportStartup = new HttpPut(url.trim());

        CloseableHttpResponse response = null;
        try {
            response = httpclient.execute(putReportStartup);
            StatusLine statusLine = response.getStatusLine();
            int statusCode = statusLine.getStatusCode();

            switch (statusCode) {
            case HttpURLConnection.HTTP_NO_CONTENT:
                // bounce proxy instance was already known at the bounce proxy
                // controller
                // nothing else to do
                logger.info("Bounce proxy was already registered with the controller");
                break;

            case HttpURLConnection.HTTP_CREATED:
                // bounce proxy instance was registered for the very first time
                // TODO maybe read URL
                logger.info("Registered bounce proxy with controller");
                break;

            default:
                logger.error("Failed to send startup notification: {}", response);
                throw new JoynrHttpException(statusCode,
                        "Failed to send startup notification. Bounce Proxy won't get any channels assigned.");
            }

        } finally {
            if (response != null) {
                response.close();
            }
        }
    }

}