diff options
author | John Ahlroos <john@vaadin.com> | 2013-11-20 14:26:40 +0200 |
---|---|---|
committer | John Ahlroos <john@vaadin.com> | 2013-11-20 14:27:09 +0200 |
commit | e3b1e6be389cbe48d1782723adf1595edbd10ea2 (patch) | |
tree | 61dbd2fac407f0b6657863d100d6b1f09e67e356 /server | |
parent | 96de019ea4ee5536dab87d1f04d4cb94ae71371d (diff) | |
parent | dd7e6fee8c4717df59699f04f7e72a6f2e92008f (diff) | |
download | vaadin-framework-e3b1e6be389cbe48d1782723adf1595edbd10ea2.tar.gz vaadin-framework-e3b1e6be389cbe48d1782723adf1595edbd10ea2.zip |
Merge branch 'master' into grid
Change-Id: I9f669ec38c39a42d1ef2a25121b77aab31551863
Diffstat (limited to 'server')
32 files changed, 1247 insertions, 245 deletions
diff --git a/server/ivy.xml b/server/ivy.xml index b78541c52f..ba859b08ee 100644 --- a/server/ivy.xml +++ b/server/ivy.xml @@ -58,7 +58,7 @@ <!-- TESTING DEPENDENCIES --> <!-- Test frameworks & related --> - <dependency org="junit" name="junit" rev="4.5" + <dependency org="junit" name="junit" rev="4.11" conf="test,ide -> default" /> <dependency org="org.easymock" name="easymock" rev="3.0" conf="test,ide-> default" transitive="true" /> diff --git a/server/src/com/vaadin/data/Container.java b/server/src/com/vaadin/data/Container.java index e93db52a35..bf553f31d2 100644 --- a/server/src/com/vaadin/data/Container.java +++ b/server/src/com/vaadin/data/Container.java @@ -582,6 +582,60 @@ public interface Container extends Serializable { public Item addItemAt(int index, Object newItemId) throws UnsupportedOperationException; + /** + * An <code>Event</code> object specifying information about the added + * items. + */ + public interface ItemAddEvent extends ItemSetChangeEvent { + + /** + * Gets the item id of the first added item. + * + * @return item id of the first added item + */ + public Object getFirstItemId(); + + /** + * Gets the index of the first added item. + * + * @return index of the first added item + */ + public int getFirstIndex(); + + /** + * Gets the number of the added items. + * + * @return the number of added items. + */ + public int getAddedItemsCount(); + } + + /** + * An <code>Event</code> object specifying information about the removed + * items. + */ + public interface ItemRemoveEvent extends ItemSetChangeEvent { + /** + * Gets the item id of the first removed item. + * + * @return item id of the first removed item + */ + public Object getFirstItemId(); + + /** + * Gets the index of the first removed item. + * + * @return index of the first removed item + */ + public int getFirstIndex(); + + /** + * Gets the number of the removed items. + * + * @return the number of removed items + */ + public int getRemovedItemsCount(); + } } /** diff --git a/server/src/com/vaadin/data/util/AbstractBeanContainer.java b/server/src/com/vaadin/data/util/AbstractBeanContainer.java index cd5c0c809d..67239996a2 100644 --- a/server/src/com/vaadin/data/util/AbstractBeanContainer.java +++ b/server/src/com/vaadin/data/util/AbstractBeanContainer.java @@ -222,6 +222,7 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends @Override public boolean removeAllItems() { int origSize = size(); + IDTYPE firstItem = getFirstVisibleItem(); internalRemoveAllItems(); @@ -234,7 +235,7 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends // fire event only if the visible view changed, regardless of whether // filtered out items were removed or not if (origSize != 0) { - fireItemSetChange(); + fireItemsRemoved(0, firstItem, origSize); } return true; @@ -679,6 +680,8 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends protected void addAll(Collection<? extends BEANTYPE> collection) throws IllegalStateException, IllegalArgumentException { boolean modified = false; + int origSize = size(); + for (BEANTYPE bean : collection) { // TODO skipping invalid beans - should not allow them in javadoc? if (bean == null @@ -699,13 +702,22 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends if (modified) { // Filter the contents when all items have been added if (isFiltered()) { - filterAll(); - } else { - fireItemSetChange(); + doFilterContainer(!getFilters().isEmpty()); + } + if (visibleNewItemsWasAdded(origSize)) { + // fire event about added items + int firstPosition = origSize; + IDTYPE firstItemId = getVisibleItemIds().get(firstPosition); + int affectedItems = size() - origSize; + fireItemsAdded(firstPosition, firstItemId, affectedItems); } } } + private boolean visibleNewItemsWasAdded(int origSize) { + return size() > origSize; + } + /** * Use the bean resolver to get the identifier for a bean. * diff --git a/server/src/com/vaadin/data/util/AbstractInMemoryContainer.java b/server/src/com/vaadin/data/util/AbstractInMemoryContainer.java index 84304431bc..9a7922b928 100644 --- a/server/src/com/vaadin/data/util/AbstractInMemoryContainer.java +++ b/server/src/com/vaadin/data/util/AbstractInMemoryContainer.java @@ -15,8 +15,10 @@ */ package com.vaadin.data.util; +import java.io.Serializable; import java.util.Collection; import java.util.Collections; +import java.util.EventObject; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; @@ -146,6 +148,85 @@ public abstract class AbstractInMemoryContainer<ITEMIDTYPE, PROPERTYIDCLASS, ITE } } + private static abstract class BaseItemAddOrRemoveEvent extends + EventObject implements Serializable { + protected Object itemId; + protected int index; + protected int count; + + public BaseItemAddOrRemoveEvent(Container source, Object itemId, + int index, int count) { + super(source); + this.itemId = itemId; + this.index = index; + this.count = count; + } + + public Container getContainer() { + return (Container) getSource(); + } + + public Object getFirstItemId() { + return itemId; + } + + public int getFirstIndex() { + return index; + } + + public int getAffectedItemsCount() { + return count; + } + } + + /** + * An <code>Event</code> object specifying information about the added + * items. + * + * <p> + * This class provides information about the first added item and the number + * of added items. + * </p> + */ + protected static class BaseItemAddEvent extends + BaseItemAddOrRemoveEvent implements + Container.Indexed.ItemAddEvent { + + public BaseItemAddEvent(Container source, Object itemId, int index, + int count) { + super(source, itemId, index, count); + } + + @Override + public int getAddedItemsCount() { + return getAffectedItemsCount(); + } + } + + /** + * An <code>Event</code> object specifying information about the removed + * items. + * + * <p> + * This class provides information about the first removed item and the + * number of removed items. + * </p> + */ + protected static class BaseItemRemoveEvent extends + BaseItemAddOrRemoveEvent implements + Container.Indexed.ItemRemoveEvent { + + public BaseItemRemoveEvent(Container source, Object itemId, + int index, int count) { + super(source, itemId, index, count); + } + + @Override + public int getRemovedItemsCount() { + return getAffectedItemsCount(); + } + } + /** * Get an item even if filtered out. * @@ -898,36 +979,69 @@ public abstract class AbstractInMemoryContainer<ITEMIDTYPE, PROPERTYIDCLASS, ITE * Notify item set change listeners that an item has been added to the * container. * - * Unless subclasses specify otherwise, the default notification indicates a - * full refresh. - * * @param postion - * position of the added item in the view (if visible) + * position of the added item in the view * @param itemId * id of the added item * @param item * the added item */ protected void fireItemAdded(int position, ITEMIDTYPE itemId, ITEMCLASS item) { - fireItemSetChange(); + fireItemsAdded(position, itemId, 1); + } + + /** + * Notify item set change listeners that items has been added to the + * container. + * + * @param firstPosition + * position of the first visible added item in the view + * @param firstItemId + * id of the first visible added item + * @param numberOfItems + * the number of visible added items + */ + protected void fireItemsAdded(int firstPosition, ITEMIDTYPE firstItemId, + int numberOfItems) { + BaseItemAddEvent addEvent = new BaseItemAddEvent(this, + firstItemId, firstPosition, numberOfItems); + fireItemSetChange(addEvent); } /** * Notify item set change listeners that an item has been removed from the * container. * - * Unless subclasses specify otherwise, the default notification indicates a - * full refresh. + * @param position + * position of the removed item in the view prior to removal * - * @param postion - * position of the removed item in the view prior to removal (if - * was visible) * @param itemId * id of the removed item, of type {@link Object} to satisfy * {@link Container#removeItem(Object)} API */ protected void fireItemRemoved(int position, Object itemId) { - fireItemSetChange(); + fireItemsRemoved(position, itemId, 1); + } + + /** + * Notify item set change listeners that items has been removed from the + * container. + * + * @param firstPosition + * position of the first visible removed item in the view prior + * to removal + * @param firstItemId + * id of the first visible removed item, of type {@link Object} + * to satisfy {@link Container#removeItem(Object)} API + * @param numberOfItems + * the number of removed visible items + * + */ + protected void fireItemsRemoved(int firstPosition, Object firstItemId, + int numberOfItems) { + BaseItemRemoveEvent removeEvent = new BaseItemRemoveEvent(this, + firstItemId, firstPosition, numberOfItems); + fireItemSetChange(removeEvent); } // visible and filtered item identifier lists @@ -946,6 +1060,21 @@ public abstract class AbstractInMemoryContainer<ITEMIDTYPE, PROPERTYIDCLASS, ITE } /** + * Returns the item id of the first visible item after filtering. 'Null' is + * returned if there is no visible items. + * + * For internal use only. + * + * @return item id of the first visible item + */ + protected ITEMIDTYPE getFirstVisibleItem() { + if (!getVisibleItemIds().isEmpty()) { + return getVisibleItemIds().get(0); + } + return null; + } + + /** * Returns true is the container has active filters. * * @return true if the container is currently filtered diff --git a/server/src/com/vaadin/data/util/IndexedContainer.java b/server/src/com/vaadin/data/util/IndexedContainer.java index d7bf70caf6..5d20919208 100644 --- a/server/src/com/vaadin/data/util/IndexedContainer.java +++ b/server/src/com/vaadin/data/util/IndexedContainer.java @@ -226,6 +226,7 @@ public class IndexedContainer extends @Override public boolean removeAllItems() { int origSize = size(); + Object firstItem = getFirstVisibleItem(); internalRemoveAllItems(); @@ -235,7 +236,7 @@ public class IndexedContainer extends // filtered out items were removed or not if (origSize != 0) { // Sends a change event - fireItemSetChange(); + fireItemsRemoved(0, firstItem, origSize); } return true; @@ -620,8 +621,7 @@ public class IndexedContainer extends @Override protected void fireItemAdded(int position, Object itemId, Item item) { if (position >= 0) { - fireItemSetChange(new IndexedContainer.ItemSetChangeEvent(this, - position)); + super.fireItemAdded(position, itemId, item); } } @@ -1211,4 +1211,5 @@ public class IndexedContainer extends public Collection<Filter> getContainerFilters() { return super.getContainerFilters(); } + } diff --git a/server/src/com/vaadin/event/ConnectorActionManager.java b/server/src/com/vaadin/event/ConnectorActionManager.java new file mode 100644 index 0000000000..297f78f179 --- /dev/null +++ b/server/src/com/vaadin/event/ConnectorActionManager.java @@ -0,0 +1,88 @@ +/* + * Copyright 2000-2013 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 java.util.logging.Logger; + +import com.vaadin.event.Action.Container; +import com.vaadin.server.ClientConnector; +import com.vaadin.server.VariableOwner; +import com.vaadin.server.communication.ServerRpcHandler; +import com.vaadin.ui.Component; + +/** + * An ActionManager connected to a connector. Takes care of verifying that the + * connector can receive events before triggering an action. + * <p> + * This is mostly a workaround until shortcut actions are re-implemented in a + * more sensible way. + * + * @since 7.1.8 + * @author Vaadin Ltd + */ +public class ConnectorActionManager extends ActionManager { + + private ClientConnector connector; + + /** + * Initialize an action manager for the given connector. + * + * @param connector + * the owner of this action manager + */ + public ConnectorActionManager(ClientConnector connector) { + super(); + this.connector = connector; + } + + /** + * Initialize an action manager for the given connector using the given + * viewer. + * + * @param connector + * the owner of this action manager + * @param viewer + * the viewer connected + */ + public <T extends Component & Container & VariableOwner> ConnectorActionManager( + ClientConnector connector, T viewer) { + super(viewer); + this.connector = connector; + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.event.ActionManager#handleAction(com.vaadin.event.Action, + * java.lang.Object, java.lang.Object) + */ + @Override + public void handleAction(Action action, Object sender, Object target) { + if (!connector.isConnectorEnabled()) { + getLogger().warning( + ServerRpcHandler.getIgnoredDisabledError("action", + connector)); + return; + } + + super.handleAction(action, sender, target); + } + + private static final Logger getLogger() { + return Logger.getLogger(ConnectorActionManager.class.getName()); + } + +} diff --git a/server/src/com/vaadin/event/UIEvents.java b/server/src/com/vaadin/event/UIEvents.java new file mode 100644 index 0000000000..321bfc9251 --- /dev/null +++ b/server/src/com/vaadin/event/UIEvents.java @@ -0,0 +1,116 @@ +/* + * Copyright 2000-2013 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 java.io.Serializable; +import java.lang.reflect.Method; + +import com.vaadin.ui.Component; +import com.vaadin.ui.UI; +import com.vaadin.util.ReflectTools; + +/** + * A class that contains events, listeners and handlers specific to the + * {@link UI} class. + * + * @since 7.2 + * @author Vaadin Ltd + */ +public interface UIEvents { + + /** + * A {@link PollListener} receives and handles {@link PollEvent PollEvents} + * fired by {@link PollNotifier PollNotifiers}. + * + * @since 7.2 + * @author Vaadin Ltd + */ + public interface PollListener extends Serializable { + public static final Method POLL_METHOD = ReflectTools.findMethod( + PollListener.class, "poll", PollEvent.class); + + /** + * A poll request has been received by the server. + * + * @param event + * poll event + */ + public void poll(PollEvent event); + } + + /** + * An event that is fired whenever a client polls the server for + * asynchronous UI updates. + * + * @since 7.2 + * @author Vaadin Ltd + */ + public static class PollEvent extends Component.Event { + public PollEvent(UI ui) { + super(ui); + } + + /** + * Get the {@link UI} instance that received the poll request. + * + * @return the {@link UI} that received the poll request. Never + * <code>null</code>. + */ + public UI getUI() { + /* + * This cast is safe to make, since this class' constructor + * constrains the source to be a UI instance. + */ + return (UI) getComponent(); + } + } + + /** + * The interface for adding and removing {@link PollEvent} listeners. + * <p> + * By implementing this interface, a class publicly announces that it is + * able to send {@link PollEvent PollEvents} whenever the client sends a + * periodic poll message to the client, to check for asynchronous + * server-side modifications. + * + * @since 7.2 + * @see UI#setPollInterval(int) + */ + public interface PollNotifier extends Serializable { + /** + * Add a poll listener. + * <p> + * The listener is called whenever the client polls the server for + * asynchronous UI updates. + * + * @see UI#setPollInterval(int) + * @see #removePollListener(PollListener) + * @param listener + * the {@link PollListener} to add + */ + public void addPollListener(PollListener listener); + + /** + * Remove a poll listener. + * + * @see #addPollListener(PollListener) + * @param listener + * the listener to be removed + */ + public void removePollListener(PollListener listener); + } + +} diff --git a/server/src/com/vaadin/server/ClientMethodInvocation.java b/server/src/com/vaadin/server/ClientMethodInvocation.java index 9c8318b064..3a6a87a53c 100644 --- a/server/src/com/vaadin/server/ClientMethodInvocation.java +++ b/server/src/com/vaadin/server/ClientMethodInvocation.java @@ -16,10 +16,16 @@ package com.vaadin.server; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.Method; import java.lang.reflect.Type; +import org.json.JSONArray; +import org.json.JSONException; + /** * Internal class for keeping track of pending server to client method * invocations for a Connector. @@ -80,4 +86,52 @@ public class ClientMethodInvocation implements Serializable, } return Long.signum(getSequenceNumber() - o.getSequenceNumber()); } + + private void writeObject(ObjectOutputStream stream) throws IOException { + // Need to have custom serialization and deserialization because the + // constructor allows parameters of any type with Object[]. Thus, having + // parameters that are not Serializable will lead to + // NotSerializableException when trying to serialize this class. + // An example case of this is in #12532 (JavaScriptCallbackHelper -> + // JSONArray as parameter and not Serializable), for which this + // hac..workaround is implemented. + + // Goes through the parameter types, and apply "custom serialization" to + // the ones that are not Serializable by changing them into something + // that is Serializable. On deserialization (readObject-method below) + // the process should be reversed. + + // Easy way for implementing serialization & deserialization is by + // writing/parsing the object's content as string. + for (int i = 0; i < parameterTypes.length; i++) { + Type type = parameterTypes[i]; + if (type instanceof Class<?>) { + Class<?> clazz = (Class<?>) type; + if (JSONArray.class.isAssignableFrom(clazz)) { + parameters[i] = ((JSONArray) parameters[i]).toString(); + } + } + } + stream.defaultWriteObject(); + } + + private void readObject(ObjectInputStream stream) throws IOException, + ClassNotFoundException { + // Reverses the serialization done in writeObject. Basically just + // parsing the serialized type back to the non-serializable type. + stream.defaultReadObject(); + for (int i = 0; i < parameterTypes.length; i++) { + Type type = parameterTypes[i]; + if (type instanceof Class<?>) { + Class<?> clazz = (Class<?>) type; + if (JSONArray.class.isAssignableFrom(clazz)) { + try { + parameters[i] = new JSONArray(((String) parameters[i])); + } catch (JSONException e) { + throw new IOException(e); + } + } + } + } + } }
\ No newline at end of file diff --git a/server/src/com/vaadin/server/Constants.java b/server/src/com/vaadin/server/Constants.java index 8c379abe06..b0841da314 100644 --- a/server/src/com/vaadin/server/Constants.java +++ b/server/src/com/vaadin/server/Constants.java @@ -67,7 +67,7 @@ public interface Constants { // Keep the version number in sync with push/build.xml and other locations // listed in that file - static final String REQUIRED_ATMOSPHERE_VERSION = "1.0.14.vaadin4"; + static final String REQUIRED_ATMOSPHERE_RUNTIME_VERSION = "1.0.18.vaadin1"; static final String INVALID_ATMOSPHERE_VERSION_WARNING = "\n" + "=================================================================\n" @@ -82,7 +82,7 @@ public interface Constants { + "If using a dependency management system, please add a dependency\n" + "to vaadin-push.\n" + "If managing dependencies manually, please make sure Atmosphere\n" - + REQUIRED_ATMOSPHERE_VERSION + + REQUIRED_ATMOSPHERE_RUNTIME_VERSION + " is included on the classpath.\n" + "Will fall back to using " + PushMode.class.getSimpleName() diff --git a/server/src/com/vaadin/server/ErrorHandlingRunnable.java b/server/src/com/vaadin/server/ErrorHandlingRunnable.java new file mode 100644 index 0000000000..3970a14ee8 --- /dev/null +++ b/server/src/com/vaadin/server/ErrorHandlingRunnable.java @@ -0,0 +1,38 @@ +/* + * Copyright 2000-2013 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.server; + +import java.io.Serializable; + +/** + * Defines the interface to handle exceptions thrown during the execution of a + * FutureAccess. + * + * @since 7.1.8 + * @author Vaadin Ltd + */ +public interface ErrorHandlingRunnable extends Runnable, Serializable { + + /** + * Handles exceptions thrown during the execution of a FutureAccess. + * + * @since 7.1.8 + * @param exception + * the thrown exception. + */ + public void handleError(Exception exception); + +} diff --git a/server/src/com/vaadin/server/VaadinPortlet.java b/server/src/com/vaadin/server/VaadinPortlet.java index adef90c45f..a41f301219 100644 --- a/server/src/com/vaadin/server/VaadinPortlet.java +++ b/server/src/com/vaadin/server/VaadinPortlet.java @@ -422,16 +422,37 @@ public class VaadinPortlet extends GenericPortlet implements Constants, * @return A wrapped version of the PorletRequest */ protected VaadinPortletRequest createVaadinRequest(PortletRequest request) { - String portalInfo = request.getPortalContext().getPortalInfo() - .toLowerCase(); - if (portalInfo.contains("liferay")) { + if (isLiferay(request)) { return new VaadinLiferayRequest(request, getService()); - } else if (portalInfo.contains("gatein")) { + } else if (isGateIn(request)) { return new VaadinGateinRequest(request, getService()); } else { return new VaadinPortletRequest(request, getService()); } + } + /** + * Returns true if the portlet request is from Liferay. + * + * @param request + * @return True if Liferay, false otherwise + */ + private static boolean isLiferay(PortletRequest request) { + String portalInfo = request.getPortalContext().getPortalInfo() + .toLowerCase(); + return portalInfo.contains("liferay"); + } + + /** + * Returns true if the portlet request if from GateIn + * + * @param request + * @return True if GateIn, false otherwise + */ + private static boolean isGateIn(PortletRequest request) { + String portalInfo = request.getPortalContext().getPortalInfo() + .toLowerCase(); + return portalInfo.contains("gatein"); } private VaadinPortletResponse createVaadinResponse(PortletResponse response) { diff --git a/server/src/com/vaadin/server/VaadinService.java b/server/src/com/vaadin/server/VaadinService.java index cf6c806ead..aff0124d16 100644 --- a/server/src/com/vaadin/server/VaadinService.java +++ b/server/src/com/vaadin/server/VaadinService.java @@ -1736,6 +1736,13 @@ public abstract class VaadinService implements Serializable { .getCurrentInstances()); CurrentInstance.setCurrent(session); pendingAccess.run(); + + try { + pendingAccess.get(); + + } catch (Exception exception) { + pendingAccess.handleError(exception); + } } } } finally { diff --git a/server/src/com/vaadin/server/VaadinServlet.java b/server/src/com/vaadin/server/VaadinServlet.java index d34cd3bf0e..baf97d23d9 100644 --- a/server/src/com/vaadin/server/VaadinServlet.java +++ b/server/src/com/vaadin/server/VaadinServlet.java @@ -426,8 +426,6 @@ public class VaadinServlet extends HttpServlet implements Constants { outWriter.print(output); outWriter.flush(); outWriter.close(); - out.flush(); - } /** diff --git a/server/src/com/vaadin/server/VaadinServletService.java b/server/src/com/vaadin/server/VaadinServletService.java index 3b39f17849..818f2e87c6 100644 --- a/server/src/com/vaadin/server/VaadinServletService.java +++ b/server/src/com/vaadin/server/VaadinServletService.java @@ -66,11 +66,11 @@ public class VaadinServletService extends VaadinService { private static boolean checkAtmosphereSupport() { try { String rawVersion = Version.getRawVersion(); - if (!Constants.REQUIRED_ATMOSPHERE_VERSION.equals(rawVersion)) { + if (!Constants.REQUIRED_ATMOSPHERE_RUNTIME_VERSION.equals(rawVersion)) { getLogger().log( Level.WARNING, Constants.INVALID_ATMOSPHERE_VERSION_WARNING, - new Object[] { Constants.REQUIRED_ATMOSPHERE_VERSION, + new Object[] { Constants.REQUIRED_ATMOSPHERE_RUNTIME_VERSION, rawVersion }); } return true; diff --git a/server/src/com/vaadin/server/VaadinSession.java b/server/src/com/vaadin/server/VaadinSession.java index 8f15dacc98..f34721944a 100644 --- a/server/src/com/vaadin/server/VaadinSession.java +++ b/server/src/com/vaadin/server/VaadinSession.java @@ -16,6 +16,8 @@ package com.vaadin.server; +import java.io.IOException; +import java.io.ObjectInputStream; import java.io.Serializable; import java.lang.reflect.Method; import java.util.Collection; @@ -33,6 +35,7 @@ import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Level; import java.util.logging.Logger; import javax.portlet.PortletSession; @@ -83,6 +86,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable { private final Map<Class<?>, CurrentInstance> instances = CurrentInstance .getInstances(true); private final VaadinSession session; + private Runnable runnable; /** * Creates an instance for the given runnable @@ -97,6 +101,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable { public FutureAccess(VaadinSession session, Runnable runnable) { super(runnable, null); this.session = session; + this.runnable = runnable; } @Override @@ -126,6 +131,36 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable { public Map<Class<?>, CurrentInstance> getCurrentInstances() { return instances; } + + /** + * Handles exceptions thrown during the execution of this task. + * + * @since 7.1.8 + * @param exception + * the thrown exception. + */ + public void handleError(Exception exception) { + try { + if (runnable instanceof ErrorHandlingRunnable) { + ErrorHandlingRunnable errorHandlingRunnable = (ErrorHandlingRunnable) runnable; + + errorHandlingRunnable.handleError(exception); + } else { + ErrorEvent errorEvent = new ErrorEvent(exception); + + ErrorHandler errorHandler = ErrorEvent + .findErrorHandler(session); + + if (errorHandler == null) { + errorHandler = new DefaultErrorHandler(); + } + + errorHandler.error(errorEvent); + } + } catch (Exception e) { + getLogger().log(Level.SEVERE, e.getMessage(), e); + } + } } /** @@ -202,10 +237,10 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable { * session is serialized as long as it doesn't happen while some other * thread has the lock. */ - private transient ConcurrentLinkedQueue<FutureAccess> pendingAccessQueue; + private transient ConcurrentLinkedQueue<FutureAccess> pendingAccessQueue = new ConcurrentLinkedQueue<FutureAccess>(); /** - * Create a new service session tied to a Vaadin service + * Creates a new VaadinSession tied to a VaadinService. * * @param service * the Vaadin service for the new session @@ -1260,18 +1295,15 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable { } /** - * Gets the queue of tasks submitted using {@link #access(Runnable)}. + * Gets the queue of tasks submitted using {@link #access(Runnable)}. It is + * safe to call this method and access the returned queue without holding + * the {@link #lock() session lock}. * * @since 7.1 * - * @return the pending access queue + * @return the queue of pending access tasks */ public Queue<FutureAccess> getPendingAccessQueue() { - if (pendingAccessQueue == null) { - // pendingAccessQueue is transient, so will be null after - // deserialization - pendingAccessQueue = new ConcurrentLinkedQueue<FutureAccess>(); - } return pendingAccessQueue; } @@ -1288,6 +1320,16 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable { } /** + * Override default deserialization logic to account for transient + * {@link #pendingAccessQueue}. + */ + private void readObject(ObjectInputStream stream) throws IOException, + ClassNotFoundException { + stream.defaultReadObject(); + pendingAccessQueue = new ConcurrentLinkedQueue<FutureAccess>(); + } + + /** * Finds the UI with the corresponding embed id. * * @since 7.2 diff --git a/server/src/com/vaadin/server/communication/PortletBootstrapHandler.java b/server/src/com/vaadin/server/communication/PortletBootstrapHandler.java index 2458951ada..dd6d3c9283 100644 --- a/server/src/com/vaadin/server/communication/PortletBootstrapHandler.java +++ b/server/src/com/vaadin/server/communication/PortletBootstrapHandler.java @@ -31,6 +31,7 @@ import org.json.JSONObject; import com.vaadin.server.BootstrapHandler; import com.vaadin.server.PaintException; import com.vaadin.server.VaadinPortlet; +import com.vaadin.server.VaadinPortlet.VaadinLiferayRequest; import com.vaadin.server.VaadinPortletRequest; import com.vaadin.server.VaadinPortletResponse; import com.vaadin.server.VaadinRequest; @@ -98,6 +99,8 @@ public class PortletBootstrapHandler extends BootstrapHandler { JSONObject parameters = super.getApplicationParameters(context); VaadinPortletResponse response = (VaadinPortletResponse) context .getResponse(); + VaadinPortletRequest request = (VaadinPortletRequest) context + .getRequest(); MimeResponse portletResponse = (MimeResponse) response .getPortletResponse(); ResourceURL resourceURL = portletResponse.createResourceURL(); @@ -108,6 +111,14 @@ public class PortletBootstrapHandler extends BootstrapHandler { parameters .put(ApplicationConstants.SERVICE_URL_PATH_AS_PARAMETER, true); + // If we are running in Liferay then we need to prefix all parameters + // with the portlet namespace + if (request instanceof VaadinLiferayRequest) { + parameters.put( + ApplicationConstants.SERVICE_URL_PARAMETER_NAMESPACE, + response.getPortletResponse().getNamespace()); + } + return parameters; } }
\ No newline at end of file diff --git a/server/src/com/vaadin/server/communication/PushHandler.java b/server/src/com/vaadin/server/communication/PushHandler.java index 81dd00084d..09428e47a9 100644 --- a/server/src/com/vaadin/server/communication/PushHandler.java +++ b/server/src/com/vaadin/server/communication/PushHandler.java @@ -31,6 +31,8 @@ import org.atmosphere.cpr.AtmosphereResourceEvent; import org.atmosphere.cpr.AtmosphereResourceEventListenerAdapter; import org.json.JSONException; +import com.vaadin.server.ErrorEvent; +import com.vaadin.server.ErrorHandler; import com.vaadin.server.LegacyCommunicationManager.InvalidUIDLSecurityKeyException; import com.vaadin.server.ServiceException; import com.vaadin.server.ServletPortletHelper; @@ -262,11 +264,12 @@ public class PushHandler extends AtmosphereResourceEventListenerAdapter return; } + UI ui = null; session.lock(); try { VaadinSession.setCurrent(session); // Sets UI.currentInstance - final UI ui = service.findUI(vaadinRequest); + ui = service.findUI(vaadinRequest); if (ui == null) { sendNotificationAndDisconnect(resource, UidlRequestHandler.getUINotFoundErrorJSON(service, @@ -274,14 +277,63 @@ public class PushHandler extends AtmosphereResourceEventListenerAdapter } else { callback.run(resource, ui); } - } catch (IOException e) { - getLogger().log(Level.WARNING, "Error writing a push response", - e); + } catch (final IOException e) { + callErrorHandler(session, e); + } catch (final Exception e) { + SystemMessages msg = service.getSystemMessages( + ServletPortletHelper.findLocale(null, null, + vaadinRequest), vaadinRequest); + + AtmosphereResource errorResource = resource; + if (ui != null && ui.getPushConnection() != null) { + // We MUST use the opened push connection if there is one. + // Otherwise we will write the response to the wrong request + // when using streaming (the client -> server request + // instead of the opened push channel) + errorResource = ((AtmospherePushConnection) ui + .getPushConnection()).getResource(); + } + + sendNotificationAndDisconnect( + errorResource, + VaadinService.createCriticalNotificationJSON( + msg.getInternalErrorCaption(), + msg.getInternalErrorMessage(), null, + msg.getInternalErrorURL())); + callErrorHandler(session, e); } finally { - session.unlock(); + try { + session.unlock(); + } catch (Exception e) { + getLogger().log(Level.WARNING, + "Error while unlocking session", e); + // can't call ErrorHandler, we (hopefully) don't have a lock + } } } finally { - service.requestEnd(vaadinRequest, null, session); + try { + service.requestEnd(vaadinRequest, null, session); + } catch (Exception e) { + getLogger().log(Level.WARNING, "Error while ending request", e); + + // can't call ErrorHandler, we don't have a lock + } + } + } + + /** + * Call the session's {@link ErrorHandler}, if it has one, with the given + * exception wrapped in an {@link ErrorEvent}. + */ + private void callErrorHandler(VaadinSession session, Exception e) { + try { + ErrorHandler errorHandler = ErrorEvent.findErrorHandler(session); + if (errorHandler != null) { + errorHandler.error(new ErrorEvent(e)); + } + } catch (Exception ex) { + // Let's not allow error handling to cause trouble; log fails + getLogger().log(Level.WARNING, "ErrorHandler call failed", ex); } } diff --git a/server/src/com/vaadin/server/communication/ServerRpcHandler.java b/server/src/com/vaadin/server/communication/ServerRpcHandler.java index eff9ceebf4..432a9ea893 100644 --- a/server/src/com/vaadin/server/communication/ServerRpcHandler.java +++ b/server/src/com/vaadin/server/communication/ServerRpcHandler.java @@ -248,15 +248,8 @@ public class ServerRpcHandler implements Serializable { } // Connector is disabled, log a warning and move to the next - String msg = "Ignoring RPC call for disabled connector " - + connector.getClass().getName(); - if (connector instanceof Component) { - String caption = ((Component) connector).getCaption(); - if (caption != null) { - msg += ", caption=" + caption; - } - } - getLogger().warning(msg); + getLogger().warning( + getIgnoredDisabledError("RPC call", connector)); continue; } // DragAndDropService has null UI @@ -495,4 +488,26 @@ public class ServerRpcHandler implements Serializable { private static final Logger getLogger() { return Logger.getLogger(ServerRpcHandler.class.getName()); } + + /** + * Generates an error message when the client is trying to to something + * ('what') with a connector which is disabled or invisible. + * + * @since 7.1.8 + * @param connector + * the connector which is disabled (or invisible) + * @return an error message + */ + public static String getIgnoredDisabledError(String what, + ClientConnector connector) { + String msg = "Ignoring " + what + " for disabled connector " + + connector.getClass().getName(); + if (connector instanceof Component) { + String caption = ((Component) connector).getCaption(); + if (caption != null) { + msg += ", caption=" + caption; + } + } + return msg; + } } diff --git a/server/src/com/vaadin/ui/AbstractComponent.java b/server/src/com/vaadin/ui/AbstractComponent.java index 262d47af18..61bcf00ad8 100644 --- a/server/src/com/vaadin/ui/AbstractComponent.java +++ b/server/src/com/vaadin/ui/AbstractComponent.java @@ -27,6 +27,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import com.vaadin.event.ActionManager; +import com.vaadin.event.ConnectorActionManager; import com.vaadin.event.ShortcutListener; import com.vaadin.server.AbstractClientConnector; import com.vaadin.server.ComponentSizeValidator; @@ -90,7 +91,7 @@ public abstract class AbstractComponent extends AbstractClientConnector * Keeps track of the Actions added to this component; the actual * handling/notifying is delegated, usually to the containing window. */ - private ActionManager actionManager; + private ConnectorActionManager actionManager; private boolean visible = true; @@ -929,7 +930,7 @@ public abstract class AbstractComponent extends AbstractClientConnector */ protected ActionManager getActionManager() { if (actionManager == null) { - actionManager = new ActionManager(); + actionManager = new ConnectorActionManager(this); setActionManagerViewer(); } return actionManager; diff --git a/server/src/com/vaadin/ui/Link.java b/server/src/com/vaadin/ui/Link.java index cf8e1a9693..e1a47777bd 100644 --- a/server/src/com/vaadin/ui/Link.java +++ b/server/src/com/vaadin/ui/Link.java @@ -16,13 +16,10 @@ package com.vaadin.ui; -import java.util.Map; - -import com.vaadin.server.PaintException; -import com.vaadin.server.PaintTarget; import com.vaadin.server.Resource; import com.vaadin.shared.ui.BorderStyle; import com.vaadin.shared.ui.link.LinkConstants; +import com.vaadin.shared.ui.link.LinkState; /** * Link is used to create external or internal URL links. @@ -31,7 +28,7 @@ import com.vaadin.shared.ui.link.LinkConstants; * @since 3.0 */ @SuppressWarnings("serial") -public class Link extends AbstractComponent implements LegacyComponent { +public class Link extends AbstractComponent { /** * @deprecated As of 7.0, use {@link BorderStyle#NONE} instead @@ -51,14 +48,6 @@ public class Link extends AbstractComponent implements LegacyComponent { @Deprecated public static final BorderStyle TARGET_BORDER_DEFAULT = BorderStyle.DEFAULT; - private String targetName; - - private BorderStyle targetBorder = BorderStyle.DEFAULT; - - private int targetWidth = -1; - - private int targetHeight = -1; - /** * Creates a new link. */ @@ -105,43 +94,14 @@ public class Link extends AbstractComponent implements LegacyComponent { setTargetBorder(border); } - /** - * Paints the content of this component. - * - * @param target - * the Paint Event. - * @throws PaintException - * if the paint operation failed. - */ @Override - public void paintContent(PaintTarget target) throws PaintException { - if (getResource() == null) { - return; - } - - // Target window name - final String name = getTargetName(); - if (name != null && name.length() > 0) { - target.addAttribute("name", name); - } - - // Target window size - if (getTargetWidth() >= 0) { - target.addAttribute("targetWidth", getTargetWidth()); - } - if (getTargetHeight() >= 0) { - target.addAttribute("targetHeight", getTargetHeight()); - } - - // Target window border - switch (getTargetBorder()) { - case MINIMAL: - target.addAttribute("border", "minimal"); - break; - case NONE: - target.addAttribute("border", "none"); - break; - } + protected LinkState getState() { + return (LinkState) super.getState(); + } + + @Override + protected LinkState getState(boolean markAsDirty) { + return (LinkState) super.getState(markAsDirty); } /** @@ -150,7 +110,7 @@ public class Link extends AbstractComponent implements LegacyComponent { * @return the target window border. */ public BorderStyle getTargetBorder() { - return targetBorder; + return getState(false).targetBorder; } /** @@ -159,7 +119,8 @@ public class Link extends AbstractComponent implements LegacyComponent { * @return the target window height. */ public int getTargetHeight() { - return targetHeight < 0 ? -1 : targetHeight; + return getState(false).targetHeight < 0 ? -1 + : getState(false).targetHeight; } /** @@ -169,7 +130,7 @@ public class Link extends AbstractComponent implements LegacyComponent { * @return the target window name. */ public String getTargetName() { - return targetName; + return getState(false).target; } /** @@ -178,7 +139,8 @@ public class Link extends AbstractComponent implements LegacyComponent { * @return the target window width. */ public int getTargetWidth() { - return targetWidth < 0 ? -1 : targetWidth; + return getState(false).targetWidth < 0 ? -1 + : getState(false).targetWidth; } /** @@ -188,8 +150,7 @@ public class Link extends AbstractComponent implements LegacyComponent { * the targetBorder to set. */ public void setTargetBorder(BorderStyle targetBorder) { - this.targetBorder = targetBorder; - markAsDirty(); + getState().targetBorder = targetBorder; } /** @@ -199,8 +160,7 @@ public class Link extends AbstractComponent implements LegacyComponent { * the targetHeight to set. */ public void setTargetHeight(int targetHeight) { - this.targetHeight = targetHeight; - markAsDirty(); + getState().targetHeight = targetHeight; } /** @@ -210,8 +170,7 @@ public class Link extends AbstractComponent implements LegacyComponent { * the targetName to set. */ public void setTargetName(String targetName) { - this.targetName = targetName; - markAsDirty(); + getState().target = targetName; } /** @@ -221,8 +180,7 @@ public class Link extends AbstractComponent implements LegacyComponent { * the targetWidth to set. */ public void setTargetWidth(int targetWidth) { - this.targetWidth = targetWidth; - markAsDirty(); + getState().targetWidth = targetWidth; } /** @@ -244,8 +202,4 @@ public class Link extends AbstractComponent implements LegacyComponent { setResource(LinkConstants.HREF_RESOURCE, resource); } - @Override - public void changeVariables(Object source, Map<String, Object> variables) { - // TODO Remove once LegacyComponent is no longer implemented - } } diff --git a/server/src/com/vaadin/ui/Table.java b/server/src/com/vaadin/ui/Table.java index bd2b7828de..32ed738697 100644 --- a/server/src/com/vaadin/ui/Table.java +++ b/server/src/com/vaadin/ui/Table.java @@ -427,6 +427,12 @@ public class Table extends AbstractSelect implements Action.Container, private int currentPageFirstItemIndex = 0; /** + * Index of the "first" item on the last page if a user has used + * setCurrentPageFirstItemIndex to scroll down. -1 if not set. + */ + private int currentPageFirstItemIndexOnLastPage = -1; + + /** * Holds value of property selectable. */ private boolean selectable = false; @@ -1477,12 +1483,14 @@ public class Table extends AbstractSelect implements Action.Container, } /* - * FIXME #7607 Take somehow into account the case where we want to - * scroll to the bottom so that the last row is completely visible even - * if (table height) / (row height) is not an integer. Reverted the - * original fix because of #8662 regression. + * If the new index is on the last page we set the index to be the first + * item on that last page and make a note of the real index for the + * client side to be able to move the scroll position to the correct + * position. */ + int indexOnLastPage = -1; if (newIndex > maxIndex) { + indexOnLastPage = newIndex; newIndex = maxIndex; } @@ -1494,6 +1502,20 @@ public class Table extends AbstractSelect implements Action.Container, currentPageFirstItemId = null; } currentPageFirstItemIndex = newIndex; + + if (needsPageBufferReset) { + /* + * The flag currentPageFirstItemIndexOnLastPage denotes a user + * set scrolling position on the last page via + * setCurrentPageFirstItemIndex() and shouldn't be changed by + * the table component internally changing the firstvisible item + * on lazy row fetching. Doing so would make the scrolling + * position not be updated correctly when the lazy rows are + * finally rendered. + */ + currentPageFirstItemIndexOnLastPage = indexOnLastPage; + } + } else { // For containers not supporting indexes, we must iterate the @@ -3447,6 +3469,8 @@ public class Table extends AbstractSelect implements Action.Container, if (getCurrentPageFirstItemIndex() != 0 || getPageLength() > 0) { target.addVariable(this, "firstvisible", getCurrentPageFirstItemIndex()); + target.addVariable(this, "firstvisibleonlastpage", + currentPageFirstItemIndexOnLastPage); } } diff --git a/server/src/com/vaadin/ui/TreeTable.java b/server/src/com/vaadin/ui/TreeTable.java index e150db9423..1c13eae8d9 100644 --- a/server/src/com/vaadin/ui/TreeTable.java +++ b/server/src/com/vaadin/ui/TreeTable.java @@ -590,11 +590,11 @@ public class TreeTable extends Table implements Hierarchical { // does not change component hierarchy during paint containerSupportsPartialUpdates = (newDataSource instanceof ItemSetChangeNotifier) && false; - if (!(newDataSource instanceof Hierarchical)) { + if (newDataSource != null && !(newDataSource instanceof Hierarchical)) { newDataSource = new ContainerHierarchicalWrapper(newDataSource); } - if (!(newDataSource instanceof Ordered)) { + if (newDataSource != null && !(newDataSource instanceof Ordered)) { newDataSource = new HierarchicalContainerOrderedWrapper( (Hierarchical) newDataSource); } diff --git a/server/src/com/vaadin/ui/UI.java b/server/src/com/vaadin/ui/UI.java index 8beebb0f1e..a292e6b829 100644 --- a/server/src/com/vaadin/ui/UI.java +++ b/server/src/com/vaadin/ui/UI.java @@ -24,6 +24,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.concurrent.Future; +import java.util.logging.Level; import java.util.logging.Logger; import com.vaadin.event.Action; @@ -31,10 +32,16 @@ import com.vaadin.event.Action.Handler; import com.vaadin.event.ActionManager; import com.vaadin.event.MouseEvents.ClickEvent; import com.vaadin.event.MouseEvents.ClickListener; +import com.vaadin.event.UIEvents.PollEvent; +import com.vaadin.event.UIEvents.PollListener; +import com.vaadin.event.UIEvents.PollNotifier; import com.vaadin.navigator.Navigator; import com.vaadin.server.ClientConnector; import com.vaadin.server.ComponentSizeValidator; import com.vaadin.server.ComponentSizeValidator.InvalidLayout; +import com.vaadin.server.DefaultErrorHandler; +import com.vaadin.server.ErrorHandler; +import com.vaadin.server.ErrorHandlingRunnable; import com.vaadin.server.LocaleService; import com.vaadin.server.Page; import com.vaadin.server.PaintException; @@ -91,7 +98,8 @@ import com.vaadin.util.CurrentInstance; * @since 7.0 */ public abstract class UI extends AbstractSingleComponentContainer implements - Action.Container, Action.Notifier, LegacyComponent, Focusable { + Action.Container, Action.Notifier, PollNotifier, LegacyComponent, + Focusable { /** * The application to which this UI belongs @@ -163,10 +171,7 @@ public abstract class UI extends AbstractSingleComponentContainer implements @Override public void poll() { - /* - * No-op. This is only called to cause a server visit to check for - * changes. - */ + fireEvent(new PollEvent(UI.this)); } }; private DebugWindowServerRpc debugRpc = new DebugWindowServerRpc() { @@ -405,9 +410,12 @@ public abstract class UI extends AbstractSingleComponentContainer implements * @see #getSession() */ public void setSession(VaadinSession session) { - if ((session == null) == (this.session == null)) { + if (session == null && this.session == null) { + throw new IllegalStateException( + "Session should never be set to null when UI.session is already null"); + } else if (session != null && this.session != null) { throw new IllegalStateException( - "VaadinServiceSession has already been set. Old session: " + "Session has already been set. Old session: " + getSessionDetails(this.session) + ". New session: " + getSessionDetails(session) + "."); @@ -1296,11 +1304,36 @@ public abstract class UI extends AbstractSingleComponentContainer implements throw new UIDetachedException(); } - return session.access(new Runnable() { + return session.access(new ErrorHandlingRunnable() { @Override public void run() { accessSynchronously(runnable); } + + @Override + public void handleError(Exception exception) { + try { + if (runnable instanceof ErrorHandlingRunnable) { + ErrorHandlingRunnable errorHandlingRunnable = (ErrorHandlingRunnable) runnable; + + errorHandlingRunnable.handleError(exception); + } else { + ConnectorErrorEvent errorEvent = new ConnectorErrorEvent( + UI.this, exception); + + ErrorHandler errorHandler = com.vaadin.server.ErrorEvent + .findErrorHandler(UI.this); + + if (errorHandler == null) { + errorHandler = new DefaultErrorHandler(); + } + + errorHandler.error(errorEvent); + } + } catch (Exception e) { + getLogger().log(Level.SEVERE, e.getMessage(), e); + } + } }); } @@ -1458,6 +1491,17 @@ public abstract class UI extends AbstractSingleComponentContainer implements return getState(false).pollInterval; } + @Override + public void addPollListener(PollListener listener) { + addListener(EventId.POLL, PollEvent.class, listener, + PollListener.POLL_METHOD); + } + + @Override + public void removePollListener(PollListener listener) { + removeListener(EventId.POLL, PollEvent.class, listener); + } + /** * Retrieves the object used for configuring the push channel. * diff --git a/server/src/com/vaadin/ui/Window.java b/server/src/com/vaadin/ui/Window.java index 8e7c6dbc80..d3afdaacf1 100644 --- a/server/src/com/vaadin/ui/Window.java +++ b/server/src/com/vaadin/ui/Window.java @@ -82,6 +82,16 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, public void windowModeChanged(WindowMode newState) { setWindowMode(newState); } + + @Override + public void windowMoved(int x, int y) { + if (x != getState(false).positionX) { + setPositionX(x); + } + if (y != getState(false).positionY) { + setPositionY(y); + } + } }; /** diff --git a/server/tests/src/com/vaadin/data/util/BeanItemContainerTest.java b/server/tests/src/com/vaadin/data/util/BeanItemContainerTest.java index 3a2cb268b9..35f09fc8f3 100644 --- a/server/tests/src/com/vaadin/data/util/BeanItemContainerTest.java +++ b/server/tests/src/com/vaadin/data/util/BeanItemContainerTest.java @@ -10,8 +10,15 @@ import java.util.Map; import junit.framework.Assert; +import org.easymock.Capture; +import org.easymock.EasyMock; + import com.vaadin.data.Container; +import com.vaadin.data.Container.Indexed.ItemAddEvent; +import com.vaadin.data.Container.Indexed.ItemRemoveEvent; +import com.vaadin.data.Container.ItemSetChangeListener; import com.vaadin.data.Item; +import com.vaadin.data.util.filter.Compare; /** * Test basic functionality of BeanItemContainer. @@ -737,4 +744,182 @@ public class BeanItemContainerTest extends AbstractBeanContainerTest { // should throw exception } } + + public void testItemAddedEvent() { + BeanItemContainer<Person> container = new BeanItemContainer<Person>( + Person.class); + Person bean = new Person("John"); + ItemSetChangeListener addListener = createListenerMockFor(container); + addListener.containerItemSetChange(EasyMock.isA(ItemAddEvent.class)); + EasyMock.replay(addListener); + + container.addItem(bean); + + EasyMock.verify(addListener); + } + + public void testItemAddedEvent_AddedItem() { + BeanItemContainer<Person> container = new BeanItemContainer<Person>( + Person.class); + Person bean = new Person("John"); + ItemSetChangeListener addListener = createListenerMockFor(container); + Capture<ItemAddEvent> capturedEvent = captureAddEvent(addListener); + EasyMock.replay(addListener); + + container.addItem(bean); + + assertEquals(bean, capturedEvent.getValue().getFirstItemId()); + } + + public void testItemAddedEvent_addItemAt_IndexOfAddedItem() { + BeanItemContainer<Person> container = new BeanItemContainer<Person>( + Person.class); + Person bean = new Person("John"); + container.addItem(bean); + ItemSetChangeListener addListener = createListenerMockFor(container); + Capture<ItemAddEvent> capturedEvent = captureAddEvent(addListener); + EasyMock.replay(addListener); + + container.addItemAt(1, new Person("")); + + assertEquals(1, capturedEvent.getValue().getFirstIndex()); + } + + public void testItemAddedEvent_addItemAfter_IndexOfAddedItem() { + BeanItemContainer<Person> container = new BeanItemContainer<Person>( + Person.class); + Person bean = new Person("John"); + container.addItem(bean); + ItemSetChangeListener addListener = createListenerMockFor(container); + Capture<ItemAddEvent> capturedEvent = captureAddEvent(addListener); + EasyMock.replay(addListener); + + container.addItemAfter(bean, new Person("")); + + assertEquals(1, capturedEvent.getValue().getFirstIndex()); + } + + public void testItemAddedEvent_amountOfAddedItems() { + BeanItemContainer<Person> container = new BeanItemContainer<Person>( + Person.class); + ItemSetChangeListener addListener = createListenerMockFor(container); + Capture<ItemAddEvent> capturedEvent = captureAddEvent(addListener); + EasyMock.replay(addListener); + List<Person> beans = Arrays.asList(new Person("Jack"), new Person( + "John")); + + container.addAll(beans); + + assertEquals(2, capturedEvent.getValue().getAddedItemsCount()); + } + + public void testItemAddedEvent_someItemsAreFiltered_amountOfAddedItemsIsReducedByAmountOfFilteredItems() { + BeanItemContainer<Person> container = new BeanItemContainer<Person>( + Person.class); + ItemSetChangeListener addListener = createListenerMockFor(container); + Capture<ItemAddEvent> capturedEvent = captureAddEvent(addListener); + EasyMock.replay(addListener); + List<Person> beans = Arrays.asList(new Person("Jack"), new Person( + "John")); + container.addFilter(new Compare.Equal("name", "John")); + + container.addAll(beans); + + assertEquals(1, capturedEvent.getValue().getAddedItemsCount()); + } + + public void testItemAddedEvent_someItemsAreFiltered_addedItemIsTheFirstVisibleItem() { + BeanItemContainer<Person> container = new BeanItemContainer<Person>( + Person.class); + Person bean = new Person("John"); + ItemSetChangeListener addListener = createListenerMockFor(container); + Capture<ItemAddEvent> capturedEvent = captureAddEvent(addListener); + EasyMock.replay(addListener); + List<Person> beans = Arrays.asList(new Person("Jack"), bean); + container.addFilter(new Compare.Equal("name", "John")); + + container.addAll(beans); + + assertEquals(bean, capturedEvent.getValue().getFirstItemId()); + } + + public void testItemRemovedEvent() { + BeanItemContainer<Person> container = new BeanItemContainer<Person>( + Person.class); + Person bean = new Person("John"); + container.addItem(bean); + ItemSetChangeListener removeListener = createListenerMockFor(container); + removeListener.containerItemSetChange(EasyMock + .isA(ItemRemoveEvent.class)); + EasyMock.replay(removeListener); + + container.removeItem(bean); + + EasyMock.verify(removeListener); + } + + public void testItemRemovedEvent_RemovedItem() { + BeanItemContainer<Person> container = new BeanItemContainer<Person>( + Person.class); + Person bean = new Person("John"); + container.addItem(bean); + ItemSetChangeListener removeListener = createListenerMockFor(container); + Capture<ItemRemoveEvent> capturedEvent = captureRemoveEvent(removeListener); + EasyMock.replay(removeListener); + + container.removeItem(bean); + + assertEquals(bean, capturedEvent.getValue().getFirstItemId()); + } + + public void testItemRemovedEvent_indexOfRemovedItem() { + BeanItemContainer<Person> container = new BeanItemContainer<Person>( + Person.class); + container.addItem(new Person("Jack")); + Person secondBean = new Person("John"); + container.addItem(secondBean); + ItemSetChangeListener removeListener = createListenerMockFor(container); + Capture<ItemRemoveEvent> capturedEvent = captureRemoveEvent(removeListener); + EasyMock.replay(removeListener); + + container.removeItem(secondBean); + + assertEquals(1, capturedEvent.getValue().getFirstIndex()); + } + + public void testItemRemovedEvent_amountOfRemovedItems() { + BeanItemContainer<Person> container = new BeanItemContainer<Person>( + Person.class); + container.addItem(new Person("Jack")); + container.addItem(new Person("John")); + ItemSetChangeListener removeListener = createListenerMockFor(container); + Capture<ItemRemoveEvent> capturedEvent = captureRemoveEvent(removeListener); + EasyMock.replay(removeListener); + + container.removeAllItems(); + + assertEquals(2, capturedEvent.getValue().getRemovedItemsCount()); + } + + private Capture<ItemAddEvent> captureAddEvent( + ItemSetChangeListener addListener) { + Capture<ItemAddEvent> capturedEvent = new Capture<ItemAddEvent>(); + addListener.containerItemSetChange(EasyMock.capture(capturedEvent)); + return capturedEvent; + } + + private Capture<ItemRemoveEvent> captureRemoveEvent( + ItemSetChangeListener removeListener) { + Capture<ItemRemoveEvent> capturedEvent = new Capture<ItemRemoveEvent>(); + removeListener.containerItemSetChange(EasyMock.capture(capturedEvent)); + return capturedEvent; + } + + private ItemSetChangeListener createListenerMockFor( + BeanItemContainer<Person> container) { + ItemSetChangeListener listener = EasyMock + .createNiceMock(ItemSetChangeListener.class); + container.addItemSetChangeListener(listener); + return listener; + } } diff --git a/server/tests/src/com/vaadin/data/util/TestIndexedContainer.java b/server/tests/src/com/vaadin/data/util/TestIndexedContainer.java index 09e5a26c15..5c78965092 100644 --- a/server/tests/src/com/vaadin/data/util/TestIndexedContainer.java +++ b/server/tests/src/com/vaadin/data/util/TestIndexedContainer.java @@ -4,6 +4,12 @@ import java.util.List; import junit.framework.Assert; +import org.easymock.Capture; +import org.easymock.EasyMock; + +import com.vaadin.data.Container.Indexed.ItemAddEvent; +import com.vaadin.data.Container.Indexed.ItemRemoveEvent; +import com.vaadin.data.Container.ItemSetChangeListener; import com.vaadin.data.Item; public class TestIndexedContainer extends AbstractInMemoryContainerTest { @@ -271,6 +277,113 @@ public class TestIndexedContainer extends AbstractInMemoryContainerTest { counter.assertNone(); } + public void testItemAddedEvent() { + IndexedContainer container = new IndexedContainer(); + ItemSetChangeListener addListener = createListenerMockFor(container); + addListener.containerItemSetChange(EasyMock.isA(ItemAddEvent.class)); + EasyMock.replay(addListener); + + container.addItem(); + + EasyMock.verify(addListener); + } + + public void testItemAddedEvent_AddedItem() { + IndexedContainer container = new IndexedContainer(); + ItemSetChangeListener addListener = createListenerMockFor(container); + Capture<ItemAddEvent> capturedEvent = captureAddEvent(addListener); + EasyMock.replay(addListener); + + Object itemId = container.addItem(); + + assertEquals(itemId, capturedEvent.getValue().getFirstItemId()); + } + + public void testItemAddedEvent_IndexOfAddedItem() { + IndexedContainer container = new IndexedContainer(); + ItemSetChangeListener addListener = createListenerMockFor(container); + container.addItem(); + Capture<ItemAddEvent> capturedEvent = captureAddEvent(addListener); + EasyMock.replay(addListener); + + Object itemId = container.addItemAt(1); + + assertEquals(1, capturedEvent.getValue().getFirstIndex()); + } + + public void testItemRemovedEvent() { + IndexedContainer container = new IndexedContainer(); + Object itemId = container.addItem(); + ItemSetChangeListener removeListener = createListenerMockFor(container); + removeListener.containerItemSetChange(EasyMock + .isA(ItemRemoveEvent.class)); + EasyMock.replay(removeListener); + + container.removeItem(itemId); + + EasyMock.verify(removeListener); + } + + public void testItemRemovedEvent_RemovedItem() { + IndexedContainer container = new IndexedContainer(); + Object itemId = container.addItem(); + ItemSetChangeListener removeListener = createListenerMockFor(container); + Capture<ItemRemoveEvent> capturedEvent = captureRemoveEvent(removeListener); + EasyMock.replay(removeListener); + + container.removeItem(itemId); + + assertEquals(itemId, capturedEvent.getValue().getFirstItemId()); + } + + public void testItemRemovedEvent_indexOfRemovedItem() { + IndexedContainer container = new IndexedContainer(); + container.addItem(); + Object secondItemId = container.addItem(); + ItemSetChangeListener removeListener = createListenerMockFor(container); + Capture<ItemRemoveEvent> capturedEvent = captureRemoveEvent(removeListener); + EasyMock.replay(removeListener); + + container.removeItem(secondItemId); + + assertEquals(1, capturedEvent.getValue().getFirstIndex()); + } + + public void testItemRemovedEvent_amountOfRemovedItems() { + IndexedContainer container = new IndexedContainer(); + container.addItem(); + container.addItem(); + ItemSetChangeListener removeListener = createListenerMockFor(container); + Capture<ItemRemoveEvent> capturedEvent = captureRemoveEvent(removeListener); + EasyMock.replay(removeListener); + + container.removeAllItems(); + + assertEquals(2, capturedEvent.getValue().getRemovedItemsCount()); + } + + private Capture<ItemAddEvent> captureAddEvent( + ItemSetChangeListener addListener) { + Capture<ItemAddEvent> capturedEvent = new Capture<ItemAddEvent>(); + addListener.containerItemSetChange(EasyMock.capture(capturedEvent)); + return capturedEvent; + } + + private Capture<ItemRemoveEvent> captureRemoveEvent( + ItemSetChangeListener removeListener) { + Capture<ItemRemoveEvent> capturedEvent = new Capture<ItemRemoveEvent>(); + removeListener.containerItemSetChange(EasyMock.capture(capturedEvent)); + return capturedEvent; + } + + private ItemSetChangeListener createListenerMockFor( + IndexedContainer container) { + ItemSetChangeListener listener = EasyMock + .createNiceMock(ItemSetChangeListener.class); + container.addItemSetChangeListener(listener); + return listener; + } + // Ticket 8028 public void testGetItemIdsRangeIndexOutOfBounds() { IndexedContainer ic = new IndexedContainer(); diff --git a/server/tests/src/com/vaadin/tests/server/TestAtmosphereVersion.java b/server/tests/src/com/vaadin/tests/server/TestAtmosphereVersion.java index 5c27ef0752..3d37022b81 100644 --- a/server/tests/src/com/vaadin/tests/server/TestAtmosphereVersion.java +++ b/server/tests/src/com/vaadin/tests/server/TestAtmosphereVersion.java @@ -12,7 +12,7 @@ public class TestAtmosphereVersion extends TestCase { * classpath */ public void testAtmosphereVersion() { - assertEquals(Constants.REQUIRED_ATMOSPHERE_VERSION, + assertEquals(Constants.REQUIRED_ATMOSPHERE_RUNTIME_VERSION, Version.getRawVersion()); } } diff --git a/server/tests/src/com/vaadin/tests/server/TestClientMethodSerialization.java b/server/tests/src/com/vaadin/tests/server/TestClientMethodSerialization.java new file mode 100644 index 0000000000..1e0210dc63 --- /dev/null +++ b/server/tests/src/com/vaadin/tests/server/TestClientMethodSerialization.java @@ -0,0 +1,111 @@ +/* + * Copyright 2000-2013 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.tests.server; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.Arrays; + +import junit.framework.TestCase; + +import org.json.JSONArray; + +import com.vaadin.server.ClientMethodInvocation; +import com.vaadin.server.JavaScriptCallbackHelper; +import com.vaadin.ui.JavaScript.JavaScriptCallbackRpc; +import com.vaadin.util.ReflectTools; + +public class TestClientMethodSerialization extends TestCase { + + private static final Method JAVASCRIPT_CALLBACK_METHOD = ReflectTools + .findMethod(JavaScriptCallbackRpc.class, "call", String.class, + JSONArray.class); + + private static final Method BASIC_PARAMS_CALL_METHOD = ReflectTools + .findMethod(TestClientMethodSerialization.class, + "basicParamsMethodForTesting", String.class, Integer.class); + + private static final Method NO_PARAMS_CALL_METHOD = ReflectTools + .findMethod(TestClientMethodSerialization.class, + "noParamsMethodForTesting"); + + public void basicParamsMethodForTesting(String stringParam, + Integer integerParam) { + } + + public void noParamsMethodForTesting() { + } + + /** + * Tests the {@link ClientMethodInvocation} serialization when using + * {@link JavaScriptCallbackHelper#invokeCallback(String, Object...)}. + * #12532 + */ + public void testClientMethodSerialization_WithJSONArray_ContentStaysSame() + throws Exception { + JSONArray originalArray = new JSONArray(Arrays.asList( + "callbackParameter1", "callBackParameter2", "12345")); + ClientMethodInvocation original = new ClientMethodInvocation(null, + "interfaceName", JAVASCRIPT_CALLBACK_METHOD, new Object[] { + "callBackMethodName", originalArray }); + + ClientMethodInvocation copy = (ClientMethodInvocation) serializeAndDeserialize(original); + JSONArray copyArray = (JSONArray) copy.getParameters()[1]; + assertEquals(originalArray.toString(), copyArray.toString()); + } + + public void testClientMethodSerialization_WithBasicParams_NoChanges() + throws Exception { + String stringParam = "a string 123"; + Integer integerParam = 1234567890; + ClientMethodInvocation original = new ClientMethodInvocation(null, + "interfaceName", BASIC_PARAMS_CALL_METHOD, new Serializable[] { + stringParam, integerParam }); + ClientMethodInvocation copy = (ClientMethodInvocation) serializeAndDeserialize(original); + String copyString = (String) copy.getParameters()[0]; + Integer copyInteger = (Integer) copy.getParameters()[1]; + assertEquals(copyString, stringParam); + assertEquals(copyInteger, integerParam); + } + + public void testClientMethodSerialization_NoParams_NoExceptions() { + ClientMethodInvocation original = new ClientMethodInvocation(null, + "interfaceName", NO_PARAMS_CALL_METHOD, null); + ClientMethodInvocation copy = (ClientMethodInvocation) serializeAndDeserialize(original); + } + + private static Serializable serializeAndDeserialize(Serializable input) { + Serializable output = null; + try { + ByteArrayOutputStream bs = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(bs); + out.writeObject(input); + byte[] data = bs.toByteArray(); + ObjectInputStream in = new ObjectInputStream( + new ByteArrayInputStream(data)); + output = (Serializable) in.readObject(); + } catch (Exception e) { + fail("Exception during serialization/deserialization: " + + e.getMessage()); + } + return output; + } + +} diff --git a/server/tests/src/com/vaadin/tests/server/TestSerialization.java b/server/tests/src/com/vaadin/tests/server/TestSerialization.java index 84ff5ad6fa..a52821a919 100644 --- a/server/tests/src/com/vaadin/tests/server/TestSerialization.java +++ b/server/tests/src/com/vaadin/tests/server/TestSerialization.java @@ -14,6 +14,7 @@ import com.vaadin.data.Property; import com.vaadin.data.util.IndexedContainer; import com.vaadin.data.util.MethodProperty; import com.vaadin.data.validator.RegexpValidator; +import com.vaadin.server.VaadinSession; import com.vaadin.ui.Form; public class TestSerialization extends TestCase { @@ -21,7 +22,7 @@ public class TestSerialization extends TestCase { public void testValidators() throws Exception { RegexpValidator validator = new RegexpValidator(".*", "Error"); validator.validate("aaa"); - RegexpValidator validator2 = (RegexpValidator) serializeAndDeserialize(validator); + RegexpValidator validator2 = serializeAndDeserialize(validator); validator2.validate("aaa"); } @@ -67,7 +68,17 @@ public class TestSerialization extends TestCase { serializeAndDeserialize(mp); } - private static Serializable serializeAndDeserialize(Serializable s) + public void testVaadinSession() throws Exception { + VaadinSession session = new VaadinSession(null); + + session = serializeAndDeserialize(session); + + assertNotNull( + "Pending access queue was not recreated after deserialization", + session.getPendingAccessQueue()); + } + + private static <S extends Serializable> S serializeAndDeserialize(S s) throws IOException, ClassNotFoundException { // Serialize and deserialize @@ -77,10 +88,12 @@ public class TestSerialization extends TestCase { byte[] data = bs.toByteArray(); ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream( data)); - Serializable s2 = (Serializable) in.readObject(); + @SuppressWarnings("unchecked") + S s2 = (S) in.readObject(); // using special toString(Object) method to avoid calling // Property.toString(), which will be temporarily disabled + // TODO This is hilariously broken (#12723) if (s.equals(s2)) { System.out.println(toString(s) + " equals " + toString(s2)); } else { diff --git a/server/tests/src/com/vaadin/tests/server/component/abstractfield/AbsFieldValueConversions.java b/server/tests/src/com/vaadin/tests/server/component/abstractfield/AbsFieldValueConversions.java index dd46b11520..85116dd152 100644 --- a/server/tests/src/com/vaadin/tests/server/component/abstractfield/AbsFieldValueConversions.java +++ b/server/tests/src/com/vaadin/tests/server/component/abstractfield/AbsFieldValueConversions.java @@ -250,6 +250,7 @@ public class AbsFieldValueConversions extends TestCase { @Test public void testNullConverter() { TextField tf = new TextField("foo"); + tf.setConverter(new StringToIntegerConverter()); tf.setPropertyDataSource(new ObjectProperty<Integer>(12)); tf.setConverter((Converter) null); try { diff --git a/server/tests/src/com/vaadin/tests/server/component/table/TableRemovedQuicklySendsInvalidRpcCalls.java b/server/tests/src/com/vaadin/tests/server/component/table/TableRemovedQuicklySendsInvalidRpcCalls.java deleted file mode 100644 index b539e42efe..0000000000 --- a/server/tests/src/com/vaadin/tests/server/component/table/TableRemovedQuicklySendsInvalidRpcCalls.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2000-2013 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.tests.server.component.table; - -import com.vaadin.annotations.Push; -import com.vaadin.event.ItemClickEvent; -import com.vaadin.event.ItemClickEvent.ItemClickListener; -import com.vaadin.server.VaadinRequest; -import com.vaadin.tests.components.AbstractTestUI; -import com.vaadin.ui.Button; -import com.vaadin.ui.Button.ClickEvent; -import com.vaadin.ui.Table; - -@Push -public class TableRemovedQuicklySendsInvalidRpcCalls extends AbstractTestUI { - - @Override - protected void setup(VaadinRequest request) { - addComponent(new Button("Blink a table", new Button.ClickListener() { - @Override - public void buttonClick(ClickEvent event) { - blinkTable(); - } - })); - } - - private void blinkTable() { - final Table table = new Table(); - table.setPageLength(5); - table.addContainerProperty(new Object(), String.class, null); - - for (int i = 0; i < 50; i++) { - table.addItem(new Object[] { "Row" }, new Object()); - } - - table.addItemClickListener(new ItemClickListener() { - private int i; - - @Override - public void itemClick(ItemClickEvent event) { - /* - * Ignore implementation. This is only an easy way to make the - * client-side update table's variables (by furiously clicking - * on the table row. - * - * This way, we get variable changes queued. The push call will - * then remove the Table, while the variable changes being still - * in the queue, leading to the issue as described in the - * ticket. - */ - System.out.println("clicky " + (++i)); - } - }); - - System.out.println("adding component"); - addComponent(table); - - new Thread() { - @Override - public void run() { - getSession().lock(); - try { - Thread.sleep(500); - access(new Runnable() { - @Override - public void run() { - System.out.println("removing component"); - removeComponent(table); - } - }); - } catch (InterruptedException e) { - e.printStackTrace(); - } finally { - getSession().unlock(); - } - }; - }.start(); - } - - @Override - protected String getTestDescription() { - return "Adding and subsequently quickly removing a table " - + "should not leave any pending RPC calls waiting " - + "in a Timer. Issue can be reproduced by " - + "1) pressing the button 2) clicking furiously " - + "on a row in the table."; - } - - @Override - protected Integer getTicketNumber() { - return 12337; - } -} diff --git a/server/tests/src/com/vaadin/tests/server/component/treetable/TreeTableSetContainerNull.java b/server/tests/src/com/vaadin/tests/server/component/treetable/TreeTableSetContainerNull.java new file mode 100644 index 0000000000..a3b79a14f0 --- /dev/null +++ b/server/tests/src/com/vaadin/tests/server/component/treetable/TreeTableSetContainerNull.java @@ -0,0 +1,15 @@ +package com.vaadin.tests.server.component.treetable; + +import junit.framework.TestCase; + +import com.vaadin.ui.TreeTable; + +public class TreeTableSetContainerNull extends TestCase { + + public void testNullContainer() { + TreeTable treeTable = new TreeTable(); + + // should not cause an exception + treeTable.setContainerDataSource(null); + } +} |