001package runwar;
002
003import java.io.File;
004import java.io.FileNotFoundException;
005import java.io.FileOutputStream;
006import java.io.IOException;
007import java.io.PrintStream;
008import java.io.BufferedReader;
009import java.io.InputStreamReader;
010import java.io.PrintWriter;
011import java.lang.management.ManagementFactory;
012import java.lang.reflect.Method;
013import java.net.InetAddress;
014import java.net.ServerSocket;
015import java.net.Socket;
016import java.net.URL;
017import java.net.URLClassLoader;
018import java.net.UnknownHostException;
019import java.nio.file.Files;
020import java.nio.file.Paths;
021import java.util.ArrayList;
022import java.util.Arrays;
023import java.util.List;
024import java.util.Timer;
025import java.util.TimerTask;
026import java.awt.Image;
027
028import javax.net.SocketFactory;
029import javax.servlet.DispatcherType;
030
031import runwar.logging.Logger;
032import runwar.logging.LogSubverter;
033import runwar.mariadb4j.MariaDB4jManager;
034import runwar.options.CommandLineHandler;
035import runwar.options.ServerOptions;
036import runwar.undertow.MappedResourceManager;
037import runwar.undertow.WebXMLParser;
038import runwar.util.TeeOutputStream;
039import io.undertow.Handlers;
040import io.undertow.Undertow;
041import io.undertow.Undertow.Builder;
042import io.undertow.predicate.Predicates;
043import io.undertow.server.HandlerWrapper;
044import io.undertow.server.HttpHandler;
045import io.undertow.server.HttpServerExchange;
046import io.undertow.server.handlers.PathHandler;
047import io.undertow.server.handlers.PredicateHandler;
048import io.undertow.server.handlers.cache.CacheHandler;
049import io.undertow.server.handlers.cache.DirectBufferCache;
050import io.undertow.server.handlers.encoding.ContentEncodingRepository;
051import io.undertow.server.handlers.encoding.EncodingHandler;
052import io.undertow.server.handlers.encoding.GzipEncodingProvider;
053import io.undertow.server.handlers.resource.ResourceHandler;
054import io.undertow.servlet.api.DeploymentInfo;
055import io.undertow.servlet.api.DeploymentManager;
056import io.undertow.servlet.api.FilterInfo;
057import io.undertow.servlet.api.MimeMapping;
058import io.undertow.servlet.api.ServletInfo;
059import io.undertow.servlet.handlers.DefaultServlet;
060import io.undertow.util.Headers;
061import io.undertow.util.MimeMappings;
062import static io.undertow.servlet.Servlets.defaultContainer;
063import static io.undertow.servlet.Servlets.deployment;
064import static io.undertow.servlet.Servlets.servlet;
065import static java.nio.file.StandardCopyOption.*;
066
067public class Server {
068
069    private static Logger log = Logger.getLogger("RunwarLogger");
070    private TeeOutputStream tee;
071    private static ServerOptions serverOptions;
072    private static MariaDB4jManager mariadb4jManager;
073    static volatile boolean listening;
074    int portNumber;
075    int socketNumber;
076    private DeploymentManager manager;
077    private Undertow undertow;
078
079    private String PID;
080    private String serverState = ServerState.STOPPED;
081
082    private static URLClassLoader _classLoader;
083
084    private String serverName = "default";
085    private File statusFile = null;
086    public static final String bar = "******************************************************************************";
087    
088    public Server() {
089    }
090    
091    // for openBrowser 
092    public Server(int seconds) {
093        Timer timer = new Timer();
094        timer.schedule(this.new OpenBrowserTask(), seconds * 1000);
095    }
096    
097    protected void initClassLoader(List<URL> _classpath) {
098        if (_classLoader == null) {
099            log.debug("Loading classes from lib dir");
100            if( _classpath != null && _classpath.size() > 0) {
101            log.debugf("classpath: %s",_classpath);
102    //          _classLoader = new URLClassLoader(_classpath.toArray(new URL[_classpath.size()]),Thread.currentThread().getContextClassLoader());
103    //          _classLoader = new URLClassLoader(_classpath.toArray(new URL[_classpath.size()]),ClassLoader.getSystemClassLoader());
104    //          _classLoader = new URLClassLoader(_classpath.toArray(new URL[_classpath.size()]));
105                _classLoader = new URLClassLoader(_classpath.toArray(new URL[_classpath.size()]));
106    //          _classLoader = new XercesFriendlyURLClassLoader(_classpath.toArray(new URL[_classpath.size()]),ClassLoader.getSystemClassLoader());
107    //          Thread.currentThread().setContextClassLoader(_classLoader);
108            } else {
109                _classLoader = new URLClassLoader(null);
110            }
111        }
112    }
113    
114    protected void setClassLoader(URLClassLoader classLoader){
115        _classLoader = classLoader;
116    }
117    
118    public static URLClassLoader getClassLoader(){
119        return _classLoader;
120    }
121    
122    public void startServer(String[] args, URLClassLoader classLoader) throws Exception {
123        setClassLoader(classLoader);
124        startServer(args);
125    }
126    
127    public void ensureJavaVersion() {
128        Class<?> nio;
129        log.debug("Checking that we're running on > java7");
130        try{
131            nio = Server.class.getClassLoader().loadClass("java.nio.charset.StandardCharsets");
132            nio.getClass().getName();
133        } catch (java.lang.ClassNotFoundException e) {
134            throw new RuntimeException("Could not load NIO!  Are we running on Java 7 or greater?  Sorry, exiting...");
135        }
136    }
137    
138    @SuppressWarnings({ "rawtypes", "unchecked" })
139    public void startServer(final String[] args) throws Exception {
140        ensureJavaVersion();
141        serverState = ServerState.STARTING;
142        serverOptions = CommandLineHandler.parseArguments(args);
143        if(serverOptions.getAction().equals("stop")){
144            Stop.stopServer(args,true);
145        }
146        serverName = serverOptions.getServerName();
147        portNumber = serverOptions.getPortNumber();
148        socketNumber = serverOptions.getSocketNumber();
149        String cfengine = serverOptions.getCFEngineName();
150        String processName = serverOptions.getProcessName();
151        String contextPath = serverOptions.getContextPath();
152        String host = serverOptions.getHost();
153        File warFile = serverOptions.getWarFile();
154        if (serverOptions.getStatusFile() != null) {
155            statusFile = serverOptions.getStatusFile();
156        }
157        String warPath = serverOptions.getWarPath();
158        String loglevel = serverOptions.getLoglevel();
159        char[] stoppassword = serverOptions.getStopPassword();
160        Long transferMinSize= serverOptions.getTransferMinSize();
161
162        if (serverOptions.isBackground()) {
163            setServerState(ServerState.STARTING_BACKGROUND);
164            // this will eventually system.exit();
165            List<String> argarray = new ArrayList<String>();
166            for (String arg : args) {
167                if (arg.contains("background") || arg.startsWith("-b")) {
168                    continue;
169                } else {
170                    argarray.add(arg);
171                }
172            }
173            argarray.add("--background");
174            argarray.add("false");
175            int launchTimeout = serverOptions.getLaunchTimeout();
176            LaunchUtil.relaunchAsBackgroundProcess(launchTimeout, argarray.toArray(new String[argarray.size()]),
177                    processName);
178            setServerState(ServerState.STARTED_BACKGROUND);
179            // just in case
180            Thread.sleep(200);
181            System.exit(0);
182        }
183        
184        // if the war is archived, unpack it to system temp
185        if(warFile.exists() && !warFile.isDirectory()) {
186            URL zipResource = warFile.toURI().toURL();
187            String warDir = warFile.getName().toLowerCase().replace(".war", "");
188            warFile = new File(warFile.getParentFile(), warDir);
189            if(!warFile.exists()) {
190                warFile.mkdir();
191                log.debug("Exploding compressed WAR to " + warFile.getAbsolutePath());
192                LaunchUtil.unzipResource(zipResource, warFile, false);
193            } else {
194                log.debug("Using already exploded WAR in " + warFile.getAbsolutePath());
195            }
196            warPath = warFile.getAbsolutePath();
197            if(serverOptions.getWarFile().getAbsolutePath().equals(serverOptions.getCfmlDirs())) {
198                serverOptions.setCfmlDirs(warFile.getAbsolutePath());
199            }
200        }
201
202        tee = null;
203        if (serverOptions.getLogDir() != null) {
204            File logDirectory = serverOptions.getLogDir();
205            logDirectory.mkdir();
206            File outLog = new File(logDirectory,"server.out.txt");
207            if (logDirectory.exists()) {
208                if(outLog.exists()) {
209                    if(Files.size(Paths.get(outLog.getPath())) > 10 * 1024 * 1024) {
210                        log.info("Log is over 10MB, moving " + outLog.getPath() + " to " + outLog.getPath() + ".bak");
211                        Files.move(Paths.get(outLog.getPath()), Paths.get(outLog.getPath()+".bak"), REPLACE_EXISTING);
212                    }
213                }
214                log.info("Logging to " + outLog.getPath());
215                tee = new TeeOutputStream(System.out, new FileOutputStream(outLog.getPath(), outLog.exists()));
216                PrintStream newOut = new PrintStream(tee, true);
217                System.setOut(newOut);
218                System.setErr(newOut);
219            } else {
220                log.error("Could not create log: " + outLog.getPath());
221            }
222        }
223        
224        new AgentInitialization().loadAgentFromLocalJarFile(new File(warFile, "/WEB-INF/lib/"));
225
226        String osName = System.getProperties().getProperty("os.name");
227        String iconPNG = System.getProperty("cfml.server.trayicon");
228        if( iconPNG != null && iconPNG.length() > 0) {
229            serverOptions.setIconImage(iconPNG);
230        }
231        String dockIconPath = System.getProperty("cfml.server.dockicon");
232        if( dockIconPath == null || dockIconPath.length() == 0) {
233            dockIconPath = serverOptions.getIconImage();
234        }
235
236        if (osName != null && osName.startsWith("Mac OS X")) {
237            Image dockIcon = LaunchUtil.getIconImage(dockIconPath);
238            System.setProperty("com.apple.mrj.application.apple.menu.about.name", processName);
239            System.setProperty("com.apple.mrj.application.growbox.intrudes", "false");
240            System.setProperty("apple.laf.useScreenMenuBar", "true");
241            System.setProperty("-Xdock:name", processName);
242            try {
243                Class<?> appClass = Class.forName("com.apple.eawt.Application");
244                Method getAppMethod = appClass.getMethod("getApplication");
245                Object appInstance = getAppMethod.invoke(null);
246                Method dockMethod = appInstance.getClass().getMethod("setDockIconImage", java.awt.Image.class);
247                dockMethod.invoke(appInstance, dockIcon);
248            } catch (Exception e) {
249                log.warn(e);
250            }
251        }
252        String startingtext = "Starting - port:" + portNumber + " stop-port:" + socketNumber + " warpath:" + warPath;
253        startingtext += "\ncontext: " + contextPath + "  -  version: " + getVersion();
254        String cfmlDirs = serverOptions.getCfmlDirs();
255        if (cfmlDirs.length() > 0) {
256            startingtext += "\nweb-dirs: " + cfmlDirs;
257        }
258        startingtext += "\nLog Directory: " + serverOptions.getLogDir().getAbsolutePath();
259        System.out.println(bar);
260        System.out.println(startingtext);
261        //System.out.println("background: " + background);
262        System.out.println(bar);
263        addShutDownHook();
264        portNumber = getPortOrErrorOut(portNumber, host);
265        socketNumber = getPortOrErrorOut(socketNumber, host);
266        String cfmlServletConfigWebDir = serverOptions.getCFMLServletConfigWebDir();
267        String cfmlServletConfigServerDir = serverOptions.getCFMLServletConfigServerDir();
268        File webXmlFile = serverOptions.getWebXmlFile();
269        File webinf = new File(warFile, "WEB-INF");
270        if (webXmlFile != null && new File(webXmlFile.getParentFile(), "lib").exists()) {
271            webinf = webXmlFile.getParentFile();
272        }
273        String libDirs = serverOptions.getLibDirs();
274        URL jarURL = serverOptions.getJarURL();
275        if (warFile.isDirectory() && webinf.exists()) {
276            libDirs = webinf.getAbsolutePath() + "/lib";
277            log.info("Using existing WEB-INF/lib of: " + libDirs);
278        }
279
280        List<URL> cp = new ArrayList<URL>();
281        if (libDirs != null || jarURL != null) {
282            if (libDirs != null)
283                cp.addAll(getJarList(libDirs));
284            if (jarURL != null)
285                cp.add(jarURL);
286        }
287        if(serverOptions.getMariaDB4jImportSQLFile() != null){
288            System.out.println("ADDN"+serverOptions.getMariaDB4jImportSQLFile().toURI().toURL());
289            cp.add(serverOptions.getMariaDB4jImportSQLFile().toURI().toURL());
290        }
291        cp.addAll(getClassesList(new File(webinf, "/classes")));
292        initClassLoader(cp);
293        
294        mariadb4jManager = new MariaDB4jManager(_classLoader);
295
296        log.debug("Transfer Min Size: " + serverOptions.getTransferMinSize());
297
298        final DeploymentInfo servletBuilder = deployment()
299                .setContextPath(contextPath.equals("/") ? "" : contextPath)
300                .setTempDir(new File(System.getProperty("java.io.tmpdir")))
301                .setDeploymentName(warPath);
302
303        if (!warFile.exists()) {
304            throw new RuntimeException("war does not exist: " + warFile.getAbsolutePath());
305        }
306
307        // hack to prevent . being picked up as the system path (jacob.x.dll)
308        if (System.getProperty("java.library.path") == null) {
309            if (webXmlFile != null) {
310                System.setProperty("java.library.path", getThisJarLocation().getPath() 
311                        + ':' + new File(webXmlFile.getParentFile(), "lib").getPath());
312            } else {
313                System.setProperty("java.library.path", getThisJarLocation().getPath() 
314                        + ':' + new File(warFile, "/WEB-INF/lib/").getPath());
315            }
316        } else {
317            System.setProperty("java.library.path",
318                    getThisJarLocation().getPath() + ":" + System.getProperty("java.library.path"));
319        }
320        log.debug("java.library.path:" + System.getProperty("java.library.path"));
321
322        if (System.getProperty("coldfusion.home") == null) {
323            String cfusionDir = new File(webinf,"cfusion").getAbsolutePath();
324            if (webXmlFile != null) {
325                cfusionDir = new File(webXmlFile.getParentFile(),"cfusion").getAbsolutePath();
326            }
327            log.debug("Setting coldfusion home:" + cfusionDir);
328            System.setProperty("coldfusion.home", cfusionDir);
329            System.setProperty("coldfusion.rootDir", cfusionDir);
330//            System.setProperty("javax.servlet.context.tempdir", cfusionDir + "/../cfclasses");
331            System.setProperty("coldfusion.libPath", cfusionDir + "/lib");
332            System.setProperty("flex.dir", new File(webinf,"cfform").getAbsolutePath());
333            System.setProperty("coldfusion.jsafe.defaultalgo", "FIPS186Random");
334            System.setProperty("coldfusion.classPath", cfusionDir + "/lib/updates/," + cfusionDir + "/lib/,"
335                    + cfusionDir + "/lib/axis2,"+ cfusionDir + "/gateway/lib/,"+ cfusionDir + "/../cfform/jars,"
336                    + cfusionDir + "/../flex/jars,"+ cfusionDir + "/lib/oosdk/lib,"+ cfusionDir + "/lib/oosdk/classes");
337            System.setProperty("java.security.policy", cfusionDir + "/lib/coldfusion.policy");
338            System.setProperty("java.security.auth.policy", cfusionDir + "/lib/neo_jaas.policy");
339            System.setProperty("java.nixlibrary.path", cfusionDir + "/lib");
340            System.setProperty("java.library.path", cfusionDir + "/lib");
341        }
342
343        if(warFile.isDirectory() && !webinf.exists()) {
344            if (cfmlServletConfigWebDir == null) {
345                File webConfigDirFile = new File(getThisJarLocation().getParentFile(), "engine/cfml/server/cfml-web/");
346                cfmlServletConfigWebDir = webConfigDirFile.getPath() + "/" + serverName;
347            }
348            log.debug("cfml.web.config.dir: " + cfmlServletConfigWebDir);
349            if (cfmlServletConfigServerDir == null || cfmlServletConfigServerDir.length() == 0) {
350                File serverConfigDirFile = new File(getThisJarLocation().getParentFile(), "engine/cfml/server/");
351                cfmlServletConfigServerDir = serverConfigDirFile.getAbsolutePath();
352            }
353            log.debug("cfml.server.config.dir: " + cfmlServletConfigServerDir);
354            String webinfDir = System.getProperty("cfml.webinf");
355            if (webinfDir == null) {
356                webinf = new File(cfmlServletConfigWebDir, "WEB-INF/");
357            } else {
358                webinf = new File(webinfDir);
359            }
360            log.debug("cfml.webinf: " + webinf.getPath());
361
362            // servletBuilder.setResourceManager(new CFMLResourceManager(new
363            // File(homeDir,"server/"), transferMinSize, cfmlDirs));
364            File internalCFMLServerRoot = webinf;
365            internalCFMLServerRoot.mkdirs();
366            servletBuilder.setResourceManager(new MappedResourceManager(warFile, transferMinSize, cfmlDirs, internalCFMLServerRoot));
367
368            if (webXmlFile != null) {
369                log.debug("using specified web.xml : " + webXmlFile.getAbsolutePath());
370                servletBuilder.setClassLoader(_classLoader);
371                WebXMLParser.parseWebXml(webXmlFile, servletBuilder);
372            } else {
373                if (_classLoader == null) {
374                    throw new RuntimeException("FATAL: Could not load any libs for war: " + warFile.getAbsolutePath());
375                }
376                servletBuilder.setClassLoader(_classLoader);
377                Class cfmlServlet;
378                Class restServlet;
379                try {
380                    cfmlServlet = _classLoader.loadClass(cfengine + ".loader.servlet.CFMLServlet");
381                    log.debug("dynamically loaded CFML servlet from runwar child classloader");
382                } catch (java.lang.ClassNotFoundException e) {
383                    cfmlServlet = Server.class.getClassLoader().loadClass(cfengine + ".loader.servlet.CFMLServlet");
384                    log.debug("dynamically loaded CFML servlet from runwar classloader");
385                }
386                try {
387                    restServlet = _classLoader.loadClass(cfengine + ".loader.servlet.RestServlet");
388                } catch (java.lang.ClassNotFoundException e) {
389                    restServlet = Server.class.getClassLoader().loadClass(cfengine + ".loader.servlet.RestServlet");
390                }
391                log.debug("loaded servlet classes");
392                servletBuilder
393                    .addWelcomePages(serverOptions.getWelcomeFiles())
394                    .addServlets(
395                        servlet("CFMLServlet", cfmlServlet)
396                                .setRequireWelcomeFileMapping(true)
397                                .addInitParam("configuration",cfmlServletConfigWebDir)
398                                .addInitParam(cfengine+"-server-root",cfmlServletConfigServerDir)
399                                .addMapping("*.cfm")
400                                .addMapping("*.cfc")
401                                .addMapping("/index.cfc/*")
402                                .addMapping("/index.cfm/*")
403                                .addMapping("/index.cfml/*")
404                                .setLoadOnStartup(1)
405                                ,
406                        servlet("RESTServlet", restServlet)
407                                .setRequireWelcomeFileMapping(true)
408                                .addInitParam(cfengine+"-web-directory",cfmlServletConfigWebDir)
409                                .addMapping("/rest/*")
410                                .setLoadOnStartup(2));
411            }
412        } else if(webinf.exists()) {
413            log.debug("found WEB-INF: " + webinf.getAbsolutePath());
414            if (_classLoader == null) {
415                throw new RuntimeException("FATAL: Could not load any libs for war: " + warFile.getAbsolutePath());
416            }
417            servletBuilder.setClassLoader(_classLoader);
418            servletBuilder.setResourceManager(new MappedResourceManager(warFile, transferMinSize, cfmlDirs, webinf));
419            LogSubverter.subvertJDKLoggers(loglevel);
420            WebXMLParser.parseWebXml(new File(webinf, "/web.xml"), servletBuilder);
421        } else {
422            throw new RuntimeException("Didn't know how to handle war:"+warFile.getAbsolutePath());
423        }
424        /*      
425        servletBuilder.addInitialHandlerChainWrapper(new HandlerWrapper() {
426            @Override
427            public HttpHandler wrap(final HttpHandler handler) {
428                return resource(new FileResourceManager(new File(libDir,"server/WEB-INF"), transferMinSize))
429                        .setDirectoryListingEnabled(true);
430            }
431        });
432        */
433
434        configureURLRewrite(servletBuilder, webinf);
435
436        if (serverOptions.isCacheEnabled()) {
437            addCacheHandler(servletBuilder);
438        }
439
440        if (serverOptions.isCustomHTTPStatusEnabled()) {
441            servletBuilder.setSendCustomReasonPhraseOnError(true);
442        }
443
444        // this prevents us from having to use our own ResourceHandler (directory listing, welcome files, see below) and error handler for now
445        servletBuilder.addServlet(new ServletInfo(io.undertow.servlet.handlers.ServletPathMatches.DEFAULT_SERVLET_NAME, DefaultServlet.class)
446            .addInitParam("directory-listing", Boolean.toString(serverOptions.isDirectoryListingEnabled())));
447
448        manager = defaultContainer().addDeployment(servletBuilder);
449
450        manager.deploy();
451        HttpHandler servletHandler = manager.start();
452        log.debug("started servlet deployment manager");
453/*
454        List welcomePages =  manager.getDeployment().getDeploymentInfo().getWelcomePages();
455        CFMLResourceHandler resourceHandler = new CFMLResourceHandler(servletBuilder.getResourceManager(), servletHandler, welcomePages);
456        resourceHandler.setDirectoryListingEnabled(directoryListingEnabled);
457        PathHandler pathHandler = Handlers.path(Handlers.redirect(contextPath))
458                .addPrefixPath(contextPath, resourceHandler);
459        HttpHandler errPageHandler = new SimpleErrorPageHandler(pathHandler);
460        Builder serverBuilder = Undertow.builder().addHttpListener(portNumber, host).setHandler(errPageHandler);
461*/
462        Builder serverBuilder = Undertow.builder();
463
464        if(serverOptions.isEnableHTTP()) {
465            serverBuilder.addHttpListener(portNumber, host);
466        }
467
468        if (serverOptions.isEnableSSL()) {
469            int sslPort = serverOptions.getSSLPort();
470            serverBuilder.setDirectBuffers(true);
471            log.info("Enabling SSL protocol on port " + sslPort);
472            if (serverOptions.getSSLCertificate() != null) {
473                File certfile = serverOptions.getSSLCertificate();
474                File keyfile = serverOptions.getSSLKey();
475                char[] keypass = serverOptions.getSSLKeyPass();
476                serverBuilder.addHttpsListener(sslPort, host, SSLUtil.createSSLContext(certfile, keyfile, keypass));
477                Arrays.fill(keypass, '*');
478            } else {
479                serverBuilder.addHttpsListener(sslPort, host, SSLUtil.createSSLContext());
480            }
481        }
482        
483        if (serverOptions.isEnableAJP()) {
484            log.info("Enabling AJP protocol on port " + serverOptions.getAJPPort());
485            serverBuilder.addAjpListener(serverOptions.getAJPPort(), host);
486        }
487
488//        final PathHandler pathHandler = Handlers.path(Handlers.redirect(contextPath))
489//                .addPrefixPath(contextPath, servletHandler);
490
491        final PathHandler pathHandler = new PathHandler(Handlers.redirect(contextPath)) {
492            @Override
493            public void handleRequest(final HttpServerExchange exchange) throws Exception {
494                if (exchange.getRequestPath().endsWith(".svgz")) {
495                    exchange.getResponseHeaders().put(Headers.CONTENT_ENCODING, "gzip");
496                }
497                super.handleRequest(exchange);
498            }
499        };
500        pathHandler.addPrefixPath(contextPath, servletHandler);
501
502//        SessionManager sessionManager = new InMemorySessionManager("SESSION_MANAGER");
503//        SessionCookieConfig sessionConfig = new SessionCookieConfig();
504//        SessionAttachmentHandler sessionAttachmentHandler = new SessionAttachmentHandler(sessionManager, sessionConfig);
505//        // set as next handler your root handler
506//        sessionAttachmentHandler.setNext(pathHandler);
507
508        
509        if (serverOptions.isGzipEnabled()) {
510            final EncodingHandler handler = new EncodingHandler(new ContentEncodingRepository().addEncodingHandler(
511                    "gzip", new GzipEncodingProvider(), 50, Predicates.parse("max-content-size[5]")))
512                    .setNext(pathHandler);
513            serverBuilder.setHandler(handler);
514        } else {
515            serverBuilder.setHandler(pathHandler);
516        }
517
518        try {
519            PID = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
520            String pidFile = serverOptions.getPidFile();
521            if (pidFile != null && pidFile.length() > 0) {
522                File file = new File(pidFile);
523                file.deleteOnExit();
524                PrintWriter writer = new PrintWriter(file);
525                writer.print(PID);
526                writer.close();
527            }
528        } catch (Exception e) {
529            log.error("Unable to get PID:" + e.getMessage());
530        }
531        if (serverOptions.isKeepRequestLog()) {
532            log.error("request log currently unsupported");
533        }
534        
535        // start the stop monitor thread
536        undertow = serverBuilder.build();
537        Thread monitor = new MonitorThread(stoppassword);
538        monitor.start();
539        log.debug("started stop monitor");
540        LaunchUtil.hookTray(this);
541        log.debug("hooked system tray");
542
543        if (serverOptions.isOpenbrowser()) {
544            new Server(3);
545        }
546        
547        // if this println changes be sure to update the LaunchUtil so it will know success
548        String msg = "Server is up - http-port:" + portNumber + " stop-port:" + socketNumber +" PID:" + PID + " version " + getVersion();
549        log.debug(msg);
550        System.out.println(msg);
551        setServerState(ServerState.STARTED);
552
553        if (serverOptions.isMariaDB4jEnabled()) {
554            try {
555                mariadb4jManager.start(serverOptions.getMariaDB4jPort(), serverOptions.getMariaDB4jBaseDir(),
556                        serverOptions.getMariaDB4jDataDir(), serverOptions.getMariaDB4jImportSQLFile());
557            } catch (Exception dbStartException) {
558                log.error("Could not start MariaDB4j");
559                log.error(dbStartException);
560                System.out.println("Error starting MariaDB4j: " + dbStartException.getMessage());
561            }
562        }
563
564        undertow.start();
565    }
566
567    private void addShutDownHook() {
568        final Thread mainThread = Thread.currentThread();
569        Runtime.getRuntime().addShutdownHook(new Thread() {
570            public void run() {
571                try {
572                    stopServer();
573//                    if(tempWarDir != null) {
574//                        LaunchUtil.deleteRecursive(tempWarDir);
575//                    }
576                    mainThread.join();
577                } catch ( Exception e) {
578                    // TODO Auto-generated catch block
579                    e.printStackTrace();
580                }
581            }
582        });
583        log.debug("Added shutdown hook");
584    }
585
586    public void stopServer() {
587        int exitCode = 0;
588        try{
589            System.out.println();
590            System.out.println(bar);
591            System.out.println("*** stopping server");
592            if (serverOptions.isMariaDB4jEnabled()) {
593                mariadb4jManager.stop();
594            }
595            try {
596                manager.undeploy();
597                undertow.stop();
598                Thread.sleep(1000);
599            } catch (Exception notRunning) {
600                System.out.println("*** server did not appear to be running");
601            }
602            System.out.println(bar);
603            setServerState(ServerState.STOPPED);
604        } catch (Exception e) {
605            e.printStackTrace();
606            setServerState(ServerState.UNKNOWN);
607            log.error(e);
608            exitCode = 1;
609        }
610        try {
611            if (tee != null)
612                tee.close();
613        } catch (Exception e) {
614            System.out.println("Redirect:  Unable to close this log file!");
615        }
616        if(exitCode != 0) {
617            System.exit(exitCode);
618        }
619    }
620
621    
622    @SuppressWarnings({ "rawtypes", "unchecked" })
623    private void configureURLRewrite(DeploymentInfo servletBuilder, File webInfDir) throws ClassNotFoundException, IOException {
624        if(serverOptions.isEnableURLRewrite()) {
625            log.debug("enabling URL rewriting");
626            Class rewriteFilter;
627            String urlRewriteFile = "runwar/urlrewrite.xml";
628            try{
629                rewriteFilter = _classLoader.loadClass("org.tuckey.web.filters.urlrewrite.UrlRewriteFilter");
630            } catch (java.lang.ClassNotFoundException e) {
631                rewriteFilter = Server.class.getClassLoader().loadClass("org.tuckey.web.filters.urlrewrite.UrlRewriteFilter");
632            }
633            if(serverOptions.getURLRewriteFile() != null) {
634                if(!serverOptions.getURLRewriteFile().isFile()) {
635                    log.error("The URL rewrite file " + urlRewriteFile + " does not exist!");
636                } else {
637                    String rewriteFileName = "urlrewrite.xml";
638                    LaunchUtil.copyFile(serverOptions.getURLRewriteFile(), new File(webInfDir, rewriteFileName));
639                    log.debug("Copying URL rewrite file " + serverOptions.getURLRewriteFile().getPath() + " to WEB-INF: " + webInfDir.getPath() + "/"+rewriteFileName);
640                    urlRewriteFile = "/WEB-INF/"+rewriteFileName;
641                }
642            }
643            log.debug("URL rewriting config file: " + urlRewriteFile);
644            servletBuilder.addFilter(new FilterInfo("UrlRewriteFilter", rewriteFilter)
645                .addInitParam("confPath", urlRewriteFile)
646                .addInitParam("statusEnabled", Boolean.toString(serverOptions.isDebug()))
647                .addInitParam("modRewriteConf", "false"));
648            servletBuilder.addFilterUrlMapping("UrlRewriteFilter", "/*", DispatcherType.REQUEST);
649        } else {
650            log.debug("URL rewriting is disabled");            
651        }
652    }
653
654    private void addCacheHandler(final DeploymentInfo servletBuilder) {
655        // this handles mime types and adds a simple cache for static files
656        servletBuilder.addInitialHandlerChainWrapper(new HandlerWrapper() {
657            @Override
658            public HttpHandler wrap(final HttpHandler handler) {
659              final ResourceHandler resourceHandler = new ResourceHandler(servletBuilder.getResourceManager());
660                io.undertow.util.MimeMappings.Builder mimes = MimeMappings.builder();
661                List<String> suffixList = new ArrayList<String>();
662                // add font mime types not included by default
663                mimes.addMapping("eot", "application/vnd.ms-fontobject");
664                mimes.addMapping("otf", "font/opentype");
665                mimes.addMapping("ttf", "application/x-font-ttf");
666                mimes.addMapping("woff", "application/x-font-woff");
667                suffixList.addAll(Arrays.asList(".eot",".otf",".ttf",".woff"));
668                // add the default types and any added in web.xml files
669                for(MimeMapping mime : servletBuilder.getMimeMappings()) {
670                    log.debug("Adding mime-type: " + mime.getExtension() + " - " + mime.getMimeType());
671                    mimes.addMapping(mime.getExtension(), mime.getMimeType());
672                    suffixList.add("."+mime.getExtension());
673                }
674                resourceHandler.setMimeMappings(mimes.build());
675                String[] suffixes = new String[suffixList.size()];
676                suffixes = suffixList.toArray(suffixes);
677                // simple cacheHandler, someday maybe make this configurable
678                final CacheHandler cacheHandler = new CacheHandler(new DirectBufferCache(1024, 10, 10480), resourceHandler);
679                final PredicateHandler predicateHandler = new PredicateHandler(Predicates.suffixes(suffixes), cacheHandler, handler);
680                return predicateHandler;
681            }
682        });
683    }
684    
685    public static File getThisJarLocation() {
686        return LaunchUtil.getJarDir(Server.class);
687    }
688
689    public String getPID() {
690        return PID;
691    }
692
693    private int getPortOrErrorOut(int portNumber, String host) {
694        try {
695            ServerSocket nextAvail = new ServerSocket(portNumber, 1, InetAddress.getByName(host));
696            portNumber = nextAvail.getLocalPort();
697            nextAvail.close();
698            return portNumber;
699        } catch (java.net.BindException e) {
700            throw new RuntimeException("Error getting port " + portNumber + "!  Cannot start.  " + e.getMessage());
701        } catch (UnknownHostException e) {
702            throw new RuntimeException("Unknown host (" + host + ")");
703        } catch (IOException e) {
704            throw new RuntimeException(e);
705        }
706    }
707
708    private List<URL> getJarList(String libDirs) throws IOException {
709        List<URL> classpath = new ArrayList<URL>();
710        String[] list = libDirs.split(",");
711        if (list == null)
712            return classpath;
713
714        for (String path : list) {
715            if (".".equals(path) || "..".equals(path))
716                continue;
717
718            File file = new File(path);
719            for (File item : file.listFiles()) {
720                String fileName = item.getAbsolutePath();
721                if (!item.isDirectory()) {
722                    if (fileName.toLowerCase().endsWith(".jar") || fileName.toLowerCase().endsWith(".zip")) {
723                        URL url = item.toURI().toURL();
724                        classpath.add(url);
725                        log.debug("lib: added to classpath: " + fileName);
726                    }
727                }
728            }
729        }
730        return classpath;
731    }
732
733    private List<URL> getClassesList(File classesDir) throws IOException {
734        List<URL> classpath = new ArrayList<URL>();
735        if (classesDir == null)
736            return classpath;
737        if (classesDir.exists() && classesDir.isDirectory()) {
738            URL url = classesDir.toURI().toURL();
739            classpath.add(url);
740            for (File item : classesDir.listFiles()) {
741                if (item.isDirectory()) {
742                    classpath.addAll(getClassesList(item));
743                }
744            }
745        } else {
746            log.debug("WEB-INF classes directory (" + classesDir.getAbsolutePath() + ") does not exist");
747        }
748        return classpath;
749    }
750
751    public static void printVersion() {
752        System.out.println(LaunchUtil.getResourceAsString("runwar/version.properties"));
753        System.out.println(LaunchUtil.getResourceAsString("io/undertow/version.properties"));
754    }
755
756    private static String getVersion() {
757        String[] version = LaunchUtil.getResourceAsString("runwar/version.properties").split("=");
758        return version[version.length - 1].trim();
759    }
760
761    private class MonitorThread extends Thread {
762
763        private char[] stoppassword;
764
765        public MonitorThread(char[] stoppassword) {
766            this.stoppassword = stoppassword;
767            setDaemon(true);
768            setName("StopMonitor");
769        }
770
771        @Override
772        public void run() {
773            // Executor exe = Executors.newCachedThreadPool();
774            ServerSocket serverSocket = null;
775            int exitCode = 0;
776            listening = true;
777            try {
778                serverSocket = new ServerSocket(socketNumber, 1, InetAddress.getByName(serverOptions.getHost()));
779                System.out.println(bar);
780                System.out.println("*** starting 'stop' listener thread - Host: " + serverOptions.getHost()
781                        + " - Socket: " + socketNumber);
782                System.out.println(bar);
783                while (listening) {
784                    final Socket clientSocket = serverSocket.accept();
785                    int r, i = 0;
786                    BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
787                    try {
788                        while ((r = reader.read()) != -1) {
789                            char ch = (char) r;
790                            if (stoppassword.length > i && ch == stoppassword[i]) {
791                                i++;
792                            } else {
793                                i = 0; // prevent prefix only matches
794                            }
795                        }
796                        if (i == stoppassword.length) {
797                            listening = false;
798                        } else {
799                            log.warn("Incorrect password used when trying to stop server.");
800                        }
801                    } catch (java.net.SocketException e) {
802                        // reset
803                    }
804                    try {
805                        clientSocket.close();
806                    } catch (IOException e) {
807                        e.printStackTrace();
808                    }
809                }
810                serverSocket.close();
811            } catch (Exception e) {
812                exitCode = 1;
813                e.printStackTrace();
814            }
815//            stopServer();
816            System.exit(exitCode);
817//            Thread.currentThread().interrupt();
818            return;
819        }
820    }
821
822    public static boolean serverWentDown(int timeout, long sleepTime, InetAddress server, int port) {
823        long start = System.currentTimeMillis();
824        while ((System.currentTimeMillis() - start) < timeout) {
825            if (checkServerIsUp(server, port)) {
826                try {
827                    Thread.sleep(sleepTime);
828                } catch (InterruptedException e) {
829                    return false;
830                }
831            } else {
832                return true;
833            }
834        }
835        return false;
836    }
837
838    public static boolean serverCameUp(int timeout, long sleepTime, InetAddress server, int port) {
839        long start = System.currentTimeMillis();
840        while ((System.currentTimeMillis() - start) < timeout) {
841            if (!checkServerIsUp(server, port)) {
842                try {
843                    Thread.sleep(sleepTime);
844                } catch (InterruptedException e) {
845                    return false;
846                }
847            } else {
848                return true;
849            }
850        }
851        return false;
852    }
853
854    public static boolean checkServerIsUp(InetAddress server, int port) {
855        Socket sock = null;
856        try {
857            sock = SocketFactory.getDefault().createSocket(server, port);
858            sock.setSoLinger(true, 0);
859            return true;
860        } catch (IOException e) {
861            return false;
862        } finally {
863            if (sock != null) {
864                try {
865                    sock.close();
866                } catch (IOException e) {
867                    // don't care
868                }
869            }
870        }
871    }
872
873    class OpenBrowserTask extends TimerTask {
874        public void run() {
875            int portNumber = serverOptions.getPortNumber();
876            String protocol = "http";
877            String host = serverOptions.getHost();
878            String openbrowserURL = serverOptions.getOpenbrowserURL();
879            int timeout = serverOptions.getLaunchTimeout();
880            if (openbrowserURL == null || openbrowserURL.length() == 0) {
881                openbrowserURL = "http://" + host + ":" + portNumber;
882            }
883            if(serverOptions.isEnableSSL()) {
884                portNumber = serverOptions.getSSLPort();
885                protocol = "https";
886                openbrowserURL = openbrowserURL.replace("http:", "https:");
887            }
888            if (!openbrowserURL.startsWith("http")) {
889                openbrowserURL = (!openbrowserURL.startsWith("/")) ? "/" + openbrowserURL : openbrowserURL;
890                openbrowserURL = protocol + "://" + host + ":" + portNumber + openbrowserURL;
891            }
892            System.out.println("Waiting up to " + (timeout/1000) + " seconds for " + host + ":" + portNumber + "...");
893            try {
894                if (serverCameUp(timeout, 3000, InetAddress.getByName(host), portNumber)) {
895                    System.out.println("Opening browser to..." + openbrowserURL);
896                    BrowserOpener.openURL(openbrowserURL.trim());
897                } else {
898                    System.out.println("could not open browser to..." + openbrowserURL + "... timeout...");
899                }
900            } catch (Exception e) {
901                log.error(e.getMessage());
902            }
903            return;
904        }
905    }
906
907    public static ServerOptions getServerOptions() {
908        return serverOptions;
909    }
910
911    private void setServerState(String state) {
912        serverState = state;
913        if (statusFile != null) {
914            try {
915                PrintWriter writer;
916                writer = new PrintWriter(statusFile);
917                writer.print(state);
918                writer.close();
919            } catch (FileNotFoundException e) {
920                log.error(e.getMessage());
921            }
922        }
923    }
924
925    public String getServerState() {
926        return serverState;
927    }
928    
929    public static class ServerState {
930        public static final String STARTING = "STARTING";
931        public static final String STARTED = "STARTED";
932        public static final String STARTING_BACKGROUND = "STARTING_BACKGROUND";
933        public static final String STARTED_BACKGROUND = "STARTED_BACKGROUND";
934        public static final String STOPPING = "STOPPING";
935        public static final String STOPPED = "STOPPED";
936        public static final String UNKNOWN = "UNKNOWN";
937    }
938
939}