diff --git a/pom.xml b/pom.xml index 6bd7bd7..a46ee15 100644 --- a/pom.xml +++ b/pom.xml @@ -48,8 +48,12 @@ org.eclipse.jetty jetty-security - - org.antlr + + org.eclipse.jetty + jetty-servlet + + + org.antlr stringtemplate 3.2.1 diff --git a/src/main/java/bdv/model/DataSet.java b/src/main/java/bdv/model/DataSet.java index 48fe116..f39bb1c 100644 --- a/src/main/java/bdv/model/DataSet.java +++ b/src/main/java/bdv/model/DataSet.java @@ -1,10 +1,14 @@ package bdv.model; +import org.eclipse.jetty.util.log.Log; + import java.io.BufferedWriter; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; /** @@ -12,6 +16,8 @@ */ public class DataSet { + private static final org.eclipse.jetty.util.log.Logger LOG = Log.getLogger( DataSet.class ); + private static Path dataSetListPath; /** * DataSet Context name of this {@link bdv.server.CellHandler} is serving. @@ -31,6 +37,8 @@ public class DataSet private String thumbnailUrl; private String datasetUrl; + public static int numBackups = 5; + /** * Instantiates a new DataSet * @@ -198,6 +206,34 @@ public static void storeDataSet( ArrayList< DataSet > list ) throws IOException { if ( dataSetListPath != null ) { + // fist make a copy of the XML and save it to not loose it + if ( Files.exists( dataSetListPath ) ) + { + int maxExistingBackup = 0; + for ( int i = 1; i < numBackups; ++i ) + if ( Files.exists( Paths.get( dataSetListPath + "~" + i ) ) ) + maxExistingBackup = i; + else + break; + + // copy the backups + try + { + for ( int i = maxExistingBackup; i >= 1; --i ) + Files.copy( Paths.get( dataSetListPath + "~" + i ), + Paths.get( dataSetListPath + "~" + ( i + 1 ) ), + StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING ); + + Files.copy( dataSetListPath, Paths.get( dataSetListPath + "~" + 1 ), + StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING ); + } + catch ( final IOException e ) + { + LOG.warn( "Could not save backup of data list file: " + e ); + e.printStackTrace(); + } + } + BufferedWriter writer = Files.newBufferedWriter( dataSetListPath, StandardCharsets.UTF_8 ); for ( DataSet ds : list ) { diff --git a/src/main/java/bdv/server/BigDataServer.java b/src/main/java/bdv/server/BigDataServer.java index 79cf3d0..786150b 100644 --- a/src/main/java/bdv/server/BigDataServer.java +++ b/src/main/java/bdv/server/BigDataServer.java @@ -11,22 +11,51 @@ import org.eclipse.jetty.server.*; import org.eclipse.jetty.server.handler.*; import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.security.Constraint; +import org.eclipse.jetty.util.security.Password; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; - +import sun.security.x509.AlgorithmId; +import sun.security.x509.CertificateAlgorithmId; +import sun.security.x509.CertificateIssuerName; +import sun.security.x509.CertificateSerialNumber; +import sun.security.x509.CertificateSubjectName; +import sun.security.x509.CertificateValidity; +import sun.security.x509.CertificateVersion; +import sun.security.x509.CertificateX509Key; +import sun.security.x509.X500Name; +import sun.security.x509.X509CertImpl; +import sun.security.x509.X509CertInfo; + +import java.io.BufferedWriter; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; +import java.math.BigInteger; 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.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.SecureRandom; +import java.security.SignatureException; +import java.security.cert.CertificateException; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Serve XML/HDF5 datasets over HTTP. @@ -39,16 +68,14 @@ * 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) + * -m <SECURE_PORT>Manager context HTTPS port. The manager context is automatically enabled. + * (default: 8443) + * -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 * @author HongKee Moon */ @@ -93,7 +120,7 @@ public static void main( final String[] args ) throws Exception httpConfig.setSecurePort( params.getSslport() ); // Setup buffers on http - HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory( httpConfig ); + final HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory( httpConfig ); httpConnectionFactory.setInputBufferSize( 64 * 1024 ); // ServerConnector configuration @@ -108,23 +135,36 @@ public static void main( final String[] args ) throws Exception final HandlerCollection handlers = new HandlerCollection(); final ContextHandlerCollection datasetHandlers = createHandlers( baseURL, params.getDatasets(), thumbnailsDirectoryName ); + + final DataSetContextHandler dataSetContextHandler = new DataSetContextHandler( datasetHandlers ); + handlers.addHandler( datasetHandlers ); + + handlers.addHandler( dataSetContextHandler ); + handlers.addHandler( new JsonDatasetListHandler( server, datasetHandlers ) ); Handler handler = handlers; if ( params.enableManagerContext() ) { - HttpConfiguration https = new HttpConfiguration(); + if ( !checkKeystore() ) + throw new IllegalArgumentException( "Keystore file does not exist." ); + + if ( !checkRealmProperty() ) + throw new IllegalArgumentException( "Login property file does not exist." ); + + final HttpConfiguration https = new HttpConfiguration(); https.addCustomizer( new SecureRequestCustomizer() ); - // Please, change localhost according to your site name - // keytool -genkey -alias localhost -keyalg RSA -keystore keystore.jks -keysize 2048 - SslContextFactory sslContextFactory = new SslContextFactory(); - sslContextFactory.setKeyStorePath( Resource.newClassPathResource( "etc/keystore.jks" ).toString() ); - sslContextFactory.setKeyStorePassword( "123456" ); - sslContextFactory.setKeyManagerPassword( "123456" ); + final SslContextFactory sslContextFactory = new SslContextFactory(); + sslContextFactory.setKeyStorePath( "etc/keystore.jks" ); - ServerConnector sslConnector = new ServerConnector( server, + final char passwordArray[] = System.console().readPassword( "Please, enter your keystore password: " ); + String password = new String( passwordArray ); + sslContextFactory.setKeyStorePassword( password ); + sslContextFactory.setKeyManagerPassword( password ); + + final ServerConnector sslConnector = new ServerConnector( server, new SslConnectionFactory( sslContextFactory, "http/1.1" ), new HttpConnectionFactory( https ) ); sslConnector.setHost( params.getHostname() ); @@ -141,40 +181,35 @@ public static void main( final String[] args ) throws Exception handlers.addHandler( new ManagerHandler( baseURL, server, connectorStats, statHandler, datasetHandlers, thumbnailsDirectoryName ) ); statHandler.setHandler( handlers ); - Constraint constraint = new Constraint(); + final Constraint constraint = new Constraint(); constraint.setName( Constraint.__BASIC_AUTH ); - constraint.setRoles( new String[] { "admin", "superuser" } ); + constraint.setRoles( new String[] { "admin" } ); constraint.setAuthenticate( true ); // 2 means CONFIDENTIAL. 1 means INTEGRITY constraint.setDataConstraint( Constraint.DC_CONFIDENTIAL ); - ConstraintMapping cm = new ConstraintMapping(); - cm.setPathSpec( "/manager/*" ); + final ConstraintMapping cm = new ConstraintMapping(); + cm.setPathSpec( "/" + Constants.MANAGER_CONTEXT_NAME + "/*" ); cm.setConstraint( constraint ); // Please change the password in realm.properties - HashLoginService loginService = new HashLoginService( "BigDataServerRealm", Resource.newClassPathResource( "etc/realm.properties" ).toString() ); + final HashLoginService loginService = new HashLoginService( "BigDataServerRealm", "etc/realm.properties" ); server.addBean( loginService ); - HandlerList handlerList = new HandlerList(); - - ContextHandler redirectHandler = new ContextHandler(); - redirectHandler.setContextPath( "/" + Constants.MANAGER_CONTEXT_NAME ); - redirectHandler.setHandler( new SecuredRedirectHandler() ); - - handlerList.addHandler( redirectHandler ); - - ConstraintSecurityHandler sh = new ConstraintSecurityHandler(); + final ConstraintSecurityHandler sh = new ConstraintSecurityHandler(); sh.setLoginService( loginService ); sh.setAuthenticator( new BasicAuthenticator() ); sh.addConstraintMapping( cm ); sh.setHandler( statHandler ); + final HandlerList handlerList = new HandlerList(); handlerList.addHandler( sh ); handler = handlerList; } + handlers.addHandler( new IndexPageHandler( server ) ); + LOG.info( "Set handler: " + handler ); server.setHandler( handler ); LOG.info( "Server Base URL: " + baseURL ); @@ -183,6 +218,211 @@ public static void main( final String[] args ) throws Exception server.join(); } + private static boolean checkKeystore() + { + // check if "etc/keystore.jks" exists + if ( Files.exists( Paths.get( "etc/keystore.jks" ) ) ) + return true; + else + { + try + { + // keytool -genkey -alias localhost -keyalg RSA -keystore keystore.jks -keysize 2048 + if ( Files.notExists( Paths.get( "etc/" ) ) ) + Files.createDirectory( Paths.get( "etc/" ) ); + + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance( "RSA" ); + keyPairGenerator.initialize( 2048 ); + KeyPair KPair = keyPairGenerator.generateKeyPair(); + PrivateKey privkey = KPair.getPrivate(); + + X509CertInfo info = new X509CertInfo(); + Date from = new Date(); + Date to = new Date( from.getTime() + 365 * 86400000l ); + CertificateValidity interval = new CertificateValidity( from, to ); + BigInteger sn = new BigInteger( 64, new SecureRandom() ); + X500Name owner = new X500Name( "CN=Unknown, L=Unknown, ST=Unknown, O=Unknown, OU=Unknown, C=Unknown" ); + + info.set( X509CertInfo.VALIDITY, interval ); + info.set( X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber( sn ) ); + boolean justName = isJavaAtLeast( 1.8 ); + if ( justName ) + { + info.set( X509CertInfo.SUBJECT, owner ); + info.set( X509CertInfo.ISSUER, owner ); + } + else + { + info.set( X509CertInfo.SUBJECT, new CertificateSubjectName( owner ) ); + info.set( X509CertInfo.ISSUER, new CertificateIssuerName( owner ) ); + } + + info.set( X509CertInfo.KEY, new CertificateX509Key( KPair.getPublic() ) ); + info.set( X509CertInfo.VERSION, new CertificateVersion( CertificateVersion.V3 ) ); + AlgorithmId algo = new AlgorithmId( AlgorithmId.sha256WithRSAEncryption_oid ); + info.set( X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId( algo ) ); + + // Sign the cert to identify the algorithm that's used. + X509CertImpl cert = new X509CertImpl( info ); + cert.sign( privkey, "SHA256withRSA" ); + + // Update the algorithm, and resign. + algo = ( AlgorithmId ) cert.get( X509CertImpl.SIG_ALG ); + info.set( CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, algo ); + cert = new X509CertImpl( info ); + cert.sign( privkey, "SHA256withRSA" ); + + KeyStore keyStore = null; + FileOutputStream keyStoreFile = null; + + // Load the default Java keystore + keyStore = KeyStore.getInstance( KeyStore.getDefaultType() ); + keyStore.load( null, "changeit".toCharArray() ); + + final char passwordArray[] = System.console().readPassword( "Enter your new keystore password for SSL connection: " ); + + String password = new String( passwordArray ); + + // Put our information + keyStore.setCertificateEntry( "localhost", cert ); + keyStore.setKeyEntry( "localhost", privkey, + password.toCharArray(), + new java.security.cert.Certificate[] { cert } ); + + // Generate new cert + keyStoreFile = new FileOutputStream( "etc/keystore.jks" ); + keyStore.store( keyStoreFile, password.toCharArray() ); + keyStoreFile.close(); + } + catch ( FileNotFoundException e ) + { + e.printStackTrace(); + return false; + } + catch ( KeyStoreException e ) + { + e.printStackTrace(); + return false; + } + catch ( IOException e ) + { + e.printStackTrace(); + return false; + } + catch ( NoSuchAlgorithmException e ) + { + e.printStackTrace(); + return false; + } + catch ( CertificateException e ) + { + e.printStackTrace(); + return false; + } + catch ( SignatureException e ) + { + e.printStackTrace(); + return false; + } + catch ( NoSuchProviderException e ) + { + e.printStackTrace(); + return false; + } + catch ( InvalidKeyException e ) + { + e.printStackTrace(); + return false; + } + } + return true; + } + + public static final Pattern JAVA_VERSION = Pattern.compile( "([0-9]*.[0-9]*)(.*)?" ); + + /** + * Checks whether the current Java runtime has a version equal or higher then the given one. As Java version are + * not double (because they can use more digits such as 1.8.0), this method extracts the two first digits and + * transforms it as a double. + * @param version the version + * @return {@literal true} if the current Java runtime is at least the specified one, + * {@literal false} if not or if the current version cannot be retrieve or is the retrieved version cannot be + * parsed as a double. + */ + public static boolean isJavaAtLeast( double version ) + { + String javaVersion = System.getProperty( "java.version" ); + if ( javaVersion == null ) + { + return false; + } + + // if the retrieved version is one three digits, remove the last one. + Matcher matcher = JAVA_VERSION.matcher( javaVersion ); + if ( matcher.matches() ) + { + javaVersion = matcher.group( 1 ); + } + + try + { + double v = Double.parseDouble( javaVersion ); + return v >= version; + } + catch ( NumberFormatException e ) + { + return false; + } + } + + private static boolean checkRealmProperty() + { + Path path = Paths.get( "etc/realm.properties" ); + // check if "etc/realm.properties" exists + if ( Files.exists( path ) ) + return true; + else + { + try + { + if ( Files.notExists( Paths.get( "etc/" ) ) ) + Files.createDirectory( Paths.get( "etc/" ) ); + + final String userId = System.console().readLine( "Enter your ID for manager : " ); + final char passwordArray[] = System.console().readPassword( "Enter your password for \"%s\": ", userId ); + + String md5 = Password.MD5.digest( new String( passwordArray ) ); + + BufferedWriter writer = Files.newBufferedWriter( path, StandardCharsets.UTF_8 ); + + writer.append( "# http://www.eclipse.org/jetty/documentation/current/configuring-security-secure-passwords.html\n" ); + writer.append( "# Please, use obfuscated/MD5/crypted password only by using below instructions\n" ); + writer.append( "#\n" ); + writer.append( "# \t$ export JETTY_VERSION=9.0.0.RC0\n" ); + writer.append( "# \t$ java -cp lib/jetty-util-$JETTY_VERSION.jar org.eclipse.jetty.util.security.Password username blahblah\n" ); + writer.append( "# \tOBF:20771x1b206z\n" ); + writer.append( "# \tMD5:639bae9ac6b3e1a84cebb7b403297b79\n" ); + writer.append( "# \tCRYPT:me/ks90E221EY\n" ); + + writer.newLine(); + writer.append( userId ); + writer.append( ": " ); + writer.append( md5 ); + writer.append( ", admin" ); + writer.newLine(); + + writer.flush(); + writer.close(); + } + catch ( IOException e ) + { + e.printStackTrace(); + return false; + } + } + return true; + } + /** * Server parameters: hostname, port, sslPort, datasets. */ @@ -235,7 +475,6 @@ public String getThumbnailDirectory() /** * Get datasets. - * * @return datasets as a map from dataset name to dataset xml path. */ public Map< String, DataSet > getDatasets() @@ -286,18 +525,11 @@ static private Parameters processOptions( final String[] args, final Parameters .withArgName( "DIRECTORY" ) .create( "t" ) ); - if ( Constants.ENABLE_EXPERIMENTAL_FEATURES ) - { - options.addOption( OptionBuilder - .withDescription( "Enable statistics and manager context. EXPERIMENTAL!" ) - .create( "m" ) ); - - options.addOption( OptionBuilder - .withDescription( "Manager context HTTPS port. EXPERIMENTAL!" + "\n(default: " + defaultParameters.getSslport() + ")" ) - .hasArg() - .withArgName( "SECURE_PORT" ) - .create( "mp" ) ); - } + options.addOption( OptionBuilder + .withDescription( "Manager context HTTPS port. The manager context is automatically enabled." + "\n(default: " + defaultParameters.getSslport() + ")" ) + .hasArg() + .withArgName( "SECURE_PORT" ) + .create( "m" ) ); try { @@ -318,19 +550,19 @@ static private Parameters processOptions( final String[] args, final Parameters boolean enableManagerContext = false; int sslPort = defaultParameters.getSslport(); - if ( Constants.ENABLE_EXPERIMENTAL_FEATURES ) + + if ( cmd.hasOption( "m" ) ) { - if ( cmd.hasOption( "m" ) ) - { - enableManagerContext = true; + enableManagerContext = true; - final String securePortString = cmd.getOptionValue( "mp", Integer.toString( defaultParameters.getSslport() ) ); - sslPort = Integer.parseInt( securePortString ); - } + final String securePortString = cmd.getOptionValue( "m", Integer.toString( defaultParameters.getSslport() ) ); + sslPort = Integer.parseInt( securePortString ); + + if ( !cmd.hasOption( "d" ) ) + throw new IllegalArgumentException( "Dataset list file is necessary for BigDataServer manager" ); } // Path for holding the dataset file - Path readDatasetFilePath; if ( cmd.hasOption( "d" ) ) { // process the file given with "-d" @@ -342,25 +574,8 @@ static private Parameters processOptions( final String[] args, final Parameters if ( Files.notExists( path ) ) throw new IllegalArgumentException( "Dataset list file does not exist." ); - readDatasetFilePath = path; + readDatasetFile( datasets, path ); } - else - { - // If the user does not provide any dataset file, - // we keep the adding dataset in {Working folder}/.data/list.txt - final Path datasetFileFolderPath = Paths.get( System.getProperty( "user.dir" ) + "/.data" ); - - if ( Files.notExists( datasetFileFolderPath ) ) - Files.createDirectory( datasetFileFolderPath ); - - final Path datasetFilePath = Paths.get( System.getProperty( "user.dir" ) + "/.data/list.txt" ); - - if ( Files.notExists( datasetFilePath ) ) - Files.createFile( datasetFilePath ); - - readDatasetFilePath = datasetFilePath; - } - readDatasetFile( datasets, readDatasetFilePath ); // process additional {name, name.xml} pairs given on the // command-line @@ -375,6 +590,9 @@ static private Parameters processOptions( final String[] args, final Parameters tryAddDataset( datasets, name, xmlpath ); } + if ( datasets.isEmpty() ) + throw new IllegalArgumentException( "Dataset list is empty." ); + return new Parameters( port, sslPort, serverName, datasets, thumbnailDirectory, enableManagerContext ); } catch ( final ParseException | IllegalArgumentException e ) @@ -421,12 +639,12 @@ else if ( tokens.length == 5 ) } } - private static void tryAddDataset( final HashMap< String, DataSet > datasetNameToDataSet, final String ... args ) throws IllegalArgumentException + private static void tryAddDataset( final HashMap< String, DataSet > datasetNameToDataSet, final String... args ) throws IllegalArgumentException { - if ( args.length >= 2) + if ( args.length >= 2 ) { - final String name = args[0]; - final String xmlpath = args[1]; + final String name = args[ 0 ]; + final String xmlpath = args[ 1 ]; for ( final String reserved : Constants.RESERVED_CONTEXT_NAMES ) if ( name.equals( reserved ) ) @@ -440,11 +658,11 @@ private static void tryAddDataset( final HashMap< String, DataSet > datasetNameT String desc = ""; String index = ""; - if( args.length == 5 ) + if ( args.length == 5 ) { - category = args[2]; - desc = args[3]; - index = args[4]; + category = args[ 2 ]; + desc = args[ 3 ]; + index = args[ 4 ]; } DataSet ds = new DataSet( name, xmlpath, category, desc, index ); @@ -459,7 +677,7 @@ private static String getThumbnailDirectoryPath( final Parameters params ) throw if ( thumbnailDirectoryName != null ) { Path thumbnails = Paths.get( thumbnailDirectoryName ); - if ( ! Files.exists( thumbnails ) ) + if ( !Files.exists( thumbnails ) ) { try { @@ -474,7 +692,7 @@ private static String getThumbnailDirectoryPath( final Parameters params ) throw } else { - if ( ! Files.isDirectory( thumbnails ) ) + if ( !Files.isDirectory( thumbnails ) ) LOG.warn( "Thumbnails directory \"" + thumbnailDirectoryName + "\" is not a directory.\n Trying to create temporary directory." ); else return thumbnails.toFile().getAbsolutePath(); @@ -493,7 +711,7 @@ private static ContextHandlerCollection createHandlers( final String baseURL, fi { final String name = entry.getKey(); final DataSet ds = entry.getValue(); - final String context = "/" + name; + final String context = "/" + Constants.DATASET_CONTEXT_NAME + "/" + name; final CellHandler ctx = new CellHandler( baseURL + context + "/", ds, thumbnailsDirectoryName ); ctx.setContextPath( context ); handlers.addHandler( ctx ); diff --git a/src/main/java/bdv/server/CellHandler.java b/src/main/java/bdv/server/CellHandler.java index 922f503..e9024c6 100644 --- a/src/main/java/bdv/server/CellHandler.java +++ b/src/main/java/bdv/server/CellHandler.java @@ -18,10 +18,12 @@ import com.google.gson.GsonBuilder; +import com.google.gson.stream.JsonWriter; import mpicbg.spim.data.SpimDataException; import net.imglib2.img.basictypeaccess.volatiles.array.VolatileShortArray; import net.imglib2.realtransform.AffineTransform3D; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.log.Log; @@ -84,6 +86,8 @@ public class CellHandler extends ContextHandler private Hdf5ImageLoader imgLoader; + private String baseUrl; + /** * DataSet information holder */ @@ -92,7 +96,7 @@ public class CellHandler extends ContextHandler public CellHandler( final String baseUrl, final DataSet dataSet, final String thumbnailsDirectory ) throws SpimDataException, IOException { active = true; - + this.baseUrl = baseUrl; this.dataSet = dataSet; final XmlIoSpimDataMinimal io = new XmlIoSpimDataMinimal(); @@ -139,6 +143,12 @@ public void doHandle( final String target, final Request baseRequest, final Http return; } + if ( target.startsWith( "/json" ) ) + { + provideJson( baseRequest, response ); + return; + } + if ( target.equals( "/png" ) ) { provideThumbnail( baseRequest, response ); @@ -374,4 +384,56 @@ public DataSet getDataSet() { return dataSet; } + + private void provideJson( final Request baseRequest, final HttpServletResponse response ) throws IOException + { + response.setContentType( "application/json" ); + response.setStatus( HttpServletResponse.SC_OK ); + baseRequest.setHandled( true ); + + final PrintWriter ow = response.getWriter(); + + final JsonWriter writer = new JsonWriter( ow ); + + writer.setIndent( "\t" ); + + writer.beginObject(); + + writer.name( getDataSet().getName() ).beginObject(); + + writer.name( "id" ).value( getDataSet().getName() ); + + writer.name( "category" ).value( getDataSet().getCategory() ); + + writer.name( "description" ).value( getDataSet().getDescription() ); + + writer.name( "index" ).value( getDataSet().getIndex() ); + + writer.name( "thumbnailUrl" ).value( getDataSet().getThumbnailUrl() ); + + writer.name( "datasetUrl" ).value( getDataSet().getDatasetUrl() ); + + writer.endObject(); + + writer.endObject(); + + writer.flush(); + + writer.close(); + + ow.close(); + } + + public void handleXml( final Request baseRequest, final HttpServletResponse response ) throws IOException + { + respondWithString( baseRequest, response, "application/xml", datasetXmlString ); + } + + public void handleBdv( final Request baseRequest, final HttpServletResponse response ) throws IOException + { + String url = baseUrl; + if ( url.endsWith( "/" ) ) + url = url.substring( 0, url.lastIndexOf( "/" ) ); + respondWithString( baseRequest, response, "application/bdv", url ); + } } diff --git a/src/main/java/bdv/server/Constants.java b/src/main/java/bdv/server/Constants.java index f11adae..a0b4297 100644 --- a/src/main/java/bdv/server/Constants.java +++ b/src/main/java/bdv/server/Constants.java @@ -6,6 +6,8 @@ public class Constants public static final String MANAGER_CONTEXT_NAME = "manager"; + public static final String DATASET_CONTEXT_NAME = "dataset"; + public static final String[] RESERVED_CONTEXT_NAMES = new String[] { DATASETLIST_CONTEXT_NAME, @@ -15,6 +17,4 @@ public class Constants public static final int THUMBNAIL_WIDTH = 100; public static final int THUMBNAIL_HEIGHT = 100; - - public static final boolean ENABLE_EXPERIMENTAL_FEATURES = false; } diff --git a/src/main/java/bdv/server/DataSetContextHandler.java b/src/main/java/bdv/server/DataSetContextHandler.java new file mode 100644 index 0000000..007a2ec --- /dev/null +++ b/src/main/java/bdv/server/DataSetContextHandler.java @@ -0,0 +1,77 @@ +package bdv.server; + +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.ContextHandlerCollection; +import org.eclipse.jetty.servlet.DefaultServlet; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.log.Log; + +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import java.io.IOException; + +/** + * DataSet Handler handles /dataset context + */ +public class DataSetContextHandler extends ServletContextHandler +{ + private static final org.eclipse.jetty.util.log.Logger LOG = Log.getLogger( DataSetContextHandler.class ); + + private final ContextHandlerCollection datasetHandlers; + + public DataSetContextHandler( final ContextHandlerCollection datasetHandlers ) + { + this.datasetHandlers = datasetHandlers; + setContextPath( "/" + Constants.DATASET_CONTEXT_NAME ); + + final ServletHandler servletHandler = new ServletHandler(); + Servlet servlet = new DefaultServlet(); + ServletHolder servletHolder = new ServletHolder( servlet ); + servletHandler.addServletWithMapping( servletHolder, "/*.xml" ); + servletHandler.addServletWithMapping( servletHolder, "/*.bdv" ); + + setHandler( servletHandler ); + } + + @Override + public void doHandle( final String target, final Request baseRequest, final HttpServletRequest request, final HttpServletResponse response ) throws IOException, ServletException + { + String datasetName = target; + + if ( datasetName.lastIndexOf( ".xml" ) != -1 ) + { + datasetName = datasetName.substring( 0, datasetName.lastIndexOf( ".xml" ) ); + findCellHandler( datasetName ).handleXml( baseRequest, response ); + } + else if ( datasetName.lastIndexOf( ".bdv" ) != -1 ) + { + datasetName = datasetName.substring( 0, datasetName.lastIndexOf( ".bdv" ) ); + findCellHandler( datasetName ).handleBdv( baseRequest, response ); + } + else + super.doHandle( Constants.DATASET_CONTEXT_NAME + "/" + target, baseRequest, request, response ); + } + + private CellHandler findCellHandler( final String datasetName ) + { + CellHandler found = null; + for ( final Handler handler : datasetHandlers.getChildHandlersByClass( CellHandler.class ) ) + { + final CellHandler contextHandler = ( CellHandler ) handler; + + if ( contextHandler.getContextPath().equals( getContextPath() + datasetName ) ) + { + found = contextHandler; + break; + } + } + + return found; + } +} diff --git a/src/main/java/bdv/server/IndexPageHandler.java b/src/main/java/bdv/server/IndexPageHandler.java new file mode 100644 index 0000000..41330a5 --- /dev/null +++ b/src/main/java/bdv/server/IndexPageHandler.java @@ -0,0 +1,138 @@ +package bdv.server; + +import bdv.model.DataSet; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.HandlerCollection; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import java.io.IOException; +import java.io.PrintWriter; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +/** + * Provides the default index page of available datasets on this {@link BigDataServer} + * @author HongKee Moon + */ +public class IndexPageHandler extends ContextHandler +{ + private final Server server; + + public IndexPageHandler( final Server server ) throws IOException, URISyntaxException + { + this.server = server; + setContextPath( "/" ); + } + + @Override + public void doHandle( final String target, final Request baseRequest, final HttpServletRequest request, final HttpServletResponse response ) throws IOException, ServletException + { + if ( target.equals( "/" ) ) + list( baseRequest, response ); + else + super.doHandle( target, baseRequest, request, response ); + } + + private void list( final Request baseRequest, final HttpServletResponse response ) throws IOException + { + response.setContentType( "text/html" ); + response.setStatus( HttpServletResponse.SC_OK ); + baseRequest.setHandled( true ); + + final PrintWriter ow = response.getWriter(); + getHtmlDatasetList( ow ); + ow.close(); + } + + private void getHtmlDatasetList( final PrintWriter out ) throws IOException + { + final ArrayList< DataSet > list = new ArrayList<>(); + + for ( final Handler handler : server.getChildHandlersByClass( CellHandler.class ) ) + { + CellHandler contextHandler = null; + if ( handler instanceof CellHandler ) + { + contextHandler = ( CellHandler ) handler; + + if ( contextHandler.isActive() ) + { + list.add( contextHandler.getDataSet() ); + } + } + } + + // Sort the list by Category and Index + Collections.sort( list, new Comparator< DataSet >() + { + @Override + public int compare( final DataSet lhs, DataSet rhs ) + { + // return 1 if rhs should be before lhs + // return -1 if lhs should be before rhs + // return 0 otherwise + if ( lhs.getCategory().equals( rhs.getCategory() ) ) + { + return lhs.getIndex().compareToIgnoreCase( rhs.getIndex() ); + } + else + { + return lhs.getCategory().compareToIgnoreCase( rhs.getCategory() ); + } + } + } ); + + // Build html table for dataset list + final StringBuilder sb = new StringBuilder(); + sb.append( "\n" ); + sb.append( "\n" ); + sb.append( "\n" ); + sb.append( "\n" ); + + sb.append( "
\n" ); + sb.append( "

BigDataServer DataSet list

" ); + sb.append( "\n" ); + + sb.append( "\n" ); + sb.append( "\n" ); + sb.append( "\n" ); + sb.append( "\n" ); + sb.append( "\n" ); + sb.append( "\n" ); + + for ( DataSet ds : list ) + { + sb.append( "\n" ); + sb.append( "\t\n" ); + sb.append( "\t\n" ); + sb.append( "\n" ); + } + sb.append( "
DataSetDescription
\n" ); + sb.append( "\t\t\n" ); + sb.append( "\t\n" ); + sb.append( "\t\tCategory: " + ds.getCategory() + "
\n" ); + sb.append( "\t\tName: " + ds.getName() + "
\n" ); + sb.append( "\t\tDescription: " + ds.getDescription() + "
\n" ); + + String url = ds.getDatasetUrl(); + if ( url.endsWith( "/" ) ) + url = url.substring( 0, url.lastIndexOf( "/" ) ); + + sb.append( "\t\tXML " ); + sb.append( "\t\tBDV
\n" ); + sb.append( "\t
\n" ); + sb.append( "
\n" ); + sb.append( "\n" ); + + out.write( sb.toString() ); + out.close(); + } +} \ No newline at end of file diff --git a/src/main/resources/etc/keystore.jks b/src/main/resources/etc/keystore.jks deleted file mode 100644 index 9b493f5..0000000 Binary files a/src/main/resources/etc/keystore.jks and /dev/null differ diff --git a/src/main/resources/etc/realm.properties b/src/main/resources/etc/realm.properties deleted file mode 100644 index 13b722e..0000000 --- a/src/main/resources/etc/realm.properties +++ /dev/null @@ -1,11 +0,0 @@ -# http://www.eclipse.org/jetty/documentation/current/configuring-security-secure-passwords.html -# Please, use obfuscated/MD5/crypted password only by using below instructions -# -# $ export JETTY_VERSION=9.0.0.RC0 -# $ java -cp lib/jetty-util-$JETTY_VERSION.jar org.eclipse.jetty.util.security.Password username blahblah -# OBF:20771x1b206z -# MD5:639bae9ac6b3e1a84cebb7b403297b79 -# CRYPT:me/ks90E221EY - -admin: bdmanager, admin -user: MD5:639bae9ac6b3e1a84cebb7b403297b79, superuser diff --git a/src/main/resources/webapp/pages/datasets.html b/src/main/resources/webapp/pages/datasets.html index 7051e36..ea25544 100644 --- a/src/main/resources/webapp/pages/datasets.html +++ b/src/main/resources/webapp/pages/datasets.html @@ -241,7 +241,7 @@

Dataset deployment

$.ajax({ type: "POST", url: "/manager/?op=activate", - data: { name: $(this).parent().next().text(), active: $(this).prop('checked')}, + data: { name: $(this).parent().next().next().next().text(), active: $(this).prop('checked')}, success: function (msg) { console.info("Dataset activated: " + msg); }