aboutsummaryrefslogtreecommitdiffstats
path: root/server/src
diff options
context:
space:
mode:
Diffstat (limited to 'server/src')
-rw-r--r--server/src/com/vaadin/annotations/PreserveOnRefresh.java5
-rw-r--r--server/src/com/vaadin/annotations/Widgetset.java2
-rw-r--r--server/src/com/vaadin/data/fieldgroup/BeanFieldGroup.java54
-rw-r--r--server/src/com/vaadin/data/fieldgroup/FieldGroup.java6
-rw-r--r--server/src/com/vaadin/data/util/ContainerHierarchicalWrapper.java2
-rw-r--r--server/src/com/vaadin/data/util/ListSet.java2
-rw-r--r--server/src/com/vaadin/data/util/MethodProperty.java30
-rw-r--r--server/src/com/vaadin/data/util/MethodPropertyDescriptor.java2
-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/util/filter/Compare.java2
-rw-r--r--server/src/com/vaadin/data/util/filter/Like.java4
-rw-r--r--server/src/com/vaadin/data/validator/BeanValidator.java19
-rw-r--r--server/src/com/vaadin/event/FieldEvents.java2
-rw-r--r--server/src/com/vaadin/event/ListenerMethod.java4
-rw-r--r--server/src/com/vaadin/event/UIEvents.java116
-rw-r--r--server/src/com/vaadin/navigator/Navigator.java2
-rw-r--r--server/src/com/vaadin/server/AbstractClientConnector.java13
-rw-r--r--server/src/com/vaadin/server/BootstrapHandler.java15
-rw-r--r--server/src/com/vaadin/server/ConnectorResourceHandler.java8
-rw-r--r--server/src/com/vaadin/server/Constants.java2
-rw-r--r--server/src/com/vaadin/server/DefaultDeploymentConfiguration.java17
-rw-r--r--server/src/com/vaadin/server/DefaultErrorHandler.java41
-rw-r--r--server/src/com/vaadin/server/DragAndDropService.java10
-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.java57
-rw-r--r--server/src/com/vaadin/server/LegacyCommunicationManager.java29
-rw-r--r--server/src/com/vaadin/server/Page.java108
-rw-r--r--server/src/com/vaadin/server/ResourceReference.java7
-rw-r--r--server/src/com/vaadin/server/Responsive.java161
-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/UIProvider.java5
-rw-r--r--server/src/com/vaadin/server/VaadinPortlet.java235
-rw-r--r--server/src/com/vaadin/server/VaadinService.java95
-rw-r--r--server/src/com/vaadin/server/VaadinServlet.java134
-rw-r--r--server/src/com/vaadin/server/VaadinSession.java181
-rw-r--r--server/src/com/vaadin/server/WrappedHttpSession.java14
-rw-r--r--server/src/com/vaadin/server/communication/AtmospherePushConnection.java119
-rw-r--r--server/src/com/vaadin/server/communication/DateSerializer.java42
-rw-r--r--server/src/com/vaadin/server/communication/FileUploadHandler.java28
-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.java165
-rw-r--r--server/src/com/vaadin/server/communication/PushRequestHandler.java82
-rw-r--r--server/src/com/vaadin/server/communication/ServerRpcHandler.java223
-rw-r--r--server/src/com/vaadin/server/communication/UIInitHandler.java87
-rw-r--r--server/src/com/vaadin/server/communication/UidlRequestHandler.java8
-rw-r--r--server/src/com/vaadin/server/communication/UidlWriter.java8
-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/AbstractOrderedLayout.java10
-rw-r--r--server/src/com/vaadin/ui/AbstractSelect.java31
-rw-r--r--server/src/com/vaadin/ui/AbstractSingleComponentContainer.java15
-rw-r--r--server/src/com/vaadin/ui/Accordion.java12
-rw-r--r--server/src/com/vaadin/ui/Button.java25
-rw-r--r--server/src/com/vaadin/ui/ComboBox.java16
-rw-r--r--server/src/com/vaadin/ui/Component.java6
-rw-r--r--server/src/com/vaadin/ui/ConnectorTracker.java148
-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.java33
-rw-r--r--server/src/com/vaadin/ui/NotificationConfiguration.java185
-rw-r--r--server/src/com/vaadin/ui/PushConfiguration.java42
-rw-r--r--server/src/com/vaadin/ui/TabSheet.java460
-rw-r--r--server/src/com/vaadin/ui/Table.java1
-rw-r--r--server/src/com/vaadin/ui/UI.java204
-rw-r--r--server/src/com/vaadin/ui/Upload.java94
-rw-r--r--server/src/com/vaadin/ui/Window.java207
75 files changed, 3595 insertions, 1048 deletions
diff --git a/server/src/com/vaadin/annotations/PreserveOnRefresh.java b/server/src/com/vaadin/annotations/PreserveOnRefresh.java
index d6216d1ee0..7f4ef3ffe5 100644
--- a/server/src/com/vaadin/annotations/PreserveOnRefresh.java
+++ b/server/src/com/vaadin/annotations/PreserveOnRefresh.java
@@ -22,6 +22,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.vaadin.server.UIProvider;
+import com.vaadin.ui.UI;
/**
* Marks a UI that should be retained when the user refreshed the browser
@@ -30,6 +31,10 @@ import com.vaadin.server.UIProvider;
* adding this annotation to a UI class, the framework will instead reuse the
* current UI instance when a reload is detected.
* <p>
+ * Whenever a request is received that reloads a preserved UI, the UI's
+ * {@link UI#refresh(com.vaadin.server.VaadinRequest) refresh} method is invoked
+ * by the framework.
+ * <p>
* By using
* {@link UIProvider#isPreservedOnRefresh(com.vaadin.server.UICreateEvent)}, the
* decision can also be made dynamically based on other parameters than only
diff --git a/server/src/com/vaadin/annotations/Widgetset.java b/server/src/com/vaadin/annotations/Widgetset.java
index 2047fa377d..c6ef6a7194 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/fieldgroup/BeanFieldGroup.java b/server/src/com/vaadin/data/fieldgroup/BeanFieldGroup.java
index ff4ecc8426..e5d53b759d 100644
--- a/server/src/com/vaadin/data/fieldgroup/BeanFieldGroup.java
+++ b/server/src/com/vaadin/data/fieldgroup/BeanFieldGroup.java
@@ -225,4 +225,58 @@ public class BeanFieldGroup<T> extends FieldGroup {
}
return beanValidationImplementationAvailable;
}
+
+ /**
+ * Convenience method to bind Fields from a given "field container" to a
+ * given bean with buffering disabled.
+ * <p>
+ * The returned {@link BeanFieldGroup} can be used for further
+ * configuration.
+ *
+ * @see #bindFieldsBuffered(Object, Object)
+ * @see #bindMemberFields(Object)
+ * @since 7.2
+ * @param bean
+ * the bean to be bound
+ * @param objectWithMemberFields
+ * the class that contains {@link Field}s for bean properties
+ * @return the bean field group used to make binding
+ */
+ public static <T> BeanFieldGroup<T> bindFieldsUnbuffered(T bean,
+ Object objectWithMemberFields) {
+ return createAndBindFields(bean, objectWithMemberFields, false);
+ }
+
+ /**
+ * Convenience method to bind Fields from a given "field container" to a
+ * given bean with buffering enabled.
+ * <p>
+ * The returned {@link BeanFieldGroup} can be used for further
+ * configuration.
+ *
+ * @see #bindFieldsUnbuffered(Object, Object)
+ * @see #bindMemberFields(Object)
+ * @since 7.2
+ * @param bean
+ * the bean to be bound
+ * @param objectWithMemberFields
+ * the class that contains {@link Field}s for bean properties
+ * @return the bean field group used to make binding
+ */
+ public static <T> BeanFieldGroup<T> bindFieldsBuffered(T bean,
+ Object objectWithMemberFields) {
+ return createAndBindFields(bean, objectWithMemberFields, true);
+ }
+
+ private static <T> BeanFieldGroup<T> createAndBindFields(T bean,
+ Object objectWithMemberFields, boolean buffered) {
+ @SuppressWarnings("unchecked")
+ BeanFieldGroup<T> beanFieldGroup = new BeanFieldGroup<T>(
+ (Class<T>) bean.getClass());
+ beanFieldGroup.setItemDataSource(bean);
+ beanFieldGroup.setBuffered(buffered);
+ beanFieldGroup.bindMemberFields(objectWithMemberFields);
+ return beanFieldGroup;
+ }
+
}
diff --git a/server/src/com/vaadin/data/fieldgroup/FieldGroup.java b/server/src/com/vaadin/data/fieldgroup/FieldGroup.java
index 4772dce5e6..e647bdbf6d 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;
@@ -1019,9 +1015,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/ContainerHierarchicalWrapper.java b/server/src/com/vaadin/data/util/ContainerHierarchicalWrapper.java
index 038b036f4e..eafd3573bc 100644
--- a/server/src/com/vaadin/data/util/ContainerHierarchicalWrapper.java
+++ b/server/src/com/vaadin/data/util/ContainerHierarchicalWrapper.java
@@ -95,7 +95,7 @@ public class ContainerHierarchicalWrapper implements Container.Hierarchical,
}
return 0;
}
- };
+ }
/**
* Constructs a new hierarchical wrapper for an existing Container. Works
diff --git a/server/src/com/vaadin/data/util/ListSet.java b/server/src/com/vaadin/data/util/ListSet.java
index c4201692d4..ccc9e0dbfd 100644
--- a/server/src/com/vaadin/data/util/ListSet.java
+++ b/server/src/com/vaadin/data/util/ListSet.java
@@ -82,7 +82,7 @@ public class ListSet<E> extends ArrayList<E> {
} else {
return false;
}
- };
+ }
/**
* Works as java.util.ArrayList#add(int, java.lang.Object) but returns
diff --git a/server/src/com/vaadin/data/util/MethodProperty.java b/server/src/com/vaadin/data/util/MethodProperty.java
index 5b9f3c90fd..5ec8ebffe0 100644
--- a/server/src/com/vaadin/data/util/MethodProperty.java
+++ b/server/src/com/vaadin/data/util/MethodProperty.java
@@ -19,6 +19,7 @@ package com.vaadin.data.util;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -85,6 +86,10 @@ public class MethodProperty<T> extends AbstractProperty<T> {
*/
private transient Class<? extends T> type;
+ private static final Object[] DEFAULT_GET_ARGS = new Object[0];
+
+ private static final Object[] DEFAULT_SET_ARGS = new Object[1];
+
/* Special serialization to handle method references */
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
@@ -108,7 +113,7 @@ public class MethodProperty<T> extends AbstractProperty<T> {
out.writeObject(null);
out.writeObject(null);
}
- };
+ }
/* Special serialization to handle method references */
private void readObject(java.io.ObjectInputStream in) throws IOException,
@@ -120,8 +125,9 @@ public class MethodProperty<T> extends AbstractProperty<T> {
Class<T> class1 = (Class<T>) SerializerHelper.readClass(in);
type = class1;
instance = in.readObject();
- setArgs = (Object[]) in.readObject();
- getArgs = (Object[]) in.readObject();
+ Object[] setArgs = (Object[]) in.readObject();
+ Object[] getArgs = (Object[]) in.readObject();
+ setArguments(getArgs, setArgs, setArgumentIndex);
String name = (String) in.readObject();
Class<?>[] paramTypes = SerializerHelper.readClassArray(in);
if (name != null) {
@@ -142,7 +148,7 @@ public class MethodProperty<T> extends AbstractProperty<T> {
} catch (NoSuchMethodException e) {
getLogger().log(Level.SEVERE, "Internal deserialization error", e);
}
- };
+ }
/**
* <p>
@@ -219,7 +225,7 @@ public class MethodProperty<T> extends AbstractProperty<T> {
type = (Class<T>) returnType;
}
- setArguments(new Object[] {}, new Object[] { null }, 0);
+ setArguments(DEFAULT_GET_ARGS, DEFAULT_SET_ARGS, 0);
this.instance = instance;
}
@@ -627,13 +633,15 @@ public class MethodProperty<T> extends AbstractProperty<T> {
*/
public void setArguments(Object[] getArgs, Object[] setArgs,
int setArgumentIndex) {
- this.getArgs = new Object[getArgs.length];
- for (int i = 0; i < getArgs.length; i++) {
- this.getArgs[i] = getArgs[i];
+ if (getArgs.length == 0) {
+ this.getArgs = DEFAULT_GET_ARGS;
+ } else {
+ this.getArgs = Arrays.copyOf(getArgs, getArgs.length);
}
- this.setArgs = new Object[setArgs.length];
- for (int i = 0; i < setArgs.length; i++) {
- this.setArgs[i] = setArgs[i];
+ if (Arrays.equals(setArgs, DEFAULT_SET_ARGS)) {
+ this.setArgs = DEFAULT_SET_ARGS;
+ } else {
+ this.setArgs = Arrays.copyOf(setArgs, setArgs.length);
}
this.setArgumentIndex = setArgumentIndex;
}
diff --git a/server/src/com/vaadin/data/util/MethodPropertyDescriptor.java b/server/src/com/vaadin/data/util/MethodPropertyDescriptor.java
index c0cdffe2e7..04a6ab1cc9 100644
--- a/server/src/com/vaadin/data/util/MethodPropertyDescriptor.java
+++ b/server/src/com/vaadin/data/util/MethodPropertyDescriptor.java
@@ -122,7 +122,7 @@ public class MethodPropertyDescriptor<BT> implements
} catch (NoSuchMethodException e) {
getLogger().log(Level.SEVERE, "Internal deserialization error", e);
}
- };
+ }
@Override
public String getName() {
diff --git a/server/src/com/vaadin/data/util/converter/DefaultConverterFactory.java b/server/src/com/vaadin/data/util/converter/DefaultConverterFactory.java
index b97a5b9047..fdf858a528 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 6d33000160..0000000000
--- a/server/src/com/vaadin/data/util/converter/StringToNumberConverter.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2000-2014 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.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/util/filter/Compare.java b/server/src/com/vaadin/data/util/filter/Compare.java
index 5a82392f77..1fcbe85580 100644
--- a/server/src/com/vaadin/data/util/filter/Compare.java
+++ b/server/src/com/vaadin/data/util/filter/Compare.java
@@ -37,7 +37,7 @@ public abstract class Compare implements Filter {
public enum Operation {
EQUAL, GREATER, LESS, GREATER_OR_EQUAL, LESS_OR_EQUAL
- };
+ }
private final Object propertyId;
private final Operation operation;
diff --git a/server/src/com/vaadin/data/util/filter/Like.java b/server/src/com/vaadin/data/util/filter/Like.java
index 656d3d5c32..9b7b2af292 100644
--- a/server/src/com/vaadin/data/util/filter/Like.java
+++ b/server/src/com/vaadin/data/util/filter/Like.java
@@ -23,11 +23,11 @@ public class Like implements Filter {
private final String value;
private boolean caseSensitive;
- public Like(String propertyId, String value) {
+ public Like(Object propertyId, String value) {
this(propertyId, value, true);
}
- public Like(String propertyId, String value, boolean caseSensitive) {
+ public Like(Object propertyId, String value, boolean caseSensitive) {
this.propertyId = propertyId;
this.value = value;
setCaseSensitive(caseSensitive);
diff --git a/server/src/com/vaadin/data/validator/BeanValidator.java b/server/src/com/vaadin/data/validator/BeanValidator.java
index b8c4e1b493..b25f7e687c 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/FieldEvents.java b/server/src/com/vaadin/event/FieldEvents.java
index 7164044cf3..364ac76ffd 100644
--- a/server/src/com/vaadin/event/FieldEvents.java
+++ b/server/src/com/vaadin/event/FieldEvents.java
@@ -325,6 +325,6 @@ public interface FieldEvents {
public void focus() {
fireEvent(new FocusEvent(component));
}
- };
+ }
}
diff --git a/server/src/com/vaadin/event/ListenerMethod.java b/server/src/com/vaadin/event/ListenerMethod.java
index 1f1bcd619d..3311ed705e 100644
--- a/server/src/com/vaadin/event/ListenerMethod.java
+++ b/server/src/com/vaadin/event/ListenerMethod.java
@@ -98,7 +98,7 @@ public class ListenerMethod implements EventListener, Serializable {
throw e;
}
- };
+ }
/* Special serialization to handle method references */
private void readObject(java.io.ObjectInputStream in) throws IOException,
@@ -113,7 +113,7 @@ public class ListenerMethod implements EventListener, Serializable {
} catch (SecurityException e) {
getLogger().log(Level.SEVERE, "Internal deserialization error", e);
}
- };
+ }
private static Method findHighestMethod(Class<?> cls, String method,
Class<?>[] paramTypes) {
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/navigator/Navigator.java b/server/src/com/vaadin/navigator/Navigator.java
index 2f0e91e9e6..80dad2244e 100644
--- a/server/src/com/vaadin/navigator/Navigator.java
+++ b/server/src/com/vaadin/navigator/Navigator.java
@@ -794,7 +794,7 @@ public class Navigator implements Serializable {
@Override
public View getView(String viewName) {
return view;
- };
+ }
@Override
public String getViewName(String navigationState) {
diff --git a/server/src/com/vaadin/server/AbstractClientConnector.java b/server/src/com/vaadin/server/AbstractClientConnector.java
index 1a8a5697ee..1e58fb30c0 100644
--- a/server/src/com/vaadin/server/AbstractClientConnector.java
+++ b/server/src/com/vaadin/server/AbstractClientConnector.java
@@ -132,13 +132,22 @@ public abstract class AbstractClientConnector implements ClientConnector,
/* Documentation copied from interface */
@Override
public void markAsDirty() {
- assert getSession() == null || getSession().hasLock() : "Session must be locked when markAsDirty() is called";
+ assert getSession() == null || getSession().hasLock() : buildLockAssertMessage("markAsDirty()");
UI uI = getUI();
if (uI != null) {
uI.getConnectorTracker().markDirty(this);
}
}
+ private String buildLockAssertMessage(String method) {
+ if (VaadinService.isOtherSessionLocked(getSession())) {
+ return "The session of this connecor is not locked, but there is another session that is locked. "
+ + "This might be caused by accidentally using a connector that belongs to another session.";
+ } else {
+ return "Session must be locked when " + method + " is called";
+ }
+ }
+
/**
* Registers an RPC interface implementation for this component.
*
@@ -217,7 +226,7 @@ public abstract class AbstractClientConnector implements ClientConnector,
* @see #getState()
*/
protected SharedState getState(boolean markAsDirty) {
- assert getSession() == null || getSession().hasLock() : "Session must be locked when getState() is called";
+ assert getSession() == null || getSession().hasLock() : buildLockAssertMessage("getState()");
if (null == sharedState) {
sharedState = createState();
diff --git a/server/src/com/vaadin/server/BootstrapHandler.java b/server/src/com/vaadin/server/BootstrapHandler.java
index a0acea6976..3c75764075 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 {
List<UIProvider> uiProviders = session.getUIProviders();
@@ -281,7 +282,7 @@ public abstract class BootstrapHandler extends SynchronizedRequestHandler {
* Enable Chrome Frame in all versions of IE if installed.
*/
head.appendElement("meta").attr("http-equiv", "X-UA-Compatible")
- .attr("content", "IE=10;chrome=1");
+ .attr("content", "IE=11;chrome=1");
String title = response.getUIProvider().getPageTitle(
new UICreateEvent(context.getRequest(), context.getUIClass()));
diff --git a/server/src/com/vaadin/server/ConnectorResourceHandler.java b/server/src/com/vaadin/server/ConnectorResourceHandler.java
index d50e0e80f2..6c486a2d65 100644
--- a/server/src/com/vaadin/server/ConnectorResourceHandler.java
+++ b/server/src/com/vaadin/server/ConnectorResourceHandler.java
@@ -87,6 +87,14 @@ public class ConnectorResourceHandler implements RequestHandler {
+ connector.getConnectorId()
+ ") did not handle connector request for " + key);
}
+ } catch (Exception e) {
+ session.lock();
+ try {
+ session.getCommunicationManager()
+ .handleConnectorRelatedException(connector, e);
+ } finally {
+ session.unlock();
+ }
} finally {
CurrentInstance.restoreInstances(oldInstances);
}
diff --git a/server/src/com/vaadin/server/Constants.java b/server/src/com/vaadin/server/Constants.java
index c2ed91f612..39329c32ce 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.1.2.vaadin2";
static final String INVALID_ATMOSPHERE_VERSION_WARNING = "\n"
+ "=================================================================\n"
diff --git a/server/src/com/vaadin/server/DefaultDeploymentConfiguration.java b/server/src/com/vaadin/server/DefaultDeploymentConfiguration.java
index e5150fef68..e72b411720 100644
--- a/server/src/com/vaadin/server/DefaultDeploymentConfiguration.java
+++ b/server/src/com/vaadin/server/DefaultDeploymentConfiguration.java
@@ -142,16 +142,25 @@ public class DefaultDeploymentConfiguration implements DeploymentConfiguration {
pkgName = pkg.getName();
} else {
final String className = systemPropertyBaseClass.getName();
- pkgName = new String(className.toCharArray(), 0,
- className.lastIndexOf('.'));
+ int index = className.lastIndexOf('.');
+ if (index >= 0) {
+ pkgName = className.substring(0, index);
+ } else {
+ pkgName = null;
+ }
+ }
+ if (pkgName == null) {
+ pkgName = "";
+ } else {
+ pkgName += '.';
}
- val = System.getProperty(pkgName + "." + parameterName);
+ val = System.getProperty(pkgName + parameterName);
if (val != null) {
return val;
}
// Try lowercased system properties
- val = System.getProperty(pkgName + "." + parameterName.toLowerCase());
+ val = System.getProperty(pkgName + parameterName.toLowerCase());
return val;
}
diff --git a/server/src/com/vaadin/server/DefaultErrorHandler.java b/server/src/com/vaadin/server/DefaultErrorHandler.java
index 127aaf0fc0..bbb15b4d62 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/DragAndDropService.java b/server/src/com/vaadin/server/DragAndDropService.java
index fc4d692ce8..087a670b5b 100644
--- a/server/src/com/vaadin/server/DragAndDropService.java
+++ b/server/src/com/vaadin/server/DragAndDropService.java
@@ -50,14 +50,14 @@ public class DragAndDropService implements VariableOwner, ClientConnector {
private DragAndDropEvent dragEvent;
- private final LegacyCommunicationManager manager;
+ private final VaadinSession session;
private AcceptCriterion acceptCriterion;
private ErrorHandler errorHandler;
- public DragAndDropService(LegacyCommunicationManager manager) {
- this.manager = manager;
+ public DragAndDropService(VaadinSession session) {
+ this.session = session;
}
@Override
@@ -233,8 +233,8 @@ public class DragAndDropService implements VariableOwner, ClientConnector {
outWriter.write(", \"dd\":");
- JsonPaintTarget jsonPaintTarget = new JsonPaintTarget(manager,
- outWriter, false);
+ JsonPaintTarget jsonPaintTarget = new JsonPaintTarget(
+ session.getCommunicationManager(), outWriter, false);
jsonPaintTarget.startTag("dd");
jsonPaintTarget.addAttribute("visitId", lastVisitId);
if (acceptCriterion != null) {
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 73611ffc8d..eb11cde343 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;
@@ -59,6 +62,25 @@ import com.vaadin.ui.ConnectorTracker;
*/
public class JsonCodec implements Serializable {
+ /* Immutable Encode Result representing null */
+ private static final EncodeResult ENCODE_RESULT_NULL = new EncodeResult(
+ JSONObject.NULL);
+
+ /* Immutable empty JSONArray */
+ private static final JSONArray EMPTY_JSON_ARRAY = new JSONArray() {
+ @Override
+ public JSONArray put(Object value) {
+ throw new UnsupportedOperationException(
+ "Immutable empty JSONArray.");
+ };
+
+ @Override
+ public JSONArray put(int index, Object value) {
+ throw new UnsupportedOperationException(
+ "Immutable empty JSONArray.");
+ };
+ };
+
public static interface BeanProperty extends Serializable {
public Object getValue(Object bean) throws Exception;
@@ -176,6 +198,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 +310,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 +636,7 @@ public class JsonCodec implements Serializable {
return decodedObject;
} catch (Exception e) {
- throw new JSONException(e);
+ throw new JSONException(e.getMessage());
}
}
@@ -624,7 +654,7 @@ public class JsonCodec implements Serializable {
}
if (null == value) {
- return encodeNull();
+ return ENCODE_RESULT_NULL;
}
if (value instanceof String[]) {
@@ -669,13 +699,17 @@ public class JsonCodec implements Serializable {
if (value instanceof Component
&& !(LegacyCommunicationManager
.isComponentVisibleToClient((Component) value))) {
- return encodeNull();
+ return ENCODE_RESULT_NULL;
}
return new EncodeResult(connector.getConnectorId());
} else if (value instanceof Enum) {
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
@@ -686,10 +720,6 @@ public class JsonCodec implements Serializable {
}
}
- private static EncodeResult encodeNull() {
- return new EncodeResult(JSONObject.NULL);
- }
-
public static Collection<BeanProperty> getProperties(Class<?> type)
throws IntrospectionException {
Collection<BeanProperty> cachedProperties = typePropertyCache.get(type);
@@ -750,7 +780,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);
}
@@ -766,14 +796,17 @@ public class JsonCodec implements Serializable {
if (fieldValue == JSONObject.NULL) {
fieldValue = null;
}
- if (referenceValue == JSONObject.NULL) {
- referenceValue = null;
- }
if (fieldValue == referenceValue) {
return true;
} else if (fieldValue == null || referenceValue == null) {
return false;
+ } else if (fieldValue instanceof Integer
+ && referenceValue instanceof Integer) {
+ return ((Integer) fieldValue).equals(referenceValue);
+ } else if (fieldValue instanceof Boolean
+ && referenceValue instanceof Boolean) {
+ return ((Boolean) fieldValue).equals(referenceValue);
} else {
return fieldValue.toString().equals(referenceValue.toString());
}
@@ -834,7 +867,7 @@ public class JsonCodec implements Serializable {
if (map.isEmpty()) {
// Client -> server encodes empty map as an empty array because of
// #8906. Do the same for server -> client to maintain symmetry.
- return new JSONArray();
+ return EMPTY_JSON_ARRAY;
}
if (keyType == String.class) {
diff --git a/server/src/com/vaadin/server/LegacyCommunicationManager.java b/server/src/com/vaadin/server/LegacyCommunicationManager.java
index ee5ecd471a..0dda5661bd 100644
--- a/server/src/com/vaadin/server/LegacyCommunicationManager.java
+++ b/server/src/com/vaadin/server/LegacyCommunicationManager.java
@@ -62,9 +62,6 @@ public class LegacyCommunicationManager implements Serializable {
*/
private final VaadinSession session;
- // TODO Move to VaadinSession (#11409)
- private DragAndDropService dragAndDropService;
-
// TODO Refactor (#11412)
private String requestThemeName;
@@ -277,28 +274,14 @@ public class LegacyCommunicationManager implements Serializable {
}
/**
- * @deprecated As of 7.1. See #11411.
+ * @deprecated As of 7.1. In 7.2 and later, use
+ * {@link ConnectorTracker#getConnector(String)
+ * uI.getConnectorTracker().getConnector(connectorId)} instead.
+ * See ticket #11411.
*/
@Deprecated
public ClientConnector getConnector(UI uI, String connectorId) {
- ClientConnector c = uI.getConnectorTracker().getConnector(connectorId);
- if (c == null
- && connectorId.equals(getDragAndDropService().getConnectorId())) {
- return getDragAndDropService();
- }
-
- return c;
- }
-
- /**
- * @deprecated As of 7.1. See #11409.
- */
- @Deprecated
- public DragAndDropService getDragAndDropService() {
- if (dragAndDropService == null) {
- dragAndDropService = new DragAndDropService(this);
- }
- return dragAndDropService;
+ return uI.getConnectorTracker().getConnector(connectorId);
}
/**
@@ -316,8 +299,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 375f589eb8..d58ba548e3 100644
--- a/server/src/com/vaadin/server/Page.java
+++ b/server/src/com/vaadin/server/Page.java
@@ -32,6 +32,7 @@ import com.vaadin.shared.ui.ui.PageClientRpc;
import com.vaadin.shared.ui.ui.PageState;
import com.vaadin.shared.ui.ui.UIConstants;
import com.vaadin.shared.ui.ui.UIState;
+import com.vaadin.shared.util.SharedUtil;
import com.vaadin.ui.JavaScript;
import com.vaadin.ui.LegacyWindow;
import com.vaadin.ui.Link;
@@ -476,6 +477,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;
@@ -633,10 +636,14 @@ public class Page implements Serializable {
}
public void init(VaadinRequest request) {
+ // NOTE: UI.refresh makes assumptions about the semantics of this method.
+ // It should be kept in sync if this method is changed.
+
// Extract special parameter sent by vaadinBootstrap.js
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,28 +669,62 @@ public class Page implements Serializable {
}
/**
- * Updates the internal state with the given values. Does not resize the
- * Page or browser window.
+ * 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;
+ }
+
+ /**
+ * For internal use only. Updates the internal state with the given values.
+ * Does not resize the Page or browser window.
+ *
+ * @deprecated As of 7.2, use
+ * {@link #updateBrowserWindowSize(int, int, boolean)} instead.
+ *
* @param width
- * The new width
+ * the new browser window width
* @param height
- * The new height
+ * the new browse window height
*/
+ @Deprecated
public void updateBrowserWindowSize(int width, int height) {
- boolean fireEvent = false;
+ updateBrowserWindowSize(width, height, true);
+ }
+
+ /**
+ * For internal use only. Updates the internal state with the given values.
+ * Does not resize the Page or browser window.
+ *
+ * @since 7.2
+ *
+ * @param width
+ * the new browser window width
+ * @param height
+ * the new browser window height
+ * @param fireEvents
+ * whether to fire {@link BrowserWindowResizeEvent} if the size
+ * changes
+ */
+ public void updateBrowserWindowSize(int width, int height,
+ boolean fireEvents) {
+ boolean sizeChanged = false;
if (width != browserWindowWidth) {
browserWindowWidth = width;
- fireEvent = true;
+ sizeChanged = true;
}
if (height != browserWindowHeight) {
browserWindowHeight = height;
- fireEvent = true;
+ sizeChanged = true;
}
- if (fireEvent) {
+ if (fireEvents && sizeChanged) {
fireEvent(new BrowserWindowResizeEvent(this, browserWindowWidth,
browserWindowHeight));
}
@@ -854,18 +895,33 @@ public class Page implements Serializable {
/**
* Navigates this page to the given URI. The contents of this page in the
* browser is replaced with whatever is returned for the given URI.
+ * <p>
+ * This method should not be used to start downloads, as the client side
+ * will assume the browser will navigate away when opening the URI. Use one
+ * of the {@code Page.open} methods or {@code FileDownloader} instead.
+ *
+ * @see #open(String, String)
+ * @see FileDownloader
*
* @param uri
* the URI to show
*/
public void setLocation(String uri) {
- openList.add(new OpenResource(uri, null, -1, -1, BORDER_DEFAULT, false));
+ openList.add(new OpenResource(uri, "_self", -1, -1, BORDER_DEFAULT,
+ false));
uI.markAsDirty();
}
/**
* Navigates this page to the given URI. The contents of this page in the
* browser is replaced with whatever is returned for the given URI.
+ * <p>
+ * This method should not be used to start downloads, as the client side
+ * will assume the browser will navigate away when opening the URI. Use one
+ * of the {@code Page.open} methods or {@code FileDownloader} instead.
+ *
+ * @see #open(String, String)
+ * @see FileDownloader
*
* @param uri
* the URI to show
@@ -888,14 +944,37 @@ public class Page implements Serializable {
/**
* For internal use only. Used to update the server-side location when the
* client-side location changes.
+ *
+ * @deprecated As of 7.2, use {@link #updateLocation(String, boolean)}
+ * instead.
+ *
+ * @param location
+ * the new location URI
*/
+ @Deprecated
public void updateLocation(String location) {
+ updateLocation(location, true);
+ }
+
+ /**
+ * For internal use only. Used to update the server-side location when the
+ * client-side location changes.
+ *
+ * @since 7.2
+ *
+ * @param location
+ * the new location URI
+ * @param fireEvents
+ * whether to fire {@link UriFragmentChangedEvent} if the URI
+ * fragment changes
+ */
+ public void updateLocation(String location, boolean fireEvents) {
try {
String oldUriFragment = this.location.getFragment();
this.location = new URI(location);
String newUriFragment = this.location.getFragment();
- if (newUriFragment == null ? oldUriFragment != null
- : !newUriFragment.equals(oldUriFragment)) {
+ if (fireEvents
+ && !SharedUtil.equals(oldUriFragment, newUriFragment)) {
fireEvent(new UriFragmentChangedEvent(this, newUriFragment));
}
} catch (URISyntaxException e) {
@@ -1119,12 +1198,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 f0c532b8e0..31dfa41ef9 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/Responsive.java b/server/src/com/vaadin/server/Responsive.java
new file mode 100644
index 0000000000..d69c204c94
--- /dev/null
+++ b/server/src/com/vaadin/server/Responsive.java
@@ -0,0 +1,161 @@
+/*
+ * 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 com.vaadin.ui.Component;
+
+/**
+ * An extension providing responsive layout capabilities to any Vaadin
+ * component. The Responsive extension allows specifying different CSS rules for
+ * different dimensions of extended components. This allows creating
+ * applications that provide an optimal viewing experience – easy reading and
+ * navigation with a minimum of resizing, panning, and scrolling – across a wide
+ * range of devices (from mobile phones to desktop computer monitors).
+ * <p>
+ * NOTE! You should always specify a relative (%) size for the extended
+ * component, doing otherwise will prevent the Responsive extension from
+ * working, as the component will not dynamically resize.
+ * </p>
+ * <p>
+ * All configuration of the visual breakpoints (ranges) for the component are
+ * done with CSS. Pixels (px) are the only supported unit. Fractional pixels are
+ * not supported.
+ * </p>
+ * <p>
+ * <i>Dynamic style injections (e.g. through
+ * <code>Page.getCurrent().getStyles().add(...)</code>) or any other style
+ * updates after the initial page load are not supported at the moment.</i>
+ * </p>
+ *
+ *
+ * <p>
+ * Example:
+ *
+ * <b>Java</b>
+ *
+ * <pre>
+ * CssLayout layout = new CssLayout();
+ * layout.setStyleName("responsive");
+ * layout.setSizeFull();
+ * Responsive.makeResponsive(layout);
+ * </pre>
+ *
+ * <b>SCSS</b>
+ *
+ * <pre>
+ * .v-csslayout.responsive {
+ * &[width-range~="0-300px"] {
+ * // Styles for the layout when its width is between 0 and 300 pixels
+ * }
+ * &[width-range~="301-500px"] {
+ * // Styles for the layout when its width is between 301 and 500 pixels
+ * }
+ * &[width-range~="501px-"] {
+ * // Styles for the layout when its width is over 500 pixels
+ * }
+ * &[height-range~="0-300px"] {
+ * // Styles for the layout when its height is between 0 and 300 pixels
+ * }
+ * &[height-range~="301-500px"] {
+ * // Styles for the layout when its height is between 301 and 500 pixels
+ * }
+ * &[height-range~="501-"] {
+ * // Styles for the layout when its height is over 500 pixels
+ * }
+ * }
+ * </pre>
+ *
+ * <b>CSS</b>
+ *
+ * <pre>
+ * .v-csslayout.responsive[width-range~="0-300px"] {
+ * // Styles for the layout when its width is between 0 and 300 pixels
+ * }
+ * .v-csslayout.responsive[width-range~="301-500px"] {
+ * // Styles for the layout when its width is between 301 and 500 pixels
+ * }
+ * .v-csslayout.responsive[width-range~="501-"] {
+ * // Styles for the layout when its width is over 500 pixels
+ * }
+ *
+ * .v-csslayout.responsive[height-range~="0-300px"] {
+ * // Styles for the layout when its height is between 0 and 300 pixels
+ * }
+ * .v-csslayout.responsive[height-range~="301-500px"] {
+ * // Styles for the layout when its height is between 301 and 500 pixels
+ * }
+ * .v-csslayout.responsive[height-range~="501px-"] {
+ * // Styles for the layout when its height is over 500 pixels
+ * }
+ * </pre>
+ *
+ * </p>
+ * <p>
+ * <b>Note:</b> <i>The defined ranges are applied on a global context, so even
+ * if you would write your CSS to target only a given context, the ranges would
+ * be applied to all other instances with the same style name.</i>
+ * </p>
+ * <p>
+ * E.g. this would affect all CssLayout instances in the application, even
+ * though the CSS implies it would only affect CssLayout instances inside a
+ * parent with a style name "foobar":
+ * </p>
+ *
+ * <pre>
+ * .foobar .v-csslayout[width-range~="0px-100px"] {
+ * // These properties will affect all responsive CssLayout instances
+ * }
+ * </pre>
+ *
+ * <p>
+ * To scope the ranges, use an additional style name for the target component,
+ * and add that to your CSS selector:
+ * </p>
+ *
+ * <pre>
+ * .v-csslayout.mystyle[width-range="0px-100px"] {
+ * // These properties will only affect responsive CssLayout instances with an additional style name of 'mystyle'
+ * }
+ * </pre>
+ *
+ * @author Vaadin Ltd
+ * @since 7.2
+ */
+public class Responsive extends AbstractExtension {
+
+ /**
+ * Creates a new instance, which can be used to extend a component.
+ */
+ protected Responsive() {
+ }
+
+ /**
+ * Enable responsive width and height range styling for the target component
+ * or UI instance.
+ *
+ * @param target
+ * The component which should be able to respond to width and/or
+ * height changes.
+ */
+ public static void makeResponsive(Component... components) {
+ for (Component c : components) {
+ if (c instanceof AbstractClientConnector) {
+ new Responsive().extend((AbstractClientConnector) c);
+ }
+ }
+ }
+}
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..1549d82d51
--- /dev/null
+++ b/server/src/com/vaadin/server/ServiceDestroyListener.java
@@ -0,0 +1,39 @@
+/*
+ * 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 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 59de4fc52b..56abfbb8f3 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 bd63796448..51e9da5800 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/UIProvider.java b/server/src/com/vaadin/server/UIProvider.java
index fd010ac48e..d3d834cad7 100644
--- a/server/src/com/vaadin/server/UIProvider.java
+++ b/server/src/com/vaadin/server/UIProvider.java
@@ -129,6 +129,11 @@ public abstract class UIProvider implements Serializable {
* detect that the application is opened in a browser window where it has
* previously been open. The framework attempts to discover this by checking
* the value of window.name in the browser.
+ * <p>
+ * Whenever a preserved UI is reused, its
+ * {@link UI#refresh(com.vaadin.server.VaadinRequest) refresh} method is
+ * invoked by the framework first.
+ *
*
* @param event
* the UI create event with information about the UI and the
diff --git a/server/src/com/vaadin/server/VaadinPortlet.java b/server/src/com/vaadin/server/VaadinPortlet.java
index 0c31407b76..77a1b18d10 100644
--- a/server/src/com/vaadin/server/VaadinPortlet.java
+++ b/server/src/com/vaadin/server/VaadinPortlet.java
@@ -29,6 +29,7 @@ import javax.portlet.ActionResponse;
import javax.portlet.EventRequest;
import javax.portlet.EventResponse;
import javax.portlet.GenericPortlet;
+import javax.portlet.PortalContext;
import javax.portlet.PortletConfig;
import javax.portlet.PortletContext;
import javax.portlet.PortletException;
@@ -51,61 +52,83 @@ import com.vaadin.util.CurrentInstance;
* Portlet 2.0 base class. This replaces the servlet in servlet/portlet 1.0
* deployments and handles various portlet requests from the browser.
*
- * TODO Document me!
- *
- * @author peholmst
+ * @author Vaadin Ltd
*/
public class VaadinPortlet extends GenericPortlet implements Constants,
Serializable {
/**
- * @deprecated As of 7.0. Will likely change or be removed in a future
- * version
+ * Base class for portlet requests that need access to HTTP servlet
+ * requests.
*/
- @Deprecated
- public static final String RESOURCE_URL_ID = "APP";
-
- public static class VaadinHttpAndPortletRequest extends
+ public static abstract class VaadinHttpAndPortletRequest extends
VaadinPortletRequest {
+ /**
+ * Constructs a new {@link VaadinHttpAndPortletRequest}.
+ *
+ * @since 7.2
+ * @param request
+ * {@link PortletRequest} to be wrapped
+ * @param vaadinService
+ * {@link VaadinPortletService} associated with this request
+ */
public VaadinHttpAndPortletRequest(PortletRequest request,
- HttpServletRequest originalRequest,
VaadinPortletService vaadinService) {
super(request, vaadinService);
- this.originalRequest = originalRequest;
}
- private final HttpServletRequest originalRequest;
+ private HttpServletRequest originalRequest;
+
+ /**
+ * Returns the original HTTP servlet request for this portlet request.
+ *
+ * @since 7.2
+ * @param request
+ * {@link PortletRequest} used to
+ * @return the original HTTP servlet request
+ */
+ protected abstract HttpServletRequest getServletRequest(
+ PortletRequest request);
+
+ private HttpServletRequest getOriginalRequest() {
+ if (originalRequest == null) {
+ PortletRequest request = getRequest();
+ originalRequest = getServletRequest(request);
+ }
+
+ return originalRequest;
+ }
@Override
public String getParameter(String name) {
String parameter = super.getParameter(name);
if (parameter == null) {
- parameter = originalRequest.getParameter(name);
+ parameter = getOriginalRequest().getParameter(name);
}
return parameter;
}
@Override
public String getRemoteAddr() {
- return originalRequest.getRemoteAddr();
+ return getOriginalRequest().getRemoteAddr();
}
@Override
public String getRemoteHost() {
- return originalRequest.getRemoteHost();
+ return getOriginalRequest().getRemoteHost();
}
@Override
public int getRemotePort() {
- return originalRequest.getRemotePort();
+ return getOriginalRequest().getRemotePort();
}
@Override
public String getHeader(String name) {
String header = super.getHeader(name);
if (header == null) {
- header = originalRequest.getHeader(name);
+ header = getOriginalRequest().getHeader(name);
}
return header;
}
@@ -114,7 +137,7 @@ public class VaadinPortlet extends GenericPortlet implements Constants,
public Enumeration<String> getHeaderNames() {
Enumeration<String> headerNames = super.getHeaderNames();
if (headerNames == null) {
- headerNames = originalRequest.getHeaderNames();
+ headerNames = getOriginalRequest().getHeaderNames();
}
return headerNames;
}
@@ -123,7 +146,7 @@ public class VaadinPortlet extends GenericPortlet implements Constants,
public Enumeration<String> getHeaders(String name) {
Enumeration<String> headers = super.getHeaders(name);
if (headers == null) {
- headers = originalRequest.getHeaders(name);
+ headers = getOriginalRequest().getHeaders(name);
}
return headers;
}
@@ -132,64 +155,21 @@ public class VaadinPortlet extends GenericPortlet implements Constants,
public Map<String, String[]> getParameterMap() {
Map<String, String[]> parameterMap = super.getParameterMap();
if (parameterMap == null) {
- parameterMap = originalRequest.getParameterMap();
+ parameterMap = getOriginalRequest().getParameterMap();
}
return parameterMap;
}
}
- public static class VaadinGateinRequest extends VaadinHttpAndPortletRequest {
- public VaadinGateinRequest(PortletRequest request,
- VaadinPortletService vaadinService) {
- super(request, getOriginalRequest(request), vaadinService);
- }
-
- private static final HttpServletRequest getOriginalRequest(
- PortletRequest request) {
- try {
- Method getRealReq = request.getClass().getMethod(
- "getRealRequest");
- HttpServletRequestWrapper origRequest = (HttpServletRequestWrapper) getRealReq
- .invoke(request);
- return origRequest;
- } catch (Exception e) {
- throw new IllegalStateException("GateIn request not detected",
- e);
- }
- }
- }
-
- // Intentionally internal, will be refactored out in 7.2.
- static class WebSpherePortalRequest extends VaadinHttpAndPortletRequest {
-
- public WebSpherePortalRequest(PortletRequest request,
- VaadinPortletService vaadinService) {
- super(request, getServletRequest(request), vaadinService);
- }
-
- private static HttpServletRequest getServletRequest(
- PortletRequest request) {
- try {
- Class<?> portletUtils = Class
- .forName("com.ibm.ws.portletcontainer.portlet.PortletUtils");
- Method getHttpServletRequest = portletUtils.getMethod(
- "getHttpServletRequest", PortletRequest.class);
-
- return (HttpServletRequest) getHttpServletRequest.invoke(null,
- request);
- } catch (Exception e) {
- throw new IllegalStateException(
- "WebSphere Portal request not detected.");
- }
- }
- }
-
+ /**
+ * Portlet request for Liferay.
+ */
public static class VaadinLiferayRequest extends
VaadinHttpAndPortletRequest {
public VaadinLiferayRequest(PortletRequest request,
VaadinPortletService vaadinService) {
- super(request, getOriginalRequest(request), vaadinService);
+ super(request, vaadinService);
}
@Override
@@ -219,7 +199,7 @@ public class VaadinPortlet extends GenericPortlet implements Constants,
* @throws Exception
* @return return value of the invoked method
*/
- private static Object invokeStaticLiferayMethod(String className,
+ private Object invokeStaticLiferayMethod(String className,
String methodName, Object argument, String parameterClassName)
throws Exception {
Thread currentThread = Thread.currentThread();
@@ -251,8 +231,8 @@ public class VaadinPortlet extends GenericPortlet implements Constants,
}
}
- private static HttpServletRequest getOriginalRequest(
- PortletRequest request) {
+ @Override
+ protected HttpServletRequest getServletRequest(PortletRequest request) {
try {
// httpRequest = PortalUtil.getHttpServletRequest(request);
HttpServletRequest httpRequest = (HttpServletRequest) invokeStaticLiferayMethod(
@@ -272,10 +252,68 @@ public class VaadinPortlet extends GenericPortlet implements Constants,
e);
}
}
+ }
+
+ /**
+ * Portlet request for GateIn.
+ */
+ public static class VaadinGateInRequest extends VaadinHttpAndPortletRequest {
+ public VaadinGateInRequest(PortletRequest request,
+ VaadinPortletService vaadinService) {
+ super(request, vaadinService);
+ }
+
+ @Override
+ protected HttpServletRequest getServletRequest(PortletRequest request) {
+ try {
+ Method getRealReq = request.getClass().getMethod(
+ "getRealRequest");
+ HttpServletRequestWrapper origRequest = (HttpServletRequestWrapper) getRealReq
+ .invoke(request);
+ return origRequest;
+ } catch (Exception e) {
+ throw new IllegalStateException("GateIn request not detected",
+ e);
+ }
+ }
+ }
+ /**
+ * Portlet request for WebSphere Portal.
+ */
+ public static class VaadinWebSpherePortalRequest extends
+ VaadinHttpAndPortletRequest {
+
+ public VaadinWebSpherePortalRequest(PortletRequest request,
+ VaadinPortletService vaadinService) {
+ super(request, vaadinService);
+ }
+
+ @Override
+ protected HttpServletRequest getServletRequest(PortletRequest request) {
+ try {
+ Class<?> portletUtils = Class
+ .forName("com.ibm.ws.portletcontainer.portlet.PortletUtils");
+ Method getHttpServletRequest = portletUtils.getMethod(
+ "getHttpServletRequest", PortletRequest.class);
+
+ return (HttpServletRequest) getHttpServletRequest.invoke(null,
+ request);
+ } catch (Exception e) {
+ throw new IllegalStateException(
+ "WebSphere Portal request not detected.");
+ }
+ }
}
/**
+ * @deprecated As of 7.0. Will likely change or be removed in a future
+ * version
+ */
+ @Deprecated
+ public static final String RESOURCE_URL_ID = "APP";
+
+ /**
* This portlet parameter is used to add styles to the main element. E.g
* "height:500px" generates a style="height:500px" to the main element.
*
@@ -390,7 +428,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)) {
@@ -444,50 +481,26 @@ public class VaadinPortlet extends GenericPortlet implements Constants,
*
* @param request
* The original PortletRequest
- * @return A wrapped version of the PorletRequest
+ * @return A wrapped version of the PortletRequest
*/
protected VaadinPortletRequest createVaadinRequest(PortletRequest request) {
- if (isLiferay(request)) {
- return new VaadinLiferayRequest(request, getService());
- } else if (isGateIn(request)) {
- return new VaadinGateinRequest(request, getService());
- } else if (isWebSphere(request)) {
- return new WebSpherePortalRequest(request, getService());
- } else {
+ PortalContext portalContext = request.getPortalContext();
+ String portalInfo = portalContext.getPortalInfo().toLowerCase().trim();
+ VaadinPortletService service = getService();
- return new VaadinPortletRequest(request, getService());
+ if (portalInfo.contains("gatein")) {
+ return new VaadinGateInRequest(request, service);
}
- }
- /**
- * Returns true if the portlet request is from Liferay.
- *
- * @param request
- * @return True if Liferay, false otherwise
- */
- private static boolean isLiferay(PortletRequest request) {
- String portalInfo = request.getPortalContext().getPortalInfo()
- .toLowerCase();
- return portalInfo.contains("liferay");
- }
-
- /**
- * Returns true if the portlet request if from GateIn
- *
- * @param request
- * @return True if GateIn, false otherwise
- */
- private static boolean isGateIn(PortletRequest request) {
- String portalInfo = request.getPortalContext().getPortalInfo()
- .toLowerCase();
- return portalInfo.contains("gatein");
- }
+ if (portalInfo.contains("liferay")) {
+ return new VaadinLiferayRequest(request, service);
+ }
- private static boolean isWebSphere(PortletRequest request) {
- String portalInfo = request.getPortalContext().getPortalInfo()
- .toLowerCase();
+ if (portalInfo.contains("websphere portal")) {
+ return new VaadinWebSpherePortalRequest(request, service);
+ }
- return portalInfo.contains("websphere portal");
+ return new VaadinPortletRequest(request, service);
}
private VaadinPortletResponse createVaadinResponse(PortletResponse response) {
@@ -538,6 +551,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 131d84baa6..f2181c7c6f 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;
@@ -51,6 +53,7 @@ import org.json.JSONObject;
import com.vaadin.annotations.PreserveOnRefresh;
import com.vaadin.event.EventRouter;
import com.vaadin.server.VaadinSession.FutureAccess;
+import com.vaadin.server.VaadinSession.State;
import com.vaadin.server.communication.FileUploadHandler;
import com.vaadin.server.communication.HeartbeatHandler;
import com.vaadin.server.communication.PublishedFileHandler;
@@ -97,6 +100,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}.
*/
@@ -440,6 +447,12 @@ public abstract class VaadinService implements Serializable {
session.accessSynchronously(new Runnable() {
@Override
public void run() {
+ if (session.getState() == State.CLOSED) {
+ return;
+ }
+ if (session.getState() == State.OPEN) {
+ closeSession(session);
+ }
ArrayList<UI> uis = new ArrayList<UI>(session.getUIs());
for (final UI ui : uis) {
ui.accessSynchronously(new Runnable() {
@@ -463,6 +476,8 @@ public abstract class VaadinService implements Serializable {
// destroy listeners
eventRouter.fireEvent(new SessionDestroyEvent(
VaadinService.this, session), session.getErrorHandler());
+
+ session.setState(State.CLOSED);
}
});
}
@@ -1121,7 +1136,7 @@ public abstract class VaadinService implements Serializable {
closeInactiveUIs(session);
removeClosedUIs(session);
} else {
- if (!session.isClosing()) {
+ if (session.getState() == State.OPEN) {
closeSession(session);
if (session.getSession() != null) {
getLogger().log(Level.FINE, "Closing inactive session {0}",
@@ -1273,7 +1288,7 @@ public abstract class VaadinService implements Serializable {
* @return true if the session is active, false if it could be closed.
*/
private boolean isSessionActive(VaadinSession session) {
- if (session.isClosing() || session.getSession() == null) {
+ if (session.getState() != State.OPEN || session.getSession() == null) {
return false;
} else {
long now = System.currentTimeMillis();
@@ -1571,9 +1586,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) {
@@ -1629,15 +1644,33 @@ public abstract class VaadinService implements Serializable {
* if the current thread holds the lock for another session
*/
public static void verifyNoOtherSessionLocked(VaadinSession session) {
- VaadinSession otherSession = VaadinSession.getCurrent();
- if (otherSession != null && otherSession != session
- && otherSession.hasLock()) {
+ if (isOtherSessionLocked(session)) {
throw new IllegalStateException(
"Can't access session while another session is locked by the same thread. This restriction is intended to help avoid deadlocks.");
}
}
/**
+ * Checks whether there might be some {@link VaadinSession} other than the
+ * provided one for which the current thread holds a lock. This method might
+ * not detect all cases where some other session is locked, but it should
+ * cover the most typical situations.
+ *
+ * @since 7.2
+ * @param session
+ * the session that is expected to be locked
+ * @return <code>true</code> if another session is also locked by the
+ * current thread; <code>false</code> if no such session was found
+ */
+ public static boolean isOtherSessionLocked(VaadinSession session) {
+ VaadinSession otherSession = VaadinSession.getCurrent();
+ if (otherSession == null || otherSession == session) {
+ return false;
+ }
+ return otherSession.hasLock();
+ }
+
+ /**
* Verifies that the given CSRF token (aka double submit cookie) is valid
* for the given session. This is used to protect against Cross Site Request
* Forgery attacks.
@@ -1778,4 +1811,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 15f243d6f7..81c3f374ea 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;
@@ -259,14 +258,22 @@ public class VaadinServlet extends HttpServlet implements Constants {
*/
protected boolean handleContextRootWithoutSlash(HttpServletRequest request,
HttpServletResponse response) throws IOException {
+ // Query parameters like "?a=b" are handled by the servlet container but
+ // path parameter (e.g. ;jsessionid=) needs to be handled here
+ String location = request.getRequestURI();
+
+ String lastPathParameter = getLastPathParameter(location);
+ location = location.substring(0,
+ location.length() - lastPathParameter.length());
+
if ((request.getPathInfo() == null || "/".equals(request.getPathInfo()))
&& "".equals(request.getServletPath())
- && !request.getRequestURI().endsWith("/")) {
+ && !location.endsWith("/")) {
/*
* Path info is for the root but request URI doesn't end with a
* slash -> redirect to the same URI but with an ending slash.
*/
- String location = request.getRequestURI() + "/";
+ location = location + "/" + lastPathParameter;
String queryString = request.getQueryString();
if (queryString != null) {
location += '?' + queryString;
@@ -278,6 +285,40 @@ public class VaadinServlet extends HttpServlet implements Constants {
}
}
+ /**
+ * Finds any path parameter added to the last part of the uri. A path
+ * parameter is any string separated by ";" from the path and ends in / or
+ * at the end of the string.
+ * <p>
+ * For example the uri http://myhost.com/foo;a=1/bar;b=1 contains two path
+ * parameters, {@literal a=1} related to {@literal /foo} and {@literal b=1}
+ * related to /bar.
+ * <p>
+ * For http://myhost.com/foo;a=1/bar;b=1 this method will return ;b=1
+ *
+ * @since 7.2
+ * @param uri
+ * a URI
+ * @return the last path parameter of the uri including the semicolon or an
+ * empty string. Never null.
+ */
+ protected static String getLastPathParameter(String uri) {
+ int lastPathStart = uri.lastIndexOf('/');
+ if (lastPathStart == -1) {
+ return "";
+ }
+
+ int semicolonPos = uri.indexOf(';', lastPathStart);
+ if (semicolonPos < 0) {
+ // No path parameter for the last part
+ return "";
+ } else {
+ // This includes the semicolon.
+ String semicolonString = uri.substring(semicolonPos);
+ return semicolonString;
+ }
+ }
+
private VaadinServletResponse createVaadinResponse(
HttpServletResponse response) {
return new VaadinServletResponse(response, getService());
@@ -670,21 +711,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 +724,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.
*
@@ -826,7 +894,7 @@ public class VaadinServlet extends HttpServlet implements Constants {
// cache it
response.setHeader("Cache-Control", "no-cache");
final String mimetype = getService().getMimeType(filename);
- writeResponse(response, mimetype, scss.toString());
+ writeResponse(response, mimetype, scss.printState());
return true;
}
@@ -982,20 +1050,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 +1131,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 a2f8febbca..bbabd881f8 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;
@@ -165,6 +167,33 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
}
/**
+ * The lifecycle state of a VaadinSession.
+ *
+ * @since 7.2
+ */
+ public enum State {
+ /**
+ * The session is active and accepting client requests.
+ */
+ OPEN,
+ /**
+ * The {@link VaadinSession#close() close} method has been called; the
+ * session will be closed as soon as the current request ends.
+ */
+ CLOSING,
+ /**
+ * The session is closed; all the {@link UI}s have been removed and
+ * {@link SessionDestroyListener}s have been called.
+ */
+ CLOSED;
+
+ private boolean isValidChange(State newState) {
+ return (this == OPEN && newState == CLOSING)
+ || (this == CLOSING && newState == CLOSED);
+ }
+ }
+
+ /**
* The name of the parameter that is by default used in e.g. web.xml to
* define the name of the default {@link UI} class.
*/
@@ -205,7 +234,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();
@@ -213,6 +242,8 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
protected WebBrowser browser = new WebBrowser();
+ private DragAndDropService dragAndDropService;
+
private LegacyCommunicationManager communicationManager;
private long cumulativeRequestDuration = 0;
@@ -221,7 +252,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
private long lastRequestTimestamp = System.currentTimeMillis();
- private boolean closing = false;
+ private State state = State.OPEN;
private transient WrappedSession session;
@@ -275,24 +306,20 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
} else if (VaadinService.getCurrentRequest() != null
&& getCurrent() == this) {
assert hasLock();
- /*
- * Ignore if the session is being moved to a different backing
- * session or if GAEVaadinServlet is doing its normal cleanup.
- */
+ // Ignore if the session is being moved to a different backing
+ // session or if GAEVaadinServlet is doing its normal cleanup.
if (getAttribute(VaadinService.PRESERVE_UNBOUND_SESSION_ATTRIBUTE) == Boolean.TRUE) {
return;
}
// There is still a request in progress for this session. The
// session will be destroyed after the response has been written.
- if (!isClosing()) {
+ if (getState() == State.OPEN) {
close();
}
} else {
- /*
- * We are not in a request related to this session so we can
- * immediately destroy it
- */
+ // We are not in a request related to this session so we can destroy
+ // it as soon as we acquire the lock.
service.fireSessionDestroy(this);
}
session = null;
@@ -393,6 +420,13 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
return communicationManager;
}
+ public DragAndDropService getDragAndDropService() {
+ if (dragAndDropService == null) {
+ dragAndDropService = new DragAndDropService(this);
+ }
+ return dragAndDropService;
+ }
+
/**
* Loads the VaadinSession for the given service and WrappedSession from the
* HTTP session.
@@ -424,6 +458,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 +888,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);
+ }
}
/**
@@ -1099,20 +1162,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 +1178,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();
+ }
+ }
}
/**
@@ -1186,19 +1249,52 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
*/
public void close() {
assert hasLock();
- closing = true;
+ state = State.CLOSING;
}
/**
- * Returns whether this session is marked to be closed.
+ * Returns whether this session is marked to be closed. Note that this
+ * method also returns true if the session is actually already closed.
*
* @see #close()
*
+ * @deprecated As of 7.2, use
+ * <code>{@link #getState() getState() != State.OPEN}</code>
+ * instead.
+ *
* @return true if this session is marked to be closed, false otherwise
*/
+ @Deprecated
public boolean isClosing() {
assert hasLock();
- return closing;
+ return state == State.CLOSING || state == State.CLOSED;
+ }
+
+ /**
+ * Returns the lifecycle state of this session.
+ *
+ * @since 7.2
+ * @return the current state
+ */
+ public State getState() {
+ assert hasLock();
+ return state;
+ }
+
+ /**
+ * Sets the lifecycle state of this session. The allowed transitions are
+ * OPEN to CLOSING and CLOSING to CLOSED.
+ *
+ * @since 7.2
+ * @param state
+ * the new state
+ */
+ protected void setState(State state) {
+ assert hasLock();
+ assert this.state.isValidChange(state) : "Invalid session state change "
+ + this.state + "->" + state;
+
+ this.state = state;
}
private static final Logger getLogger() {
@@ -1340,4 +1436,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/WrappedHttpSession.java b/server/src/com/vaadin/server/WrappedHttpSession.java
index a2cc7d001b..137ccf2cdd 100644
--- a/server/src/com/vaadin/server/WrappedHttpSession.java
+++ b/server/src/com/vaadin/server/WrappedHttpSession.java
@@ -85,6 +85,20 @@ public class WrappedHttpSession implements WrappedSession {
@Override
public void invalidate() {
+ if (session == null) {
+ throw new IllegalStateException(
+ "Session is null and cannot be invalidated");
+ }
+
+ if (session.getClass().getName()
+ .equals("org.atmosphere.util.FakeHttpSession")) {
+ throw new UnsupportedOperationException(
+ "FakeHttpSession cannot be invalidated. "
+ + "This typically means you are using websockets together with Tomcat 7. "
+ + "Because Tomcat 7 does not support sharing the HTTP session between standard HTTP requests and websockets, a copy of the session is used for websockets. "
+ + "Invalidating this session does not have the desired effect. "
+ + "To resolve this, upgrade to Tomcat 8 or use another transport mechanism than websockets.");
+ }
session.invalidate();
}
diff --git a/server/src/com/vaadin/server/communication/AtmospherePushConnection.java b/server/src/com/vaadin/server/communication/AtmospherePushConnection.java
index 11563b0c7c..4727720f4b 100644
--- a/server/src/com/vaadin/server/communication/AtmospherePushConnection.java
+++ b/server/src/com/vaadin/server/communication/AtmospherePushConnection.java
@@ -30,14 +30,13 @@ 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,52 +91,83 @@ 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;
+ private Future<Object> outgoingMessage;
- 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());
@@ -157,7 +187,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 +209,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);
+ }
}
/**
@@ -229,15 +287,12 @@ public class AtmospherePushConnection implements PushConnection {
outgoingMessage = null;
}
- resource.resume();
resource = null;
+ state = State.DISCONNECTED;
}
- /**
- * @since
- * @return
- */
private static Logger getLogger() {
return Logger.getLogger(AtmospherePushConnection.class.getName());
}
+
}
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..429941abfd
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/DateSerializer.java
@@ -0,0 +1,42 @@
+/*
+ * 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.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 38b78de02c..22c6a76106 100644
--- a/server/src/com/vaadin/server/communication/FileUploadHandler.java
+++ b/server/src/com/vaadin/server/communication/FileUploadHandler.java
@@ -271,8 +271,7 @@ public class FileUploadHandler implements RequestHandler {
return true;
}
- source = session.getCommunicationManager().getConnector(uI,
- connectorId);
+ source = uI.getConnectorTracker().getConnector(connectorId);
} finally {
session.unlock();
}
@@ -287,7 +286,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;
}
@@ -339,7 +338,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;
@@ -406,9 +405,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();
@@ -477,7 +489,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
@@ -507,7 +519,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(
@@ -515,7 +527,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 3ef7974d94..c6711d1d18 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 3f76eafe35..52efcfcd89 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 dd494c06ca..c6126f9d21 100644
--- a/server/src/com/vaadin/server/communication/PushHandler.java
+++ b/server/src/com/vaadin/server/communication/PushHandler.java
@@ -18,8 +18,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;
@@ -29,6 +27,7 @@ import org.atmosphere.cpr.AtmosphereResource;
import org.atmosphere.cpr.AtmosphereResource.TRANSPORT;
import org.atmosphere.cpr.AtmosphereResourceEvent;
import org.atmosphere.cpr.AtmosphereResourceEventListenerAdapter;
+import org.atmosphere.handler.AbstractReflectorAtmosphereHandler;
import org.json.JSONException;
import com.vaadin.server.ErrorEvent;
@@ -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;
@@ -54,8 +52,31 @@ import com.vaadin.ui.UI;
* @author Vaadin Ltd
* @since 7.1
*/
-public class PushHandler extends AtmosphereResourceEventListenerAdapter
- implements AtmosphereHandler {
+public class PushHandler extends AtmosphereResourceEventListenerAdapter {
+
+ AtmosphereHandler handler = new AbstractReflectorAtmosphereHandler() {
+
+ @Override
+ public void onStateChange(AtmosphereResourceEvent event)
+ throws IOException {
+ super.onStateChange(event);
+ if (event.isCancelled() || event.isResumedOnTimeout()) {
+ disconnect(event);
+ }
+ }
+
+ @Override
+ public void onRequest(AtmosphereResource resource) {
+ AtmosphereRequest req = resource.getRequest();
+
+ if (req.getMethod().equalsIgnoreCase("GET")) {
+ callWithUi(resource, establishCallback);
+ } else if (req.getMethod().equalsIgnoreCase("POST")) {
+ callWithUi(resource, receiveCallback);
+ }
+ }
+
+ };
/**
* Callback interface used internally to process an event with the
@@ -75,8 +96,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 +105,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 +128,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);
}
};
@@ -174,14 +186,46 @@ public class PushHandler extends AtmosphereResourceEventListenerAdapter
}
};
- private static final String LONG_PADDING;
+ /**
+ * Callback used when a connection is closed, either deliberately or because
+ * an error occurred.
+ */
+ private final PushEventCallback disconnectCallback = new PushEventCallback() {
+ @Override
+ public void run(AtmosphereResource resource, UI ui) throws IOException {
+ PushMode pushMode = ui.getPushConfiguration().getPushMode();
+ AtmospherePushConnection connection = getConnectionForUI(ui);
- static {
- char[] array = new char[4096];
- Arrays.fill(array, '-');
- LONG_PADDING = String.copyValueOf(array);
+ String id = resource.uuid();
+
+ if (connection == null) {
+ getLogger()
+ .log(Level.WARNING,
+ "Could not find push connection to close: {0} with transport {1}",
+ new Object[] { id, resource.transport() });
+ } else {
+ if (!pushMode.isEnabled()) {
+ /*
+ * The client is expected to close the connection after push
+ * mode has been set to disabled.
+ */
+ getLogger().log(Level.FINER,
+ "Connection closed for resource {0}", id);
+ } else {
+ /*
+ * Unexpected cancel, e.g. if the user closes the browser
+ * tab.
+ */
+ getLogger()
+ .log(Level.FINER,
+ "Connection unexpectedly closed for resource {0} with transport {1}",
+ new Object[] { id, resource.transport() });
+ }
+ connection.disconnect();
+ }
+ }
+ };
- }
private VaadinServletService service;
public PushHandler(VaadinServletService service) {
@@ -297,66 +341,12 @@ public class PushHandler extends AtmosphereResourceEventListenerAdapter
}
}
- @Override
- public void onRequest(AtmosphereResource resource) {
- AtmosphereRequest req = resource.getRequest();
-
- if (req.getMethod().equalsIgnoreCase("GET")) {
- callWithUi(resource, establishCallback);
- } else if (req.getMethod().equalsIgnoreCase("POST")) {
- callWithUi(resource, receiveCallback);
- }
- }
-
private static AtmospherePushConnection getConnectionForUI(UI ui) {
PushConnection pushConnection = ui.getPushConnection();
if (pushConnection instanceof AtmospherePushConnection) {
- assert pushConnection.isConnected();
return (AtmospherePushConnection) pushConnection;
- }
- return null;
- }
-
- @Override
- public void onStateChange(AtmosphereResourceEvent event) throws IOException {
- AtmosphereResource resource = event.getResource();
-
- String id = resource.uuid();
- if (event.isCancelled() || event.isResumedOnTimeout()) {
- getLogger().log(Level.FINER,
- "Cancelled connection for resource {0}", id);
- disconnect(event);
- } else if (event.isResuming()) {
- // A connection that was suspended earlier was resumed (committed to
- // the client.) Should only happen if the transport is JSONP or
- // long-polling.
- getLogger().log(Level.FINER, "Resuming request for resource {0}",
- id);
} else {
- // A message was broadcast to this resource and should be sent to
- // the client. We don't do any actual broadcasting, in the sense of
- // sending to multiple recipients; any UIDL message is specific to a
- // single client.
- getLogger().log(Level.FINER, "Writing message to resource {0}", id);
-
- Writer writer = resource.getResponse().getWriter();
- writer.write(event.getMessage().toString());
-
- switch (resource.transport()) {
- case WEBSOCKET:
- break;
- case SSE:
- case STREAMING:
- writer.flush();
- break;
- case JSONP:
- case LONG_POLLING:
- resource.resume();
- break;
- default:
- getLogger().log(Level.SEVERE, "Unknown transport {0}",
- resource.transport());
- }
+ return null;
}
}
@@ -374,17 +364,6 @@ public class PushHandler extends AtmosphereResourceEventListenerAdapter
disconnect(event);
}
- @Override
- public void onResume(AtmosphereResourceEvent event) {
- // Log event on trace level
- super.onResume(event);
- disconnect(event);
- }
-
- @Override
- public void destroy() {
- }
-
private void disconnect(AtmosphereResourceEvent event) {
// We don't want to use callWithUi here, as it assumes there's a client
// request active and does requestStart and requestEnd among other
@@ -480,8 +459,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 67f7575a87..db14e73c1a 100644
--- a/server/src/com/vaadin/server/communication/PushRequestHandler.java
+++ b/server/src/com/vaadin/server/communication/PushRequestHandler.java
@@ -21,6 +21,7 @@ import java.io.IOException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
+import org.atmosphere.cache.UUIDBroadcasterCache;
import org.atmosphere.client.TrackMessageSizeInterceptor;
import org.atmosphere.cpr.ApplicationConfig;
import org.atmosphere.cpr.AtmosphereFramework;
@@ -29,6 +30,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;
@@ -54,6 +57,12 @@ public class PushRequestHandler implements RequestHandler,
private AtmosphereFramework atmosphere;
private PushHandler pushHandler;
+ /**
+ * Atmosphere 2.x has a race condition when AtmosphereFramework init(config)
+ * is run from two threads at once. See http://dev.vaadin.com/ticket/13528
+ */
+ private static Object atmosphereInitRaceConditionWorkaroundLock = new Object();
+
public PushRequestHandler(VaadinServletService service)
throws ServiceException {
@@ -75,36 +84,49 @@ public class PushRequestHandler implements RequestHandler,
}
};
- pushHandler = new PushHandler(service);
- atmosphere.addAtmosphereHandler("/*", pushHandler);
- atmosphere.addInitParameter(ApplicationConfig.PROPERTY_SESSION_SUPPORT,
- "true");
- atmosphere.addInitParameter(ApplicationConfig.MESSAGE_DELIMITER,
- String.valueOf(PushConstants.MESSAGE_DELIMITER));
-
- final String bufferSize = String
- .valueOf(PushConstants.WEBSOCKET_BUFFER_SIZE);
- atmosphere.addInitParameter(ApplicationConfig.WEBSOCKET_BUFFER_SIZE,
- bufferSize);
- atmosphere.addInitParameter(ApplicationConfig.WEBSOCKET_MAXTEXTSIZE,
- bufferSize);
- atmosphere.addInitParameter(ApplicationConfig.WEBSOCKET_MAXBINARYSIZE,
- bufferSize);
-
- // Disable Atmosphere's message about commercial support
- atmosphere.addInitParameter("org.atmosphere.cpr.showSupportMessage",
- "false");
-
- try {
- atmosphere.init(config);
-
- // Ensure the client-side knows how to split the message stream
- // into individual messages when using certain transports
- AtmosphereInterceptor trackMessageSize = new TrackMessageSizeInterceptor();
- trackMessageSize.configure(atmosphere.getAtmosphereConfig());
- atmosphere.interceptor(trackMessageSize);
- } catch (ServletException e) {
- throw new ServiceException("Atmosphere init failed", e);
+ service.addServiceDestroyListener(new ServiceDestroyListener() {
+ @Override
+ public void serviceDestroy(ServiceDestroyEvent event) {
+ destroy();
+ }
+ });
+
+ synchronized (atmosphereInitRaceConditionWorkaroundLock) {
+ pushHandler = new PushHandler(service);
+ atmosphere.addAtmosphereHandler("/*", pushHandler.handler);
+ atmosphere.addInitParameter(ApplicationConfig.BROADCASTER_CACHE,
+ UUIDBroadcasterCache.class.getName());
+ atmosphere.addInitParameter(
+ ApplicationConfig.PROPERTY_SESSION_SUPPORT, "true");
+ atmosphere.addInitParameter(ApplicationConfig.MESSAGE_DELIMITER,
+ String.valueOf(PushConstants.MESSAGE_DELIMITER));
+
+ final String bufferSize = String
+ .valueOf(PushConstants.WEBSOCKET_BUFFER_SIZE);
+ atmosphere.addInitParameter(
+ ApplicationConfig.WEBSOCKET_BUFFER_SIZE, bufferSize);
+ atmosphere.addInitParameter(
+ ApplicationConfig.WEBSOCKET_MAXTEXTSIZE, bufferSize);
+ atmosphere.addInitParameter(
+ ApplicationConfig.WEBSOCKET_MAXBINARYSIZE, bufferSize);
+ atmosphere.addInitParameter(
+ ApplicationConfig.PROPERTY_ALLOW_SESSION_TIMEOUT_REMOVAL,
+ "false");
+ // Disable Atmosphere's message about commercial support
+ atmosphere.addInitParameter(
+ "org.atmosphere.cpr.showSupportMessage", "false");
+
+ try {
+ atmosphere.init(config);
+
+ // Ensure the client-side knows how to split the message stream
+ // into individual messages when using certain transports
+ AtmosphereInterceptor trackMessageSize = new TrackMessageSizeInterceptor();
+ trackMessageSize.configure(atmosphere.getAtmosphereConfig());
+ atmosphere.interceptor(trackMessageSize);
+ } catch (ServletException e) {
+ throw new ServiceException("Atmosphere init failed", e);
+ }
}
}
diff --git a/server/src/com/vaadin/server/communication/ServerRpcHandler.java b/server/src/com/vaadin/server/communication/ServerRpcHandler.java
index d875347633..89d87567d7 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));
+ }
+
+ /**
+ * 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;
+ }
- public static final char VAR_ESCAPE_CHARACTER = '\u001b';
+ /**
+ * 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,57 +150,65 @@ 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();
try {
+ ConnectorTracker connectorTracker = uI.getConnectorTracker();
+
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());
+ final ClientConnector connector = connectorTracker
+ .getConnector(invocation.getConnectorId());
if (connector != null && connector.isConnectorEnabled()) {
enabledConnectors.add(connector);
@@ -150,8 +218,8 @@ public class ServerRpcHandler implements Serializable {
for (int i = 0; i < invocations.size(); i++) {
MethodInvocation invocation = invocations.get(i);
- final ClientConnector connector = manager.getConnector(uI,
- invocation.getConnectorId());
+ final ClientConnector connector = connectorTracker
+ .getConnector(invocation.getConnectorId());
if (connector == null) {
getLogger()
.log(Level.WARNING,
@@ -243,21 +311,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 +336,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 +351,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 +360,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 +471,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 f71dcc91ee..cf0de8e9ee 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
-
- // Special parameter sent by vaadinBootstrap.js
- String windowName = request.getParameter("v-wn");
-
- Map<String, Integer> retainOnRefreshUIs = session
- .getPreserveOnRefreshUIs();
- if (windowName != null && !retainOnRefreshUIs.isEmpty()) {
- // Check for a known UI
+ // Check for an existing UI based on embed id
- Integer retainedUIId = retainOnRefreshUIs.get(windowName);
+ String embedId = getEmbedId(request);
- 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}.
*
@@ -248,12 +266,7 @@ public abstract class UIInitHandler extends SynchronizedRequestHandler {
*/
private void reinitUI(UI ui, VaadinRequest request) {
UI.setCurrent(ui);
-
- // Fire fragment change if the fragment has changed
- String location = request.getParameter("v-loc");
- if (location != null) {
- ui.getPage().updateLocation(location);
- }
+ ui.doRefresh(request);
}
/**
diff --git a/server/src/com/vaadin/server/communication/UidlRequestHandler.java b/server/src/com/vaadin/server/communication/UidlRequestHandler.java
index 4e71685805..0d8ddb7bc7 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 af035e619c..00522e2aa5 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,
@@ -281,9 +285,7 @@ public class UidlWriter implements Serializable {
+ new JSONArray(styleDependencies).toString());
}
- if (manager.getDragAndDropService() != null) {
- manager.getDragAndDropService().printJSONResponse(writer);
- }
+ session.getDragAndDropService().printJSONResponse(writer);
for (ClientConnector connector : dirtyVisibleConnectors) {
uiConnectorTracker.markClientSideInitialized(connector);
diff --git a/server/src/com/vaadin/ui/AbstractComponent.java b/server/src/com/vaadin/ui/AbstractComponent.java
index 1215891056..d0406c3eb7 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 dbe3a386e6..47ac953319 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/AbstractOrderedLayout.java b/server/src/com/vaadin/ui/AbstractOrderedLayout.java
index 59d53f77ab..039c87333e 100644
--- a/server/src/com/vaadin/ui/AbstractOrderedLayout.java
+++ b/server/src/com/vaadin/ui/AbstractOrderedLayout.java
@@ -213,8 +213,12 @@ public abstract class AbstractOrderedLayout extends AbstractLayout implements
if (oldLocation == -1) {
addComponent(newComponent);
} else if (newLocation == -1) {
+ Alignment alignment = getComponentAlignment(oldComponent);
+ float expandRatio = getExpandRatio(oldComponent);
+
removeComponent(oldComponent);
addComponent(newComponent, oldLocation);
+ applyLayoutSettings(newComponent, alignment, expandRatio);
} else {
// Both old and new are in the layout
if (oldLocation > newLocation) {
@@ -444,4 +448,10 @@ public abstract class AbstractOrderedLayout extends AbstractLayout implements
defaultComponentAlignment = defaultAlignment;
}
+ private void applyLayoutSettings(Component target, Alignment alignment,
+ float expandRatio) {
+ setComponentAlignment(target, alignment);
+ setExpandRatio(target, expandRatio);
+ }
+
}
diff --git a/server/src/com/vaadin/ui/AbstractSelect.java b/server/src/com/vaadin/ui/AbstractSelect.java
index ec11895762..c6abb8e685 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/AbstractSingleComponentContainer.java b/server/src/com/vaadin/ui/AbstractSingleComponentContainer.java
index 0a606183a6..de1bb29846 100644
--- a/server/src/com/vaadin/ui/AbstractSingleComponentContainer.java
+++ b/server/src/com/vaadin/ui/AbstractSingleComponentContainer.java
@@ -19,6 +19,8 @@ import java.util.Collections;
import java.util.Iterator;
import com.vaadin.server.ComponentSizeValidator;
+import com.vaadin.server.VaadinService;
+import com.vaadin.server.VaadinSession;
/**
* Abstract base class for component containers that have only one child
@@ -150,6 +152,19 @@ public abstract class AbstractSingleComponentContainer extends
// TODO move utility method elsewhere?
public static void removeFromParent(Component content)
throws IllegalArgumentException {
+ // Verify the appropriate session is locked
+ UI parentUI = content.getUI();
+ if (parentUI != null) {
+ VaadinSession parentSession = parentUI.getSession();
+ if (parentSession != null && !parentSession.hasLock()) {
+ String message = "Cannot remove from parent when the session is not locked.";
+ if (VaadinService.isOtherSessionLocked(parentSession)) {
+ message += " Furthermore, there is another locked session, indicating that the component might be about to be moved from one session to another.";
+ }
+ throw new IllegalStateException(message);
+ }
+ }
+
HasComponents parent = content.getParent();
if (parent instanceof ComponentContainer) {
// If the component already has a parent, try to remove it
diff --git a/server/src/com/vaadin/ui/Accordion.java b/server/src/com/vaadin/ui/Accordion.java
index 8ecf33f291..1d53937d24 100644
--- a/server/src/com/vaadin/ui/Accordion.java
+++ b/server/src/com/vaadin/ui/Accordion.java
@@ -15,6 +15,8 @@
*/
package com.vaadin.ui;
+import com.vaadin.shared.ui.accordion.AccordionState;
+
/**
* An accordion is a component similar to a {@link TabSheet}, but with a
* vertical orientation and the selected component presented between tabs.
@@ -46,4 +48,14 @@ public class Accordion extends TabSheet {
addComponents(components);
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.TabSheet#getState()
+ */
+ @Override
+ protected AccordionState getState() {
+ return (AccordionState) super.getState();
+ }
+
}
diff --git a/server/src/com/vaadin/ui/Button.java b/server/src/com/vaadin/ui/Button.java
index bec39c3efe..5a5d03a3ee 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 071f215d1e..048726dc84 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;
@@ -105,22 +103,30 @@ public class ComboBox extends AbstractSelect implements
private boolean textInputAllowed = true;
public ComboBox() {
- setNewItemsAllowed(false);
+ initDefaults();
}
public ComboBox(String caption, Collection<?> options) {
super(caption, options);
- setNewItemsAllowed(false);
+ initDefaults();
}
public ComboBox(String caption, Container dataSource) {
super(caption, dataSource);
- setNewItemsAllowed(false);
+ initDefaults();
}
public ComboBox(String caption) {
super(caption);
+ initDefaults();
+ }
+
+ /**
+ * Initialize the ComboBox with default settings
+ */
+ private void initDefaults() {
setNewItemsAllowed(false);
+ setImmediate(true);
}
/**
diff --git a/server/src/com/vaadin/ui/Component.java b/server/src/com/vaadin/ui/Component.java
index b3a343804a..e10b5e1cd9 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 c0b60e276d..ce8c452c2c 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;
@@ -34,6 +35,7 @@ import org.json.JSONObject;
import com.vaadin.server.AbstractClientConnector;
import com.vaadin.server.ClientConnector;
+import com.vaadin.server.DragAndDropService;
import com.vaadin.server.GlobalResourceHandler;
import com.vaadin.server.LegacyCommunicationManager;
import com.vaadin.server.StreamVariable;
@@ -81,6 +83,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 +182,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)) {
@@ -251,8 +272,16 @@ public class ConnectorTracker implements Serializable {
// Ignore connectors that have been unregistered but not yet cleaned up
if (unregisteredConnectors.contains(connector)) {
return null;
+ } else if (connector != null) {
+ return connector;
+ } else {
+ DragAndDropService service = uI.getSession()
+ .getDragAndDropService();
+ if (connectorId.equals(service.getConnectorId())) {
+ return service;
+ }
}
- return connector;
+ return null;
}
/**
@@ -570,12 +599,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 +622,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;
}
@@ -601,7 +644,7 @@ public class ConnectorTracker implements Serializable {
stringDiffStates.put(key, diffStates.get(key).toString());
}
out.writeObject(stringDiffStates);
- };
+ }
/* Special serialization to JSONObjects which are not serializable */
private void readObject(java.io.ObjectInputStream in) throws IOException,
@@ -732,4 +775,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 224c0dc941..cb94a774a5 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 71298289f4..b4685adcea 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 841d7efe50..2731e93ef2 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 46803802d6..aaf25a9e9d 100644
--- a/server/src/com/vaadin/ui/Notification.java
+++ b/server/src/com/vaadin/ui/Notification.java
@@ -63,7 +63,27 @@ import com.vaadin.shared.Position;
*/
public class Notification implements Serializable {
public enum Type {
- HUMANIZED_MESSAGE, WARNING_MESSAGE, ERROR_MESSAGE, TRAY_NOTIFICATION;
+ HUMANIZED_MESSAGE("humanized"), WARNING_MESSAGE("warning"), ERROR_MESSAGE(
+ "error"), TRAY_NOTIFICATION("tray"),
+ /**
+ * @since 7.2
+ */
+ ASSISTIVE_NOTIFICATION("assistive");
+
+ private String style;
+
+ Type(String style) {
+ this.style = style;
+ }
+
+ /**
+ * @since 7.2
+ *
+ * @return the style name for this notification type.
+ */
+ public String getStyle() {
+ return style;
+ }
}
@Deprecated
@@ -186,25 +206,26 @@ public class Notification implements Serializable {
}
private void setType(Type type) {
+ styleName = type.getStyle();
switch (type) {
case WARNING_MESSAGE:
delayMsec = 1500;
- styleName = "warning";
break;
case ERROR_MESSAGE:
delayMsec = -1;
- styleName = "error";
break;
case TRAY_NOTIFICATION:
delayMsec = 3000;
position = Position.BOTTOM_RIGHT;
- styleName = "tray";
-
+ break;
+ case ASSISTIVE_NOTIFICATION:
+ delayMsec = 3000;
+ position = Position.ASSISTIVE;
+ break;
case HUMANIZED_MESSAGE:
default:
break;
}
-
}
/**
diff --git a/server/src/com/vaadin/ui/NotificationConfiguration.java b/server/src/com/vaadin/ui/NotificationConfiguration.java
new file mode 100644
index 0000000000..e6d19f84f7
--- /dev/null
+++ b/server/src/com/vaadin/ui/NotificationConfiguration.java
@@ -0,0 +1,185 @@
+/*
+ * 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.NotificationRole;
+import com.vaadin.shared.ui.ui.UIState.NotificationTypeConfiguration;
+import com.vaadin.ui.Notification.Type;
+
+/**
+ * Provides methods for configuring the notification.
+ *
+ * @author Vaadin Ltd
+ * @since 7.2
+ */
+public interface NotificationConfiguration extends Serializable {
+ /**
+ * Sets the accessibility prefix for a notification type.
+ * <p>
+ * 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 setAssistivePrefix(Type type, String prefix);
+
+ /**
+ * Gets the accessibility prefix for a notification type.
+ * <p>
+ * 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 getAssistivePrefix(Type type);
+
+ /**
+ * Sets the accessibility postfix for a notification type.
+ * <p>
+ * 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 setAssistivePostfix(Type type, String postfix);
+
+ /**
+ * Gets the accessibility postfix for a notification type.
+ * <p>
+ * 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 getAssistivePostfix(Type type);
+
+ /**
+ * Sets the WAI-ARIA role for a notification type.
+ * <p>
+ * 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 setAssistiveRole(Type type, NotificationRole role);
+
+ /**
+ * Gets the WAI-ARIA role for a notification type.
+ * <p>
+ * 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>)
+ * <p>
+ * The default role is alert.
+ *
+ * @param type
+ * type of the notification
+ * @return role to set for the notification type
+ */
+ public NotificationRole getAssistiveRole(Type type);
+}
+
+class NotificationConfigurationImpl implements NotificationConfiguration {
+
+ private UI ui;
+
+ public NotificationConfigurationImpl(UI ui) {
+ this.ui = ui;
+ }
+
+ @Override
+ public void setAssistivePrefix(Type type, String prefix) {
+ getConfigurationBean(type).prefix = prefix;
+ }
+
+ @Override
+ public String getAssistivePrefix(Type type) {
+ NotificationTypeConfiguration styleSetup = getTypeConf(type);
+ if (styleSetup != null) {
+ return styleSetup.prefix;
+ }
+
+ return null;
+ }
+
+ @Override
+ public void setAssistivePostfix(Type type, String postfix) {
+ getConfigurationBean(type).postfix = postfix;
+ }
+
+ @Override
+ public String getAssistivePostfix(Type type) {
+ NotificationTypeConfiguration styleSetup = getTypeConf(type);
+ if (styleSetup != null) {
+ return styleSetup.postfix;
+ }
+
+ return null;
+ }
+
+ @Override
+ public void setAssistiveRole(Type type, NotificationRole role) {
+ getConfigurationBean(type).notificationRole = role;
+ }
+
+ @Override
+ public NotificationRole getAssistiveRole(Type type) {
+ NotificationTypeConfiguration styleSetup = getTypeConf(type);
+ if (styleSetup != null) {
+ return styleSetup.notificationRole;
+ }
+
+ return null;
+ }
+
+ private NotificationTypeConfiguration getConfigurationBean(Type type) {
+ NotificationTypeConfiguration styleSetup = getTypeConf(type);
+ if (styleSetup == null) {
+ styleSetup = new NotificationTypeConfiguration();
+ ui.getState().notificationConfigurations.put(type.getStyle(), styleSetup);
+ }
+
+ return styleSetup;
+ }
+
+ private NotificationTypeConfiguration getTypeConf(Type type) {
+ return ui.getState().notificationConfigurations.get(type.getStyle());
+ }
+}
diff --git a/server/src/com/vaadin/ui/PushConfiguration.java b/server/src/com/vaadin/ui/PushConfiguration.java
index e68c72429b..84f59d0313 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 e417e36ddf..2fdb3b40a7 100644
--- a/server/src/com/vaadin/ui/TabSheet.java
+++ b/server/src/com/vaadin/ui/TabSheet.java
@@ -27,17 +27,18 @@ import java.util.Map;
import com.vaadin.event.FieldEvents.BlurEvent;
import com.vaadin.event.FieldEvents.BlurListener;
import com.vaadin.event.FieldEvents.BlurNotifier;
+import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcImpl;
import com.vaadin.event.FieldEvents.FocusEvent;
import com.vaadin.event.FieldEvents.FocusListener;
import com.vaadin.event.FieldEvents.FocusNotifier;
import com.vaadin.server.ErrorMessage;
import com.vaadin.server.KeyMapper;
-import com.vaadin.server.LegacyPaint;
-import com.vaadin.server.PaintException;
-import com.vaadin.server.PaintTarget;
import com.vaadin.server.Resource;
-import com.vaadin.shared.ui.tabsheet.TabsheetBaseConstants;
-import com.vaadin.shared.ui.tabsheet.TabsheetConstants;
+import com.vaadin.shared.ComponentConstants;
+import com.vaadin.shared.ui.tabsheet.TabState;
+import com.vaadin.shared.ui.tabsheet.TabsheetClientRpc;
+import com.vaadin.shared.ui.tabsheet.TabsheetServerRpc;
+import com.vaadin.shared.ui.tabsheet.TabsheetState;
import com.vaadin.ui.Component.Focusable;
import com.vaadin.ui.themes.Reindeer;
import com.vaadin.ui.themes.Runo;
@@ -70,7 +71,28 @@ import com.vaadin.ui.themes.Runo;
* @since 3.0
*/
public class TabSheet extends AbstractComponentContainer implements Focusable,
- FocusNotifier, BlurNotifier, LegacyComponent, SelectiveRenderer {
+ FocusNotifier, BlurNotifier, SelectiveRenderer {
+
+ /**
+ * Client to server RPC implementation for TabSheet.
+ *
+ * @since 7.2
+ */
+ protected class TabsheetServerRpcImpl implements TabsheetServerRpc {
+
+ @Override
+ public void setSelected(String key) {
+ setSelectedTab(keyMapper.get(key));
+ }
+
+ @Override
+ public void closeTab(String key) {
+ final Component tab = keyMapper.get(key);
+ if (tab != null) {
+ closeHandler.onTabClose(TabSheet.this, tab);
+ }
+ }
+ }
/**
* List of component tabs (tab contents). In addition to being on this list,
@@ -96,23 +118,20 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
private final KeyMapper<Component> keyMapper = new KeyMapper<Component>();
/**
- * When true, the tab selection area is not displayed to the user.
- */
- private boolean tabsHidden;
-
- /**
* Handler to be called when a tab is closed.
*/
private CloseHandler closeHandler;
- private int tabIndex;
-
/**
* Constructs a new TabSheet. A TabSheet is immediate by default, and the
* default close handler removes the tab being closed.
*/
public TabSheet() {
super();
+
+ registerRpc(rpc);
+ registerRpc(focusBlurRpc);
+
// expand horizontally by default
setWidth(100, UNITS_PERCENTAGE);
setImmediate(true);
@@ -167,18 +186,23 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
* If the tab was selected, the first eligible (visible and enabled)
* remaining tab is selected.
*
- * @param c
+ * @param component
* the component to be removed.
*/
@Override
- public void removeComponent(Component c) {
- if (c != null && components.contains(c)) {
- super.removeComponent(c);
- keyMapper.remove(c);
- components.remove(c);
- tabs.remove(c);
- if (c.equals(selected)) {
+ public void removeComponent(Component component) {
+ if (component != null && components.contains(component)) {
+ super.removeComponent(component);
+ keyMapper.remove(component);
+ components.remove(component);
+
+ Tab removedTab = tabs.remove(component);
+
+ getState().tabs
+ .remove(((TabSheetTabImpl) removedTab).getTabState());
+
+ if (component.equals(selected)) {
if (components.isEmpty()) {
setSelected(null);
} else {
@@ -281,7 +305,7 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
* and icon and returns the corresponding (old) tab, preserving other tab
* metadata like the position.
*
- * @param c
+ * @param tabComponent
* 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
@@ -293,25 +317,28 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
* the position at where the the tab should be added.
* @return the created {@link Tab}
*/
- public Tab addTab(Component c, String caption, Resource icon, int position) {
- if (c == null) {
+ public Tab addTab(Component tabComponent, String caption, Resource icon, int position) {
+ if (tabComponent == null) {
return null;
- } else if (tabs.containsKey(c)) {
- Tab tab = tabs.get(c);
+ } else if (tabs.containsKey(tabComponent)) {
+ Tab tab = tabs.get(tabComponent);
tab.setCaption(caption);
tab.setIcon(icon);
return tab;
} else {
- components.add(position, c);
+ components.add(position, tabComponent);
- Tab tab = new TabSheetTabImpl(caption, icon);
+ TabSheetTabImpl tab = new TabSheetTabImpl(
+ keyMapper.key(tabComponent), caption, icon);
+
+ getState().tabs.add(position, tab.getTabState());
+ tabs.put(tabComponent, tab);
- tabs.put(c, tab);
if (selected == null) {
- setSelected(c);
+ setSelected(tabComponent);
fireSelectedTabChange();
}
- super.addComponent(c);
+ super.addComponent(tabComponent);
markAsDirty();
return tab;
}
@@ -337,20 +364,21 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
*
* If the tab sheet already contains the component, its tab is returned.
*
- * @param c
+ * @param component
* the component to be added onto tab - should not be null.
* @param position
* The position where the tab should be added
* @return the created {@link Tab}
*/
- public Tab addTab(Component c, int position) {
- if (c == null) {
- return null;
- } else if (tabs.containsKey(c)) {
- return tabs.get(c);
- } else {
- return addTab(c, c.getCaption(), c.getIcon(), position);
+ public Tab addTab(Component component, int position) {
+ Tab result = tabs.get(component);
+
+ if (result == null) {
+ result = addTab(component, component.getCaption(),
+ component.getIcon(), position);
}
+
+ return result;
}
/**
@@ -371,111 +399,26 @@ 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.getIconAlternateText();
}
source.removeComponent(c);
- addTab(c, caption, icon);
-
+ Tab tab = addTab(c, caption, icon);
+ tab.setIconAlternateText(iconAltText);
}
}
/**
- * Paints the content of this component.
- *
- * @param target
- * the paint target
- * @throws PaintException
- * if the paint operation failed.
- */
-
- @Override
- public void paintContent(PaintTarget target) throws PaintException {
-
- if (areTabsHidden()) {
- target.addAttribute("hidetabs", true);
- }
-
- if (tabIndex != 0) {
- target.addAttribute("tabindex", tabIndex);
- }
-
- target.startTag("tabs");
-
- for (final Iterator<Component> i = getComponentIterator(); i.hasNext();) {
- final Component component = i.next();
-
- Tab tab = tabs.get(component);
-
- target.startTag("tab");
- if (!tab.isEnabled() && tab.isVisible()) {
- target.addAttribute(
- TabsheetBaseConstants.ATTRIBUTE_TAB_DISABLED, true);
- }
-
- if (!tab.isVisible()) {
- target.addAttribute("hidden", true);
- }
-
- if (tab.isClosable()) {
- target.addAttribute("closable", true);
- }
-
- // tab icon, caption and description, but used via
- // VCaption.updateCaption(uidl)
- final Resource icon = tab.getIcon();
- if (icon != null) {
- target.addAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ICON,
- icon);
- }
- final String caption = tab.getCaption();
- if (caption != null && caption.length() > 0) {
- target.addAttribute(
- TabsheetBaseConstants.ATTRIBUTE_TAB_CAPTION, caption);
- }
- ErrorMessage tabError = tab.getComponentError();
- if (tabError != null) {
- target.addAttribute(
- TabsheetBaseConstants.ATTRIBUTE_TAB_ERROR_MESSAGE,
- tabError.getFormattedHtmlMessage());
- }
- final String description = tab.getDescription();
- if (description != null) {
- target.addAttribute(
- TabsheetBaseConstants.ATTRIBUTE_TAB_DESCRIPTION,
- description);
- }
-
- final String styleName = tab.getStyleName();
- if (styleName != null && styleName.length() != 0) {
- target.addAttribute(TabsheetConstants.TAB_STYLE_NAME, styleName);
- }
-
- target.addAttribute("key", keyMapper.key(component));
- if (component.equals(selected)) {
- target.addAttribute("selected", true);
- LegacyPaint.paint(component, target);
- }
- target.endTag("tab");
- }
-
- target.endTag("tabs");
-
- if (selected != null) {
- target.addVariable(this, "selected", keyMapper.key(selected));
- }
-
- }
-
- /**
* Are the tab selection parts ("tabs") hidden.
*
* @return true if the tabs are hidden in the UI
*/
public boolean areTabsHidden() {
- return tabsHidden;
+ return !getState(false).tabsVisible;
}
/**
@@ -485,8 +428,7 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
* true if the tabs should be hidden
*/
public void hideTabs(boolean tabsHidden) {
- this.tabsHidden = tabsHidden;
- markAsDirty();
+ getState().tabsVisible = !tabsHidden;
}
/**
@@ -531,6 +473,7 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
updateSelection();
fireSelectedTabChange();
markAsDirty();
+ getRpcProxy(TabsheetClientRpc.class).revertToSharedStateSelection();
}
}
@@ -538,17 +481,29 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
* Sets the selected tab in the TabSheet. Ensures that the selected tab is
* repainted if needed.
*
- * @param c
+ * @param component
* The new selection or null for no selection
*/
- private void setSelected(Component c) {
- selected = c;
+ private void setSelected(Component component) {
+ Tab tab = tabs.get(selected);
+
+ selected = component;
// Repaint of the selected component is needed as only the selected
// component is communicated to the client. Otherwise this will be a
// "cached" update even though the client knows nothing about the
// connector
if (selected != null) {
+ tab = getTab(component);
+
+ if (tab != null && tab.getDefaultFocusComponent() != null) {
+ tab.getDefaultFocusComponent().focus();
+ }
+
+ getState().selected = keyMapper.key(selected);
+
selected.markAsDirtyRecursive();
+ } else {
+ getState().selected = null;
}
}
@@ -632,27 +587,16 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
return selected;
}
- // inherits javadoc
+ private TabsheetServerRpcImpl rpc = new TabsheetServerRpcImpl();
- @Override
- public void changeVariables(Object source, Map<String, Object> variables) {
- if (variables.containsKey("selected")) {
- setSelectedTab(keyMapper.get((String) variables.get("selected")));
- }
- if (variables.containsKey("close")) {
- final Component tab = keyMapper
- .get((String) variables.get("close"));
- if (tab != null) {
- closeHandler.onTabClose(this, tab);
- }
- }
- if (variables.containsKey(FocusEvent.EVENT_ID)) {
- fireEvent(new FocusEvent(this));
- }
- if (variables.containsKey(BlurEvent.EVENT_ID)) {
- fireEvent(new BlurEvent(this));
+ private FocusAndBlurServerRpcImpl focusBlurRpc = new FocusAndBlurServerRpcImpl(
+ this) {
+
+ @Override
+ protected void fireEvent(Event event) {
+ TabSheet.this.fireEvent(event);
}
- }
+ };
/**
* Replaces a component (tab content) with another. This can be used to
@@ -674,10 +618,10 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
@Override
public void replaceComponent(Component oldComponent, Component newComponent) {
+ boolean selectAfterInserting = false;
if (selected == oldComponent) {
- // keep selection w/o selectedTabChange event
- setSelected(newComponent);
+ selectAfterInserting = true;
}
Tab newTab = tabs.get(newComponent);
@@ -687,6 +631,7 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
int oldLocation = -1;
int newLocation = -1;
int location = 0;
+
for (final Iterator<Component> i = components.iterator(); i.hasNext();) {
final Component component = i.next();
@@ -705,6 +650,11 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
} else if (newLocation == -1) {
removeComponent(oldComponent);
newTab = addTab(newComponent, oldLocation);
+
+ if (selectAfterInserting) {
+ setSelected(newComponent);
+ }
+
// Copy all relevant metadata to the new tab (#8793)
// TODO Should reuse the old tab instance instead?
copyTabMetadata(oldTab, newTab);
@@ -712,17 +662,20 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
components.set(oldLocation, newComponent);
components.set(newLocation, oldComponent);
+ if (selectAfterInserting) {
+ setSelected(newComponent);
+ }
+
// Tab associations are not changed, but metadata is swapped between
// the instances
// TODO Should reassociate the instances instead?
- Tab tmp = new TabSheetTabImpl(null, null);
+ Tab tmp = new TabSheetTabImpl(null, null, null);
copyTabMetadata(newTab, tmp);
copyTabMetadata(oldTab, newTab);
copyTabMetadata(tmp, oldTab);
markAsDirty();
}
-
}
/* Click event */
@@ -882,13 +835,30 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
* Note! Currently only supported by TabSheet, not Accordion.
* </p>
*
- * @param visible
+ * @param closable
* true if the end user is allowed to close the tab, false
* for not allowing to close. Should default to false.
*/
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 +902,31 @@ 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.
+ *
+ * @since 7.2
+ */
+ public String getIconAlternateText();
+
+ /**
+ * Sets the icon alt text for the tab.
+ *
+ * @since 7.2
+ *
+ * @param iconAltText
+ * the icon to set
+ */
+ public void setIconAlternateText(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 +1010,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();
}
/**
@@ -1022,21 +1034,23 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
*/
public class TabSheetTabImpl implements Tab {
- private String caption = "";
- private Resource icon = null;
- private boolean enabled = true;
- private boolean visible = true;
- private boolean closable = false;
- private String description = null;
- private ErrorMessage componentError = null;
- private String styleName;
+ private TabState tabState;
+
+ private Focusable defaultFocus;
+
+ private ErrorMessage componentError;
+
+ public TabSheetTabImpl(String key, String caption, Resource icon) {
+ tabState = new TabState();
- public TabSheetTabImpl(String caption, Resource icon) {
if (caption == null) {
caption = "";
}
- this.caption = caption;
- this.icon = icon;
+
+ tabState.key = key;
+ tabState.caption = caption;
+
+ setIcon(icon);
}
/**
@@ -1045,34 +1059,58 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
@Override
public String getCaption() {
- return caption;
+ return tabState.caption;
}
@Override
public void setCaption(String caption) {
- this.caption = caption;
+ tabState.caption = caption;
markAsDirty();
}
@Override
public Resource getIcon() {
- return icon;
+ return getResource(ComponentConstants.ICON_RESOURCE
+ + tabState.key);
}
@Override
public void setIcon(Resource icon) {
- this.icon = icon;
+ // this might not be ideal (resetting icon altText), but matches
+ // previous semantics
+ setIcon(icon, "");
+ }
+
+ @Override
+ public String getIconAlternateText() {
+ return tabState.iconAltText;
+ }
+
+ @Override
+ public void setIconAlternateText(String iconAltText) {
+ tabState.iconAltText = iconAltText;
markAsDirty();
}
@Override
+ public void setDefaultFocusComponent(Focusable defaultFocus) {
+ this.defaultFocus = defaultFocus;
+ }
+
+ @Override
+ public Focusable getDefaultFocusComponent() {
+ return defaultFocus;
+ }
+
+ @Override
public boolean isEnabled() {
- return enabled;
+ return tabState.enabled;
}
@Override
public void setEnabled(boolean enabled) {
- this.enabled = enabled;
+ tabState.enabled = enabled;
+
if (updateSelection()) {
fireSelectedTabChange();
}
@@ -1081,12 +1119,13 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
@Override
public boolean isVisible() {
- return visible;
+ return tabState.visible;
}
@Override
public void setVisible(boolean visible) {
- this.visible = visible;
+ tabState.visible = visible;
+
if (updateSelection()) {
fireSelectedTabChange();
}
@@ -1095,27 +1134,24 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
@Override
public boolean isClosable() {
- return closable;
+ return tabState.closable;
}
@Override
public void setClosable(boolean closable) {
- this.closable = closable;
- markAsDirty();
- }
-
- public void close() {
+ tabState.closable = closable;
+ markAsDirty();
}
@Override
public String getDescription() {
- return description;
+ return tabState.description;
}
@Override
public void setDescription(String description) {
- this.description = description;
+ tabState.description = description;
markAsDirty();
}
@@ -1127,6 +1163,11 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
@Override
public void setComponentError(ErrorMessage componentError) {
this.componentError = componentError;
+
+ String formattedHtmlMessage = componentError != null ? componentError
+ .getFormattedHtmlMessage() : null;
+ tabState.componentError = formattedHtmlMessage;
+
markAsDirty();
}
@@ -1142,13 +1183,37 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
@Override
public void setStyleName(String styleName) {
- this.styleName = styleName;
+ tabState.styleName = styleName;
+
markAsDirty();
}
@Override
public String getStyleName() {
- return styleName;
+ return tabState.styleName;
+ }
+
+ protected TabState getTabState() {
+ return tabState;
+ }
+
+ @Override
+ public void setId(String id) {
+ tabState.id = id;
+ markAsDirty();
+
+ }
+
+ @Override
+ public String getId() {
+ return tabState.id;
+ }
+
+ @Override
+ public void setIcon(Resource icon, String iconAltText) {
+ setResource(ComponentConstants.ICON_RESOURCE + tabState.key,
+ icon);
+ tabState.iconAltText = iconAltText;
}
}
@@ -1203,7 +1268,9 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
int oldPosition = getTabPosition(tab);
components.remove(oldPosition);
components.add(position, tab.getComponent());
- markAsDirty();
+
+ getState().tabs.remove(oldPosition);
+ getState().tabs.add(position, ((TabSheetTabImpl) tab).getTabState());
}
/**
@@ -1224,13 +1291,12 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
@Override
public int getTabIndex() {
- return tabIndex;
+ return getState(false).tabIndex;
}
@Override
public void setTabIndex(int tabIndex) {
- this.tabIndex = tabIndex;
- markAsDirty();
+ getState().tabIndex = tabIndex;
}
@Override
@@ -1309,7 +1375,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.getIconAlternateText());
to.setDescription(from.getDescription());
to.setVisible(from.isVisible());
to.setEnabled(from.isEnabled());
@@ -1317,4 +1383,14 @@ public class TabSheet extends AbstractComponentContainer implements Focusable,
to.setStyleName(from.getStyleName());
to.setComponentError(from.getComponentError());
}
+
+ @Override
+ protected TabsheetState getState(boolean markAsDirty) {
+ return (TabsheetState) super.getState(markAsDirty);
+ }
+
+ @Override
+ protected TabsheetState getState() {
+ return (TabsheetState) super.getState();
+ }
}
diff --git a/server/src/com/vaadin/ui/Table.java b/server/src/com/vaadin/ui/Table.java
index b4bb2cde4c..4c15aca2eb 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 d701c9c92f..7a71083e85 100644
--- a/server/src/com/vaadin/ui/UI.java
+++ b/server/src/com/vaadin/ui/UI.java
@@ -16,6 +16,7 @@
package com.vaadin.ui;
+import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -27,11 +28,15 @@ import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
+import com.vaadin.annotations.PreserveOnRefresh;
import com.vaadin.event.Action;
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;
@@ -48,10 +53,12 @@ import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinService;
import com.vaadin.server.VaadinServlet;
import com.vaadin.server.VaadinSession;
+import com.vaadin.server.VaadinSession.State;
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 +84,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 +102,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
@@ -156,7 +164,7 @@ public abstract class UI extends AbstractSingleComponentContainer implements
public void resize(int viewWidth, int viewHeight, int windowWidth,
int windowHeight) {
// TODO We're not doing anything with the view dimensions
- getPage().updateBrowserWindowSize(windowWidth, windowHeight);
+ getPage().updateBrowserWindowSize(windowWidth, windowHeight, true);
}
@Override
@@ -167,10 +175,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 +227,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.
@@ -356,7 +364,7 @@ public abstract class UI extends AbstractSingleComponentContainer implements
if (variables.containsKey(UIConstants.LOCATION_VARIABLE)) {
String location = (String) variables
.get(UIConstants.LOCATION_VARIABLE);
- getPage().updateLocation(location);
+ getPage().updateLocation(location, true);
}
}
@@ -418,8 +426,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 +555,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,8 +607,14 @@ 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) {
String message = "This UI instance is already initialized (as UI id "
+ this.uiId
@@ -615,6 +630,7 @@ public abstract class UI extends AbstractSingleComponentContainer implements
throw new IllegalStateException(message);
}
this.uiId = uiId;
+ this.embedId = embedId;
// Actual theme - used for finding CustomLayout templates
theme = request.getParameter("theme");
@@ -647,6 +663,57 @@ public abstract class UI extends AbstractSingleComponentContainer implements
protected abstract void init(VaadinRequest request);
/**
+ * Internal reinitialization method, should not be overridden.
+ *
+ * @since 7.2
+ * @param request
+ * the request that caused this UI to be reloaded
+ */
+ public void doRefresh(VaadinRequest request) {
+ // This is a horrible hack. We want to have the most recent location and
+ // browser window size available in refresh(), but we want to call
+ // listeners, if any, only after refresh(). So we momentarily assign the
+ // old values back before setting the new values again to ensure the
+ // events are properly fired.
+
+ Page page = getPage();
+
+ URI oldLocation = page.getLocation();
+ int oldWidth = page.getBrowserWindowWidth();
+ int oldHeight = page.getBrowserWindowHeight();
+
+ page.init(request);
+
+ refresh(request);
+
+ URI newLocation = page.getLocation();
+ int newWidth = page.getBrowserWindowWidth();
+ int newHeight = page.getBrowserWindowHeight();
+
+ page.updateLocation(oldLocation.toString(), false);
+ page.updateBrowserWindowSize(oldWidth, oldHeight, false);
+
+ page.updateLocation(newLocation.toString(), true);
+ page.updateBrowserWindowSize(newWidth, newHeight, true);
+ }
+
+ /**
+ * Reinitializes this UI after a browser refresh if the UI is set to be
+ * preserved on refresh, typically using the {@link PreserveOnRefresh}
+ * annotation. This method is intended to be overridden by subclasses if
+ * needed; the default implementation is empty.
+ * <p>
+ * The {@link VaadinRequest} can be used to get information about the
+ * request that caused this UI to be reloaded.
+ *
+ * @since 7.2
+ * @param request
+ * the request that caused this UI to be reloaded
+ */
+ protected void refresh(VaadinRequest request) {
+ }
+
+ /**
* Sets the thread local for the current UI. This method is used by the
* framework to set the current application whenever a new request is
* processed and it is cleared when the request has been processed.
@@ -1096,7 +1163,7 @@ public abstract class UI extends AbstractSingleComponentContainer implements
public void close() {
closing = true;
- boolean sessionExpired = (session == null || session.isClosing());
+ boolean sessionExpired = (session == null || session.getState() != State.OPEN);
getRpcProxy(UIClientRpc.class).uiClosed(sessionExpired);
if (getPushConnection() != null) {
// Push the Rpc to the client. The connection will be closed when
@@ -1345,6 +1412,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
@@ -1357,6 +1433,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
@@ -1374,79 +1453,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();
- }
}
/**
@@ -1480,6 +1553,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.
*
@@ -1529,4 +1613,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 f440613905..869c32751a 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;
@@ -1094,5 +1184,5 @@ public class Upload extends AbstractComponent implements Component.Focusable,
}
return super.getListeners(eventType);
- };
+ }
}
diff --git a/server/src/com/vaadin/ui/Window.java b/server/src/com/vaadin/ui/Window.java
index 149051d2fb..149fcd536f 100644
--- a/server/src/com/vaadin/ui/Window.java
+++ b/server/src/com/vaadin/ui/Window.java
@@ -33,10 +33,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.WindowRole;
import com.vaadin.util.ReflectTools;
/**
@@ -238,8 +240,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 +644,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 +1008,202 @@ 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 components
+ * the components to use as description
+ */
+ public void setAssistiveDescription(Component... components) {
+ if (components == null) {
+ throw new IllegalArgumentException(
+ "Parameter connectors must be non-null");
+ } else {
+ getState().contentDescription = components;
+ }
+ }
+
+ /**
+ * 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 array of previously set components
+ */
+ public Component[] getAssistiveDescription() {
+ Connector[] contentDescription = getState().contentDescription;
+ if (contentDescription == null) {
+ return null;
+ }
+
+ Component[] target = new Component[contentDescription.length];
+ System.arraycopy(contentDescription, 0, target, 0,
+ contentDescription.length);
+
+ return target;
+ }
+
+ /**
+ * 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;
+ }
}