This patch also removes the old automatic update logic. Change-Id: Idb50137eee2592c1acd14ff67f577ad5edbbd2fffeature/vaadin8-book
@@ -1,97 +0,0 @@ | |||
/* | |||
* Copyright 2000-2014 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.tokka.server.communication.data; | |||
import java.util.LinkedHashSet; | |||
import java.util.Set; | |||
import com.vaadin.tokka.event.Registration; | |||
/** | |||
* Base class for AbstractDataSource. Provides tracking for | |||
* {@link DataChangeHandler}s and helper methods to call them. | |||
* | |||
* @since | |||
*/ | |||
public abstract class AbstractDataSource<T> implements DataSource<T> { | |||
protected final Set<DataChangeHandler<T>> handlers = new LinkedHashSet<DataChangeHandler<T>>(); | |||
@Override | |||
public Registration addDataChangeHandler(DataChangeHandler<T> handler) { | |||
if (handler != null) { | |||
handlers.add(handler); | |||
return () -> handlers.remove(handler); | |||
} | |||
return () -> { /* NO-OP */ }; | |||
} | |||
/** | |||
* Informs all handlers of a generic change in the data. This usually | |||
* triggers a full cache invalidation and refresh of all data, which is | |||
* usually an expensive operation. Should be called when none of the other | |||
* methods help. | |||
* | |||
* @see #fireDataAppend(Object) | |||
* @see #fireDataRemove(Object) | |||
* @see #fireDataUpdate(Object) | |||
*/ | |||
protected void fireDataChange() { | |||
for (DataChangeHandler<T> handler : handlers) { | |||
handler.onDataChange(); | |||
} | |||
} | |||
/** | |||
* Informs all handlers of an added data object in the back end. This method | |||
* should only be called when the newly added data object is the last object | |||
* in the back end. Other additions can be handled with | |||
* {@link #fireDataChange()}. | |||
* | |||
* @param data | |||
* added data object | |||
*/ | |||
protected void fireDataAppend(T data) { | |||
for (DataChangeHandler<T> handler : handlers) { | |||
handler.onDataAppend(data); | |||
} | |||
} | |||
/** | |||
* Informs all handlers of a data object that was removed from the back end. | |||
* | |||
* @param data | |||
* removed data object | |||
*/ | |||
protected void fireDataRemove(T data) { | |||
for (DataChangeHandler<T> handler : handlers) { | |||
handler.onDataRemove(data); | |||
} | |||
} | |||
/** | |||
* Informs all handlers of an existing data object that was updated in the | |||
* back end. | |||
* | |||
* @param data | |||
* updated data object | |||
*/ | |||
protected void fireDataUpdate(T data) { | |||
for (DataChangeHandler<T> handler : handlers) { | |||
handler.onDataUpdate(data); | |||
} | |||
} | |||
} |
@@ -25,7 +25,6 @@ import java.util.stream.Collectors; | |||
import java.util.stream.Stream; | |||
import com.vaadin.server.AbstractExtension; | |||
import com.vaadin.server.ClientConnector; | |||
import com.vaadin.shared.data.DataRequestRpc; | |||
import com.vaadin.shared.data.typed.DataCommunicatorClientRpc; | |||
import com.vaadin.shared.data.typed.DataProviderConstants; | |||
@@ -72,8 +71,8 @@ public class DataCommunicator<T> extends AbstractExtension { | |||
* longer needed. Data tracking is based on key string provided by | |||
* {@link DataKeyMapper}. | |||
* <p> | |||
* When the {@link DataCommunicator} is pushing new data to the client-side via | |||
* {@link DataCommunicator#pushData(long, Collection)}, | |||
* When the {@link DataCommunicator} is pushing new data to the client-side | |||
* via {@link DataCommunicator#pushData(long, Collection)}, | |||
* {@link #addActiveData(Collection)} and {@link #cleanUp(Collection)} are | |||
* called with the same parameter. In the clean up method any dropped data | |||
* objects that are not in the given collection will be cleaned up and | |||
@@ -173,8 +172,6 @@ public class DataCommunicator<T> extends AbstractExtension { | |||
protected DataCommunicatorClientRpc rpc; | |||
protected DataSource<T> dataSource; | |||
private Registration dataChangeHandler; | |||
private DetachListener detachListener; | |||
private DataKeyMapper<T> keyMapper; | |||
private boolean reset = false; | |||
@@ -186,8 +183,6 @@ public class DataCommunicator<T> extends AbstractExtension { | |||
this.dataSource = dataSource; | |||
rpc = getRpcProxy(DataCommunicatorClientRpc.class); | |||
registerRpc(createRpc()); | |||
dataChangeHandler = this.dataSource | |||
.addDataChangeHandler(createDataChangeHandler()); | |||
keyMapper = createKeyMapper(); | |||
} | |||
@@ -200,11 +195,13 @@ public class DataCommunicator<T> extends AbstractExtension { | |||
super.beforeClientResponse(initial); | |||
if (initial || reset) { | |||
rpc.reset(dataSource.size()); | |||
// FIXME: Rethink the size question. | |||
rpc.reset((int) dataSource.apply(null).count()); | |||
} | |||
if (!pushRows.isEmpty()) { | |||
Stream<T> rowsToPush = dataSource.request() | |||
// FIXME: Query object | |||
Stream<T> rowsToPush = dataSource.apply(null) | |||
.skip(pushRows.getStart()).limit(pushRows.length()); | |||
pushData(pushRows.getStart(), rowsToPush); | |||
} | |||
@@ -223,32 +220,6 @@ public class DataCommunicator<T> extends AbstractExtension { | |||
updatedData.clear(); | |||
} | |||
@Override | |||
public void attach() { | |||
super.attach(); | |||
if (detachListener == null) { | |||
detachListener = new DetachListener() { | |||
@Override | |||
public void detach(DetachEvent event) { | |||
cleanUp(); | |||
} | |||
}; | |||
getUI().addDetachListener(detachListener); | |||
} | |||
} | |||
@Override | |||
public void setParent(ClientConnector parent) { | |||
if (getParent() != null && parent == null) { | |||
// Removing from parent, clean up. | |||
cleanUp(); | |||
} | |||
super.setParent(parent); | |||
} | |||
/** | |||
* Adds a {@link TypedDataGenerator} to this {@link DataCommunicator}. | |||
* | |||
@@ -340,22 +311,6 @@ public class DataCommunicator<T> extends AbstractExtension { | |||
} | |||
} | |||
/** | |||
* Clean up method for removing all listeners attached by the | |||
* {@link DataCommunicator}. This method is called from {@link #remove()} or | |||
* when the UI gets detached. | |||
*/ | |||
protected void cleanUp() { | |||
if (dataSource != null) { | |||
dataChangeHandler.removeHandler(); | |||
dataChangeHandler = null; | |||
} | |||
if (detachListener != null) { | |||
getUI().removeDetachListener(detachListener); | |||
detachListener = null; | |||
} | |||
} | |||
/** | |||
* Informs the DataProvider that a data object has been added. It is assumed | |||
* to be the last object in the collection. | |||
@@ -406,7 +361,7 @@ public class DataCommunicator<T> extends AbstractExtension { | |||
} | |||
/** | |||
* Creates a {@link DataKeyMapper} to use with this {@link DataCommunicator}. | |||
* Creates a {@link DataKeyMapper} to use with this DataCommunicator. | |||
* <p> | |||
* This method is called from the constructor. | |||
* | |||
@@ -426,36 +381,4 @@ public class DataCommunicator<T> extends AbstractExtension { | |||
protected DataRequestRpc createRpc() { | |||
return new SimpleDataRequestRpc(); | |||
} | |||
/** | |||
* Creates a {@link DataChangeHandler} to use with the {@link DataSource}. | |||
* <p> | |||
* This method is called from the constructor. | |||
* | |||
* @return data change handler | |||
*/ | |||
protected DataChangeHandler<T> createDataChangeHandler() { | |||
return new DataChangeHandler<T>() { | |||
@Override | |||
public void onDataChange() { | |||
reset(); | |||
} | |||
@Override | |||
public void onDataAppend(T data) { | |||
add(data); | |||
} | |||
@Override | |||
public void onDataRemove(T data) { | |||
remove(data); | |||
} | |||
@Override | |||
public void onDataUpdate(T data) { | |||
refresh(data); | |||
} | |||
}; | |||
} | |||
} |
@@ -18,6 +18,7 @@ package com.vaadin.tokka.server.communication.data; | |||
import java.io.Serializable; | |||
import java.util.Arrays; | |||
import java.util.Collection; | |||
import java.util.function.Function; | |||
import java.util.stream.Stream; | |||
import com.vaadin.tokka.event.Registration; | |||
@@ -25,74 +26,40 @@ import com.vaadin.tokka.event.Registration; | |||
/** | |||
* Minimal DataSource API for communication between the DataProvider and a back | |||
* end service. | |||
* <p> | |||
* FIXME: Missing Query class | |||
* | |||
* @since | |||
* @param <T> | |||
* data type | |||
*/ | |||
public interface DataSource<T> extends Serializable { | |||
public interface DataSource<T> extends Function<Object, Stream<T>>, | |||
Serializable { | |||
/** | |||
* Requests data from the back end. | |||
* | |||
* @return stream of results | |||
*/ | |||
Stream<T> request(/* Query query */); | |||
/** | |||
* Saves a data object to the back end. If it's a new object, it should be | |||
* created in the back end. Existing objects with changes should be stored. | |||
* | |||
* @param data | |||
* data object to save | |||
*/ | |||
void save(T data); | |||
/** | |||
* Removes the given data object from the back end. | |||
* | |||
* @param data | |||
* data object to remove | |||
*/ | |||
void remove(T data); | |||
/** | |||
* TODO: Decide the fate of the size method. | |||
* | |||
* @return size of the data source | |||
*/ | |||
int size(); | |||
/** | |||
* Adds a new DataChangeHandler to this DataSource. DataChangeHandler is | |||
* called when changes occur in DataSource. | |||
* | |||
* @param handler | |||
* data change handler | |||
*/ | |||
Registration addDataChangeHandler(DataChangeHandler<T> handler); | |||
/** | |||
* This method creates a new {@link ListDataSource} from a given Collection. | |||
* The ListDataSource creates a protective List copy of all the contents in | |||
* the Collection. | |||
* This method creates a new {@link InMemoryDataSource} from a given | |||
* Collection. The InMemoryDataSource creates a protective List copy of all | |||
* the contents in the Collection. | |||
* | |||
* @param data | |||
* collection of data | |||
* @return list data source | |||
* @return in-memory data source | |||
*/ | |||
public static <T> ListDataSource<T> create(Collection<T> data) { | |||
return new ListDataSource<>(data); | |||
public static <T> InMemoryDataSource<T> create(Collection<T> data) { | |||
return new InMemoryDataSource<>(data); | |||
} | |||
/** | |||
* This method creates a new {@link ListDataSource} from given objects. | |||
* This method creates a new {@link InMemoryDataSource} from given | |||
* objects.The InMemoryDataSource creates a protective List copy of all the | |||
* contents in the array. | |||
* | |||
* @param data | |||
* data objects | |||
* @return list data source | |||
* @return in-memory data source | |||
*/ | |||
public static <T> ListDataSource<T> create(T... data) { | |||
return new ListDataSource<>(Arrays.asList(data)); | |||
@SafeVarargs | |||
public static <T> InMemoryDataSource<T> create(T... data) { | |||
return new InMemoryDataSource<>(Arrays.asList(data)); | |||
} | |||
} |
@@ -18,6 +18,7 @@ package com.vaadin.tokka.server.communication.data; | |||
import java.util.ArrayList; | |||
import java.util.Collection; | |||
import java.util.List; | |||
import java.util.function.Function; | |||
import java.util.stream.Stream; | |||
/** | |||
@@ -26,9 +27,10 @@ import java.util.stream.Stream; | |||
* @param <T> | |||
* data type | |||
*/ | |||
public class ListDataSource<T> extends AbstractDataSource<T> { | |||
public class InMemoryDataSource<T> implements DataSource<T> { | |||
private List<T> backend; | |||
// FIXME: Missing Query object | |||
private Function<Object, Stream<T>> request; | |||
/** | |||
* Constructs a new ListDataSource. This method makes a protective copy of | |||
@@ -37,35 +39,13 @@ public class ListDataSource<T> extends AbstractDataSource<T> { | |||
* @param collection | |||
* initial data | |||
*/ | |||
public ListDataSource(Collection<T> collection) { | |||
backend = new ArrayList<T>(collection); | |||
public InMemoryDataSource(Collection<T> collection) { | |||
final List<T> backend = new ArrayList<T>(collection); | |||
request = query -> backend.stream(); | |||
} | |||
@Override | |||
public void save(T data) { | |||
if (!backend.contains(data)) { | |||
backend.add(data); | |||
fireDataAppend(data); | |||
} else { | |||
fireDataUpdate(data); | |||
} | |||
} | |||
@Override | |||
public void remove(T data) { | |||
if (backend.contains(data)) { | |||
backend.remove(data); | |||
fireDataRemove(data); | |||
} | |||
} | |||
@Override | |||
public Stream<T> request() { | |||
return backend.stream(); | |||
} | |||
@Override | |||
public int size() { | |||
return backend.size(); | |||
public Stream<T> apply(Object query) { | |||
return request.apply(query); | |||
} | |||
} |
@@ -0,0 +1,55 @@ | |||
package com.vaadin.tokka.data.datasource; | |||
import static org.junit.Assert.assertTrue; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import java.util.stream.Stream; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import com.vaadin.tokka.server.communication.data.DataSource; | |||
import com.vaadin.tokka.server.communication.data.InMemoryDataSource; | |||
public class InMemoryDataSourceTest { | |||
private static class StrBean { | |||
private String value; | |||
public StrBean(String value) { | |||
this.value = value; | |||
} | |||
public String getValue() { | |||
return value; | |||
} | |||
public void setValue(String value) { | |||
this.value = value; | |||
} | |||
} | |||
private InMemoryDataSource<StrBean> dataSource; | |||
private List<StrBean> data; | |||
@Before | |||
public void setUp() { | |||
data = createData(); | |||
dataSource = DataSource.create(data); | |||
} | |||
@Test | |||
public void testListContainsAllData() { | |||
dataSource.apply(null).forEach(str -> assertTrue(data.contains(str))); | |||
} | |||
private List<StrBean> createData() { | |||
List<StrBean> list = new ArrayList<>(); | |||
Stream.of("Foo", "Bar", "Baz").map(StrBean::new).forEach(list::add); | |||
return list; | |||
} | |||
} |
@@ -1,100 +0,0 @@ | |||
package com.vaadin.tokka.data.datasource; | |||
import static org.junit.Assert.assertTrue; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import java.util.stream.Stream; | |||
import org.easymock.EasyMock; | |||
import org.junit.After; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import com.vaadin.tokka.server.communication.data.DataChangeHandler; | |||
import com.vaadin.tokka.server.communication.data.DataSource; | |||
import com.vaadin.tokka.server.communication.data.ListDataSource; | |||
public class ListDataSourceTest { | |||
private static class StrBean { | |||
private String value; | |||
public StrBean(String value) { | |||
this.value = value; | |||
} | |||
public String getValue() { | |||
return value; | |||
} | |||
public void setValue(String value) { | |||
this.value = value; | |||
} | |||
} | |||
private ListDataSource<StrBean> dataSource; | |||
private List<StrBean> data; | |||
private DataChangeHandler<StrBean> handler; | |||
@Before | |||
public void setUp() { | |||
data = createData(); | |||
dataSource = DataSource.create(data); | |||
handler = EasyMock.mock(DataChangeHandler.class); | |||
} | |||
@Test | |||
public void testListContainsAllData() { | |||
EasyMock.replay(handler); | |||
dataSource.request().forEach(str -> assertTrue(data.contains(str))); | |||
} | |||
@Test | |||
public void testListFiresDataAddEvent() { | |||
StrBean strBean = new StrBean("Spam"); | |||
handler.onDataAppend(strBean); | |||
EasyMock.expectLastCall().times(1); | |||
EasyMock.replay(handler); | |||
dataSource.addDataChangeHandler(handler); | |||
dataSource.save(strBean); | |||
} | |||
@Test | |||
public void testListFiresDataRemoveEvent() { | |||
StrBean strBean = data.get(0); | |||
handler.onDataRemove(strBean); | |||
EasyMock.expectLastCall().times(1); | |||
EasyMock.replay(handler); | |||
dataSource.addDataChangeHandler(handler); | |||
dataSource.remove(strBean); | |||
} | |||
@Test | |||
public void testListFiresDataUpdateEvent() { | |||
StrBean strBean = data.get(0); | |||
handler.onDataUpdate(strBean); | |||
EasyMock.expectLastCall().times(1); | |||
EasyMock.replay(handler); | |||
dataSource.addDataChangeHandler(handler); | |||
strBean.setValue("Fuu"); | |||
dataSource.save(strBean); | |||
} | |||
@After | |||
public void tearDown() { | |||
EasyMock.verify(handler); | |||
} | |||
private List<StrBean> createData() { | |||
List<StrBean> list = new ArrayList<>(); | |||
Stream.of("Foo", "Bar", "Baz").map(StrBean::new).forEach(list::add); | |||
return list; | |||
} | |||
} |
@@ -2,15 +2,11 @@ package com.vaadin.tokka.ui.components; | |||
import static org.junit.Assert.assertEquals; | |||
import java.util.stream.Stream; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import com.vaadin.tokka.server.communication.data.AbstractDataSource; | |||
import com.vaadin.tokka.server.communication.data.DataCommunicator; | |||
import com.vaadin.tokka.server.communication.data.DataSource; | |||
import com.vaadin.tokka.ui.components.AbstractListing; | |||
import com.vaadin.tokka.ui.components.AbstractListing.AbstractListingExtension; | |||
import elemental.json.JsonObject; | |||
@@ -49,26 +45,7 @@ public class AbstractListingTest { | |||
@Before | |||
public void setUp() { | |||
testComponent.setDataSource(new AbstractDataSource<String>() { | |||
@Override | |||
public void save(String data) { | |||
} | |||
@Override | |||
public void remove(String data) { | |||
} | |||
@Override | |||
public Stream<String> request() { | |||
return Stream.of("Foo"); | |||
} | |||
@Override | |||
public int size() { | |||
return 1; | |||
} | |||
}); | |||
testComponent.setDataSource(DataSource.create("Foo")); | |||
} | |||
@Test |
@@ -62,7 +62,6 @@ public class ListingTestUI extends AbstractTestUI { | |||
.nextInt(100))); | |||
} | |||
return beans; | |||
} | |||
} | |||
@@ -82,8 +81,8 @@ public class ListingTestUI extends AbstractTestUI { | |||
.forEach(s -> Notification.show(s)))); | |||
hLayout.addComponent(new Button("Random select", e -> { | |||
DataSource<String> ds = select.getDataSource(); | |||
int skip = r.nextInt(ds.size()); | |||
ds.request().skip(skip).findFirst() | |||
int skip = r.nextInt((int) ds.apply(null).count()); | |||
ds.apply(null).skip(skip).findFirst() | |||
.ifPresent(select.getSelectionModel()::select); | |||
})); | |||