summaryrefslogtreecommitdiffstats
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/fieldgroup/FieldGroup.java6
-rw-r--r--server/src/com/vaadin/data/util/AbstractBeanContainer.java20
-rw-r--r--server/src/com/vaadin/data/util/AbstractInMemoryContainer.java151
-rw-r--r--server/src/com/vaadin/data/util/IndexedContainer.java7
-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/Constants.java2
-rw-r--r--server/src/com/vaadin/server/DefaultErrorHandler.java41
-rw-r--r--server/src/com/vaadin/server/FontAwesome.java447
-rw-r--r--server/src/com/vaadin/server/FontIcon.java67
-rw-r--r--server/src/com/vaadin/server/JsonCodec.java19
-rw-r--r--server/src/com/vaadin/server/LegacyCommunicationManager.java2
-rw-r--r--server/src/com/vaadin/server/Page.java21
-rw-r--r--server/src/com/vaadin/server/ResourceReference.java7
-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.java7
-rw-r--r--server/src/com/vaadin/server/VaadinService.java77
-rw-r--r--server/src/com/vaadin/server/VaadinServlet.java86
-rw-r--r--server/src/com/vaadin/server/VaadinSession.java104
-rw-r--r--server/src/com/vaadin/server/communication/AtmospherePushConnection.java146
-rw-r--r--server/src/com/vaadin/server/communication/DateSerializer.java42
-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/JSONSerializer.java72
-rw-r--r--server/src/com/vaadin/server/communication/PushConnection.java14
-rw-r--r--server/src/com/vaadin/server/communication/PushHandler.java52
-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/AbstractComponent.java30
-rw-r--r--server/src/com/vaadin/ui/AbstractField.java4
-rw-r--r--server/src/com/vaadin/ui/AbstractSelect.java31
-rw-r--r--server/src/com/vaadin/ui/Button.java25
-rw-r--r--server/src/com/vaadin/ui/ComboBox.java2
-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/Label.java23
-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/PushConfiguration.java42
-rw-r--r--server/src/com/vaadin/ui/TabSheet.java180
-rw-r--r--server/src/com/vaadin/ui/Table.java1
-rw-r--r--server/src/com/vaadin/ui/UI.java144
-rw-r--r--server/src/com/vaadin/ui/Upload.java92
-rw-r--r--server/src/com/vaadin/ui/Window.java202
-rw-r--r--server/tests/src/com/vaadin/data/util/BeanContainerTest.java2
-rw-r--r--server/tests/src/com/vaadin/data/util/BeanItemContainerTest.java186
-rw-r--r--server/tests/src/com/vaadin/data/util/TestIndexedContainer.java113
-rw-r--r--server/tests/src/com/vaadin/data/util/sqlcontainer/SQLContainerTest.java5
-rw-r--r--server/tests/src/com/vaadin/data/util/sqlcontainer/TicketTests.java1
-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/TestClassesSerializable.java3
-rw-r--r--server/tests/src/com/vaadin/tests/server/component/abstractfield/AbsFieldValueConversions.java9
-rw-r--r--server/tests/src/com/vaadin/tests/server/component/abstractselect/TestVarargsItemAddition.java26
-rw-r--r--server/tests/src/com/vaadin/tests/server/component/label/LabelListeners.java79
-rw-r--r--server/tests/src/com/vaadin/tests/server/validation/TestBeanValidation.java29
73 files changed, 3507 insertions, 661 deletions
diff --git a/server/src/com/vaadin/annotations/Widgetset.java b/server/src/com/vaadin/annotations/Widgetset.java
index 4cc81b4bd3..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 widget set 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 ef507c5f31..1e053d1091 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/fieldgroup/FieldGroup.java b/server/src/com/vaadin/data/fieldgroup/FieldGroup.java
index 23f2da53ce..7edcc9719c 100644
--- a/server/src/com/vaadin/data/fieldgroup/FieldGroup.java
+++ b/server/src/com/vaadin/data/fieldgroup/FieldGroup.java
@@ -23,7 +23,6 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
-import java.util.logging.Logger;
import com.vaadin.data.Item;
import com.vaadin.data.Property;
@@ -55,9 +54,6 @@ import com.vaadin.util.ReflectTools;
*/
public class FieldGroup implements Serializable {
- private static final Logger logger = Logger.getLogger(FieldGroup.class
- .getName());
-
private Item itemDataSource;
private boolean buffered = true;
@@ -1013,9 +1009,7 @@ public class FieldGroup implements Serializable {
*/
public Field<?> buildAndBind(String caption, Object propertyId)
throws BindException {
- Class<?> type = getPropertyType(propertyId);
return buildAndBind(caption, propertyId, Field.class);
-
}
/**
diff --git a/server/src/com/vaadin/data/util/AbstractBeanContainer.java b/server/src/com/vaadin/data/util/AbstractBeanContainer.java
index b19cdd980c..d94588bdc9 100644
--- a/server/src/com/vaadin/data/util/AbstractBeanContainer.java
+++ b/server/src/com/vaadin/data/util/AbstractBeanContainer.java
@@ -222,6 +222,7 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends
@Override
public boolean removeAllItems() {
int origSize = size();
+ IDTYPE firstItem = getFirstVisibleItem();
internalRemoveAllItems();
@@ -234,7 +235,7 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends
// fire event only if the visible view changed, regardless of whether
// filtered out items were removed or not
if (origSize != 0) {
- fireItemSetChange();
+ fireItemsRemoved(0, firstItem, origSize);
}
return true;
@@ -679,6 +680,8 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends
protected void addAll(Collection<? extends BEANTYPE> collection)
throws IllegalStateException, IllegalArgumentException {
boolean modified = false;
+ int origSize = size();
+
for (BEANTYPE bean : collection) {
// TODO skipping invalid beans - should not allow them in javadoc?
if (bean == null
@@ -699,13 +702,22 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends
if (modified) {
// Filter the contents when all items have been added
if (isFiltered()) {
- filterAll();
- } else {
- fireItemSetChange();
+ doFilterContainer(!getFilters().isEmpty());
+ }
+ if (visibleNewItemsWasAdded(origSize)) {
+ // fire event about added items
+ int firstPosition = origSize;
+ IDTYPE firstItemId = getVisibleItemIds().get(firstPosition);
+ int affectedItems = size() - origSize;
+ fireItemsAdded(firstPosition, firstItemId, affectedItems);
}
}
}
+ private boolean visibleNewItemsWasAdded(int origSize) {
+ return size() > origSize;
+ }
+
/**
* Use the bean resolver to get the identifier for a bean.
*
diff --git a/server/src/com/vaadin/data/util/AbstractInMemoryContainer.java b/server/src/com/vaadin/data/util/AbstractInMemoryContainer.java
index 84304431bc..9a7922b928 100644
--- a/server/src/com/vaadin/data/util/AbstractInMemoryContainer.java
+++ b/server/src/com/vaadin/data/util/AbstractInMemoryContainer.java
@@ -15,8 +15,10 @@
*/
package com.vaadin.data.util;
+import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
+import java.util.EventObject;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
@@ -146,6 +148,85 @@ public abstract class AbstractInMemoryContainer<ITEMIDTYPE, PROPERTYIDCLASS, ITE
}
}
+ private static abstract class BaseItemAddOrRemoveEvent extends
+ EventObject implements Serializable {
+ protected Object itemId;
+ protected int index;
+ protected int count;
+
+ public BaseItemAddOrRemoveEvent(Container source, Object itemId,
+ int index, int count) {
+ super(source);
+ this.itemId = itemId;
+ this.index = index;
+ this.count = count;
+ }
+
+ public Container getContainer() {
+ return (Container) getSource();
+ }
+
+ public Object getFirstItemId() {
+ return itemId;
+ }
+
+ public int getFirstIndex() {
+ return index;
+ }
+
+ public int getAffectedItemsCount() {
+ return count;
+ }
+ }
+
+ /**
+ * An <code>Event</code> object specifying information about the added
+ * items.
+ *
+ * <p>
+ * This class provides information about the first added item and the number
+ * of added items.
+ * </p>
+ */
+ protected static class BaseItemAddEvent extends
+ BaseItemAddOrRemoveEvent implements
+ Container.Indexed.ItemAddEvent {
+
+ public BaseItemAddEvent(Container source, Object itemId, int index,
+ int count) {
+ super(source, itemId, index, count);
+ }
+
+ @Override
+ public int getAddedItemsCount() {
+ return getAffectedItemsCount();
+ }
+ }
+
+ /**
+ * An <code>Event</code> object specifying information about the removed
+ * items.
+ *
+ * <p>
+ * This class provides information about the first removed item and the
+ * number of removed items.
+ * </p>
+ */
+ protected static class BaseItemRemoveEvent extends
+ BaseItemAddOrRemoveEvent implements
+ Container.Indexed.ItemRemoveEvent {
+
+ public BaseItemRemoveEvent(Container source, Object itemId,
+ int index, int count) {
+ super(source, itemId, index, count);
+ }
+
+ @Override
+ public int getRemovedItemsCount() {
+ return getAffectedItemsCount();
+ }
+ }
+
/**
* Get an item even if filtered out.
*
@@ -898,36 +979,69 @@ public abstract class AbstractInMemoryContainer<ITEMIDTYPE, PROPERTYIDCLASS, ITE
* Notify item set change listeners that an item has been added to the
* container.
*
- * Unless subclasses specify otherwise, the default notification indicates a
- * full refresh.
- *
* @param postion
- * position of the added item in the view (if visible)
+ * position of the added item in the view
* @param itemId
* id of the added item
* @param item
* the added item
*/
protected void fireItemAdded(int position, ITEMIDTYPE itemId, ITEMCLASS item) {
- fireItemSetChange();
+ fireItemsAdded(position, itemId, 1);
+ }
+
+ /**
+ * Notify item set change listeners that items has been added to the
+ * container.
+ *
+ * @param firstPosition
+ * position of the first visible added item in the view
+ * @param firstItemId
+ * id of the first visible added item
+ * @param numberOfItems
+ * the number of visible added items
+ */
+ protected void fireItemsAdded(int firstPosition, ITEMIDTYPE firstItemId,
+ int numberOfItems) {
+ BaseItemAddEvent addEvent = new BaseItemAddEvent(this,
+ firstItemId, firstPosition, numberOfItems);
+ fireItemSetChange(addEvent);
}
/**
* Notify item set change listeners that an item has been removed from the
* container.
*
- * Unless subclasses specify otherwise, the default notification indicates a
- * full refresh.
+ * @param position
+ * position of the removed item in the view prior to removal
*
- * @param postion
- * position of the removed item in the view prior to removal (if
- * was visible)
* @param itemId
* id of the removed item, of type {@link Object} to satisfy
* {@link Container#removeItem(Object)} API
*/
protected void fireItemRemoved(int position, Object itemId) {
- fireItemSetChange();
+ fireItemsRemoved(position, itemId, 1);
+ }
+
+ /**
+ * Notify item set change listeners that items has been removed from the
+ * container.
+ *
+ * @param firstPosition
+ * position of the first visible removed item in the view prior
+ * to removal
+ * @param firstItemId
+ * id of the first visible removed item, of type {@link Object}
+ * to satisfy {@link Container#removeItem(Object)} API
+ * @param numberOfItems
+ * the number of removed visible items
+ *
+ */
+ protected void fireItemsRemoved(int firstPosition, Object firstItemId,
+ int numberOfItems) {
+ BaseItemRemoveEvent removeEvent = new BaseItemRemoveEvent(this,
+ firstItemId, firstPosition, numberOfItems);
+ fireItemSetChange(removeEvent);
}
// visible and filtered item identifier lists
@@ -946,6 +1060,21 @@ public abstract class AbstractInMemoryContainer<ITEMIDTYPE, PROPERTYIDCLASS, ITE
}
/**
+ * Returns the item id of the first visible item after filtering. 'Null' is
+ * returned if there is no visible items.
+ *
+ * For internal use only.
+ *
+ * @return item id of the first visible item
+ */
+ protected ITEMIDTYPE getFirstVisibleItem() {
+ if (!getVisibleItemIds().isEmpty()) {
+ return getVisibleItemIds().get(0);
+ }
+ return null;
+ }
+
+ /**
* Returns true is the container has active filters.
*
* @return true if the container is currently filtered
diff --git a/server/src/com/vaadin/data/util/IndexedContainer.java b/server/src/com/vaadin/data/util/IndexedContainer.java
index d7bf70caf6..5d20919208 100644
--- a/server/src/com/vaadin/data/util/IndexedContainer.java
+++ b/server/src/com/vaadin/data/util/IndexedContainer.java
@@ -226,6 +226,7 @@ public class IndexedContainer extends
@Override
public boolean removeAllItems() {
int origSize = size();
+ Object firstItem = getFirstVisibleItem();
internalRemoveAllItems();
@@ -235,7 +236,7 @@ public class IndexedContainer extends
// filtered out items were removed or not
if (origSize != 0) {
// Sends a change event
- fireItemSetChange();
+ fireItemsRemoved(0, firstItem, origSize);
}
return true;
@@ -620,8 +621,7 @@ public class IndexedContainer extends
@Override
protected void fireItemAdded(int position, Object itemId, Item item) {
if (position >= 0) {
- fireItemSetChange(new IndexedContainer.ItemSetChangeEvent(this,
- position));
+ super.fireItemAdded(position, itemId, item);
}
}
@@ -1211,4 +1211,5 @@ public class IndexedContainer extends
public Collection<Filter> getContainerFilters() {
return super.getContainerFilters();
}
+
}
diff --git a/server/src/com/vaadin/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/Constants.java b/server/src/com/vaadin/server/Constants.java
index 430a5153c0..a39c39fa72 100644
--- a/server/src/com/vaadin/server/Constants.java
+++ b/server/src/com/vaadin/server/Constants.java
@@ -67,7 +67,7 @@ public interface Constants {
// Keep the version number in sync with push/build.xml and other locations
// listed in that file
- static final String REQUIRED_ATMOSPHERE_RUNTIME_VERSION = "1.0.18.vaadin3";
+ static final String REQUIRED_ATMOSPHERE_RUNTIME_VERSION = "2.0.3.vaadin1";
static final String INVALID_ATMOSPHERE_VERSION_WARNING = "\n"
+ "=================================================================\n"
diff --git a/server/src/com/vaadin/server/DefaultErrorHandler.java b/server/src/com/vaadin/server/DefaultErrorHandler.java
index 2f46354500..f8b684f1d1 100644
--- a/server/src/com/vaadin/server/DefaultErrorHandler.java
+++ b/server/src/com/vaadin/server/DefaultErrorHandler.java
@@ -16,11 +16,14 @@
package com.vaadin.server;
+import java.lang.reflect.InvocationTargetException;
import java.net.SocketException;
import java.util.logging.Level;
import java.util.logging.Logger;
+import com.vaadin.event.ListenerMethod.MethodException;
import com.vaadin.server.ClientConnector.ConnectorErrorEvent;
+import com.vaadin.server.ServerRpcManager.RpcInvocationException;
import com.vaadin.shared.Connector;
import com.vaadin.ui.AbstractComponent;
import com.vaadin.ui.Component;
@@ -32,7 +35,7 @@ public class DefaultErrorHandler implements ErrorHandler {
}
public static void doDefault(ErrorEvent event) {
- final Throwable t = event.getThrowable();
+ Throwable t = event.getThrowable();
if (t instanceof SocketException) {
// Most likely client browser closed socket
getLogger().info(
@@ -41,6 +44,8 @@ public class DefaultErrorHandler implements ErrorHandler {
return;
}
+ t = findRelevantThrowable(t);
+
// Finds the original source of the error/exception
AbstractComponent component = findAbstractComponent(event);
if (component != null) {
@@ -54,6 +59,40 @@ public class DefaultErrorHandler implements ErrorHandler {
getLogger().log(Level.SEVERE, "", t);
}
+ /**
+ * Vaadin wraps exceptions in its own and due to reflection usage there
+ * might be also other irrelevant exceptions that make no sense for Vaadin
+ * users (~developers using Vaadin). This method tries to choose the
+ * relevant one to be reported.
+ *
+ * @since 7.2
+ * @param t
+ * throwable given for default error handler
+ * @return the throwable that is relevant for Vaadin users
+ */
+ private static Throwable findRelevantThrowable(Throwable t) {
+ try {
+ if ((t instanceof RpcInvocationException)
+ && (t.getCause() instanceof InvocationTargetException)) {
+ /*
+ * RpcInvocationException (that always wraps irrelevant
+ * java.lang.reflect.InvocationTargetException) might only be
+ * relevant for core Vaadin developers.
+ */
+ return findRelevantThrowable(t.getCause().getCause());
+ } else if (t instanceof MethodException) {
+ /*
+ * Method exception might only be relevant for core Vaadin
+ * developers.
+ */
+ return t.getCause();
+ }
+ } catch (Exception e) {
+ // NOP, just return the original one
+ }
+ return t;
+ }
+
private static Logger getLogger() {
return Logger.getLogger(DefaultErrorHandler.class.getName());
}
diff --git a/server/src/com/vaadin/server/FontAwesome.java b/server/src/com/vaadin/server/FontAwesome.java
new file mode 100644
index 0000000000..a7f4c7b342
--- /dev/null
+++ b/server/src/com/vaadin/server/FontAwesome.java
@@ -0,0 +1,447 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.server;
+
+/**
+ * FontAwesome set of font icons.
+ * <p>
+ * Each {@link FontIcon} comes from the FontAwesome font family, which is
+ * included in the theme.<br/>
+ * Consider this a starting point: it is unlikely an application needs exactly
+ * these icons, and all of them, so you might want to consider making a custom
+ * icon font - either to get other icons, or to minimize the size of the font.
+ * </p>
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ * @see http://fortawesome.github.io/Font-Awesome/
+ */
+public enum FontAwesome implements FontIcon {
+ GLASS(0XF000), //
+ MUSIC(0XF001), //
+ SEARCH(0XF002), //
+ ENVELOPE_O(0XF003), //
+ HEART(0XF004), //
+ STAR(0XF005), //
+ STAR_O(0XF006), //
+ USER(0XF007), //
+ FILM(0XF008), //
+ TH_LARGE(0XF009), //
+ TH(0XF00A), //
+ TH_LIST(0XF00B), //
+ CHECK(0XF00C), //
+ TIMES(0XF00D), //
+ SEARCH_PLUS(0XF00E), //
+ SEARCH_MINUS(0XF010), //
+ POWER_OFF(0XF011), //
+ SIGNAL(0XF012), //
+ COG(0XF013), //
+ TRASH_O(0XF014), //
+ HOME(0XF015), //
+ FILE_O(0XF016), //
+ CLOCK_O(0XF017), //
+ ROAD(0XF018), //
+ DOWNLOAD(0XF019), //
+ ARROW_CIRCLE_O_DOWN(0XF01A), //
+ ARROW_CIRCLE_O_UP(0XF01B), //
+ INBOX(0XF01C), //
+ PLAY_CIRCLE_O(0XF01D), //
+ REPEAT(0XF01E), //
+ REFRESH(0XF021), //
+ LIST_ALT(0XF022), //
+ LOCK(0XF023), //
+ FLAG(0XF024), //
+ HEADPHONES(0XF025), //
+ VOLUME_OFF(0XF026), //
+ VOLUME_DOWN(0XF027), //
+ VOLUME_UP(0XF028), //
+ QRCODE(0XF029), //
+ BARCODE(0XF02A), //
+ TAG(0XF02B), //
+ TAGS(0XF02C), //
+ BOOK(0XF02D), //
+ BOOKMARK(0XF02E), //
+ PRINT(0XF02F), //
+ CAMERA(0XF030), //
+ FONT(0XF031), //
+ BOLD(0XF032), //
+ ITALIC(0XF033), //
+ TEXT_HEIGHT(0XF034), //
+ TEXT_WIDTH(0XF035), //
+ ALIGN_LEFT(0XF036), //
+ ALIGN_CENTER(0XF037), //
+ ALIGN_RIGHT(0XF038), //
+ ALIGN_JUSTIFY(0XF039), //
+ LIST(0XF03A), //
+ OUTDENT(0XF03B), //
+ INDENT(0XF03C), //
+ VIDEO_CAMERA(0XF03D), //
+ PICTURE_O(0XF03E), //
+ PENCIL(0XF040), //
+ MAP_MARKER(0XF041), //
+ ADJUST(0XF042), //
+ TINT(0XF043), //
+ PENCIL_SQUARE_O(0XF044), //
+ SHARE_SQUARE_O(0XF045), //
+ CHECK_SQUARE_O(0XF046), //
+ ARROWS(0XF047), //
+ STEP_BACKWARD(0XF048), //
+ FAST_BACKWARD(0XF049), //
+ BACKWARD(0XF04A), //
+ PLAY(0XF04B), //
+ PAUSE(0XF04C), //
+ STOP(0XF04D), //
+ FORWARD(0XF04E), //
+ FAST_FORWARD(0XF050), //
+ STEP_FORWARD(0XF051), //
+ EJECT(0XF052), //
+ CHEVRON_LEFT(0XF053), //
+ CHEVRON_RIGHT(0XF054), //
+ PLUS_CIRCLE(0XF055), //
+ MINUS_CIRCLE(0XF056), //
+ TIMES_CIRCLE(0XF057), //
+ CHECK_CIRCLE(0XF058), //
+ QUESTION_CIRCLE(0XF059), //
+ INFO_CIRCLE(0XF05A), //
+ CROSSHAIRS(0XF05B), //
+ TIMES_CIRCLE_O(0XF05C), //
+ CHECK_CIRCLE_O(0XF05D), //
+ BAN(0XF05E), //
+ ARROW_LEFT(0XF060), //
+ ARROW_RIGHT(0XF061), //
+ ARROW_UP(0XF062), //
+ ARROW_DOWN(0XF063), //
+ SHARE(0XF064), //
+ EXPAND(0XF065), //
+ COMPRESS(0XF066), //
+ PLUS(0XF067), //
+ MINUS(0XF068), //
+ ASTERISK(0XF069), //
+ EXCLAMATION_CIRCLE(0XF06A), //
+ GIFT(0XF06B), //
+ LEAF(0XF06C), //
+ FIRE(0XF06D), //
+ EYE(0XF06E), //
+ EYE_SLASH(0XF070), //
+ EXCLAMATION_TRIANGLE(0XF071), //
+ PLANE(0XF072), //
+ CALENDAR(0XF073), //
+ RANDOM(0XF074), //
+ COMMENT(0XF075), //
+ MAGNET(0XF076), //
+ CHEVRON_UP(0XF077), //
+ CHEVRON_DOWN(0XF078), //
+ RETWEET(0XF079), //
+ SHOPPING_CART(0XF07A), //
+ FOLDER(0XF07B), //
+ FOLDER_OPEN(0XF07C), //
+ ARROWS_V(0XF07D), //
+ ARROWS_H(0XF07E), //
+ BAR_CHART_O(0XF080), //
+ TWITTER_SQUARE(0XF081), //
+ FACEBOOK_SQUARE(0XF082), //
+ CAMERA_RETRO(0XF083), //
+ KEY(0XF084), //
+ COGS(0XF085), //
+ COMMENTS(0XF086), //
+ THUMBS_O_UP(0XF087), //
+ THUMBS_O_DOWN(0XF088), //
+ STAR_HALF(0XF089), //
+ HEART_O(0XF08A), //
+ SIGN_OUT(0XF08B), //
+ LINKEDIN_SQUARE(0XF08C), //
+ THUMB_TACK(0XF08D), //
+ EXTERNAL_LINK(0XF08E), //
+ SIGN_IN(0XF090), //
+ TROPHY(0XF091), //
+ GITHUB_SQUARE(0XF092), //
+ UPLOAD(0XF093), //
+ LEMON_O(0XF094), //
+ PHONE(0XF095), //
+ SQUARE_O(0XF096), //
+ BOOKMARK_O(0XF097), //
+ PHONE_SQUARE(0XF098), //
+ TWITTER(0XF099), //
+ FACEBOOK(0XF09A), //
+ GITHUB(0XF09B), //
+ UNLOCK(0XF09C), //
+ CREDIT_CARD(0XF09D), //
+ RSS(0XF09E), //
+ HDD_O(0XF0A0), //
+ BULLHORN(0XF0A1), //
+ BELL(0XF0F3), //
+ CERTIFICATE(0XF0A3), //
+ HAND_O_RIGHT(0XF0A4), //
+ HAND_O_LEFT(0XF0A5), //
+ HAND_O_UP(0XF0A6), //
+ HAND_O_DOWN(0XF0A7), //
+ ARROW_CIRCLE_LEFT(0XF0A8), //
+ ARROW_CIRCLE_RIGHT(0XF0A9), //
+ ARROW_CIRCLE_UP(0XF0AA), //
+ ARROW_CIRCLE_DOWN(0XF0AB), //
+ GLOBE(0XF0AC), //
+ WRENCH(0XF0AD), //
+ TASKS(0XF0AE), //
+ FILTER(0XF0B0), //
+ BRIEFCASE(0XF0B1), //
+ ARROWS_ALT(0XF0B2), //
+ USERS(0XF0C0), //
+ LINK(0XF0C1), //
+ CLOUD(0XF0C2), //
+ FLASK(0XF0C3), //
+ SCISSORS(0XF0C4), //
+ FILES_O(0XF0C5), //
+ PAPERCLIP(0XF0C6), //
+ FLOPPY_O(0XF0C7), //
+ SQUARE(0XF0C8), //
+ BARS(0XF0C9), //
+ LIST_UL(0XF0CA), //
+ LIST_OL(0XF0CB), //
+ STRIKETHROUGH(0XF0CC), //
+ UNDERLINE(0XF0CD), //
+ TABLE(0XF0CE), //
+ MAGIC(0XF0D0), //
+ TRUCK(0XF0D1), //
+ PINTEREST(0XF0D2), //
+ PINTEREST_SQUARE(0XF0D3), //
+ GOOGLE_PLUS_SQUARE(0XF0D4), //
+ GOOGLE_PLUS(0XF0D5), //
+ MONEY(0XF0D6), //
+ CARET_DOWN(0XF0D7), //
+ CARET_UP(0XF0D8), //
+ CARET_LEFT(0XF0D9), //
+ CARET_RIGHT(0XF0DA), //
+ COLUMNS(0XF0DB), //
+ SORT(0XF0DC), //
+ SORT_ASC(0XF0DD), //
+ SORT_DESC(0XF0DE), //
+ ENVELOPE(0XF0E0), //
+ LINKEDIN(0XF0E1), //
+ UNDO(0XF0E2), //
+ GAVEL(0XF0E3), //
+ TACHOMETER(0XF0E4), //
+ COMMENT_O(0XF0E5), //
+ COMMENTS_O(0XF0E6), //
+ BOLT(0XF0E7), //
+ SITEMAP(0XF0E8), //
+ UMBRELLA(0XF0E9), //
+ CLIPBOARD(0XF0EA), //
+ LIGHTBULB_O(0XF0EB), //
+ EXCHANGE(0XF0EC), //
+ CLOUD_DOWNLOAD(0XF0ED), //
+ CLOUD_UPLOAD(0XF0EE), //
+ USER_MD(0XF0F0), //
+ STETHOSCOPE(0XF0F1), //
+ SUITCASE(0XF0F2), //
+ BELL_O(0XF0A2), //
+ COFFEE(0XF0F4), //
+ CUTLERY(0XF0F5), //
+ FILE_TEXT_O(0XF0F6), //
+ BUILDING_O(0XF0F7), //
+ HOSPITAL_O(0XF0F8), //
+ AMBULANCE(0XF0F9), //
+ MEDKIT(0XF0FA), //
+ FIGHTER_JET(0XF0FB), //
+ BEER(0XF0FC), //
+ H_SQUARE(0XF0FD), //
+ PLUS_SQUARE(0XF0FE), //
+ ANGLE_DOUBLE_LEFT(0XF100), //
+ ANGLE_DOUBLE_RIGHT(0XF101), //
+ ANGLE_DOUBLE_UP(0XF102), //
+ ANGLE_DOUBLE_DOWN(0XF103), //
+ ANGLE_LEFT(0XF104), //
+ ANGLE_RIGHT(0XF105), //
+ ANGLE_UP(0XF106), //
+ ANGLE_DOWN(0XF107), //
+ DESKTOP(0XF108), //
+ LAPTOP(0XF109), //
+ TABLET(0XF10A), //
+ MOBILE(0XF10B), //
+ CIRCLE_O(0XF10C), //
+ QUOTE_LEFT(0XF10D), //
+ QUOTE_RIGHT(0XF10E), //
+ SPINNER(0XF110), //
+ CIRCLE(0XF111), //
+ REPLY(0XF112), //
+ GITHUB_ALT(0XF113), //
+ FOLDER_O(0XF114), //
+ FOLDER_OPEN_O(0XF115), //
+ SMILE_O(0XF118), //
+ FROWN_O(0XF119), //
+ MEH_O(0XF11A), //
+ GAMEPAD(0XF11B), //
+ KEYBOARD_O(0XF11C), //
+ FLAG_O(0XF11D), //
+ FLAG_CHECKERED(0XF11E), //
+ TERMINAL(0XF120), //
+ CODE(0XF121), //
+ REPLY_ALL(0XF122), //
+ MAIL_REPLY_ALL(0XF122), //
+ STAR_HALF_O(0XF123), //
+ LOCATION_ARROW(0XF124), //
+ CROP(0XF125), //
+ CODE_FORK(0XF126), //
+ CHAIN_BROKEN(0XF127), //
+ QUESTION(0XF128), //
+ INFO(0XF129), //
+ EXCLAMATION(0XF12A), //
+ SUPERSCRIPT(0XF12B), //
+ SUBSCRIPT(0XF12C), //
+ ERASER(0XF12D), //
+ PUZZLE_PIECE(0XF12E), //
+ MICROPHONE(0XF130), //
+ MICROPHONE_SLASH(0XF131), //
+ SHIELD(0XF132), //
+ CALENDAR_O(0XF133), //
+ FIRE_EXTINGUISHER(0XF134), //
+ ROCKET(0XF135), //
+ MAXCDN(0XF136), //
+ CHEVRON_CIRCLE_LEFT(0XF137), //
+ CHEVRON_CIRCLE_RIGHT(0XF138), //
+ CHEVRON_CIRCLE_UP(0XF139), //
+ CHEVRON_CIRCLE_DOWN(0XF13A), //
+ HTML5(0XF13B), //
+ CSS3(0XF13C), //
+ ANCHOR(0XF13D), //
+ UNLOCK_ALT(0XF13E), //
+ BULLSEYE(0XF140), //
+ ELLIPSIS_H(0XF141), //
+ ELLIPSIS_V(0XF142), //
+ RSS_SQUARE(0XF143), //
+ PLAY_CIRCLE(0XF144), //
+ TICKET(0XF145), //
+ MINUS_SQUARE(0XF146), //
+ MINUS_SQUARE_O(0XF147), //
+ LEVEL_UP(0XF148), //
+ LEVEL_DOWN(0XF149), //
+ CHECK_SQUARE(0XF14A), //
+ PENCIL_SQUARE(0XF14B), //
+ EXTERNAL_LINK_SQUARE(0XF14C), //
+ SHARE_SQUARE(0XF14D), //
+ COMPASS(0XF14E), //
+ CARET_SQUARE_O_DOWN(0XF150), //
+ CARET_SQUARE_O_UP(0XF151), //
+ CARET_SQUARE_O_RIGHT(0XF152), //
+ EUR(0XF153), //
+ GBP(0XF154), //
+ USD(0XF155), //
+ INR(0XF156), //
+ JPY(0XF157), //
+ RUB(0XF158), //
+ KRW(0XF159), //
+ BTC(0XF15A), //
+ FILE(0XF15B), //
+ FILE_TEXT(0XF15C), //
+ SORT_ALPHA_ASC(0XF15D), //
+ SORT_ALPHA_DESC(0XF15E), //
+ SORT_AMOUNT_ASC(0XF160), //
+ SORT_AMOUNT_DESC(0XF161), //
+ SORT_NUMERIC_ASC(0XF162), //
+ SORT_NUMERIC_DESC(0XF163), //
+ THUMBS_UP(0XF164), //
+ THUMBS_DOWN(0XF165), //
+ YOUTUBE_SQUARE(0XF166), //
+ YOUTUBE(0XF167), //
+ XING(0XF168), //
+ XING_SQUARE(0XF169), //
+ YOUTUBE_PLAY(0XF16A), //
+ DROPBOX(0XF16B), //
+ STACK_OVERFLOW(0XF16C), //
+ INSTAGRAM(0XF16D), //
+ FLICKR(0XF16E), //
+ ADN(0XF170), //
+ BITBUCKET(0XF171), //
+ BITBUCKET_SQUARE(0XF172), //
+ TUMBLR(0XF173), //
+ TUMBLR_SQUARE(0XF174), //
+ LONG_ARROW_DOWN(0XF175), //
+ LONG_ARROW_UP(0XF176), //
+ LONG_ARROW_LEFT(0XF177), //
+ LONG_ARROW_RIGHT(0XF178), //
+ APPLE(0XF179), //
+ WINDOWS(0XF17A), //
+ ANDROID(0XF17B), //
+ LINUX(0XF17C), //
+ DRIBBBLE(0XF17D), //
+ SKYPE(0XF17E), //
+ FOURSQUARE(0XF180), //
+ TRELLO(0XF181), //
+ FEMALE(0XF182), //
+ MALE(0XF183), //
+ GITTIP(0XF184), //
+ SUN_O(0XF185), //
+ MOON_O(0XF186), //
+ ARCHIVE(0XF187), //
+ BUG(0XF188), //
+ VK(0XF189), //
+ WEIBO(0XF18A), //
+ RENREN(0XF18B), //
+ PAGELINES(0XF18C), //
+ STACK_EXCHANGE(0XF18D), //
+ ARROW_CIRCLE_O_RIGHT(0XF18E), //
+ ARROW_CIRCLE_O_LEFT(0XF190), //
+ CARET_SQUARE_O_LEFT(0XF191), //
+ DOT_CIRCLE_O(0XF192), //
+ WHEELCHAIR(0XF193), //
+ VIMEO_SQUARE(0XF194), //
+ TRY(0XF195), //
+ PLUS_SQUARE_O(0XF196);
+
+ private static final String fontFamily = "FontAwesome";
+ private int codepoint;
+
+ FontAwesome(int codepoint) {
+ this.codepoint = codepoint;
+ }
+
+ /**
+ * Unsupported: {@link FontIcon} does not have a MIME type and is not a
+ * {@link Resource} that can be used in a context where a MIME type would be
+ * needed.
+ */
+ @Override
+ public String getMIMEType() {
+ throw new UnsupportedOperationException(FontIcon.class.getSimpleName()
+ + " should not be used where a MIME type is needed.");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.FontIcon#getFontFamily()
+ */
+ @Override
+ public String getFontFamily() {
+ return fontFamily;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.FontIcon#getCodepoint()
+ */
+ @Override
+ public int getCodepoint() {
+ return codepoint;
+ }
+
+ @Override
+ public String getHtml() {
+ return "<span class=\"v-icon\" style=\"font-family: " + fontFamily
+ + ";\">&#x" + Integer.toHexString(codepoint) + ";</span>";
+ }
+
+}
diff --git a/server/src/com/vaadin/server/FontIcon.java b/server/src/com/vaadin/server/FontIcon.java
new file mode 100644
index 0000000000..45279f2c44
--- /dev/null
+++ b/server/src/com/vaadin/server/FontIcon.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.server;
+
+import com.vaadin.shared.ui.label.ContentMode;
+import com.vaadin.ui.Label;
+
+/**
+ * A font icon is a type of icon that is made by displaying one character from a
+ * specially constructed font containing icons ("icon font").
+ * <p>
+ * {@link FontIcon} is a custom resource type which uses the URI scheme
+ * <code>fonticon://&lt;fontfamily&gt;/&lt;codepoint&gt;</code> to reference a
+ * specific icon from a specific icon font. <br/>
+ * </p>
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public interface FontIcon extends Resource {
+ /**
+ * Returns the name (font family) of the font from which this icon comes.
+ * The name is used to apply the correct font where the icon is used.
+ *
+ * @since 7.2
+ * @return
+ */
+ public String getFontFamily();
+
+ /**
+ * Returns the unicode codepoint (character location) for this icon within
+ * the font given in {@link #getFontFamily()}.
+ * <p>
+ * For example, 0x0021 would in a regular font be the codepoint for the
+ * exclamation-point character.<br/>
+ * When constructing icon fonts, it might be a good idea to use the
+ * codepoints in the "Private use area", from 0xE000 0xF8FF.
+ * </p>
+ *
+ * @since 7.2
+ * @return
+ */
+ public int getCodepoint();
+
+ /**
+ * Returns HTML that can be used to display the icon in places where HTML
+ * can be used, such as a {@link Label} with {@link ContentMode#HTML}.
+ *
+ *
+ * @since 7.2
+ * @return HTML needed to display icon
+ */
+ public String getHtml();
+}
diff --git a/server/src/com/vaadin/server/JsonCodec.java b/server/src/com/vaadin/server/JsonCodec.java
index d533ed99f3..d05922b40d 100644
--- a/server/src/com/vaadin/server/JsonCodec.java
+++ b/server/src/com/vaadin/server/JsonCodec.java
@@ -31,6 +31,7 @@ import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -45,6 +46,8 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
+import com.vaadin.server.communication.DateSerializer;
+import com.vaadin.server.communication.JSONSerializer;
import com.vaadin.shared.Connector;
import com.vaadin.shared.JsonConstants;
import com.vaadin.shared.communication.UidlValue;
@@ -176,6 +179,11 @@ public class JsonCodec implements Serializable {
*/
private static Map<String, Class<?>> transportTypeToType = new HashMap<String, Class<?>>();
+ private static Map<Class<?>, JSONSerializer<?>> customSerializers = new HashMap<Class<?>, JSONSerializer<?>>();
+ static {
+ customSerializers.put(Date.class, new DateSerializer());
+ }
+
static {
registerType(String.class, JsonConstants.VTYPE_STRING);
registerType(Connector.class, JsonConstants.VTYPE_CONNECTOR);
@@ -283,6 +291,9 @@ public class JsonCodec implements Serializable {
Class<?> classForType = getClassForType(targetType);
return decodeEnum(classForType.asSubclass(Enum.class),
(String) value);
+ } else if (customSerializers.containsKey(getClassForType(targetType))) {
+ return customSerializers.get(getClassForType(targetType))
+ .deserialize(targetType, value, connectorTracker);
} else {
return decodeObject(targetType, (JSONObject) value,
connectorTracker);
@@ -606,7 +617,7 @@ public class JsonCodec implements Serializable {
return decodedObject;
} catch (Exception e) {
- throw new JSONException(e);
+ throw new JSONException(e.getMessage());
}
}
@@ -676,6 +687,10 @@ public class JsonCodec implements Serializable {
return encodeEnum((Enum<?>) value, connectorTracker);
} else if (value instanceof JSONArray || value instanceof JSONObject) {
return new EncodeResult(value);
+ } else if (customSerializers.containsKey(value.getClass())) {
+ JSONSerializer serializer = customSerializers.get(value.getClass());
+ return new EncodeResult(serializer.serialize(value,
+ connectorTracker));
} else if (valueType instanceof Class<?>) {
// Any object that we do not know how to encode we encode by looping
// through fields
@@ -750,7 +765,7 @@ public class JsonCodec implements Serializable {
}
} catch (Exception e) {
// TODO: Should exceptions be handled in a different way?
- throw new JSONException(e);
+ throw new JSONException(e.getMessage());
}
return new EncodeResult(encoded, diff);
}
diff --git a/server/src/com/vaadin/server/LegacyCommunicationManager.java b/server/src/com/vaadin/server/LegacyCommunicationManager.java
index ad662cf6df..8d61968b47 100644
--- a/server/src/com/vaadin/server/LegacyCommunicationManager.java
+++ b/server/src/com/vaadin/server/LegacyCommunicationManager.java
@@ -316,8 +316,6 @@ public class LegacyCommunicationManager implements Serializable {
private final HashMap<Class<? extends ClientConnector>, Integer> typeToKey = new HashMap<Class<? extends ClientConnector>, Integer>();
private int nextTypeKey = 0;
- private BootstrapHandler bootstrapHandler;
-
/**
* @deprecated As of 7.1. Will be removed in the future.
*/
diff --git a/server/src/com/vaadin/server/Page.java b/server/src/com/vaadin/server/Page.java
index 037d8e8352..19b84381d5 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.
*
@@ -1119,12 +1133,15 @@ public class Page implements Serializable {
/**
* Sets the page title. The page title is displayed by the browser e.g. as
* the title of the browser window or as the title of the tab.
+ * <p>
+ * If the title is set to null, it will not left as-is. Set to empty string
+ * to clear the title.
*
* @param title
- * the new page title to set
+ * the 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/ResourceReference.java b/server/src/com/vaadin/server/ResourceReference.java
index 6747dd2b74..4bc8febd72 100644
--- a/server/src/com/vaadin/server/ResourceReference.java
+++ b/server/src/com/vaadin/server/ResourceReference.java
@@ -67,6 +67,13 @@ public class ResourceReference extends URLReference {
final String uri = "theme://"
+ ((ThemeResource) resource).getResourceId();
return uri;
+ } else if (resource instanceof FontIcon) {
+ // fonticon://[font-family]/[codepoint]
+ final FontIcon icon = (FontIcon) resource;
+ final String uri = ApplicationConstants.FONTICON_PROTOCOL_PREFIX
+ + urlEncode(icon.getFontFamily()) + "/"
+ + Integer.toHexString(icon.getCodepoint());
+ return uri;
} else {
throw new RuntimeException(getClass().getSimpleName()
+ " does not support resources of type: "
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..6cf30e85e9 100644
--- a/server/src/com/vaadin/server/VaadinPortlet.java
+++ b/server/src/com/vaadin/server/VaadinPortlet.java
@@ -365,7 +365,6 @@ public class VaadinPortlet extends GenericPortlet implements Constants,
if (request instanceof RenderRequest) {
return RequestType.RENDER;
} else if (request instanceof ResourceRequest) {
- ResourceRequest resourceRequest = (ResourceRequest) request;
if (ServletPortletHelper.isUIDLRequest(vaadinRequest)) {
return RequestType.UIDL;
} else if (PortletUIInitHandler.isUIInitRequest(vaadinRequest)) {
@@ -503,6 +502,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 eda794438f..c926efc67c 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}.
*/
@@ -1568,9 +1574,9 @@ public abstract class VaadinService implements Serializable {
meta.put("appError", appError);
JSONObject json = new JSONObject();
- json.put("changes", Collections.EMPTY_LIST);
- json.put("resources", Collections.EMPTY_MAP);
- json.put("locales", Collections.EMPTY_LIST);
+ json.put("changes", new JSONObject());
+ json.put("resources", new JSONObject());
+ json.put("locales", new JSONObject());
json.put("meta", meta);
returnString = json.toString();
} catch (JSONException e) {
@@ -1690,23 +1696,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
@@ -1729,6 +1718,8 @@ public abstract class VaadinService implements Serializable {
} catch (InterruptedException e) {
// Just ignore
}
+
+ return future;
}
/**
@@ -1775,4 +1766,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..265d18b859 100644
--- a/server/src/com/vaadin/server/VaadinSession.java
+++ b/server/src/com/vaadin/server/VaadinSession.java
@@ -22,12 +22,15 @@ import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
+import java.util.Enumeration;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Queue;
+import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
@@ -43,7 +46,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 +207,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();
@@ -424,6 +426,32 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
}
/**
+ * Retrieves all {@link VaadinSession}s which are stored in the given HTTP
+ * session
+ *
+ * @since 7.2
+ * @param httpSession
+ * the HTTP session
+ * @return the found VaadinSessions
+ */
+ public static Collection<VaadinSession> getAllSessions(
+ HttpSession httpSession) {
+ Set<VaadinSession> sessions = new HashSet<VaadinSession>();
+ Enumeration<String> attributeNames = httpSession.getAttributeNames();
+
+ while (attributeNames.hasMoreElements()) {
+ String attributeName = attributeNames.nextElement();
+ if (attributeName.startsWith(VaadinSession.class.getName() + ".")) {
+ Object value = httpSession.getAttribute(attributeName);
+ if (value instanceof VaadinSession) {
+ sessions.add((VaadinSession) value);
+ }
+ }
+ }
+ return sessions;
+ }
+
+ /**
* Removes this VaadinSession from the HTTP session.
*
* @param service
@@ -828,10 +856,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 +969,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 +992,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 +1116,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 +1132,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 +1357,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/AtmospherePushConnection.java b/server/src/com/vaadin/server/communication/AtmospherePushConnection.java
index b9d4955b12..56dd576403 100644
--- a/server/src/com/vaadin/server/communication/AtmospherePushConnection.java
+++ b/server/src/com/vaadin/server/communication/AtmospherePushConnection.java
@@ -22,22 +22,16 @@ import java.io.Serializable;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.logging.Level;
-import java.util.logging.Logger;
import org.atmosphere.cpr.AtmosphereResource;
import org.atmosphere.cpr.AtmosphereResource.TRANSPORT;
-import org.json.JSONException;
import com.vaadin.shared.communication.PushConstants;
import com.vaadin.ui.UI;
/**
- * {@link PushConnection} implementation using the Atmosphere push support that
- * is by default included in Vaadin.
+ * A {@link PushConnection} implementation using the Atmosphere push support
+ * that is by default included in Vaadin.
*
* @author Vaadin Ltd
* @since 7.1
@@ -92,55 +86,84 @@ public class AtmospherePushConnection implements PushConnection {
}
}
+ protected enum State {
+ /**
+ * Not connected. Trying to push will set the connection state to
+ * PUSH_PENDING or RESPONSE_PENDING and defer sending the message until
+ * a connection is established.
+ */
+ DISCONNECTED,
+
+ /**
+ * Not connected. An asynchronous push is pending the opening of the
+ * connection.
+ */
+ PUSH_PENDING,
+
+ /**
+ * Not connected. A response to a client request is pending the opening
+ * of the connection.
+ */
+ RESPONSE_PENDING,
+
+ /**
+ * Connected. Messages can be sent through the connection.
+ */
+ CONNECTED;
+ }
+
+ private State state = State.DISCONNECTED;
private UI ui;
private AtmosphereResource resource;
- private Future<String> outgoingMessage;
private FragmentedMessage incomingMessage;
- public AtmospherePushConnection(UI ui, AtmosphereResource resource) {
+ public AtmospherePushConnection(UI ui) {
this.ui = ui;
- this.resource = resource;
}
@Override
public void push() {
- assert isConnected();
- try {
- push(true);
- } catch (IOException e) {
- // TODO Error handling
- throw new RuntimeException("Push failed", e);
- }
+ push(true);
}
/**
- * Pushes pending state changes and client RPC calls to the client.
+ * Pushes pending state changes and client RPC calls to the client. If
+ * {@code isConnected()} is false, defers the push until a connection is
+ * established.
*
* @param async
* True if this push asynchronously originates from the server,
* false if it is a response to a client request.
- * @throws IOException
*/
- protected void push(boolean async) throws IOException {
- Writer writer = new StringWriter();
- try {
- new UidlWriter().write(getUI(), writer, false, async);
- } catch (JSONException e) {
- throw new IOException("Error writing UIDL", e);
+ public void push(boolean async) {
+ if (!isConnected()) {
+ if (async && state != State.RESPONSE_PENDING) {
+ state = State.PUSH_PENDING;
+ } else {
+ state = State.RESPONSE_PENDING;
+ }
+ } else {
+ try {
+ Writer writer = new StringWriter();
+ new UidlWriter().write(getUI(), writer, false, async);
+ sendMessage("for(;;);[{" + writer.toString() + "}]");
+ } catch (Exception e) {
+ throw new RuntimeException("Push failed", e);
+ }
}
- sendMessage("for(;;);[{" + writer.toString() + "}]");
}
/**
- * Sends the given message to the current client.
+ * Sends the given message to the current client. Cannot be called if
+ * {@isConnected()} is false.
*
* @param message
* The message to send
*/
void sendMessage(String message) {
+ assert (isConnected());
// "Broadcast" the changes to the single client only
- outgoingMessage = getResource().getBroadcaster().broadcast(message,
- getResource());
+ getResource().getBroadcaster().broadcast(message, getResource());
}
/**
@@ -157,7 +180,7 @@ public class AtmospherePushConnection implements PushConnection {
*/
protected Reader receiveMessage(Reader reader) throws IOException {
- if (resource.transport() != TRANSPORT.WEBSOCKET) {
+ if (resource == null || resource.transport() != TRANSPORT.WEBSOCKET) {
return reader;
}
@@ -179,9 +202,37 @@ public class AtmospherePushConnection implements PushConnection {
@Override
public boolean isConnected() {
- return resource != null
- && resource.getBroadcaster().getAtmosphereResources()
- .contains(resource);
+ assert (state == State.CONNECTED) ^ (resource == null);
+ return state == State.CONNECTED;
+ }
+
+ /**
+ * Associates this {@code AtmospherePushConnection} with the given
+ * {@AtmosphereResource} representing an established
+ * push connection. If already connected, calls {@link #disconnect()} first.
+ * If there is a deferred push, carries it out via the new connection.
+ *
+ * @since 7.2
+ */
+ public void connect(AtmosphereResource resource) {
+
+ assert resource != null;
+ assert resource != this.resource;
+
+ if (isConnected()) {
+ disconnect();
+ }
+
+ this.resource = resource;
+ State oldState = state;
+ state = State.CONNECTED;
+
+ if (oldState == State.PUSH_PENDING
+ || oldState == State.RESPONSE_PENDING) {
+ // Sending a "response" message (async=false) also takes care of a
+ // pending push, but not vice versa
+ push(oldState == State.PUSH_PENDING);
+ }
}
/**
@@ -202,33 +253,8 @@ public class AtmospherePushConnection implements PushConnection {
@Override
public void disconnect() {
assert isConnected();
-
- if (outgoingMessage != null) {
- // Wait for the last message to be sent before closing the
- // connection (assumes that futures are completed in order)
- try {
- outgoingMessage.get(1000, TimeUnit.MILLISECONDS);
- } catch (TimeoutException e) {
- getLogger()
- .log(Level.INFO,
- "Timeout waiting for messages to be sent to client before disconnect");
- } catch (Exception e) {
- getLogger()
- .log(Level.INFO,
- "Error waiting for messages to be sent to client before disconnect");
- }
- outgoingMessage = null;
- }
-
resource.resume();
resource = null;
- }
-
- /**
- * @since
- * @return
- */
- private static Logger getLogger() {
- return Logger.getLogger(AtmospherePushConnection.class.getName());
+ state = State.DISCONNECTED;
}
}
diff --git a/server/src/com/vaadin/server/communication/DateSerializer.java b/server/src/com/vaadin/server/communication/DateSerializer.java
new file mode 100644
index 0000000000..9179eb922b
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/DateSerializer.java
@@ -0,0 +1,42 @@
+/*
+ * 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.communication;
+
+import java.lang.reflect.Type;
+import java.util.Date;
+
+import com.vaadin.ui.ConnectorTracker;
+
+/**
+ * Server side serializer/deserializer for java.util.Date
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public class DateSerializer implements JSONSerializer<Date> {
+
+ @Override
+ public Date deserialize(Type type, Object jsonValue,
+ ConnectorTracker connectorTracker) {
+ return new Date(Long.valueOf(String.valueOf(jsonValue)));
+ }
+
+ @Override
+ public Object serialize(Date value, ConnectorTracker connectorTracker) {
+ return value.getTime();
+ }
+
+}
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/JSONSerializer.java b/server/src/com/vaadin/server/communication/JSONSerializer.java
new file mode 100644
index 0000000000..fe609c70b6
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/JSONSerializer.java
@@ -0,0 +1,72 @@
+/*
+ * 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.communication;
+
+import java.lang.reflect.Type;
+
+import com.vaadin.ui.ConnectorTracker;
+
+/**
+ * Implementors of this interface knows how to serialize an Object of a given
+ * type to JSON and how to deserialize the JSON back into an object.
+ * <p>
+ * The {@link #serialize(Object, ConnectorTracker)} and
+ * {@link #deserialize(Type, Object, ConnectorTracker)} methods must be
+ * symmetric so they can be chained and produce the original result (or an equal
+ * result).
+ * <p>
+ * Each {@link JSONSerializer} implementation can handle an object of a single
+ * type.
+ * <p>
+ * This is the server side interface, see
+ * com.vaadin.client.communication.JSONSerializer for the client side interface.
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public interface JSONSerializer<T> {
+ /**
+ * Creates and deserializes an object received from the client. Must be
+ * compatible with {@link #serialize(Object, ConnectorTracker)} and also
+ * with the client side com.vaadin.client.communication.JSONSerializer.
+ * <p>
+ * The json parameter is of type Object as org.json JSON classes have no
+ * other common super class
+ *
+ * @param type
+ * The expected return type
+ * @param jsonValue
+ * the value from the JSON
+ * @param connectorTracker
+ * the connector tracker instance for the UI
+ * @return A deserialized object
+ */
+ T deserialize(Type type, Object jsonValue, ConnectorTracker connectorTracker);
+
+ /**
+ * Serialize the given object into JSON. Must be compatible with
+ * {@link #deserialize(Object, connectorTracker)} and the client side
+ * com.vaadin.client.communication.JSONSerializer
+ *
+ * @param value
+ * The object to serialize
+ * @param connectorTracker
+ * The connector tracker instance for the UI
+ * @return A JSON serialized version of the object
+ */
+ Object serialize(T value, ConnectorTracker connectorTracker);
+
+}
diff --git a/server/src/com/vaadin/server/communication/PushConnection.java b/server/src/com/vaadin/server/communication/PushConnection.java
index 7f78d1d48e..cab3c94824 100644
--- a/server/src/com/vaadin/server/communication/PushConnection.java
+++ b/server/src/com/vaadin/server/communication/PushConnection.java
@@ -20,7 +20,12 @@ import com.vaadin.ui.UI;
/**
* Represents a bidirectional ("push") connection between a single UI and its
- * client-side.
+ * client-side. A single {@code PushConnection} instance is bound to a UI as
+ * long as push is enabled in that UI, even if the actual connection is
+ * momentarily dropped either due to a network failure or as a normal part of
+ * the transport mechanism.
+ * <p>
+ * This interface is an internal API, only meant to be used by the framework.
*
* @author Vaadin Ltd
* @since 7.1
@@ -28,9 +33,10 @@ import com.vaadin.ui.UI;
public interface PushConnection {
/**
- * Pushes pending state changes and client RPC calls to the client. Cannot
- * be called if {@link #isConnected()} is false. It is NOT safe to invoke
- * this method if not holding the session lock.
+ * Pushes pending state changes and client RPC calls to the client. Can be
+ * called even if {@link #isConnected()} is false; the push will be deferred
+ * until a connection is available. It is NOT safe to invoke this method if
+ * not holding the session lock.
* <p>
* This is internal API; please use {@link UI#push()} instead.
*/
diff --git a/server/src/com/vaadin/server/communication/PushHandler.java b/server/src/com/vaadin/server/communication/PushHandler.java
index 09428e47a9..99aff3780f 100644
--- a/server/src/com/vaadin/server/communication/PushHandler.java
+++ b/server/src/com/vaadin/server/communication/PushHandler.java
@@ -19,7 +19,6 @@ package com.vaadin.server.communication;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
-import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -43,7 +42,6 @@ import com.vaadin.server.VaadinService;
import com.vaadin.server.VaadinServletRequest;
import com.vaadin.server.VaadinServletService;
import com.vaadin.server.VaadinSession;
-import com.vaadin.server.WebBrowser;
import com.vaadin.shared.ApplicationConstants;
import com.vaadin.shared.communication.PushMode;
import com.vaadin.ui.UI;
@@ -75,8 +73,8 @@ public class PushHandler extends AtmosphereResourceEventListenerAdapter
@Override
public void run(AtmosphereResource resource, UI ui) throws IOException {
getLogger().log(Level.FINER,
- "New push connection with transport {0}",
- resource.transport());
+ "New push connection for resource {0} with transport {1}",
+ new Object[] { resource.uuid(), resource.transport() });
resource.addEventListener(PushHandler.this);
@@ -84,14 +82,6 @@ public class PushHandler extends AtmosphereResourceEventListenerAdapter
VaadinSession session = ui.getSession();
if (resource.transport() == TRANSPORT.STREAMING) {
- // IE8 requires a longer padding to work properly if the
- // initial message is small (#11573). Chrome does not work
- // without the original padding...
- WebBrowser browser = session.getBrowser();
- if (browser.isIE() && browser.getBrowserMajorVersion() == 8) {
- resource.padding(LONG_PADDING);
- }
-
// Must ensure that the streaming response contains
// "Connection: close", otherwise iOS 6 will wait for the
// response to this request before sending another request to
@@ -115,10 +105,9 @@ public class PushHandler extends AtmosphereResourceEventListenerAdapter
resource.suspend();
- AtmospherePushConnection connection = new AtmospherePushConnection(
- ui, resource);
-
- ui.setPushConnection(connection);
+ AtmospherePushConnection connection = getConnectionForUI(ui);
+ assert (connection != null);
+ connection.connect(resource);
}
};
@@ -182,11 +171,11 @@ public class PushHandler extends AtmosphereResourceEventListenerAdapter
@Override
public void run(AtmosphereResource resource, UI ui) throws IOException {
PushMode pushMode = ui.getPushConfiguration().getPushMode();
- AtmospherePushConnection pushConnection = getConnectionForUI(ui);
+ AtmospherePushConnection connection = getConnectionForUI(ui);
String id = resource.uuid();
- if (pushConnection == null) {
+ if (connection == null) {
getLogger()
.log(Level.WARNING,
"Could not find push connection to close: {0} with transport {1}",
@@ -209,19 +198,11 @@ public class PushHandler extends AtmosphereResourceEventListenerAdapter
"Connection unexpectedly closed for resource {0} with transport {1}",
new Object[] { id, resource.transport() });
}
- ui.setPushConnection(null);
+ connection.disconnect();
}
}
};
- private static final String LONG_PADDING;
-
- static {
- char[] array = new char[4096];
- Arrays.fill(array, '-');
- LONG_PADDING = String.copyValueOf(array);
-
- }
private VaadinServletService service;
public PushHandler(VaadinServletService service) {
@@ -351,10 +332,10 @@ public class PushHandler extends AtmosphereResourceEventListenerAdapter
private static AtmospherePushConnection getConnectionForUI(UI ui) {
PushConnection pushConnection = ui.getPushConnection();
if (pushConnection instanceof AtmospherePushConnection) {
- assert pushConnection.isConnected();
return (AtmospherePushConnection) pushConnection;
+ } else {
+ return null;
}
- return null;
}
@Override
@@ -391,7 +372,7 @@ public class PushHandler extends AtmosphereResourceEventListenerAdapter
break;
case JSONP:
case LONG_POLLING:
- resource.resume();
+ disconnect(event);
break;
default:
getLogger().log(Level.SEVERE, "Unknown transport {0}",
@@ -415,13 +396,6 @@ public class PushHandler extends AtmosphereResourceEventListenerAdapter
}
@Override
- public void onResume(AtmosphereResourceEvent event) {
- // Log event on trace level
- super.onResume(event);
- disconnect(event);
- }
-
- @Override
public void destroy() {
}
@@ -444,8 +418,8 @@ public class PushHandler extends AtmosphereResourceEventListenerAdapter
*/
private static void sendRefreshAndDisconnect(AtmosphereResource resource)
throws IOException {
- AtmospherePushConnection connection = new AtmospherePushConnection(
- null, resource);
+ AtmospherePushConnection connection = new AtmospherePushConnection(null);
+ connection.connect(resource);
try {
connection.sendMessage(VaadinService
.createCriticalNotificationJSON(null, null, null, null));
diff --git a/server/src/com/vaadin/server/communication/PushRequestHandler.java b/server/src/com/vaadin/server/communication/PushRequestHandler.java
index 272dd8e05c..aec3aa54c0 100644
--- a/server/src/com/vaadin/server/communication/PushRequestHandler.java
+++ b/server/src/com/vaadin/server/communication/PushRequestHandler.java
@@ -29,6 +29,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;
@@ -75,6 +77,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 ea25777525..ce9cec5e2a 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 implements Serializable {
+
+ 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/AbstractComponent.java b/server/src/com/vaadin/ui/AbstractComponent.java
index 61bcf00ad8..33aa689a88 100644
--- a/server/src/com/vaadin/ui/AbstractComponent.java
+++ b/server/src/com/vaadin/ui/AbstractComponent.java
@@ -31,13 +31,13 @@ import com.vaadin.event.ConnectorActionManager;
import com.vaadin.event.ShortcutListener;
import com.vaadin.server.AbstractClientConnector;
import com.vaadin.server.ComponentSizeValidator;
-import com.vaadin.server.ErrorHandler;
import com.vaadin.server.ErrorMessage;
import com.vaadin.server.Resource;
import com.vaadin.server.VaadinSession;
import com.vaadin.shared.AbstractComponentState;
import com.vaadin.shared.ComponentConstants;
import com.vaadin.shared.ui.ComponentStateUtil;
+import com.vaadin.ui.Field.ValueChangeEvent;
import com.vaadin.util.ReflectTools;
/**
@@ -85,8 +85,6 @@ public abstract class AbstractComponent extends AbstractClientConnector
private static final Pattern sizePattern = Pattern
.compile("^(-?\\d+(\\.\\d+)?)(%|px|em|rem|ex|in|cm|mm|pt|pc)?$");
- private ErrorHandler errorHandler = null;
-
/**
* Keeps track of the Actions added to this component; the actual
* handling/notifying is delegated, usually to the containing window.
@@ -97,6 +95,8 @@ public abstract class AbstractComponent extends AbstractClientConnector
private HasComponents parent;
+ private Boolean explicitImmediateValue;
+
/* Constructor */
/**
@@ -360,25 +360,29 @@ public abstract class AbstractComponent extends AbstractClientConnector
}
}
- /*
- * Tests if the component is in the immediate mode. Don't add a JavaDoc
- * comment here, we use the default documentation from implemented
- * interface.
- */
public boolean isImmediate() {
- return getState(false).immediate;
+ if (explicitImmediateValue != null) {
+ return explicitImmediateValue;
+ } else if (hasListeners(ValueChangeEvent.class)) {
+ /*
+ * Automatic immediate for fields that developers are interested
+ * about.
+ */
+ return true;
+ } else {
+ return false;
+ }
}
/**
- * Sets the component's immediate mode to the specified status. This method
- * will trigger a {@link RepaintRequestEvent}.
+ * Sets the component's immediate mode to the specified status.
*
* @param immediate
* the boolean value specifying if the component should be in the
* immediate mode after the call.
- * @see Component#isImmediate()
*/
public void setImmediate(boolean immediate) {
+ explicitImmediateValue = immediate;
getState().immediate = immediate;
}
@@ -675,6 +679,8 @@ public abstract class AbstractComponent extends AbstractClientConnector
} else {
getState().errorMessage = null;
}
+
+ getState().immediate = isImmediate();
}
/* General event framework */
diff --git a/server/src/com/vaadin/ui/AbstractField.java b/server/src/com/vaadin/ui/AbstractField.java
index b96e331889..300e130c4e 100644
--- a/server/src/com/vaadin/ui/AbstractField.java
+++ b/server/src/com/vaadin/ui/AbstractField.java
@@ -1086,6 +1086,8 @@ public abstract class AbstractField<T> extends AbstractComponent implements
public void addValueChangeListener(Property.ValueChangeListener listener) {
addListener(AbstractField.ValueChangeEvent.class, listener,
VALUE_CHANGE_METHOD);
+ // ensure "automatic immediate handling" works
+ markAsDirty();
}
/**
@@ -1107,6 +1109,8 @@ public abstract class AbstractField<T> extends AbstractComponent implements
public void removeValueChangeListener(Property.ValueChangeListener listener) {
removeListener(AbstractField.ValueChangeEvent.class, listener,
VALUE_CHANGE_METHOD);
+ // ensure "automatic immediate handling" works
+ markAsDirty();
}
/**
diff --git a/server/src/com/vaadin/ui/AbstractSelect.java b/server/src/com/vaadin/ui/AbstractSelect.java
index 556b16943f..a32d40b11d 100644
--- a/server/src/com/vaadin/ui/AbstractSelect.java
+++ b/server/src/com/vaadin/ui/AbstractSelect.java
@@ -878,6 +878,37 @@ public abstract class AbstractSelect extends AbstractField<Object> implements
return retval;
}
+ /**
+ * Adds given items with given item ids to container.
+ *
+ * @since 7.2
+ * @param itemId
+ * item identifiers to be added to underlying container
+ * @throws UnsupportedOperationException
+ * if the underlying container don't support adding items with
+ * identifiers
+ */
+ public void addItems(Object... itemId) throws UnsupportedOperationException {
+ for (Object id : itemId) {
+ addItem(id);
+ }
+ }
+
+ /**
+ * Adds given items with given item ids to container.
+ *
+ * @since 7.2
+ * @param itemIds
+ * item identifiers to be added to underlying container
+ * @throws UnsupportedOperationException
+ * if the underlying container don't support adding items with
+ * identifiers
+ */
+ public void addItems(Collection<Object> itemIds)
+ throws UnsupportedOperationException {
+ addItems(itemIds.toArray());
+ }
+
/*
* (non-Javadoc)
*
diff --git a/server/src/com/vaadin/ui/Button.java b/server/src/com/vaadin/ui/Button.java
index 1bcf802f12..765c805d37 100644
--- a/server/src/com/vaadin/ui/Button.java
+++ b/server/src/com/vaadin/ui/Button.java
@@ -100,6 +100,31 @@ public class Button extends AbstractComponent implements
}
/**
+ * Creates a new push button with the given icon.
+ *
+ * @param icon
+ * the icon
+ */
+ public Button(Resource icon) {
+ this();
+ setIcon(icon);
+ }
+
+ /**
+ * Creates a new push button with the given caption and icon.
+ *
+ * @param caption
+ * the caption
+ * @param icon
+ * the icon
+ */
+ public Button(String caption, Resource icon) {
+ this();
+ setCaption(caption);
+ setIcon(icon);
+ }
+
+ /**
* Creates a new push button with a click listener.
*
* @param caption
diff --git a/server/src/com/vaadin/ui/ComboBox.java b/server/src/com/vaadin/ui/ComboBox.java
index 88e895df82..5fb2f81011 100644
--- a/server/src/com/vaadin/ui/ComboBox.java
+++ b/server/src/com/vaadin/ui/ComboBox.java
@@ -56,8 +56,6 @@ public class ComboBox extends AbstractSelect implements
*/
protected int pageLength = 10;
- private int columns = 0;
-
// Current page when the user is 'paging' trough options
private int currentPage = -1;
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/Label.java b/server/src/com/vaadin/ui/Label.java
index d7cee2a80d..71d54fc29a 100644
--- a/server/src/com/vaadin/ui/Label.java
+++ b/server/src/com/vaadin/ui/Label.java
@@ -18,7 +18,6 @@ package com.vaadin.ui;
import java.lang.reflect.Method;
import java.util.Locale;
-import java.util.logging.Logger;
import com.vaadin.data.Property;
import com.vaadin.data.util.AbstractProperty;
@@ -56,9 +55,6 @@ public class Label extends AbstractComponent implements Property<String>,
Property.Viewer, Property.ValueChangeListener,
Property.ValueChangeNotifier, Comparable<Label> {
- private static final Logger logger = Logger
- .getLogger(Label.class.getName());
-
/**
* @deprecated As of 7.0, use {@link ContentMode#TEXT} instead
*/
@@ -190,7 +186,8 @@ public class Label extends AbstractComponent implements Property<String>,
/**
* Set the value of the label. Value of the label is the XML contents of the
- * label.
+ * label. Since Vaadin 7.2, changing the value of Label instance with that
+ * method will fire ValueChangeEvent.
*
* @param newStringValue
* the New value of the label.
@@ -198,7 +195,13 @@ public class Label extends AbstractComponent implements Property<String>,
@Override
public void setValue(String newStringValue) {
if (getPropertyDataSource() == null) {
- getState().text = newStringValue;
+
+ LabelState state = (LabelState) getState(false);
+ String oldTextValue = state.text;
+ if (!SharedUtil.equals(oldTextValue, newStringValue)) {
+ getState().text = newStringValue;
+ fireValueChange();
+ }
} else {
throw new IllegalStateException(
"Label is only a Property.Viewer and cannot update its data source");
@@ -227,7 +230,8 @@ public class Label extends AbstractComponent implements Property<String>,
}
/**
- * Sets the property as data-source for viewing.
+ * Sets the property as data-source for viewing. Since Vaadin 7.2 a
+ * ValueChangeEvent is fired if the new value is different from previous.
*
* @param newDataSource
* the new data source Property
@@ -257,7 +261,7 @@ public class Label extends AbstractComponent implements Property<String>,
if (dataSource != null) {
// Update the value from the data source. If data source was set to
// null, retain the old value
- getState().text = getDataSourceValue();
+ updateValueFromDataSource();
}
// Listens the new data source if possible
@@ -408,7 +412,8 @@ public class Label extends AbstractComponent implements Property<String>,
private void updateValueFromDataSource() {
// Update the internal value from the data source
String newConvertedValue = getDataSourceValue();
- if (!SharedUtil.equals(newConvertedValue, getState().text)) {
+ if (!SharedUtil.equals(newConvertedValue,
+ ((LabelState) getState(false)).text)) {
getState().text = newConvertedValue;
fireValueChange();
}
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/PushConfiguration.java b/server/src/com/vaadin/ui/PushConfiguration.java
index a592b39bef..49738c5aff 100644
--- a/server/src/com/vaadin/ui/PushConfiguration.java
+++ b/server/src/com/vaadin/ui/PushConfiguration.java
@@ -21,6 +21,7 @@ import java.util.Collection;
import java.util.Collections;
import com.vaadin.server.VaadinSession;
+import com.vaadin.server.communication.AtmospherePushConnection;
import com.vaadin.shared.communication.PushMode;
import com.vaadin.shared.ui.ui.Transport;
import com.vaadin.shared.ui.ui.UIState.PushConfigurationState;
@@ -170,20 +171,32 @@ class PushConfigurationImpl implements PushConfiguration {
throw new IllegalArgumentException("Push mode cannot be null");
}
- if (pushMode.isEnabled()) {
- VaadinSession session = ui.getSession();
- if (session != null && !session.getService().ensurePushAvailable()) {
- throw new IllegalStateException(
- "Push is not available. See previous log messages for more information.");
- }
+ VaadinSession session = ui.getSession();
+
+ if (session == null) {
+ throw new UIDetachedException(
+ "Cannot set the push mode for a detached UI");
+ }
+
+ assert session.hasLock();
+
+ if (pushMode.isEnabled() && !session.getService().ensurePushAvailable()) {
+ throw new IllegalStateException(
+ "Push is not available. See previous log messages for more information.");
}
- /*
- * Client-side will open a new connection or disconnect the old
- * connection, so there's nothing more to do on the server at this
- * point.
- */
- getState().mode = pushMode;
+ PushMode oldMode = getState().mode;
+ if (oldMode != pushMode) {
+ getState().mode = pushMode;
+
+ if (!oldMode.isEnabled() && pushMode.isEnabled()) {
+ // The push connection is initially in a disconnected state;
+ // the client will establish the connection
+ ui.setPushConnection(new AtmospherePushConnection(ui));
+ }
+ // Nothing to do here if disabling push;
+ // the client will close the connection
+ }
}
/*
@@ -274,9 +287,8 @@ class PushConfigurationImpl implements PushConfiguration {
@Override
public Collection<String> getParameterNames() {
- return Collections
- .unmodifiableCollection(ui.getState(false).pushConfiguration.parameters
- .keySet());
+ return Collections.unmodifiableCollection(getState(false).parameters
+ .keySet());
}
}
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/Table.java b/server/src/com/vaadin/ui/Table.java
index 32ed738697..06e82dedcb 100644
--- a/server/src/com/vaadin/ui/Table.java
+++ b/server/src/com/vaadin/ui/Table.java
@@ -2165,7 +2165,6 @@ public class Table extends AbstractSelect implements Action.Container,
// more efficient implementation for containers supporting access by
// index
- Container.Indexed indexed = ((Container.Indexed) items);
List<?> itemIds = getItemIds(firstIndex, rows);
for (int i = 0; i < rows && i < itemIds.size(); i++) {
Object id = itemIds.get(i);
diff --git a/server/src/com/vaadin/ui/UI.java b/server/src/com/vaadin/ui/UI.java
index 746fa194ac..e688c06061 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;
@@ -52,6 +55,7 @@ import com.vaadin.server.communication.PushConnection;
import com.vaadin.shared.Connector;
import com.vaadin.shared.EventId;
import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.shared.communication.PushMode;
import com.vaadin.shared.ui.ui.DebugWindowClientRpc;
import com.vaadin.shared.ui.ui.DebugWindowServerRpc;
import com.vaadin.shared.ui.ui.ScrollClientRpc;
@@ -77,7 +81,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 +99,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 +172,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 +224,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.
@@ -418,8 +423,9 @@ public abstract class UI extends AbstractSingleComponentContainer implements
} else {
if (session == null) {
detach();
- // Close the push connection when UI is detached. Otherwise the
+ // Disable push when the UI is detached. Otherwise the
// push connection and possibly VaadinSession will live on.
+ getPushConfiguration().setPushMode(PushMode.DISABLED);
setPushConnection(null);
}
this.session = session;
@@ -546,11 +552,11 @@ public abstract class UI extends AbstractSingleComponentContainer implements
private transient PushConnection pushConnection = null;
- private boolean hasPendingPush = false;
-
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
@@ -1346,6 +1368,9 @@ public abstract class UI extends AbstractSingleComponentContainer implements
* Pushes the pending changes and client RPC invocations of this UI to the
* client-side.
* <p>
+ * If push is enabled, but the push connection is not currently open, the
+ * push will be done when the connection is established.
+ * <p>
* As with all UI methods, the session must be locked when calling this
* method. It is also recommended that {@link UI#getCurrent()} is set up to
* return this UI since writing the response may invoke logic in any
@@ -1363,79 +1388,73 @@ public abstract class UI extends AbstractSingleComponentContainer implements
*/
public void push() {
VaadinSession session = getSession();
- if (session != null) {
- assert session.hasLock();
-
- /*
- * Purge the pending access queue as it might mark a connector as
- * dirty when the push would otherwise be ignored because there are
- * no changes to push.
- */
- session.getService().runPendingAccessTasks(session);
-
- if (!getConnectorTracker().hasDirtyConnectors()) {
- // Do not push if there is nothing to push
- return;
- }
- if (!getPushConfiguration().getPushMode().isEnabled()) {
- throw new IllegalStateException("Push not enabled");
- }
+ if (session == null) {
+ throw new UIDetachedException("Cannot push a detached UI");
+ }
+ assert session.hasLock();
- if (pushConnection == null) {
- hasPendingPush = true;
- } else {
- pushConnection.push();
- }
- } else {
- throw new UIDetachedException("Trying to push a detached UI");
+ if (!getPushConfiguration().getPushMode().isEnabled()) {
+ throw new IllegalStateException("Push not enabled");
}
+ assert pushConnection != null;
+
+ /*
+ * Purge the pending access queue as it might mark a connector as dirty
+ * when the push would otherwise be ignored because there are no changes
+ * to push.
+ */
+ session.getService().runPendingAccessTasks(session);
+
+ if (!getConnectorTracker().hasDirtyConnectors()) {
+ // Do not push if there is nothing to push
+ return;
+ }
+
+ pushConnection.push();
}
/**
* Returns the internal push connection object used by this UI. This method
- * should only be called by the framework. If the returned PushConnection is
- * not null, it is guaranteed to have {@code isConnected() == true}.
+ * should only be called by the framework.
* <p>
* This method is not intended to be overridden. If it is overridden, care
* should be taken since this method might be called in situations where
* {@link UI#getCurrent()} does not return this UI.
*
- * @return the push connection used by this UI, <code>null</code> if there
- * is no active push connection.
+ * @return the push connection used by this UI, or {@code null} if push is
+ * not available.
*/
public PushConnection getPushConnection() {
- assert (pushConnection == null || pushConnection.isConnected());
+ assert !(getPushConfiguration().getPushMode().isEnabled() && pushConnection == null);
return pushConnection;
}
/**
* Sets the internal push connection object used by this UI. This method
- * should only be called by the framework. If {@pushConnection} is not null,
- * its {@code isConnected()} must be true.
+ * should only be called by the framework.
+ * <p>
+ * The {@code pushConnection} argument must be non-null if and only if
+ * {@code getPushConfiguration().getPushMode().isEnabled()}.
*
* @param pushConnection
* the push connection to use for this UI
*/
public void setPushConnection(PushConnection pushConnection) {
- // If pushMode is disabled then there should never be a pushConnection
- assert (pushConnection == null || getPushConfiguration().getPushMode()
- .isEnabled());
- assert (pushConnection == null || pushConnection.isConnected());
+ // If pushMode is disabled then there should never be a pushConnection;
+ // if enabled there should always be
+ assert (pushConnection == null)
+ ^ getPushConfiguration().getPushMode().isEnabled();
if (pushConnection == this.pushConnection) {
return;
}
- if (this.pushConnection != null) {
+ if (this.pushConnection != null && this.pushConnection.isConnected()) {
this.pushConnection.disconnect();
}
this.pushConnection = pushConnection;
- if (pushConnection != null && hasPendingPush) {
- hasPendingPush = false;
- pushConnection.push();
- }
}
/**
@@ -1469,6 +1488,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 +1548,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/Upload.java b/server/src/com/vaadin/ui/Upload.java
index 98f5d2ded9..c8d9f3ff09 100644
--- a/server/src/com/vaadin/ui/Upload.java
+++ b/server/src/com/vaadin/ui/Upload.java
@@ -28,7 +28,10 @@ import com.vaadin.server.NoOutputStreamException;
import com.vaadin.server.PaintException;
import com.vaadin.server.PaintTarget;
import com.vaadin.server.StreamVariable.StreamingProgressEvent;
+import com.vaadin.shared.EventId;
import com.vaadin.shared.ui.upload.UploadClientRpc;
+import com.vaadin.shared.ui.upload.UploadServerRpc;
+import com.vaadin.util.ReflectTools;
/**
* Component for uploading files from client to server.
@@ -113,9 +116,16 @@ public class Upload extends AbstractComponent implements Component.Focusable,
* The receiver must be set before performing an upload.
*/
public Upload() {
+ registerRpc(new UploadServerRpc() {
+ @Override
+ public void change(String filename) {
+ fireEvent(new ChangeEvent(Upload.this, filename));
+ }
+ });
}
public Upload(String caption, Receiver uploadReceiver) {
+ this();
setCaption(caption);
receiver = uploadReceiver;
}
@@ -486,6 +496,42 @@ public class Upload extends AbstractComponent implements Component.Focusable,
}
/**
+ * Upload.ChangeEvent event is sent when the value (filename) of the upload
+ * changes.
+ *
+ * @since 7.2
+ */
+ public static class ChangeEvent extends Component.Event {
+
+ private final String filename;
+
+ public ChangeEvent(Upload source, String filename) {
+ super(source);
+ this.filename = filename;
+ }
+
+ /**
+ * Uploads where the event occurred.
+ *
+ * @return the Source of the event.
+ */
+ @Override
+ public Upload getSource() {
+ return (Upload) super.getSource();
+ }
+
+ /**
+ * Gets the file name.
+ *
+ * @return the filename.
+ */
+ public String getFilename() {
+ return filename;
+ }
+
+ }
+
+ /**
* Receives the events when the upload starts.
*
* @author Vaadin Ltd.
@@ -554,6 +600,25 @@ public class Upload extends AbstractComponent implements Component.Focusable,
}
/**
+ * Listener for {@link ChangeEvent}
+ *
+ * @since 7.2
+ */
+ public interface ChangeListener extends Serializable {
+
+ Method FILENAME_CHANGED = ReflectTools.findMethod(ChangeListener.class,
+ "filenameChanged", ChangeEvent.class);
+
+ /**
+ * A file has been selected but upload has not yet started.
+ *
+ * @param event
+ * the change event
+ */
+ public void filenameChanged(ChangeEvent event);
+ }
+
+ /**
* Adds the upload started event listener.
*
* @param listener
@@ -740,6 +805,27 @@ public class Upload extends AbstractComponent implements Component.Focusable,
}
/**
+ * Adds a filename change event listener
+ *
+ * @param listener
+ * the Listener to add
+ */
+ public void addChangeListener(ChangeListener listener) {
+ super.addListener(EventId.CHANGE, ChangeEvent.class, listener,
+ ChangeListener.FILENAME_CHANGED);
+ }
+
+ /**
+ * Removes a filename change event listener
+ *
+ * @param listener
+ * the listener to be removed
+ */
+ public void removeChangeListener(ChangeListener listener) {
+ super.removeListener(EventId.CHANGE, ChangeEvent.class, listener);
+ }
+
+ /**
* @deprecated As of 7.0, replaced by
* {@link #removeProgressListener(ProgressListener)}
**/
@@ -1040,7 +1126,11 @@ public class Upload extends AbstractComponent implements Component.Focusable,
@Override
public OutputStream getOutputStream() {
- OutputStream receiveUpload = receiver.receiveUpload(
+ if (getReceiver() == null) {
+ throw new IllegalStateException(
+ "Upload cannot be performed without a receiver set");
+ }
+ OutputStream receiveUpload = getReceiver().receiveUpload(
lastStartedEvent.getFileName(),
lastStartedEvent.getMimeType());
lastStartedEvent = null;
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 684ab5d6bc..a6c3bb0b8b 100644
--- a/server/tests/src/com/vaadin/data/util/BeanContainerTest.java
+++ b/server/tests/src/com/vaadin/data/util/BeanContainerTest.java
@@ -466,6 +466,8 @@ public class BeanContainerTest extends AbstractBeanContainerTest {
assertTrue(container
.addNestedContainerProperty("address.postalCodeObject"));
assertTrue(container.addNestedContainerProperty("address.street"));
+ // the nested properties added with allowNullBean setting should return
+ // null
assertNull(container.getContainerProperty("John", "address.street")
.getValue());
}
diff --git a/server/tests/src/com/vaadin/data/util/BeanItemContainerTest.java b/server/tests/src/com/vaadin/data/util/BeanItemContainerTest.java
index 767a9e2e4d..b9633753b4 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.
@@ -723,7 +730,186 @@ public class BeanItemContainerTest extends AbstractBeanContainerTest {
assertTrue(container
.addNestedContainerProperty("address.postalCodeObject"));
assertTrue(container.addNestedContainerProperty("address.street"));
+ // the nested properties should return null
assertNull(container.getContainerProperty(john, "address.street")
.getValue());
}
+
+ public void testItemAddedEvent() {
+ BeanItemContainer<Person> container = new BeanItemContainer<Person>(
+ Person.class);
+ Person bean = new Person("John");
+ ItemSetChangeListener addListener = createListenerMockFor(container);
+ addListener.containerItemSetChange(EasyMock.isA(ItemAddEvent.class));
+ EasyMock.replay(addListener);
+
+ container.addItem(bean);
+
+ EasyMock.verify(addListener);
+ }
+
+ public void testItemAddedEvent_AddedItem() {
+ BeanItemContainer<Person> container = new BeanItemContainer<Person>(
+ Person.class);
+ Person bean = new Person("John");
+ ItemSetChangeListener addListener = createListenerMockFor(container);
+ Capture<ItemAddEvent> capturedEvent = captureAddEvent(addListener);
+ EasyMock.replay(addListener);
+
+ container.addItem(bean);
+
+ assertEquals(bean, capturedEvent.getValue().getFirstItemId());
+ }
+
+ public void testItemAddedEvent_addItemAt_IndexOfAddedItem() {
+ BeanItemContainer<Person> container = new BeanItemContainer<Person>(
+ Person.class);
+ Person bean = new Person("John");
+ container.addItem(bean);
+ ItemSetChangeListener addListener = createListenerMockFor(container);
+ Capture<ItemAddEvent> capturedEvent = captureAddEvent(addListener);
+ EasyMock.replay(addListener);
+
+ container.addItemAt(1, new Person(""));
+
+ assertEquals(1, capturedEvent.getValue().getFirstIndex());
+ }
+
+ public void testItemAddedEvent_addItemAfter_IndexOfAddedItem() {
+ BeanItemContainer<Person> container = new BeanItemContainer<Person>(
+ Person.class);
+ Person bean = new Person("John");
+ container.addItem(bean);
+ ItemSetChangeListener addListener = createListenerMockFor(container);
+ Capture<ItemAddEvent> capturedEvent = captureAddEvent(addListener);
+ EasyMock.replay(addListener);
+
+ container.addItemAfter(bean, new Person(""));
+
+ assertEquals(1, capturedEvent.getValue().getFirstIndex());
+ }
+
+ public void testItemAddedEvent_amountOfAddedItems() {
+ BeanItemContainer<Person> container = new BeanItemContainer<Person>(
+ Person.class);
+ ItemSetChangeListener addListener = createListenerMockFor(container);
+ Capture<ItemAddEvent> capturedEvent = captureAddEvent(addListener);
+ EasyMock.replay(addListener);
+ List<Person> beans = Arrays.asList(new Person("Jack"), new Person(
+ "John"));
+
+ container.addAll(beans);
+
+ assertEquals(2, capturedEvent.getValue().getAddedItemsCount());
+ }
+
+ public void testItemAddedEvent_someItemsAreFiltered_amountOfAddedItemsIsReducedByAmountOfFilteredItems() {
+ BeanItemContainer<Person> container = new BeanItemContainer<Person>(
+ Person.class);
+ ItemSetChangeListener addListener = createListenerMockFor(container);
+ Capture<ItemAddEvent> capturedEvent = captureAddEvent(addListener);
+ EasyMock.replay(addListener);
+ List<Person> beans = Arrays.asList(new Person("Jack"), new Person(
+ "John"));
+ container.addFilter(new Compare.Equal("name", "John"));
+
+ container.addAll(beans);
+
+ assertEquals(1, capturedEvent.getValue().getAddedItemsCount());
+ }
+
+ public void testItemAddedEvent_someItemsAreFiltered_addedItemIsTheFirstVisibleItem() {
+ BeanItemContainer<Person> container = new BeanItemContainer<Person>(
+ Person.class);
+ Person bean = new Person("John");
+ ItemSetChangeListener addListener = createListenerMockFor(container);
+ Capture<ItemAddEvent> capturedEvent = captureAddEvent(addListener);
+ EasyMock.replay(addListener);
+ List<Person> beans = Arrays.asList(new Person("Jack"), bean);
+ container.addFilter(new Compare.Equal("name", "John"));
+
+ container.addAll(beans);
+
+ assertEquals(bean, capturedEvent.getValue().getFirstItemId());
+ }
+
+ public void testItemRemovedEvent() {
+ BeanItemContainer<Person> container = new BeanItemContainer<Person>(
+ Person.class);
+ Person bean = new Person("John");
+ container.addItem(bean);
+ ItemSetChangeListener removeListener = createListenerMockFor(container);
+ removeListener.containerItemSetChange(EasyMock
+ .isA(ItemRemoveEvent.class));
+ EasyMock.replay(removeListener);
+
+ container.removeItem(bean);
+
+ EasyMock.verify(removeListener);
+ }
+
+ public void testItemRemovedEvent_RemovedItem() {
+ BeanItemContainer<Person> container = new BeanItemContainer<Person>(
+ Person.class);
+ Person bean = new Person("John");
+ container.addItem(bean);
+ ItemSetChangeListener removeListener = createListenerMockFor(container);
+ Capture<ItemRemoveEvent> capturedEvent = captureRemoveEvent(removeListener);
+ EasyMock.replay(removeListener);
+
+ container.removeItem(bean);
+
+ assertEquals(bean, capturedEvent.getValue().getFirstItemId());
+ }
+
+ public void testItemRemovedEvent_indexOfRemovedItem() {
+ BeanItemContainer<Person> container = new BeanItemContainer<Person>(
+ Person.class);
+ container.addItem(new Person("Jack"));
+ Person secondBean = new Person("John");
+ container.addItem(secondBean);
+ ItemSetChangeListener removeListener = createListenerMockFor(container);
+ Capture<ItemRemoveEvent> capturedEvent = captureRemoveEvent(removeListener);
+ EasyMock.replay(removeListener);
+
+ container.removeItem(secondBean);
+
+ assertEquals(1, capturedEvent.getValue().getFirstIndex());
+ }
+
+ public void testItemRemovedEvent_amountOfRemovedItems() {
+ BeanItemContainer<Person> container = new BeanItemContainer<Person>(
+ Person.class);
+ container.addItem(new Person("Jack"));
+ container.addItem(new Person("John"));
+ ItemSetChangeListener removeListener = createListenerMockFor(container);
+ Capture<ItemRemoveEvent> capturedEvent = captureRemoveEvent(removeListener);
+ EasyMock.replay(removeListener);
+
+ container.removeAllItems();
+
+ assertEquals(2, capturedEvent.getValue().getRemovedItemsCount());
+ }
+
+ private Capture<ItemAddEvent> captureAddEvent(
+ ItemSetChangeListener addListener) {
+ Capture<ItemAddEvent> capturedEvent = new Capture<ItemAddEvent>();
+ addListener.containerItemSetChange(EasyMock.capture(capturedEvent));
+ return capturedEvent;
+ }
+
+ private Capture<ItemRemoveEvent> captureRemoveEvent(
+ ItemSetChangeListener removeListener) {
+ Capture<ItemRemoveEvent> capturedEvent = new Capture<ItemRemoveEvent>();
+ removeListener.containerItemSetChange(EasyMock.capture(capturedEvent));
+ return capturedEvent;
+ }
+
+ private ItemSetChangeListener createListenerMockFor(
+ BeanItemContainer<Person> container) {
+ ItemSetChangeListener listener = EasyMock
+ .createNiceMock(ItemSetChangeListener.class);
+ container.addItemSetChangeListener(listener);
+ return listener;
+ }
}
diff --git a/server/tests/src/com/vaadin/data/util/TestIndexedContainer.java b/server/tests/src/com/vaadin/data/util/TestIndexedContainer.java
index 09e5a26c15..5da0bdc8a2 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);
+
+ 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/data/util/sqlcontainer/SQLContainerTest.java b/server/tests/src/com/vaadin/data/util/sqlcontainer/SQLContainerTest.java
index 844ef705b1..4c132eba30 100644
--- a/server/tests/src/com/vaadin/data/util/sqlcontainer/SQLContainerTest.java
+++ b/server/tests/src/com/vaadin/data/util/sqlcontainer/SQLContainerTest.java
@@ -1917,7 +1917,6 @@ public class SQLContainerTest {
EasyMock.expect(delegate.getCountStatement())
.andAnswer(new IAnswer<StatementHelper>() {
@Override
- @SuppressWarnings("deprecation")
public StatementHelper answer() throws Throwable {
StatementHelper sh = new StatementHelper();
StringBuffer query = new StringBuffer(
@@ -1990,7 +1989,6 @@ public class SQLContainerTest {
EasyMock.expect(delegate.getCountStatement())
.andAnswer(new IAnswer<StatementHelper>() {
@Override
- @SuppressWarnings("deprecation")
public StatementHelper answer() throws Throwable {
StatementHelper sh = new StatementHelper();
StringBuffer query = new StringBuffer(
@@ -2134,7 +2132,6 @@ public class SQLContainerTest {
EasyMock.expect(delegate.getCountStatement())
.andAnswer(new IAnswer<StatementHelper>() {
@Override
- @SuppressWarnings("deprecation")
public StatementHelper answer() throws Throwable {
StatementHelper sh = new StatementHelper();
StringBuffer query = new StringBuffer(
@@ -2213,7 +2210,6 @@ public class SQLContainerTest {
EasyMock.expect(delegate.getCountStatement())
.andAnswer(new IAnswer<StatementHelper>() {
@Override
- @SuppressWarnings("deprecation")
public StatementHelper answer() throws Throwable {
StatementHelper sh = new StatementHelper();
StringBuffer query = new StringBuffer(
@@ -2292,7 +2288,6 @@ public class SQLContainerTest {
EasyMock.expect(delegate.getCountStatement())
.andAnswer(new IAnswer<StatementHelper>() {
@Override
- @SuppressWarnings("deprecation")
public StatementHelper answer() throws Throwable {
StatementHelper sh = new StatementHelper();
StringBuffer query = new StringBuffer(
diff --git a/server/tests/src/com/vaadin/data/util/sqlcontainer/TicketTests.java b/server/tests/src/com/vaadin/data/util/sqlcontainer/TicketTests.java
index 8e8c83d234..110225e206 100644
--- a/server/tests/src/com/vaadin/data/util/sqlcontainer/TicketTests.java
+++ b/server/tests/src/com/vaadin/data/util/sqlcontainer/TicketTests.java
@@ -88,7 +88,6 @@ public class TicketTests {
EasyMock.expect(delegate.getCountStatement())
.andAnswer(new IAnswer<StatementHelper>() {
@Override
- @SuppressWarnings("deprecation")
public StatementHelper answer() throws Throwable {
StatementHelper sh = new StatementHelper();
StringBuffer query = new StringBuffer(
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/TestClassesSerializable.java b/server/tests/src/com/vaadin/tests/server/TestClassesSerializable.java
index e5420b8921..0a36c7f7ce 100644
--- a/server/tests/src/com/vaadin/tests/server/TestClassesSerializable.java
+++ b/server/tests/src/com/vaadin/tests/server/TestClassesSerializable.java
@@ -42,6 +42,7 @@ public class TestClassesSerializable extends TestCase {
"com\\.vaadin\\.event\\.FieldEvents", //
"com\\.vaadin\\.event\\.LayoutEvents", //
"com\\.vaadin\\.event\\.MouseEvents", //
+ "com\\.vaadin\\.event\\.UIEvents", //
"com\\.vaadin\\.server\\.VaadinPortlet", //
"com\\.vaadin\\.server\\.MockServletConfig", //
"com\\.vaadin\\.server\\.MockServletContext", //
@@ -49,6 +50,8 @@ public class TestClassesSerializable extends TestCase {
"com\\.vaadin\\.server\\.communication\\.FileUploadHandler\\$SimpleMultiPartInputStream", //
"com\\.vaadin\\.server\\.communication\\.PushRequestHandler.*",
"com\\.vaadin\\.server\\.communication\\.PushHandler.*", // PushHandler
+ "com\\.vaadin\\.server\\.communication\\.DateSerializer", //
+ "com\\.vaadin\\.server\\.communication\\.JSONSerializer", //
// and its inner classes do not need to be serializable
"com\\.vaadin\\.util\\.SerializerHelper", // fully static
// class level filtering, also affecting nested classes and
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/component/abstractselect/TestVarargsItemAddition.java b/server/tests/src/com/vaadin/tests/server/component/abstractselect/TestVarargsItemAddition.java
new file mode 100644
index 0000000000..5575b8fd3d
--- /dev/null
+++ b/server/tests/src/com/vaadin/tests/server/component/abstractselect/TestVarargsItemAddition.java
@@ -0,0 +1,26 @@
+package com.vaadin.tests.server.component.abstractselect;
+
+import java.util.Collection;
+
+import junit.framework.TestCase;
+
+import org.junit.Assert;
+
+import com.vaadin.ui.OptionGroup;
+
+public class TestVarargsItemAddition extends TestCase {
+
+ public void itemAddition() throws Exception {
+
+ OptionGroup optionGroup = new OptionGroup();
+
+ optionGroup.addItems("foo", "bar", "car");
+
+ Collection<?> itemIds = optionGroup.getItemIds();
+ Assert.assertEquals(3, itemIds.size());
+ Assert.assertTrue(itemIds.contains("foo"));
+ Assert.assertTrue(itemIds.contains("bar"));
+ Assert.assertTrue(itemIds.contains("car"));
+
+ }
+}
diff --git a/server/tests/src/com/vaadin/tests/server/component/label/LabelListeners.java b/server/tests/src/com/vaadin/tests/server/component/label/LabelListeners.java
index 3ed79f5010..9bb4c53ba2 100644
--- a/server/tests/src/com/vaadin/tests/server/component/label/LabelListeners.java
+++ b/server/tests/src/com/vaadin/tests/server/component/label/LabelListeners.java
@@ -1,5 +1,14 @@
package com.vaadin.tests.server.component.label;
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.createStrictMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+
+import org.easymock.EasyMock;
+
+import com.vaadin.data.Property;
import com.vaadin.data.Property.ValueChangeListener;
import com.vaadin.tests.server.component.AbstractListenerMethodsTest;
import com.vaadin.ui.Label;
@@ -10,4 +19,74 @@ public class LabelListeners extends AbstractListenerMethodsTest {
testListenerAddGetRemove(Label.class, ValueChangeEvent.class,
ValueChangeListener.class);
}
+
+ public void testValueChangeFiredWhenSettingValue() {
+ Label underTest = new Label();
+
+ // setup the mock listener
+ ValueChangeListener mockListener = createStrictMock(ValueChangeListener.class);
+ // record
+ mockListener
+ .valueChange(anyObject(com.vaadin.data.Property.ValueChangeEvent.class));
+
+ // test
+ underTest.addValueChangeListener(mockListener);
+
+ replay(mockListener);
+ underTest.setValue("A new value");
+
+ verify(mockListener);
+
+ }
+
+ public void testValueChangeFiredWhenSettingPropertyDataSource() {
+ // setup
+ Label underTest = new Label();
+
+ Property mockProperty = EasyMock.createMock(Property.class);
+
+ ValueChangeListener mockListener = createStrictMock(ValueChangeListener.class);
+ // record
+ mockListener
+ .valueChange(anyObject(com.vaadin.data.Property.ValueChangeEvent.class));
+
+ expect(mockProperty.getType()).andReturn(String.class).atLeastOnce();
+ expect(mockProperty.getValue()).andReturn("Any").atLeastOnce();
+
+ // test
+
+ replay(mockListener, mockProperty);
+ underTest.addValueChangeListener(mockListener);
+ underTest.setPropertyDataSource(mockProperty);
+
+ verify(mockListener);
+
+ }
+
+ public void testValueChangeNotFiredWhenNotSettingValue() {
+ Label underTest = new Label();
+ // setup the mock listener
+ ValueChangeListener mockListener = createStrictMock(ValueChangeListener.class);
+ // record: nothing to record
+
+ // test
+ underTest.addValueChangeListener(mockListener);
+ replay(mockListener);
+ verify(mockListener);
+ }
+
+ public void testNoValueChangeFiredWhenSettingPropertyDataSourceToNull() {
+ Label underTest = new Label();
+ // setup the mock Listener
+ ValueChangeListener mockListener = createStrictMock(ValueChangeListener.class);
+ // record: nothing to record
+
+ // test
+ underTest.addValueChangeListener(mockListener);
+ underTest.setPropertyDataSource(null);
+
+ replay(mockListener);
+ verify(mockListener);
+ }
+
}
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>(