package nl.tudelft.simulation.dsol.naming.context; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; import java.io.IOException; import java.io.Serializable; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Path; import java.rmi.RemoteException; import java.util.Arrays; import java.util.HashSet; import java.util.Properties; import javax.naming.NameAlreadyBoundException; import javax.naming.NameNotFoundException; import javax.naming.NamingException; import javax.naming.NoInitialContextException; import javax.naming.NotContextException; import org.djutils.exceptions.Try; import org.junit.Test; import nl.tudelft.simulation.naming.context.ContextInterface; import nl.tudelft.simulation.naming.context.FileContext; import nl.tudelft.simulation.naming.context.InitialEventContext; import nl.tudelft.simulation.naming.context.JVMContext; import nl.tudelft.simulation.naming.context.RemoteContextInterface; /** * Tests the context. *

* Copyright (c) 2004-2020 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See * for project information https://simulation.tudelft.nl. The DSOL * project is distributed under a three-clause BSD-style license, which can be found at * * https://simulation.tudelft.nl/dsol/3.0/license.html. *

* @author Alexander Verbraeck */ public class ContextTest { /** * test for different types of context. * @throws NamingException on error * @throws RemoteException on RMI error * @throws IOException on creation of temporary file */ @Test public void testContext() throws NamingException, RemoteException, IOException { // test the ContextName object testContextName(); // test JVMContext ContextInterface jvmContext = new JVMContext(null, "root"); assertNull(jvmContext.getParent()); assertEquals("root", jvmContext.getAtomicName()); assertEquals(jvmContext, jvmContext.getRootContext()); testContext(jvmContext, jvmContext, true); // test FileContext directly Path path = Files.createTempFile("context-file", ".jpo"); File file = path.toFile(); file.deleteOnExit(); ContextInterface fileContext = new FileContext(file); testContext(fileContext, fileContext, true); // test InitialEventContext Properties properties = new Properties(); properties.put("java.naming.factory.initial", "nl.tudelft.simulation.naming.context.JVMContextFactory"); InitialEventContext eventContext = InitialEventContext.instantiate(properties); assertNull(eventContext.getParent()); assertEquals("", eventContext.getAtomicName()); testContext(eventContext, eventContext.getRootContext(), true); InitialEventContext eventContext2 = InitialEventContext.instantiate(properties); assertEquals(eventContext, eventContext2); assertEquals(eventContext.getRootContext(), eventContext2.getRootContext()); testContext(eventContext, eventContext.getRootContext(), true); eventContext.close(); testNullEventContext(eventContext); destroy(eventContext); // test FileContext via FileContextFactory Path fcPath = Files.createTempFile("factory-context-file", ".jpo"); File fcFile = fcPath.toFile(); fcFile.delete(); // should not exist yet -- only the name and handle. fcFile.deleteOnExit(); String fcName = fcPath.toUri().toURL().toString(); properties.put("java.naming.factory.initial", "nl.tudelft.simulation.naming.context.FileContextFactory"); properties.put("java.naming.provider.url", fcName); InitialEventContext factoryFileContext = InitialEventContext.instantiate(properties); testContext(factoryFileContext, factoryFileContext.getRootContext(), true); destroy(eventContext); // test RemoteContext properties.put("java.naming.factory.initial", "nl.tudelft.simulation.naming.context.RemoteContextFactory"); properties.put("java.naming.provider.url", "http://localhost:1099/remoteContext"); properties.put("wrapped.naming.factory.initial", "nl.tudelft.simulation.naming.context.JVMContextFactory"); InitialEventContext remoteContext = InitialEventContext.instantiate(properties); testContext(remoteContext, remoteContext.getRootContext(), false); destroy(eventContext); } /** * test basics for a Context. * @param context the context to test * @param expectedRootContext the expected root context (different from context in case of wrapping) * @param testSub boolean; temporary switch to not test Subtrees for Remote * @throws RemoteException on RMI error * @throws NamingException on error */ public void testContext(final ContextInterface context, final ContextInterface expectedRootContext, boolean testSub) throws NamingException, RemoteException { testContextBindUnbind(context, context, "", 0, expectedRootContext); testContextBindUnbind(context, context, "/", 0, expectedRootContext); ContextInterface subContext = context.createSubcontext("level1"); testContextBindUnbind(context, subContext, "level1/", 0, expectedRootContext); testContextBindUnbind(context, subContext, "/level1/", 0, expectedRootContext); testContextBindUnbind(subContext, subContext, "", 0, subContext); testContextBindUnbind(subContext, context, "/", 1, subContext); ContextInterface level21 = subContext.createSubcontext("/level1/level21"); ContextInterface level22 = subContext.createSubcontext("level22"); testContextBindUnbind(subContext, level21, "level21/", 0, subContext); testContextBindUnbind(context, level22, "/level1/level22/", 0, expectedRootContext); context.destroySubcontext("/level1/level21"); subContext.destroySubcontext("level22"); context.destroySubcontext("level1"); // TODO: make sure testSubContext also runs for RemoteContext if (testSub) testSubContext(context, expectedRootContext); } /** * test bind, rebind and unbind, get, exists, keySet, values, bindings for a Context. * @param context the context to test * @param subContext the subcontext in which objects will be inserted * @param absrel the name for absolute or relative reference * @param initialSize the expected initial size of the subContext * @param expectedRootContext the expected root context (different from context in case of wrapping) * @throws NamingException on error * @throws RemoteException on RMI error */ public void testContextBindUnbind(final ContextInterface context, final ContextInterface subContext, final String absrel, final int initialSize, final ContextInterface expectedRootContext) throws NamingException, RemoteException { // test empty root context assertEquals(initialSize == 0, subContext.isEmpty()); assertEquals(initialSize, subContext.keySet().size()); assertEquals(initialSize, subContext.values().size()); assertEquals(initialSize, subContext.bindings().size()); assertFalse(context.exists(absrel + "abc")); assertFalse(subContext.hasKey("abc")); assertFalse(subContext.hasObject("abc")); // bind an object context.bind(absrel + "key1", "value1"); assertFalse(subContext.isEmpty()); assertEquals(1 + initialSize, subContext.keySet().size()); assertEquals(1 + initialSize, subContext.values().size()); assertEquals(1 + initialSize, subContext.bindings().size()); assertTrue(subContext.hasKey("key1")); assertTrue(subContext.hasObject("value1")); assertEquals("value1", subContext.getObject("key1")); assertTrue(context.exists(absrel + "key1")); assertEquals("value1", context.get(absrel + "key1")); subContext.bindObject("key3", "value3"); assertTrue(subContext.hasKey("key3")); assertTrue(subContext.hasObject("value3")); // rebind an object context.rebind(absrel + "key1", "value1new"); assertFalse(subContext.isEmpty()); assertEquals(2 + initialSize, subContext.keySet().size()); assertEquals(2 + initialSize, subContext.values().size()); assertEquals(2 + initialSize, subContext.bindings().size()); assertTrue(subContext.hasKey("key1")); assertFalse(subContext.hasObject("value1")); assertTrue(subContext.hasObject("value1new")); assertTrue(context.exists(absrel + "key1")); assertEquals("value1new", context.get(absrel + "key1")); subContext.rebindObject("key3", "value3new"); assertTrue(subContext.hasKey("key3")); assertFalse(subContext.hasObject("value3")); assertTrue(subContext.hasObject("value3new")); // rename an object context.rename(absrel + "key1", absrel + "key1new"); assertFalse(subContext.isEmpty()); assertEquals(2 + initialSize, subContext.keySet().size()); assertEquals(2 + initialSize, subContext.values().size()); assertEquals(2 + initialSize, subContext.bindings().size()); assertFalse(subContext.hasKey("key1")); assertTrue(subContext.hasKey("key1new")); assertFalse(subContext.hasObject("value1")); assertTrue(subContext.hasObject("value1new")); assertFalse(context.exists(absrel + "key1")); assertTrue(context.exists(absrel + "key1new")); assertEquals("value1new", context.get(absrel + "key1new")); // unbind an object context.unbind(absrel + "key1new"); assertEquals(1 + initialSize == 0, subContext.isEmpty()); assertEquals(1 + initialSize, subContext.keySet().size()); assertEquals(1 + initialSize, subContext.values().size()); assertEquals(1 + initialSize, subContext.bindings().size()); assertFalse(subContext.hasKey("key1")); assertFalse(subContext.hasKey("key1new")); assertFalse(subContext.hasObject("value1")); assertFalse(subContext.hasObject("value1new")); assertFalse(context.exists(absrel + "key1")); assertFalse(context.exists(absrel + "key1new")); subContext.unbindObject("key3"); assertFalse(subContext.hasKey("key3")); assertFalse(subContext.hasObject("value3")); assertFalse(subContext.hasObject("value3new")); // unbind a non-existing object should not give a problem context.unbind(absrel + "key2"); // get an non-existing object Try.testFail(new Try.Execution() { @Override public void execute() throws Throwable { context.get(absrel + "key2"); } }); // test the current context and root context if (!(expectedRootContext instanceof RemoteContextInterface)) { assertEquals(expectedRootContext, context.get("")); if (context.getParent() == null) { assertEquals(expectedRootContext, context.get("/")); } } assertTrue(context.exists("")); assertTrue(context.exists("/")); // rebind a non-existing object context.rebind(absrel + "key2", "value2"); assertFalse(subContext.isEmpty()); assertEquals(1 + initialSize, subContext.keySet().size()); assertEquals(1 + initialSize, subContext.values().size()); assertEquals(1 + initialSize, subContext.bindings().size()); assertTrue(subContext.hasKey("key2")); assertTrue(subContext.hasObject("value2")); assertTrue(context.exists(absrel + "key2")); assertEquals("value2", context.get(absrel + "key2")); // test already bound exception Try.testFail(new Try.Execution() { @Override public void execute() throws Throwable { context.bind(absrel + "key2", "newobject"); } }, NameAlreadyBoundException.class); // create a name clash for rename context.bind(absrel + "key1", "value1"); Try.testFail(new Try.Execution() { @Override public void execute() throws Throwable { context.rename(absrel + "key1", absrel + "key2"); } }, NameAlreadyBoundException.class); // clean up context.unbind(absrel + "key1"); context.unbind(absrel + "key2"); // test binding of object with toString() TestObject val1 = new TestObject("val1"); subContext.bindObject(val1); assertFalse(subContext.isEmpty()); assertEquals(1 + initialSize, subContext.keySet().size()); assertEquals(1 + initialSize, subContext.values().size()); assertEquals(1 + initialSize, subContext.bindings().size()); assertTrue(subContext.hasKey("val1")); assertTrue(subContext.hasObject(val1)); assertTrue(subContext.exists("val1")); assertEquals(val1, subContext.get("val1")); subContext.unbind("val1"); // test object with forward slash in toString() TestObject val2 = new TestObject("a/b/c//"); subContext.bindObject(val2); assertFalse(subContext.isEmpty()); assertEquals(1 + initialSize, subContext.keySet().size()); assertEquals(1 + initialSize, subContext.values().size()); assertEquals(1 + initialSize, subContext.bindings().size()); assertTrue(subContext.hasKey("a#b#c##")); assertTrue(subContext.hasObject(val2)); assertTrue(subContext.exists("a#b#c##")); assertEquals(val2, subContext.get("a#b#c##")); subContext.unbind("a#b#c##"); assertEquals(initialSize == 0, subContext.isEmpty()); // Checking empty name, null name, slash for get for (String wrong : new String[] {"//", null}) { Try.testFail(new Try.Execution() { @Override public void execute() throws Throwable { subContext.get(wrong); } }, "test should have failed for name [" + wrong + "]"); } // Checking empty name, null name, slash for exists for (String wrong : new String[] {"//", null}) { Try.testFail(new Try.Execution() { @Override public void execute() throws Throwable { subContext.exists(wrong); } }, "test should have failed for name [" + wrong + "]"); } // Checking empty name, null name, slash for hasKey for (String wrong : new String[] {"", "//", "/", "/xyz/abc", null}) { Try.testFail(new Try.Execution() { @Override public void execute() throws Throwable { subContext.hasKey(wrong); } }, "test should have failed for name [" + wrong + "]"); } // Checking empty name, null name, slash for getObject for (String wrong : new String[] {"", "//", "/", null}) { Try.testFail(new Try.Execution() { @Override public void execute() throws Throwable { subContext.getObject(wrong); } }, "test should have failed for name [" + wrong + "]"); } // Checking empty name, null name, slash for bindObject for (String wrong : new String[] {"", "//", "/", "/xyz/abc", null}) { Try.testFail(new Try.Execution() { @Override public void execute() throws Throwable { subContext.bindObject(wrong, "newobject"); } }, "test should have failed for name [" + wrong + "]"); } // Checking empty name, null name, slash for rebindObject for (String wrong : new String[] {"", "//", "/", "/xyz/abc", null}) { Try.testFail(new Try.Execution() { @Override public void execute() throws Throwable { subContext.rebindObject(wrong, "newobject"); } }, "test should have failed for name [" + wrong + "]"); } // Checking empty name, null name, slash for unbindObject for (String wrong : new String[] {"", "//", "/", "/xyz/abc", null}) { Try.testFail(new Try.Execution() { @Override public void execute() throws Throwable { subContext.unbindObject(wrong); } }, "test should have failed for name [" + wrong + "]"); } } /** * test create and destroy for a subContext. * @param context the context to test * @param expectedRootContext the expected root context (different from context in case of wrapping) * @throws NamingException on error * @throws RemoteException on RMI error */ public void testSubContext(final ContextInterface context, final ContextInterface expectedRootContext) throws NamingException, RemoteException { // test empty root context assertTrue(context.isEmpty()); // test toString() assertTrue(context.toString().contains("parent=null")); // build a normal tree ContextInterface level1 = context.createSubcontext("level1"); assertFalse(context.isEmpty()); assertEquals(new HashSet(Arrays.asList("level1")), context.keySet()); assertEquals(level1, context.get("level1")); assertTrue(level1.isEmpty()); ContextInterface level2 = level1.createSubcontext("level2"); assertEquals(level2, context.get("level1/level2")); assertTrue(level2.isEmpty()); assertEquals(expectedRootContext, level2.getRootContext()); // test toString() assertTrue(context.toString().contains("parent=null")); assertTrue(level2.toString().contains("parent=level1")); assertTrue(level1.toString().contains("atomicName=level1")); // name clash with existing subcontext Try.testFail(new Try.Execution() { @Override public void execute() throws Throwable { context.createSubcontext("/level1"); } }); // try to remove non-existing subcontext Try.testFail(new Try.Execution() { @Override public void execute() throws Throwable { context.destroySubcontext("/level3"); } }); // name clash with existing key context.bind("key1", "value1"); Try.testFail(new Try.Execution() { @Override public void execute() throws Throwable { context.createSubcontext("/key1"); } }, NameAlreadyBoundException.class); // try to remove subcontext that points to key Try.testFail(new Try.Execution() { @Override public void execute() throws Throwable { context.destroySubcontext("/key1"); } }, NotContextException.class); // Checking empty name, null name, slash for createSubcontext for (String wrong : new String[] {"", "//", "/", null}) { Try.testFail(new Try.Execution() { @Override public void execute() throws Throwable { context.createSubcontext(wrong); } }, "test should have failed for name [" + wrong + "]"); } // Checking empty name, null name, slash for destroySubcontext for (String wrong : new String[] {"", "//", "/", null}) { Try.testFail(new Try.Execution() { @Override public void execute() throws Throwable { context.destroySubcontext(wrong); } }, "test should have failed for name [" + wrong + "]"); } // use a key as part of the path Try.testFail(new Try.Execution() { @Override public void execute() throws Throwable { context.get("/key1/level2"); } }, NameNotFoundException.class); // TODO: test circular references when check has been implemented // clean up context.unbind("key1"); context.destroySubcontext("/level1/level2"); context.destroySubcontext("/level1"); assertTrue(context.isEmpty()); } /** * test the ContextName object */ public void testContextName() { ContextInterface c1 = new JVMContext(null, "c1"); ContextInterface c2 = new JVMContext(null, "c2"); String name1 = "name1"; String name2 = "name2"; JVMContext.ContextName cn1 = new JVMContext.ContextName(c1, name1); JVMContext.ContextName cn2 = new JVMContext.ContextName(c2, name2); assertEquals(cn1, cn1); assertEquals(cn1, new JVMContext.ContextName(c1, name1)); assertNotEquals(cn1, cn2); assertNotEquals(cn1, null); assertNotEquals(cn1, new Object()); assertNotEquals(cn1, new JVMContext.ContextName(null, name1)); assertNotEquals(cn1, new JVMContext.ContextName(c1, null)); assertNotEquals(new JVMContext.ContextName(null, name1), cn1); assertNotEquals(new JVMContext.ContextName(c1, null), cn1); assertNotEquals(new JVMContext.ContextName(null, name1), new JVMContext.ContextName(null, name2)); assertEquals(new JVMContext.ContextName(null, name1), new JVMContext.ContextName(null, name1)); assertNotEquals(new JVMContext.ContextName(c1, null), new JVMContext.ContextName(c2, null)); assertEquals(new JVMContext.ContextName(c1, null), new JVMContext.ContextName(c1, null)); assertEquals(cn1.hashCode(), cn1.hashCode()); assertNotEquals(cn1.hashCode(), cn2.hashCode()); assertNotEquals(cn1.hashCode(), new JVMContext.ContextName(c1, null).hashCode()); assertNotEquals(cn1.hashCode(), new JVMContext.ContextName(null, name1).hashCode()); assertTrue(cn1.toString().contains("name=name1")); assertTrue(cn1.toString().contains("atomicName=c1")); } /** Test object with ToString() method. */ protected class TestObject implements Serializable { /** */ private static final long serialVersionUID = 1L; /** */ private final String field; /** * @param field the field */ public TestObject(String field) { this.field = field; } /** {@inheritDoc} */ @Override public String toString() { return this.field; } } /** * test the non-initialized InitialEventContext. * @param ctx the uninitialized (closed) InitialEventContext * @throws NamingException on failed test * @throws RemoteException on RMI error */ public void testNullEventContext(final InitialEventContext ctx) throws NamingException, RemoteException { try { ctx.getAtomicName(); fail("should have thrown exception"); } catch (RuntimeException rte) { // ok } try { ctx.getParent(); fail("should have thrown exception"); } catch (RuntimeException rte) { // ok } try { ctx.getRootContext(); fail("should have thrown exception"); } catch (RuntimeException rte) { // ok } try { ctx.get("abc"); fail("should have thrown exception"); } catch (RuntimeException rte) { // ok } try { ctx.getObject("abc"); fail("should have thrown exception"); } catch (NoInitialContextException nce) { // ok } try { ctx.exists("abc"); fail("should have thrown exception"); } catch (NoInitialContextException nce) { // ok } try { ctx.hasKey("abc"); fail("should have thrown exception"); } catch (NoInitialContextException nce) { // ok } try { ctx.hasObject("def"); fail("should have thrown exception"); } catch (RuntimeException rte) { // ok } try { ctx.isEmpty(); fail("should have thrown exception"); } catch (RuntimeException rte) { // ok } try { ctx.bind("abc", "def"); fail("should have thrown exception"); } catch (NoInitialContextException nce) { // ok } try { ctx.bindObject("abc", "def"); fail("should have thrown exception"); } catch (NoInitialContextException nce) { // ok } try { ctx.bindObject("def"); fail("should have thrown exception"); } catch (NoInitialContextException nce) { // ok } try { ctx.unbind("abc"); fail("should have thrown exception"); } catch (NoInitialContextException nce) { // ok } try { ctx.unbindObject("abc"); fail("should have thrown exception"); } catch (NoInitialContextException nce) { // ok } try { ctx.rebind("abc", "def"); fail("should have thrown exception"); } catch (NoInitialContextException nce) { // ok } try { ctx.rebindObject("abc", "def"); fail("should have thrown exception"); } catch (NoInitialContextException nce) { // ok } try { ctx.rename("abc", "abc2"); fail("should have thrown exception"); } catch (NoInitialContextException nce) { // ok } try { ctx.createSubcontext("sub"); fail("should have thrown exception"); } catch (NoInitialContextException nce) { // ok } try { ctx.destroySubcontext("sub"); fail("should have thrown exception"); } catch (NoInitialContextException nce) { // ok } try { ctx.checkCircular("sub"); fail("should have thrown exception"); } catch (NoInitialContextException nce) { // ok } try { ctx.keySet(); fail("should have thrown exception"); } catch (RuntimeException rte) { // ok } try { ctx.values(); fail("should have thrown exception"); } catch (RuntimeException rte) { // ok } try { ctx.bindings(); fail("should have thrown exception"); } catch (RuntimeException rte) { // ok } assertTrue(ctx.toString().startsWith("InitialEventContext")); assertTrue(ctx.toString().contains("null")); } /** * destroy the singleton InitialEventContext between tests... * @param ctx InitialEventContext; the context to clear */ private void destroy(final InitialEventContext ctx) { try { Field instance = ctx.getClass().getDeclaredField("INSTANCE"); instance.setAccessible(true); instance.set(null, null); instance.setAccessible(false); } catch (Exception e) { fail(e.getMessage()); } } }