org.red5.server.service.ShutdownServer.java Source code

Java tutorial

Introduction

Here is the source code for org.red5.server.service.ShutdownServer.java

Source

/*
 * RED5 Open Source Media Server - https://github.com/Red5/
 * 
 * Copyright 2006-2016 by respective authors (see below). All rights reserved.
 * 
 * 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.
 */

package org.red5.server.service;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import org.red5.logging.Red5LoggerFactory;
import org.red5.server.ContextLoader;
import org.red5.server.LoaderBase;
import org.red5.server.plugin.PluginRegistry;
import org.slf4j.Logger;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;

/**
 * Server/service to perform orderly and controlled shutdown and clean up of Red5.
 * 
 * @author Paul Gregoire (mondain@gmail.com)
 */
public class ShutdownServer implements ApplicationContextAware, InitializingBean, DisposableBean {

    private Logger log = Red5LoggerFactory.getLogger(ShutdownServer.class);

    /**
     * Port to which the server listens for shutdown requests. Default is 9999.
     */
    private int port = 9999;

    /**
     * Delay or wait time in seconds before exiting.
     */
    private int shutdownDelay = 30;

    /**
     * Name for the file containing the shutdown token
     */
    private String shutdownTokenFileName = "shutdown.token";

    /**
     * Spring Application context
     */
    private ApplicationContext applicationContext;

    /**
     * Red5 core context
     */
    private ApplicationContext coreContext;

    /**
     * Red5 core context
     */
    private ApplicationContext commonContext;

    /**
     * Red5 context loader
     */
    private ContextLoader contextLoader;

    // random token to verify shutdown request is genuine
    private final String token = UUID.randomUUID().toString();

    // whether the server is shutdown
    private AtomicBoolean shutdown = new AtomicBoolean(false);

    // single thread executor for the internal startup / server
    private ExecutorService executor = Executors.newSingleThreadExecutor();

    // reference to the runnable
    private Future<?> future;

    @Override
    public void afterPropertiesSet() throws Exception {
        // check for an embedded jee server
        LoaderBase jeeServer = applicationContext.getBean(LoaderBase.class);
        // lookup the jee container
        if (jeeServer == null) {
            log.info("JEE server was not found");
        } else {
            log.info("JEE server was found: {}", jeeServer.toString());
        }
        // start blocks, so it must be on its own thread
        future = executor.submit(new Runnable() {
            public void run() {
                start();
            }
        });
    }

    @Override
    public void destroy() throws Exception {
        shutdownOrderly();
        future.cancel(true);
    }

    /**
     * Starts internal server listening for shutdown requests.
     */
    public void start() {
        // dump to stdout
        System.out.printf("Token: %s%n", token);
        // write out the token to a file so that red5 may be shutdown external to this VM instance.
        try {
            // delete existing file
            Files.deleteIfExists(Paths.get(shutdownTokenFileName));
            // write to file
            Path path = Files.createFile(Paths.get(shutdownTokenFileName));
            File tokenFile = path.toFile();
            RandomAccessFile raf = new RandomAccessFile(tokenFile, "rws");
            raf.write(token.getBytes());
            raf.close();
        } catch (Exception e) {
            log.warn("Exception handling token file", e);
        }
        while (!shutdown.get()) {
            try (ServerSocket serverSocket = new ServerSocket(port);
                    Socket clientSocket = serverSocket.accept();
                    PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
                    BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));) {
                log.info("Connected - local: {} remote: {}", clientSocket.getLocalSocketAddress(),
                        clientSocket.getRemoteSocketAddress());
                String inputLine = in.readLine();
                if (inputLine != null && token.equals(inputLine)) {
                    log.info("Shutdown request validated using token");
                    out.println("Ok");
                    shutdownOrderly();
                } else {
                    out.println("Bye");
                }
            } catch (BindException be) {
                log.error("Cannot bind to port: {}, ensure no other instances are bound or choose another port",
                        port, be);
                shutdownOrderly();
            } catch (IOException e) {
                log.warn("Exception caught when trying to listen on port {} or listening for a connection", port,
                        e);
            }
        }
    }

    private void shutdownOrderly() {
        // shutdown internal listener
        shutdown.compareAndSet(false, true);
        // shutdown the plug-in launcher
        try {
            log.debug("Attempting to shutdown plugin registry");
            PluginRegistry.shutdown();
        } catch (Exception e) {
            log.warn("Exception shutting down plugin registry", e);
        }
        // shutdown the context loader
        if (contextLoader != null) {
            log.debug("Attempting to shutdown context loader");
            contextLoader.shutdown();
            contextLoader = null;
        }
        // shutdown the jee server
        LoaderBase jeeServer = applicationContext.getBean(LoaderBase.class);
        if (jeeServer != null) {
            // destroy is a DisposibleBean method not LoaderBase
            // jeeServer.destroy();
            jeeServer = null;
        }
        // attempt to kill the contexts
        final CountDownLatch latch = new CountDownLatch(3);
        new Thread(new Runnable() {
            public void run() {
                try {
                    log.debug("Attempting to close core context");
                    ((ConfigurableApplicationContext) coreContext).close();
                    latch.countDown();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(new Runnable() {
            public void run() {
                try {
                    log.debug("Attempting to close common context");
                    ((ConfigurableApplicationContext) commonContext).close();
                    latch.countDown();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(new Runnable() {
            public void run() {
                try {
                    log.debug("Attempting to close app context");
                    ((ConfigurableApplicationContext) applicationContext).close();
                    latch.countDown();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
        try {
            if (latch.await(shutdownDelay, TimeUnit.SECONDS)) {
                log.info("Application contexts are closed");
            } else {
                log.info("One or more contexts didn't close in the allotted time");
            }
        } catch (InterruptedException e) {
            log.error("Exception attempting to close app contexts", e);
        }
        // exit
        System.exit(0);
    }

    public void setPort(int port) {
        this.port = port;
    }

    public void setShutdownDelay(int shutdownDelay) {
        this.shutdownDelay = shutdownDelay;
    }

    public void setShutdownTokenFileName(String shutdownTokenFileName) {
        this.shutdownTokenFileName = shutdownTokenFileName;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public void setCoreContext(ApplicationContext coreContext) {
        this.coreContext = coreContext;
    }

    public void setCommonContext(ApplicationContext commonContext) {
        this.commonContext = commonContext;
    }

    public void setContextLoader(ContextLoader contextLoader) {
        this.contextLoader = contextLoader;
    }

}