aboutsummaryrefslogtreecommitdiffstats
path: root/server/src
diff options
context:
space:
mode:
Diffstat (limited to 'server/src')
-rw-r--r--server/src/com/vaadin/annotations/Widgetset.java2
-rw-r--r--server/src/com/vaadin/data/util/AbstractBeanContainer.java57
-rw-r--r--server/src/com/vaadin/data/util/BeanItem.java21
-rw-r--r--server/src/com/vaadin/data/util/NestedMethodProperty.java59
-rw-r--r--server/src/com/vaadin/data/util/NestedPropertyDescriptor.java24
-rw-r--r--server/src/com/vaadin/data/util/converter/DefaultConverterFactory.java5
-rw-r--r--server/src/com/vaadin/data/util/converter/StringToBigDecimalConverter.java60
-rw-r--r--server/src/com/vaadin/data/util/converter/StringToNumberConverter.java65
-rw-r--r--server/src/com/vaadin/data/validator/BeanValidator.java19
-rw-r--r--server/src/com/vaadin/server/BootstrapHandler.java13
-rw-r--r--server/src/com/vaadin/server/Page.java16
-rw-r--r--server/src/com/vaadin/server/ServiceDestroyEvent.java50
-rw-r--r--server/src/com/vaadin/server/ServiceDestroyListener.java39
-rw-r--r--server/src/com/vaadin/server/SynchronizedRequestHandler.java25
-rw-r--r--server/src/com/vaadin/server/SystemMessages.java12
-rw-r--r--server/src/com/vaadin/server/VaadinPortlet.java6
-rw-r--r--server/src/com/vaadin/server/VaadinService.java70
-rw-r--r--server/src/com/vaadin/server/VaadinServlet.java86
-rw-r--r--server/src/com/vaadin/server/VaadinSession.java72
-rw-r--r--server/src/com/vaadin/server/communication/FileUploadHandler.java25
-rw-r--r--server/src/com/vaadin/server/communication/HeartbeatHandler.java9
-rw-r--r--server/src/com/vaadin/server/communication/PushRequestHandler.java9
-rw-r--r--server/src/com/vaadin/server/communication/UIInitHandler.java77
-rw-r--r--server/src/com/vaadin/server/communication/UidlRequestHandler.java8
-rw-r--r--server/src/com/vaadin/ui/Component.java6
-rw-r--r--server/src/com/vaadin/ui/DragAndDropWrapper.java2
-rw-r--r--server/src/com/vaadin/ui/Notification.java150
-rw-r--r--server/src/com/vaadin/ui/NotificationConfiguration.java269
-rw-r--r--server/src/com/vaadin/ui/TabSheet.java180
-rw-r--r--server/src/com/vaadin/ui/UI.java39
-rw-r--r--server/src/com/vaadin/ui/Window.java194
31 files changed, 1427 insertions, 242 deletions
diff --git a/server/src/com/vaadin/annotations/Widgetset.java b/server/src/com/vaadin/annotations/Widgetset.java
index 40276c18a2..006bf59acf 100644
--- a/server/src/com/vaadin/annotations/Widgetset.java
+++ b/server/src/com/vaadin/annotations/Widgetset.java
@@ -24,7 +24,7 @@ import java.lang.annotation.Target;
import com.vaadin.ui.UI;
/**
- * Defines a specific theme for a {@link UI}.
+ * Defines a specific widgetset for a {@link UI}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
diff --git a/server/src/com/vaadin/data/util/AbstractBeanContainer.java b/server/src/com/vaadin/data/util/AbstractBeanContainer.java
index 35403d6419..cd5c0c809d 100644
--- a/server/src/com/vaadin/data/util/AbstractBeanContainer.java
+++ b/server/src/com/vaadin/data/util/AbstractBeanContainer.java
@@ -845,8 +845,32 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends
* @return true if the property was added
*/
public boolean addNestedContainerProperty(String propertyId) {
+ return addNestedContainerProperty(propertyId, false);
+ }
+
+ /**
+ * Adds a nested container property for the container, e.g.
+ * "manager.address.street".
+ *
+ * All intermediate getters must exist and must return non-null values when
+ * the property value is accessed or the <code>nullBeansAllowed</code> must
+ * be set to true. If the <code>nullBeansAllowed</code> flag is set to true,
+ * calling getValue of the added property will return null if the property
+ * or any of its intermediate getters returns null. If set to false, null
+ * values returned by intermediate getters will cause NullPointerException.
+ * The default value is false to ensure backwards compatibility.
+ *
+ * @see NestedMethodProperty
+ *
+ * @param propertyId
+ * @param nullBeansAllowed
+ * set true to allow null values from intermediate getters
+ * @return true if the property was added
+ */
+ public boolean addNestedContainerProperty(String propertyId,
+ boolean nullBeansAllowed) {
return addContainerProperty(propertyId, new NestedPropertyDescriptor(
- propertyId, type));
+ propertyId, type, nullBeansAllowed));
}
/**
@@ -864,13 +888,42 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends
*/
@SuppressWarnings("unchecked")
public void addNestedContainerBean(String propertyId) {
+ addNestedContainerBean(propertyId, false);
+ }
+
+ /**
+ * Adds a nested container properties for all sub-properties of a named
+ * property to the container. The named property itself is removed from the
+ * model as its subproperties are added.
+ *
+ * Unless
+ * <code>nullBeansAllowed<code> is set to true, all intermediate getters must
+ * exist and must return non-null values when the property values are
+ * accessed. If the <code>nullBeansAllowed</code> flag is set to true,
+ * calling getValue of the added subproperties will return null if the
+ * property or any of their intermediate getters returns null. If set to
+ * false, null values returned by intermediate getters will cause
+ * NullPointerException. The default value is false to ensure backwards
+ * compatibility.
+ *
+ * @see NestedMethodProperty
+ * @see #addNestedContainerProperty(String)
+ *
+ * @param propertyId
+ * @param nullBeansAllowed
+ * set true to allow null values from intermediate getters
+ */
+ @SuppressWarnings("unchecked")
+ public void addNestedContainerBean(String propertyId,
+ boolean nullBeansAllowed) {
Class<?> propertyType = getType(propertyId);
LinkedHashMap<String, VaadinPropertyDescriptor<Object>> pds = BeanItem
.getPropertyDescriptors((Class<Object>) propertyType);
for (String subPropertyId : pds.keySet()) {
String qualifiedPropertyId = propertyId + "." + subPropertyId;
NestedPropertyDescriptor<BEANTYPE> pd = new NestedPropertyDescriptor<BEANTYPE>(
- qualifiedPropertyId, (Class<BEANTYPE>) type);
+ qualifiedPropertyId, (Class<BEANTYPE>) type,
+ nullBeansAllowed);
model.put(qualifiedPropertyId, pd);
model.remove(propertyId);
for (BeanItem<BEANTYPE> item : itemIdToItem.values()) {
diff --git a/server/src/com/vaadin/data/util/BeanItem.java b/server/src/com/vaadin/data/util/BeanItem.java
index fc51be8f36..4834fe4f89 100644
--- a/server/src/com/vaadin/data/util/BeanItem.java
+++ b/server/src/com/vaadin/data/util/BeanItem.java
@@ -268,6 +268,27 @@ public class BeanItem<BT> extends PropertysetItem {
}
/**
+ * Adds a nested property to the item. If the <code>nullBeansAllowed</code>
+ * flag is set to true, calling getValue of the added property will return
+ * null if the property or any of its intermediate getters returns null. If
+ * set to false, null values returned by intermediate getters will cause
+ * NullPointerException. The default value is false to ensure backwards
+ * compatibility.
+ *
+ * @param nestedPropertyId
+ * property id to add. This property must not exist in the item
+ * already and must of of form "field1.field2" where field2 is a
+ * field in the object referenced to by field1
+ * @param nullBeansAllowed
+ * set true to allow null values from intermediate getters
+ */
+ public void addNestedProperty(String nestedPropertyId,
+ boolean nullBeansAllowed) {
+ addItemProperty(nestedPropertyId, new NestedMethodProperty<Object>(
+ getBean(), nestedPropertyId, nullBeansAllowed));
+ }
+
+ /**
* Gets the underlying JavaBean object.
*
* @return the bean object.
diff --git a/server/src/com/vaadin/data/util/NestedMethodProperty.java b/server/src/com/vaadin/data/util/NestedMethodProperty.java
index b62ecfbfc3..7a3963c17e 100644
--- a/server/src/com/vaadin/data/util/NestedMethodProperty.java
+++ b/server/src/com/vaadin/data/util/NestedMethodProperty.java
@@ -32,7 +32,7 @@ import com.vaadin.data.util.MethodProperty.MethodException;
* can contain multiple levels of nesting.
*
* When accessing the property value, all intermediate getters must return
- * non-null values.
+ * non-null values or the <code>nullBeansAllowed</code> must be set to true.
*
* @see MethodProperty
*
@@ -55,6 +55,15 @@ public class NestedMethodProperty<T> extends AbstractProperty<T> {
*/
private Object instance;
+ /**
+ * a boolean flag indicating whether intermediate getters may return null
+ * values. If the flag is set to true, calling getValue will return null if
+ * the property or any of its intermediate getters returns null. If set to
+ * false, intermediate getters returning null value will throw Exception.
+ * The default value is false to ensure backwards compatibility.
+ */
+ private boolean nullBeansAllowed = false;
+
private Class<? extends T> type;
/* Special serialization to handle method references */
@@ -85,7 +94,33 @@ public class NestedMethodProperty<T> extends AbstractProperty<T> {
* if the property name is invalid
*/
public NestedMethodProperty(Object instance, String propertyName) {
+ this(instance, propertyName, false);
+ }
+
+ /**
+ * Constructs a nested method property for a given object instance. The
+ * property name is a dot separated string pointing to a nested property,
+ * e.g. "manager.address.street". The <code>nullBeansAllowed</code> controls
+ * the behavior in cases where the intermediate getters may return null
+ * values. If the flag is set to true, calling getValue will return null if
+ * the property or any of its intermediate getters returns null. If set to
+ * false, null values returned by intermediate getters will cause
+ * NullPointerException. The default value is false to ensure backwards
+ * compatibility.
+ *
+ * @param instance
+ * top-level bean to which the property applies
+ * @param propertyName
+ * dot separated nested property name
+ * @param nullBeansAllowed
+ * set true to allow null values from intermediate getters
+ * @throws IllegalArgumentException
+ * if the property name is invalid
+ */
+ public NestedMethodProperty(Object instance, String propertyName,
+ boolean nullBeansAllowed) {
this.instance = instance;
+ this.nullBeansAllowed = nullBeansAllowed;
initialize(instance.getClass(), propertyName);
}
@@ -104,6 +139,25 @@ public class NestedMethodProperty<T> extends AbstractProperty<T> {
}
/**
+ * For internal use to deduce property type etc. without a bean instance.
+ * Calling {@link #setValue(Object)} or {@link #getValue()} on properties
+ * constructed this way is not supported.
+ *
+ * @param instanceClass
+ * class of the top-level bean
+ * @param propertyName
+ * dot separated nested property name
+ * @param nullBeansAllowed
+ * set true to allow null values from intermediate getters
+ */
+ NestedMethodProperty(Class<?> instanceClass, String propertyName,
+ boolean nullBeansAllowed) {
+ instance = null;
+ this.nullBeansAllowed = nullBeansAllowed;
+ initialize(instanceClass, propertyName);
+ }
+
+ /**
* Initializes most of the internal fields based on the top-level bean
* instance and property name (dot-separated string).
*
@@ -199,6 +253,9 @@ public class NestedMethodProperty<T> extends AbstractProperty<T> {
Object object = instance;
for (Method m : getMethods) {
object = m.invoke(object);
+ if (object == null && nullBeansAllowed) {
+ return null;
+ }
}
return (T) object;
} catch (final Throwable e) {
diff --git a/server/src/com/vaadin/data/util/NestedPropertyDescriptor.java b/server/src/com/vaadin/data/util/NestedPropertyDescriptor.java
index b2055fe776..67eb30fae5 100644
--- a/server/src/com/vaadin/data/util/NestedPropertyDescriptor.java
+++ b/server/src/com/vaadin/data/util/NestedPropertyDescriptor.java
@@ -34,6 +34,7 @@ public class NestedPropertyDescriptor<BT> implements
private final String name;
private final Class<?> propertyType;
+ private final boolean nullBeansAllowed;
/**
* Creates a property descriptor that can create MethodProperty instances to
@@ -48,10 +49,29 @@ public class NestedPropertyDescriptor<BT> implements
*/
public NestedPropertyDescriptor(String name, Class<BT> beanType)
throws IllegalArgumentException {
+ this(name, beanType, false);
+ }
+
+ /**
+ * Creates a property descriptor that can create MethodProperty instances to
+ * access the underlying bean property.
+ *
+ * @param name
+ * of the property in a dotted path format, e.g. "address.street"
+ * @param beanType
+ * type (class) of the top-level bean
+ * @param nullBeansAllowed
+ * set true to allow null values from intermediate getters
+ * @throws IllegalArgumentException
+ * if the property name is invalid
+ */
+ public NestedPropertyDescriptor(String name, Class<BT> beanType,
+ boolean nullBeansAllowed) throws IllegalArgumentException {
this.name = name;
NestedMethodProperty<?> property = new NestedMethodProperty<Object>(
- beanType, name);
+ beanType, name, nullBeansAllowed);
this.propertyType = property.getType();
+ this.nullBeansAllowed = nullBeansAllowed;
}
@Override
@@ -66,7 +86,7 @@ public class NestedPropertyDescriptor<BT> implements
@Override
public Property<?> createProperty(BT bean) {
- return new NestedMethodProperty<Object>(bean, name);
+ return new NestedMethodProperty<Object>(bean, name, nullBeansAllowed);
}
}
diff --git a/server/src/com/vaadin/data/util/converter/DefaultConverterFactory.java b/server/src/com/vaadin/data/util/converter/DefaultConverterFactory.java
index bbd3945a37..0b3cfcd1b0 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;
@@ -101,10 +102,10 @@ public class DefaultConverterFactory implements ConverterFactory {
return new StringToFloatConverter();
} else if (Integer.class.isAssignableFrom(sourceType)) {
return new StringToIntegerConverter();
+ } 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/server/BootstrapHandler.java b/server/src/com/vaadin/server/BootstrapHandler.java
index b21fdb0b74..f237d9edd4 100644
--- a/server/src/com/vaadin/server/BootstrapHandler.java
+++ b/server/src/com/vaadin/server/BootstrapHandler.java
@@ -146,14 +146,15 @@ public abstract class BootstrapHandler extends SynchronizedRequestHandler {
}
@Override
+ protected boolean canHandleRequest(VaadinRequest request) {
+ // We do not want to handle /APP requests here, instead let it fall
+ // through and produce a 404
+ return !ServletPortletHelper.isAppRequest(request);
+ }
+
+ @Override
public boolean synchronizedHandleRequest(VaadinSession session,
VaadinRequest request, VaadinResponse response) throws IOException {
- if (ServletPortletHelper.isAppRequest(request)) {
- // We do not want to handle /APP requests here, instead let it fall
- // through and produce a 404
- return false;
- }
-
try {
// Update WebBrowser here only to make WebBrowser information
// available in init for LegacyApplications
diff --git a/server/src/com/vaadin/server/Page.java b/server/src/com/vaadin/server/Page.java
index 037d8e8352..5c8b1aeb42 100644
--- a/server/src/com/vaadin/server/Page.java
+++ b/server/src/com/vaadin/server/Page.java
@@ -476,6 +476,8 @@ public class Page implements Serializable {
private final PageState state;
+ private String windowName;
+
public Page(UI uI, PageState state) {
this.uI = uI;
this.state = state;
@@ -637,6 +639,7 @@ public class Page implements Serializable {
String location = request.getParameter("v-loc");
String clientWidth = request.getParameter("v-cw");
String clientHeight = request.getParameter("v-ch");
+ windowName = request.getParameter("v-wn");
if (location != null) {
try {
@@ -662,6 +665,17 @@ public class Page implements Serializable {
}
/**
+ * Gets the window.name value of the browser window of this page.
+ *
+ * @since 7.2
+ *
+ * @return the window name, <code>null</code> if the name is not known
+ */
+ public String getWindowName() {
+ return windowName;
+ }
+
+ /**
* Updates the internal state with the given values. Does not resize the
* Page or browser window.
*
@@ -1124,7 +1138,7 @@ public class Page implements Serializable {
* the new page title to set
*/
public void setTitle(String title) {
- uI.getRpcProxy(PageClientRpc.class).setTitle(title);
+ getState(true).title = title;
}
/**
diff --git a/server/src/com/vaadin/server/ServiceDestroyEvent.java b/server/src/com/vaadin/server/ServiceDestroyEvent.java
new file mode 100644
index 0000000000..2ae4cc10af
--- /dev/null
+++ b/server/src/com/vaadin/server/ServiceDestroyEvent.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server;
+
+import java.util.EventObject;
+
+/**
+ * Event fired to {@link ServiceDestroyListener} when a {@link VaadinService} is
+ * being destroyed.
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public class ServiceDestroyEvent extends EventObject {
+
+ /**
+ * Creates a new event for the given service.
+ *
+ * @param service
+ * the service being destroyed
+ */
+ public ServiceDestroyEvent(VaadinService service) {
+ super(service);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.util.EventObject#getSource()
+ */
+ @Override
+ public VaadinService getSource() {
+ return (VaadinService) super.getSource();
+ }
+
+}
diff --git a/server/src/com/vaadin/server/ServiceDestroyListener.java b/server/src/com/vaadin/server/ServiceDestroyListener.java
new file mode 100644
index 0000000000..ad4966dd58
--- /dev/null
+++ b/server/src/com/vaadin/server/ServiceDestroyListener.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server;
+
+import java.io.Serializable;
+
+/**
+ * Listener that gets notified when the {@link VaadinService} to which it has
+ * been registered is destroyed.
+ *
+ * @see VaadinService#addServiceDestroyListener(ServiceDestroyListener)
+ * @see VaadinService#removeServiceDestroyListener(ServiceDestroyListener)
+ *
+ * @since 7.2
+ * @author Vaadin Ltd
+ */
+public interface ServiceDestroyListener extends Serializable {
+ /**
+ * Invoked when a service is destroyed
+ *
+ * @param event
+ * the event
+ */
+ public void serviceDestroy(ServiceDestroyEvent event);
+}
diff --git a/server/src/com/vaadin/server/SynchronizedRequestHandler.java b/server/src/com/vaadin/server/SynchronizedRequestHandler.java
index ac730dcecb..c695855d7d 100644
--- a/server/src/com/vaadin/server/SynchronizedRequestHandler.java
+++ b/server/src/com/vaadin/server/SynchronizedRequestHandler.java
@@ -32,6 +32,10 @@ public abstract class SynchronizedRequestHandler implements RequestHandler {
@Override
public boolean handleRequest(VaadinSession session, VaadinRequest request,
VaadinResponse response) throws IOException {
+ if (!canHandleRequest(request)) {
+ return false;
+ }
+
session.lock();
try {
return synchronizedHandleRequest(session, request, response);
@@ -62,4 +66,25 @@ public abstract class SynchronizedRequestHandler implements RequestHandler {
public abstract boolean synchronizedHandleRequest(VaadinSession session,
VaadinRequest request, VaadinResponse response) throws IOException;
+ /**
+ * Check whether a request may be handled by this handler. This can be used
+ * as an optimization to avoid locking the session just to investigate some
+ * method property. The default implementation just returns
+ * <code>true</code> which means that all requests will be handled by
+ * calling
+ * {@link #synchronizedHandleRequest(VaadinSession, VaadinRequest, VaadinResponse)}
+ * with the session locked.
+ *
+ * @since 7.2
+ * @param request
+ * the request to handle
+ * @return <code>true</code> if the request handling should continue once
+ * the session has been locked; <code>false</code> if there's no
+ * need to lock the session since the request would still not be
+ * handled.
+ */
+ protected boolean canHandleRequest(VaadinRequest request) {
+ return true;
+ }
+
}
diff --git a/server/src/com/vaadin/server/SystemMessages.java b/server/src/com/vaadin/server/SystemMessages.java
index 5e0fde1d4a..299c725207 100644
--- a/server/src/com/vaadin/server/SystemMessages.java
+++ b/server/src/com/vaadin/server/SystemMessages.java
@@ -63,32 +63,32 @@ public class SystemMessages implements Serializable {
protected String sessionExpiredURL = null;
protected boolean sessionExpiredNotificationEnabled = true;
protected String sessionExpiredCaption = "Session Expired";
- protected String sessionExpiredMessage = "Take note of any unsaved data, and <u>click here</u> to continue.";
+ protected String sessionExpiredMessage = "Take note of any unsaved data, and <u>click here</u> or press ESC key to continue.";
protected String communicationErrorURL = null;
protected boolean communicationErrorNotificationEnabled = true;
protected String communicationErrorCaption = "Communication problem";
- protected String communicationErrorMessage = "Take note of any unsaved data, and <u>click here</u> to continue.";
+ protected String communicationErrorMessage = "Take note of any unsaved data, and <u>click here</u> or press ESC to continue.";
protected String authenticationErrorURL = null;
protected boolean authenticationErrorNotificationEnabled = true;
protected String authenticationErrorCaption = "Authentication problem";
- protected String authenticationErrorMessage = "Take note of any unsaved data, and <u>click here</u> to continue.";
+ protected String authenticationErrorMessage = "Take note of any unsaved data, and <u>click here</u> or press ESC to continue.";
protected String internalErrorURL = null;
protected boolean internalErrorNotificationEnabled = true;
protected String internalErrorCaption = "Internal error";
- protected String internalErrorMessage = "Please notify the administrator.<br/>Take note of any unsaved data, and <u>click here</u> to continue.";
+ protected String internalErrorMessage = "Please notify the administrator.<br/>Take note of any unsaved data, and <u>click here</u> or press ESC to continue.";
protected String outOfSyncURL = null;
protected boolean outOfSyncNotificationEnabled = true;
protected String outOfSyncCaption = "Out of sync";
- protected String outOfSyncMessage = "Something has caused us to be out of sync with the server.<br/>Take note of any unsaved data, and <u>click here</u> to re-sync.";
+ protected String outOfSyncMessage = "Something has caused us to be out of sync with the server.<br/>Take note of any unsaved data, and <u>click here</u> or press ESC to re-sync.";
protected String cookiesDisabledURL = null;
protected boolean cookiesDisabledNotificationEnabled = true;
protected String cookiesDisabledCaption = "Cookies disabled";
- protected String cookiesDisabledMessage = "This application requires cookies to function.<br/>Please enable cookies in your browser and <u>click here</u> to try again.";
+ protected String cookiesDisabledMessage = "This application requires cookies to function.<br/>Please enable cookies in your browser and <u>click here</u> or press ESC to try again.";
/**
* Use {@link CustomizedSystemMessages} to customize
diff --git a/server/src/com/vaadin/server/VaadinPortlet.java b/server/src/com/vaadin/server/VaadinPortlet.java
index d86e5e6507..adef90c45f 100644
--- a/server/src/com/vaadin/server/VaadinPortlet.java
+++ b/server/src/com/vaadin/server/VaadinPortlet.java
@@ -482,6 +482,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 17bce7ad15..bd4637c407 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}.
*/
@@ -1655,23 +1661,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
@@ -1694,6 +1683,8 @@ public abstract class VaadinService implements Serializable {
} catch (InterruptedException e) {
// Just ignore
}
+
+ return future;
}
/**
@@ -1734,4 +1725,49 @@ public abstract class VaadinService implements Serializable {
}
}
+ /**
+ * 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 c16be33de2..d34cd3bf0e 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;
@@ -672,21 +671,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);
}
@@ -695,6 +684,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.
*
@@ -984,20 +1010,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/");
}
/**
@@ -1077,15 +1091,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 8de01e22bc..8f15dacc98 100644
--- a/server/src/com/vaadin/server/VaadinSession.java
+++ b/server/src/com/vaadin/server/VaadinSession.java
@@ -40,7 +40,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;
@@ -170,7 +169,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();
@@ -793,10 +792,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);
+ }
}
/**
@@ -926,18 +928,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 (!getPendingAccessQueue().isEmpty()) {
- getService().ensureAccessQueuePurged(this);
- }
}
/**
@@ -1062,20 +1052,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
@@ -1092,7 +1068,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();
+ }
+ }
}
/**
@@ -1297,4 +1287,24 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
return csrfToken;
}
+ /**
+ * Finds the UI with the corresponding embed id.
+ *
+ * @since 7.2
+ * @param embedId
+ * the embed id
+ * @return the UI with the corresponding embed id, or <code>null</code> if
+ * no UI is found
+ *
+ * @see UI#getEmbedId()
+ */
+ public UI getUIByEmbedId(String embedId) {
+ Integer uiId = embedIdMap.get(embedId);
+ if (uiId == null) {
+ return null;
+ } else {
+ return getUIById(uiId.intValue());
+ }
+ }
+
}
diff --git a/server/src/com/vaadin/server/communication/FileUploadHandler.java b/server/src/com/vaadin/server/communication/FileUploadHandler.java
index 3f6bfd9267..41a16601fe 100644
--- a/server/src/com/vaadin/server/communication/FileUploadHandler.java
+++ b/server/src/com/vaadin/server/communication/FileUploadHandler.java
@@ -284,7 +284,7 @@ public class FileUploadHandler implements RequestHandler {
// if boundary string does not exist, the posted file is from
// XHR2.post(File)
doHandleXhrFilePost(session, request, response, streamVariable,
- variableName, source, request.getContentLength());
+ variableName, source, getContentLength(request));
}
return true;
}
@@ -336,7 +336,7 @@ public class FileUploadHandler implements RequestHandler {
final InputStream inputStream = request.getInputStream();
- int contentLength = request.getContentLength();
+ long contentLength = getContentLength(request);
boolean atStart = false;
boolean firstFileFieldFound = false;
@@ -403,9 +403,22 @@ public class FileUploadHandler implements RequestHandler {
}
+ /*
+ * request.getContentLength() is limited to "int" by the Servlet
+ * specification. To support larger file uploads manually evaluate the
+ * Content-Length header which can contain long values.
+ */
+ private long getContentLength(VaadinRequest request) {
+ try {
+ return Long.parseLong(request.getHeader("Content-Length"));
+ } catch (NumberFormatException e) {
+ return -1l;
+ }
+ }
+
private void handleFileUploadValidationAndData(VaadinSession session,
InputStream inputStream, StreamVariable streamVariable,
- String filename, String mimeType, int contentLength,
+ String filename, String mimeType, long contentLength,
ClientConnector connector, String variableName)
throws UploadException {
session.lock();
@@ -474,7 +487,7 @@ public class FileUploadHandler implements RequestHandler {
protected void doHandleXhrFilePost(VaadinSession session,
VaadinRequest request, VaadinResponse response,
StreamVariable streamVariable, String variableName,
- ClientConnector owner, int contentLength) throws IOException {
+ ClientConnector owner, long contentLength) throws IOException {
// These are unknown in filexhr ATM, maybe add to Accept header that
// is accessible in portlets
@@ -504,7 +517,7 @@ public class FileUploadHandler implements RequestHandler {
*/
protected final boolean streamToReceiver(VaadinSession session,
final InputStream in, StreamVariable streamVariable,
- String filename, String type, int contentLength)
+ String filename, String type, long contentLength)
throws UploadException {
if (streamVariable == null) {
throw new IllegalStateException(
@@ -512,7 +525,7 @@ public class FileUploadHandler implements RequestHandler {
}
OutputStream out = null;
- int totalBytes = 0;
+ long totalBytes = 0;
StreamingStartEventImpl startedEvent = new StreamingStartEventImpl(
filename, type, contentLength);
try {
diff --git a/server/src/com/vaadin/server/communication/HeartbeatHandler.java b/server/src/com/vaadin/server/communication/HeartbeatHandler.java
index 4c95859203..04cb1b5a25 100644
--- a/server/src/com/vaadin/server/communication/HeartbeatHandler.java
+++ b/server/src/com/vaadin/server/communication/HeartbeatHandler.java
@@ -43,6 +43,11 @@ import com.vaadin.ui.UI;
public class HeartbeatHandler extends SynchronizedRequestHandler implements
SessionExpiredHandler {
+ @Override
+ protected boolean canHandleRequest(VaadinRequest request) {
+ return ServletPortletHelper.isHeartbeatRequest(request);
+ }
+
/**
* Handles a heartbeat request for the given session. Reads the GET
* parameter named {@link UIConstants#UI_ID_PARAMETER} to identify the UI.
@@ -53,10 +58,6 @@ public class HeartbeatHandler extends SynchronizedRequestHandler implements
@Override
public boolean synchronizedHandleRequest(VaadinSession session,
VaadinRequest request, VaadinResponse response) throws IOException {
- if (!ServletPortletHelper.isHeartbeatRequest(request)) {
- return false;
- }
-
UI ui = session.getService().findUI(request);
if (ui != null) {
ui.setLastHeartbeatTimestamp(System.currentTimeMillis());
diff --git a/server/src/com/vaadin/server/communication/PushRequestHandler.java b/server/src/com/vaadin/server/communication/PushRequestHandler.java
index 8d0da24896..74595322a0 100644
--- a/server/src/com/vaadin/server/communication/PushRequestHandler.java
+++ b/server/src/com/vaadin/server/communication/PushRequestHandler.java
@@ -28,6 +28,8 @@ import org.atmosphere.cpr.AtmosphereRequest;
import org.atmosphere.cpr.AtmosphereResponse;
import com.vaadin.server.RequestHandler;
+import com.vaadin.server.ServiceDestroyEvent;
+import com.vaadin.server.ServiceDestroyListener;
import com.vaadin.server.ServiceException;
import com.vaadin.server.ServletPortletHelper;
import com.vaadin.server.SessionExpiredHandler;
@@ -63,6 +65,13 @@ public class PushRequestHandler implements RequestHandler,
}
};
+ service.addServiceDestroyListener(new ServiceDestroyListener() {
+ @Override
+ public void serviceDestroy(ServiceDestroyEvent event) {
+ destroy();
+ }
+ });
+
pushHandler = new PushHandler(service);
atmosphere.addAtmosphereHandler("/*", pushHandler);
atmosphere.addInitParameter(ApplicationConfig.PROPERTY_SESSION_SUPPORT,
diff --git a/server/src/com/vaadin/server/communication/UIInitHandler.java b/server/src/com/vaadin/server/communication/UIInitHandler.java
index d4b0bc709f..b2c17d00c1 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;
@@ -56,12 +55,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 {
@@ -163,31 +163,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 +218,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/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/DragAndDropWrapper.java b/server/src/com/vaadin/ui/DragAndDropWrapper.java
index 6c6aa3c3f4..7a2cfb82e4 100644
--- a/server/src/com/vaadin/ui/DragAndDropWrapper.java
+++ b/server/src/com/vaadin/ui/DragAndDropWrapper.java
@@ -54,7 +54,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/Notification.java b/server/src/com/vaadin/ui/Notification.java
index cf1d03ab5c..31fa265b02 100644
--- a/server/src/com/vaadin/ui/Notification.java
+++ b/server/src/com/vaadin/ui/Notification.java
@@ -21,6 +21,7 @@ import java.io.Serializable;
import com.vaadin.server.Page;
import com.vaadin.server.Resource;
import com.vaadin.shared.Position;
+import com.vaadin.shared.ui.ui.NotificationConfigurationBean.Role;
/**
* A notification message, used to display temporary messages to the user - for
@@ -63,7 +64,7 @@ import com.vaadin.shared.Position;
*/
public class Notification implements Serializable {
public enum Type {
- HUMANIZED_MESSAGE, WARNING_MESSAGE, ERROR_MESSAGE, TRAY_NOTIFICATION;
+ HUMANIZED_MESSAGE, WARNING_MESSAGE, ERROR_MESSAGE, TRAY_NOTIFICATION, ASSISTIVE_NOTIFICATION;
}
@Deprecated
@@ -190,21 +191,38 @@ public class Notification implements Serializable {
case WARNING_MESSAGE:
delayMsec = 1500;
styleName = "warning";
+ setNavigationConfiguration("Warning: ", "", Role.ALERT);
break;
case ERROR_MESSAGE:
delayMsec = -1;
styleName = "error";
+ setNavigationConfiguration("Error: ", " - close with ESC",
+ Role.ALERT);
break;
case TRAY_NOTIFICATION:
delayMsec = 3000;
position = Position.BOTTOM_RIGHT;
styleName = "tray";
-
+ setNavigationConfiguration("Info: ", "", Role.STATUS);
+ break;
+ case ASSISTIVE_NOTIFICATION:
+ delayMsec = 3000;
+ position = Position.ASSISTIVE;
+ styleName = "assistive";
+ setNavigationConfiguration("Note: ", "", Role.ALERT);
+ break;
case HUMANIZED_MESSAGE:
default:
+ styleName = "humanized";
+ setNavigationConfiguration("Info: ", "", Role.ALERT);
break;
}
+ }
+ private void setNavigationConfiguration(String prefix, String postfix,
+ Role ariaRole) {
+ UI.getCurrent().getNotificationConfiguration()
+ .setStyleConfiguration(styleName, prefix, postfix, ariaRole);
}
/**
@@ -322,6 +340,132 @@ public class Notification implements Serializable {
}
/**
+ * Sets the accessibility prefix for a notification type.
+ *
+ * This prefix is read to assistive device users before the content of the
+ * notification, but not visible on the page.
+ *
+ * @param type
+ * Type of the notification
+ * @param prefix
+ * String that is placed before the notification content
+ */
+ public void setAssistivePrefixForType(Type type, String prefix) {
+ UI.getCurrent().getNotificationConfiguration()
+ .setAssistivePrefixForStyle(getStyle(type), prefix);
+ }
+
+ /**
+ * Gets the accessibility prefix for a notification type.
+ *
+ * This prefix is read to assistive device users before the content of the
+ * notification, but not visible on the page.
+ *
+ * @param type
+ * Type of the notification
+ * @return The accessibility prefix for the provided notification type
+ */
+ public String getAssistivePrefixForType(Type type) {
+ return UI.getCurrent().getNotificationConfiguration()
+ .getAssistivePrefixForStyle(getStyle(type));
+ }
+
+ /**
+ * Sets the accessibility postfix for a notification type.
+ *
+ * This postfix is read to assistive device users after the content of the
+ * notification, but not visible on the page.
+ *
+ * @param type
+ * Type of the notification
+ * @param postfix
+ * String that is placed after the notification content
+ */
+ public void setAssistivePostfixForType(Type type, String postfix) {
+ UI.getCurrent().getNotificationConfiguration()
+ .setAssistivePostfixForStyle(getStyle(type), postfix);
+ }
+
+ /**
+ * Gets the accessibility postfix for a notification type.
+ *
+ * This postfix is read to assistive device users after the content of the
+ * notification, but not visible on the page.
+ *
+ * @param type
+ * Type of the notification
+ * @return The accessibility postfix for the provided notification type
+ */
+ public String getAssistivePostfixForType(Type type) {
+ return UI.getCurrent().getNotificationConfiguration()
+ .getAssistivePostfixForStyle(getStyle(type));
+ }
+
+ /**
+ * Sets the WAI-ARIA role for a notification type.
+ *
+ * This role defines how an assistive device handles a notification.
+ * Available roles are alert and status (@see <a
+ * href="http://www.w3.org/TR/2011/CR-wai-aria-20110118/roles">Roles
+ * Model</a>).
+ *
+ * The default role is alert.
+ *
+ * @param type
+ * Type of the notification
+ * @param role
+ * Role to set for the notification type
+ */
+ public void setAssistiveRoleForType(Type type, Role role) {
+ UI.getCurrent().getNotificationConfiguration()
+ .setAssistiveRoleForStyle(getStyle(type), role);
+ }
+
+ /**
+ * Gets the WAI-ARIA role for a notification type.
+ *
+ * This role defines how an assistive device handles a notification.
+ * Available roles are alert and status (@see <a
+ * href="http://www.w3.org/TR/2011/CR-wai-aria-20110118/roles">Roles
+ * Model</a>)
+ *
+ * The default role is alert.
+ *
+ * @param type
+ * Type of the notification
+ * @return Role to set for the notification type
+ */
+ public Role getAssistiveRoleForType(Type type) {
+ return UI.getCurrent().getNotificationConfiguration()
+ .getAssistiveRoleForStyle(getStyle(type));
+ }
+
+ private String getStyle(Type type) {
+ String style = "";
+
+ switch (type) {
+ case WARNING_MESSAGE:
+ style = "warning";
+ break;
+ case ERROR_MESSAGE:
+ style = "error";
+ break;
+ case TRAY_NOTIFICATION:
+ style = "tray";
+ break;
+ case ASSISTIVE_NOTIFICATION:
+ style = "assistive";
+ break;
+ case HUMANIZED_MESSAGE:
+ default:
+ style = "humanized";
+ break;
+ }
+
+ return style;
+ }
+
+ /**
* Sets whether html is allowed in the caption and description. If set to
* true, the texts are passed to the browser as html and the developer is
* responsible for ensuring no harmful html is used. If set to false, the
@@ -414,4 +558,4 @@ public class Notification implements Serializable {
public static void show(String caption, String description, Type type) {
new Notification(caption, description, type).show(Page.getCurrent());
}
-} \ No newline at end of file
+}
diff --git a/server/src/com/vaadin/ui/NotificationConfiguration.java b/server/src/com/vaadin/ui/NotificationConfiguration.java
new file mode 100644
index 0000000000..52d3e76d63
--- /dev/null
+++ b/server/src/com/vaadin/ui/NotificationConfiguration.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+/**
+ *
+ */
+package com.vaadin.ui;
+
+import java.io.Serializable;
+
+import com.vaadin.shared.ui.ui.NotificationConfigurationBean;
+import com.vaadin.shared.ui.ui.NotificationConfigurationBean.Role;
+import com.vaadin.shared.ui.ui.UIState.NotificationConfigurationState;
+
+/**
+ * Provides methods for configuring the notification.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public interface NotificationConfiguration extends Serializable {
+ public void setStyleConfiguration(String style, String prefix,
+ String postfix, Role ariaRole);
+
+ /**
+ * Returns the complete configuration object for the given notification
+ * style.
+ *
+ * @param style
+ * String of the notification style to return
+ * @return The notification configuration object
+ */
+ public NotificationConfigurationBean getStyleConfiguration(String style);
+
+ /**
+ * Sets the accessibility prefix for the given notification style.
+ *
+ * This prefix is read to assistive device users in front of the content of
+ * the notification, but not visible on the page.
+ *
+ * @param style
+ * String of the notification style
+ * @param prefix
+ * String that is placed before the notification content
+ */
+ public void setAssistivePrefixForStyle(String style, String prefix);
+
+ /**
+ * Returns the accessibility prefix for the given notification style.
+ *
+ * This prefix is read to assistive device users in front of the content of
+ * the notification, but not visible on the page.
+ *
+ * @param style
+ * String of the notification style
+ * @return The prefix of the provided notification style
+ */
+ public String getAssistivePrefixForStyle(String style);
+
+ /**
+ * Sets the accessibility postfix for the given notification style.
+ *
+ * This postfix is read to assistive device users after the content of the
+ * notification, but not visible on the page.
+ *
+ * @param style
+ * String of the notification style
+ * @param postfix
+ * String that is placed after the notification content
+ */
+ public void setAssistivePostfixForStyle(String style, String postfix);
+
+ /**
+ * Returns the accessibility postfix for the given notification style.
+ *
+ * This postfix is read to assistive device users after the content of the
+ * notification, but not visible on the page.
+ *
+ * @param style
+ * String of the notification style
+ * @return The postfix of the provided notification style
+ */
+ public String getAssistivePostfixForStyle(String style);
+
+ /**
+ * Sets the WAI-ARIA role for a notification style.
+ *
+ * This role defines how an assistive device handles a notification.
+ * Available roles are alert, alertdialog and status (@see <a
+ * href="http://www.w3.org/TR/2011/CR-wai-aria-20110118/roles">Roles
+ * Model</a>)
+ *
+ * The default role is alert.
+ *
+ * @param style
+ * String of the notification style
+ * @param role
+ * Role to set for the notification type
+ */
+ public void setAssistiveRoleForStyle(String style, Role role);
+
+ /**
+ * Returns the WAI-ARIA role for a notification style.
+ *
+ * This role defines how an assistive device handles a notification.
+ * Available roles are alert, alertdialog and status (@see <a
+ * href="http://www.w3.org/TR/2011/CR-wai-aria-20110118/roles">Roles
+ * Model</a> )
+ *
+ * The default role is alert.
+ *
+ * @param style
+ * String of the notification style
+ * @return The current Role for the notification type
+ */
+ public Role getAssistiveRoleForStyle(String style);
+}
+
+class NotificationConfigurationImpl implements NotificationConfiguration {
+
+ private UI ui;
+
+ public NotificationConfigurationImpl(UI ui) {
+ this.ui = ui;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.ui.NotificationConfiguration#setStyleConfiguration(java.lang
+ * .String, java.lang.String, java.lang.String,
+ * com.vaadin.ui.NotificationConfiguration.Role)
+ */
+ @Override
+ public void setStyleConfiguration(String style, String prefix,
+ String postfix, Role ariaRole) {
+ getState().setup.put(style, new NotificationConfigurationBean(prefix,
+ postfix, ariaRole));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.ui.NotificationConfiguration#getStyleConfiguration(java.lang
+ * .String)
+ */
+ @Override
+ public NotificationConfigurationBean getStyleConfiguration(String style) {
+ return getState(false).setup.get(style);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.ui.NotificationConfiguration#setStylePrefix(java.lang.String,
+ * java.lang.String)
+ */
+ @Override
+ public void setAssistivePrefixForStyle(String style, String prefix) {
+ getConfigurationBean(style).setAssistivePrefix(prefix);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.ui.NotificationConfiguration#getStylePrefix(java.lang.String)
+ */
+ @Override
+ public String getAssistivePrefixForStyle(String style) {
+ NotificationConfigurationBean styleSetup = getState().setup.get(style);
+ if (styleSetup != null) {
+ return styleSetup.getAssistivePrefix();
+ }
+
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.ui.NotificationConfiguration#setStylePostfix(com.vaadin.ui
+ * .Notification.Type, java.lang.String)
+ */
+ @Override
+ public void setAssistivePostfixForStyle(String style, String postfix) {
+ getConfigurationBean(style).setAssistivePostfix(postfix);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.ui.NotificationConfiguration#getStylePostfix(com.vaadin.ui
+ * .Notification.Type)
+ */
+ @Override
+ public String getAssistivePostfixForStyle(String style) {
+ NotificationConfigurationBean styleSetup = getState().setup.get(style);
+ if (styleSetup != null) {
+ return styleSetup.getAssistivePostfix();
+ }
+
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.NotificationConfiguration#setStyleRole(com.vaadin.ui.
+ * Notification.Type, com.vaadin.ui.NotificationConfiguration.Role)
+ */
+ @Override
+ public void setAssistiveRoleForStyle(String style, Role role) {
+ getConfigurationBean(style).setAssistiveRole(role);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.NotificationConfiguration#getStyleRole(com.vaadin.ui.
+ * Notification.Type)
+ */
+ @Override
+ public Role getAssistiveRoleForStyle(String style) {
+ NotificationConfigurationBean styleSetup = getState().setup.get(style);
+ if (styleSetup != null) {
+ return styleSetup.getAssistiveRole();
+ }
+
+ return null;
+ }
+
+ private NotificationConfigurationBean getConfigurationBean(String style) {
+ NotificationConfigurationBean styleSetup = getState().setup.get(style);
+ if (styleSetup == null) {
+ styleSetup = new NotificationConfigurationBean();
+ getState().setup.put(style, styleSetup);
+ }
+
+ return styleSetup;
+ }
+
+ private NotificationConfigurationState getState() {
+ return ui.getState().notificationConfiguration;
+ }
+
+ private NotificationConfigurationState getState(boolean markAsDirty) {
+ return ui.getState(markAsDirty).notificationConfiguration;
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/TabSheet.java b/server/src/com/vaadin/ui/TabSheet.java
index 36022adb74..a1f9e9dd26 100644
--- a/server/src/com/vaadin/ui/TabSheet.java
+++ b/server/src/com/vaadin/ui/TabSheet.java
@@ -268,7 +268,34 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
* @return the created {@link Tab}
*/
public Tab addTab(Component c, String caption, Resource icon) {
- return addTab(c, caption, icon, components.size());
+ return addTab(c, caption, icon, "", components.size());
+ }
+
+ /**
+ * Adds a new tab into TabSheet.
+ *
+ * The first tab added to a tab sheet is automatically selected and a tab
+ * selection event is fired.
+ *
+ * If the component is already present in the tab sheet, changes its caption
+ * and icon and icon alternate text and returns the corresponding (old) tab,
+ * preserving other tab metadata.
+ *
+ * @param c
+ * the component to be added onto tab - should not be null.
+ * @param caption
+ * the caption to be set for the component and used rendered in
+ * tab bar
+ * @param icon
+ * the icon to be set for the component and used rendered in tab
+ * bar
+ * @param iconAltText
+ * the alternate text for the icon
+ * @return the created {@link Tab}
+ */
+ public Tab addTab(Component c, String caption, Resource icon,
+ String iconAltText) {
+ return addTab(c, caption, icon, iconAltText, components.size());
}
/**
@@ -294,12 +321,41 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
* @return the created {@link Tab}
*/
public Tab addTab(Component c, String caption, Resource icon, int position) {
+ return addTab(c, caption, icon, "", position);
+ }
+
+ /**
+ * Adds a new tab into TabSheet.
+ *
+ * The first tab added to a tab sheet is automatically selected and a tab
+ * selection event is fired.
+ *
+ * If the component is already present in the tab sheet, changes its caption
+ * and icon and icon alternate text and returns the corresponding (old) tab,
+ * preserving other tab metadata like the position.
+ *
+ * @param c
+ * the component to be added onto tab - should not be null.
+ * @param caption
+ * the caption to be set for the component and used rendered in
+ * tab bar
+ * @param icon
+ * the icon to be set for the component and used rendered in tab
+ * bar
+ * @param iconAltText
+ * the alternate text for the icon
+ * @param position
+ * the position at where the the tab should be added.
+ * @return the created {@link Tab}
+ */
+ public Tab addTab(Component c, String caption, Resource icon,
+ String iconAltText, int position) {
if (c == null) {
return null;
} else if (tabs.containsKey(c)) {
Tab tab = tabs.get(c);
tab.setCaption(caption);
- tab.setIcon(icon);
+ tab.setIcon(icon, iconAltText);
return tab;
} else {
components.add(position, c);
@@ -371,13 +427,15 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
final Component c = i.next();
String caption = null;
Resource icon = null;
+ String iconAltText = "";
if (TabSheet.class.isAssignableFrom(source.getClass())) {
Tab tab = ((TabSheet) source).getTab(c);
caption = tab.getCaption();
icon = tab.getIcon();
+ iconAltText = tab.getIconAltText();
}
source.removeComponent(c);
- addTab(c, caption, icon);
+ addTab(c, caption, icon, iconAltText);
}
}
@@ -429,9 +487,12 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
if (icon != null) {
target.addAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ICON,
icon);
+ target.addAttribute(
+ TabsheetBaseConstants.ATTRIBUTE_TAB_ICON_ALT,
+ tab.getIconAltText());
}
final String caption = tab.getCaption();
- if (caption != null && caption.length() > 0) {
+ if (caption != null && !caption.isEmpty()) {
target.addAttribute(
TabsheetBaseConstants.ATTRIBUTE_TAB_CAPTION, caption);
}
@@ -449,10 +510,15 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
}
final String styleName = tab.getStyleName();
- if (styleName != null && styleName.length() != 0) {
+ if (styleName != null && !styleName.isEmpty()) {
target.addAttribute(TabsheetConstants.TAB_STYLE_NAME, styleName);
}
+ final String id = tab.getId();
+ if (id != null && !id.isEmpty()) {
+ target.addAttribute("id", id);
+ }
+
target.addAttribute("key", keyMapper.key(component));
if (component.equals(selected)) {
target.addAttribute("selected", true);
@@ -549,6 +615,11 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
// connector
if (selected != null) {
selected.markAsDirtyRecursive();
+
+ Tab tab = getTab(c);
+ if (tab != null && tab.getDefaultFocusComponent() != null) {
+ tab.getDefaultFocusComponent().focus();
+ }
}
}
@@ -889,6 +960,23 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
public void setClosable(boolean closable);
/**
+ * Set the component that should automatically focused when the tab is
+ * selected.
+ *
+ * @param component
+ * the component to focus
+ */
+ public void setDefaultFocusComponent(Focusable component);
+
+ /**
+ * Get the component that should be automatically focused when the tab
+ * is selected.
+ *
+ * @return the focusable component
+ */
+ public Focusable getDefaultFocusComponent();
+
+ /**
* Returns the enabled status for the tab. A disabled tab is shown as
* such in the tab bar and cannot be selected.
*
@@ -932,6 +1020,27 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
public void setIcon(Resource icon);
/**
+ * Sets the icon and alt text for the tab.
+ *
+ * @param icon
+ * the icon to set
+ */
+ public void setIcon(Resource icon, String iconAltText);
+
+ /**
+ * Gets the icon alt text for the tab.
+ */
+ public String getIconAltText();
+
+ /**
+ * Sets the icon alt text for the tab.
+ *
+ * @param iconAltText
+ * the icon to set
+ */
+ public void setIconAltText(String iconAltText);
+
+ /**
* Gets the description for the tab. The description can be used to
* briefly describe the state of the tab to the user, and is typically
* shown as a tooltip when hovering over the tab.
@@ -1015,6 +1124,23 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
* @see #setStyleName(String)
*/
public String getStyleName();
+
+ /**
+ * Adds an unique id for component that is used in the client-side for
+ * testing purposes. Keeping identifiers unique is the responsibility of
+ * the programmer.
+ *
+ * @param id
+ * An alphanumeric id
+ */
+ public void setId(String id);
+
+ /**
+ * Gets currently set debug identifier
+ *
+ * @return current id, null if not set
+ */
+ public String getId();
}
/**
@@ -1030,6 +1156,9 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
private String description = null;
private ErrorMessage componentError = null;
private String styleName;
+ private String id;
+ private String iconAltText = "";
+ private Focusable defaultFocus;
public TabSheetTabImpl(String caption, Resource icon) {
if (caption == null) {
@@ -1061,11 +1190,38 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
@Override
public void setIcon(Resource icon) {
+ setIcon(icon, "");
+ }
+
+ @Override
+ public void setIcon(Resource icon, String iconAltText) {
this.icon = icon;
+ this.iconAltText = iconAltText;
markAsDirty();
}
@Override
+ public String getIconAltText() {
+ return iconAltText;
+ }
+
+ @Override
+ public void setIconAltText(String iconAltText) {
+ this.iconAltText = iconAltText;
+ markAsDirty();
+ }
+
+ @Override
+ public void setDefaultFocusComponent(Focusable defaultFocus) {
+ this.defaultFocus = defaultFocus;
+ }
+
+ @Override
+ public Focusable getDefaultFocusComponent() {
+ return defaultFocus;
+ }
+
+ @Override
public boolean isEnabled() {
return enabled;
}
@@ -1150,6 +1306,18 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
public String getStyleName() {
return styleName;
}
+
+ @Override
+ public void setId(String id) {
+ this.id = id;
+ markAsDirty();
+
+ }
+
+ @Override
+ public String getId() {
+ return id;
+ }
}
/**
@@ -1309,7 +1477,7 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
*/
private static void copyTabMetadata(Tab from, Tab to) {
to.setCaption(from.getCaption());
- to.setIcon(from.getIcon());
+ to.setIcon(from.getIcon(), from.getIconAltText());
to.setDescription(from.getDescription());
to.setVisible(from.isVisible());
to.setEnabled(from.isEnabled());
diff --git a/server/src/com/vaadin/ui/UI.java b/server/src/com/vaadin/ui/UI.java
index 0746431302..8beebb0f1e 100644
--- a/server/src/com/vaadin/ui/UI.java
+++ b/server/src/com/vaadin/ui/UI.java
@@ -73,7 +73,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>
@@ -218,6 +218,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.
@@ -544,6 +547,8 @@ public abstract class UI extends AbstractSingleComponentContainer implements
private LocaleService localeService = new LocaleService(this,
getState(false).localeServiceState);
+ private String embedId;
+
/**
* This method is used by Component.Focusable objects to request focus to
* themselves. Focus renders must be handled at window level (instead of
@@ -591,12 +596,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");
@@ -1302,6 +1314,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
@@ -1486,4 +1507,18 @@ public abstract class UI extends AbstractSingleComponentContainer implements
private static Logger getLogger() {
return Logger.getLogger(UI.class.getName());
}
+
+ /**
+ * Gets a string the uniquely distinguishes this UI instance based on where
+ * it is embedded. The embed identifier is based on the
+ * <code>window.name</code> DOM attribute of the browser window where the UI
+ * is displayed and the id of the div element where the UI is embedded.
+ *
+ * @since 7.2
+ * @return the embed id for this UI, or <code>null</code> if no id known
+ */
+ public String getEmbedId() {
+ return embedId;
+ }
+
}
diff --git a/server/src/com/vaadin/ui/Window.java b/server/src/com/vaadin/ui/Window.java
index 5820161c1c..dfe83d48a1 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;
/**
@@ -228,8 +233,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);
}
@@ -995,4 +998,191 @@ 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 the
+ * window with the tab key.
+ * <p>
+ * This is meant to help users of assistive devices to not leaving the
+ * window unintentionally.
+ *
+ * @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;
+ }
}