Servers101App.java

/***************************************************************************
   Copyright 2013 Emily Estes

   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 net.metanotion.web.examples;


import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import javax.sql.DataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.metanotion.sql.DbUtil;
import net.metanotion.util.AppUtil;
import net.metanotion.util.IniProperties;
import net.metanotion.util.IniToStruct;
import net.metanotion.util.JDBCConfig;

/** <p>The purpose of this class is to provide a place to document ONE method of running a Java based app server
on recent'ish Linux distros and Windows. App servers are just a program(like this one) that run in the background.
You can start an app server by just running it, which is great for testing(especially if they are producing logging
and debugging output on the console). However, in production, you will want to make sure the service survives system
reboots, etc. While this class is relatively content free, it does document some of the helper classes in the
framework that can be useful to writing a full blown app.</p>

<p>On Windows, the usual method is to install the application as a windows service. The simplest way to do this is with
the Apache Commons Daemon tools, in particular, you need the "prunsrv" and "prunmgr" exe files provided.
( <a href="http://commons.apache.org/proper/commons-daemon/procrun.html">
http://commons.apache.org/proper/commons-daemon/procrun.html</a>). One of the gotcha's is that if you are running a
64 bit JVM, you need the 64 bit versions of those exe's and likewise for the 32 bit JVM. After you do that, you can
write two simple batch files as follows to "install" the service, and "remove" the service. When upgrading your app
server you can go to the Windows Service control panel and stop the service, replace the files, and then start the
service.</p>

<p>Example to install a service:<br />
<code>prunmgr.exe //IS//Servers101App --Install=Servers101App.exe --Description="My App Server." --Jvm=auto
--Classpath=myApp.jar --StartMode=jvm --StartClass=net.metanotion.web.examples.Servers101App
--StartParams=myIni.ini#myLog4j.xml --StopMode=jvm --StopClass=java.lang.System --StopMethod=exit --StopParams=0
--LogPath=logs --StdOutput=auto --StdError=auto<br /></code>
<br />
Example to remove a service:<br />
<code>prunmgr.exe //DS//Servers101App<br /></code></p>

<p>Hopefully that coupled with the Commons Daemon documentation should be enough to get you started.</p>

<p>For those primarily hosting on *nix based systems, things are simpler and more complicated. Basically, you're looking at
process managers and init replacements. The old way to create services(daemons) on *nix systems is with a shell script
for "init"(searching for "SysV init script" should be fruitful). You can still do this on most(all?) *nix systems, however
there are other options that are much simpler and quicker to get started with. Do not take this as a guide on what
you're supposed to do, just an introduction to how to get started and tools to search for.</p>

<p>If you're using Ubuntu, you should probably take a look at Upstart, which is a replacement for init on Ubuntu systems.
For upstart, you should create a file servicename.conf and place it in /etc/init/</p>

As a basic example of Servers101App.conf's would look like:<br />
<code>#begin<br />
description "My app server"<br />
<br />
start on runlevel [2345]<br />
stop on runlevel [!2345]<br />
<br />
respawn<br />
exec /usr/bin/java -Xmx128m -jar /var/hosts/mywebsite/java/myApp.jar /var/hosts/mywebsite/java/myApp.ini
/var/hosts/mywebsite/java/loggingConfig.xml<br />
#end<br /></code>

To start your service you would type the command:<br />
<code>sudo service Servers101App start<br /></code>
To stop your service:<br />
<code>sudo service Servers101App stop<br /></code>

<p>systemd is the default init on many other Linux distros.</p>

<p>In addition to init replacements, there are process managers(that can be used with init replacements), such as
runit, supervisord, god, circus. This is not meant to be a complete intro to operations, and there are many options for
these tools, but this should be a good start to what is possible.</p>

<p>Furthermore, to fully automate your deployment environment, you should consider configuration management tools to
push these changes to servers. Chef, puppet, cfengine, and many others are out there.</p>
*/
public final class Servers101App {
	/** This is the standard way to get a logger for our class from SLF4J. */
	private static final Logger logger = LoggerFactory.getLogger(Servers101App.class);

	/** This class is used to collect all the default constants for your app in one place.
	Since this is used with the {@link net.metanotion.util.IniToStruct} class, the static variables
	are ignored, however, the instance variables correspond to keys in the config file, and the values from
	this class will be used if the config file doesn't define a value for that key. */
	private static final class Defaults {
		/** The default filename to use for the app server config file if one is not specificed on the c
		command line. */
		public static final String SERVER_CONF = "httpd.conf";

		/** The temp file folder to use for file uploads. We're just using the JVM default here. */
		public static final File TEMPDIR = new File(System.getProperty("java.io.tmpdir"));

		// Config file defaults
		/** If the app server config file doesn't specify a value for <code>rootFolder</code> this one will
		be used, and in this case, we're assuming to use the home folder of the user account that the app
		server is running under. */
		public final File rootFolder = new File(System.getProperty("user.dir"));

		/** If the app server config file doesn't specify a value for <code>serverPort</code>, we'll use this one.
		Since this is an HTTP server, we set the default value to port 80, the standard HTTP port. In
		production we would probably be run our app server behind a reverse proxy like nginx, varnish, HA-proxy,
		or Apache, etc. and we would probably want to bind it to a high port like 8080. */
		public final int serverPort = 80;
	}

	/** The instance variables in this class represent values used for the runtime configuration of this server.
	The IniToStruct class will use a configuration INI file, and the instance variables in a defaults
	class to populate an instance of this class, and throwing exceptions on required missing values(see Javadocs
	for net.metanotion.util.INIToStruct ). The purpose of this arrangement is to eliminate ambient authorities
	by eliminating hard coded constants. While obviously, a programmer does not have to be strict about this or
	use this particular combo of classes and library code, my personal feeling is this a) eliminates a lot of error
	prone code while preserving static type checks, and b) promotes good hygiene regarding hard coded constants.
	*/
	private static final class Config {
		/** The port for the HTTP server to listen on. */
		public int serverPort;
		/** The information needed to configure the JDBC database connection pooling. Since this is a container
		object/"struct", it will go in it's own section in the config file, e.g.:<br />
		<code>[jdbc]<br />
		connect = ...<br/>
		username = ...<br />
		password = ...<br /></code> */
		public JDBCConfig jdbc;
		/** The root folder this app server will use for content, file storage, etc. */
		public File rootFolder;
		/** The subfolder we want to use for content (under rootFolder). */
		public String contentFolder;
		/** The subfolder we want to use store user uploaded content (under rootFolder). */
		public String uploadFolder;
	}


	/** The ARG_* variables are just used as constants for encoding the order of command line arguments for the args array in
	main(String[] args). ARG_CONFIG is the position of the .ini filename. */
	private static final int ARG_CONFIG = 0;
	/** The ARG_* variables are just used as constants for encoding the order of command line arguments for the args array in
	main(String[] args). ARG_LOGGING_CONFIG is the position of the .xml file for the logback configuration. */
	private static final int ARG_LOGGING_CONFIG = 1;

	/** This is the entrance point to our app server. When you start it(with a process manager for instance), this
		is where execution begins.
		@param args The command line arguments stored in args are all optional, and passed in via commandline or
			process manager/init configuration.
	*/
	public static void main(String[] args) {
		/** Configure Logging. This assumes we're using logback as our SLF4J implementation AND we don't want to use
		the logback's standard approach of looking for a property or a logback.xml file. */
		try {
			/** This method is designed to look at the args array. If the array.length is less than the 2nd parameter
			it uses Logback's default configuration. Since this reads a file, it may throw an exception. */
			AppUtil.startLogging(args, ARG_LOGGING_CONFIG);
		} catch (Exception e) {
			/** Since we couldn't start our logging system to log critical errors, this is itself a critical error.
			Also, this really shouldn't fail, so, it must be bad, so let's exit with an error condition, and print a
			last ditch error to stderr and do a stack dump. */
			System.err.println("Unable to configure Logging, exiting server.");
			e.printStackTrace();
			System.exit(-1);
		}

		try {
			/* Parse the configuration options. */
			/** We need the name of our configuration file. If this was passed on on the command line then
			args.length will be greater than the ARG_CONFIG position, and we can safely use args[ARG_CONFIG].
			If not, we used a static variable in Defaults called "SERVER_CONF" to put a default file name, so we'll
			use that instead. */
			final String cFile = (args.length > ARG_CONFIG) ? args[ARG_CONFIG] : Defaults.SERVER_CONF;
			/** <p>Create an instance of IniToStruct using our Config class as a template. And since the only reason we made this
			instance was to load our config file, use the filename we got in the last step and an instance of our Defaults class
			to get an actual instance of our configuration.</p>

			<p>Note, instead of using "startLogging" and manually constructing the config from an IniToStruct instance,
			we could have instead called:<br />
			<code>final Config config = util.standardStart(args, Defaults.SERVER_CONF, Config.class,
			new Defaults());<br /></code>
			However, we chose to break it apart into discrete steps here to show how
			{@link net.metanotion.web.servlets.AppUtil}'s convenience method operates.
			*/
			final IniProperties ini = new IniProperties();
			try (final Reader r = new InputStreamReader(new FileInputStream(cFile), StandardCharsets.UTF_8)) {
				ini.load(r);
			}
			final Config config = new IniToStruct<Config>(Config.class).getInstance(new Defaults(), ini);

			/** Start Database Connection Pooling Driver */
			final DataSource dataSource = DbUtil.startDBConnectionPool(config.jdbc);

			/** This is where we would start listening for HTTP connections, but that's what the Hello World example is for. So go
			look at that one. This app server literally does nothing. Aggressively. */
			while(true) { /* Doing nothing aggressively. */ }
		} catch (Exception e) {
			/** Since something happened while starting up our server, the program can't start up. This is a pretty
			bad situation and the init script/monitoring system/etc. will have to attempt to restart the server.
			So we're going to hope it's just a transient error and we can actually be restarted, but either way, we
			should log a "Critical error" with the stack trace/exception that caused it so someone can figure out
			what's going on. */
			logger.error("Critical error", e);
		}
	}
}