package textbender.a.r.desk; // Copyright 2006-2007, Michael Allan. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Textbender Software"), to deal in the Textbender Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Textbender Software, and to permit persons to whom the Textbender Software is furnished to do so, subject to the following conditions: The preceding copyright notice and this permission notice shall be included in all copies or substantial portions of the Textbender Software. THE TEXTBENDER SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE TEXTBENDER SOFTWARE OR THE USE OR OTHER DEALINGS IN THE TEXTBENDER SOFTWARE. import java.io.*; import java.math.BigInteger; import java.net.*; import java.rmi.*; import java.rmi.registry.*; import java.rmi.server.*; import java.util.HashMap; import java.util.concurrent.atomic.AtomicReference; import textbender.g.hold.Hold; import textbender.g.io.FileX; import textbender.g.lang.*; import textbender.g.rmi.RegistryX; import textbender.g.util.logging.LoggerX; /** Service registry implementation. * The single instance of HostServiceRegistry1 is available via HostServiceRegistry1.i(). */ public @ThreadSafe final class HostServiceRegistry1 extends UnicastRemoteObject implements HostServiceRegistry { /** The single instance of HostServiceRegistry1, or null if there is none. * * @return instance, or null if none was created */ public static HostServiceRegistry1 i() { return instanceA.get(); } private static final AtomicReference instanceA = new AtomicReference(); private Remote remoteReferenceAsInitiallyBound; /** Creates the single instance of HostServiceRegistry1; * makes it available via {@linkplain #i() i}(); * and binds it in the RMI boot-registry. * * @throws NotBoundException if an RMI exception prevents binding * @throws RemoteException if the instance cannot be created or bound */ HostServiceRegistry1() throws NotBoundException, RemoteException { // Create. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if( !instanceA.compareAndSet( /*expect*/null, HostServiceRegistry1.this )) throw new IllegalStateException(); // Run.i().spool().add( new Hold() // { // public @ThreadSafe void release() { instanceA.set( null ); } // }); //// not necessary, only shuts down with VM // Bind. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - final Registry bootRegistry = RegistryX.ensureRegistry(); bootRegistry.rebind( HostServiceRegistry.class.getName(), HostServiceRegistry1.this ); remoteReferenceAsInitiallyBound = bootRegistry.lookup( HostServiceRegistry.class.getName() ); Run.i().spool().add( new Hold() { public @ThreadSafe void release() { try { Remote remoteReference = bootRegistry.lookup( HostServiceRegistry.class.getName() ); if( remoteReference.equals( remoteReferenceAsInitiallyBound )) { LocateRegistry.getRegistry().unbind( HostServiceRegistry.class.getName() ); } else LoggerX.i(getClass()).config( "skipping my unbind from the boot-registry, another run has bound itself there" ); } catch( Exception x ) { LoggerX.i(getClass()).log( LoggerX.WARNING, /*message*/"", x ); } // NotBoundException, RemoteException } }); } // ------------------------------------------------------------------------------------ /** Adds a new service to the registry. * Once added, the service cannot be removed. * * @param serviceInterface per {@linkplain HostServiceRegistry#getService(Class) getService}() * @param serviceInstance that actually implements the interface * * @throws IllegalStateException if a service was already added for that serviceInterface */ public void addService( Class serviceInterface, S serviceInstance ) { // LoggerX.i(getClass()).fine( "registering " + serviceInterface.getName() ); synchronized( HostServiceRegistry1.this ) { Remote existingService = serviceMap.put( /*key*/serviceInterface, /*value*/serviceInstance ); if( existingService == null ) return; serviceMap.put( /*key*/serviceInterface, /*value*/existingService ); // put it back } throw new IllegalStateException( "was already added: " + serviceInterface ); } // - H o s t - S e r v i c e - R e g i s t r y ---------------------------------------- public synchronized File allInOneJar() throws IOException // sync for atomic test/set { // if( allInOneJar != null && allInOneJar.isFile() ) return allInOneJar; ///// The isFile() test was intended to permit code update during development, by having new builds delete the old cached JAR file, so the procedure further below could recreate it from the modified codebase. But this has resulted in a corrupt file, probably because the classloader is caching parts of the resource. So the build script now writes update JARs, and we switch to the latest. See a/b/demo/Demo.pm if( allInOneJar != null ) { final File updateJar; // latest, per a/b/demo/Demo.pm { File a = new File( FileX.tempDirectory(), "textbender-all-in-one_update-A.jar" ); File b = new File( FileX.tempDirectory(), "textbender-all-in-one_update-B.jar" ); if( a.lastModified() > b.lastModified() ) updateJar = a; else updateJar = b; } if( updateJar.lastModified() > allInOneJar.lastModified() ) return updateJar; else return allInOneJar; // no recent updates, this is the normal situation } // else create // // final String resourcePath = "/textbender-all-in-one.jar"; // // final InputStream in = getClass().getResourceAsStream( resourcePath ); final String resourcePath = "textbender-all-in-one.jar"; // // final InputStream in = ClassLoader.getSystemResourceAsStream( resourcePath ); final InputStream in = getClass().getClassLoader().getResourceAsStream( resourcePath ); if( in == null ) throw new IOException( "resource unavailable: " + resourcePath ); try { allInOneJar = File.createTempFile( "textbender-all-in-one", ".jar" ); // allInOneJar = new File( FileX.tempDirectory(), resourcePath ); // per a/b/demo/Demo.pm allInOneJar.deleteOnExit(); // temporary file if( !allInOneJar.isFile() ) allInOneJar.setWritable( true, /*owner only*/false ); // allow overwrite by new builds, as a development convenience. Ibid. final OutputStream out = new BufferedOutputStream( new FileOutputStream( allInOneJar )); try { for( ;; ) { int b = in.read(); if( b == -1 ) break; out.write( b ); } } finally{ out.close(); } } finally{ in.close(); } return allInOneJar; } private File allInOneJar; // lazilly created public S getService( Class serviceInterface ) throws AccessException, java.rmi.UnknownHostException { try { InetAddress clientAddress = InetAddress.getByName( getClientHost() ); if( !InetAddress.getLocalHost().equals( clientAddress )) { throw new AccessException( "services restricted to local host" ); // per HostServiceRegistry.getService() } } catch( java.net.UnknownHostException xUH ) { throw new java.rmi.UnknownHostException( "services restricted to local host", xUH ); } // per HostServiceRegistry.getService() // not expected at this level catch( ServerNotActiveException xSNA ) {} // invoker in this VM, so grant access final Remote serviceInstance; synchronized( HostServiceRegistry1.this ) { serviceInstance = serviceMap.get( serviceInterface ); } return serviceInterface.cast( serviceInstance ); } public synchronized UniqueID nextUniqueID() // FIX to use java.rmi.server.UID { UniqueID id = new UniqueID( nextUniqueNumber.toByteArray() ); nextUniqueNumber = nextUniqueNumber.add( BigInteger.ONE ); return id; } private @ThreadRestricted("holds this") BigInteger nextUniqueNumber = BigInteger.valueOf( System.currentTimeMillis() ); // initially, making overlap unlikely even between separate runs of desk daemon public @Deprecated URL resolveJarURL() throws IOException // returns original http URL in 1.6, instead of cached file URL; no longer useful for sharing cache { final URL classFileURL = HostServiceRegistry1.class.getResource // in-jar URI of class file ( "HostServiceRegistry1.class" ); final JarURLConnection connection = (JarURLConnection)classFileURL.openConnection(); return connection.getJarFileURL(); // of the JAR containing the class file } //// P r i v a t e /////////////////////////////////////////////////////////////////////// /** Map of services, keyed by class. */ @ThreadRestricted("holds HostServiceRegistry1.this") private final HashMap,Remote> serviceMap = new HashMap,Remote>(); }