aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/src/com/vaadin/annotations/Widgetset.java2
-rw-r--r--server/src/com/vaadin/data/Container.java54
-rw-r--r--server/src/com/vaadin/data/util/AbstractBeanContainer.java77
-rw-r--r--server/src/com/vaadin/data/util/AbstractInMemoryContainer.java151
-rw-r--r--server/src/com/vaadin/data/util/BeanItem.java21
-rw-r--r--server/src/com/vaadin/data/util/IndexedContainer.java7
-rw-r--r--server/src/com/vaadin/data/util/NestedMethodProperty.java59
-rw-r--r--server/src/com/vaadin/data/util/NestedPropertyDescriptor.java24
-rw-r--r--server/src/com/vaadin/data/util/converter/DefaultConverterFactory.java5
-rw-r--r--server/src/com/vaadin/data/util/converter/StringToBigDecimalConverter.java60
-rw-r--r--server/src/com/vaadin/data/util/converter/StringToNumberConverter.java65
-rw-r--r--server/src/com/vaadin/data/validator/BeanValidator.java19
-rw-r--r--server/src/com/vaadin/event/UIEvents.java116
-rw-r--r--server/src/com/vaadin/server/BootstrapHandler.java13
-rw-r--r--server/src/com/vaadin/server/Page.java16
-rw-r--r--server/src/com/vaadin/server/ServiceDestroyEvent.java50
-rw-r--r--server/src/com/vaadin/server/ServiceDestroyListener.java39
-rw-r--r--server/src/com/vaadin/server/SynchronizedRequestHandler.java25
-rw-r--r--server/src/com/vaadin/server/SystemMessages.java12
-rw-r--r--server/src/com/vaadin/server/VaadinPortlet.java6
-rw-r--r--server/src/com/vaadin/server/VaadinService.java71
-rw-r--r--server/src/com/vaadin/server/VaadinServlet.java86
-rw-r--r--server/src/com/vaadin/server/VaadinSession.java75
-rw-r--r--server/src/com/vaadin/server/communication/FileUploadHandler.java25
-rw-r--r--server/src/com/vaadin/server/communication/HeartbeatHandler.java9
-rw-r--r--server/src/com/vaadin/server/communication/PushRequestHandler.java9
-rw-r--r--server/src/com/vaadin/server/communication/ServerRpcHandler.java213
-rw-r--r--server/src/com/vaadin/server/communication/UIInitHandler.java80
-rw-r--r--server/src/com/vaadin/server/communication/UidlRequestHandler.java8
-rw-r--r--server/src/com/vaadin/server/communication/UidlWriter.java4
-rw-r--r--server/src/com/vaadin/ui/Component.java6
-rw-r--r--server/src/com/vaadin/ui/ConnectorTracker.java135
-rw-r--r--server/src/com/vaadin/ui/DragAndDropWrapper.java2
-rw-r--r--server/src/com/vaadin/ui/Link.java84
-rw-r--r--server/src/com/vaadin/ui/Notification.java150
-rw-r--r--server/src/com/vaadin/ui/NotificationConfiguration.java269
-rw-r--r--server/src/com/vaadin/ui/TabSheet.java180
-rw-r--r--server/src/com/vaadin/ui/UI.java61
-rw-r--r--server/src/com/vaadin/ui/Window.java202
-rw-r--r--server/tests/src/com/vaadin/data/util/BeanContainerTest.java24
-rw-r--r--server/tests/src/com/vaadin/data/util/BeanItemContainerTest.java208
-rw-r--r--server/tests/src/com/vaadin/data/util/NestedMethodPropertyTest.java31
-rw-r--r--server/tests/src/com/vaadin/data/util/PropertyDescriptorTest.java16
-rw-r--r--server/tests/src/com/vaadin/data/util/TestIndexedContainer.java113
-rw-r--r--server/tests/src/com/vaadin/server/VaadinSessionTest.java2
-rw-r--r--server/tests/src/com/vaadin/tests/data/bean/BeanToValidate.java13
-rw-r--r--server/tests/src/com/vaadin/tests/data/bean/PersonWithBeanValidationAnnotations.java3
-rw-r--r--server/tests/src/com/vaadin/tests/data/converter/TestStringToBigDecimalConverter.java53
-rw-r--r--server/tests/src/com/vaadin/tests/data/converter/TestStringToNumberConverter.java24
-rw-r--r--server/tests/src/com/vaadin/tests/server/component/abstractfield/AbsFieldValueConversions.java9
-rw-r--r--server/tests/src/com/vaadin/tests/server/validation/TestBeanValidation.java29
51 files changed, 2558 insertions, 457 deletions
diff --git a/server/src/com/vaadin/annotations/Widgetset.java b/server/src/com/vaadin/annotations/Widgetset.java
index 40276c18a2..006bf59acf 100644
--- a/server/src/com/vaadin/annotations/Widgetset.java
+++ b/server/src/com/vaadin/annotations/Widgetset.java
@@ -24,7 +24,7 @@ import java.lang.annotation.Target;
import com.vaadin.ui.UI;
/**
- * Defines a specific theme for a {@link UI}.
+ * Defines a specific widgetset for a {@link UI}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
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 35403d6419..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.
*
@@ -845,8 +857,32 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends
* @return true if the property was added
*/
public boolean addNestedContainerProperty(String propertyId) {
+ return addNestedContainerProperty(propertyId, false);
+ }
+
+ /**
+ * Adds a nested container property for the container, e.g.
+ * "manager.address.street".
+ *
+ * All intermediate getters must exist and must return non-null values when
+ * the property value is accessed or the <code>nullBeansAllowed</code> must
+ * be set to true. If the <code>nullBeansAllowed</code> flag is set to true,
+ * calling getValue of the added property will return null if the property
+ * or any of its intermediate getters returns null. If set to false, null
+ * values returned by intermediate getters will cause NullPointerException.
+ * The default value is false to ensure backwards compatibility.
+ *
+ * @see NestedMethodProperty
+ *
+ * @param propertyId
+ * @param nullBeansAllowed
+ * set true to allow null values from intermediate getters
+ * @return true if the property was added
+ */
+ public boolean addNestedContainerProperty(String propertyId,
+ boolean nullBeansAllowed) {
return addContainerProperty(propertyId, new NestedPropertyDescriptor(
- propertyId, type));
+ propertyId, type, nullBeansAllowed));
}
/**
@@ -864,13 +900,42 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends
*/
@SuppressWarnings("unchecked")
public void addNestedContainerBean(String propertyId) {
+ addNestedContainerBean(propertyId, false);
+ }
+
+ /**
+ * Adds a nested container properties for all sub-properties of a named
+ * property to the container. The named property itself is removed from the
+ * model as its subproperties are added.
+ *
+ * Unless
+ * <code>nullBeansAllowed<code> is set to true, all intermediate getters must
+ * exist and must return non-null values when the property values are
+ * accessed. If the <code>nullBeansAllowed</code> flag is set to true,
+ * calling getValue of the added subproperties will return null if the
+ * property or any of their intermediate getters returns null. If set to
+ * false, null values returned by intermediate getters will cause
+ * NullPointerException. The default value is false to ensure backwards
+ * compatibility.
+ *
+ * @see NestedMethodProperty
+ * @see #addNestedContainerProperty(String)
+ *
+ * @param propertyId
+ * @param nullBeansAllowed
+ * set true to allow null values from intermediate getters
+ */
+ @SuppressWarnings("unchecked")
+ public void addNestedContainerBean(String propertyId,
+ boolean nullBeansAllowed) {
Class<?> propertyType = getType(propertyId);
LinkedHashMap<String, VaadinPropertyDescriptor<Object>> pds = BeanItem
.getPropertyDescriptors((Class<Object>) propertyType);
for (String subPropertyId : pds.keySet()) {
String qualifiedPropertyId = propertyId + "." + subPropertyId;
NestedPropertyDescriptor<BEANTYPE> pd = new NestedPropertyDescriptor<BEANTYPE>(
- qualifiedPropertyId, (Class<BEANTYPE>) type);
+ qualifiedPropertyId, (Class<BEANTYPE>) type,
+ nullBeansAllowed);
model.put(qualifiedPropertyId, pd);
model.remove(propertyId);
for (BeanItem<BEANTYPE> item : itemIdToItem.values()) {
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/BeanItem.java b/server/src/com/vaadin/data/util/BeanItem.java
index fc51be8f36..4834fe4f89 100644
--- a/server/src/com/vaadin/data/util/BeanItem.java
+++ b/server/src/com/vaadin/data/util/BeanItem.java
@@ -268,6 +268,27 @@ public class BeanItem<BT> extends PropertysetItem {
}
/**
+ * Adds a nested property to the item. If the <code>nullBeansAllowed</code>
+ * flag is set to true, calling getValue of the added property will return
+ * null if the property or any of its intermediate getters returns null. If
+ * set to false, null values returned by intermediate getters will cause
+ * NullPointerException. The default value is false to ensure backwards
+ * compatibility.
+ *
+ * @param nestedPropertyId
+ * property id to add. This property must not exist in the item
+ * already and must of of form "field1.field2" where field2 is a
+ * field in the object referenced to by field1
+ * @param nullBeansAllowed
+ * set true to allow null values from intermediate getters
+ */
+ public void addNestedProperty(String nestedPropertyId,
+ boolean nullBeansAllowed) {
+ addItemProperty(nestedPropertyId, new NestedMethodProperty<Object>(
+ getBean(), nestedPropertyId, nullBeansAllowed));
+ }
+
+ /**
* Gets the underlying JavaBean object.
*
* @return the bean object.
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/data/util/NestedMethodProperty.java b/server/src/com/vaadin/data/util/NestedMethodProperty.java
index b62ecfbfc3..7a3963c17e 100644
--- a/server/src/com/vaadin/data/util/NestedMethodProperty.java
+++ b/server/src/com/vaadin/data/util/NestedMethodProperty.java
@@ -32,7 +32,7 @@ import com.vaadin.data.util.MethodProperty.MethodException;
* can contain multiple levels of nesting.
*
* When accessing the property value, all intermediate getters must return
- * non-null values.
+ * non-null values or the <code>nullBeansAllowed</code> must be set to true.
*
* @see MethodProperty
*
@@ -55,6 +55,15 @@ public class NestedMethodProperty<T> extends AbstractProperty<T> {
*/
private Object instance;
+ /**
+ * a boolean flag indicating whether intermediate getters may return null
+ * values. If the flag is set to true, calling getValue will return null if
+ * the property or any of its intermediate getters returns null. If set to
+ * false, intermediate getters returning null value will throw Exception.
+ * The default value is false to ensure backwards compatibility.
+ */
+ private boolean nullBeansAllowed = false;
+
private Class<? extends T> type;
/* Special serialization to handle method references */
@@ -85,7 +94,33 @@ public class NestedMethodProperty<T> extends AbstractProperty<T> {
* if the property name is invalid
*/
public NestedMethodProperty(Object instance, String propertyName) {
+ this(instance, propertyName, false);
+ }
+
+ /**
+ * Constructs a nested method property for a given object instance. The
+ * property name is a dot separated string pointing to a nested property,
+ * e.g. "manager.address.street". The <code>nullBeansAllowed</code> controls
+ * the behavior in cases where the intermediate getters may return null
+ * values. If the flag is set to true, calling getValue will return null if
+ * the property or any of its intermediate getters returns null. If set to
+ * false, null values returned by intermediate getters will cause
+ * NullPointerException. The default value is false to ensure backwards
+ * compatibility.
+ *
+ * @param instance
+ * top-level bean to which the property applies
+ * @param propertyName
+ * dot separated nested property name
+ * @param nullBeansAllowed
+ * set true to allow null values from intermediate getters
+ * @throws IllegalArgumentException
+ * if the property name is invalid
+ */
+ public NestedMethodProperty(Object instance, String propertyName,
+ boolean nullBeansAllowed) {
this.instance = instance;
+ this.nullBeansAllowed = nullBeansAllowed;
initialize(instance.getClass(), propertyName);
}
@@ -104,6 +139,25 @@ public class NestedMethodProperty<T> extends AbstractProperty<T> {
}
/**
+ * For internal use to deduce property type etc. without a bean instance.
+ * Calling {@link #setValue(Object)} or {@link #getValue()} on properties
+ * constructed this way is not supported.
+ *
+ * @param instanceClass
+ * class of the top-level bean
+ * @param propertyName
+ * dot separated nested property name
+ * @param nullBeansAllowed
+ * set true to allow null values from intermediate getters
+ */
+ NestedMethodProperty(Class<?> instanceClass, String propertyName,
+ boolean nullBeansAllowed) {
+ instance = null;
+ this.nullBeansAllowed = nullBeansAllowed;
+ initialize(instanceClass, propertyName);
+ }
+
+ /**
* Initializes most of the internal fields based on the top-level bean
* instance and property name (dot-separated string).
*
@@ -199,6 +253,9 @@ public class NestedMethodProperty<T> extends AbstractProperty<T> {
Object object = instance;
for (Method m : getMethods) {
object = m.invoke(object);
+ if (object == null && nullBeansAllowed) {
+ return null;
+ }
}
return (T) object;
} catch (final Throwable e) {
diff --git a/server/src/com/vaadin/data/util/NestedPropertyDescriptor.java b/server/src/com/vaadin/data/util/NestedPropertyDescriptor.java
index b2055fe776..67eb30fae5 100644
--- a/server/src/com/vaadin/data/util/NestedPropertyDescriptor.java
+++ b/server/src/com/vaadin/data/util/NestedPropertyDescriptor.java
@@ -34,6 +34,7 @@ public class NestedPropertyDescriptor<BT> implements
private final String name;
private final Class<?> propertyType;
+ private final boolean nullBeansAllowed;
/**
* Creates a property descriptor that can create MethodProperty instances to
@@ -48,10 +49,29 @@ public class NestedPropertyDescriptor<BT> implements
*/
public NestedPropertyDescriptor(String name, Class<BT> beanType)
throws IllegalArgumentException {
+ this(name, beanType, false);
+ }
+
+ /**
+ * Creates a property descriptor that can create MethodProperty instances to
+ * access the underlying bean property.
+ *
+ * @param name
+ * of the property in a dotted path format, e.g. "address.street"
+ * @param beanType
+ * type (class) of the top-level bean
+ * @param nullBeansAllowed
+ * set true to allow null values from intermediate getters
+ * @throws IllegalArgumentException
+ * if the property name is invalid
+ */
+ public NestedPropertyDescriptor(String name, Class<BT> beanType,
+ boolean nullBeansAllowed) throws IllegalArgumentException {
this.name = name;
NestedMethodProperty<?> property = new NestedMethodProperty<Object>(
- beanType, name);
+ beanType, name, nullBeansAllowed);
this.propertyType = property.getType();
+ this.nullBeansAllowed = nullBeansAllowed;
}
@Override
@@ -66,7 +86,7 @@ public class NestedPropertyDescriptor<BT> implements
@Override
public Property<?> createProperty(BT bean) {
- return new NestedMethodProperty<Object>(bean, name);
+ return new NestedMethodProperty<Object>(bean, name, nullBeansAllowed);
}
}
diff --git a/server/src/com/vaadin/data/util/converter/DefaultConverterFactory.java b/server/src/com/vaadin/data/util/converter/DefaultConverterFactory.java
index cadfdcc774..4d3717e9ba 100644
--- a/server/src/com/vaadin/data/util/converter/DefaultConverterFactory.java
+++ b/server/src/com/vaadin/data/util/converter/DefaultConverterFactory.java
@@ -16,6 +16,7 @@
package com.vaadin.data.util.converter;
+import java.math.BigDecimal;
import java.util.Date;
import java.util.logging.Logger;
@@ -103,10 +104,10 @@ public class DefaultConverterFactory implements ConverterFactory {
return new StringToIntegerConverter();
} else if (Long.class.isAssignableFrom(sourceType)) {
return new StringToLongConverter();
+ } else if (BigDecimal.class.isAssignableFrom(sourceType)) {
+ return new StringToBigDecimalConverter();
} else if (Boolean.class.isAssignableFrom(sourceType)) {
return new StringToBooleanConverter();
- } else if (Number.class.isAssignableFrom(sourceType)) {
- return new StringToNumberConverter();
} else if (Date.class.isAssignableFrom(sourceType)) {
return new StringToDateConverter();
} else {
diff --git a/server/src/com/vaadin/data/util/converter/StringToBigDecimalConverter.java b/server/src/com/vaadin/data/util/converter/StringToBigDecimalConverter.java
new file mode 100644
index 0000000000..75d4cedd23
--- /dev/null
+++ b/server/src/com/vaadin/data/util/converter/StringToBigDecimalConverter.java
@@ -0,0 +1,60 @@
+/*
+ * 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.data.util.converter;
+
+import java.math.BigDecimal;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.util.Locale;
+
+/**
+ * A converter that converts from {@link String} to {@link BigDecimal} and back.
+ * Uses the given locale and a {@link NumberFormat} instance for formatting and
+ * parsing.
+ * <p>
+ * Leading and trailing white spaces are ignored when converting from a String.
+ * </p>
+ * <p>
+ * Override and overwrite {@link #getFormat(Locale)} to use a different format.
+ * </p>
+ *
+ * @author Vaadin Ltd
+ * @since 7.2
+ */
+public class StringToBigDecimalConverter extends
+ AbstractStringToNumberConverter<BigDecimal> {
+ @Override
+ protected NumberFormat getFormat(Locale locale) {
+ NumberFormat numberFormat = super.getFormat(locale);
+ if (numberFormat instanceof DecimalFormat) {
+ ((DecimalFormat) numberFormat).setParseBigDecimal(true);
+ }
+
+ return numberFormat;
+ }
+
+ @Override
+ public BigDecimal convertToModel(String value,
+ Class<? extends BigDecimal> targetType, Locale locale)
+ throws com.vaadin.data.util.converter.Converter.ConversionException {
+ return (BigDecimal) convertToNumber(value, BigDecimal.class, locale);
+ }
+
+ @Override
+ public Class<BigDecimal> getModelType() {
+ return BigDecimal.class;
+ }
+}
diff --git a/server/src/com/vaadin/data/util/converter/StringToNumberConverter.java b/server/src/com/vaadin/data/util/converter/StringToNumberConverter.java
deleted file mode 100644
index 22df42403f..0000000000
--- a/server/src/com/vaadin/data/util/converter/StringToNumberConverter.java
+++ /dev/null
@@ -1,65 +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.data.util.converter;
-
-import java.text.NumberFormat;
-import java.util.Locale;
-
-/**
- * A converter that converts from {@link Number} to {@link String} and back.
- * Uses the given locale and {@link NumberFormat} for formatting and parsing.
- * <p>
- * Override and overwrite {@link #getFormat(Locale)} to use a different format.
- * </p>
- *
- * @author Vaadin Ltd
- * @since 7.0
- */
-public class StringToNumberConverter extends
- AbstractStringToNumberConverter<Number> {
-
- /*
- * (non-Javadoc)
- *
- * @see
- * com.vaadin.data.util.converter.Converter#convertToModel(java.lang.Object,
- * java.lang.Class, java.util.Locale)
- */
- @Override
- public Number convertToModel(String value,
- Class<? extends Number> targetType, Locale locale)
- throws ConversionException {
- if (targetType != getModelType()) {
- throw new ConversionException("Converter only supports "
- + getModelType().getName() + " (targetType was "
- + targetType.getName() + ")");
- }
-
- return convertToNumber(value, targetType, locale);
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.data.util.converter.Converter#getModelType()
- */
- @Override
- public Class<Number> getModelType() {
- return Number.class;
- }
-
-}
diff --git a/server/src/com/vaadin/data/validator/BeanValidator.java b/server/src/com/vaadin/data/validator/BeanValidator.java
index ea7189bc5e..54efa51ac1 100644
--- a/server/src/com/vaadin/data/validator/BeanValidator.java
+++ b/server/src/com/vaadin/data/validator/BeanValidator.java
@@ -17,8 +17,6 @@
package com.vaadin.data.validator;
import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Locale;
import java.util.Set;
@@ -115,7 +113,9 @@ public class BeanValidator implements Validator {
Set<?> violations = getJavaxBeanValidator().validateValue(beanClass,
propertyName, value);
if (violations.size() > 0) {
- List<String> exceptions = new ArrayList<String>();
+ InvalidValueException[] causes = new InvalidValueException[violations
+ .size()];
+ int i = 0;
for (Object v : violations) {
final ConstraintViolation<?> violation = (ConstraintViolation<?>) v;
String msg = getJavaxBeanValidatorFactory()
@@ -123,16 +123,11 @@ public class BeanValidator implements Validator {
violation.getMessageTemplate(),
new SimpleContext(value, violation
.getConstraintDescriptor()), locale);
- exceptions.add(msg);
+ causes[i] = new InvalidValueException(msg);
+ ++i;
}
- StringBuilder b = new StringBuilder();
- for (int i = 0; i < exceptions.size(); i++) {
- if (i != 0) {
- b.append("<br/>");
- }
- b.append(exceptions.get(i));
- }
- throw new InvalidValueException(b.toString());
+
+ throw new InvalidValueException(null, causes);
}
}
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/BootstrapHandler.java b/server/src/com/vaadin/server/BootstrapHandler.java
index 0a4949ffa7..5a117958a0 100644
--- a/server/src/com/vaadin/server/BootstrapHandler.java
+++ b/server/src/com/vaadin/server/BootstrapHandler.java
@@ -146,14 +146,15 @@ public abstract class BootstrapHandler extends SynchronizedRequestHandler {
}
@Override
+ protected boolean canHandleRequest(VaadinRequest request) {
+ // We do not want to handle /APP requests here, instead let it fall
+ // through and produce a 404
+ return !ServletPortletHelper.isAppRequest(request);
+ }
+
+ @Override
public boolean synchronizedHandleRequest(VaadinSession session,
VaadinRequest request, VaadinResponse response) throws IOException {
- if (ServletPortletHelper.isAppRequest(request)) {
- // We do not want to handle /APP requests here, instead let it fall
- // through and produce a 404
- return false;
- }
-
try {
// Update WebBrowser here only to make WebBrowser information
// available in init for LegacyApplications
diff --git a/server/src/com/vaadin/server/Page.java b/server/src/com/vaadin/server/Page.java
index 037d8e8352..5c8b1aeb42 100644
--- a/server/src/com/vaadin/server/Page.java
+++ b/server/src/com/vaadin/server/Page.java
@@ -476,6 +476,8 @@ public class Page implements Serializable {
private final PageState state;
+ private String windowName;
+
public Page(UI uI, PageState state) {
this.uI = uI;
this.state = state;
@@ -637,6 +639,7 @@ public class Page implements Serializable {
String location = request.getParameter("v-loc");
String clientWidth = request.getParameter("v-cw");
String clientHeight = request.getParameter("v-ch");
+ windowName = request.getParameter("v-wn");
if (location != null) {
try {
@@ -662,6 +665,17 @@ public class Page implements Serializable {
}
/**
+ * Gets the window.name value of the browser window of this page.
+ *
+ * @since 7.2
+ *
+ * @return the window name, <code>null</code> if the name is not known
+ */
+ public String getWindowName() {
+ return windowName;
+ }
+
+ /**
* Updates the internal state with the given values. Does not resize the
* Page or browser window.
*
@@ -1124,7 +1138,7 @@ public class Page implements Serializable {
* the new page title to set
*/
public void setTitle(String title) {
- uI.getRpcProxy(PageClientRpc.class).setTitle(title);
+ getState(true).title = title;
}
/**
diff --git a/server/src/com/vaadin/server/ServiceDestroyEvent.java b/server/src/com/vaadin/server/ServiceDestroyEvent.java
new file mode 100644
index 0000000000..2ae4cc10af
--- /dev/null
+++ b/server/src/com/vaadin/server/ServiceDestroyEvent.java
@@ -0,0 +1,50 @@
+/*
+ * 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.util.EventObject;
+
+/**
+ * Event fired to {@link ServiceDestroyListener} when a {@link VaadinService} is
+ * being destroyed.
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public class ServiceDestroyEvent extends EventObject {
+
+ /**
+ * Creates a new event for the given service.
+ *
+ * @param service
+ * the service being destroyed
+ */
+ public ServiceDestroyEvent(VaadinService service) {
+ super(service);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.util.EventObject#getSource()
+ */
+ @Override
+ public VaadinService getSource() {
+ return (VaadinService) super.getSource();
+ }
+
+}
diff --git a/server/src/com/vaadin/server/ServiceDestroyListener.java b/server/src/com/vaadin/server/ServiceDestroyListener.java
new file mode 100644
index 0000000000..ad4966dd58
--- /dev/null
+++ b/server/src/com/vaadin/server/ServiceDestroyListener.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+/**
+ * Listener that gets notified when the {@link VaadinService} to which it has
+ * been registered is destroyed.
+ *
+ * @see VaadinService#addServiceDestroyListener(ServiceDestroyListener)
+ * @see VaadinService#removeServiceDestroyListener(ServiceDestroyListener)
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public interface ServiceDestroyListener extends Serializable {
+ /**
+ * Invoked when a service is destroyed
+ *
+ * @param event
+ * the event
+ */
+ public void serviceDestroy(ServiceDestroyEvent event);
+}
diff --git a/server/src/com/vaadin/server/SynchronizedRequestHandler.java b/server/src/com/vaadin/server/SynchronizedRequestHandler.java
index ac730dcecb..c695855d7d 100644
--- a/server/src/com/vaadin/server/SynchronizedRequestHandler.java
+++ b/server/src/com/vaadin/server/SynchronizedRequestHandler.java
@@ -32,6 +32,10 @@ public abstract class SynchronizedRequestHandler implements RequestHandler {
@Override
public boolean handleRequest(VaadinSession session, VaadinRequest request,
VaadinResponse response) throws IOException {
+ if (!canHandleRequest(request)) {
+ return false;
+ }
+
session.lock();
try {
return synchronizedHandleRequest(session, request, response);
@@ -62,4 +66,25 @@ public abstract class SynchronizedRequestHandler implements RequestHandler {
public abstract boolean synchronizedHandleRequest(VaadinSession session,
VaadinRequest request, VaadinResponse response) throws IOException;
+ /**
+ * Check whether a request may be handled by this handler. This can be used
+ * as an optimization to avoid locking the session just to investigate some
+ * method property. The default implementation just returns
+ * <code>true</code> which means that all requests will be handled by
+ * calling
+ * {@link #synchronizedHandleRequest(VaadinSession, VaadinRequest, VaadinResponse)}
+ * with the session locked.
+ *
+ * @since 7.2
+ * @param request
+ * the request to handle
+ * @return <code>true</code> if the request handling should continue once
+ * the session has been locked; <code>false</code> if there's no
+ * need to lock the session since the request would still not be
+ * handled.
+ */
+ protected boolean canHandleRequest(VaadinRequest request) {
+ return true;
+ }
+
}
diff --git a/server/src/com/vaadin/server/SystemMessages.java b/server/src/com/vaadin/server/SystemMessages.java
index 5e0fde1d4a..299c725207 100644
--- a/server/src/com/vaadin/server/SystemMessages.java
+++ b/server/src/com/vaadin/server/SystemMessages.java
@@ -63,32 +63,32 @@ public class SystemMessages implements Serializable {
protected String sessionExpiredURL = null;
protected boolean sessionExpiredNotificationEnabled = true;
protected String sessionExpiredCaption = "Session Expired";
- protected String sessionExpiredMessage = "Take note of any unsaved data, and <u>click here</u> to continue.";
+ protected String sessionExpiredMessage = "Take note of any unsaved data, and <u>click here</u> or press ESC key to continue.";
protected String communicationErrorURL = null;
protected boolean communicationErrorNotificationEnabled = true;
protected String communicationErrorCaption = "Communication problem";
- protected String communicationErrorMessage = "Take note of any unsaved data, and <u>click here</u> to continue.";
+ protected String communicationErrorMessage = "Take note of any unsaved data, and <u>click here</u> or press ESC to continue.";
protected String authenticationErrorURL = null;
protected boolean authenticationErrorNotificationEnabled = true;
protected String authenticationErrorCaption = "Authentication problem";
- protected String authenticationErrorMessage = "Take note of any unsaved data, and <u>click here</u> to continue.";
+ protected String authenticationErrorMessage = "Take note of any unsaved data, and <u>click here</u> or press ESC to continue.";
protected String internalErrorURL = null;
protected boolean internalErrorNotificationEnabled = true;
protected String internalErrorCaption = "Internal error";
- protected String internalErrorMessage = "Please notify the administrator.<br/>Take note of any unsaved data, and <u>click here</u> to continue.";
+ protected String internalErrorMessage = "Please notify the administrator.<br/>Take note of any unsaved data, and <u>click here</u> or press ESC to continue.";
protected String outOfSyncURL = null;
protected boolean outOfSyncNotificationEnabled = true;
protected String outOfSyncCaption = "Out of sync";
- protected String outOfSyncMessage = "Something has caused us to be out of sync with the server.<br/>Take note of any unsaved data, and <u>click here</u> to re-sync.";
+ protected String outOfSyncMessage = "Something has caused us to be out of sync with the server.<br/>Take note of any unsaved data, and <u>click here</u> or press ESC to re-sync.";
protected String cookiesDisabledURL = null;
protected boolean cookiesDisabledNotificationEnabled = true;
protected String cookiesDisabledCaption = "Cookies disabled";
- protected String cookiesDisabledMessage = "This application requires cookies to function.<br/>Please enable cookies in your browser and <u>click here</u> to try again.";
+ protected String cookiesDisabledMessage = "This application requires cookies to function.<br/>Please enable cookies in your browser and <u>click here</u> or press ESC to try again.";
/**
* Use {@link CustomizedSystemMessages} to customize
diff --git a/server/src/com/vaadin/server/VaadinPortlet.java b/server/src/com/vaadin/server/VaadinPortlet.java
index 093a1c9152..a41f301219 100644
--- a/server/src/com/vaadin/server/VaadinPortlet.java
+++ b/server/src/com/vaadin/server/VaadinPortlet.java
@@ -503,6 +503,12 @@ public class VaadinPortlet extends GenericPortlet implements Constants,
handleRequest(request, response);
}
+ @Override
+ public void destroy() {
+ super.destroy();
+ getService().destroy();
+ }
+
private static final Logger getLogger() {
return Logger.getLogger(VaadinPortlet.class.getName());
}
diff --git a/server/src/com/vaadin/server/VaadinService.java b/server/src/com/vaadin/server/VaadinService.java
index 44ceaaaf87..aff0124d16 100644
--- a/server/src/com/vaadin/server/VaadinService.java
+++ b/server/src/com/vaadin/server/VaadinService.java
@@ -41,7 +41,9 @@ import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
+import javax.portlet.Portlet;
import javax.portlet.PortletContext;
+import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletResponse;
@@ -97,6 +99,10 @@ public abstract class VaadinService implements Serializable {
.findMethod(SessionDestroyListener.class, "sessionDestroy",
SessionDestroyEvent.class);
+ private static final Method SERVICE_DESTROY_METHOD = ReflectTools
+ .findMethod(ServiceDestroyListener.class, "serviceDestroy",
+ ServiceDestroyEvent.class);
+
/**
* @deprecated As of 7.0. Only supported for {@link LegacyApplication}.
*/
@@ -1674,23 +1680,6 @@ public abstract class VaadinService implements Serializable {
FutureAccess future = new FutureAccess(session, runnable);
session.getPendingAccessQueue().add(future);
- ensureAccessQueuePurged(session);
-
- return future;
- }
-
- /**
- * Makes sure the pending access queue is purged for the provided session.
- * If the session is currently locked by the current thread or some other
- * thread, the queue will be purged when the session is unlocked. If the
- * lock is not held by any thread, it is acquired and the queue is purged
- * right away.
- *
- * @since 7.1.2
- * @param session
- * the session for which the access queue should be purged
- */
- public void ensureAccessQueuePurged(VaadinSession session) {
/*
* If no thread is currently holding the lock, pending changes for UIs
* with automatic push would not be processed and pushed until the next
@@ -1713,6 +1702,8 @@ public abstract class VaadinService implements Serializable {
} catch (InterruptedException e) {
// Just ignore
}
+
+ return future;
}
/**
@@ -1759,4 +1750,50 @@ public abstract class VaadinService implements Serializable {
CurrentInstance.restoreInstances(oldInstances);
}
}
+
+ /**
+ * Adds a service destroy listener that gets notified when this service is
+ * destroyed.
+ *
+ * @since 7.2
+ * @param listener
+ * the service destroy listener to add
+ *
+ * @see #destroy()
+ * @see #removeServiceDestroyListener(ServiceDestroyListener)
+ * @see ServiceDestroyListener
+ */
+ public void addServiceDestroyListener(ServiceDestroyListener listener) {
+ eventRouter.addListener(ServiceDestroyEvent.class, listener,
+ SERVICE_DESTROY_METHOD);
+ }
+
+ /**
+ * Removes a service destroy listener that was previously added with
+ * {@link #addServiceDestroyListener(ServiceDestroyListener)}.
+ *
+ * @since 7.2
+ * @param listener
+ * the service destroy listener to remove
+ */
+ public void removeServiceDestroyListener(ServiceDestroyListener listener) {
+ eventRouter.removeListener(ServiceDestroyEvent.class, listener,
+ SERVICE_DESTROY_METHOD);
+ }
+
+ /**
+ * Called when the servlet, portlet or similar for this service is being
+ * destroyed. After this method has been called, no more requests will be
+ * handled by this service.
+ *
+ * @see #addServiceDestroyListener(ServiceDestroyListener)
+ * @see Servlet#destroy()
+ * @see Portlet#destroy()
+ *
+ * @since 7.2
+ */
+ public void destroy() {
+ eventRouter.fireEvent(new ServiceDestroyEvent(this));
+ }
+
}
diff --git a/server/src/com/vaadin/server/VaadinServlet.java b/server/src/com/vaadin/server/VaadinServlet.java
index 7c0f9599f3..baf97d23d9 100644
--- a/server/src/com/vaadin/server/VaadinServlet.java
+++ b/server/src/com/vaadin/server/VaadinServlet.java
@@ -43,7 +43,6 @@ import javax.servlet.http.HttpServletResponse;
import com.vaadin.annotations.VaadinServletConfiguration;
import com.vaadin.annotations.VaadinServletConfiguration.InitParameterName;
import com.vaadin.sass.internal.ScssStylesheet;
-import com.vaadin.server.communication.PushRequestHandler;
import com.vaadin.server.communication.ServletUIInitHandler;
import com.vaadin.shared.JsonConstants;
import com.vaadin.ui.UI;
@@ -670,21 +669,11 @@ public class VaadinServlet extends HttpServlet implements Constants {
// Provide modification timestamp to the browser if it is known.
if (lastModifiedTime > 0) {
response.setDateHeader("Last-Modified", lastModifiedTime);
- /*
- * The browser is allowed to cache for 1 hour without checking if
- * the file has changed. This forces browsers to fetch a new version
- * when the Vaadin version is updated. This will cause more requests
- * to the servlet than without this but for high volume sites the
- * static files should never be served through the servlet. The
- * cache timeout can be configured by setting the resourceCacheTime
- * parameter in web.xml
- */
- int resourceCacheTime = getService().getDeploymentConfiguration()
- .getResourceCacheTime();
- String cacheControl = "max-age="
- + String.valueOf(resourceCacheTime);
- if (filename.contains("nocache")) {
- cacheControl = "public, max-age=0, must-revalidate";
+
+ String cacheControl = "public, max-age=0, must-revalidate";
+ int resourceCacheTime = getCacheTime(filename);
+ if (resourceCacheTime > 0) {
+ cacheControl = "max-age=" + String.valueOf(resourceCacheTime);
}
response.setHeader("Cache-Control", cacheControl);
}
@@ -693,6 +682,43 @@ public class VaadinServlet extends HttpServlet implements Constants {
}
/**
+ * Calculates the cache lifetime for the given filename in seconds. By
+ * default filenames containing ".nocache." return 0, filenames containing
+ * ".cache." return one year, all other return the value defined in the
+ * web.xml using resourceCacheTime (defaults to 1 hour).
+ *
+ * @param filename
+ * @return cache lifetime for the given filename in seconds
+ */
+ protected int getCacheTime(String filename) {
+ /*
+ * GWT conventions:
+ *
+ * - files containing .nocache. will not be cached.
+ *
+ * - files containing .cache. will be cached for one year.
+ *
+ * https://developers.google.com/web-toolkit/doc/latest/
+ * DevGuideCompilingAndDebugging#perfect_caching
+ */
+ if (filename.contains(".nocache.")) {
+ return 0;
+ }
+ if (filename.contains(".cache.")) {
+ return 60 * 60 * 24 * 365;
+ }
+ /*
+ * For all other files, the browser is allowed to cache for 1 hour
+ * without checking if the file has changed. This forces browsers to
+ * fetch a new version when the Vaadin version is updated. This will
+ * cause more requests to the servlet than without this but for high
+ * volume sites the static files should never be served through the
+ * servlet.
+ */
+ return getService().getDeploymentConfiguration().getResourceCacheTime();
+ }
+
+ /**
* Writes the contents of the given resourceUrl in the response. Can be
* overridden to add/modify response headers and similar.
*
@@ -982,20 +1008,8 @@ public class VaadinServlet extends HttpServlet implements Constants {
}
protected boolean isStaticResourceRequest(HttpServletRequest request) {
- String pathInfo = request.getPathInfo();
- if (pathInfo == null) {
- return false;
- }
-
- if ((request.getContextPath() != null)
- && (request.getRequestURI().startsWith("/VAADIN/"))) {
- return true;
- } else if (request.getRequestURI().startsWith(
- request.getContextPath() + "/VAADIN/")) {
- return true;
- }
-
- return false;
+ return request.getRequestURI().startsWith(
+ request.getContextPath() + "/VAADIN/");
}
/**
@@ -1075,15 +1089,15 @@ public class VaadinServlet extends HttpServlet implements Constants {
return u;
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see javax.servlet.GenericServlet#destroy()
+ */
@Override
public void destroy() {
super.destroy();
-
- for (RequestHandler handler : getService().getRequestHandlers()) {
- if (handler instanceof PushRequestHandler) {
- ((PushRequestHandler) handler).destroy();
- }
- }
+ getService().destroy();
}
/**
diff --git a/server/src/com/vaadin/server/VaadinSession.java b/server/src/com/vaadin/server/VaadinSession.java
index fd2ed79acd..f34721944a 100644
--- a/server/src/com/vaadin/server/VaadinSession.java
+++ b/server/src/com/vaadin/server/VaadinSession.java
@@ -43,7 +43,6 @@ import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
-import com.vaadin.annotations.PreserveOnRefresh;
import com.vaadin.data.util.converter.Converter;
import com.vaadin.data.util.converter.ConverterFactory;
import com.vaadin.data.util.converter.DefaultConverterFactory;
@@ -205,7 +204,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
private int nextUIId = 0;
private Map<Integer, UI> uIs = new HashMap<Integer, UI>();
- private final Map<String, Integer> retainOnRefreshUIs = new HashMap<String, Integer>();
+ private final Map<String, Integer> embedIdMap = new HashMap<String, Integer>();
private final EventRouter eventRouter = new EventRouter();
@@ -828,10 +827,13 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
*/
public void removeUI(UI ui) {
assert hasLock();
- int id = ui.getUIId();
+ Integer id = Integer.valueOf(ui.getUIId());
ui.setSession(null);
uIs.remove(id);
- retainOnRefreshUIs.values().remove(id);
+ String embedId = ui.getEmbedId();
+ if (embedId != null && id.equals(embedIdMap.get(embedId))) {
+ embedIdMap.remove(embedId);
+ }
}
/**
@@ -938,14 +940,12 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
*/
public void unlock() {
assert hasLock();
- boolean ultimateRelease = false;
try {
/*
* Run pending tasks and push if the reentrant lock will actually be
* released by this unlock() invocation.
*/
if (((ReentrantLock) getLockInstance()).getHoldCount() == 1) {
- ultimateRelease = true;
getService().runPendingAccessTasks(this);
for (UI ui : getUIs()) {
@@ -963,18 +963,6 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
} finally {
getLockInstance().unlock();
}
-
- /*
- * If the session is locked when a new access task is added, it is
- * assumed that the queue will be purged when the lock is released. This
- * might however not happen if a task is enqueued between the moment
- * when unlock() purges the queue and the moment when the lock is
- * actually released. This means that the queue should be purged again
- * if it is not empty after unlocking.
- */
- if (ultimateRelease && !getPendingAccessQueue().isEmpty()) {
- getService().ensureAccessQueuePurged(this);
- }
}
/**
@@ -1099,20 +1087,6 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
}
/**
- * Gets the mapping from <code>window.name</code> to UI id for UIs that are
- * should be retained on refresh.
- *
- * @see VaadinService#preserveUIOnRefresh(VaadinRequest, UI, UIProvider)
- * @see PreserveOnRefresh
- *
- * @return the mapping between window names and UI ids for this session.
- */
- public Map<String, Integer> getPreserveOnRefreshUIs() {
- assert hasLock();
- return retainOnRefreshUIs;
- }
-
- /**
* Adds an initialized UI to this session.
*
* @param ui
@@ -1129,7 +1103,21 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
"The UI belongs to a different session");
}
- uIs.put(Integer.valueOf(ui.getUIId()), ui);
+ Integer uiId = Integer.valueOf(ui.getUIId());
+ uIs.put(uiId, ui);
+
+ String embedId = ui.getEmbedId();
+ if (embedId != null) {
+ Integer previousUiId = embedIdMap.put(embedId, uiId);
+ if (previousUiId != null) {
+ UI previousUi = uIs.get(previousUiId);
+ assert previousUi != null
+ && embedId.equals(previousUi.getEmbedId()) : "UI id map and embed id map not in sync";
+
+ // Will fire cleanup events at the end of the request handling.
+ previousUi.close();
+ }
+ }
}
/**
@@ -1340,4 +1328,25 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
stream.defaultReadObject();
pendingAccessQueue = new ConcurrentLinkedQueue<FutureAccess>();
}
+
+ /**
+ * Finds the UI with the corresponding embed id.
+ *
+ * @since 7.2
+ * @param embedId
+ * the embed id
+ * @return the UI with the corresponding embed id, or <code>null</code> if
+ * no UI is found
+ *
+ * @see UI#getEmbedId()
+ */
+ public UI getUIByEmbedId(String embedId) {
+ Integer uiId = embedIdMap.get(embedId);
+ if (uiId == null) {
+ return null;
+ } else {
+ return getUIById(uiId.intValue());
+ }
+ }
+
}
diff --git a/server/src/com/vaadin/server/communication/FileUploadHandler.java b/server/src/com/vaadin/server/communication/FileUploadHandler.java
index 3f6bfd9267..41a16601fe 100644
--- a/server/src/com/vaadin/server/communication/FileUploadHandler.java
+++ b/server/src/com/vaadin/server/communication/FileUploadHandler.java
@@ -284,7 +284,7 @@ public class FileUploadHandler implements RequestHandler {
// if boundary string does not exist, the posted file is from
// XHR2.post(File)
doHandleXhrFilePost(session, request, response, streamVariable,
- variableName, source, request.getContentLength());
+ variableName, source, getContentLength(request));
}
return true;
}
@@ -336,7 +336,7 @@ public class FileUploadHandler implements RequestHandler {
final InputStream inputStream = request.getInputStream();
- int contentLength = request.getContentLength();
+ long contentLength = getContentLength(request);
boolean atStart = false;
boolean firstFileFieldFound = false;
@@ -403,9 +403,22 @@ public class FileUploadHandler implements RequestHandler {
}
+ /*
+ * request.getContentLength() is limited to "int" by the Servlet
+ * specification. To support larger file uploads manually evaluate the
+ * Content-Length header which can contain long values.
+ */
+ private long getContentLength(VaadinRequest request) {
+ try {
+ return Long.parseLong(request.getHeader("Content-Length"));
+ } catch (NumberFormatException e) {
+ return -1l;
+ }
+ }
+
private void handleFileUploadValidationAndData(VaadinSession session,
InputStream inputStream, StreamVariable streamVariable,
- String filename, String mimeType, int contentLength,
+ String filename, String mimeType, long contentLength,
ClientConnector connector, String variableName)
throws UploadException {
session.lock();
@@ -474,7 +487,7 @@ public class FileUploadHandler implements RequestHandler {
protected void doHandleXhrFilePost(VaadinSession session,
VaadinRequest request, VaadinResponse response,
StreamVariable streamVariable, String variableName,
- ClientConnector owner, int contentLength) throws IOException {
+ ClientConnector owner, long contentLength) throws IOException {
// These are unknown in filexhr ATM, maybe add to Accept header that
// is accessible in portlets
@@ -504,7 +517,7 @@ public class FileUploadHandler implements RequestHandler {
*/
protected final boolean streamToReceiver(VaadinSession session,
final InputStream in, StreamVariable streamVariable,
- String filename, String type, int contentLength)
+ String filename, String type, long contentLength)
throws UploadException {
if (streamVariable == null) {
throw new IllegalStateException(
@@ -512,7 +525,7 @@ public class FileUploadHandler implements RequestHandler {
}
OutputStream out = null;
- int totalBytes = 0;
+ long totalBytes = 0;
StreamingStartEventImpl startedEvent = new StreamingStartEventImpl(
filename, type, contentLength);
try {
diff --git a/server/src/com/vaadin/server/communication/HeartbeatHandler.java b/server/src/com/vaadin/server/communication/HeartbeatHandler.java
index 4c95859203..04cb1b5a25 100644
--- a/server/src/com/vaadin/server/communication/HeartbeatHandler.java
+++ b/server/src/com/vaadin/server/communication/HeartbeatHandler.java
@@ -43,6 +43,11 @@ import com.vaadin.ui.UI;
public class HeartbeatHandler extends SynchronizedRequestHandler implements
SessionExpiredHandler {
+ @Override
+ protected boolean canHandleRequest(VaadinRequest request) {
+ return ServletPortletHelper.isHeartbeatRequest(request);
+ }
+
/**
* Handles a heartbeat request for the given session. Reads the GET
* parameter named {@link UIConstants#UI_ID_PARAMETER} to identify the UI.
@@ -53,10 +58,6 @@ public class HeartbeatHandler extends SynchronizedRequestHandler implements
@Override
public boolean synchronizedHandleRequest(VaadinSession session,
VaadinRequest request, VaadinResponse response) throws IOException {
- if (!ServletPortletHelper.isHeartbeatRequest(request)) {
- return false;
- }
-
UI ui = session.getService().findUI(request);
if (ui != null) {
ui.setLastHeartbeatTimestamp(System.currentTimeMillis());
diff --git a/server/src/com/vaadin/server/communication/PushRequestHandler.java b/server/src/com/vaadin/server/communication/PushRequestHandler.java
index 8d0da24896..74595322a0 100644
--- a/server/src/com/vaadin/server/communication/PushRequestHandler.java
+++ b/server/src/com/vaadin/server/communication/PushRequestHandler.java
@@ -28,6 +28,8 @@ import org.atmosphere.cpr.AtmosphereRequest;
import org.atmosphere.cpr.AtmosphereResponse;
import com.vaadin.server.RequestHandler;
+import com.vaadin.server.ServiceDestroyEvent;
+import com.vaadin.server.ServiceDestroyListener;
import com.vaadin.server.ServiceException;
import com.vaadin.server.ServletPortletHelper;
import com.vaadin.server.SessionExpiredHandler;
@@ -63,6 +65,13 @@ public class PushRequestHandler implements RequestHandler,
}
};
+ service.addServiceDestroyListener(new ServiceDestroyListener() {
+ @Override
+ public void serviceDestroy(ServiceDestroyEvent event) {
+ destroy();
+ }
+ });
+
pushHandler = new PushHandler(service);
atmosphere.addAtmosphereHandler("/*", pushHandler);
atmosphere.addInitParameter(ApplicationConfig.PROPERTY_SESSION_SUPPORT,
diff --git a/server/src/com/vaadin/server/communication/ServerRpcHandler.java b/server/src/com/vaadin/server/communication/ServerRpcHandler.java
index f14d703454..432a9ea893 100644
--- a/server/src/com/vaadin/server/communication/ServerRpcHandler.java
+++ b/server/src/com/vaadin/server/communication/ServerRpcHandler.java
@@ -20,8 +20,6 @@ import java.io.IOException;
import java.io.Reader;
import java.io.Serializable;
import java.lang.reflect.Type;
-import java.text.CharacterIterator;
-import java.text.StringCharacterIterator;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -32,6 +30,7 @@ import java.util.logging.Logger;
import org.json.JSONArray;
import org.json.JSONException;
+import org.json.JSONObject;
import com.vaadin.server.ClientConnector;
import com.vaadin.server.JsonCodec;
@@ -62,10 +61,71 @@ import com.vaadin.ui.UI;
*/
public class ServerRpcHandler implements Serializable {
- /* Variable records indexes */
- public static final char VAR_BURST_SEPARATOR = '\u001d';
+ /**
+ * A data transfer object representing an RPC request sent by the client
+ * side.
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+ public static class RpcRequest {
+
+ private final String csrfToken;
+ private final JSONArray invocations;
+ private final int syncId;
+ private final JSONObject json;
+
+ public RpcRequest(String jsonString) throws JSONException {
+ json = new JSONObject(jsonString);
+ csrfToken = json.getString(ApplicationConstants.CSRF_TOKEN);
+ syncId = json.getInt(ApplicationConstants.SERVER_SYNC_ID);
+ invocations = new JSONArray(
+ json.getString(ApplicationConstants.RPC_INVOCATIONS));
+ }
- public static final char VAR_ESCAPE_CHARACTER = '\u001b';
+ /**
+ * Gets the CSRF security token (double submit cookie) for this request.
+ *
+ * @return the CSRF security token for this current change request
+ */
+ public String getCsrfToken() {
+ return csrfToken;
+ }
+
+ /**
+ * Gets the data to recreate the RPC as requested by the client side.
+ *
+ * @return the data describing which RPC should be made, and all their
+ * data
+ */
+ public JSONArray getRpcInvocationsData() {
+ return invocations;
+ }
+
+ /**
+ * Gets the sync id last seen by the client.
+ *
+ * @return the last sync id given by the server, according to the
+ * client's request
+ */
+ public int getSyncId() {
+ return syncId;
+ }
+
+ /**
+ * Gets the entire request in JSON format, as it was received from the
+ * client.
+ * <p>
+ * <em>Note:</em> This is a shared reference - any modifications made
+ * will be shared.
+ *
+ * @return the raw JSON object that was received from the client
+ *
+ */
+ public JSONObject getRawJson() {
+ return json;
+ }
+ }
private static final int MAX_BUFFER_SIZE = 64 * 1024;
@@ -90,45 +150,50 @@ public class ServerRpcHandler implements Serializable {
throws IOException, InvalidUIDLSecurityKeyException, JSONException {
ui.getSession().setLastRequestTimestamp(System.currentTimeMillis());
- String changes = getMessage(reader);
+ String changeMessage = getMessage(reader);
- final String[] bursts = changes.split(String
- .valueOf(VAR_BURST_SEPARATOR));
-
- if (bursts.length > 2) {
- throw new RuntimeException(
- "Multiple variable bursts not supported in Vaadin 7");
- } else if (bursts.length <= 1) {
+ if (changeMessage == null || changeMessage.equals("")) {
// The client sometimes sends empty messages, this is probably a bug
return;
}
+ RpcRequest rpcRequest = new RpcRequest(changeMessage);
+
// Security: double cookie submission pattern unless disabled by
// property
- if (!VaadinService.isCsrfTokenValid(ui.getSession(), bursts[0])) {
+ if (!VaadinService.isCsrfTokenValid(ui.getSession(),
+ rpcRequest.getCsrfToken())) {
throw new InvalidUIDLSecurityKeyException("");
}
- handleBurst(ui, unescapeBurst(bursts[1]));
+ handleInvocations(ui, rpcRequest.getSyncId(),
+ rpcRequest.getRpcInvocationsData());
+
+ ui.getConnectorTracker().cleanConcurrentlyRemovedConnectorIds(
+ rpcRequest.getSyncId());
}
/**
- * Processes a message burst received from the client.
- *
- * A burst can contain any number of RPC calls, including legacy variable
- * change calls that are processed separately.
- *
+ * Processes invocations data received from the client.
+ * <p>
+ * The invocations data can contain any number of RPC calls, including
+ * legacy variable change calls that are processed separately.
+ * <p>
* Consecutive changes to the value of the same variable are combined and
* changeVariables() is only called once for them. This preserves the Vaadin
* 6 semantics for components and add-ons that do not use Vaadin 7 RPC
* directly.
*
- * @param source
* @param uI
- * the UI receiving the burst
- * @param burst
- * the content of the burst as a String to be parsed
+ * the UI receiving the invocations data
+ * @param lastSyncIdSeenByClient
+ * the most recent sync id the client has seen at the time the
+ * request was sent
+ * @param invocationsData
+ * JSON containing all information needed to execute all
+ * requested RPC calls.
*/
- private void handleBurst(UI uI, String burst) {
+ private void handleInvocations(UI uI, int lastSyncIdSeenByClient,
+ JSONArray invocationsData) {
// TODO PUSH Refactor so that this is not needed
LegacyCommunicationManager manager = uI.getSession()
.getCommunicationManager();
@@ -137,7 +202,8 @@ public class ServerRpcHandler implements Serializable {
Set<Connector> enabledConnectors = new HashSet<Connector>();
List<MethodInvocation> invocations = parseInvocations(
- uI.getConnectorTracker(), burst);
+ uI.getConnectorTracker(), invocationsData,
+ lastSyncIdSeenByClient);
for (MethodInvocation invocation : invocations) {
final ClientConnector connector = manager.getConnector(uI,
invocation.getConnectorId());
@@ -243,21 +309,22 @@ public class ServerRpcHandler implements Serializable {
}
/**
- * Parse a message burst from the client into a list of MethodInvocation
- * instances.
+ * Parse JSON from the client into a list of MethodInvocation instances.
*
* @param connectorTracker
* The ConnectorTracker used to lookup connectors
- * @param burst
- * message string (JSON)
+ * @param invocationsJson
+ * JSON containing all information needed to execute all
+ * requested RPC calls.
+ * @param lastSyncIdSeenByClient
+ * the most recent sync id the client has seen at the time the
+ * request was sent
* @return list of MethodInvocation to perform
* @throws JSONException
*/
private List<MethodInvocation> parseInvocations(
- ConnectorTracker connectorTracker, String burst)
- throws JSONException {
- JSONArray invocationsJson = new JSONArray(burst);
-
+ ConnectorTracker connectorTracker, JSONArray invocationsJson,
+ int lastSyncIdSeenByClient) throws JSONException {
ArrayList<MethodInvocation> invocations = new ArrayList<MethodInvocation>();
MethodInvocation previousInvocation = null;
@@ -267,7 +334,8 @@ public class ServerRpcHandler implements Serializable {
JSONArray invocationJson = invocationsJson.getJSONArray(i);
MethodInvocation invocation = parseInvocation(invocationJson,
- previousInvocation, connectorTracker);
+ previousInvocation, connectorTracker,
+ lastSyncIdSeenByClient);
if (invocation != null) {
// Can be null if the invocation was a legacy invocation and it
// was merged with the previous one or if the invocation was
@@ -281,7 +349,8 @@ public class ServerRpcHandler implements Serializable {
private MethodInvocation parseInvocation(JSONArray invocationJson,
MethodInvocation previousInvocation,
- ConnectorTracker connectorTracker) throws JSONException {
+ ConnectorTracker connectorTracker, long lastSyncIdSeenByClient)
+ throws JSONException {
String connectorId = invocationJson.getString(0);
String interfaceName = invocationJson.getString(1);
String methodName = invocationJson.getString(2);
@@ -289,18 +358,22 @@ public class ServerRpcHandler implements Serializable {
if (connectorTracker.getConnector(connectorId) == null
&& !connectorId
.equals(ApplicationConstants.DRAG_AND_DROP_CONNECTOR_ID)) {
- getLogger()
- .log(Level.WARNING,
- "RPC call to "
- + interfaceName
- + "."
- + methodName
- + " received for connector "
- + connectorId
- + " but no such connector could be found. Resynchronizing client.");
- // This is likely an out of sync issue (client tries to update a
- // connector which is not present). Force resync.
- connectorTracker.markAllConnectorsDirty();
+
+ if (!connectorTracker.connectorWasPresentAsRequestWasSent(
+ connectorId, lastSyncIdSeenByClient)) {
+ getLogger()
+ .log(Level.WARNING,
+ "RPC call to "
+ + interfaceName
+ + "."
+ + methodName
+ + " received for connector "
+ + connectorId
+ + " but no such connector could be found. Resynchronizing client.");
+ // This is likely an out of sync issue (client tries to update a
+ // connector which is not present). Force resync.
+ connectorTracker.markAllConnectorsDirty();
+ }
return null;
}
@@ -396,50 +469,6 @@ public class ServerRpcHandler implements Serializable {
owner.changeVariables(source, m);
}
- /**
- * Unescape encoded burst separator characters in a burst received from the
- * client. This protects from separator injection attacks.
- *
- * @param encodedValue
- * to decode
- * @return decoded value
- */
- protected String unescapeBurst(String encodedValue) {
- final StringBuilder result = new StringBuilder();
- final StringCharacterIterator iterator = new StringCharacterIterator(
- encodedValue);
- char character = iterator.current();
- while (character != CharacterIterator.DONE) {
- if (VAR_ESCAPE_CHARACTER == character) {
- character = iterator.next();
- switch (character) {
- case VAR_ESCAPE_CHARACTER + 0x30:
- // escaped escape character
- result.append(VAR_ESCAPE_CHARACTER);
- break;
- case VAR_BURST_SEPARATOR + 0x30:
- // +0x30 makes these letters for easier reading
- result.append((char) (character - 0x30));
- break;
- case CharacterIterator.DONE:
- // error
- throw new RuntimeException(
- "Communication error: Unexpected end of message");
- default:
- // other escaped character - probably a client-server
- // version mismatch
- throw new RuntimeException(
- "Invalid escaped character from the client - check that the widgetset and server versions match");
- }
- } else {
- // not a special character - add it to the result as is
- result.append(character);
- }
- character = iterator.next();
- }
- return result.toString();
- }
-
protected String getMessage(Reader reader) throws IOException {
StringBuilder sb = new StringBuilder(MAX_BUFFER_SIZE);
diff --git a/server/src/com/vaadin/server/communication/UIInitHandler.java b/server/src/com/vaadin/server/communication/UIInitHandler.java
index d4b0bc709f..6ab9d9dc58 100644
--- a/server/src/com/vaadin/server/communication/UIInitHandler.java
+++ b/server/src/com/vaadin/server/communication/UIInitHandler.java
@@ -20,7 +20,6 @@ import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.util.List;
-import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -38,6 +37,7 @@ import com.vaadin.server.VaadinResponse;
import com.vaadin.server.VaadinService;
import com.vaadin.server.VaadinSession;
import com.vaadin.shared.ApplicationConstants;
+import com.vaadin.shared.JsonConstants;
import com.vaadin.shared.communication.PushMode;
import com.vaadin.shared.ui.ui.Transport;
import com.vaadin.shared.ui.ui.UIConstants;
@@ -56,12 +56,13 @@ public abstract class UIInitHandler extends SynchronizedRequestHandler {
protected abstract boolean isInitRequest(VaadinRequest request);
@Override
+ protected boolean canHandleRequest(VaadinRequest request) {
+ return isInitRequest(request);
+ }
+
+ @Override
public boolean synchronizedHandleRequest(VaadinSession session,
VaadinRequest request, VaadinResponse response) throws IOException {
- if (!isInitRequest(request)) {
- return false;
- }
-
StringWriter stringWriter = new StringWriter();
try {
@@ -107,7 +108,7 @@ public abstract class UIInitHandler extends SynchronizedRequestHandler {
static boolean commitJsonResponse(VaadinRequest request,
VaadinResponse response, String json) throws IOException {
// The response was produced without errors so write it to the client
- response.setContentType("application/json; charset=UTF-8");
+ response.setContentType(JsonConstants.JSON_CONTENT_TYPE);
// Ensure that the browser does not cache UIDL responses.
// iOS 6 Safari requires this (#9732)
@@ -163,31 +164,29 @@ public abstract class UIInitHandler extends SynchronizedRequestHandler {
return null;
}
- // Check for an existing UI based on window.name
+ // Check for an existing UI based on embed id
- // Special parameter sent by vaadinBootstrap.js
- String windowName = request.getParameter("v-wn");
+ String embedId = getEmbedId(request);
- Map<String, Integer> retainOnRefreshUIs = session
- .getPreserveOnRefreshUIs();
- if (windowName != null && !retainOnRefreshUIs.isEmpty()) {
- // Check for a known UI
-
- Integer retainedUIId = retainOnRefreshUIs.get(windowName);
-
- if (retainedUIId != null) {
- UI retainedUI = session.getUIById(retainedUIId.intValue());
+ UI retainedUI = session.getUIByEmbedId(embedId);
+ if (retainedUI != null) {
+ if (vaadinService.preserveUIOnRefresh(provider, new UICreateEvent(
+ request, uiClass))) {
if (uiClass.isInstance(retainedUI)) {
reinitUI(retainedUI, request);
return retainedUI;
} else {
getLogger().info(
- "Not using retained UI in " + windowName
- + " because retained UI was of type "
+ "Not using the preserved UI " + embedId
+ + " because it is of type "
+ retainedUI.getClass() + " but " + uiClass
+ " is expected for the request.");
}
}
+ /*
+ * Previous UI without preserve on refresh will be closed when the
+ * new UI gets added to the session.
+ */
}
// No existing UI found - go on by creating and initializing one
@@ -220,26 +219,45 @@ public abstract class UIInitHandler extends SynchronizedRequestHandler {
// Set thread local here so it is available in init
UI.setCurrent(ui);
- ui.doInit(request, uiId.intValue());
+ ui.doInit(request, uiId.intValue(), embedId);
session.addUI(ui);
- // Remember if it should be remembered
- if (vaadinService.preserveUIOnRefresh(provider, event)) {
- // Remember this UI
- if (windowName == null) {
- getLogger().warning(
- "There is no window.name available for UI " + uiClass
- + " that should be preserved.");
- } else {
- session.getPreserveOnRefreshUIs().put(windowName, uiId);
- }
+ // Warn if the window can't be preserved
+ if (embedId == null
+ && vaadinService.preserveUIOnRefresh(provider, event)) {
+ getLogger().warning(
+ "There is no embed id available for UI " + uiClass
+ + " that should be preserved.");
}
return ui;
}
/**
+ * Constructs an embed id based on information in the request.
+ *
+ * @since 7.2
+ *
+ * @param request
+ * the request to get embed information from
+ * @return the embed id, or <code>null</code> if id is not available.
+ *
+ * @see UI#getEmbedId()
+ */
+ protected String getEmbedId(VaadinRequest request) {
+ // Parameters sent by vaadinBootstrap.js
+ String windowName = request.getParameter("v-wn");
+ String appId = request.getParameter("v-appId");
+
+ if (windowName != null && appId != null) {
+ return windowName + '.' + appId;
+ } else {
+ return null;
+ }
+ }
+
+ /**
* Updates a UI that has already been initialized but is now loaded again,
* e.g. because of {@link PreserveOnRefresh}.
*
diff --git a/server/src/com/vaadin/server/communication/UidlRequestHandler.java b/server/src/com/vaadin/server/communication/UidlRequestHandler.java
index d52c5e9fe0..cf25910fa4 100644
--- a/server/src/com/vaadin/server/communication/UidlRequestHandler.java
+++ b/server/src/com/vaadin/server/communication/UidlRequestHandler.java
@@ -60,11 +60,13 @@ public class UidlRequestHandler extends SynchronizedRequestHandler implements
}
@Override
+ protected boolean canHandleRequest(VaadinRequest request) {
+ return ServletPortletHelper.isUIDLRequest(request);
+ }
+
+ @Override
public boolean synchronizedHandleRequest(VaadinSession session,
VaadinRequest request, VaadinResponse response) throws IOException {
- if (!ServletPortletHelper.isUIDLRequest(request)) {
- return false;
- }
UI uI = session.getService().findUI(request);
if (uI == null) {
// This should not happen but it will if the UI has been closed. We
diff --git a/server/src/com/vaadin/server/communication/UidlWriter.java b/server/src/com/vaadin/server/communication/UidlWriter.java
index 60933a75c2..b46fbbf58a 100644
--- a/server/src/com/vaadin/server/communication/UidlWriter.java
+++ b/server/src/com/vaadin/server/communication/UidlWriter.java
@@ -38,6 +38,7 @@ import com.vaadin.server.LegacyCommunicationManager;
import com.vaadin.server.LegacyCommunicationManager.ClientCache;
import com.vaadin.server.SystemMessages;
import com.vaadin.server.VaadinSession;
+import com.vaadin.shared.ApplicationConstants;
import com.vaadin.ui.ConnectorTracker;
import com.vaadin.ui.UI;
@@ -98,6 +99,9 @@ public class UidlWriter implements Serializable {
uiConnectorTracker.setWritingResponse(true);
try {
+ writer.write("\"" + ApplicationConstants.SERVER_SYNC_ID
+ + "\": " + uiConnectorTracker.getCurrentSyncId() + ", ");
+
writer.write("\"changes\" : ");
JsonPaintTarget paintTarget = new JsonPaintTarget(manager, writer,
diff --git a/server/src/com/vaadin/ui/Component.java b/server/src/com/vaadin/ui/Component.java
index 485327bb54..c385805675 100644
--- a/server/src/com/vaadin/ui/Component.java
+++ b/server/src/com/vaadin/ui/Component.java
@@ -651,7 +651,7 @@ public interface Component extends ClientConnector, Sizeable, Serializable {
public Locale getLocale();
/**
- * Adds an unique id for component that get's transferred to terminal for
+ * Adds an unique id for component that is used in the client-side for
* testing purposes. Keeping identifiers unique is the responsibility of the
* programmer.
*
@@ -661,7 +661,7 @@ public interface Component extends ClientConnector, Sizeable, Serializable {
public void setId(String id);
/**
- * Get's currently set debug identifier
+ * Gets currently set debug identifier
*
* @return current id, null if not set
*/
@@ -669,7 +669,7 @@ public interface Component extends ClientConnector, Sizeable, Serializable {
/**
* <p>
- * Gets the component's description, used in tooltips and can be displayed
+ * Gets the components description, used in tooltips and can be displayed
* directly in certain other components such as forms. The description can
* be used to briefly describe the state of the component to the user. The
* description string may contain certain XML tags:
diff --git a/server/src/com/vaadin/ui/ConnectorTracker.java b/server/src/com/vaadin/ui/ConnectorTracker.java
index 0f8ec60104..33d585adca 100644
--- a/server/src/com/vaadin/ui/ConnectorTracker.java
+++ b/server/src/com/vaadin/ui/ConnectorTracker.java
@@ -25,6 +25,7 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
+import java.util.TreeMap;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -81,6 +82,16 @@ public class ConnectorTracker implements Serializable {
private Map<StreamVariable, String> streamVariableToSeckey;
+ private int currentSyncId = 0;
+
+ /**
+ * Map to track on which syncId each connector was removed.
+ *
+ * @see #getCurrentSyncId()
+ * @see #cleanConcurrentlyRemovedConnectorIds(long)
+ */
+ private TreeMap<Integer, Set<String>> syncIdToUnregisteredConnectorIds = new TreeMap<Integer, Set<String>>();
+
/**
* Gets a logger for this class
*
@@ -170,6 +181,15 @@ public class ConnectorTracker implements Serializable {
+ " is not the one that was registered for that id");
}
+ Set<String> unregisteredConnectorIds = syncIdToUnregisteredConnectorIds
+ .get(currentSyncId);
+ if (unregisteredConnectorIds == null) {
+ unregisteredConnectorIds = new HashSet<String>();
+ syncIdToUnregisteredConnectorIds.put(currentSyncId,
+ unregisteredConnectorIds);
+ }
+ unregisteredConnectorIds.add(connectorId);
+
dirtyConnectors.remove(connector);
if (unregisteredConnectors.add(connector)) {
if (getLogger().isLoggable(Level.FINE)) {
@@ -570,12 +590,18 @@ public class ConnectorTracker implements Serializable {
/**
* Sets the current response write status. Connectors can not be marked as
* dirty when the response is written.
+ * <p>
+ * This method has a side-effect of incrementing the sync id by one (see
+ * {@link #getCurrentSyncId()}), if {@link #isWritingResponse()} returns
+ * <code>false</code> and <code>writingResponse</code> is set to
+ * <code>true</code>.
*
* @param writingResponse
* the new response status.
*
* @see #markDirty(ClientConnector)
* @see #isWritingResponse()
+ * @see #getCurrentSyncId()
*
* @throws IllegalArgumentException
* if the new response status is the same as the previous value.
@@ -587,6 +613,14 @@ public class ConnectorTracker implements Serializable {
throw new IllegalArgumentException(
"The old value is same as the new value");
}
+
+ /*
+ * the right hand side of the && is unnecessary here because of the
+ * if-clause above, but rigorous coding is always rigorous coding.
+ */
+ if (writingResponse && !this.writingResponse) {
+ currentSyncId++;
+ }
this.writingResponse = writingResponse;
}
@@ -732,4 +766,105 @@ public class ConnectorTracker implements Serializable {
}
return streamVariableToSeckey.get(variable);
}
+
+ /**
+ * Check whether a connector was present on the client when the it was
+ * creating this request, but was removed server-side before the request
+ * arrived.
+ *
+ * @since 7.2
+ * @param connectorId
+ * The connector id to check for whether it was removed
+ * concurrently or not.
+ * @param lastSyncIdSeenByClient
+ * the most recent sync id the client has seen at the time the
+ * request was sent
+ * @return <code>true</code> if the connector was removed before the client
+ * had a chance to react to it.
+ */
+ public boolean connectorWasPresentAsRequestWasSent(String connectorId,
+ long lastSyncIdSeenByClient) {
+
+ assert getConnector(connectorId) == null : "Connector " + connectorId
+ + " is still attached";
+
+ boolean clientRequestIsTooOld = lastSyncIdSeenByClient < currentSyncId;
+ if (clientRequestIsTooOld) {
+ /*
+ * The headMap call is present here because we're only interested in
+ * connectors removed "in the past" (i.e. the server has removed
+ * them before the client ever knew about that), since those are the
+ * ones that we choose to handle as a special case.
+ */
+ /*-
+ * Server Client
+ * [#1 add table] ---------.
+ * \
+ * [push: #2 remove table]-. `--> [adding table, storing #1]
+ * \ .- [table from request #1 needs more data]
+ * \/
+ * /`-> [removing table, storing #2]
+ * [#1 < #2 - ignoring] <---´
+ */
+ for (Set<String> unregisteredConnectors : syncIdToUnregisteredConnectorIds
+ .headMap(currentSyncId).values()) {
+ if (unregisteredConnectors.contains(connectorId)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Gets the most recently generated server sync id.
+ * <p>
+ * The sync id is incremented by one whenever a new response is being
+ * written. This id is then sent over to the client. The client then adds
+ * the most recent sync id to each communication packet it sends back to the
+ * server. This way, the server knows at what state the client is when the
+ * packet is sent. If the state has changed on the server side since that,
+ * the server can try to adjust the way it handles the actions from the
+ * client side.
+ *
+ * @see #setWritingResponse(boolean)
+ * @see #connectorWasPresentAsRequestWasSent(String, long)
+ * @since 7.2
+ * @return the current sync id
+ */
+ public int getCurrentSyncId() {
+ return currentSyncId;
+ }
+
+ /**
+ * Maintains the bookkeeping connector removal and concurrency by removing
+ * entries that have become too old.
+ * <p>
+ * <em>It is important to run this call for each transmission from the client</em>
+ * , otherwise the bookkeeping gets out of date and the results form
+ * {@link #connectorWasPresentAsRequestWasSent(String, long)} will become
+ * invalid (that is, even though the client knew the component was removed,
+ * the aforementioned method would start claiming otherwise).
+ * <p>
+ * Entries that both client and server agree upon are removed. Since
+ * argument is the last sync id that the client has seen from the server, we
+ * know that entries earlier than that cannot cause any problems anymore.
+ *
+ * @see #connectorWasPresentAsRequestWasSent(String, long)
+ * @since 7.2
+ * @param lastSyncIdSeenByClient
+ * the sync id the client has most recently received from the
+ * server.
+ */
+ public void cleanConcurrentlyRemovedConnectorIds(int lastSyncIdSeenByClient) {
+ /*
+ * We remove all entries _older_ than the one reported right now,
+ * because the remaining still contain components that might cause
+ * conflicts. In any case, it's better to clean up too little than too
+ * much, especially as the data will hardly grow into the kilobytes.
+ */
+ syncIdToUnregisteredConnectorIds.headMap(lastSyncIdSeenByClient)
+ .clear();
+ }
}
diff --git a/server/src/com/vaadin/ui/DragAndDropWrapper.java b/server/src/com/vaadin/ui/DragAndDropWrapper.java
index 5d6825c868..2ab3e872c6 100644
--- a/server/src/com/vaadin/ui/DragAndDropWrapper.java
+++ b/server/src/com/vaadin/ui/DragAndDropWrapper.java
@@ -56,7 +56,7 @@ public class DragAndDropWrapper extends CustomComponent implements DropTarget,
for (int i = 0; i < fc; i++) {
Html5File file = new Html5File(
(String) rawVariables.get("fn" + i), // name
- (Integer) rawVariables.get("fs" + i), // size
+ ((Double) rawVariables.get("fs" + i)).longValue(), // size
(String) rawVariables.get("ft" + i)); // mime
String id = (String) rawVariables.get("fi" + i);
files[i] = file;
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/Notification.java b/server/src/com/vaadin/ui/Notification.java
index cf1d03ab5c..31fa265b02 100644
--- a/server/src/com/vaadin/ui/Notification.java
+++ b/server/src/com/vaadin/ui/Notification.java
@@ -21,6 +21,7 @@ import java.io.Serializable;
import com.vaadin.server.Page;
import com.vaadin.server.Resource;
import com.vaadin.shared.Position;
+import com.vaadin.shared.ui.ui.NotificationConfigurationBean.Role;
/**
* A notification message, used to display temporary messages to the user - for
@@ -63,7 +64,7 @@ import com.vaadin.shared.Position;
*/
public class Notification implements Serializable {
public enum Type {
- HUMANIZED_MESSAGE, WARNING_MESSAGE, ERROR_MESSAGE, TRAY_NOTIFICATION;
+ HUMANIZED_MESSAGE, WARNING_MESSAGE, ERROR_MESSAGE, TRAY_NOTIFICATION, ASSISTIVE_NOTIFICATION;
}
@Deprecated
@@ -190,21 +191,38 @@ public class Notification implements Serializable {
case WARNING_MESSAGE:
delayMsec = 1500;
styleName = "warning";
+ setNavigationConfiguration("Warning: ", "", Role.ALERT);
break;
case ERROR_MESSAGE:
delayMsec = -1;
styleName = "error";
+ setNavigationConfiguration("Error: ", " - close with ESC",
+ Role.ALERT);
break;
case TRAY_NOTIFICATION:
delayMsec = 3000;
position = Position.BOTTOM_RIGHT;
styleName = "tray";
-
+ setNavigationConfiguration("Info: ", "", Role.STATUS);
+ break;
+ case ASSISTIVE_NOTIFICATION:
+ delayMsec = 3000;
+ position = Position.ASSISTIVE;
+ styleName = "assistive";
+ setNavigationConfiguration("Note: ", "", Role.ALERT);
+ break;
case HUMANIZED_MESSAGE:
default:
+ styleName = "humanized";
+ setNavigationConfiguration("Info: ", "", Role.ALERT);
break;
}
+ }
+ private void setNavigationConfiguration(String prefix, String postfix,
+ Role ariaRole) {
+ UI.getCurrent().getNotificationConfiguration()
+ .setStyleConfiguration(styleName, prefix, postfix, ariaRole);
}
/**
@@ -322,6 +340,132 @@ public class Notification implements Serializable {
}
/**
+ * Sets the accessibility prefix for a notification type.
+ *
+ * This prefix is read to assistive device users before the content of the
+ * notification, but not visible on the page.
+ *
+ * @param type
+ * Type of the notification
+ * @param prefix
+ * String that is placed before the notification content
+ */
+ public void setAssistivePrefixForType(Type type, String prefix) {
+ UI.getCurrent().getNotificationConfiguration()
+ .setAssistivePrefixForStyle(getStyle(type), prefix);
+ }
+
+ /**
+ * Gets the accessibility prefix for a notification type.
+ *
+ * This prefix is read to assistive device users before the content of the
+ * notification, but not visible on the page.
+ *
+ * @param type
+ * Type of the notification
+ * @return The accessibility prefix for the provided notification type
+ */
+ public String getAssistivePrefixForType(Type type) {
+ return UI.getCurrent().getNotificationConfiguration()
+ .getAssistivePrefixForStyle(getStyle(type));
+ }
+
+ /**
+ * Sets the accessibility postfix for a notification type.
+ *
+ * This postfix is read to assistive device users after the content of the
+ * notification, but not visible on the page.
+ *
+ * @param type
+ * Type of the notification
+ * @param postfix
+ * String that is placed after the notification content
+ */
+ public void setAssistivePostfixForType(Type type, String postfix) {
+ UI.getCurrent().getNotificationConfiguration()
+ .setAssistivePostfixForStyle(getStyle(type), postfix);
+ }
+
+ /**
+ * Gets the accessibility postfix for a notification type.
+ *
+ * This postfix is read to assistive device users after the content of the
+ * notification, but not visible on the page.
+ *
+ * @param type
+ * Type of the notification
+ * @return The accessibility postfix for the provided notification type
+ */
+ public String getAssistivePostfixForType(Type type) {
+ return UI.getCurrent().getNotificationConfiguration()
+ .getAssistivePostfixForStyle(getStyle(type));
+ }
+
+ /**
+ * Sets the WAI-ARIA role for a notification type.
+ *
+ * This role defines how an assistive device handles a notification.
+ * Available roles are alert and status (@see <a
+ * href="http://www.w3.org/TR/2011/CR-wai-aria-20110118/roles">Roles
+ * Model</a>).
+ *
+ * The default role is alert.
+ *
+ * @param type
+ * Type of the notification
+ * @param role
+ * Role to set for the notification type
+ */
+ public void setAssistiveRoleForType(Type type, Role role) {
+ UI.getCurrent().getNotificationConfiguration()
+ .setAssistiveRoleForStyle(getStyle(type), role);
+ }
+
+ /**
+ * Gets the WAI-ARIA role for a notification type.
+ *
+ * This role defines how an assistive device handles a notification.
+ * Available roles are alert and status (@see <a
+ * href="http://www.w3.org/TR/2011/CR-wai-aria-20110118/roles">Roles
+ * Model</a>)
+ *
+ * The default role is alert.
+ *
+ * @param type
+ * Type of the notification
+ * @return Role to set for the notification type
+ */
+ public Role getAssistiveRoleForType(Type type) {
+ return UI.getCurrent().getNotificationConfiguration()
+ .getAssistiveRoleForStyle(getStyle(type));
+ }
+
+ private String getStyle(Type type) {
+ String style = "";
+
+ switch (type) {
+ case WARNING_MESSAGE:
+ style = "warning";
+ break;
+ case ERROR_MESSAGE:
+ style = "error";
+ break;
+ case TRAY_NOTIFICATION:
+ style = "tray";
+ break;
+ case ASSISTIVE_NOTIFICATION:
+ style = "assistive";
+ break;
+ case HUMANIZED_MESSAGE:
+ default:
+ style = "humanized";
+ break;
+ }
+
+ return style;
+ }
+
+ /**
* Sets whether html is allowed in the caption and description. If set to
* true, the texts are passed to the browser as html and the developer is
* responsible for ensuring no harmful html is used. If set to false, the
@@ -414,4 +558,4 @@ public class Notification implements Serializable {
public static void show(String caption, String description, Type type) {
new Notification(caption, description, type).show(Page.getCurrent());
}
-} \ No newline at end of file
+}
diff --git a/server/src/com/vaadin/ui/NotificationConfiguration.java b/server/src/com/vaadin/ui/NotificationConfiguration.java
new file mode 100644
index 0000000000..52d3e76d63
--- /dev/null
+++ b/server/src/com/vaadin/ui/NotificationConfiguration.java
@@ -0,0 +1,269 @@
+/*
+ * 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.ui;
+
+import java.io.Serializable;
+
+import com.vaadin.shared.ui.ui.NotificationConfigurationBean;
+import com.vaadin.shared.ui.ui.NotificationConfigurationBean.Role;
+import com.vaadin.shared.ui.ui.UIState.NotificationConfigurationState;
+
+/**
+ * Provides methods for configuring the notification.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public interface NotificationConfiguration extends Serializable {
+ public void setStyleConfiguration(String style, String prefix,
+ String postfix, Role ariaRole);
+
+ /**
+ * Returns the complete configuration object for the given notification
+ * style.
+ *
+ * @param style
+ * String of the notification style to return
+ * @return The notification configuration object
+ */
+ public NotificationConfigurationBean getStyleConfiguration(String style);
+
+ /**
+ * Sets the accessibility prefix for the given notification style.
+ *
+ * This prefix is read to assistive device users in front of the content of
+ * the notification, but not visible on the page.
+ *
+ * @param style
+ * String of the notification style
+ * @param prefix
+ * String that is placed before the notification content
+ */
+ public void setAssistivePrefixForStyle(String style, String prefix);
+
+ /**
+ * Returns the accessibility prefix for the given notification style.
+ *
+ * This prefix is read to assistive device users in front of the content of
+ * the notification, but not visible on the page.
+ *
+ * @param style
+ * String of the notification style
+ * @return The prefix of the provided notification style
+ */
+ public String getAssistivePrefixForStyle(String style);
+
+ /**
+ * Sets the accessibility postfix for the given notification style.
+ *
+ * This postfix is read to assistive device users after the content of the
+ * notification, but not visible on the page.
+ *
+ * @param style
+ * String of the notification style
+ * @param postfix
+ * String that is placed after the notification content
+ */
+ public void setAssistivePostfixForStyle(String style, String postfix);
+
+ /**
+ * Returns the accessibility postfix for the given notification style.
+ *
+ * This postfix is read to assistive device users after the content of the
+ * notification, but not visible on the page.
+ *
+ * @param style
+ * String of the notification style
+ * @return The postfix of the provided notification style
+ */
+ public String getAssistivePostfixForStyle(String style);
+
+ /**
+ * Sets the WAI-ARIA role for a notification style.
+ *
+ * This role defines how an assistive device handles a notification.
+ * Available roles are alert, alertdialog and status (@see <a
+ * href="http://www.w3.org/TR/2011/CR-wai-aria-20110118/roles">Roles
+ * Model</a>)
+ *
+ * The default role is alert.
+ *
+ * @param style
+ * String of the notification style
+ * @param role
+ * Role to set for the notification type
+ */
+ public void setAssistiveRoleForStyle(String style, Role role);
+
+ /**
+ * Returns the WAI-ARIA role for a notification style.
+ *
+ * This role defines how an assistive device handles a notification.
+ * Available roles are alert, alertdialog and status (@see <a
+ * href="http://www.w3.org/TR/2011/CR-wai-aria-20110118/roles">Roles
+ * Model</a> )
+ *
+ * The default role is alert.
+ *
+ * @param style
+ * String of the notification style
+ * @return The current Role for the notification type
+ */
+ public Role getAssistiveRoleForStyle(String style);
+}
+
+class NotificationConfigurationImpl implements NotificationConfiguration {
+
+ private UI ui;
+
+ public NotificationConfigurationImpl(UI ui) {
+ this.ui = ui;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.ui.NotificationConfiguration#setStyleConfiguration(java.lang
+ * .String, java.lang.String, java.lang.String,
+ * com.vaadin.ui.NotificationConfiguration.Role)
+ */
+ @Override
+ public void setStyleConfiguration(String style, String prefix,
+ String postfix, Role ariaRole) {
+ getState().setup.put(style, new NotificationConfigurationBean(prefix,
+ postfix, ariaRole));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.ui.NotificationConfiguration#getStyleConfiguration(java.lang
+ * .String)
+ */
+ @Override
+ public NotificationConfigurationBean getStyleConfiguration(String style) {
+ return getState(false).setup.get(style);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.ui.NotificationConfiguration#setStylePrefix(java.lang.String,
+ * java.lang.String)
+ */
+ @Override
+ public void setAssistivePrefixForStyle(String style, String prefix) {
+ getConfigurationBean(style).setAssistivePrefix(prefix);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.ui.NotificationConfiguration#getStylePrefix(java.lang.String)
+ */
+ @Override
+ public String getAssistivePrefixForStyle(String style) {
+ NotificationConfigurationBean styleSetup = getState().setup.get(style);
+ if (styleSetup != null) {
+ return styleSetup.getAssistivePrefix();
+ }
+
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.ui.NotificationConfiguration#setStylePostfix(com.vaadin.ui
+ * .Notification.Type, java.lang.String)
+ */
+ @Override
+ public void setAssistivePostfixForStyle(String style, String postfix) {
+ getConfigurationBean(style).setAssistivePostfix(postfix);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.ui.NotificationConfiguration#getStylePostfix(com.vaadin.ui
+ * .Notification.Type)
+ */
+ @Override
+ public String getAssistivePostfixForStyle(String style) {
+ NotificationConfigurationBean styleSetup = getState().setup.get(style);
+ if (styleSetup != null) {
+ return styleSetup.getAssistivePostfix();
+ }
+
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.NotificationConfiguration#setStyleRole(com.vaadin.ui.
+ * Notification.Type, com.vaadin.ui.NotificationConfiguration.Role)
+ */
+ @Override
+ public void setAssistiveRoleForStyle(String style, Role role) {
+ getConfigurationBean(style).setAssistiveRole(role);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.NotificationConfiguration#getStyleRole(com.vaadin.ui.
+ * Notification.Type)
+ */
+ @Override
+ public Role getAssistiveRoleForStyle(String style) {
+ NotificationConfigurationBean styleSetup = getState().setup.get(style);
+ if (styleSetup != null) {
+ return styleSetup.getAssistiveRole();
+ }
+
+ return null;
+ }
+
+ private NotificationConfigurationBean getConfigurationBean(String style) {
+ NotificationConfigurationBean styleSetup = getState().setup.get(style);
+ if (styleSetup == null) {
+ styleSetup = new NotificationConfigurationBean();
+ getState().setup.put(style, styleSetup);
+ }
+
+ return styleSetup;
+ }
+
+ private NotificationConfigurationState getState() {
+ return ui.getState().notificationConfiguration;
+ }
+
+ private NotificationConfigurationState getState(boolean markAsDirty) {
+ return ui.getState(markAsDirty).notificationConfiguration;
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/TabSheet.java b/server/src/com/vaadin/ui/TabSheet.java
index 36022adb74..a1f9e9dd26 100644
--- a/server/src/com/vaadin/ui/TabSheet.java
+++ b/server/src/com/vaadin/ui/TabSheet.java
@@ -268,7 +268,34 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
* @return the created {@link Tab}
*/
public Tab addTab(Component c, String caption, Resource icon) {
- return addTab(c, caption, icon, components.size());
+ return addTab(c, caption, icon, "", components.size());
+ }
+
+ /**
+ * Adds a new tab into TabSheet.
+ *
+ * The first tab added to a tab sheet is automatically selected and a tab
+ * selection event is fired.
+ *
+ * If the component is already present in the tab sheet, changes its caption
+ * and icon and icon alternate text and returns the corresponding (old) tab,
+ * preserving other tab metadata.
+ *
+ * @param c
+ * the component to be added onto tab - should not be null.
+ * @param caption
+ * the caption to be set for the component and used rendered in
+ * tab bar
+ * @param icon
+ * the icon to be set for the component and used rendered in tab
+ * bar
+ * @param iconAltText
+ * the alternate text for the icon
+ * @return the created {@link Tab}
+ */
+ public Tab addTab(Component c, String caption, Resource icon,
+ String iconAltText) {
+ return addTab(c, caption, icon, iconAltText, components.size());
}
/**
@@ -294,12 +321,41 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
* @return the created {@link Tab}
*/
public Tab addTab(Component c, String caption, Resource icon, int position) {
+ return addTab(c, caption, icon, "", position);
+ }
+
+ /**
+ * Adds a new tab into TabSheet.
+ *
+ * The first tab added to a tab sheet is automatically selected and a tab
+ * selection event is fired.
+ *
+ * If the component is already present in the tab sheet, changes its caption
+ * and icon and icon alternate text and returns the corresponding (old) tab,
+ * preserving other tab metadata like the position.
+ *
+ * @param c
+ * the component to be added onto tab - should not be null.
+ * @param caption
+ * the caption to be set for the component and used rendered in
+ * tab bar
+ * @param icon
+ * the icon to be set for the component and used rendered in tab
+ * bar
+ * @param iconAltText
+ * the alternate text for the icon
+ * @param position
+ * the position at where the the tab should be added.
+ * @return the created {@link Tab}
+ */
+ public Tab addTab(Component c, String caption, Resource icon,
+ String iconAltText, int position) {
if (c == null) {
return null;
} else if (tabs.containsKey(c)) {
Tab tab = tabs.get(c);
tab.setCaption(caption);
- tab.setIcon(icon);
+ tab.setIcon(icon, iconAltText);
return tab;
} else {
components.add(position, c);
@@ -371,13 +427,15 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
final Component c = i.next();
String caption = null;
Resource icon = null;
+ String iconAltText = "";
if (TabSheet.class.isAssignableFrom(source.getClass())) {
Tab tab = ((TabSheet) source).getTab(c);
caption = tab.getCaption();
icon = tab.getIcon();
+ iconAltText = tab.getIconAltText();
}
source.removeComponent(c);
- addTab(c, caption, icon);
+ addTab(c, caption, icon, iconAltText);
}
}
@@ -429,9 +487,12 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
if (icon != null) {
target.addAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ICON,
icon);
+ target.addAttribute(
+ TabsheetBaseConstants.ATTRIBUTE_TAB_ICON_ALT,
+ tab.getIconAltText());
}
final String caption = tab.getCaption();
- if (caption != null && caption.length() > 0) {
+ if (caption != null && !caption.isEmpty()) {
target.addAttribute(
TabsheetBaseConstants.ATTRIBUTE_TAB_CAPTION, caption);
}
@@ -449,10 +510,15 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
}
final String styleName = tab.getStyleName();
- if (styleName != null && styleName.length() != 0) {
+ if (styleName != null && !styleName.isEmpty()) {
target.addAttribute(TabsheetConstants.TAB_STYLE_NAME, styleName);
}
+ final String id = tab.getId();
+ if (id != null && !id.isEmpty()) {
+ target.addAttribute("id", id);
+ }
+
target.addAttribute("key", keyMapper.key(component));
if (component.equals(selected)) {
target.addAttribute("selected", true);
@@ -549,6 +615,11 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
// connector
if (selected != null) {
selected.markAsDirtyRecursive();
+
+ Tab tab = getTab(c);
+ if (tab != null && tab.getDefaultFocusComponent() != null) {
+ tab.getDefaultFocusComponent().focus();
+ }
}
}
@@ -889,6 +960,23 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
public void setClosable(boolean closable);
/**
+ * Set the component that should automatically focused when the tab is
+ * selected.
+ *
+ * @param component
+ * the component to focus
+ */
+ public void setDefaultFocusComponent(Focusable component);
+
+ /**
+ * Get the component that should be automatically focused when the tab
+ * is selected.
+ *
+ * @return the focusable component
+ */
+ public Focusable getDefaultFocusComponent();
+
+ /**
* Returns the enabled status for the tab. A disabled tab is shown as
* such in the tab bar and cannot be selected.
*
@@ -932,6 +1020,27 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
public void setIcon(Resource icon);
/**
+ * Sets the icon and alt text for the tab.
+ *
+ * @param icon
+ * the icon to set
+ */
+ public void setIcon(Resource icon, String iconAltText);
+
+ /**
+ * Gets the icon alt text for the tab.
+ */
+ public String getIconAltText();
+
+ /**
+ * Sets the icon alt text for the tab.
+ *
+ * @param iconAltText
+ * the icon to set
+ */
+ public void setIconAltText(String iconAltText);
+
+ /**
* Gets the description for the tab. The description can be used to
* briefly describe the state of the tab to the user, and is typically
* shown as a tooltip when hovering over the tab.
@@ -1015,6 +1124,23 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
* @see #setStyleName(String)
*/
public String getStyleName();
+
+ /**
+ * Adds an unique id for component that is used in the client-side for
+ * testing purposes. Keeping identifiers unique is the responsibility of
+ * the programmer.
+ *
+ * @param id
+ * An alphanumeric id
+ */
+ public void setId(String id);
+
+ /**
+ * Gets currently set debug identifier
+ *
+ * @return current id, null if not set
+ */
+ public String getId();
}
/**
@@ -1030,6 +1156,9 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
private String description = null;
private ErrorMessage componentError = null;
private String styleName;
+ private String id;
+ private String iconAltText = "";
+ private Focusable defaultFocus;
public TabSheetTabImpl(String caption, Resource icon) {
if (caption == null) {
@@ -1061,11 +1190,38 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
@Override
public void setIcon(Resource icon) {
+ setIcon(icon, "");
+ }
+
+ @Override
+ public void setIcon(Resource icon, String iconAltText) {
this.icon = icon;
+ this.iconAltText = iconAltText;
markAsDirty();
}
@Override
+ public String getIconAltText() {
+ return iconAltText;
+ }
+
+ @Override
+ public void setIconAltText(String iconAltText) {
+ this.iconAltText = iconAltText;
+ markAsDirty();
+ }
+
+ @Override
+ public void setDefaultFocusComponent(Focusable defaultFocus) {
+ this.defaultFocus = defaultFocus;
+ }
+
+ @Override
+ public Focusable getDefaultFocusComponent() {
+ return defaultFocus;
+ }
+
+ @Override
public boolean isEnabled() {
return enabled;
}
@@ -1150,6 +1306,18 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
public String getStyleName() {
return styleName;
}
+
+ @Override
+ public void setId(String id) {
+ this.id = id;
+ markAsDirty();
+
+ }
+
+ @Override
+ public String getId() {
+ return id;
+ }
}
/**
@@ -1309,7 +1477,7 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
*/
private static void copyTabMetadata(Tab from, Tab to) {
to.setCaption(from.getCaption());
- to.setIcon(from.getIcon());
+ to.setIcon(from.getIcon(), from.getIconAltText());
to.setDescription(from.getDescription());
to.setVisible(from.isVisible());
to.setEnabled(from.isEnabled());
diff --git a/server/src/com/vaadin/ui/UI.java b/server/src/com/vaadin/ui/UI.java
index 746fa194ac..a292e6b829 100644
--- a/server/src/com/vaadin/ui/UI.java
+++ b/server/src/com/vaadin/ui/UI.java
@@ -32,6 +32,9 @@ 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;
@@ -77,7 +80,7 @@ import com.vaadin.util.CurrentInstance;
* When a new UI instance is needed, typically because the user opens a URL in a
* browser window which points to e.g. {@link VaadinServlet}, all
* {@link UIProvider}s registered to the current {@link VaadinSession} are
- * queried for the UI class that should be used. The selection is by defaylt
+ * queried for the UI class that should be used. The selection is by default
* based on the <code>UI</code> init parameter from web.xml.
* </p>
* <p>
@@ -95,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
@@ -167,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() {
@@ -222,6 +223,9 @@ public abstract class UI extends AbstractSingleComponentContainer implements
private PushConfiguration pushConfiguration = new PushConfigurationImpl(
this);
+ private NotificationConfiguration notificationConfiguration = new NotificationConfigurationImpl(
+ this);
+
/**
* Creates a new empty UI without a caption. The content of the UI must be
* set by calling {@link #setContent(Component)} before using the UI.
@@ -551,6 +555,8 @@ public abstract class UI extends AbstractSingleComponentContainer implements
private LocaleService localeService = new LocaleService(this,
getState(false).localeServiceState);
+ private String embedId;
+
/**
* This method is used by Component.Focusable objects to request focus to
* themselves. Focus renders must be handled at window level (instead of
@@ -598,12 +604,19 @@ public abstract class UI extends AbstractSingleComponentContainer implements
* the initialization request
* @param uiId
* the id of the new ui
+ * @param embedId
+ * the embed id of this UI, or <code>null</code> if no id is
+ * known
+ *
+ * @see #getUIId()
+ * @see #getEmbedId()
*/
- public void doInit(VaadinRequest request, int uiId) {
+ public void doInit(VaadinRequest request, int uiId, String embedId) {
if (this.uiId != -1) {
throw new IllegalStateException("UI id has already been defined");
}
this.uiId = uiId;
+ this.embedId = embedId;
// Actual theme - used for finding CustomLayout templates
theme = request.getParameter("theme");
@@ -1334,6 +1347,15 @@ public abstract class UI extends AbstractSingleComponentContainer implements
}
/**
+ * Retrieves the object used for configuring notifications.
+ *
+ * @return The instance used for notification configuration
+ */
+ public NotificationConfiguration getNotificationConfiguration() {
+ return notificationConfiguration;
+ }
+
+ /**
* Retrieves the object used for configuring the loading indicator.
*
* @return The instance used for configuring the loading indicator
@@ -1469,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.
*
@@ -1518,4 +1551,18 @@ public abstract class UI extends AbstractSingleComponentContainer implements
private static Logger getLogger() {
return Logger.getLogger(UI.class.getName());
}
+
+ /**
+ * Gets a string the uniquely distinguishes this UI instance based on where
+ * it is embedded. The embed identifier is based on the
+ * <code>window.name</code> DOM attribute of the browser window where the UI
+ * is displayed and the id of the div element where the UI is embedded.
+ *
+ * @since 7.2
+ * @return the embed id for this UI, or <code>null</code> if no id known
+ */
+ public String getEmbedId() {
+ return embedId;
+ }
+
}
diff --git a/server/src/com/vaadin/ui/Window.java b/server/src/com/vaadin/ui/Window.java
index c173b401b9..d3afdaacf1 100644
--- a/server/src/com/vaadin/ui/Window.java
+++ b/server/src/com/vaadin/ui/Window.java
@@ -18,6 +18,9 @@ package com.vaadin.ui;
import java.io.Serializable;
import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
import java.util.Map;
import com.vaadin.event.FieldEvents.BlurEvent;
@@ -33,10 +36,12 @@ import com.vaadin.event.ShortcutAction.ModifierKey;
import com.vaadin.event.ShortcutListener;
import com.vaadin.server.PaintException;
import com.vaadin.server.PaintTarget;
+import com.vaadin.shared.Connector;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.ui.window.WindowMode;
import com.vaadin.shared.ui.window.WindowServerRpc;
import com.vaadin.shared.ui.window.WindowState;
+import com.vaadin.shared.ui.window.WindowState.WindowRole;
import com.vaadin.util.ReflectTools;
/**
@@ -238,8 +243,6 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier,
// Don't do anything if not attached to a UI
if (uI != null) {
- // focus is restored to the parent window
- uI.focus();
// window is removed from the UI
uI.removeWindow(this);
}
@@ -644,7 +647,10 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier,
/**
* Sets window modality. When a modal window is open, components outside
- * that window it cannot be accessed.
+ * that window cannot be accessed.
+ * <p>
+ * Keyboard navigation is restricted by blocking the tab key at the top and
+ * bottom of the window by activating the tab stop function internally.
*
* @param modal
* true if modality is to be turned on
@@ -1005,4 +1011,194 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier,
protected WindowState getState(boolean markAsDirty) {
return (WindowState) super.getState(markAsDirty);
}
+
+ /**
+ * Allows to specify which components contain the description for the
+ * window. Text contained in these components will be read by assistive
+ * devices when it is opened.
+ *
+ * @param connectors
+ * with the components to use as description
+ */
+ public void setAssistiveDescription(Connector... connectors) {
+ if (connectors == null) {
+ throw new IllegalArgumentException(
+ "Parameter connectors must be non-null");
+ } else {
+ getState().contentDescription = connectors;
+ }
+ }
+
+ /**
+ * Gets the components that are used as assistive description. Text
+ * contained in these components will be read by assistive devices when the
+ * window is opened.
+ *
+ * @return list of previously set components
+ */
+ public List<Connector> getAssistiveDescription() {
+ return Collections.unmodifiableList(Arrays
+ .asList(getState().contentDescription));
+ }
+
+ /**
+ * Sets the accessibility prefix for the window caption.
+ *
+ * This prefix is read to assistive device users before the window caption,
+ * but not visible on the page.
+ *
+ * @param prefix
+ * String that is placed before the window caption
+ */
+ public void setAssistivePrefix(String prefix) {
+ getState().assistivePrefix = prefix;
+ }
+
+ /**
+ * Gets the accessibility prefix for the window caption.
+ *
+ * This prefix is read to assistive device users before the window caption,
+ * but not visible on the page.
+ *
+ * @return The accessibility prefix
+ */
+ public String getAssistivePrefix() {
+ return getState().assistivePrefix;
+ }
+
+ /**
+ * Sets the accessibility postfix for the window caption.
+ *
+ * This postfix is read to assistive device users after the window caption,
+ * but not visible on the page.
+ *
+ * @param prefix
+ * String that is placed after the window caption
+ */
+ public void setAssistivePostfix(String assistivePostfix) {
+ getState().assistivePostfix = assistivePostfix;
+ }
+
+ /**
+ * Gets the accessibility postfix for the window caption.
+ *
+ * This postfix is read to assistive device users after the window caption,
+ * but not visible on the page.
+ *
+ * @return The accessibility postfix
+ */
+ public String getAssistivePostfix() {
+ return getState().assistivePostfix;
+ }
+
+ /**
+ * Sets the WAI-ARIA role the window.
+ *
+ * This role defines how an assistive device handles a window. Available
+ * roles are alertdialog and dialog (@see <a
+ * href="http://www.w3.org/TR/2011/CR-wai-aria-20110118/roles">Roles
+ * Model</a>).
+ *
+ * The default role is dialog.
+ *
+ * @param role
+ * WAI-ARIA role to set for the window
+ */
+ public void setAssistiveRole(WindowRole role) {
+ getState().role = role;
+ }
+
+ /**
+ * Gets the WAI-ARIA role the window.
+ *
+ * This role defines how an assistive device handles a window. Available
+ * roles are alertdialog and dialog (@see <a
+ * href="http://www.w3.org/TR/2011/CR-wai-aria-20110118/roles">Roles
+ * Model</a>).
+ *
+ * @return WAI-ARIA role set for the window
+ */
+ public WindowRole getAssistiveRole() {
+ return getState().role;
+ }
+
+ /**
+ * Set if it should be prevented to set the focus to a component outside a
+ * non-modal window with the tab key.
+ * <p>
+ * This is meant to help users of assistive devices to not leaving the
+ * window unintentionally.
+ * <p>
+ * For modal windows, this function is activated automatically, while
+ * preserving the stored value of tabStop.
+ *
+ * @param tabStop
+ * true to keep the focus inside the window when reaching the top
+ * or bottom, false (default) to allow leaving the window
+ */
+ public void setTabStopEnabled(boolean tabStop) {
+ getState().assistiveTabStop = tabStop;
+ }
+
+ /**
+ * Get if it is prevented to leave a window with the tab key.
+ *
+ * @return true when the focus is limited to inside the window, false when
+ * focus can leave the window
+ */
+ public boolean isTabStopEnabled() {
+ return getState().assistiveTabStop;
+ }
+
+ /**
+ * Sets the message that is provided to users of assistive devices when the
+ * user reaches the top of the window when leaving a window with the tab key
+ * is prevented.
+ * <p>
+ * This message is not visible on the screen.
+ *
+ * @param topMessage
+ * String provided when the user navigates with Shift-Tab keys to
+ * the top of the window
+ */
+ public void setTabStopTopAssistiveText(String topMessage) {
+ getState().assistiveTabStopTopText = topMessage;
+ }
+
+ /**
+ * Sets the message that is provided to users of assistive devices when the
+ * user reaches the bottom of the window when leaving a window with the tab
+ * key is prevented.
+ * <p>
+ * This message is not visible on the screen.
+ *
+ * @param bottomMessage
+ * String provided when the user navigates with the Tab key to
+ * the bottom of the window
+ */
+ public void setTabStopBottomAssistiveText(String bottomMessage) {
+ getState().assistiveTabStopBottomText = bottomMessage;
+ }
+
+ /**
+ * Gets the message that is provided to users of assistive devices when the
+ * user reaches the top of the window when leaving a window with the tab key
+ * is prevented.
+ *
+ * @return the top message
+ */
+ public String getTabStopTopAssistiveText() {
+ return getState().assistiveTabStopTopText;
+ }
+
+ /**
+ * Gets the message that is provided to users of assistive devices when the
+ * user reaches the bottom of the window when leaving a window with the tab
+ * key is prevented.
+ *
+ * @return the bottom message
+ */
+ public String getTabStopBottomAssistiveText() {
+ return getState().assistiveTabStopBottomText;
+ }
}
diff --git a/server/tests/src/com/vaadin/data/util/BeanContainerTest.java b/server/tests/src/com/vaadin/data/util/BeanContainerTest.java
index 9037e303a8..2dcbb4aed8 100644
--- a/server/tests/src/com/vaadin/data/util/BeanContainerTest.java
+++ b/server/tests/src/com/vaadin/data/util/BeanContainerTest.java
@@ -457,4 +457,28 @@ public class BeanContainerTest extends AbstractBeanContainerTest {
.getValue());
}
+ public void testNestedContainerPropertyWithNullBean() {
+ BeanContainer<String, NestedMethodPropertyTest.Person> container = new BeanContainer<String, NestedMethodPropertyTest.Person>(
+ NestedMethodPropertyTest.Person.class);
+ container.setBeanIdProperty("name");
+
+ container.addBean(new NestedMethodPropertyTest.Person("John", null));
+ assertTrue(container
+ .addNestedContainerProperty("address.postalCodeObject"));
+ assertTrue(container.addNestedContainerProperty("address.street", true));
+ // the nested properties added with allowNullBean setting should return
+ // null
+ assertNull(container.getContainerProperty("John", "address.street")
+ .getValue());
+ // nested properties added without allowNullBean setting should throw
+ // exception
+ try {
+ container.getContainerProperty("John", "address.postalCodeObject")
+ .getValue();
+ fail();
+ } catch (Exception e) {
+ // should throw exception
+ }
+ }
+
}
diff --git a/server/tests/src/com/vaadin/data/util/BeanItemContainerTest.java b/server/tests/src/com/vaadin/data/util/BeanItemContainerTest.java
index 6b88eb336d..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.
@@ -714,4 +721,205 @@ public class BeanItemContainerTest extends AbstractBeanContainerTest {
.getValue());
}
+ public void testNestedContainerPropertyWithNullBean() {
+ BeanItemContainer<NestedMethodPropertyTest.Person> container = new BeanItemContainer<NestedMethodPropertyTest.Person>(
+ NestedMethodPropertyTest.Person.class);
+ NestedMethodPropertyTest.Person john = new NestedMethodPropertyTest.Person(
+ "John", null);
+ assertNotNull(container.addBean(john));
+ assertTrue(container
+ .addNestedContainerProperty("address.postalCodeObject"));
+ assertTrue(container.addNestedContainerProperty("address.street", true));
+ // the nested properties added with allowNullBean setting should return
+ // null
+ assertNull(container.getContainerProperty(john, "address.street")
+ .getValue());
+ // nested properties added without allowNullBean setting should throw
+ // exception
+ try {
+ container.getContainerProperty(john, "address.postalCodeObject")
+ .getValue();
+ fail();
+ } catch (Exception e) {
+ // 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/NestedMethodPropertyTest.java b/server/tests/src/com/vaadin/data/util/NestedMethodPropertyTest.java
index 640ede8743..d517322010 100644
--- a/server/tests/src/com/vaadin/data/util/NestedMethodPropertyTest.java
+++ b/server/tests/src/com/vaadin/data/util/NestedMethodPropertyTest.java
@@ -273,6 +273,23 @@ public class NestedMethodPropertyTest extends TestCase {
Assert.assertEquals("Joonas", managerNameProperty.getValue());
}
+ public void testNullNestedPropertyWithAllowNullBeans() {
+ NestedMethodProperty<String> managerNameProperty = new NestedMethodProperty<String>(
+ vaadin, "manager.name", true);
+ NestedMethodProperty<String> streetProperty = new NestedMethodProperty<String>(
+ vaadin, "manager.address.street", true);
+
+ joonas.setAddress(null);
+ // should return null
+ Assert.assertNull(streetProperty.getValue());
+
+ vaadin.setManager(null);
+ Assert.assertNull(managerNameProperty.getValue());
+ vaadin.setManager(joonas);
+ Assert.assertEquals("Joonas", managerNameProperty.getValue());
+ Assert.assertNull(streetProperty.getValue());
+ }
+
public void testMultiLevelNestedPropertySetValue() {
NestedMethodProperty<String> managerNameProperty = new NestedMethodProperty<String>(
vaadin, "manager.name");
@@ -314,6 +331,20 @@ public class NestedMethodPropertyTest extends TestCase {
Assert.assertEquals("Ruukinkatu 2-4", property2.getValue());
}
+ public void testSerializationWithNullBeansAllowed() throws IOException,
+ ClassNotFoundException {
+ vaadin.setManager(null);
+ NestedMethodProperty<String> streetProperty = new NestedMethodProperty<String>(
+ vaadin, "manager.address.street", true);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ new ObjectOutputStream(baos).writeObject(streetProperty);
+ @SuppressWarnings("unchecked")
+ NestedMethodProperty<String> property2 = (NestedMethodProperty<String>) new ObjectInputStream(
+ new ByteArrayInputStream(baos.toByteArray())).readObject();
+
+ Assert.assertNull(property2.getValue());
+ }
+
public void testIsReadOnly() {
NestedMethodProperty<String> streetProperty = new NestedMethodProperty<String>(
vaadin, "manager.address.street");
diff --git a/server/tests/src/com/vaadin/data/util/PropertyDescriptorTest.java b/server/tests/src/com/vaadin/data/util/PropertyDescriptorTest.java
index 14e70d76d4..0ae76430f6 100644
--- a/server/tests/src/com/vaadin/data/util/PropertyDescriptorTest.java
+++ b/server/tests/src/com/vaadin/data/util/PropertyDescriptorTest.java
@@ -52,4 +52,20 @@ public class PropertyDescriptorTest extends TestCase {
Property<?> property = pd2.createProperty(new Person("John", null));
Assert.assertEquals("John", property.getValue());
}
+
+ public void testNestedPropertyDescriptorWithNullBeansAllowedSerialization()
+ throws Exception {
+ NestedPropertyDescriptor<Person> pd = new NestedPropertyDescriptor<Person>(
+ "address.street", Person.class, true);
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ new ObjectOutputStream(baos).writeObject(pd);
+ @SuppressWarnings("unchecked")
+ VaadinPropertyDescriptor<Person> pd2 = (VaadinPropertyDescriptor<Person>) new ObjectInputStream(
+ new ByteArrayInputStream(baos.toByteArray())).readObject();
+
+ Property<?> property = pd2.createProperty(new Person("John", null));
+ Assert.assertNull(property.getValue());
+ }
+
}
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/server/VaadinSessionTest.java b/server/tests/src/com/vaadin/server/VaadinSessionTest.java
index 68f198410c..51ae2a2d13 100644
--- a/server/tests/src/com/vaadin/server/VaadinSessionTest.java
+++ b/server/tests/src/com/vaadin/server/VaadinSessionTest.java
@@ -100,7 +100,7 @@ public class VaadinSessionTest {
}
};
- ui.doInit(vaadinRequest, session.getNextUIid());
+ ui.doInit(vaadinRequest, session.getNextUIid(), null);
ui.setSession(session);
session.addUI(ui);
diff --git a/server/tests/src/com/vaadin/tests/data/bean/BeanToValidate.java b/server/tests/src/com/vaadin/tests/data/bean/BeanToValidate.java
index 416563baba..034609764f 100644
--- a/server/tests/src/com/vaadin/tests/data/bean/BeanToValidate.java
+++ b/server/tests/src/com/vaadin/tests/data/bean/BeanToValidate.java
@@ -4,6 +4,7 @@ import javax.validation.constraints.Digits;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
public class BeanToValidate {
@@ -21,6 +22,10 @@ public class BeanToValidate {
@Digits(integer = 3, fraction = 2)
private String decimals;
+ @Pattern(regexp = "V*", message = "Must start with letter V")
+ @Size(min = 3, max = 6, message = "Must contain 3 - 6 letters")
+ private String nickname;
+
public String getFirstname() {
return firstname;
}
@@ -53,4 +58,12 @@ public class BeanToValidate {
this.decimals = decimals;
}
+ public String getNickname() {
+ return nickname;
+ }
+
+ public void setNickname(String nickname) {
+ this.nickname = nickname;
+ }
+
}
diff --git a/server/tests/src/com/vaadin/tests/data/bean/PersonWithBeanValidationAnnotations.java b/server/tests/src/com/vaadin/tests/data/bean/PersonWithBeanValidationAnnotations.java
index 93b2273263..575730d946 100644
--- a/server/tests/src/com/vaadin/tests/data/bean/PersonWithBeanValidationAnnotations.java
+++ b/server/tests/src/com/vaadin/tests/data/bean/PersonWithBeanValidationAnnotations.java
@@ -8,12 +8,15 @@ import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
+import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
public class PersonWithBeanValidationAnnotations {
@NotNull
@Size(min = 5, max = 20)
+ @Pattern(regexp = "A.*")
private String firstName;
+
@NotNull
private String lastName;
diff --git a/server/tests/src/com/vaadin/tests/data/converter/TestStringToBigDecimalConverter.java b/server/tests/src/com/vaadin/tests/data/converter/TestStringToBigDecimalConverter.java
new file mode 100644
index 0000000000..5db33691b6
--- /dev/null
+++ b/server/tests/src/com/vaadin/tests/data/converter/TestStringToBigDecimalConverter.java
@@ -0,0 +1,53 @@
+/*
+ * 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.data.converter;
+
+import java.math.BigDecimal;
+import java.util.Locale;
+
+import junit.framework.TestCase;
+
+import com.vaadin.data.util.converter.StringToBigDecimalConverter;
+
+public class TestStringToBigDecimalConverter extends TestCase {
+
+ StringToBigDecimalConverter converter = new StringToBigDecimalConverter();
+
+ public void testNullConversion() {
+ assertEquals(null,
+ converter.convertToModel(null, BigDecimal.class, null));
+ }
+
+ public void testEmptyStringConversion() {
+ assertEquals(null, converter.convertToModel("", BigDecimal.class, null));
+ }
+
+ public void testValueParsing() {
+ BigDecimal converted = converter.convertToModel("10", BigDecimal.class,
+ null);
+ BigDecimal expected = new BigDecimal(10);
+ assertEquals(expected, converted);
+ }
+
+ public void testValueFormatting() {
+ BigDecimal bd = new BigDecimal(12.5);
+ String expected = "12,5";
+
+ String converted = converter.convertToPresentation(bd, String.class,
+ Locale.GERMAN);
+ assertEquals(expected, converted);
+ }
+}
diff --git a/server/tests/src/com/vaadin/tests/data/converter/TestStringToNumberConverter.java b/server/tests/src/com/vaadin/tests/data/converter/TestStringToNumberConverter.java
deleted file mode 100644
index 66fc4f6532..0000000000
--- a/server/tests/src/com/vaadin/tests/data/converter/TestStringToNumberConverter.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.vaadin.tests.data.converter;
-
-import junit.framework.TestCase;
-
-import com.vaadin.data.util.converter.StringToNumberConverter;
-
-public class TestStringToNumberConverter extends TestCase {
-
- StringToNumberConverter converter = new StringToNumberConverter();
-
- public void testNullConversion() {
- assertEquals(null, converter.convertToModel(null, Number.class, null));
- }
-
- public void testEmptyStringConversion() {
- assertEquals(null, converter.convertToModel("", Number.class, null));
- }
-
- public void testValueConversion() {
- assertEquals(Long.valueOf(10),
- converter.convertToModel("10", Number.class, null));
- assertEquals(10.5, converter.convertToModel("10.5", Number.class, null));
- }
-}
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 a5e825bddb..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
@@ -205,14 +205,15 @@ public class AbsFieldValueConversions extends TestCase {
}
+ // Now specific to Integer because StringToNumberConverter has been removed
public static class NumberBean {
- private Number number;
+ private Integer number;
- public Number getNumber() {
+ public Integer getNumber() {
return number;
}
- public void setNumber(Number number) {
+ public void setNumber(Integer number) {
this.number = number;
}
@@ -239,7 +240,7 @@ public class AbsFieldValueConversions extends TestCase {
tf.setPropertyDataSource(new MethodProperty<Number>(nb, "number"));
Converter c2 = tf.getConverter();
assertTrue(
- "StringToNumber converter is ok for integer types and should stay even though property is changed",
+ "StringToInteger converter is ok for integer types and should stay even though property is changed",
c1 == c2);
assertEquals(490, tf.getPropertyDataSource().getValue());
assertEquals("490", tf.getValue());
diff --git a/server/tests/src/com/vaadin/tests/server/validation/TestBeanValidation.java b/server/tests/src/com/vaadin/tests/server/validation/TestBeanValidation.java
index e1d08a989b..1d1a3c297e 100644
--- a/server/tests/src/com/vaadin/tests/server/validation/TestBeanValidation.java
+++ b/server/tests/src/com/vaadin/tests/server/validation/TestBeanValidation.java
@@ -1,7 +1,6 @@
package com.vaadin.tests.server.validation;
-import junit.framework.Assert;
-
+import org.junit.Assert;
import org.junit.Test;
import com.vaadin.data.Validator.InvalidValueException;
@@ -59,6 +58,32 @@ public class TestBeanValidation {
}
@Test
+ public void testBeanValidationException_OneValidationError() {
+ InvalidValueException[] causes = null;
+ BeanValidator validator = new BeanValidator(BeanToValidate.class,
+ "lastname");
+ try {
+ validator.validate(null);
+ } catch (InvalidValueException e) {
+ causes = e.getCauses();
+ }
+
+ Assert.assertEquals(1, causes.length);
+ }
+
+ @Test
+ public void testBeanValidationsException_TwoValidationErrors() {
+ InvalidValueException[] causes = null;
+ BeanValidator validator = new BeanValidator(BeanToValidate.class,
+ "nickname");
+ try {
+ validator.validate("A");
+ } catch (InvalidValueException e) {
+ causes = e.getCauses();
+ }
+
+ Assert.assertEquals(2, causes.length);
+ }
public void testBeanValidationNotAddedTwice() {
// See ticket #11045
BeanFieldGroup<BeanToValidate> fieldGroup = new BeanFieldGroup<BeanToValidate>(