From e9be0c97b234f07d926344955b3a19640c83ef69 Mon Sep 17 00:00:00 2001
From: Tobias Pietzsch <tobias.pietzsch@gmail.com>
Date: Wed, 11 Feb 2015 17:11:34 +0100
Subject: [PATCH] clean up command-line argument processing:  * use Parameters
 object instead of static variables.  * try to get a reasonable default
 hostname using java.net.InetAddress before falling back to "localhost".  *
 print usage information if there is any problem evaluating the arguments.

---
 src/main/java/bdv/server/BigDataServer.java | 257 ++++++++++++++------
 1 file changed, 177 insertions(+), 80 deletions(-)

diff --git a/src/main/java/bdv/server/BigDataServer.java b/src/main/java/bdv/server/BigDataServer.java
index 67d05bd..a906b79 100644
--- a/src/main/java/bdv/server/BigDataServer.java
+++ b/src/main/java/bdv/server/BigDataServer.java
@@ -1,6 +1,7 @@
 package bdv.server;
 
 import mpicbg.spim.data.SpimDataException;
+
 import org.apache.commons.cli.*;
 import org.apache.commons.lang.StringUtils;
 import org.eclipse.jetty.server.Connector;
@@ -15,37 +16,69 @@ import org.eclipse.jetty.util.log.Log;
 import org.eclipse.jetty.util.thread.QueuedThreadPool;
 
 import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.HashMap;
 import java.util.List;
-
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Serve XML/HDF5 datasets over HTTP.
+ *
+ * <pre>
+ * usage: BigDataServer [OPTIONS] [NAME XML]...
+ * Serves one or more XML/HDF5 datasets for remote access over HTTP.
+ * Provide (NAME XML) pairs on the command line or in a dataset file, where
+ * NAME is the name under which the dataset should be made accessible and XML
+ * is the path to the XML file of the dataset.
+ *  -d &lt;FILE&gt;       Dataset file: A plain text file specifying one dataset
+ *                  per line. Each line is formatted as "NAME &lt;TAB&gt; XML".
+ *  -p &lt;PORT&gt;       Listening port. (default: 8080)
+ *  -s &lt;HOSTNAME&gt;   Hostname of the server.
+ * </pre>
+ *
+ * @author Tobias Pietzsch <tobias.pietzsch@gmail.com>
+ * @author HongKee Moon <moon@mpi-cbg.de>
+ */
 public class BigDataServer
 {
-	static HashMap< String, String > dataSet = new HashMap<>();
-
 	private static final org.eclipse.jetty.util.log.Logger LOG = Log.getLogger( BigDataServer.class );
 
-	static int port;
-
-	static String serverName;
+	static Parameters getDefaultParameters()
+	{
+		final int port = 8080;
+		String hostname;
+		try
+		{
+			hostname = InetAddress.getLocalHost().getHostName();
+		}
+		catch ( final UnknownHostException e )
+		{
+			hostname = "localhost";
+		}
+		return new Parameters( port, hostname, new HashMap< String, String >() );
+	}
 
 	public static void main( final String[] args ) throws Exception
 	{
-		if ( !processOptions( args ) )
-			return;
-
 		System.setProperty( "org.eclipse.jetty.util.log.class", "org.eclipse.jetty.util.log.StdErrLog" );
 
+		final Parameters params = processOptions( args, getDefaultParameters() );
+		if ( params == null )
+			return;
+
 		// Threadpool for multiple connections
 		final Server server = new Server( new QueuedThreadPool( 1000, 10 ) );
 
 		// ServerConnector configuration
 		final ServerConnector connector = new ServerConnector( server );
-		connector.setHost( serverName );
-		connector.setPort( port );
+		connector.setHost( params.getHostname() );
+		connector.setPort( params.getPort() );
 		LOG.info( "Set connectors: " + connector );
 		server.setConnectors( new Connector[] { connector } );
 
@@ -53,7 +86,7 @@ public class BigDataServer
 		final ConnectorStatistics connectorStats = new ConnectorStatistics();
 		connector.addBean( connectorStats );
 
-		final String baseURL = "http://" + server.getURI().getHost() + ":" + port;
+		final String baseURL = "http://" + server.getURI().getHost() + ":" + params.getPort();
 		LOG.info( "Server Base URL: " + baseURL );
 
 		// Handler initialization
@@ -61,7 +94,7 @@ public class BigDataServer
 
 		final HandlerCollection handlers = new HandlerCollection();
 
-		final ContextHandlerCollection datasetHandlers = createHandlers( baseURL, dataSet );
+		final ContextHandlerCollection datasetHandlers = createHandlers( baseURL, params.getDatasets() );
 		handlers.addHandler( datasetHandlers );
 		handlers.addHandler( new ManagerHandler( baseURL, server, connectorStats, statHandler, datasetHandlers ) );
 		handlers.addHandler( new RequestLogHandler() );
@@ -76,107 +109,171 @@ public class BigDataServer
 		server.join();
 	}
 
-	static private boolean processOptions( String[] args ) throws ParseException, IOException
+	/**
+	 * Server parameters: hostname, port, datasets.
+	 */
+	private static class Parameters
 	{
-		// create Options object
-		Options options = new Options();
+		private final int port;
 
-		// add p option
-		options.addOption( "p", true, "listening port" );
+		private final String hostname;
 
-		// add s baseurl
-		options.addOption( "s", true, "server name" );
+		/**
+		 * maps from dataset name to dataset xml path.
+		 */
+		private final Map< String, String > datasetNameToXml;
 
-		// -d or multiple {name name.xml} pairs
-		options.addOption( "d", true, "dataset file" );
+		Parameters( final int port, final String hostname, final Map< String, String > datasetNameToXml )
+		{
+			this.port = port;
+			this.hostname = hostname;
+			this.datasetNameToXml = datasetNameToXml;
+		}
+
+		public int getPort()
+		{
+			return port;
+		}
 
-		CommandLineParser parser = new BasicParser();
-		CommandLine cmd = parser.parse( options, args );
+		public String getHostname()
+		{
+			return hostname;
+		}
 
-		// Getting port number option
-		String portString = cmd.getOptionValue( "p", "8080" );
-		port = Integer.parseInt( portString );
+		/**
+		 * Get datasets.
+		 *
+		 * @return datasets as a map from dataset name to dataset xml path.
+		 */
+		public Map< String, String > getDatasets()
+		{
+			return datasetNameToXml;
+		}
+	}
 
-		// Getting server name option
-		String serverString = cmd.getOptionValue( "s", "localhost" );
-		serverName = serverString;
+	@SuppressWarnings( "static-access" )
+	static private Parameters processOptions( final String[] args, final Parameters defaultParameters ) throws IOException
+	{
+		// create Options object
+		final Options options = new Options();
 
-		String datasetFile = cmd.getOptionValue( "d" );
-		if ( cmd.hasOption( "d" ) )
+		final String cmdLineSyntax = "BigDataServer [OPTIONS] [NAME XML]...";
+
+		final String description =
+				"Serves one or more XML/HDF5 datasets for remote access over HTTP.\n" +
+						"Provide (NAME XML) pairs on the command line or in a dataset file, where NAME is the name under which the dataset should be made accessible and XML is the path to the XML file of the dataset.";
+
+		options.addOption( OptionBuilder
+				.withDescription( "Hostname of the server.\n(default: " + defaultParameters.getHostname() + ")" )
+				.hasArg()
+				.withArgName( "HOSTNAME" )
+				.create( "s" ) );
+
+		options.addOption( OptionBuilder
+				.withDescription( "Listening port.\n(default: " + defaultParameters.getPort() + ")" )
+				.hasArg()
+				.withArgName( "PORT" )
+				.create( "p" ) );
+
+		// -d or multiple {name name.xml} pairs
+		options.addOption( OptionBuilder
+				.withDescription( "Dataset file: A plain text file specifying one dataset per line. Each line is formatted as \"NAME <TAB> XML\"." )
+				.hasArg()
+				.withArgName( "FILE" )
+				.create( "d" ) );
+
+		try
 		{
-			// process the file given with "-d"
+			final CommandLineParser parser = new BasicParser();
+			final CommandLine cmd = parser.parse( options, args );
 
-			// check the file presence
-			Path path = Paths.get( datasetFile );
+			// Getting port number option
+			final String portString = cmd.getOptionValue( "p", Integer.toString( defaultParameters.getPort() ) );
+			final int port = Integer.parseInt( portString );
 
-			if ( Files.notExists( path ) )
-			{
-				LOG.warn( "Dataset list file does not exist. Cannot start BigDataServer." );
-				return false;
-			}
-			else
+			// Getting server name option
+			final String serverString = cmd.getOptionValue( "s", defaultParameters.getHostname() );
+			final String serverName = serverString;
+
+			final HashMap< String, String > datasets = new HashMap< String, String >( defaultParameters.getDatasets() );
+
+			if ( cmd.hasOption( "d" ) )
 			{
+				// process the file given with "-d"
+				final String datasetFile = cmd.getOptionValue( "d" );
+
+				// check the file presence
+				final Path path = Paths.get( datasetFile );
+
+				if ( Files.notExists( path ) )
+					throw new IllegalArgumentException( "Dataset list file does not exist." );
+
 				// Process dataset list file
-				List< String > lines = Files.readAllLines( path, StandardCharsets.UTF_8 );
+				final List< String > lines = Files.readAllLines( path, StandardCharsets.UTF_8 );
 
-				for ( String str : lines )
+				for ( final String str : lines )
 				{
-					String[] tokens = str.split( "\t" );
-					if ( StringUtils.isNotEmpty( tokens[ 0 ].trim() ) && StringUtils.isNotEmpty( tokens[ 1 ].trim() ) )
+					final String[] tokens = str.split( "\t" );
+					if ( tokens.length == 2 && StringUtils.isNotEmpty( tokens[ 0 ].trim() ) && StringUtils.isNotEmpty( tokens[ 1 ].trim() ) )
 					{
-						dataSet.put( tokens[ 0 ].trim(), tokens[ 1 ].trim() );
-						LOG.info( "Dataset added: {" + tokens[ 0 ].trim() + ", " + tokens[ 1 ].trim() + "}" );
-
-						if ( Files.notExists( Paths.get( tokens[ 1 ].trim() ) ) )
-						{
-							LOG.warn( "Dataset file does not exist: \"" + tokens[ 1 ].trim() + "\". Cannot start BigDataServer." );
-							return false;
-						}
+						final String name = tokens[ 0 ].trim();
+						final String xmlpath = tokens[ 1 ].trim();
+						tryAddDataset( datasets, name, xmlpath );
 					}
 					else
 					{
-						LOG.warn( "Invalid dataset entry (will be skipped): {" + str + "}" );
+						LOG.warn( "Invalid dataset file line (will be skipped): {" + str + "}" );
 					}
 				}
 			}
-		}
-		else
-		{
-			String keyHolder = null;
 
-			// process {name, name.xml} pairs
-			for ( Object s : cmd.getArgList() )
-			{
-				if ( keyHolder != null )
-				{
-					dataSet.put( keyHolder, s.toString() );
-					LOG.info( "Dataset added: {" + keyHolder + ", " + s.toString() + "}" );
-					keyHolder = null;
-				}
-				else
-				{
-					keyHolder = s.toString();
-				}
-			}
+			// process additional {name, name.xml} pairs given on the
+			// command-line
+			final String[] leftoverArgs = cmd.getArgs();
+			if ( leftoverArgs.length % 2 != 0 )
+				throw new IllegalArgumentException( "Dataset list has an error while processing." );
 
-			if ( keyHolder != null )
+			for ( int i = 0; i < leftoverArgs.length; i += 2 )
 			{
-				LOG.warn( "Dataset list has an error while processing. Cannot start BigDataServer." );
-				return false;
+				final String name = leftoverArgs[ i ];
+				final String xmlpath = leftoverArgs[ i + 1 ];
+				tryAddDataset( datasets, name, xmlpath );
 			}
+
+			if ( datasets.isEmpty() )
+				throw new IllegalArgumentException( "Dataset list is empty." );
+
+			return new Parameters( port, serverName, datasets );
+		}
+		catch ( final ParseException | IllegalArgumentException e )
+		{
+			LOG.warn( e.getMessage() );
+			final HelpFormatter formatter = new HelpFormatter();
+			formatter.printHelp( cmdLineSyntax, description, options, null );
 		}
+		return null;
+	}
 
-		return true;
+	private static void tryAddDataset( final HashMap< String, String > datasetNameToXML, final String name, final String xmlpath ) throws IllegalArgumentException
+	{
+		if ( datasetNameToXML.containsKey( name ) )
+			throw new IllegalArgumentException( "Duplicate dataset name: \"" + name + "\"" );
+		if ( Files.notExists( Paths.get( xmlpath ) ) )
+			throw new IllegalArgumentException( "Dataset file does not exist: \"" + xmlpath + "\"" );
+		datasetNameToXML.put( name, xmlpath );
+		LOG.info( "Dataset added: {" + name + ", " + xmlpath + "}" );
 	}
 
-	static private ContextHandlerCollection createHandlers( final String baseURL, final HashMap< String, String > dataSet ) throws SpimDataException
+	private static ContextHandlerCollection createHandlers( final String baseURL, final Map< String, String > dataSet ) throws SpimDataException
 	{
 		final ContextHandlerCollection handlers = new ContextHandlerCollection();
 
-		for ( final String key : dataSet.keySet() )
+		for ( final Entry< String, String > entry : dataSet.entrySet() )
 		{
-			final String context = "/" + key;
-			final CellHandler ctx = new CellHandler( baseURL + context + "/", dataSet.get( key ) );
+			final String name = entry.getKey();
+			final String xmlpath = entry.getValue();
+			final String context = "/" + name;
+			final CellHandler ctx = new CellHandler( baseURL + context + "/", xmlpath );
 			ctx.setContextPath( context );
 			handlers.addHandler( ctx );
 		}
-- 
GitLab