From 14ba0e784edbb01ee1f13fd3460db193c178f7cf Mon Sep 17 00:00:00 2001 From: Gilberto Torrezan Date: Mon, 7 May 2018 16:24:39 +0300 Subject: [PATCH] Added possibility to add listener for connectorMarkedDirty (#10876) --- .../event/MarkedAsDirtyConnectorEvent.java | 44 ++++ .../vaadin/event/MarkedAsDirtyListener.java | 32 +++ .../java/com/vaadin/ui/ConnectorTracker.java | 59 +++++- .../tests/event/MarkAsDirtyListenerTest.java | 199 ++++++++++++++++++ 4 files changed, 331 insertions(+), 3 deletions(-) create mode 100644 server/src/main/java/com/vaadin/event/MarkedAsDirtyConnectorEvent.java create mode 100644 server/src/main/java/com/vaadin/event/MarkedAsDirtyListener.java create mode 100644 server/src/test/java/com/vaadin/tests/event/MarkAsDirtyListenerTest.java diff --git a/server/src/main/java/com/vaadin/event/MarkedAsDirtyConnectorEvent.java b/server/src/main/java/com/vaadin/event/MarkedAsDirtyConnectorEvent.java new file mode 100644 index 0000000000..62db27e617 --- /dev/null +++ b/server/src/main/java/com/vaadin/event/MarkedAsDirtyConnectorEvent.java @@ -0,0 +1,44 @@ +/* + * Copyright 2000-2018 Vaadin Ltd. + * + * Licensed 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.vaadin.event; + +import com.vaadin.server.ClientConnector; +import com.vaadin.ui.UI; + +/** + * Event which is fired for all registered MarkDirtyListeners when a connector + * is marked as dirty. + * + * @since + */ +public class MarkedAsDirtyConnectorEvent extends ConnectorEvent { + + private final UI ui; + + public MarkedAsDirtyConnectorEvent(ClientConnector source, UI ui) { + super(source); + this.ui = ui; + } + + /** + * Get the UI for which the connector event was fired + * + * @return target ui for event + */ + public UI getUi() { + return ui; + } +} diff --git a/server/src/main/java/com/vaadin/event/MarkedAsDirtyListener.java b/server/src/main/java/com/vaadin/event/MarkedAsDirtyListener.java new file mode 100644 index 0000000000..86134a2423 --- /dev/null +++ b/server/src/main/java/com/vaadin/event/MarkedAsDirtyListener.java @@ -0,0 +1,32 @@ +/* + * Copyright 2000-2018 Vaadin Ltd. + * + * Licensed 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.vaadin.event; + +/** + * An interface used for listening to marked as dirty events. + * + * @since + */ +public interface MarkedAsDirtyListener extends ConnectorEventListener { + + /** + * Method called when a client connector has been marked as dirty. + * + * @param event + * marked as dirty connector event object + */ + void connectorMarkedAsDirty(MarkedAsDirtyConnectorEvent event); +} diff --git a/server/src/main/java/com/vaadin/ui/ConnectorTracker.java b/server/src/main/java/com/vaadin/ui/ConnectorTracker.java index 3b48431824..713d9b47c1 100644 --- a/server/src/main/java/com/vaadin/ui/ConnectorTracker.java +++ b/server/src/main/java/com/vaadin/ui/ConnectorTracker.java @@ -23,12 +23,15 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; +import com.vaadin.event.MarkedAsDirtyConnectorEvent; +import com.vaadin.event.MarkedAsDirtyListener; import com.vaadin.server.AbstractClientConnector; import com.vaadin.server.ClientConnector; import com.vaadin.server.DragAndDropService; @@ -69,6 +72,9 @@ public class ConnectorTracker implements Serializable { private Set dirtyConnectors = new HashSet(); private Set uninitializedConnectors = new HashSet(); + private List markedDirtyListeners = new ArrayList( + 0); + /** * Connectors that have been unregistered and should be cleaned up the next * time {@link #cleanConnectorMap()} is invoked unless they have been @@ -484,8 +490,8 @@ public class ConnectorTracker implements Serializable { } /** - * Mark the connector as dirty. This should not be done while the response - * is being written. + * Mark the connector as dirty and notifies any marked as dirty listeners. + * This should not be done while the response is being written. * * @see #getDirtyConnectors() * @see #isWritingResponse() @@ -500,12 +506,16 @@ public class ConnectorTracker implements Serializable { } if (getLogger().isLoggable(Level.FINE)) { - if (!dirtyConnectors.contains(connector)) { + if (!isDirty(connector)) { getLogger().log(Level.FINE, "{0} is now dirty", getConnectorAndParentInfo(connector)); } } + if (!isDirty(connector)) { + notifyMarkedAsDirtyListeners(connector); + } + dirtyConnectors.add(connector); } @@ -877,4 +887,47 @@ public class ConnectorTracker implements Serializable { public int getCurrentSyncId() { return currentSyncId; } + + /** + * Adds a marked as dirty listener that will be called when a client + * connector is marked as dirty. + * + * @param listener + * listener to add + * @since + */ + public void addMarkedAsDirtyListener(MarkedAsDirtyListener listener) { + markedDirtyListeners.add(listener); + } + + /** + * Removes a marked as dirty listener. + * + * @param listener + * listener to remove + * @since + */ + public void removeMarkedAsDirtyListener(MarkedAsDirtyListener listener) { + markedDirtyListeners.remove(listener); + } + + /** + * Notify all registered MarkedAsDirtyListeners the given client connector + * has been marked as dirty. + * + * @param connector + * client connector marked as dirty + * @since + */ + public void notifyMarkedAsDirtyListeners(ClientConnector connector) { + MarkedAsDirtyConnectorEvent event = new MarkedAsDirtyConnectorEvent( + connector, uI); + + List copy = new ArrayList( + markedDirtyListeners); + for (MarkedAsDirtyListener listener : copy) { + listener.connectorMarkedAsDirty(event); + } + } + } diff --git a/server/src/test/java/com/vaadin/tests/event/MarkAsDirtyListenerTest.java b/server/src/test/java/com/vaadin/tests/event/MarkAsDirtyListenerTest.java new file mode 100644 index 0000000000..dde69f6adf --- /dev/null +++ b/server/src/test/java/com/vaadin/tests/event/MarkAsDirtyListenerTest.java @@ -0,0 +1,199 @@ +package com.vaadin.tests.event; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Assert; +import org.junit.Test; + +import com.vaadin.event.MarkedAsDirtyConnectorEvent; +import com.vaadin.event.MarkedAsDirtyListener; +import com.vaadin.server.ClientConnector; +import com.vaadin.tests.util.MockUI; +import com.vaadin.ui.Button; +import com.vaadin.ui.ComponentTest; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.TextField; +import com.vaadin.ui.UI; + +/** + * Test for mark as dirty listener functionality. + */ +public class MarkAsDirtyListenerTest { + + @Test + public void fire_event_when_ui_marked_dirty() { + UI ui = new MockUI(); + + final AtomicReference events = new AtomicReference(); + ui.getConnectorTracker() + .addMarkedAsDirtyListener(new MarkedAsDirtyListener() { + @Override + public void connectorMarkedAsDirty( + MarkedAsDirtyConnectorEvent event) { + Assert.assertTrue( + "No reference should have been registered", + events.compareAndSet(null, event)); + } + }); + + // UI is marked dirty on creation and when adding a listener + ComponentTest.syncToClient(ui); + + ui.getConnectorTracker().markDirty(ui); + + Assert.assertNotNull("Mark as dirty event should have fired", + events.get()); + Assert.assertEquals("Event contains wrong ui", ui, + events.get().getUi()); + Assert.assertEquals("Found wrong connector in event", ui, + events.get().getConnector()); + } + + @Test + public void fire_event_for_setContent() { + final List events = new ArrayList(); + UI ui = new MockUI() { + { + getConnectorTracker() + .addMarkedAsDirtyListener(new MarkedAsDirtyListener() { + + @Override + public void connectorMarkedAsDirty( + MarkedAsDirtyConnectorEvent event) { + events.add(event); + } + }); + } + }; + ComponentTest.syncToClient(ui); + + Button button = new Button("Button"); + ui.setContent(button); + + Assert.assertEquals("Mark as dirty events should have fired", 2, + events.size()); + Assert.assertEquals("Expected button to inform first for creation", + button, events.get(0).getConnector()); + Assert.assertEquals("Expected UI marked as dirty for setContent", ui, + events.get(1).getConnector()); + } + + @Test + public void fire_event_for_component_stateChange() { + UI ui = new MockUI(); + Button button = new Button("empty"); + ui.setContent(button); + ComponentTest.syncToClient(button); + + final AtomicReference events = new AtomicReference(); + ui.getConnectorTracker() + .addMarkedAsDirtyListener(new MarkedAsDirtyListener() { + + @Override + public void connectorMarkedAsDirty( + MarkedAsDirtyConnectorEvent event) { + Assert.assertTrue( + "No reference should have been registered", + events.compareAndSet(null, event)); + } + }); + + button.setIconAlternateText("alternate"); + + Assert.assertNotNull("Mark as dirty event should have fired", + events.get()); + Assert.assertEquals("Event contains wrong ui", ui, + events.get().getUi()); + Assert.assertEquals("Found wrong connector in event", button, + events.get().getConnector()); + } + + @Test + public void fire_events_for_each_component() { + final List events = new ArrayList(); + UI ui = new MockUI() { + { + getConnectorTracker() + .addMarkedAsDirtyListener(new MarkedAsDirtyListener() { + + @Override + public void connectorMarkedAsDirty( + MarkedAsDirtyConnectorEvent event) { + events.add(event); + } + }); + } + }; + + HorizontalLayout layout = new HorizontalLayout(); + // UI initially marked as dirty so should not show as event. + ui.setContent(layout); + TextField field = new TextField("Name"); + Button button = new Button("say hello"); + layout.addComponents(field, button); + + Assert.assertFalse("Mark as dirty event should have fired", + events.isEmpty()); + Assert.assertEquals("Unexpected amount of connector events", 3, + events.size()); + + Set connectors = new HashSet(); + for (MarkedAsDirtyConnectorEvent event : events) { + connectors.add(event.getConnector()); + } + + Assert.assertTrue( + "HorizontalLayout should have fired an markedAsDirty event", + connectors.contains(layout)); + Assert.assertTrue("TextField should have fired an markedAsDirty event", + connectors.contains(field)); + Assert.assertTrue("Button should have fired an markedAsDirty event", + connectors.contains(button)); + } + + @Test + public void event_should_only_fire_once_for_an_connector_per_roundtrip() { + UI ui = new MockUI(); + Button button = new Button("empty"); + ui.setContent(button); + ComponentTest.syncToClient(button); + + final AtomicReference events = new AtomicReference(); + ui.getConnectorTracker() + .addMarkedAsDirtyListener(new MarkedAsDirtyListener() { + @Override + public void connectorMarkedAsDirty( + MarkedAsDirtyConnectorEvent event) { + Assert.assertTrue( + "Only one event should have registered", + events.compareAndSet(null, event)); + } + }); + + button.setIconAlternateText("alternate"); + button.setCaption("Update"); + button.setDisableOnClick(true); + + Assert.assertNotNull("Mark as dirty event should have fired", + events.get()); + Assert.assertEquals("Event contains wrong ui", ui, + events.get().getUi()); + Assert.assertEquals("Found wrong connector in event", button, + events.get().getConnector()); + + events.set(null); + ComponentTest.syncToClient(button); + + button.setCaption("new caption"); + + Assert.assertNotNull("Mark as dirty event should have fired", + events.get()); + Assert.assertEquals("Found wrong connector in event", button, + events.get().getConnector()); + } + +} -- 2.39.5