package org.swellrt.beta.model.remote; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.swellrt.beta.common.SException; import org.swellrt.beta.model.IllegalCastException; import org.swellrt.beta.model.SEvent; import org.swellrt.beta.model.SHandler; import org.swellrt.beta.model.SMap; import org.swellrt.beta.model.SNodeAccessControl; import org.swellrt.beta.model.SPrimitive; import org.swellrt.beta.model.local.SMapLocal; import org.waveprotocol.wave.client.common.util.CountdownLatch; import org.waveprotocol.wave.model.util.CollectionUtils; import com.google.gwt.user.client.Command; /** * Test SMapRemote */ public class SMapRemoteTest extends SNodeRemoteAbstractTest { protected void populatePrimitiveValues(SMap map) throws SException { map.put("k0", new SPrimitive("A value for k0", new SNodeAccessControl())); map.put("k1", "A value for k1"); } protected void assertPrimitiveValues(SMap map) throws SException { assertEquals("A value for k0", (String) map.get("k0")); assertEquals("A value for k1", (String) map.get("k1")); } /** * Put only primitive values in the root map. * * Get primitive values with or without map cache * to force deserialization from Wave documents (XML) * @throws IllegalCastException * */ public void testMapWithPrimitiveValues() throws SException { populatePrimitiveValues(object); assertPrimitiveValues(object); object.clearCache(); populatePrimitiveValues(object); assertPrimitiveValues(object); } /** * Put a nested map in root map. * Put primitive values in inner map. * * Get primitive values with or without map cache to * force deserialization from Wave documents (XML) * @throws IllegalCastException * */ public void testMapWithNestedMap() throws SException { SMap submap = new SMapLocal(); populatePrimitiveValues(submap); object.put("submap", submap); assertPrimitiveValues((SMap) object.get("submap")); } /** * Create two nested local maps<br> * Add them to a live object (so create remote maps)<br> * Check primitive values are retrieved from remote maps<br> * Make changes directly in remote maps<br> * * @throws SException */ public void testMapWithNestedLocalMap() throws SException { // Create maps SMap mapA = new SMapLocal(); populatePrimitiveValues(mapA); SMap mapB = new SMapLocal(); populatePrimitiveValues(mapB); mapA.put("mapB", mapB); object.put("mapA", mapA); // Clear cache and check retrieving values object.clearCache(); SMap remoteMapA = (SMap) object.get("mapA"); assertPrimitiveValues(remoteMapA); SMap remoteMapB = (SMap) remoteMapA.get("mapB"); assertPrimitiveValues(remoteMapB); // Make changes, no exceptions must be thrown remoteMapA.put("kA1", "Some value kA1"); remoteMapB.put("kB1", "Some value kB1"); object.clearCache(); assertEquals("Some value kA1", (String) remoteMapA.get("kA1")); assertEquals("Some value kB1", (String) remoteMapB.get("kB1")); } /** * Test map events generated by local changes. * * @throws SException * @throws InterruptedException */ public void testMapEvents() throws SException, InterruptedException { List<SEvent> recvEvents = new ArrayList<SEvent>(); SHandler eventHandler = new SHandler() { @Override public boolean exec(SEvent e) { recvEvents.add(e); synchronized (this) { if (recvEvents.size() == 3) notifyAll(); } return false; } }; SMap map = new SMapLocal(); populatePrimitiveValues(map); object.put("map", map); SMapRemote remoteMap =(SMapRemote) object.get("map"); remoteMap.listen(eventHandler); remoteMap.remove("k1"); remoteMap.put("k2" , "This is new value"); remoteMap.put("k0" , "This is updated value"); synchronized (eventHandler) { eventHandler.wait(1000); } // Check whether mutations were properly done assertEquals(2, remoteMap.size()); assertEquals(null, remoteMap.get("k1")); assertEquals("This is new value", (String) remoteMap.get("k2")); assertEquals("This is updated value", (String) remoteMap.get("k0")); // Check events assertEquals(3, recvEvents.size()); assertEquals(SEvent.REMOVED_VALUE, recvEvents.get(0).getType()); assertEquals("k1", recvEvents.get(0).getKey()); assertEquals("A value for k1", (String) ((SPrimitive) recvEvents.get(0).getValue()).get()); assertEquals(SEvent.ADDED_VALUE, recvEvents.get(1).getType()); assertEquals("k2", recvEvents.get(1).getKey()); assertEquals("This is new value", (String) ((SPrimitive) recvEvents.get(1).getValue()).get()); assertEquals(SEvent.UPDATED_VALUE, recvEvents.get(2).getType()); assertEquals("k0", recvEvents.get(2).getKey()); assertEquals("This is updated value", (String) ((SPrimitive) recvEvents.get(2).getValue()).get()); } /** * Test whether events generated by map changes are properly * propagated upwards within nested maps. * @throws SException * @throws InterruptedException */ public void testMapEventsPropagation() throws SException, InterruptedException { List<SEvent> capturedEventsRoot = new ArrayList<SEvent>(); List<SEvent> capturedEventsMapA = new ArrayList<SEvent>(); List<SEvent> capturedEventsMapB = new ArrayList<SEvent>(); CountdownLatch cd = CountdownLatch.create(5, new Command() { @Override public void execute() { // Case 1) Generate event in C // captured by handlerB but not in handlerA and handlerRoot SEvent e = capturedEventsMapB.get(0); assertNotNull(e); assertEquals(SEvent.ADDED_VALUE, e.getType()); assertEquals("valueForC", (String) ((SPrimitive) e.getValue()).get()); // Case 2) Generate event in B // captured by handlerB but not in handlerA and handlerRoot e = capturedEventsMapB.get(1); assertNotNull(e); assertEquals(SEvent.ADDED_VALUE, e.getType()); assertEquals("valueForB", (String) ((SPrimitive) e.getValue()).get()); // Case 3) Generate event in A // captured by handlerA and rootHandler e = capturedEventsMapA.get(0); assertNotNull(e); assertEquals(SEvent.ADDED_VALUE, e.getType()); assertEquals("valueForA", (String) ((SPrimitive) e.getValue()).get()); assertEquals(1, capturedEventsMapA.size()); // Assert case 1 and case 2, "but" parts e = capturedEventsRoot.get(0); assertNotNull(e); assertEquals(SEvent.ADDED_VALUE, e.getType()); assertEquals("valueForA", (String) ((SPrimitive) e.getValue()).get()); assertEquals(1, capturedEventsRoot.size()); // Assert case 1 and case 2, "but" parts } }); SHandler handlerRoot = new SHandler() { @Override public boolean exec(SEvent e) { capturedEventsRoot.add(e); // System.out.println("handlerRoot: "+e.toString()); cd.tick(); // for root handler this has not actual effect return true; } }; SHandler handlerMapA = new SHandler() { @Override public boolean exec(SEvent e) { capturedEventsMapA.add(e); // System.out.println("handlerMapA: "+e.toString()); cd.tick(); // this handler will let // events to flow upwards return true; } }; SHandler handlerMapB = new SHandler() { @Override public boolean exec(SEvent e) { capturedEventsMapB.add(e); // System.out.println("handlerMapB: "+e.toString()); cd.tick(); // this handler won't let // events to flow upwards return false; } }; // Create test data and bind event listeners // SMap map = new SMapLocal(); //populatePrimitiveValues(map); // // root map <- hanlderRoot // | // -- mapA <- handlerA (allow propagation) // | // -- mapB <- handlerB (stop propagation) // | // -- mapC // SMapRemote remoteMapA = (SMapRemote) object.put("mapA", new SMapLocal()).get("mapA"); SMapRemote remoteMapB = (SMapRemote) remoteMapA.put("mapB", new SMapLocal()).get("mapB"); SMapRemote remoteMapC = (SMapRemote) remoteMapB.put("mapC", new SMapLocal()).get("mapC"); // Set handlers here to ignore events for initialization fields remoteMapA.listen(handlerMapA); remoteMapB.listen(handlerMapB); object.listen(handlerRoot); // Case 1) Generate event in C // captured by handlerB but not in handlerA and handlerRoot remoteMapC.put("keyForC", "valueForC"); // Case 2) Generate event in B // captured by handlerB but not in handlerA and handlerRoot remoteMapB.put("keyForB", "valueForB"); // Case 3) Generate event in A // captured by handlerA and rootHandler remoteMapA.put("keyForA", "valueForA"); cd.tick(); } public void testAccessControl() throws SException { // field without access control SNodeAccessControl nac = new SNodeAccessControl(); SPrimitive p = new SPrimitive("total access", nac); object.put("prop1", p); try { object.get("prop1"); } catch (SException e) { assertTrue("SException not expected", false); } try { object.put("prop1", p); } catch (SException e) { assertTrue("SException not expected", false); } try { object.remove("prop1"); } catch (SException e) { assertTrue("SException not expected", false); } // field read only, any user nac = new SNodeAccessControl(true); p = new SPrimitive("read only access", nac); object.put("prop2", p); try { object.get("prop2"); } catch (SException e) { assertTrue("SException not expected", false); } try { object.put("prop2", p); } catch (SException e) { assertTrue("SException expected, can't write", true); } try { object.remove("prop2"); } catch (SException e) { assertTrue("SException expected, can't write", true); } // field read only, restricted user list (current user in list) nac = new SNodeAccessControl(CollectionUtils.immutableSet("tom@acme.com","ann@acme.com") ,Collections.<String>emptySet(), true); p = new SPrimitive("read only with access list", nac); object.put("prop3", p); try { object.get("prop3"); } catch (SException e) { assertTrue("SException not expected", false); } try { object.put("prop3", p); } catch (SException e) { assertTrue("SException expected, can't write", true); } try { object.remove("prop3"); } catch (SException e) { assertTrue("SException expected, can't write", true); } // field read only, restricted user list (current user NOT in list) nac = new SNodeAccessControl(CollectionUtils.immutableSet("bob@acme.com","ann@acme.com") ,Collections.<String>emptySet(), true); p = new SPrimitive("read only with access list", nac); object.put("prop4", p); try { object.get("prop4"); } catch (SException e) { assertTrue("SException expected, can't read", true); } try { object.put("prop4", p); } catch (SException e) { assertTrue("SException expected, can't write", true); } try { object.remove("prop4"); } catch (SException e) { assertTrue("SException expected, can't write", true); } // field with write and read access lists (current user can't write) nac = new SNodeAccessControl( CollectionUtils.immutableSet("tom@acme.com","ann@acme.com"), CollectionUtils.immutableSet("bob@acme.com","ann@acme.com"), false); p = new SPrimitive("read and write access list", nac); object.put("prop5", p); try { object.get("prop5"); } catch (SException e) { assertTrue("SException not expected", false); } try { object.put("prop5", p); } catch (SException e) { assertTrue("SException expected, can't write", true); } try { object.remove("prop5"); } catch (SException e) { assertTrue("SException expected, can't write", true); } } protected void tearDown() throws Exception { super.tearDown(); } }