Merge branch 'reuse_remote_naming_contexts' into 'ibm-trunk'

Reuse remote naming contexts

See merge request !56
diff --git a/yoko-core/src/main/java/org/apache/yoko/orb/CosNaming/tnaming2/BindingIteratorImpl.java b/yoko-core/src/main/java/org/apache/yoko/orb/CosNaming/tnaming2/BindingIteratorImpl.java
index c71dc79..3d4322b 100644
--- a/yoko-core/src/main/java/org/apache/yoko/orb/CosNaming/tnaming2/BindingIteratorImpl.java
+++ b/yoko-core/src/main/java/org/apache/yoko/orb/CosNaming/tnaming2/BindingIteratorImpl.java
@@ -4,6 +4,7 @@
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
 
 import org.apache.yoko.orb.CosNaming.tnaming2.NamingContextImpl.BoundObject;
 import org.apache.yoko.orb.spi.naming.RemoteAccess;
@@ -22,6 +23,8 @@
     private static final long serialVersionUID = 1L;
 
     private static final class Core extends BindingIteratorPOA {
+        private static final AtomicLong NEXT_ID = new AtomicLong();
+        private final long instanceId = NEXT_ID.getAndIncrement();
         // the iterator use to access the bindings
         private final Iterator<BoundObject> iterator;
 
@@ -33,6 +36,11 @@
         public Core(Collection<BoundObject> boundObjects) {
             this.iterator = (new ArrayList<BoundObject>(boundObjects)).iterator();
         }
+
+        private byte[] getServantId() {
+            return ("BindingIterator#" + instanceId).getBytes();
+        }
+
         /**
          * Return the next object in the iteration sequence.
          * @param b The BindingHolder used to return the next item. If we've
@@ -91,7 +99,7 @@
         public POAServant(POA poa, Core core) throws Exception {
             this.poa = poa;
             this.core = core;
-            poa.activate_object(this);
+            poa.activate_object_with_id(core.getServantId(), this);
         }
 
         /**
diff --git a/yoko-core/src/main/java/org/apache/yoko/orb/CosNaming/tnaming2/NamingContextBase.java b/yoko-core/src/main/java/org/apache/yoko/orb/CosNaming/tnaming2/NamingContextBase.java
index fa92761..94de6ea 100644
--- a/yoko-core/src/main/java/org/apache/yoko/orb/CosNaming/tnaming2/NamingContextBase.java
+++ b/yoko-core/src/main/java/org/apache/yoko/orb/CosNaming/tnaming2/NamingContextBase.java
@@ -72,7 +72,7 @@
      *            must already be bound in the context tree.
      * @param obj The object to be bound.
      */
-    public void bind(NameComponent[] n, org.omg.CORBA.Object obj) 
+    public void bind(NameComponent[] n, org.omg.CORBA.Object obj)
             throws NotFound, CannotProceed, InvalidName, AlreadyBound {
         // perform various name validations
         validateName(n);
@@ -270,7 +270,7 @@
             NamingContext context = resolveContext(n[0]);
             NameComponent[] subName = extractSubName(n);
 
-            // now pass this along to the next context for the real bind operation.
+            // now pass this along to the next context for the real resolve operation.
             return context.resolve(subName);
         } else {
             NameComponent name = n[0];
@@ -280,9 +280,9 @@
                 // Object was not found
                 throw new NotFound(NotFoundReason.missing_node, n);
             }
-            if (obj instanceof Resolvable) { 
+            if (obj instanceof Resolvable) {
             	return ((Resolvable)obj).resolve();
-            } else { 
+            } else {
             	return obj;
             }
         }
@@ -578,7 +578,7 @@
             throw new NotFound(NotFoundReason.not_context, new NameComponent[]{name});
         }
 
-        // in theory, this is a naming context. Narrow it an return. Any
+        // in theory, this is a naming context. Narrow it and return. Any
         // errors just become a NotFound exception
         try {
             return NamingContextHelper.narrow(resolvedReference);
diff --git a/yoko-core/src/main/java/org/apache/yoko/orb/CosNaming/tnaming2/NamingContextImpl.java b/yoko-core/src/main/java/org/apache/yoko/orb/CosNaming/tnaming2/NamingContextImpl.java
index 0164b17..ff20584 100644
--- a/yoko-core/src/main/java/org/apache/yoko/orb/CosNaming/tnaming2/NamingContextImpl.java
+++ b/yoko-core/src/main/java/org/apache/yoko/orb/CosNaming/tnaming2/NamingContextImpl.java
@@ -1,9 +1,12 @@
 package org.apache.yoko.orb.CosNaming.tnaming2;
 
 import java.util.HashMap;
+import java.util.Map;
 import java.util.Objects;
+import java.util.concurrent.atomic.AtomicLong;
 
 import org.apache.yoko.orb.spi.naming.RemoteAccess;
+import org.apache.yoko.orb.util.UnmodifiableEnumMap;
 import org.omg.CORBA.INTERNAL;
 import org.omg.CORBA.LocalObject;
 import org.omg.CORBA.NO_PERMISSION;
@@ -25,24 +28,44 @@
 import org.omg.CosNaming.NamingContextPackage.NotFound;
 import org.omg.PortableServer.POA;
 import org.omg.PortableServer.Servant;
+import org.omg.PortableServer.POAPackage.ObjectNotActive;
 
 public final class NamingContextImpl extends LocalObject implements NamingContextExt, RemotableObject {
     private static final long serialVersionUID = 1L;
 
+    private static final class ServantCreationLock {
+    }
+
     private static final class Core extends NamingContextBase {
-        // the bindings maintained by this context
+        private static final AtomicLong NEXT_ID = new AtomicLong();
+
+        /** Unique number for this core */
+        private final long instanceId = NEXT_ID.getAndIncrement();
+
+        /** the unique ids for this context's servants (one per remote access level) */
+        @SuppressWarnings("serial")
+        private final Map<RemoteAccess, String> servantIds = new UnmodifiableEnumMap<RemoteAccess, String>(RemoteAccess.class) {
+            public String computeValueFor(RemoteAccess key) {
+                return "NamingContext#" + instanceId + "$" + key;
+            }
+        };
+
+        /** the bindings maintained by this context */
         private final HashMap<BindingKey, BoundObject> bindings = new HashMap<BindingKey, BoundObject>();
-        // the root context object
+
+        /** the root context object */
         private final org.omg.CORBA.Object rootContext;
 
+        private Core(org.omg.CORBA.Object rootContext) throws Exception {
+            this.rootContext = rootContext;
+        }
+
         /**
-         * Construct a TransientNamingContext subcontext.
-         * @param orb The orb this context is associated with.
-         * @param poa The POA the root context is activated under.
-         * @param root The root context.
+         * Get the servant id to use for this context with the specified remote
+         * access level
          */
-        Core(org.omg.CORBA.Object root) throws Exception {
-            rootContext = root;
+        private byte[] getServantId(RemoteAccess access) {
+            return servantIds.get(access).getBytes();
         }
 
         // abstract methods part of the interface contract that the
@@ -204,6 +227,11 @@
 
                 return (Objects.equals(name.id, otherKey.name.id) && Objects.equals(name.kind, otherKey.name.kind));
             }
+
+            @Override
+            public String toString() {
+                return "" + name;
+            }
         }
 
     }
@@ -224,11 +252,11 @@
         final NamingContextBase core;
         final POA poa;
 
-        protected POAServant(NamingContextImpl localContext, Core core, POA poa) throws Exception {
+        protected POAServant(NamingContextImpl localContext, Core core, POA poa, byte[] servantId) throws Exception {
             this.localContext = localContext;
             this.core = core;
             this.poa = poa;
-            poa.activate_object(this);
+            poa.activate_object_with_id(servantId, this);
         }
 
         abstract Servant convertLocalContextToRemoteContext(NamingContextImpl o) throws Exception;
@@ -268,9 +296,9 @@
 
         private static final class ReadOnly extends POAServant {
             ReadOnly(NamingContextImpl localContext, Core core, POA poa) throws Exception {
-                super(localContext, core, poa);
+                super(localContext, core, poa, core.getServantId(RemoteAccess.readOnly));
             }
-            
+
             private SystemException newSystemException() {
                 return new NO_PERMISSION();
             }
@@ -303,7 +331,7 @@
 
         private static final class ReadWrite extends POAServant {
             ReadWrite(NamingContextImpl localContext, Core core, POA poa) throws Exception {
-                super(localContext, core, poa);
+                super(localContext, core, poa, core.getServantId(RemoteAccess.readWrite));
             }
 
             @Override
@@ -370,10 +398,18 @@
             this.boundObject = boundObject;
             this.type = type;
         }
+
+        @Override
+        public String toString() {
+            return name + "->" + boundObject;
+        }
     }
 
     private final Core core;
 
+    /** lock for servant creation */
+    private final Object servantCreationLock = new ServantCreationLock();
+
     public NamingContextImpl() throws Exception {
         core = new Core(this);
     }
@@ -434,7 +470,18 @@
 
     @Override
     public Servant getServant(POA poa, RemoteAccess remoteAccess) throws Exception {
-        return POAServant.create(this, core, poa, remoteAccess);
+        byte[] sid = core.getServantId(remoteAccess);
+
+        // synchronize around creation to avoid a race
+        synchronized (servantCreationLock) {
+            // check whether the servant needs to be created
+            try {
+                return poa.id_to_servant(sid);
+            } catch (ObjectNotActive expected) {
+                // guaranteed to be the unique creator-thread for this servant
+                return POAServant.create(this, core, poa, remoteAccess);
+            }
+        }
     }
 
     @Override
diff --git a/yoko-core/src/main/java/org/apache/yoko/orb/spi/naming/NameServiceInitializer.java b/yoko-core/src/main/java/org/apache/yoko/orb/spi/naming/NameServiceInitializer.java
index a20b4aa..ef9049b 100644
--- a/yoko-core/src/main/java/org/apache/yoko/orb/spi/naming/NameServiceInitializer.java
+++ b/yoko-core/src/main/java/org/apache/yoko/orb/spi/naming/NameServiceInitializer.java
@@ -27,8 +27,8 @@
 public class NameServiceInitializer extends LocalObject implements ORBInitializer {
     /** The property name to use to initialize an ORB with this initializer. */
     public static final String NS_ORB_INIT_PROP = ORBInitializer.class.getName() + "Class." + NameServiceInitializer.class.getName();
-    /** 
-     * The name of this name service, as used with <code>corbaloc:</code> URLs 
+    /**
+     * The name of this name service, as used with <code>corbaloc:</code> URLs
      * and with calls to {@link ORB#resolve_initial_references(String)}.
      */
     public static final String SERVICE_NAME = "NameService";
@@ -36,15 +36,31 @@
     /**
      * The POA name this name service will use to activate contexts.
      * The name service will first try <code>rootPoa.find_POA()</code>
-     * to find the POA with this name. If that returns null, it will 
-     * call <code>rootPoa.create_POA()</code> to create the POA with 
+     * to find the POA with this name. If that returns null, it will
+     * call <code>rootPoa.create_POA()</code> to create the POA with
      * this name.
      */
     public static final String POA_NAME = "NameServicePOA";
 
-    /** 
+    /**
+     * The policies required for the NameService POA. Users providing
+     * the NameService POA must include these policies when creating
+     * the POA. If creation is to be left to this initializer, these
+     * policies will be used automatically.
+     * @param rootPOA the root POA for the ORB in use
+     * @return a new Policy array object, owned by the caller
+     */
+    public static final Policy[] createPOAPolicies(POA rootPOA) {
+        return new Policy[] {
+                rootPOA.create_lifespan_policy(LifespanPolicyValue.TRANSIENT),
+                rootPOA.create_id_assignment_policy(IdAssignmentPolicyValue.USER_ID),
+                rootPOA.create_servant_retention_policy(ServantRetentionPolicyValue.RETAIN)
+        };
+    }
+
+    /**
      * The ORB argument that specifies remote accessibility of this name service.
-     * The next argument must be one of these literal string values: 
+     * The next argument must be one of these literal string values:
      * <ul>
      *   <li><code>"</code>{@link #readOnly}<code>"</code></li>
      *   <li><code>"</code>{@link #readWrite}<code>"</code></li>
@@ -57,7 +73,7 @@
     private static final long serialVersionUID = 1L;
 
     private RemoteAccess remoteAccess = readWrite;
-    
+
     @Override
     public void pre_init(ORBInitInfo info) {
         try {
@@ -81,7 +97,7 @@
     @Override
     public void post_init(ORBInitInfo info) {
         try {
-            
+
             final POA rootPOA = (POA) info.resolve_initial_references("RootPOA");
             final NamingContextImpl local = (NamingContextImpl) info.resolve_initial_references("NameService");
             final String serviceName = getServiceName(info);
@@ -97,16 +113,16 @@
 
                     try {
                         rootPOA.the_POAManager().activate();
-                        
+
                         final POA nameServicePOA = findOrCreatePOA(rootPOA);
                         nameServicePOA.the_POAManager().activate();
 
                         final Servant nameServant = local.getServant(nameServicePOA, remoteAccess);
-                        
+
                         // return the context stub via the object holder
                         obj.value = nameServant._this_object();
                         // return true via the boolean holder
-                        // to tell the boot manager to re-use 
+                        // to tell the boot manager to re-use
                         // this result so we only get called once
                         add.value = true;
                     } catch (Exception e) {
@@ -118,11 +134,7 @@
                     try {
                         return rootPOA.find_POA(POA_NAME, true);
                     } catch (AdapterNonExistent e) {
-                        final Policy[] policies = {
-                                rootPOA.create_lifespan_policy(LifespanPolicyValue.TRANSIENT),
-                                rootPOA.create_id_assignment_policy(IdAssignmentPolicyValue.SYSTEM_ID),
-                                rootPOA.create_servant_retention_policy(ServantRetentionPolicyValue.RETAIN)
-                        };
+                        final Policy[] policies = createPOAPolicies(rootPOA);
                         return rootPOA.create_POA(POA_NAME, null, policies);
                     }
                 }
diff --git a/yoko-core/src/main/java/org/apache/yoko/orb/util/UnmodifiableEnumMap.java b/yoko-core/src/main/java/org/apache/yoko/orb/util/UnmodifiableEnumMap.java
new file mode 100644
index 0000000..f241a93
--- /dev/null
+++ b/yoko-core/src/main/java/org/apache/yoko/orb/util/UnmodifiableEnumMap.java
@@ -0,0 +1,45 @@
+package org.apache.yoko.orb.util;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.Map;
+import java.util.Set;
+
+public abstract class UnmodifiableEnumMap<K extends Enum<K>, V> extends EnumMap<K, V> {
+    private static final long serialVersionUID = 1L;
+
+    public UnmodifiableEnumMap(Class<K> keyType) {
+        super(keyType);
+        // initialise all values up front to avoid races later
+        for(K key : keyType.getEnumConstants())
+            super.put(key, computeValueFor(key));
+    }
+
+    protected abstract V computeValueFor(K key);
+
+    @Override
+    public final V remove(Object key) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public final V put(K key, V value) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public final void putAll(Map<? extends K, ? extends V> m) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public final Set<K> keySet() {
+        return Collections.unmodifiableSet(super.keySet());
+    }
+
+    @Override
+    public final Collection<V> values() {
+        return Collections.unmodifiableCollection(super.values());
+    }
+}
diff --git a/yoko-core/src/test/java/org/apache/yoko/AbstractOrbTestBase.java b/yoko-core/src/test/java/org/apache/yoko/AbstractOrbTestBase.java
index 801ce71..84a22f8 100644
--- a/yoko-core/src/test/java/org/apache/yoko/AbstractOrbTestBase.java
+++ b/yoko-core/src/test/java/org/apache/yoko/AbstractOrbTestBase.java
@@ -37,7 +37,7 @@
  * It also sets the java.endorsed.dirs property and launches the client process.
  *
  * Currently, the client waits for the server to create a file containing an IOR. This
- * is used to delay the client until the server has started. the setWaitFile(File) method 
+ * is used to delay the client until the server has started. the setWaitFile(File) method
  * can be used to set the name of this file, which varies in the ORB tests.
  */
 public class AbstractOrbTestBase extends TestCase {
@@ -45,15 +45,15 @@
     protected JavaProcess server, client;
     protected File waitForFile;
     int waitForFileTimeout = 10000;
-        
+
     public AbstractOrbTestBase() {
         super();
     }
-        
+
     public AbstractOrbTestBase(String name) {
         super(name);
     }
-        
+
     protected void setUp() throws Exception {
         super.setUp();
         processManager = new ProcessManager(Registry.REGISTRY_PORT);
@@ -84,15 +84,15 @@
             getWaitForFile().delete();
         }
     }
-    
+
     protected void runServerClientTest(Class<?> serverClass, Class<?> clientClass, String...commonArgs) throws Exception {
         runServerClientTest(serverClass.getName(), commonArgs, clientClass.getName(), commonArgs);
     }
-    
+
     protected void runServerClientTest(String serverClass, String clientClass) throws Exception {
         runServerClientTest(serverClass, new String[0], clientClass, new String[0]);
     }
-    protected void runServerClientTest(String serverClass, String[] serverArgs, 
+    protected void runServerClientTest(String serverClass, String[] serverArgs,
                                        String clientClass, String[] clientArgs) throws Exception {
         server.launch();
         Future<Void> serverFuture = server.invokeMainAsync(serverClass, serverArgs);
@@ -108,15 +108,15 @@
         }
         server.exit(0);
     }
-        
+
     public void setWaitForFile(String file) {
         this.waitForFile = new File(file);
     }
-        
+
     public File getWaitForFile() {
         return waitForFile;
     }
-        
+
     protected void waitForFile() {
         long timeBefore = System.currentTimeMillis();
         if(getWaitForFile() != null) {
@@ -127,7 +127,7 @@
                     }
                     Thread.sleep(50);
                     if(System.currentTimeMillis() > timeBefore + waitForFileTimeout) {
-                        fail("The file " + getWaitForFile() + 
+                        fail("The file " + getWaitForFile() +
                              " was not created within " + waitForFileTimeout + "ms");
                     }
                 }
@@ -137,5 +137,5 @@
                 getWaitForFile().deleteOnExit();
             }
         }
-    }           
+    }
 }
diff --git a/yoko-core/src/test/java/test/common/TestBase.java b/yoko-core/src/test/java/test/common/TestBase.java
index 5fd246f..3cabd35 100644
--- a/yoko-core/src/test/java/test/common/TestBase.java
+++ b/yoko-core/src/test/java/test/common/TestBase.java
@@ -49,7 +49,7 @@
         String nameString = context.to_string(name);
         out.println(nameString);
     }
-    
+
     protected static void readRef(BufferedReader reader, String[] refStrings) throws IOException {
         String line = reader.readLine();
         if (line == null) {
@@ -65,7 +65,8 @@
                 throw new RuntimeException(sw.toString());
             }
         }
-        refStrings[0] = reader.readLine();
-        refStrings[1] = reader.readLine();
+        refStrings[0] = reader.readLine(); // IOR
+        refStrings[1] = reader.readLine(); // name
+        System.out.println(refStrings[1] +  " = "+ refStrings[0]);
     }
 }
diff --git a/yoko-core/src/test/java/test/tnaming/Client.java b/yoko-core/src/test/java/test/tnaming/Client.java
index 51b3cb2..c9f879f 100644
--- a/yoko-core/src/test/java/test/tnaming/Client.java
+++ b/yoko-core/src/test/java/test/tnaming/Client.java
@@ -75,6 +75,7 @@
         //
         // Get "test" objects
         //
+        System.out.println("Started ORB, getting test object IORs from file");
         try (BufferedReader file = openFileReader(refFile)) {
             String[] refStrings = new String[2];
             readRef(file, refStrings);
@@ -117,6 +118,7 @@
     }
 
     void run() throws Exception {
+        System.out.println("Running naming client tests");
         switch (accessibility) {
             case READ_ONLY :
                 testReadOnly();
@@ -137,16 +139,23 @@
     }
 
     private void testBoundReferences() throws UserException {
+        System.out.println("Testing bound reference 1");
         Util.assertTestIsBound(test1, rootNamingContext, name1);
+        System.out.println("Testing bound reference 2");
         Util.assertTestIsBound(test2, rootNamingContext, name2);
+        System.out.println("Testing bound reference 3");
         Util.assertTestIsBound(test3, rootNamingContext, name3);
+        System.out.println("Testing bound reference 1 via corbaname");
         Util.assertCorbanameIsBound(test1, orb, "corbaname:rir:/NameService#"+name1);
+        System.out.println("Testing bound references complete.");
     }
 
     private void testIterators() throws Exception {
+        System.out.println("Testing iterators: narrowing context");
         NamingContextExt nc = NamingContextExtHelper.narrow(rootNamingContext.resolve(Util.ITERATOR_TEST_CONTEXT_PATH));
         // check the behaviour of the binding iterators
         for (int listSize = 0; listSize <= Util.EXPECTED_NAMES.size() + 1; listSize++) {
+            System.out.println("Testing iterators: list size " + listSize);
             final BindingListHolder blh = new BindingListHolder();
             final BindingIteratorHolder bih = new BindingIteratorHolder();
             final BindingHolder bh = new BindingHolder();
@@ -176,6 +185,7 @@
             assertEquals(0, bh.value.binding_name.length);
             assertEquals(Util.EXPECTED_NAMES, actualNames);
         }
+        System.out.println("Testing iterators complete.");
     }
 
     private enum WriteMethod {
@@ -220,8 +230,11 @@
     }
 
     public void testObjectFactories() throws CannotProceed, InvalidName {
+        System.out.println("Testing object factories: resolvable");
         Util.assertFactoryIsBound(rootNamingContext, Server.RESOLVABLE_TEST);
+        System.out.println("Testing object factories: resolver");
         Util.assertFactoryIsBound(rootNamingContext, Server.RESOLVER_TEST);
+        System.out.println("Testing object factories complete.");
     }
 
     @Override
diff --git a/yoko-core/src/test/java/test/tnaming/ClientForReadOnlyNameService.java b/yoko-core/src/test/java/test/tnaming/ClientForReadOnlyNameService.java
index b234afd..4b4aefa 100644
--- a/yoko-core/src/test/java/test/tnaming/ClientForReadOnlyNameService.java
+++ b/yoko-core/src/test/java/test/tnaming/ClientForReadOnlyNameService.java
@@ -13,7 +13,6 @@
         props.put("org.omg.CORBA.ORBSingletonClass", "org.apache.yoko.orb.CORBA.ORBSingleton");
 
         try (Client client = new Client(READ_ONLY, refFile, props, "-ORBInitRef", "NameService=" + Util.NS_LOC )) {
-        	client.testObjectFactories();
             client.run();
         }
     }
diff --git a/yoko-core/src/test/java/test/tnaming/Server.java b/yoko-core/src/test/java/test/tnaming/Server.java
index 74a0894..232f30e 100644
--- a/yoko-core/src/test/java/test/tnaming/Server.java
+++ b/yoko-core/src/test/java/test/tnaming/Server.java
@@ -22,16 +22,17 @@
 
 package test.tnaming;
 
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
+import java.io.BufferedWriter;
 import java.io.FileWriter;
 import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.Properties;
 
 import org.apache.yoko.orb.spi.naming.Resolvable;
 import org.apache.yoko.orb.spi.naming.Resolver;
-import org.omg.CORBA.*;
+import org.omg.CORBA.ORB;
 import org.omg.CosNaming.NameComponent;
 import org.omg.CosNaming.NamingContext;
 import org.omg.CosNaming.NamingContextExt;
@@ -50,7 +51,7 @@
     private static final NameComponent TEST1 = new NameComponent("Test1", "");
     private static final NameComponent TEST2 = new NameComponent("Test2", "");
     private static final NameComponent TEST3 = new NameComponent("Test3", "");
-    
+
     public static final NameComponent RESOLVABLE_TEST = new NameComponent("ResolvableTest", "");
     public static final NameComponent RESOLVER_TEST = new NameComponent("ResolverTest", "");
 
@@ -90,7 +91,7 @@
                     return resolvable.resolve();
                 }
             };
-            
+
             System.out.println("created references");
         } catch (Throwable t) {
             System.err.println("Caught throwable: " + t);
@@ -101,7 +102,10 @@
 
     void run() throws Exception {
         System.out.println("server starting to run");
-        try (PrintWriter out = new PrintWriter(new FileWriter(refFile))) {
+
+        // use a temporary file to avoid the client picking up an empty file when debugging the server
+        Path tmp = Files.createTempFile(refFile, "");
+        try (PrintWriter out = new PrintWriter(new FileWriter(tmp.toFile()))) {
             System.out.println("server opened file for writing");
             try {
                 NamingContext nc1 = rootNamingContext.new_context();
@@ -118,7 +122,7 @@
                 Util.assertNameNotBound(rootNamingContext, TEST1);
 
                 rootNamingContext.bind(new NameComponent[]{TEST1}, test1);
-                
+
                 Util.assertTestIsBound("Test1", rootNamingContext, TEST1);
 
                 nc1.bind(new NameComponent[]{TEST2}, test2);
@@ -141,6 +145,7 @@
                 Util.assertNameNotBound(rootNamingContext, LEVEL1, LEVEL2, TEST3);
 
                 nc1.rebind_context(new NameComponent[]{LEVEL2}, nc2);
+                System.out.println("All contexts bound");
             } catch (Exception e) {
                 e.printStackTrace(out);
                 throw e;
@@ -151,25 +156,30 @@
             // condition between the client sending a request and the
             // server not being ready yet.
             //
+            System.out.println("Writing IORs to file");
             writeRef(orb, out, test1, rootNamingContext, new NameComponent[]{TEST1});
             writeRef(orb, out, test2, rootNamingContext, new NameComponent[]{LEVEL1, TEST2});
             writeRef(orb, out, test3, rootNamingContext, new NameComponent[]{LEVEL1, LEVEL2, TEST3});
             out.flush();
+            System.out.println("IORs written to file");
         } catch (java.io.IOException ex) {
             System.err.println("Can't write to `" + ex.getMessage() + "'");
             throw ex;
         }
 
+        // rename the completed file
+        Files.move(tmp, Paths.get(refFile));
+
         orb.run();
     }
 
-    public void bindObjectFactories() throws NotFound, CannotProceed, InvalidName, AlreadyBound { 
+    public void bindObjectFactories() throws NotFound, CannotProceed, InvalidName, AlreadyBound {
         rootNamingContext.bind(new NameComponent[]{RESOLVABLE_TEST}, resolvable);
         Util.assertFactoryIsBound(rootNamingContext, RESOLVABLE_TEST);
         rootNamingContext.bind(new NameComponent[]{RESOLVER_TEST}, resolver);
         Util.assertFactoryIsBound(rootNamingContext, RESOLVER_TEST);
     }
-    
+
     @Override
     public void close() throws Exception {
         try {