All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.nanoframework.server.JettyCustomServer Maven / Gradle / Ivy

/*
 * Copyright 2015-2016 the original author or authors.
 *
 * 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.nanoframework.server;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.List;
import java.util.Properties;
import java.util.Scanner;
import java.util.concurrent.Executors;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.xml.XmlConfiguration;
import org.nanoframework.commons.loader.PropertiesLoader;
import org.nanoframework.commons.support.logging.Logger;
import org.nanoframework.commons.support.logging.LoggerFactory;
import org.nanoframework.commons.util.Assert;
import org.nanoframework.commons.util.RuntimeUtil;
import org.nanoframework.commons.util.StringUtils;
import org.nanoframework.core.context.ApplicationContext;
import org.nanoframework.core.context.ApplicationContext.JettyRedisSession;
import org.nanoframework.server.cmd.Commands;
import org.nanoframework.server.cmd.Mode;
import org.nanoframework.server.exception.JettyServerException;
import org.nanoframework.server.exception.ReadXMLException;
import org.nanoframework.server.session.redis.RedisSessionIdManager;
import org.nanoframework.server.session.redis.RedisSessionManager;

/**
 * Jetty Server
 * 
 * @author yanghe
 * @since 1.0
 */
public class JettyCustomServer extends Server {
    private static final Logger LOGGER = LoggerFactory.getLogger(JettyCustomServer.class);

    private static String DEFAULT_RESOURCE_BASE = "./webRoot";

    private static String DEFAULT_WEB_XML_PATH = DEFAULT_RESOURCE_BASE + "/WEB-INF/web.xml";

    private static String WEB_DEFAULT = DEFAULT_RESOURCE_BASE + "/WEB-INF/webdefault.xml";

    private static String DEFAULT_JETTY_CONFIG = DEFAULT_RESOURCE_BASE + "/WEB-INF/jetty.xml";
    
    private static final String JETTY_PID_FILE = "jetty.pid";
    
    private Properties context;
    
    private JettyCustomServer() {
        super();
        loadContext(ApplicationContext.MAIN_CONTEXT);
        init(DEFAULT_JETTY_CONFIG, context.getProperty(ApplicationContext.CONTEXT_ROOT), null, null, null);
    }

    private JettyCustomServer(final String contextPath) {
        super();
        loadContext(contextPath);
        init(DEFAULT_JETTY_CONFIG, context.getProperty(ApplicationContext.CONTEXT_ROOT), null, null, null);
    }
    
    public static JettyCustomServer server() {
        return new JettyCustomServer();
    }
    
    public static JettyCustomServer server(final String contextPath) {
        return new JettyCustomServer(contextPath);
    }

    protected void loadContext(final String contextPath) {
        Assert.hasLength(contextPath, "无效的context属性文件路径");
        context = PropertiesLoader.load(contextPath);
    }
    
    protected void init(final String xmlConfigPath, final String contextRoot, final String resourceBase, final String webXmlPath,
            final String warPath) {
        if (StringUtils.isNotBlank(xmlConfigPath)) {
            DEFAULT_JETTY_CONFIG = xmlConfigPath;
            readXmlConfig(xmlConfigPath);
        }

        if (StringUtils.isNotEmpty(warPath) && StringUtils.isNotEmpty(contextRoot)) {
            applyHandle(contextRoot, warPath);
        } else {
            if (StringUtils.isNotEmpty(resourceBase)) {
                DEFAULT_RESOURCE_BASE = resourceBase;
            }

            if (StringUtils.isNotEmpty(webXmlPath)) {
                DEFAULT_WEB_XML_PATH = webXmlPath;
            }

            if (StringUtils.isNotBlank(contextRoot)) {
                applyHandle(contextRoot, warPath);
            }
        }
    }

    private void readXmlConfig(final String configPath) {
        try {
            final XmlConfiguration configuration = new XmlConfiguration(new FileInputStream(configPath));
            configuration.configure(this);
        } catch (final Throwable e) {
            throw new ReadXMLException(e.getMessage(), e);
        }
    }

    private void applyHandle(final String contextPath, final String warPath) {
        final ContextHandlerCollection handler = new ContextHandlerCollection();
        final WebAppContext webapp = new WebAppContext();
        webapp.setContextPath(contextPath);
        webapp.setDefaultsDescriptor(WEB_DEFAULT);
        if (StringUtils.isEmpty(warPath)) {
            webapp.setResourceBase(DEFAULT_RESOURCE_BASE);
            webapp.setDescriptor(DEFAULT_WEB_XML_PATH);
        } else {
            webapp.setWar(warPath);
        }
        
        applySessionHandler(webapp);
        
        handler.addHandler(webapp);
        super.setHandler(handler);
    }
    
    protected void applySessionHandler(final WebAppContext webapp) {
        final String jettyCluster = context.getProperty(JettyRedisSession.JETTY_CLUSTER);
        if (StringUtils.isNotBlank(jettyCluster)) {
            setSessionIdManager(createRedisSessionIdManager(jettyCluster));
            webapp.setSessionHandler(new SessionHandler(createRedisSessionManager(jettyCluster)));
        }
    }
    
    protected RedisSessionIdManager createRedisSessionIdManager(final String jettyCluster) {
        final RedisSessionIdManager sessionIdManager = new RedisSessionIdManager(this, jettyCluster);
        
        final String workerName = context.getProperty(JettyRedisSession.JETTY_CLUSTER_WORKER_NAME, JettyRedisSession.DEFAULT_JETTY_CLUSTER_WORKER_NAME);
        sessionIdManager.setWorkerName(workerName);
        
        final long scavengerInterval = Long.parseLong(context.getProperty(JettyRedisSession.JETTY_SESSION_SCAVENGER_INTERVAL, JettyRedisSession.DEFAULT_SCAVENGER_INTERVAL));
        sessionIdManager.setScavengerInterval(scavengerInterval);
        
        return sessionIdManager;
    }
    
    protected RedisSessionManager createRedisSessionManager(final String jettyCluster) {
        final RedisSessionManager sessionManager = new RedisSessionManager(jettyCluster);
        
        final long saveInterval = Long.parseLong(context.getProperty(JettyRedisSession.JETTY_SESSION_SAVE_INTERVAL, JettyRedisSession.DEFAULT_SESSION_SAVE_INTERVAL));
        sessionManager.setSaveInterval(saveInterval);
        
        return sessionManager;
    }

    protected void startServer() {
        try {
            writePid2File();
            super.start();
            LOGGER.info("Current thread: {} | Idle thread: {}", super.getThreadPool().getThreads(), super.getThreadPool().getIdleThreads());
            super.join();
        } catch (final Throwable e) {
            // NANO-386: fixed Address already in use bug
            LOGGER.error("Bootstrap server error: {}", e.getMessage());
            System.exit(1);
        }
    }

    /**
     * 根据PID优雅停止进程
     * 
     * @since 1.2.15
     */
    protected void stopServer() {
        try {
            final String pid = readPidFile();
            if (StringUtils.isNotBlank(pid)) {
                if (RuntimeUtil.existsProcess(pid)) {
                    RuntimeUtil.exitProcess(pid);
                    return;
                }

                return;
            }

            throw new JettyServerException("Not found jetty.pid");
        } catch (Throwable e) {
            if (e instanceof JettyServerException) {
                throw (JettyServerException) e;
            }

            throw new JettyServerException("Stop Server error: " + e.getMessage());
        } finally {
            final File file = new File(JETTY_PID_FILE);
            if (file.exists()) {
                file.delete();
            }
        }
    }

    protected void startServerDaemon() {
        Executors.newFixedThreadPool(1, (runnable) -> {
            final Thread jetty = new Thread(runnable);
            jetty.setName("Jetty Server Deamon: " + System.currentTimeMillis());
            return jetty;
        }).execute(() -> startServer());
    }

    protected void writePid2File() {
        try {
            final String pid = RuntimeUtil.PID;
            final File file = new File(JETTY_PID_FILE);
            final Mode mode = mode(false);
            if (file.exists()) {
                if (mode == Mode.PROD) {
                    LOGGER.error("服务已启动或异常退出,请先删除jetty.pid文件后重试");
                    System.exit(1);
                } else if (RuntimeUtil.existsProcess(readPidFile())) {
                    LOGGER.error("服务已启动,请先停止原进程");
                    System.exit(1);
                } else {
                    file.delete();
                }
            }
            
            file.createNewFile();
            file.deleteOnExit();
            watcherPid(file);

            try (FileWriter writer = new FileWriter(file, false)) {
                writer.write(pid);
                writer.flush();
            }
        } catch (Throwable e) {
            if (e instanceof JettyServerException) {
                throw (JettyServerException) e;
            }

            throw new JettyServerException(e.getMessage(), e);
        }
    }
    
    protected void watcherPid(final File jettyPidFile) throws IOException {
        final WatchService watcher = FileSystems.getDefault().newWatchService();
        final Path path = Paths.get(".");
        path.register(watcher, StandardWatchEventKinds.ENTRY_DELETE);
        
        Executors.newFixedThreadPool(1, (runnable) -> {
            final Thread jetty = new Thread(runnable);
            jetty.setName("Jetty PID Watcher: " + System.currentTimeMillis());
            return jetty;
        }).execute(() -> {
            try {
                for (;;) {
                    final WatchKey watchKey = watcher.take();
                    final List> events = watchKey.pollEvents();
                    for(WatchEvent event : events) {
                        final String fileName = ((Path) event.context()).toFile().getAbsolutePath();
                        if (jettyPidFile.getAbsolutePath().equals(fileName)) {
                            LOGGER.info("jetty.pid已被删除,应用进入退出流程");
                            System.exit(0);
                        }
                    }
                    
                    watchKey.reset();
                }
            } catch (final InterruptedException e) {
                LOGGER.info("Stoped File Watcher");
            }
        });
    }

    protected String readPidFile() {
        try {
            final File file = new File(JETTY_PID_FILE);
            if (file.exists()) {
                try (final InputStream input = new FileInputStream(file); final Scanner scanner = new Scanner(input)) {
                    final StringBuilder builder = new StringBuilder();
                    while (scanner.hasNextLine()) {
                        builder.append(scanner.nextLine());
                    }

                    return builder.toString();
                }
            }

            return StringUtils.EMPTY;
        } catch (Throwable e) {
            throw new JettyServerException("Read PID file error: " + e.getMessage());
        }
    }

    public final void bootstrap(String... args) {
        if (args.length > 0) {
            final Mode mode = mode(true);
            final Commands cmd = cmd(args, mode);
            switch (cmd) {
                case START:
                    startServerDaemon();
                    break;
                case STOP:
                    stopServer();
                    break;
                case VERSION:
                    version();
                    break;
                case HELP:
                    usage();
                    break;
            }
            
        } else {
            usage();
        }
    }
    
    protected Commands cmd(final String[] args, final Mode mode) {
        Commands cmd;
        try {
            cmd = Commands.valueOf(args[0].toUpperCase());
        } catch (final Throwable e) {
            if (mode == Mode.DEV) {
                cmd = Commands.START;
            } else {
                throw new JettyServerException("Unknown command in args list");
            }
        }
        
        return cmd;
    }
    
    protected Mode mode(final boolean output) {
        Mode mode;
        try {
            mode = Mode.valueOf(context.getProperty(ApplicationContext.MODE, Mode.PROD.name()));
            if (output) {
                switch (mode) {
                    case DEV:
                        System.out.println("Please set  in context.properties to set 'PROD' mode.");
                        break;
                    case PROD:
                        System.out.println("Please set  in context.properties to set 'DEV' mode.");
                        break;
                }
            }
        } catch (final Throwable e) {
            if (output) {
                System.out.println("Unknown Application Mode, setting default Mode: 'PROD'.");
                System.out.println("Please set  in context.properties to set 'DEV' or 'PROD' mode.");
            }
            
            mode = Mode.PROD;
        }
        
        return mode;
    }
    
    protected void version() {
        final StringBuilder versionBuilder = new StringBuilder();
        versionBuilder.append("NanoFramework Version: ");
        versionBuilder.append(ApplicationContext.FRAMEWORK_VERSION);
        versionBuilder.append('\n');
        
        final String appContext = context.getProperty(ApplicationContext.CONTEXT_ROOT, "");
        final String appVersion = context.getProperty(ApplicationContext.VERSION, "0.0.0");
        versionBuilder.append("Application[");
        versionBuilder.append(appContext);
        versionBuilder.append("] Version: ");
        versionBuilder.append(appVersion);
        versionBuilder.append('\n');
        System.out.println(versionBuilder.toString());
    }
    
    protected void usage() {
        final StringBuilder usageBuilder = new StringBuilder();
        usageBuilder.append("Usage: \n\n");
        usageBuilder.append("    ./bootstrap.sh command\n\n");
        usageBuilder.append("The commands are: \n");
        usageBuilder.append("    start        Start Application on Jetty Server\n");
        usageBuilder.append("    stop         Stop Application\n");
        usageBuilder.append("    version      Show the NanoFramwork and Application version\n\n");
        usageBuilder.append("Use \"./bootstrap.sh help\" for more information about a command.\n");
        System.out.println(usageBuilder.toString());
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy