river-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From nic...@apache.org
Subject svn commit: r724973 [3/9] - in /incubator/river/jtsk/skunk/niclas1/tools: ./ src/ src/main/ src/main/java/ src/main/java/com/ src/main/java/com/sun/ src/main/java/com/sun/jini/ src/main/java/com/sun/jini/tool/ src/main/java/com/sun/jini/tool/envcheck/ ...
Date Wed, 10 Dec 2008 05:09:43 GMT
Added: incubator/river/jtsk/skunk/niclas1/tools/src/main/java/com/sun/jini/tool/ClassServer.java
URL: http://svn.apache.org/viewvc/incubator/river/jtsk/skunk/niclas1/tools/src/main/java/com/sun/jini/tool/ClassServer.java?rev=724973&view=auto
==============================================================================
--- incubator/river/jtsk/skunk/niclas1/tools/src/main/java/com/sun/jini/tool/ClassServer.java (added)
+++ incubator/river/jtsk/skunk/niclas1/tools/src/main/java/com/sun/jini/tool/ClassServer.java Tue Dec  9 21:09:41 2008
@@ -0,0 +1,1126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 com.sun.jini.tool;
+
+import com.sun.jini.logging.Levels;
+import com.sun.jini.start.LifeCycle;
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FilePermission;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import java.util.StringTokenizer;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A simple HTTP server, for serving up JAR and class files.
+ * <p>
+ * The following items are discussed below:
+ * <ul>
+ * <li>{@linkplain #main Command line options}
+ * <li><a href="#logging">Logging</a>
+ * <li><a href="#running">Examples for running ClassServer</a>
+ * </ul>
+ * <p>
+ * <a name="logging"><h3>Logging</h3></a>
+ * <p>
+ *
+ * This implementation uses the {@link Logger} named
+ * <code>com.sun.jini.tool.ClassServer</code> to log information at the
+ * following logging levels:
+ * <p>
+ * <table border="1" cellpadding="5"
+ * summary="Describes logging performed by ClassServer at different
+ * logging levels">
+ * <caption halign="center" valign="top"><b><code>
+ * com.sun.jini.tool.ClassServer</code></b></caption>
+ *
+ * <tr> <th scope="col">Level</th> <th scope="col">Description</th> </tr>
+ * <tr>
+ * <td>{@link Level#SEVERE SEVERE}</td>
+ * <td>failure to accept an incoming connection</td>
+ * </tr>
+ * <tr>
+ * <td>{@link Level#WARNING WARNING}</td>
+ * <td>failure to read the contents of a requested file,
+ * failure to find the message resource bundle, failure while
+ * executing the <code>-stop</code> option
+ * </td>
+ * </tr>
+ * <tr>
+ * <td>{@link Level#INFO INFO}</td>
+ * <td>server startup and termination</td>
+ * </tr>
+ * <tr>
+ * <td>{@link Level#CONFIG CONFIG}</td>
+ * <td>the JAR files being used for <code>-trees</code></td>
+ * </tr>
+ * <tr>
+ * <td>{@link Levels#HANDLED HANDLED}</td>
+ * <td>failure reading an HTTP request or writing a response</td>
+ * </tr>
+ * <tr>
+ * <td>{@link Level#FINE FINE}</td>
+ * <td>bad HTTP requests, HTTP requests for nonexistent files</td>
+ * </tr>
+ * <tr>
+ * <td>{@link Level#FINER FINER}</td>
+ * <td>good HTTP requests</td>
+ * </tr>
+ * </table>
+ *
+ * <p>
+ * <a name="running"><h3>Examples for running ClassServer</h3></a>
+ * <p>
+ *
+ * This server can be run directly from the
+ * {@linkplain #main command line}
+ * or as a nonactivatable service under the
+ * {@linkplain com.sun.jini.start Service Starter}.
+ * <p>
+ * An example of running directly from the command line is:
+ * <blockquote><pre>
+ * % java -jar <var><b>install_dir</b></var>/lib/classserver.jar \
+ *        -port 8081 -dir <var><b>install_dir</b></var>/lib-dl -verbose
+ * </pre></blockquote>
+ * where <var><b>install_dir</b></var>
+ * is the directory where the Apache River release is installed.
+ * This command places the class server on the (non-default) port
+ * 8081, which  serves out the files under the (non-default) directory
+ * <var><b>install_dir</b></var>/lib-dl. The <code>-verbose</code> option
+ * also causes download attempts to be logged.
+ * <p>
+ * An example of running under the Service Starter is:
+ * <blockquote><pre>
+ * % java -Djava.security.policy=<var><b>start_policy</b></var> \
+ *        -jar <var><b>install_dir</b></var>/lib/start.jar \
+ *        <a href="#config">httpd.config</a>
+ * </pre></blockquote>
+ * <p>
+ * where <var><b>start_policy</b></var> is the name of a security
+ * policy file (not provided), and <code>httpd.config</code> is the
+ * following configuration file:
+ * <a name="config"></a>
+ * <blockquote><pre>
+ * import com.sun.jini.start.NonActivatableServiceDescriptor;
+ * import com.sun.jini.start.ServiceDescriptor;
+ *
+ * com.sun.jini.start {
+ *
+ *   serviceDescriptors = new ServiceDescriptor[]{
+ *     new NonActivatableServiceDescriptor(
+ *       "",
+ *       "<var><b>httpd_policy</b></var>",
+ *       "<var><b>install_dir</b></var>/lib/classserver.jar",
+ *       "com.sun.jini.tool.ClassServer",
+ *       new String[]{"-port", "8081", "-dir", "<var><b>install_dir</b></var>/lib-dl", "-verbose"})
+ *     };
+ * }
+ * </pre></blockquote>
+ * where <var><b>httpd_policy</b></var> is the name of a security
+ * policy file (not provided).
+ *
+ * @author Sun Microsystems, Inc.
+ */
+public class ClassServer extends Thread
+{
+    /**
+     * Default HTTP port
+     */
+    private static int DEFAULT_PORT = 8080;
+    /**
+     * Default directory to serve files from on non-Windows OS
+     */
+    private static String DEFAULT_DIR = "/vob/jive/lib-dl";
+    /**
+     * Default directory to serve files from on Windows
+     */
+    private static String DEFAULT_WIN_DIR = "J:";
+    private static Logger logger =
+        Logger.getLogger( "com.sun.jini.tool.ClassServer" );
+
+    /**
+     * Server socket to accept connections on
+     */
+    private ServerSocket server;
+    /**
+     * Directories to serve from
+     */
+    private String[] dirs;
+    /**
+     * Map from String (JAR root) to JarFile[] (JAR class path)
+     */
+    private Map map;
+    /**
+     * Verbosity flag
+     */
+    private boolean verbose;
+    /**
+     * Stoppable flag
+     */
+    private boolean stoppable;
+    /**
+     * Read permission on dir and all subdirs, for each dir in dirs
+     */
+    private FilePermission[] perms;
+    /**
+     * Life cycle control
+     */
+    private LifeCycle lifeCycle;
+
+    /**
+     * Construct a server that does not support network shutdown.
+     * Use the {@link #start start} method to run it.
+     *
+     * @param port    the port to use
+     * @param dirlist the list of directories to serve files from, with entries
+     *                separated by the {@linkplain File#pathSeparatorChar path-separator
+     *                character}
+     * @param trees   <code>true</code> if files within JAR files should be
+     *                served up
+     * @param verbose <code>true</code> if downloads should be logged
+     * @throws IOException          if the server socket cannot be created
+     * @throws NullPointerException if <code>dir</code> is <code>null</code>
+     */
+    public ClassServer( int port,
+                        String dirlist,
+                        boolean trees,
+                        boolean verbose )
+        throws IOException
+    {
+        init( port, dirlist, trees, verbose, false, null );
+    }
+
+    /**
+     * Construct a server.  Use the {@link #start start} method to run it.
+     *
+     * @param port      the port to use
+     * @param dirlist   the list of directories to serve files from, with entries
+     *                  separated by the {@linkplain File#pathSeparatorChar path-separator
+     *                  character}
+     * @param trees     <code>true</code> if files within JAR files should be
+     *                  served up
+     * @param verbose   <code>true</code> if downloads should be logged
+     * @param stoppable <code>true</code> if network shutdown from the
+     *                  local host should be supported
+     * @throws IOException          if the server socket cannot be created
+     * @throws NullPointerException if <code>dir</code> is <code>null</code>
+     */
+    public ClassServer( int port,
+                        String dirlist,
+                        boolean trees,
+                        boolean verbose,
+                        boolean stoppable )
+        throws IOException
+    {
+        init( port, dirlist, trees, verbose, stoppable, null );
+    }
+
+    /**
+     * Do the real work of the constructor.
+     */
+    private void init( int port,
+                       String dirlist,
+                       boolean trees,
+                       boolean verbose,
+                       boolean stoppable,
+                       LifeCycle lifeCycle )
+        throws IOException
+    {
+        StringTokenizer st = new StringTokenizer( dirlist, File.pathSeparator );
+        dirs = new String[st.countTokens()];
+        perms = new FilePermission[dirs.length];
+        for( int i = 0; st.hasMoreTokens(); i++ )
+        {
+            String dir = st.nextToken();
+            if( !dir.endsWith( File.separator ) )
+            {
+                dir = dir + File.separatorChar;
+            }
+            dirs[ i ] = dir;
+            perms[ i ] = new FilePermission( dir + '-', "read" );
+        }
+        this.verbose = verbose;
+        this.stoppable = stoppable;
+        this.lifeCycle = lifeCycle;
+        server = new ServerSocket( port );
+        if( !trees )
+        {
+            return;
+        }
+        map = new HashMap();
+        Map jfmap = new HashMap();
+        for( int i = 0; i < dirs.length; i++ )
+        {
+            String[] files = new File( dirs[ i ] ).list();
+            if( files == null )
+            {
+                continue;
+            }
+            for( int j = 0; j < files.length; j++ )
+            {
+                String jar = files[ j ];
+                if( !jar.endsWith( ".jar" ) && !jar.endsWith( ".zip" ) )
+                {
+                    continue;
+                }
+                String name = jar.substring( 0, jar.length() - 4 );
+                if( map.containsKey( name ) )
+                {
+                    continue;
+                }
+                List jflist = new ArrayList( 1 );
+                addJar( jar, jflist, jfmap );
+                map.put( name, jflist.toArray( new JarFile[jflist.size()] ) );
+            }
+        }
+    }
+
+    /**
+     * Construct a running server, accepting the same command line options
+     * supported by {@link #main main}, except for the <code>-stop</code>
+     * option.
+     *
+     * @param args      command line options
+     * @param lifeCycle life cycle control object, or <code>null</code>
+     * @throws IOException              if the server socket cannot be created
+     * @throws IllegalArgumentException if a command line option is not
+     *                                  understood
+     * @throws NullPointerException     if <code>args</code> or any element
+     *                                  of <code>args</code> is <code>null</code>
+     */
+    public ClassServer( String[] args, LifeCycle lifeCycle ) throws IOException
+    {
+        int port = DEFAULT_PORT;
+        String dirlist = DEFAULT_DIR;
+        if( File.separatorChar == '\\' )
+        {
+            dirlist = DEFAULT_WIN_DIR;
+        }
+        boolean trees = false;
+        boolean verbose = false;
+        boolean stoppable = false;
+        for( int i = 0; i < args.length; i++ )
+        {
+            String arg = args[ i ];
+            if( arg.equals( "-port" ) )
+            {
+                i++;
+                port = Integer.parseInt( args[ i ] );
+            }
+            else if( arg.equals( "-dir" ) || arg.equals( "-dirs" ) )
+            {
+                i++;
+                dirlist = args[ i ];
+            }
+            else if( arg.equals( "-verbose" ) )
+            {
+                verbose = true;
+            }
+            else if( arg.equals( "-trees" ) )
+            {
+                trees = true;
+            }
+            else if( arg.equals( "-stoppable" ) )
+            {
+                stoppable = true;
+            }
+            else
+            {
+                throw new IllegalArgumentException( arg );
+            }
+        }
+        init( port, dirlist, trees, verbose, stoppable, lifeCycle );
+        start();
+    }
+
+    /**
+     * Add transitive Class-Path JARs to jflist.
+     */
+    private void addJar( String jar, List jflist, Map jfmap )
+        throws IOException
+    {
+        JarFile jf = (JarFile) jfmap.get( jar );
+        if( jf != null )
+        {
+            if( jflist.contains( jf ) )
+            {
+                return;
+            }
+        }
+        else
+        {
+            for( int i = 0; i < dirs.length; i++ )
+            {
+                File f = new File( dirs[ i ] + jar ).getCanonicalFile();
+                if( f.exists() )
+                {
+                    jf = new JarFile( f );
+                    jfmap.put( jar, jf );
+                    if( verbose )
+                    {
+                        print( "classserver.jar", f.getPath() );
+                    }
+                    logger.config( f.getPath() );
+                    break;
+                }
+            }
+            if( jf == null )
+            {
+                if( verbose )
+                {
+                    print( "classserver.notfound", jar );
+                }
+                logger.log( Level.CONFIG, "{0} not found", jar );
+                return;
+            }
+        }
+        jflist.add( jf );
+        Manifest man = jf.getManifest();
+        if( man == null )
+        {
+            return;
+        }
+        Attributes attrs = man.getMainAttributes();
+        if( attrs == null )
+        {
+            return;
+        }
+        String val = attrs.getValue( Attributes.Name.CLASS_PATH );
+        if( val == null )
+        {
+            return;
+        }
+        for( StringTokenizer st = new StringTokenizer( val );
+             st.hasMoreTokens(); )
+        {
+            String elt = st.nextToken();
+            String path = decode( elt );
+            if( path == null )
+            {
+                if( verbose )
+                {
+                    print( "classserver.notfound", elt );
+                }
+                logger.log( Level.CONFIG, "{0} not found", elt );
+            }
+            if( '/' != File.separatorChar )
+            {
+                path = path.replace( '/', File.separatorChar );
+            }
+            addJar( path, jflist, jfmap );
+        }
+    }
+
+    /**
+     * Just keep looping, spawning a new thread for each incoming request.
+     */
+    public void run()
+    {
+        logger.log( Level.INFO, "ClassServer started [{0}, port {1}]",
+                    new Object[]{ Arrays.asList( dirs ),
+                                  Integer.toString( getPort() ) } );
+        try
+        {
+            while( true )
+            {
+                new Task( server.accept() ).start();
+            }
+        }
+        catch( IOException e )
+        {
+            synchronized( this )
+            {
+                if( verbose )
+                {
+                    e.printStackTrace();
+                }
+                if( !server.isClosed() )
+                {
+                    logger.log( Level.SEVERE, "accepting connection", e );
+                }
+                terminate();
+            }
+        }
+    }
+
+    /**
+     * Close the server socket, causing the thread to terminate.
+     */
+    public synchronized void terminate()
+    {
+        verbose = false;
+        try
+        {
+            server.close();
+        }
+        catch( IOException e )
+        {
+        }
+        if( lifeCycle != null )
+        {
+            lifeCycle.unregister( this );
+        }
+        logger.log( Level.INFO, "ClassServer terminated [port {0}]",
+                    Integer.toString( getPort() ) );
+    }
+
+    /**
+     * Returns the port on which this server is listening.
+     */
+    public int getPort()
+    {
+        return server.getLocalPort();
+    }
+
+    /**
+     * Read up to CRLF, return false if EOF
+     */
+    private static boolean readLine( InputStream in, StringBuffer buf )
+        throws IOException
+    {
+        while( true )
+        {
+            int c = in.read();
+            if( c < 0 )
+            {
+                return buf.length() > 0;
+            }
+            if( c == '\r' )
+            {
+                in.mark( 1 );
+                c = in.read();
+                if( c != '\n' )
+                {
+                    in.reset();
+                }
+                return true;
+            }
+            if( c == '\n' )
+            {
+                return true;
+            }
+            buf.append( (char) c );
+        }
+    }
+
+    /**
+     * Parse % HEX HEX from s starting at i
+     */
+    private static char decode( String s, int i )
+    {
+        return (char) Integer.parseInt( s.substring( i + 1, i + 3 ), 16 );
+    }
+
+    /**
+     * Decode escape sequences
+     */
+    private static String decode( String path )
+    {
+        try
+        {
+            for( int i = path.indexOf( '%' );
+                 i >= 0;
+                 i = path.indexOf( '%', i + 1 ) )
+            {
+                char c = decode( path, i );
+                int n = 3;
+                if( ( c & 0x80 ) != 0 )
+                {
+                    switch( c >> 4 )
+                    {
+                    case 0xC:
+                    case 0xD:
+                        n = 6;
+                        c = (char) ( ( ( c & 0x1F ) << 6 ) |
+                                     ( decode( path, i + 3 ) & 0x3F ) );
+                        break;
+                    case 0xE:
+                        n = 9;
+                        c = (char) ( ( ( c & 0x0f ) << 12 ) |
+                                     ( ( decode( path, i + 3 ) & 0x3F ) << 6 ) |
+                                     ( decode( path, i + 6 ) & 0x3F ) );
+                        break;
+                    default:
+                        return null;
+                    }
+                }
+                path = path.substring( 0, i ) + c + path.substring( i + n );
+            }
+        }
+        catch( Exception e )
+        {
+            return null;
+        }
+        return path;
+    }
+
+    /**
+     * Read the request/response and return the initial line.
+     */
+    private static String getInput( Socket sock, boolean isRequest )
+        throws IOException
+    {
+        BufferedInputStream in =
+            new BufferedInputStream( sock.getInputStream(), 256 );
+        StringBuffer buf = new StringBuffer( 80 );
+        do
+        {
+            if( !readLine( in, buf ) )
+            {
+                return null;
+            }
+        }
+        while( isRequest && buf.length() == 0 );
+        String initial = buf.toString();
+        do
+        {
+            buf.setLength( 0 );
+        }
+        while( readLine( in, buf ) && buf.length() > 0 );
+        return initial;
+    }
+
+    /**
+     * Simple daemon task thread
+     */
+    private class Task extends Thread
+    {
+        /**
+         * Socket for the incoming request
+         */
+        private Socket sock;
+
+        /**
+         * Simple constructor
+         */
+        public Task( Socket sock )
+        {
+            this.sock = sock;
+            setDaemon( true );
+        }
+
+        /**
+         * Read specified number of bytes and always close the stream.
+         */
+        private byte[] getBytes( InputStream in, long length )
+            throws IOException
+        {
+            DataInputStream din = new DataInputStream( in );
+            byte[] bytes = new byte[(int) length];
+            try
+            {
+                din.readFully( bytes );
+            }
+            finally
+            {
+                din.close();
+            }
+            return bytes;
+        }
+
+        /**
+         * Canonicalize the path
+         */
+        private String canon( String path )
+        {
+            if( path.regionMatches( true, 0, "http://", 0, 7 ) )
+            {
+                int i = path.indexOf( '/', 7 );
+                if( i < 0 )
+                {
+                    path = "/";
+                }
+                else
+                {
+                    path = path.substring( i );
+                }
+            }
+            path = decode( path );
+            if( path == null || path.length() == 0 || path.charAt( 0 ) != '/' )
+            {
+                return null;
+            }
+            return path.substring( 1 );
+        }
+
+        /**
+         * Return the bytes of the requested file, or null if not found.
+         */
+        private byte[] getBytes( String path ) throws IOException
+        {
+            if( map != null )
+            {
+                int i = path.indexOf( '/' );
+                if( i > 0 )
+                {
+                    JarFile[] jfs = (JarFile[]) map.get( path.substring( 0, i ) );
+                    if( jfs != null )
+                    {
+                        String jpath = path.substring( i + 1 );
+                        for( i = 0; i < jfs.length; i++ )
+                        {
+                            JarEntry je = jfs[ i ].getJarEntry( jpath );
+                            if( je != null )
+                            {
+                                return getBytes( jfs[ i ].getInputStream( je ),
+                                                 je.getSize() );
+                            }
+                        }
+                    }
+                }
+            }
+            if( '/' != File.separatorChar )
+            {
+                path = path.replace( '/', File.separatorChar );
+            }
+            for( int i = 0; i < dirs.length; i++ )
+            {
+                File f = new File( dirs[ i ] + path );
+                if( perms[ i ].implies( new FilePermission( f.getPath(), "read" ) ) )
+                {
+                    try
+                    {
+                        return getBytes( new FileInputStream( f ), f.length() );
+                    }
+                    catch( FileNotFoundException e )
+                    {
+                    }
+                }
+            }
+            return null;
+        }
+
+        /**
+         * Process the request
+         */
+        public void run()
+        {
+            try
+            {
+                DataOutputStream out =
+                    new DataOutputStream( sock.getOutputStream() );
+                String req;
+                try
+                {
+                    req = getInput( sock, true );
+                }
+                catch( Exception e )
+                {
+                    if( verbose )
+                    {
+                        print( "classserver.inputerror",
+                               new String[]{ sock.getInetAddress().getHostName(),
+                                             Integer.toString( sock.getPort() ) } );
+                        e.printStackTrace();
+                    }
+                    logger.log( Levels.HANDLED, "reading request", e );
+                    return;
+                }
+                if( req == null )
+                {
+                    return;
+                }
+                if( req.startsWith( "SHUTDOWN *" ) )
+                {
+                    if( verbose )
+                    {
+                        print( "classserver.shutdown",
+                               new String[]{ sock.getInetAddress().getHostName(),
+                                             Integer.toString( sock.getPort() ) } );
+                    }
+                    boolean ok = stoppable;
+                    try
+                    {
+                        new ServerSocket( 0, 1, sock.getInetAddress() );
+                    }
+                    catch( IOException e )
+                    {
+                        ok = false;
+                    }
+                    if( !ok )
+                    {
+                        out.writeBytes( "HTTP/1.0 403 Forbidden\r\n\r\n" );
+                        out.flush();
+                        return;
+                    }
+                    try
+                    {
+                        out.writeBytes( "HTTP/1.0 200 OK\r\n\r\n" );
+                        out.flush();
+                    }
+                    catch( Exception e )
+                    {
+                        if( verbose )
+                        {
+                            e.printStackTrace();
+                        }
+                        logger.log( Levels.HANDLED, "writing response", e );
+                    }
+                    terminate();
+                    return;
+                }
+                String[] args = null;
+                if( verbose || logger.isLoggable( Level.FINE ) )
+                {
+                    args = new String[]{ req,
+                                         sock.getInetAddress().getHostName(),
+                                         Integer.toString( sock.getPort() ) };
+                }
+                boolean get = req.startsWith( "GET " );
+                if( !get && !req.startsWith( "HEAD " ) )
+                {
+                    if( verbose )
+                    {
+                        print( "classserver.badrequest", args );
+                    }
+                    logger.log( Level.FINE,
+                                "bad request \"{0}\" from {1}:{2}", args );
+                    out.writeBytes( "HTTP/1.0 400 Bad Request\r\n\r\n" );
+                    out.flush();
+                    return;
+                }
+                String path = req.substring( get ? 4 : 5 );
+                int i = path.indexOf( ' ' );
+                if( i > 0 )
+                {
+                    path = path.substring( 0, i );
+                }
+                path = canon( path );
+                if( path == null )
+                {
+                    if( verbose )
+                    {
+                        print( "classserver.badrequest", args );
+                    }
+                    logger.log( Level.FINE,
+                                "bad request \"{0}\" from {1}:{2}", args );
+                    out.writeBytes( "HTTP/1.0 400 Bad Request\r\n\r\n" );
+                    out.flush();
+                    return;
+                }
+                if( args != null )
+                {
+                    args[ 0 ] = path;
+                }
+                if( verbose )
+                {
+                    print( get ? "classserver.request" : "classserver.probe",
+                           args );
+                }
+                logger.log( Level.FINER,
+                            get ?
+                            "{0} requested from {1}:{2}" :
+                            "{0} probed from {1}:{2}",
+                            args );
+                byte[] bytes;
+                try
+                {
+                    bytes = getBytes( path );
+                }
+                catch( Exception e )
+                {
+                    if( verbose )
+                    {
+                        e.printStackTrace();
+                    }
+                    logger.log( Level.WARNING, "getting bytes", e );
+                    out.writeBytes( "HTTP/1.0 500 Internal Error\r\n\r\n" );
+                    out.flush();
+                    return;
+                }
+                if( bytes == null )
+                {
+                    if( verbose )
+                    {
+                        print( "classserver.notfound", path );
+                    }
+                    logger.log( Level.FINE, "{0} not found", path );
+                    out.writeBytes( "HTTP/1.0 404 Not Found\r\n\r\n" );
+                    out.flush();
+                    return;
+                }
+                out.writeBytes( "HTTP/1.0 200 OK\r\n" );
+                out.writeBytes( "Content-Length: " + bytes.length + "\r\n" );
+                out.writeBytes( "Content-Type: application/java\r\n\r\n" );
+                if( get )
+                {
+                    out.write( bytes );
+                }
+                out.flush();
+                if( get )
+                {
+                    fileDownloaded( path, sock.getInetAddress() );
+                }
+            }
+            catch( Exception e )
+            {
+                if( verbose )
+                {
+                    e.printStackTrace();
+                }
+                logger.log( Levels.HANDLED, "writing response", e );
+            }
+            finally
+            {
+                try
+                {
+                    sock.close();
+                }
+                catch( IOException e )
+                {
+                }
+            }
+        }
+    }
+
+    private static ResourceBundle resources;
+    private static boolean resinit = false;
+
+    private static synchronized String getString( String key )
+    {
+        if( !resinit )
+        {
+            resinit = true;
+            try
+            {
+                resources = ResourceBundle.getBundle( "com.sun.jini.tool.resources.classserver" );
+            }
+            catch( MissingResourceException e )
+            {
+                logger.log( Level.WARNING, "missing resource bundle {0}",
+                            "com.sun.jini.tool.resources.classserver" );
+            }
+        }
+        if( resources != null )
+        {
+            try
+            {
+                return resources.getString( key );
+            }
+            catch( MissingResourceException e )
+            {
+            }
+        }
+        return null;
+    }
+
+    private static void print( String key, String val )
+    {
+        String fmt = getString( key );
+        if( fmt == null )
+        {
+            fmt = "no text found: \"" + key + "\" {0}";
+        }
+        System.out.println( MessageFormat.format( fmt, new String[]{ val } ) );
+    }
+
+    private static void print( String key, String[] vals )
+    {
+        String fmt = getString( key );
+        if( fmt == null )
+        {
+            fmt = "no text found: \"" + key + "\" {0} {1} {2}";
+        }
+        System.out.println( MessageFormat.format( fmt, vals ) );
+    }
+
+    /**
+     * This method provides a way for subclasses to be notified when a
+     * file has been completely downloaded.
+     *
+     * @param fp The path to the file that was downloaded.
+     */
+    protected void fileDownloaded( String fp, InetAddress addr )
+    {
+    }
+
+    /**
+     * Command line interface for creating an HTTP server.
+     * The command line options are:
+     * <pre>
+     * [-port <var>port</var>] [-dir <var>dirlist</var>] [-dirs <var>dirlist</var>] [-stoppable] [-verbose] [-trees]
+     * </pre>
+     * The default port is 8080; the default can be overridden with
+     * the <code>-port</code> option.  The default directory on Windows is
+     * <code>J:</code> and the default on other systems is
+     * <code>/vob/jive/lib-dl</code>; the default can be overridden with the
+     * <code>-dir</code> or <code>-dirs</code> option, providing one or more
+     * directories separated by the {@linkplain File#pathSeparatorChar
+     * path-separator character}. All files under these directories (including
+     * all subdirectories) are served up via HTTP.  If the pathname of a file
+     * is <var>path</var> relative to one of the top-level directories, then
+     * the file can be downloaded using the URL
+     * <pre>
+     * http://<var>host</var>:<var>port</var>/<var>path</var>
+     * </pre>
+     * If a relative <var>path</var> matches a file under more than one
+     * top-level directory, the file under the first top-level directory
+     * with a match is used. No caching of directory contents or file contents
+     * is performed. <p>
+     *
+     * If the <code>-stoppable</code> option is given, the HTTP server can be
+     * shut down with a custom HTTP <code>SHUTDOWN</code> request originating
+     * from the local host. The command line options for stopping an existing
+     * HTTP server are:
+     * <pre>
+     * [-port <var>port</var>] -stop
+     * </pre>
+     * <p>
+     *
+     * If the <code>-verbose</code> option is given, then all attempts to
+     * download files are output. <p>
+     *
+     * The <code>-trees</code> option can be used to serve up individual files
+     * stored within JAR and zip files in addition to the files that are
+     * served up as described above. If the option is used, the server finds
+     * all JAR and zip files in the top-level directories (not in
+     * subdirectories).  If the name of the JAR or zip file is
+     * <var>name</var><code>.jar</code> or <var>name</var><code>.zip</code>,
+     * then any individual file named <var>file</var> within it (or within the
+     * JAR or zip files referenced transitively in <code>Class-Path</code>
+     * manifest attributes), can be downloaded using a URL of the form:
+     * <pre>
+     * http://<var>host</var>:<var>port</var>/<var>name</var>/<var>file</var>
+     * </pre>
+     * If multiple top-level directories have JAR or zip files with the same
+     * <var>name</var>, the file under the first top-level directory with a
+     * match is used. If a <code>Class-Path</code> element matches a file under
+     * more than one top-level directory, the file under the first top-level
+     * directory with a match is used. When this option is used, an open file
+     * descriptor and cached information is held for each JAR or zip file, for
+     * the life of the process.
+     */
+    public static void main( String[] args )
+    {
+        int port = DEFAULT_PORT;
+        String dirlist = DEFAULT_DIR;
+        if( File.separatorChar == '\\' )
+        {
+            dirlist = DEFAULT_WIN_DIR;
+        }
+        boolean trees = false;
+        boolean verbose = false;
+        boolean stoppable = false;
+        boolean stop = false;
+        for( int i = 0; i < args.length; i++ )
+        {
+            String arg = args[ i ];
+            if( arg.equals( "-port" ) )
+            {
+                i++;
+                port = Integer.parseInt( args[ i ] );
+            }
+            else if( arg.equals( "-dir" ) || arg.equals( "-dirs" ) )
+            {
+                i++;
+                dirlist = args[ i ];
+            }
+            else if( arg.equals( "-verbose" ) )
+            {
+                verbose = true;
+            }
+            else if( arg.equals( "-trees" ) )
+            {
+                trees = true;
+            }
+            else if( arg.equals( "-stoppable" ) )
+            {
+                stoppable = true;
+            }
+            else if( arg.equals( "-stop" ) )
+            {
+                stop = true;
+            }
+            else
+            {
+                print( "classserver.usage", (String) null );
+                return;
+            }
+        }
+        try
+        {
+            if( stop )
+            {
+                Socket sock = new Socket( InetAddress.getLocalHost(), port );
+                try
+                {
+                    DataOutputStream out =
+                        new DataOutputStream( sock.getOutputStream() );
+                    out.writeBytes( "SHUTDOWN *\r\n\r\n" );
+                    out.flush();
+                    String status = getInput( sock, false );
+                    if( status != null && status.startsWith( "HTTP/" ) )
+                    {
+                        status = status.substring( status.indexOf( ' ' ) + 1 );
+                        if( status.startsWith( "403 " ) )
+                        {
+                            print( "classserver.forbidden", status );
+                        }
+                        else if( !status.startsWith( "200 " ) &&
+                                 status.indexOf( ' ' ) == 3 )
+                        {
+                            print( "classserver.status",
+                                   new String[]{ status.substring( 0, 3 ),
+                                                 status.substring( 4 ) } );
+                        }
+                    }
+                }
+                finally
+                {
+                    try
+                    {
+                        sock.close();
+                    }
+                    catch( IOException e )
+                    {
+                    }
+                }
+            }
+            else
+            {
+                new ClassServer( port, dirlist, trees, verbose,
+                                 stoppable ).start();
+            }
+        }
+        catch( IOException e )
+        {
+            logger.log( Level.WARNING, "requesting shutdown", e);
+	}
+    }
+}

Added: incubator/river/jtsk/skunk/niclas1/tools/src/main/java/com/sun/jini/tool/ComputeDigest.java
URL: http://svn.apache.org/viewvc/incubator/river/jtsk/skunk/niclas1/tools/src/main/java/com/sun/jini/tool/ComputeDigest.java?rev=724973&view=auto
==============================================================================
--- incubator/river/jtsk/skunk/niclas1/tools/src/main/java/com/sun/jini/tool/ComputeDigest.java (added)
+++ incubator/river/jtsk/skunk/niclas1/tools/src/main/java/com/sun/jini/tool/ComputeDigest.java Tue Dec  9 21:09:41 2008
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 com.sun.jini.tool;
+
+import java.io.FileNotFoundException;
+import java.net.URL;
+import java.security.NoSuchAlgorithmException;
+import java.text.MessageFormat;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import net.jini.url.httpmd.HttpmdUtil;
+
+/**
+ * Prints the message digest for the contents of a URL. This utility is run
+ * from the {@linkplain #main command line}. <p>
+ *
+ * An example command line (shown with lines wrapped for readability) is:
+ *
+ * <blockquote>
+ * <pre>
+ * java -jar <var><b>install_dir</b></var>/lib/computedigest.jar \
+ *      <var><b>install_dir</b></var>/lib/reggie.jar \
+ *      SHA-1
+ * </pre>
+ * </blockquote>
+ *
+ * where <var><b>install_dir</b></var> is the directory where the Apache River release
+ * is installed. This command prints out the message digest for the
+ * <code>reggie.jar</code> JAR file, using the <code>SHA-1</code> algorithm.
+ *
+ * @author Sun Microsystems, Inc.
+ * @since 2.0
+ */
+public class ComputeDigest
+{
+    private static ResourceBundle resources;
+    private static boolean resinit = false;
+
+    private ComputeDigest()
+    {
+    }
+
+    /**
+     * Prints the message digest for the contents of a URL. The command
+     * line arguments are:
+     * <pre>
+     * <var><b>url</b></var> [ <var><b>algorithm</b></var> ]
+     * </pre>
+     * The first argument specifies the URL, which is parsed in the context
+     * of a <code>file:</code> URL. The second argument, if present,
+     * specifies the message digest algorithm, which defaults to
+     * <code>SHA-1</code>.
+     */
+    public static void main( String[] args )
+    {
+        if( args.length < 1 || args.length > 2 )
+        {
+            print( "computedigest.usage", null );
+            System.exit( 1 );
+        }
+        String algorithm = args.length > 1 ? args[ 1 ] : "SHA-1";
+        try
+        {
+            URL url = new URL( new URL( "file:" ), args[ 0 ] );
+            System.out.println( HttpmdUtil.computeDigest( url, algorithm ) );
+            return;
+        }
+        catch( FileNotFoundException e )
+        {
+            print( "computedigest.notfound", args[ 0 ] );
+        }
+        catch( NoSuchAlgorithmException e )
+        {
+            print( "computedigest.badalg", algorithm );
+        }
+        catch( Throwable t )
+        {
+            t.printStackTrace();
+        }
+        System.exit( 1 );
+    }
+
+    private static synchronized String getString( String key )
+    {
+        try
+        {
+            if( !resinit )
+            {
+                resources = ResourceBundle.getBundle(
+                    "com.sun.jini.tool.resources.computedigest" );
+                resinit = true;
+            }
+            return resources.getString( key );
+        }
+        catch( MissingResourceException e )
+        {
+            e.printStackTrace();
+            System.err.println( "Unable to find a required resource." );
+            System.exit( 1 );
+            return null;
+        }
+    }
+
+    private static void print( String key, String val )
+    {
+        String fmt = getString( key );
+        if( fmt == null )
+        {
+            fmt = "no text found: \"" + key + "\" {0}";
+        }
+        System.err.println( MessageFormat.format( fmt, new String[]{ val } ) );
+    }
+}

Added: incubator/river/jtsk/skunk/niclas1/tools/src/main/java/com/sun/jini/tool/ComputeHttpmdCodebase.java
URL: http://svn.apache.org/viewvc/incubator/river/jtsk/skunk/niclas1/tools/src/main/java/com/sun/jini/tool/ComputeHttpmdCodebase.java?rev=724973&view=auto
==============================================================================
--- incubator/river/jtsk/skunk/niclas1/tools/src/main/java/com/sun/jini/tool/ComputeHttpmdCodebase.java (added)
+++ incubator/river/jtsk/skunk/niclas1/tools/src/main/java/com/sun/jini/tool/ComputeHttpmdCodebase.java Tue Dec  9 21:09:41 2008
@@ -0,0 +1,216 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 com.sun.jini.tool;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.MessageFormat;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import net.jini.url.httpmd.Handler;
+import net.jini.url.httpmd.HttpmdUtil;
+
+/**
+ * Computes the message digests for a codebase with HTTPMD URLs. This utility
+ * is run from the {@linkplain #main command line}. <p>
+ * A description of HTTPMD URLs can be found in the {@link net.jini.url.httpmd}
+ * package and its {@link net.jini.url.httpmd.Handler} class.<p>
+ *
+ * An example command line (shown with lines wrapped for readability) is:
+ *
+ * <blockquote>
+ * <pre>
+ * java -jar <var><b>install_dir</b></var>/lib/computehttpmdcodebase.jar \
+ *      <var><b>install_dir</b></var>/lib-dl \
+ *      "httpmd://<var><b>your_host</b></var>:<var><b>http_port</b></var>/sdm-dl.jar;md5=0"
+ * </pre>
+ * </blockquote>
+ *
+ * where <var><b>install_dir</b></var> is the directory where the Apache River release
+ * is installed, <var><b>your_host</b></var> is the host where the HTTP server
+ * for the <code>sdm-dl.jar</code> JAR file will be running, and
+ * <var><b>http_port</b></var> is the port for that server. This command prints
+ * out the download codebase for use by a client that uses the {@link
+ * net.jini.lookup.ServiceDiscoveryManager}, using an HTTPMD URL to guarantee
+ * integrity for the classes in the <code>sdm-dl.jar</code> JAR file. The
+ * message digest will be computed using the <code>md5</code> algorithm, and
+ * the <code>0</code> will be replaced by the computed digest.
+ *
+ * @author Sun Microsystems, Inc.
+ * @since 2.0
+ */
+public class ComputeHttpmdCodebase
+{
+    private static ResourceBundle resources;
+    private static boolean resinit = false;
+
+    private ComputeHttpmdCodebase()
+    {
+    }
+
+    /**
+     * Computes the message digests for a codebase made up of HTTPMD URLs.
+     * The command line arguments are:
+     * <pre>
+     * <var><b>source-directory</b></var> <var><b>url</b></var>...
+     * </pre>
+     * The first argument is the filename or URL of the directory containing
+     * the source files for the HTTPMD URLs. The remaining arguments specify
+     * the HTTPMD URLs that make up the codebase. The digest values specified
+     * in the HTTPMD URLs will be ignored (zeroes are typically used). The
+     * path portion of each HTTPMD URL, without the message digest parameters,
+     * names a source file relative to the source directory; the message
+     * digest for that source file is computed and replaces the digest value
+     * in the HTTPMD URL. The resulting HTTPMD URLs are printed, separated by
+     * spaces.
+     * <p>
+     * Do not use a directory on a remote filesystem, or a directory URL, if
+     * the underlying network access protocol does not provide adequate data
+     * integrity or authentication of the remote host.
+     */
+    public static void main( String[] args )
+    {
+        if( args.length < 2 )
+        {
+            print( "computecodebase.usage", (String) null );
+            System.exit( 1 );
+        }
+        Handler handler = new Handler();
+        StringBuffer codebase = new StringBuffer();
+        try
+        {
+            boolean isURL;
+            URL base;
+            try
+            {
+                base = new URL( args[ 0 ].endsWith( "/" ) ?
+                                args[ 0 ] : args[ 0 ] + "/" );
+                isURL = true;
+            }
+            catch( MalformedURLException e )
+            {
+                File sourceDirectory = new File( args[ 0 ] );
+                if( !sourceDirectory.isDirectory() )
+                {
+                    print( "computecodebase.notdir", args[ 0 ] );
+                    System.exit( 1 );
+                }
+                base = sourceDirectory.toURI().toURL();
+                isURL = false;
+            }
+            for( int i = 1; i < args.length; i++ )
+            {
+                String spec = args[ i ];
+                if( !"httpmd:".regionMatches( true, 0, spec, 0, 7 ) )
+                {
+                    print( "computecodebase.nonhttpmd", spec );
+                    System.exit( 1 );
+                }
+                URL url;
+                try
+                {
+                    url = new URL( null, spec, handler );
+                }
+                catch( MalformedURLException e )
+                {
+                    print( "computecodebase.badurl",
+                           new String[]{ spec, e.getLocalizedMessage() } );
+                    System.exit( 1 );
+                    return; // not reached, make compiler happy
+                }
+                String path = url.getPath();
+                int paramIndex = path.lastIndexOf( ';' );
+                int equalsIndex = path.indexOf( '=', paramIndex );
+                int commentIndex = path.indexOf( ',', equalsIndex );
+                String algorithm = path.substring( paramIndex + 1, equalsIndex );
+                URL source =
+                    new URL( base,
+                             path.substring( path.startsWith( "/" ) ? 1 : 0,
+                                             path.indexOf( ';' ) ) );
+                String digest;
+                try
+                {
+                    digest = HttpmdUtil.computeDigest( source, algorithm );
+                }
+                catch( FileNotFoundException e )
+                {
+                    print( "computecodebase.notfound",
+                           isURL ? source.toExternalForm() : source.getPath() );
+                    System.exit( 1 );
+                    return; // not reached, make compiler happy
+                }
+                URL result = new URL(
+                    url,
+                    path.substring( 0, equalsIndex + 1 ) + digest +
+                    ( commentIndex < 0 ? "" : path.substring( commentIndex ) ) +
+                    ( url.getQuery() == null ? "" : '?' + url.getQuery() ) +
+                    ( url.getRef() == null ? "" : '#' + url.getRef() ) );
+                if( codebase.length() > 0 )
+                {
+                    codebase.append( ' ' );
+                }
+                codebase.append( result );
+            }
+        }
+        catch( Throwable t )
+        {
+            t.printStackTrace();
+            System.exit( 1 );
+        }
+        System.out.println( codebase );
+    }
+
+    private static synchronized String getString( String key )
+    {
+        try
+        {
+            if( !resinit )
+            {
+                resources = ResourceBundle.getBundle(
+                    "com.sun.jini.tool.resources.computecodebase" );
+                resinit = true;
+            }
+            return resources.getString( key );
+        }
+        catch( MissingResourceException e )
+        {
+            e.printStackTrace();
+            System.err.println( "Unable to find a required resource." );
+            System.exit( 1 );
+            return null;
+        }
+    }
+
+    private static void print( String key, String val )
+    {
+        print( key, new String[]{ val } );
+    }
+
+    private static void print( String key, String[] vals )
+    {
+        String fmt = getString( key );
+        if( fmt == null )
+        {
+            fmt = "no text found: \"" + key + "\" {0}";
+        }
+        System.err.println( MessageFormat.format( fmt, vals ) );
+    }
+}

Added: incubator/river/jtsk/skunk/niclas1/tools/src/main/java/com/sun/jini/tool/DebugDynamicPolicyProvider.java
URL: http://svn.apache.org/viewvc/incubator/river/jtsk/skunk/niclas1/tools/src/main/java/com/sun/jini/tool/DebugDynamicPolicyProvider.java?rev=724973&view=auto
==============================================================================
--- incubator/river/jtsk/skunk/niclas1/tools/src/main/java/com/sun/jini/tool/DebugDynamicPolicyProvider.java (added)
+++ incubator/river/jtsk/skunk/niclas1/tools/src/main/java/com/sun/jini/tool/DebugDynamicPolicyProvider.java Tue Dec  9 21:09:41 2008
@@ -0,0 +1,539 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 com.sun.jini.tool;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.Policy;
+import java.security.Principal;
+import java.security.PrivilegedAction;
+import java.security.ProtectionDomain;
+import java.security.Security;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import net.jini.security.policy.DynamicPolicy;
+import net.jini.security.policy.DynamicPolicyProvider;
+import net.jini.security.policy.PolicyInitializationException;
+
+/**
+ * Defines a {@link DynamicPolicy} that logs information about missing
+ * permissions, and optionally grants all permissions, which is <b>FOR
+ * DEBUGGING ONLY</b>. Do not use this security policy provider to grant
+ * all permissions in a production environment. <p>
+ *
+ * This class is intended to simplify the process of deciding what security
+ * permissions to grant to run an application.  While it is generally
+ * acceptable to grant all permissions to local, trusted code, downloaded
+ * code should typically be granted the least permission possible. <p>
+ *
+ * The usual approach to choosing which permissions to grant is to start by
+ * running the application with a security policy file that grants all
+ * permissions to local, trusted code.  When the application fails with an
+ * exception message that identifies a missing permission, add that
+ * permission to the security policy file, and repeat the process. Although
+ * straight forward, this process can be time consuming if the application
+ * requires many permission grants. <p>
+ *
+ * Another approach is to set the value of the
+ * <code>"java.security.debug"</code> system property to
+ * <code>"access,failure"</code>, which produces debugging output that
+ * describes permission grants and failures. Unfortunately, this approach
+ * produces voluminous output, making it difficult to determine which
+ * permission grants are needed. <p>
+ *
+ * This security policy provider permits another, hopefully more
+ * convenient, approach. When this class is specified as the security
+ * policy provider, and granting all permissions is enabled, it uses the
+ * standard dynamic security policy to determine what permissions are
+ * granted. If a permission is not granted by the standard policy, though,
+ * then rather than denying permission, this class logs the missing
+ * permission in the form required by the security policy file, and grants
+ * the permission, allowing the program to continue. In this way,
+ * developers can determine the complete set of security permissions
+ * required by the application. <p>
+ *
+ * Note that the information printed by this security policy provider may
+ * not be in the form you wish to use in your policy file. In particular,
+ * using system property substitutions and <code>KeyStore</code> aliases
+ * may produce a more portable file than one containing the exact entries
+ * logged. Note, too, that the information printed for
+ * <code>signedBy</code> fields specifies the principal name for
+ * <code>X.509</code> certificates, rather than the <code>KeyStore</code>
+ * alias, which is not a valid security policy file format. <p>
+ *
+ * Using this security policy provider without granting all permissions is
+ * also useful since it prints information about security exceptions that
+ * were caught, but that might have an affect on program behavior. <p>
+ *
+ * This class uses uses the {@link Logger} named
+ * <code>net.jini.security.policy</code> to log information at the following
+ * levels: <ul>
+ *
+ * <li> {@link Level#WARNING WARNING} - Permissions that were needed but not
+ * granted by the policy file.
+ *
+ * <li> {@link Level#FINE FINE} - Also include stack traces.
+ *
+ * <li> {@link Level#FINER FINER} - All permissions granted, with stack traces
+ * for ones not granted by the policy file, and dynamic grants.
+ *
+ * <li> {@link Level#FINEST FINEST} - All permissions granted, with all stack
+ * traces, and dynamic grants. </ul>
+ *
+ * To use this security policy provider, do the following: <ul>
+ *
+ * <li> Copy the <code>jsk-policy.jar</code> file from the <code>lib-ext</code>
+ * subdirectory of the Apache River release
+ * installation to the extensions directory of the Java(TM) 2 SDK (or JRE)
+ * installation, and copy the <code>jsk-debug-policy.jar</code> file
+ * from the <code>lib</code> subdirectory of the Apache River release installation to
+ * the extensions directory of the Java 2 SDK (or JRE) installation.
+ *
+ * <li> Specify this class as the security policy provider. Create a copy of
+ * the file <code>jre/lib/security/security/java.security</code>, modify the
+ * file to contain the line:
+ *
+ * <blockquote>
+ * <pre>
+ * policy.provider=com.sun.jini.tool.DebugDynamicPolicyProvider
+ * </pre>
+ * </blockquote>
+ *
+ * and then specify this new file as the value of the
+ * <code>java.security.properties</code> system property.
+ *
+ * <li> Specify whether all permissions should be granted by setting the
+ * <code>com.sun.jini.tool.DebugDynamicPolicyProvider.grantAll</code> security
+ * property to <code>true</code> by adding the following line to the security
+ * properties file:
+ *
+ * <blockquote>
+ * <pre>
+ * com.sun.jini.tool.DebugDynamicPolicyProvider.grantAll=true
+ * </pre>
+ * </blockquote> </ul> <p>
+ *
+ * Granting all permissions is disabled by default. <p>
+ *
+ * Make sure to specify a security manager, either by setting the
+ * <code>java.security.manager</code> system property, or putting the following
+ * code in the main method of the application:
+ *
+ * <blockquote>
+ * <pre>
+ * if (System.getSecurityManager() == null) {
+ *     System.setSecurityManager(new SecurityManager());
+ * }
+ * </pre>
+ * </blockquote>
+ *
+ * <p>This provider can be used in conjunction with the provider
+ * <code>com.sun.jini.start.AggregatePolicyProvider</code> by setting the
+ * <code>com.sun.jini.start.AggregatePolicyProvider.mainPolicyClass</code>
+ * system property to the fully qualified name of this class.  If this
+ * provider is used with the <code>AggregatePolicyProvider</code>, then the
+ * JAR file <code>jsk-debug-policy.jar</code> needs to be in the
+ * application's class path, and this class needs to be granted all
+ * permissions.
+ *
+ * @author Sun Microsystems, Inc.
+ */
+public class DebugDynamicPolicyProvider extends DynamicPolicyProvider
+{
+
+    /* Logger to use */
+    private static final Logger logger =
+        Logger.getLogger( "net.jini.security.policy" );
+
+    /* If true, always grant permission */
+    private static boolean grantAll =
+        ( (Boolean) AccessController.doPrivileged(
+            new PrivilegedAction()
+            {
+                public Object run()
+                {
+                    return Boolean.valueOf(
+                        Security.getProperty(
+                            "com.sun.jini.tool." +
+                            "DebugDynamicPolicyProvider.grantAll" ) );
+                }
+            } ) ).booleanValue();
+
+    /* Cache of permission requests already made */
+    private static final Set requests = new HashSet();
+
+    /**
+     * The empty codesource.
+     */
+    private static final CodeSource emptyCS =
+        new CodeSource( null, (Certificate[]) null );
+
+    /**
+     * Creates an instance of this class that wraps a default underlying
+     * policy, as specified by {@link
+     * DynamicPolicyProvider#DynamicPolicyProvider() DynamicPolicyProvider()}.
+     *
+     * @throws PolicyInitializationException if unable to construct the base
+     *                                       policy
+     * @throws SecurityException             if there is a security manager and the calling
+     *                                       context does not have adequate permissions to read the <code>
+     *                                       net.jini.security.policy.DynamicPolicyProvider.basePolicyClass
+     *                                       </code> security property, or if the calling context does not
+     *                                       have adequate permissions to access the base policy class
+     */
+    public DebugDynamicPolicyProvider() throws PolicyInitializationException
+    {
+    }
+
+    /**
+     * Creates an instance of this class that wraps around the given
+     * non-<code>null</code> base policy object.
+     *
+     * @param basePolicy base policy object containing information about
+     *                   non-dynamic grants
+     * @throws NullPointerException if <code>basePolicy</code> is
+     *                              <code>null</code>
+     */
+    public DebugDynamicPolicyProvider( Policy basePolicy )
+    {
+        super( basePolicy );
+    }
+
+    /**
+     * Log calls.
+     */
+    public void grant( Class cl,
+                       Principal[] principals,
+                       Permission[] permissions )
+    {
+        try
+        {
+            super.grant( cl, principals, permissions );
+            if( permissions == null
+                || permissions.length == 0
+                || !logger.isLoggable( Level.FINER ) )
+            {
+                return;
+            }
+            Request req = new Request( cl, principals, permissions );
+            if( cl == null )
+            {
+                logger.log( Level.FINER,
+                            "Granting permissions for all classes:\n{0}",
+                            req.toString() );
+            }
+            else
+            {
+                logger.log( Level.FINER,
+                            "Granting permissions for {0}:\n{1}",
+                            new Object[]{ cl, req.toString() } );
+            }
+        }
+        catch( SecurityException e )
+        {
+            if( logger.isLoggable( Level.FINE ) )
+            {
+                logger.log( Level.FINE, "Granting permissions failed", e );
+            }
+            throw e;
+        }
+    }
+
+    /**
+     * Always returns true, but logs unique requests
+     */
+    public boolean implies( ProtectionDomain pd, Permission perm )
+    {
+        boolean implies = super.implies( pd, perm );
+        boolean result = implies ? true : grantAll;
+        if( !( logger.isLoggable( Level.FINER )
+               || ( !implies && logger.isLoggable( Level.WARNING ) ) ) )
+        {
+            return result;
+        }
+        Request request = new Request( pd, perm );
+        synchronized( requests )
+        {
+            if( requests.contains( request ) )
+            {
+                return result;
+            }
+            requests.add( request );
+        }
+        String stackTrace = null;
+        if( logger.isLoggable( Level.FINEST )
+            || ( !implies && logger.isLoggable( Level.FINE ) ) )
+        {
+            StringWriter sw = new StringWriter();
+            PrintWriter pw = new PrintWriter( sw );
+            new Exception( "Stack trace:" ).printStackTrace( pw );
+            pw.close();
+            stackTrace = sw.toString();
+        }
+        if( implies )
+        {
+            if( logger.isLoggable( Level.FINEST ) )
+            {
+                logger.log( Level.FINEST, "Permission granted:\n{0}\n{1}",
+                            new Object[]{ request.toString(), stackTrace } );
+            }
+            else
+            {
+                logger.log( Level.FINER, "Permission granted:\n{0}",
+                            request.toString() );
+            }
+        }
+        else if( logger.isLoggable( Level.FINE ) )
+        {
+            logger.log( Level.WARNING,
+                        ( grantAll
+                          ? "Permission not granted by base policy:\n{0}\n{1}"
+                          : "Permission not granted:\n{0}\n{1}" ),
+                        new Object[]{ request.toString(), stackTrace } );
+        }
+        else
+        {
+            logger.log( Level.WARNING,
+                        ( grantAll
+                          ? "Permission not granted by base policy:\n{0}"
+                          : "Permission not granted:\n{0}" ),
+                        request.toString() );
+        }
+        return result;
+    }
+
+    /**
+     * Returns the name of the certificate.
+     */
+    private static String getCertName( Certificate cert )
+    {
+        if( cert instanceof X509Certificate )
+        {
+            return ( (X509Certificate) cert ).getSubjectDN().getName();
+        }
+        else
+        {
+            return cert.toString();
+        }
+    }
+
+    /**
+     * Returns a quoted version of the argument, such that it would result in
+     * the argument if read from a file with the standard String syntax.
+     */
+    private static String quoteString( String s )
+    {
+        if( s == null )
+        {
+            return "";
+        }
+        int len = s.length();
+        StringBuffer buf = new StringBuffer( len + 2 );
+        buf.append( '"' );
+        for( int off = 0; off < len; )
+        {
+            int quote = s.indexOf( '"', off );
+            int slash = s.indexOf( '\\', off );
+            if( quote >= 0 && ( slash < 0 || slash > quote ) )
+            {
+                buf.append( s.substring( off, quote ) );
+                buf.append( "\\\"" );
+                off = quote + 1;
+            }
+            else if( slash >= 0 )
+            {
+                buf.append( s.substring( off, slash ) );
+                buf.append( "\\\\" );
+                off = slash + 1;
+            }
+            else
+            {
+                buf.append( s.substring( off ) );
+                break;
+            }
+        }
+        buf.append( '"' );
+        return buf.toString();
+    }
+
+    /* Keeps track of an individual permission request */
+    private static class Request
+    {
+        final CodeSource codeSource;
+        final Certificate[] certs;
+        final Principal[] principals;
+        final Permission[] perms;
+
+        Request( ProtectionDomain pd, Permission perm )
+        {
+            codeSource = pd.getCodeSource();
+            certs = codeSource == null ? null : codeSource.getCertificates();
+            principals = pd.getPrincipals();
+            this.perms = new Permission[]{ perm };
+        }
+
+        Request( final Class cl, Principal[] principals, Permission[] perms )
+        {
+            codeSource = ( cl == null ) ? emptyCS :
+                         (CodeSource) AccessController.doPrivileged(
+                             new PrivilegedAction()
+                             {
+                                 public Object run()
+                                 {
+                                     return cl.getProtectionDomain().getCodeSource();
+                                 }
+                             } );
+            certs = null;
+            this.principals = principals;
+            this.perms = perms;
+        }
+
+        public boolean equals( Object o )
+        {
+            if( o instanceof Request )
+            {
+                Request other = (Request) o;
+                return ( codeSource == null
+                         ? other.codeSource == null
+                         : equals( codeSource.getLocation(),
+                                   other.codeSource.getLocation() ) )
+                       && equals( principals, other.principals )
+                       && equals( perms, other.perms );
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+        private static boolean equals( Object x, Object y )
+        {
+            return ( x == null ) ? y == null : x.equals( y );
+        }
+
+        private static boolean equals( Object[] x, Object[] y )
+        {
+            if( x == null )
+            {
+                return y == null;
+            }
+            else if( y == null )
+            {
+                return false;
+            }
+            else
+            {
+                return Arrays.equals( x, y );
+            }
+        }
+
+        public int hashCode()
+        {
+            return hash( codeSource == null ? null : codeSource.getLocation() )
+                   ^ hash( certs ) ^ hash( principals ) ^ hash( perms );
+        }
+
+        private static int hash( Object obj )
+        {
+            return ( obj == null ) ? 0 : obj.hashCode();
+        }
+
+        private static int hash( Object[] array )
+        {
+            int result = 0;
+            if( array != null )
+            {
+                for( int i = array.length; --i >= 0; )
+                {
+                    result ^= hash( array[ i ] );
+                }
+            }
+            return result;
+        }
+
+        public String toString()
+        {
+            StringBuffer buf = new StringBuffer();
+            buf.append( "grant\n" );
+            if( codeSource == null )
+            {
+                buf.append( "    /* bootstrap codebase */\n" );
+            }
+            else
+            {
+                URL location = codeSource.getLocation();
+                if( location != null )
+                {
+                    buf.append( "    codeBase " );
+                    buf.append( quoteString( location.toString() ) );
+                    buf.append( '\n' );
+                }
+                if( certs != null )
+                {
+                    for( int i = 0; i < certs.length; i++ )
+                    {
+                        buf.append( "    signedby " );
+                        buf.append( quoteString( getCertName( certs[ i ] ) ) );
+                        buf.append( '\n' );
+                    }
+                }
+            }
+            if( principals != null )
+            {
+                for( int i = 0; i < principals.length; i++ )
+                {
+                    buf.append( "    principal " );
+                    buf.append( principals[ i ].getClass().getName() );
+                    buf.append( ' ' );
+                    buf.append( quoteString( principals[ i ].getName() ) );
+                    buf.append( '\n' );
+                }
+            }
+            buf.append( "{\n" );
+            for( int i = 0; i < perms.length; i++ )
+            {
+                Permission perm = perms[ i ];
+                buf.append( "    permission " );
+                buf.append( perm.getClass().getName() );
+                buf.append( '\n' );
+                buf.append( "        " );
+                buf.append( quoteString( perm.getName() ) );
+                String actions = perm.getActions();
+                if( actions != null && actions.length() != 0 )
+                {
+                    buf.append( ",\n" );
+                    buf.append( "        " );
+                    buf.append( quoteString( perm.getActions() ) );
+                }
+                buf.append( ";\n" );
+            }
+            buf.append( "};" );
+            return buf.toString();
+        }
+    }
+}



Mime
View raw message