Newer
Older
import mpicbg.spim.data.SpimDataException;
import org.apache.commons.cli.*;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.ConnectorStatistics;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.StatisticsHandler;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
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.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 <FILE> Dataset file: A plain text file specifying one dataset
* per line. Each line is formatted as "NAME <TAB> XML".
* -p <PORT> Listening port. (default: 8080)
* -s <HOSTNAME> Hostname of the server.
* -t <DIRECTORY> Directory to store thumbnails. (new temporary directory
* by default.)
* -m enable statistics and manager context. EXPERIMENTAL!
* To enable the {@code -m} option, build with
* {@link Constants#ENABLE_EXPERIMENTAL_FEATURES} set to {@code true}.
*
* @author Tobias Pietzsch <tobias.pietzsch@gmail.com>
* @author HongKee Moon <moon@mpi-cbg.de>
public class BigDataServer
{
private static final org.eclipse.jetty.util.log.Logger LOG = Log.getLogger( BigDataServer.class );
HongKee Moon
committed
static Parameters getDefaultParameters()
{
final int port = 8080;
String hostname;
try
{
hostname = InetAddress.getLocalHost().getHostName();
}
catch ( final UnknownHostException e )
{
hostname = "localhost";
}
final String thumbnailDirectory = null;
final boolean enableManagerContext = false;
return new Parameters( port, hostname, new HashMap< String, String >(), thumbnailDirectory, enableManagerContext );
public static void main( final String[] args ) throws Exception
{
System.setProperty( "org.eclipse.jetty.util.log.class", "org.eclipse.jetty.util.log.StdErrLog" );
final Parameters params = processOptions( args, getDefaultParameters() );
if ( params == null )
return;
final String thumbnailsDirectoryName = getThumbnailDirectoryPath( params );
// Threadpool for multiple connections
Tobias Pietzsch
committed
final Server server = new Server( new QueuedThreadPool( 200, 8 ) );
// ServerConnector configuration
final ServerConnector connector = new ServerConnector( server );
connector.setHost( params.getHostname() );
connector.setPort( params.getPort() );
LOG.info( "Set connectors: " + connector );
server.setConnectors( new Connector[] { connector } );
final String baseURL = "http://" + server.getURI().getHost() + ":" + params.getPort();
Tobias Pietzsch
committed
// Handler initialization
final HandlerCollection handlers = new HandlerCollection();
final ContextHandlerCollection datasetHandlers = createHandlers( baseURL, params.getDatasets(), thumbnailsDirectoryName );
handlers.addHandler( datasetHandlers );
handlers.addHandler( new JsonDatasetListHandler( server, datasetHandlers ) );
Handler handler = handlers;
if ( params.enableManagerContext() )
{
// Add Statistics bean to the connector
final ConnectorStatistics connectorStats = new ConnectorStatistics();
connector.addBean( connectorStats );
// create StatisticsHandler wrapper and ManagerHandler
final StatisticsHandler statHandler = new StatisticsHandler();
handlers.addHandler( new ManagerHandler( baseURL, server, connectorStats, statHandler, datasetHandlers, thumbnailsDirectoryName ) );
statHandler.setHandler( handlers );
handler = statHandler;
}
LOG.info( "Set handler: " + handler );
server.setHandler( handler );
LOG.info( "Server Base URL: " + baseURL );
LOG.info( "BigDataServer starting" );
HongKee Moon
committed
server.start();
server.join();
/**
* Server parameters: hostname, port, datasets.
*/
private static class Parameters
private final String hostname;
/**
* maps from dataset name to dataset xml path.
*/
private final Map< String, String > datasetNameToXml;
private final String thumbnailDirectory;
private final boolean enableManagerContext;
Parameters( final int port, final String hostname, final Map< String, String > datasetNameToXml, final String thumbnailDirectory, final boolean enableManagerContext )
{
this.port = port;
this.hostname = hostname;
this.datasetNameToXml = datasetNameToXml;
this.thumbnailDirectory = thumbnailDirectory;
this.enableManagerContext = enableManagerContext;
}
public int getPort()
{
return port;
}
public String getHostname()
{
return hostname;
}
public String getThumbnailDirectory()
{
return thumbnailDirectory;
}
/**
* Get datasets.
*
* @return datasets as a map from dataset name to dataset xml path.
*/
public Map< String, String > getDatasets()
{
return datasetNameToXml;
}
public boolean enableManagerContext()
{
return enableManagerContext;
}
@SuppressWarnings( "static-access" )
static private Parameters processOptions( final String[] args, final Parameters defaultParameters ) throws IOException
{
// create Options object
final Options options = new Options();
Tobias Pietzsch
committed
final String cmdLineSyntax = "BigDataServer [OPTIONS] [NAME XML] ...\n";
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" ) );
options.addOption( OptionBuilder
.withDescription( "Directory to store thumbnails. (new temporary directory by default.)" )
.hasArg()
.withArgName( "DIRECTORY" )
.create( "t" ) );
if ( Constants.ENABLE_EXPERIMENTAL_FEATURES )
{
options.addOption( OptionBuilder
.withDescription( "enable statistics and manager context. EXPERIMENTAL!" )
.create( "m" ) );
}
final CommandLineParser parser = new BasicParser();
final CommandLine cmd = parser.parse( options, args );
// Getting port number option
final String portString = cmd.getOptionValue( "p", Integer.toString( defaultParameters.getPort() ) );
final int port = Integer.parseInt( portString );
// Getting server name option
final String serverName = cmd.getOptionValue( "s", defaultParameters.getHostname() );
// Getting thumbnail directory option
final String thumbnailDirectory = cmd.getOptionValue( "t", defaultParameters.getThumbnailDirectory() );
final HashMap< String, String > datasets = new HashMap< String, String >( defaultParameters.getDatasets() );
boolean enableManagerContext = false;
if ( Constants.ENABLE_EXPERIMENTAL_FEATURES )
{
if ( cmd.hasOption( "m" ) )
enableManagerContext = true;
}
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." );
final List< String > lines = Files.readAllLines( path, StandardCharsets.UTF_8 );
for ( final String str : lines )
Tobias Pietzsch
committed
final String[] tokens = str.split( "\\s*\\t\\s*" );
if ( tokens.length == 2 && StringUtils.isNotEmpty( tokens[ 0 ].trim() ) && StringUtils.isNotEmpty( tokens[ 1 ].trim() ) )
final String name = tokens[ 0 ].trim();
final String xmlpath = tokens[ 1 ].trim();
tryAddDataset( datasets, name, xmlpath );
LOG.warn( "Invalid dataset file line (will be skipped): {" + str + "}" );
// 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." );
for ( int i = 0; i < leftoverArgs.length; i += 2 )
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, thumbnailDirectory, enableManagerContext );
}
catch ( final ParseException | IllegalArgumentException e )
{
LOG.warn( e.getMessage() );
Tobias Pietzsch
committed
System.out.println();
final HelpFormatter formatter = new HelpFormatter();
formatter.printHelp( cmdLineSyntax, description, options, null );
private static void tryAddDataset( final HashMap< String, String > datasetNameToXML, final String name, final String xmlpath ) throws IllegalArgumentException
{
for ( final String reserved : Constants.RESERVED_CONTEXT_NAMES )
if ( name.equals( reserved ) )
throw new IllegalArgumentException( "Cannot use dataset name: \"" + name + "\" (reserved for internal use)." );
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 + "}" );
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
private static String getThumbnailDirectoryPath( final Parameters params ) throws IOException
{
final String thumbnailDirectoryName = params.getThumbnailDirectory();
if ( thumbnailDirectoryName != null )
{
Path thumbnails = Paths.get( thumbnailDirectoryName );
if ( ! Files.exists( thumbnails ) )
{
try
{
thumbnails = Files.createDirectories( thumbnails );
return thumbnails.toFile().getAbsolutePath();
}
catch ( final IOException e )
{
LOG.warn( e.getMessage() );
LOG.warn( "Could not create thumbnails directory \"" + thumbnailDirectoryName + "\".\n Trying to create temporary directory." );
}
}
else
{
if ( ! Files.isDirectory( thumbnails ) )
LOG.warn( "Thumbnails directory \"" + thumbnailDirectoryName + "\" is not a directory.\n Trying to create temporary directory." );
else
return thumbnails.toFile().getAbsolutePath();
}
}
final Path thumbnails = Files.createTempDirectory( "thumbnails" );
thumbnails.toFile().deleteOnExit();
return thumbnails.toFile().getAbsolutePath();
}
private static ContextHandlerCollection createHandlers( final String baseURL, final Map< String, String > dataSet, final String thumbnailsDirectoryName ) throws SpimDataException, IOException
final ContextHandlerCollection handlers = new ContextHandlerCollection();
for ( final Entry< String, String > entry : dataSet.entrySet() )
final String name = entry.getKey();
final String xmlpath = entry.getValue();
final String context = "/" + name;
final CellHandler ctx = new CellHandler( baseURL + context + "/", xmlpath, name, thumbnailsDirectoryName );
ctx.setContextPath( context );
handlers.addHandler( ctx );
}
return handlers;
}