summaryrefslogtreecommitdiffstats
path: root/server/src
diff options
context:
space:
mode:
Diffstat (limited to 'server/src')
-rw-r--r--server/src/com/vaadin/annotations/Push.java49
-rw-r--r--server/src/com/vaadin/data/Container.java9
-rw-r--r--server/src/com/vaadin/data/fieldgroup/BeanFieldGroup.java37
-rw-r--r--server/src/com/vaadin/data/fieldgroup/DefaultFieldGroupFieldFactory.java26
-rw-r--r--server/src/com/vaadin/data/fieldgroup/FieldGroup.java94
-rw-r--r--server/src/com/vaadin/data/util/AbstractBeanContainer.java20
-rw-r--r--server/src/com/vaadin/data/util/AbstractInMemoryContainer.java19
-rw-r--r--server/src/com/vaadin/data/util/AbstractProperty.java37
-rw-r--r--server/src/com/vaadin/data/util/IndexedContainer.java57
-rw-r--r--server/src/com/vaadin/data/util/LegacyPropertyHelper.java102
-rw-r--r--server/src/com/vaadin/data/util/sqlcontainer/ColumnProperty.java37
-rw-r--r--server/src/com/vaadin/data/util/sqlcontainer/SQLContainer.java22
-rw-r--r--server/src/com/vaadin/data/util/sqlcontainer/query/TableQuery.java200
-rw-r--r--server/src/com/vaadin/server/AbstractClientConnector.java24
-rw-r--r--server/src/com/vaadin/server/AbstractCommunicationManager.java2881
-rw-r--r--server/src/com/vaadin/server/BootstrapHandler.java72
-rw-r--r--server/src/com/vaadin/server/BrowserWindowOpener.java3
-rw-r--r--server/src/com/vaadin/server/ClientConnector.java10
-rw-r--r--server/src/com/vaadin/server/CommunicationManager.java93
-rw-r--r--server/src/com/vaadin/server/ComponentSizeValidator.java4
-rw-r--r--server/src/com/vaadin/server/ConnectorResource.java9
-rw-r--r--server/src/com/vaadin/server/ConnectorResourceHandler.java49
-rw-r--r--server/src/com/vaadin/server/Constants.java58
-rw-r--r--server/src/com/vaadin/server/DefaultDeploymentConfiguration.java63
-rw-r--r--server/src/com/vaadin/server/DeploymentConfiguration.java37
-rw-r--r--server/src/com/vaadin/server/DownloadStream.java5
-rw-r--r--server/src/com/vaadin/server/DragAndDropService.java10
-rw-r--r--server/src/com/vaadin/server/FileDownloader.java23
-rw-r--r--server/src/com/vaadin/server/GAEVaadinServlet.java40
-rw-r--r--server/src/com/vaadin/server/GlobalResourceHandler.java53
-rw-r--r--server/src/com/vaadin/server/JsonCodec.java4
-rw-r--r--server/src/com/vaadin/server/JsonPaintTarget.java12
-rw-r--r--server/src/com/vaadin/server/LegacyCommunicationManager.java498
-rw-r--r--server/src/com/vaadin/server/LegacyPaint.java2
-rw-r--r--server/src/com/vaadin/server/Page.java178
-rw-r--r--server/src/com/vaadin/server/PortletCommunicationManager.java147
-rw-r--r--server/src/com/vaadin/server/RequestHandler.java22
-rw-r--r--server/src/com/vaadin/server/RequestTimer.java55
-rw-r--r--server/src/com/vaadin/server/ServerRpcManager.java2
-rw-r--r--server/src/com/vaadin/server/ServletPortletHelper.java28
-rw-r--r--server/src/com/vaadin/server/SessionExpiredHandler.java48
-rw-r--r--server/src/com/vaadin/server/SynchronizedRequestHandler.java65
-rw-r--r--server/src/com/vaadin/server/UIProvider.java25
-rw-r--r--server/src/com/vaadin/server/UnsupportedBrowserHandler.java8
-rw-r--r--server/src/com/vaadin/server/VaadinPortlet.java350
-rw-r--r--server/src/com/vaadin/server/VaadinPortletService.java76
-rw-r--r--server/src/com/vaadin/server/VaadinService.java771
-rw-r--r--server/src/com/vaadin/server/VaadinServlet.java384
-rw-r--r--server/src/com/vaadin/server/VaadinServletService.java128
-rw-r--r--server/src/com/vaadin/server/VaadinSession.java280
-rw-r--r--server/src/com/vaadin/server/WebBrowser.java13
-rw-r--r--server/src/com/vaadin/server/communication/AbstractStreamingEvent.java (renamed from server/src/com/vaadin/server/AbstractStreamingEvent.java)2
-rw-r--r--server/src/com/vaadin/server/communication/AtmospherePushConnection.java247
-rw-r--r--server/src/com/vaadin/server/communication/ClientRpcWriter.java141
-rw-r--r--server/src/com/vaadin/server/communication/ConnectorHierarchyWriter.java81
-rw-r--r--server/src/com/vaadin/server/communication/ConnectorTypeWriter.java73
-rw-r--r--server/src/com/vaadin/server/communication/FileUploadHandler.java645
-rw-r--r--server/src/com/vaadin/server/communication/HeartbeatHandler.java90
-rw-r--r--server/src/com/vaadin/server/communication/LegacyUidlWriter.java118
-rw-r--r--server/src/com/vaadin/server/communication/LocaleWriter.java204
-rw-r--r--server/src/com/vaadin/server/communication/MetadataWriter.java148
-rw-r--r--server/src/com/vaadin/server/communication/PortletBootstrapHandler.java113
-rw-r--r--server/src/com/vaadin/server/communication/PortletDummyRequestHandler.java82
-rw-r--r--server/src/com/vaadin/server/communication/PortletListenerNotifier.java89
-rw-r--r--server/src/com/vaadin/server/communication/PortletUIInitHandler.java63
-rw-r--r--server/src/com/vaadin/server/communication/PublishedFileHandler.java151
-rw-r--r--server/src/com/vaadin/server/communication/PushConnection.java48
-rw-r--r--server/src/com/vaadin/server/communication/PushHandler.java380
-rw-r--r--server/src/com/vaadin/server/communication/PushRequestHandler.java134
-rw-r--r--server/src/com/vaadin/server/communication/ResourceWriter.java113
-rw-r--r--server/src/com/vaadin/server/communication/ServerRpcHandler.java469
-rw-r--r--server/src/com/vaadin/server/communication/ServletBootstrapHandler.java48
-rw-r--r--server/src/com/vaadin/server/communication/ServletUIInitHandler.java33
-rw-r--r--server/src/com/vaadin/server/communication/SessionRequestHandler.java70
-rw-r--r--server/src/com/vaadin/server/communication/SharedStateWriter.java75
-rw-r--r--server/src/com/vaadin/server/communication/StreamingEndEventImpl.java (renamed from server/src/com/vaadin/server/StreamingEndEventImpl.java)2
-rw-r--r--server/src/com/vaadin/server/communication/StreamingErrorEventImpl.java (renamed from server/src/com/vaadin/server/StreamingErrorEventImpl.java)2
-rw-r--r--server/src/com/vaadin/server/communication/StreamingProgressEventImpl.java (renamed from server/src/com/vaadin/server/StreamingProgressEventImpl.java)2
-rw-r--r--server/src/com/vaadin/server/communication/StreamingStartEventImpl.java (renamed from server/src/com/vaadin/server/StreamingStartEventImpl.java)2
-rw-r--r--server/src/com/vaadin/server/communication/UIInitHandler.java304
-rw-r--r--server/src/com/vaadin/server/communication/UidlRequestHandler.java306
-rw-r--r--server/src/com/vaadin/server/communication/UidlWriter.java317
-rw-r--r--server/src/com/vaadin/server/themeutils/SASSAddonImportFileCreator.java200
-rw-r--r--server/src/com/vaadin/server/widgetsetutils/ClassPathExplorer.java538
-rw-r--r--server/src/com/vaadin/server/widgetsetutils/WidgetSetBuilder.java213
-rw-r--r--server/src/com/vaadin/ui/AbstractColorPicker.java1
-rw-r--r--server/src/com/vaadin/ui/AbstractField.java94
-rw-r--r--server/src/com/vaadin/ui/AbstractMedia.java20
-rw-r--r--server/src/com/vaadin/ui/AbstractOrderedLayout.java29
-rw-r--r--server/src/com/vaadin/ui/Button.java30
-rw-r--r--server/src/com/vaadin/ui/Calendar.java1845
-rw-r--r--server/src/com/vaadin/ui/ConnectorTracker.java30
-rw-r--r--server/src/com/vaadin/ui/DateField.java177
-rw-r--r--server/src/com/vaadin/ui/GridLayout.java24
-rw-r--r--server/src/com/vaadin/ui/Label.java53
-rw-r--r--server/src/com/vaadin/ui/Layout.java17
-rw-r--r--server/src/com/vaadin/ui/LoadingIndicatorConfiguration.java160
-rw-r--r--server/src/com/vaadin/ui/LoginForm.java30
-rw-r--r--server/src/com/vaadin/ui/PopupDateField.java20
-rw-r--r--server/src/com/vaadin/ui/TooltipConfiguration.java240
-rw-r--r--server/src/com/vaadin/ui/Tree.java104
-rw-r--r--server/src/com/vaadin/ui/UI.java309
-rw-r--r--server/src/com/vaadin/ui/UIDetachedException.java42
-rw-r--r--server/src/com/vaadin/ui/Window.java146
-rw-r--r--server/src/com/vaadin/ui/components/calendar/CalendarComponentEvent.java51
-rw-r--r--server/src/com/vaadin/ui/components/calendar/CalendarComponentEvents.java603
-rw-r--r--server/src/com/vaadin/ui/components/calendar/CalendarDateRange.java86
-rw-r--r--server/src/com/vaadin/ui/components/calendar/CalendarTargetDetails.java80
-rw-r--r--server/src/com/vaadin/ui/components/calendar/ContainerEventProvider.java577
-rw-r--r--server/src/com/vaadin/ui/components/calendar/event/BasicEvent.java265
-rw-r--r--server/src/com/vaadin/ui/components/calendar/event/BasicEventProvider.java179
-rw-r--r--server/src/com/vaadin/ui/components/calendar/event/CalendarEditableEventProvider.java42
-rw-r--r--server/src/com/vaadin/ui/components/calendar/event/CalendarEvent.java146
-rw-r--r--server/src/com/vaadin/ui/components/calendar/event/CalendarEventProvider.java112
-rw-r--r--server/src/com/vaadin/ui/components/calendar/event/EditableCalendarEvent.java91
-rw-r--r--server/src/com/vaadin/ui/components/calendar/handler/BasicBackwardHandler.java79
-rw-r--r--server/src/com/vaadin/ui/components/calendar/handler/BasicDateClickHandler.java70
-rw-r--r--server/src/com/vaadin/ui/components/calendar/handler/BasicEventMoveHandler.java74
-rw-r--r--server/src/com/vaadin/ui/components/calendar/handler/BasicEventResizeHandler.java70
-rw-r--r--server/src/com/vaadin/ui/components/calendar/handler/BasicForwardHandler.java77
-rw-r--r--server/src/com/vaadin/ui/components/calendar/handler/BasicWeekClickHandler.java82
-rw-r--r--server/src/com/vaadin/ui/components/colorpicker/ColorPickerGrid.java2
-rw-r--r--server/src/com/vaadin/ui/components/colorpicker/ColorPickerHistory.java2
-rw-r--r--server/src/com/vaadin/ui/components/colorpicker/ColorPickerPopup.java8
-rw-r--r--server/src/com/vaadin/util/CurrentInstance.java106
125 files changed, 14640 insertions, 4275 deletions
diff --git a/server/src/com/vaadin/annotations/Push.java b/server/src/com/vaadin/annotations/Push.java
new file mode 100644
index 0000000000..58e70acf21
--- /dev/null
+++ b/server/src/com/vaadin/annotations/Push.java
@@ -0,0 +1,49 @@
+/*
+ * 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.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import com.vaadin.shared.communication.PushMode;
+import com.vaadin.ui.UI;
+
+/**
+ * Configures server push for a {@link UI}. Adding <code>@Push</code> to a UI
+ * class configures the UI for automatic push. If some other push mode is
+ * desired, it can be passed as a parameter, e.g.
+ * <code>@Push(PushMode.MANUAL)</code>.
+ *
+ * @see PushMode
+ *
+ * @author Vaadin Ltd.
+ * @since 7.1
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface Push {
+ /**
+ * Returns the {@link PushMode} to use for the annotated UI. The default
+ * push mode when this annotation is present is {@link PushMode#AUTOMATIC}.
+ *
+ * @return the push mode to use
+ */
+ public PushMode value() default PushMode.AUTOMATIC;
+
+}
diff --git a/server/src/com/vaadin/data/Container.java b/server/src/com/vaadin/data/Container.java
index ddeac62d6d..e93db52a35 100644
--- a/server/src/com/vaadin/data/Container.java
+++ b/server/src/com/vaadin/data/Container.java
@@ -953,6 +953,15 @@ public interface Container extends Serializable {
*/
public void removeAllContainerFilters();
+ /**
+ * Returns the filters which have been applied to the container
+ *
+ * @return A collection of filters which have been applied to the
+ * container. An empty collection if no filters have been
+ * applied.
+ * @since 7.1
+ */
+ public Collection<Filter> getContainerFilters();
}
/**
diff --git a/server/src/com/vaadin/data/fieldgroup/BeanFieldGroup.java b/server/src/com/vaadin/data/fieldgroup/BeanFieldGroup.java
index 9dc6037d83..0b4e3a8049 100644
--- a/server/src/com/vaadin/data/fieldgroup/BeanFieldGroup.java
+++ b/server/src/com/vaadin/data/fieldgroup/BeanFieldGroup.java
@@ -58,6 +58,23 @@ public class BeanFieldGroup<T> extends FieldGroup {
}
}
+ @Override
+ protected Object findPropertyId(java.lang.reflect.Field memberField) {
+ String fieldName = memberField.getName();
+ Item dataSource = getItemDataSource();
+ if (dataSource != null && dataSource.getItemProperty(fieldName) != null) {
+ return fieldName;
+ } else {
+ String minifiedFieldName = minifyFieldName(fieldName);
+ try {
+ return getFieldName(beanType, minifiedFieldName);
+ } catch (SecurityException e) {
+ } catch (NoSuchFieldException e) {
+ }
+ }
+ return null;
+ }
+
private static java.lang.reflect.Field getField(Class<?> cls,
String propertyId) throws SecurityException, NoSuchFieldException {
if (propertyId.contains(".")) {
@@ -75,7 +92,7 @@ public class BeanFieldGroup<T> extends FieldGroup {
} catch (NoSuchFieldException e) {
// Try super classes until we reach Object
Class<?> superClass = cls.getSuperclass();
- if (superClass != Object.class) {
+ if (superClass != null && superClass != Object.class) {
return getField(superClass, propertyId);
} else {
throw e;
@@ -84,6 +101,22 @@ public class BeanFieldGroup<T> extends FieldGroup {
}
}
+ private static String getFieldName(Class<?> cls, String propertyId)
+ throws SecurityException, NoSuchFieldException {
+ for (java.lang.reflect.Field field1 : cls.getDeclaredFields()) {
+ if (propertyId.equals(minifyFieldName(field1.getName()))) {
+ return field1.getName();
+ }
+ }
+ // Try super classes until we reach Object
+ Class<?> superClass = cls.getSuperclass();
+ if (superClass != null && superClass != Object.class) {
+ return getFieldName(superClass, propertyId);
+ } else {
+ throw new NoSuchFieldException();
+ }
+ }
+
/**
* Helper method for setting the data source directly using a bean. This
* method wraps the bean in a {@link BeanItem} and calls
@@ -176,4 +209,4 @@ public class BeanFieldGroup<T> extends FieldGroup {
}
return beanValidationImplementationAvailable;
}
-} \ No newline at end of file
+}
diff --git a/server/src/com/vaadin/data/fieldgroup/DefaultFieldGroupFieldFactory.java b/server/src/com/vaadin/data/fieldgroup/DefaultFieldGroupFieldFactory.java
index 9ced6588f5..c1e4b4933e 100644
--- a/server/src/com/vaadin/data/fieldgroup/DefaultFieldGroupFieldFactory.java
+++ b/server/src/com/vaadin/data/fieldgroup/DefaultFieldGroupFieldFactory.java
@@ -15,18 +15,23 @@
*/
package com.vaadin.data.fieldgroup;
+import java.util.Date;
import java.util.EnumSet;
import com.vaadin.data.Item;
import com.vaadin.data.fieldgroup.FieldGroup.BindException;
+import com.vaadin.ui.AbstractField;
import com.vaadin.ui.AbstractSelect;
import com.vaadin.ui.AbstractTextField;
import com.vaadin.ui.CheckBox;
import com.vaadin.ui.ComboBox;
+import com.vaadin.ui.DateField;
import com.vaadin.ui.Field;
+import com.vaadin.ui.InlineDateField;
import com.vaadin.ui.ListSelect;
import com.vaadin.ui.NativeSelect;
import com.vaadin.ui.OptionGroup;
+import com.vaadin.ui.PopupDateField;
import com.vaadin.ui.RichTextArea;
import com.vaadin.ui.Table;
import com.vaadin.ui.TextField;
@@ -39,6 +44,8 @@ public class DefaultFieldGroupFieldFactory implements FieldGroupFieldFactory {
public <T extends Field> T createField(Class<?> type, Class<T> fieldType) {
if (Enum.class.isAssignableFrom(type)) {
return createEnumField(type, fieldType);
+ } else if (Date.class.isAssignableFrom(type)) {
+ return createDateField(type, fieldType);
} else if (Boolean.class.isAssignableFrom(type)
|| boolean.class.isAssignableFrom(type)) {
return createBooleanField(fieldType);
@@ -70,6 +77,25 @@ public class DefaultFieldGroupFieldFactory implements FieldGroupFieldFactory {
return null;
}
+ private <T extends Field> T createDateField(Class<?> type,
+ Class<T> fieldType) {
+ AbstractField field;
+
+ if (InlineDateField.class.isAssignableFrom(fieldType)) {
+ field = new InlineDateField();
+ } else if (DateField.class.isAssignableFrom(fieldType)
+ || fieldType == Field.class) {
+ field = new PopupDateField();
+ } else if (AbstractTextField.class.isAssignableFrom(fieldType)) {
+ field = createAbstractTextField((Class<? extends AbstractTextField>) fieldType);
+ } else {
+ return null;
+ }
+
+ field.setImmediate(true);
+ return (T) field;
+ }
+
protected AbstractSelect createCompatibleSelect(
Class<? extends AbstractSelect> fieldType) {
AbstractSelect select;
diff --git a/server/src/com/vaadin/data/fieldgroup/FieldGroup.java b/server/src/com/vaadin/data/fieldgroup/FieldGroup.java
index dc1fdbb78d..981aea387d 100644
--- a/server/src/com/vaadin/data/fieldgroup/FieldGroup.java
+++ b/server/src/com/vaadin/data/fieldgroup/FieldGroup.java
@@ -733,11 +733,12 @@ public class FieldGroup implements Serializable {
* that have not been initialized.
* <p>
* This method processes all (Java) member fields whose type extends
- * {@link Field} and that can be mapped to a property id. Property id
- * mapping is done based on the field name or on a @{@link PropertyId}
- * annotation on the field. Fields that are not initialized (null) are built
- * using the field factory. All non-null fields for which a property id can
- * be determined are bound to the property id.
+ * {@link Field} and that can be mapped to a property id. Property ids are
+ * searched in the following order: @{@link PropertyId} annotations, exact
+ * field name matches and the case-insensitive matching that ignores
+ * underscores. Fields that are not initialized (null) are built using the
+ * field factory. All non-null fields for which a property id can be
+ * determined are bound to the property id.
* </p>
* <p>
* For example:
@@ -777,11 +778,12 @@ public class FieldGroup implements Serializable {
* member fields that have not been initialized.
* <p>
* This method processes all (Java) member fields whose type extends
- * {@link Field} and that can be mapped to a property id. Property id
- * mapping is done based on the field name or on a @{@link PropertyId}
- * annotation on the field. Fields that are not initialized (null) are built
- * using the field factory is buildFields is true. All non-null fields for
- * which a property id can be determined are bound to the property id.
+ * {@link Field} and that can be mapped to a property id. Property ids are
+ * searched in the following order: @{@link PropertyId} annotations, exact
+ * field name matches and the case-insensitive matching that ignores
+ * underscores. Fields that are not initialized (null) are built using the
+ * field factory is buildFields is true. All non-null fields for which a
+ * property id can be determined are bound to the property id.
* </p>
*
* @param objectWithMemberFields
@@ -812,7 +814,16 @@ public class FieldGroup implements Serializable {
// @PropertyId(propertyId) always overrides property id
propertyId = propertyIdAnnotation.value();
} else {
- propertyId = memberField.getName();
+ try {
+ propertyId = findPropertyId(memberField);
+ } catch (SearchException e) {
+ // Property id was not found, skip this field
+ continue;
+ }
+ if (propertyId == null) {
+ // Property id was not found, skip this field
+ continue;
+ }
}
// Ensure that the property id exists
@@ -873,6 +884,55 @@ public class FieldGroup implements Serializable {
}
}
+ /**
+ * Searches for a property id from the current itemDataSource that matches
+ * the given memberField.
+ * <p>
+ * If perfect match is not found, uses a case insensitive search that also
+ * ignores underscores. Returns null if no match is found. Throws a
+ * SearchException if no item data source has been set.
+ * </p>
+ * <p>
+ * The propertyId search logic used by
+ * {@link #buildAndBindMemberFields(Object, boolean)
+ * buildAndBindMemberFields} can easily be customized by overriding this
+ * method. No other changes are needed.
+ * </p>
+ *
+ * @param memberField
+ * The field an object id is searched for
+ * @return
+ */
+ protected Object findPropertyId(java.lang.reflect.Field memberField) {
+ String fieldName = memberField.getName();
+ if (getItemDataSource() == null) {
+ throw new SearchException(
+ "Property id type for field '"
+ + fieldName
+ + "' could not be determined. No item data source has been set.");
+ }
+ Item dataSource = getItemDataSource();
+ if (dataSource.getItemProperty(fieldName) != null) {
+ return fieldName;
+ } else {
+ String minifiedFieldName = minifyFieldName(fieldName);
+ for (Object itemPropertyId : dataSource.getItemPropertyIds()) {
+ if (itemPropertyId instanceof String) {
+ String itemPropertyName = (String) itemPropertyId;
+ if (minifiedFieldName
+ .equals(minifyFieldName(itemPropertyName))) {
+ return itemPropertyName;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ protected static String minifyFieldName(String fieldName) {
+ return fieldName.toLowerCase().replace("_", "");
+ }
+
public static class CommitException extends Exception {
public CommitException() {
@@ -909,6 +969,18 @@ public class FieldGroup implements Serializable {
}
+ public static class SearchException extends RuntimeException {
+
+ public SearchException(String message) {
+ super(message);
+ }
+
+ public SearchException(String message, Throwable t) {
+ super(message, t);
+ }
+
+ }
+
/**
* Builds a field and binds it to the given property id using the field
* binder.
diff --git a/server/src/com/vaadin/data/util/AbstractBeanContainer.java b/server/src/com/vaadin/data/util/AbstractBeanContainer.java
index db1e1afe0d..35403d6419 100644
--- a/server/src/com/vaadin/data/util/AbstractBeanContainer.java
+++ b/server/src/com/vaadin/data/util/AbstractBeanContainer.java
@@ -385,6 +385,26 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends
removeFilter(filter);
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.util.AbstractInMemoryContainer#hasContainerFilters()
+ */
+ @Override
+ public boolean hasContainerFilters() {
+ return super.hasContainerFilters();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.util.AbstractInMemoryContainer#getContainerFilters()
+ */
+ @Override
+ public Collection<Filter> getContainerFilters() {
+ return super.getContainerFilters();
+ }
+
/**
* Make this container listen to the given property provided it notifies
* when its value changes.
diff --git a/server/src/com/vaadin/data/util/AbstractInMemoryContainer.java b/server/src/com/vaadin/data/util/AbstractInMemoryContainer.java
index 504b4081c1..84304431bc 100644
--- a/server/src/com/vaadin/data/util/AbstractInMemoryContainer.java
+++ b/server/src/com/vaadin/data/util/AbstractInMemoryContainer.java
@@ -501,6 +501,25 @@ public abstract class AbstractInMemoryContainer<ITEMIDTYPE, PROPERTYIDCLASS, ITE
}
/**
+ * Returns true if any filters have been applied to the container.
+ *
+ * @return true if the container has filters applied, false otherwise
+ * @since 7.1
+ */
+ protected boolean hasContainerFilters() {
+ return !getContainerFilters().isEmpty();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Filterable#getContainerFilters()
+ */
+ protected Collection<Filter> getContainerFilters() {
+ return Collections.unmodifiableCollection(filters);
+ }
+
+ /**
* Remove a specific container filter and re-filter the view (if necessary).
*
* This can be used to implement
diff --git a/server/src/com/vaadin/data/util/AbstractProperty.java b/server/src/com/vaadin/data/util/AbstractProperty.java
index 499421a8b4..903f2f50f2 100644
--- a/server/src/com/vaadin/data/util/AbstractProperty.java
+++ b/server/src/com/vaadin/data/util/AbstractProperty.java
@@ -18,7 +18,6 @@ package com.vaadin.data.util;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
-import java.util.logging.Level;
import java.util.logging.Logger;
import com.vaadin.data.Property;
@@ -71,27 +70,33 @@ public abstract class AbstractProperty<T> implements Property<T>,
}
/**
- * Returns the value of the <code>Property</code> in human readable textual
- * format.
+ * Returns a string representation of this object. The returned string
+ * representation depends on if the legacy Property toString mode is enabled
+ * or disabled.
+ * <p>
+ * If legacy Property toString mode is enabled, returns the value of the
+ * <code>Property</code> converted to a String.
+ * </p>
+ * <p>
+ * If legacy Property toString mode is disabled, the string representation
+ * has no special meaning
+ * </p>
*
- * @return String representation of the value stored in the Property
- * @deprecated As of 7.0, use {@link #getValue()} instead and possibly
- * toString on that
+ * @see LegacyPropertyHelper#isLegacyToStringEnabled()
+ *
+ * @return A string representation of the value value stored in the Property
+ * or a string representation of the Property object.
+ * @deprecated As of 7.0. To get the property value, use {@link #getValue()}
+ * instead (and possibly toString on that)
*/
@Deprecated
@Override
public String toString() {
- getLogger()
- .log(Level.WARNING,
- "You are using Property.toString() instead of getValue() to get the value for a {0}."
- + "This will not be supported starting from Vaadin 7.1 "
- + "(your debugger might call toString() and cause this message to appear).",
- getClass().getSimpleName());
- T v = getValue();
- if (v == null) {
- return null;
+ if (!LegacyPropertyHelper.isLegacyToStringEnabled()) {
+ return super.toString();
+ } else {
+ return LegacyPropertyHelper.legacyPropertyToString(this);
}
- return v.toString();
}
/* Events */
diff --git a/server/src/com/vaadin/data/util/IndexedContainer.java b/server/src/com/vaadin/data/util/IndexedContainer.java
index 1df4dd9bfb..d7bf70caf6 100644
--- a/server/src/com/vaadin/data/util/IndexedContainer.java
+++ b/server/src/com/vaadin/data/util/IndexedContainer.java
@@ -28,7 +28,6 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
-import java.util.logging.Level;
import java.util.logging.Logger;
import com.vaadin.data.Container;
@@ -954,29 +953,32 @@ public class IndexedContainer extends
}
/**
- * Returns the value of the Property in human readable textual format.
- * The return value should be assignable to the <code>setValue</code>
- * method if the Property is not in read-only mode.
+ * Returns a string representation of this object. The returned string
+ * representation depends on if the legacy Property toString mode is
+ * enabled or disabled.
+ * <p>
+ * If legacy Property toString mode is enabled, returns the value of the
+ * <code>Property</code> converted to a String.
+ * </p>
+ * <p>
+ * If legacy Property toString mode is disabled, the string
+ * representation has no special meaning
+ * </p>
*
- * @return <code>String</code> representation of the value stored in the
- * Property
- * @deprecated As of 7.0, use {@link #getValue()} instead and possibly
- * toString on that
+ * @return A string representation of the value value stored in the
+ * Property or a string representation of the Property object.
+ * @deprecated As of 7.0. To get the property value, use
+ * {@link #getValue()} instead (and possibly toString on
+ * that)
*/
@Deprecated
@Override
public String toString() {
- getLogger()
- .log(Level.WARNING,
- "You are using IndexedContainerProperty.toString() instead of getValue() to get the value for a {0}."
- + " This will not be supported starting from Vaadin 7.1 "
- + "(your debugger might call toString() and cause this message to appear).",
- getClass().getSimpleName());
- Object v = getValue();
- if (v == null) {
- return null;
+ if (!LegacyPropertyHelper.isLegacyToStringEnabled()) {
+ return super.toString();
+ } else {
+ return LegacyPropertyHelper.legacyPropertyToString(this);
}
- return v.toString();
}
private Logger getLogger() {
@@ -1190,4 +1192,23 @@ public class IndexedContainer extends
removeFilter(filter);
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.util.AbstractInMemoryContainer#getContainerFilters()
+ */
+ @Override
+ public boolean hasContainerFilters() {
+ return super.hasContainerFilters();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.util.AbstractInMemoryContainer#getContainerFilters()
+ */
+ @Override
+ public Collection<Filter> getContainerFilters() {
+ return super.getContainerFilters();
+ }
}
diff --git a/server/src/com/vaadin/data/util/LegacyPropertyHelper.java b/server/src/com/vaadin/data/util/LegacyPropertyHelper.java
new file mode 100644
index 0000000000..0276e35dbf
--- /dev/null
+++ b/server/src/com/vaadin/data/util/LegacyPropertyHelper.java
@@ -0,0 +1,102 @@
+/*
+ * 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;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.vaadin.data.Property;
+import com.vaadin.server.Constants;
+import com.vaadin.server.DeploymentConfiguration.LegacyProperyToStringMode;
+import com.vaadin.server.VaadinService;
+
+/**
+ * Helper class which provides methods for handling Property.toString in a
+ * Vaadin 6 compatible way
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ * @deprecated This is only used internally for backwards compatibility
+ */
+@Deprecated
+public class LegacyPropertyHelper {
+
+ /**
+ * Returns the property value converted to a String.
+ *
+ * @param p
+ * The property
+ * @return A string representation of the property value, compatible with
+ * how Property implementations in Vaadin 6 do it
+ */
+ public static String legacyPropertyToString(Property p) {
+ maybeLogLegacyPropertyToStringWarning(p);
+ Object value = p.getValue();
+ if (value == null) {
+ return null;
+ }
+ return value.toString();
+ }
+
+ public static void maybeLogLegacyPropertyToStringWarning(Property p) {
+ if (!logLegacyToStringWarning()) {
+ return;
+ }
+
+ getLogger().log(Level.WARNING,
+ Constants.WARNING_LEGACY_PROPERTY_TOSTRING,
+ p.getClass().getName());
+ }
+
+ /**
+ * Checks if legacy Property.toString() implementation is enabled. The
+ * legacy Property.toString() will return the value of the property somehow
+ * converted to a String. If the legacy mode is disabled, toString() will
+ * return super.toString().
+ * <p>
+ * The legacy toString mode can be toggled using the
+ * "legacyPropertyToString" init parameter
+ * </p>
+ *
+ * @return true if legacy Property.toString() mode is enabled, false
+ * otherwise
+ */
+ public static boolean isLegacyToStringEnabled() {
+ if (VaadinService.getCurrent() == null) {
+ // This should really not happen but we need to handle it somehow.
+ // IF it happens it seems more safe to use the legacy mode and log.
+ return true;
+ }
+ return VaadinService.getCurrent().getDeploymentConfiguration()
+ .getLegacyPropertyToStringMode().useLegacyMode();
+ }
+
+ private static boolean logLegacyToStringWarning() {
+ if (VaadinService.getCurrent() == null) {
+ // This should really not happen but we need to handle it somehow.
+ // IF it happens it seems more safe to use the legacy mode and log.
+ return true;
+ }
+ return VaadinService.getCurrent().getDeploymentConfiguration()
+ .getLegacyPropertyToStringMode() == LegacyProperyToStringMode.WARNING;
+
+ }
+
+ private static Logger getLogger() {
+ return Logger.getLogger(LegacyPropertyHelper.class.getName());
+ }
+
+}
diff --git a/server/src/com/vaadin/data/util/sqlcontainer/ColumnProperty.java b/server/src/com/vaadin/data/util/sqlcontainer/ColumnProperty.java
index 378372d044..d8448a2b50 100644
--- a/server/src/com/vaadin/data/util/sqlcontainer/ColumnProperty.java
+++ b/server/src/com/vaadin/data/util/sqlcontainer/ColumnProperty.java
@@ -18,10 +18,10 @@ package com.vaadin.data.util.sqlcontainer;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
-import java.util.logging.Level;
import java.util.logging.Logger;
import com.vaadin.data.Property;
+import com.vaadin.data.util.LegacyPropertyHelper;
import com.vaadin.data.util.converter.Converter.ConversionException;
/**
@@ -255,26 +255,33 @@ final public class ColumnProperty implements Property {
}
/**
- * Returns the value of the Property in human readable textual format.
+ * Returns a string representation of this object. The returned string
+ * representation depends on if the legacy Property toString mode is enabled
+ * or disabled.
+ * <p>
+ * If legacy Property toString mode is enabled, returns the value of this
+ * <code>Property</code> converted to a String.
+ * </p>
+ * <p>
+ * If legacy Property toString mode is disabled, the string representation
+ * has no special meaning
+ * </p>
*
- * @see java.lang.Object#toString()
- * @deprecated As of 7.0, use {@link #getValue()} instead and possibly
- * toString on that
+ * @see LegacyPropertyHelper#isLegacyToStringEnabled()
+ *
+ * @return A string representation of the value value stored in the Property
+ * or a string representation of the Property object.
+ * @deprecated As of 7.0. To get the property value, use {@link #getValue()}
+ * instead (and possibly toString on that)
*/
@Deprecated
@Override
public String toString() {
- getLogger()
- .log(Level.WARNING,
- "You are using ColumnProperty.toString() instead of getValue() to get the value for a {0}. "
- + "This will not be supported starting from Vaadin 7.1 (your debugger might call toString() "
- + "and cause this message to appear).",
- getClass().getSimpleName());
- Object v = getValue();
- if (v == null) {
- return null;
+ if (!LegacyPropertyHelper.isLegacyToStringEnabled()) {
+ return super.toString();
+ } else {
+ return LegacyPropertyHelper.legacyPropertyToString(this);
}
- return v.toString();
}
private static Logger getLogger() {
diff --git a/server/src/com/vaadin/data/util/sqlcontainer/SQLContainer.java b/server/src/com/vaadin/data/util/sqlcontainer/SQLContainer.java
index aa8234ebb9..e9a1a2d98f 100644
--- a/server/src/com/vaadin/data/util/sqlcontainer/SQLContainer.java
+++ b/server/src/com/vaadin/data/util/sqlcontainer/SQLContainer.java
@@ -590,13 +590,32 @@ public class SQLContainer implements Container, Container.Filterable,
/**
* {@inheritDoc}
*/
-
@Override
public void removeAllContainerFilters() {
filters.clear();
refresh();
}
+ /**
+ * Returns true if any filters have been applied to the container.
+ *
+ * @return true if the container has filters applied, false otherwise
+ * @since 7.1
+ */
+ public boolean hasContainerFilters() {
+ return !getContainerFilters().isEmpty();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Filterable#getContainerFilters()
+ */
+ @Override
+ public Collection<Filter> getContainerFilters() {
+ return Collections.unmodifiableCollection(filters);
+ }
+
/**********************************************/
/** Methods from interface Container.Indexed **/
/**********************************************/
@@ -1820,4 +1839,5 @@ public class SQLContainer implements Container, Container.Filterable,
private static final Logger getLogger() {
return Logger.getLogger(SQLContainer.class.getName());
}
+
}
diff --git a/server/src/com/vaadin/data/util/sqlcontainer/query/TableQuery.java b/server/src/com/vaadin/data/util/sqlcontainer/query/TableQuery.java
index caed5526e3..39c8365076 100644
--- a/server/src/com/vaadin/data/util/sqlcontainer/query/TableQuery.java
+++ b/server/src/com/vaadin/data/util/sqlcontainer/query/TableQuery.java
@@ -50,9 +50,23 @@ import com.vaadin.data.util.sqlcontainer.query.generator.StatementHelper;
public class TableQuery extends AbstractTransactionalQuery implements
QueryDelegate, QueryDelegate.RowIdChangeNotifier {
- /** Table name, primary key column name(s) and version column name */
+ /**
+ * Table name (without catalog or schema information).
+ */
private String tableName;
+ private String catalogName;
+ private String schemaName;
+ /**
+ * Cached concatenated version of the table name.
+ */
+ private String fullTableName;
+ /**
+ * Primary key column name(s) in the table.
+ */
private List<String> primaryKeyColumns;
+ /**
+ * Version column name in the table.
+ */
private String versionColumn;
/** Currently set Filters and OrderBys */
@@ -70,15 +84,15 @@ public class TableQuery extends AbstractTransactionalQuery implements
/** Set to true to output generated SQL Queries to System.out */
private final boolean debug = false;
- /** Prevent no-parameters instantiation of TableQuery */
- @SuppressWarnings("unused")
- private TableQuery() {
- }
-
/**
* Creates a new TableQuery using the given connection pool, SQL generator
* and table name to fetch the data from. All parameters must be non-null.
*
+ * The table name must be a simple name with no catalog or schema
+ * information. If those are needed, use
+ * {@link #TableQuery(String, String, String, JDBCConnectionPool, SQLGenerator)}
+ * .
+ *
* @param tableName
* Name of the database table to connect to
* @param connectionPool
@@ -88,15 +102,30 @@ public class TableQuery extends AbstractTransactionalQuery implements
*/
public TableQuery(String tableName, JDBCConnectionPool connectionPool,
SQLGenerator sqlGenerator) {
- super(connectionPool);
- if (tableName == null || tableName.trim().length() < 1
- || connectionPool == null || sqlGenerator == null) {
- throw new IllegalArgumentException(
- "All parameters must be non-null and a table name must be given.");
- }
- this.tableName = tableName;
- this.sqlGenerator = sqlGenerator;
- fetchMetaData();
+ this(null, null, tableName, connectionPool, sqlGenerator);
+ }
+
+ /**
+ * Creates a new TableQuery using the given connection pool, SQL generator
+ * and table name to fetch the data from. Catalog and schema names can be
+ * null, all other parameters must be non-null.
+ *
+ * @param catalogName
+ * Name of the database catalog (can be null)
+ * @param schemaName
+ * Name of the database schema (can be null)
+ * @param tableName
+ * Name of the database table to connect to
+ * @param connectionPool
+ * Connection pool for accessing the database
+ * @param sqlGenerator
+ * SQL query generator implementation
+ * @since 7.1
+ */
+ public TableQuery(String catalogName, String schemaName, String tableName,
+ JDBCConnectionPool connectionPool, SQLGenerator sqlGenerator) {
+ this(catalogName, schemaName, tableName, connectionPool, sqlGenerator,
+ true);
}
/**
@@ -104,6 +133,11 @@ public class TableQuery extends AbstractTransactionalQuery implements
* to fetch the data from. All parameters must be non-null. The default SQL
* generator will be used for queries.
*
+ * The table name must be a simple name with no catalog or schema
+ * information. If those are needed, use
+ * {@link #TableQuery(String, String, String, JDBCConnectionPool, SQLGenerator)}
+ * .
+ *
* @param tableName
* Name of the database table to connect to
* @param connectionPool
@@ -113,6 +147,48 @@ public class TableQuery extends AbstractTransactionalQuery implements
this(tableName, connectionPool, new DefaultSQLGenerator());
}
+ /**
+ * Creates a new TableQuery using the given connection pool, SQL generator
+ * and table name to fetch the data from. Catalog and schema names can be
+ * null, all other parameters must be non-null.
+ *
+ * @param catalogName
+ * Name of the database catalog (can be null)
+ * @param schemaName
+ * Name of the database schema (can be null)
+ * @param tableName
+ * Name of the database table to connect to
+ * @param connectionPool
+ * Connection pool for accessing the database
+ * @param sqlGenerator
+ * SQL query generator implementation
+ * @param escapeNames
+ * true to escape special characters in catalog, schema and table
+ * names, false to use the names as-is
+ * @since 7.1
+ */
+ protected TableQuery(String catalogName, String schemaName,
+ String tableName, JDBCConnectionPool connectionPool,
+ SQLGenerator sqlGenerator, boolean escapeNames) {
+ super(connectionPool);
+ if (tableName == null || tableName.trim().length() < 1
+ || connectionPool == null || sqlGenerator == null) {
+ throw new IllegalArgumentException(
+ "Table name, connection pool and SQL generator parameters must be non-null and non-empty.");
+ }
+ if (escapeNames) {
+ this.catalogName = SQLUtil.escapeSQL(catalogName);
+ this.schemaName = SQLUtil.escapeSQL(schemaName);
+ this.tableName = SQLUtil.escapeSQL(tableName);
+ } else {
+ this.catalogName = catalogName;
+ this.schemaName = schemaName;
+ this.tableName = tableName;
+ }
+ this.sqlGenerator = sqlGenerator;
+ fetchMetaData();
+ }
+
/*
* (non-Javadoc)
*
@@ -121,8 +197,8 @@ public class TableQuery extends AbstractTransactionalQuery implements
@Override
public int getCount() throws SQLException {
getLogger().log(Level.FINE, "Fetching count...");
- StatementHelper sh = sqlGenerator.generateSelectQuery(tableName,
- filters, null, 0, 0, "COUNT(*)");
+ StatementHelper sh = sqlGenerator.generateSelectQuery(
+ getFullTableName(), filters, null, 0, 0, "COUNT(*)");
boolean shouldCloseTransaction = false;
if (!isInTransaction()) {
shouldCloseTransaction = true;
@@ -167,11 +243,11 @@ public class TableQuery extends AbstractTransactionalQuery implements
for (int i = 0; i < primaryKeyColumns.size(); i++) {
ob.add(new OrderBy(primaryKeyColumns.get(i), true));
}
- sh = sqlGenerator.generateSelectQuery(tableName, filters, ob,
- offset, pagelength, null);
+ sh = sqlGenerator.generateSelectQuery(getFullTableName(), filters,
+ ob, offset, pagelength, null);
} else {
- sh = sqlGenerator.generateSelectQuery(tableName, filters, orderBys,
- offset, pagelength, null);
+ sh = sqlGenerator.generateSelectQuery(getFullTableName(), filters,
+ orderBys, offset, pagelength, null);
}
return executeQuery(sh);
}
@@ -204,11 +280,11 @@ public class TableQuery extends AbstractTransactionalQuery implements
int result = 0;
if (row.getId() instanceof TemporaryRowId) {
setVersionColumnFlagInProperty(row);
- sh = sqlGenerator.generateInsertQuery(tableName, row);
+ sh = sqlGenerator.generateInsertQuery(getFullTableName(), row);
result = executeUpdateReturnKeys(sh, row);
} else {
setVersionColumnFlagInProperty(row);
- sh = sqlGenerator.generateUpdateQuery(tableName, row);
+ sh = sqlGenerator.generateUpdateQuery(getFullTableName(), row);
result = executeUpdate(sh);
}
if (versionColumn != null && result == 0) {
@@ -244,7 +320,8 @@ public class TableQuery extends AbstractTransactionalQuery implements
/* Set version column, if one is provided */
setVersionColumnFlagInProperty(row);
/* Generate query */
- StatementHelper sh = sqlGenerator.generateInsertQuery(tableName, row);
+ StatementHelper sh = sqlGenerator.generateInsertQuery(
+ getFullTableName(), row);
Connection connection = null;
PreparedStatement pstmt = null;
ResultSet generatedKeys = null;
@@ -371,10 +448,61 @@ public class TableQuery extends AbstractTransactionalQuery implements
versionColumn = column;
}
+ /**
+ * Returns the table name for the query without catalog and schema
+ * information.
+ *
+ * @return table name, not null
+ */
public String getTableName() {
return tableName;
}
+ /**
+ * Returns the catalog name for the query.
+ *
+ * @return catalog name, can be null
+ * @since 7.1
+ */
+ public String getCatalogName() {
+ return catalogName;
+ }
+
+ /**
+ * Returns the catalog name for the query.
+ *
+ * @return catalog name, can be null
+ * @since 7.1
+ */
+ public String getSchemaName() {
+ return schemaName;
+ }
+
+ /**
+ * Returns the complete table name obtained by concatenation of the catalog
+ * and schema names (if any) and the table name.
+ *
+ * This method can be overridden if customization is needed.
+ *
+ * @return table name in the form it should be used in query and update
+ * statements
+ * @since 7.1
+ */
+ protected String getFullTableName() {
+ if (fullTableName == null) {
+ StringBuilder sb = new StringBuilder();
+ if (catalogName != null) {
+ sb.append(catalogName).append(".");
+ }
+ if (schemaName != null) {
+ sb.append(schemaName).append(".");
+ }
+ sb.append(tableName);
+ fullTableName = sb.toString();
+ }
+ return fullTableName;
+ }
+
public SQLGenerator getSqlGenerator() {
return sqlGenerator;
}
@@ -480,22 +608,28 @@ public class TableQuery extends AbstractTransactionalQuery implements
connection = getConnection();
DatabaseMetaData dbmd = connection.getMetaData();
if (dbmd != null) {
- tableName = SQLUtil.escapeSQL(tableName);
- tables = dbmd.getTables(null, null, tableName, null);
+ tables = dbmd.getTables(catalogName, schemaName, tableName,
+ null);
if (!tables.next()) {
- tables = dbmd.getTables(null, null,
+ String catalog = (catalogName != null) ? catalogName
+ .toUpperCase() : null;
+ String schema = (schemaName != null) ? schemaName
+ .toUpperCase() : null;
+ tables = dbmd.getTables(catalog, schema,
tableName.toUpperCase(), null);
if (!tables.next()) {
throw new IllegalArgumentException(
"Table with the name \""
- + tableName
+ + getFullTableName()
+ "\" was not found. Check your database contents.");
} else {
+ catalogName = catalog;
+ schemaName = schema;
tableName = tableName.toUpperCase();
}
}
tables.close();
- rs = dbmd.getPrimaryKeys(null, null, tableName);
+ rs = dbmd.getPrimaryKeys(catalogName, schemaName, tableName);
List<String> names = new ArrayList<String>();
while (rs.next()) {
names.add(rs.getString("COLUMN_NAME"));
@@ -507,7 +641,7 @@ public class TableQuery extends AbstractTransactionalQuery implements
if (primaryKeyColumns == null || primaryKeyColumns.isEmpty()) {
throw new IllegalArgumentException(
"Primary key constraints have not been defined for the table \""
- + tableName
+ + getFullTableName()
+ "\". Use FreeFormQuery to access this table.");
}
for (String colName : primaryKeyColumns) {
@@ -592,7 +726,7 @@ public class TableQuery extends AbstractTransactionalQuery implements
getLogger().log(Level.FINE, "Removing row with id: {0}",
row.getId().getId()[0]);
}
- if (executeUpdate(sqlGenerator.generateDeleteQuery(getTableName(),
+ if (executeUpdate(sqlGenerator.generateDeleteQuery(getFullTableName(),
primaryKeyColumns, versionColumn, row)) == 1) {
return true;
}
@@ -622,8 +756,8 @@ public class TableQuery extends AbstractTransactionalQuery implements
filtersAndKeys.add(new Equal(colName, keys[ix]));
ix++;
}
- StatementHelper sh = sqlGenerator.generateSelectQuery(tableName,
- filtersAndKeys, orderBys, 0, 0, "*");
+ StatementHelper sh = sqlGenerator.generateSelectQuery(
+ getFullTableName(), filtersAndKeys, orderBys, 0, 0, "*");
boolean shouldCloseTransaction = false;
if (!isInTransaction()) {
diff --git a/server/src/com/vaadin/server/AbstractClientConnector.java b/server/src/com/vaadin/server/AbstractClientConnector.java
index cf579585ea..e998b8ed55 100644
--- a/server/src/com/vaadin/server/AbstractClientConnector.java
+++ b/server/src/com/vaadin/server/AbstractClientConnector.java
@@ -134,6 +134,7 @@ 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";
UI uI = getUI();
if (uI != null) {
uI.getConnectorTracker().markDirty(this);
@@ -218,6 +219,8 @@ 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";
+
if (null == sharedState) {
sharedState = createState();
}
@@ -233,7 +236,7 @@ public abstract class AbstractClientConnector implements ClientConnector,
@Override
public JSONObject encodeState() throws JSONException {
- return AbstractCommunicationManager.encodeState(this, getState());
+ return LegacyCommunicationManager.encodeState(this, getState());
}
/**
@@ -642,17 +645,22 @@ public abstract class AbstractClientConnector implements ClientConnector,
@Override
public boolean handleConnectorRequest(VaadinRequest request,
VaadinResponse response, String path) throws IOException {
+ DownloadStream stream = null;
String[] parts = path.split("/", 2);
String key = parts[0];
- ConnectorResource resource = (ConnectorResource) getResource(key);
- if (resource != null) {
- DownloadStream stream = resource.getStream();
- stream.writeResponse(request, response);
- return true;
- } else {
- return false;
+ getSession().lock();
+ try {
+ ConnectorResource resource = (ConnectorResource) getResource(key);
+ if (resource == null) {
+ return false;
+ }
+ stream = resource.getStream();
+ } finally {
+ getSession().unlock();
}
+ stream.writeResponse(request, response);
+ return true;
}
/**
diff --git a/server/src/com/vaadin/server/AbstractCommunicationManager.java b/server/src/com/vaadin/server/AbstractCommunicationManager.java
deleted file mode 100644
index 17bbbda737..0000000000
--- a/server/src/com/vaadin/server/AbstractCommunicationManager.java
+++ /dev/null
@@ -1,2881 +0,0 @@
-/*
- * Copyright 2000-2013 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.server;
-
-import java.io.BufferedWriter;
-import java.io.ByteArrayOutputStream;
-import java.io.CharArrayWriter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.io.Serializable;
-import java.io.StringWriter;
-import java.lang.reflect.Type;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.security.GeneralSecurityException;
-import java.text.CharacterIterator;
-import java.text.DateFormat;
-import java.text.DateFormatSymbols;
-import java.text.SimpleDateFormat;
-import java.text.StringCharacterIterator;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.GregorianCalendar;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import javax.servlet.http.HttpServletResponse;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import com.vaadin.annotations.JavaScript;
-import com.vaadin.annotations.PreserveOnRefresh;
-import com.vaadin.annotations.StyleSheet;
-import com.vaadin.server.ClientConnector.ConnectorErrorEvent;
-import com.vaadin.server.ComponentSizeValidator.InvalidLayout;
-import com.vaadin.server.ServerRpcManager.RpcInvocationException;
-import com.vaadin.server.StreamVariable.StreamingEndEvent;
-import com.vaadin.server.StreamVariable.StreamingErrorEvent;
-import com.vaadin.shared.ApplicationConstants;
-import com.vaadin.shared.Connector;
-import com.vaadin.shared.JavaScriptConnectorState;
-import com.vaadin.shared.Version;
-import com.vaadin.shared.communication.LegacyChangeVariablesInvocation;
-import com.vaadin.shared.communication.MethodInvocation;
-import com.vaadin.shared.communication.ServerRpc;
-import com.vaadin.shared.communication.SharedState;
-import com.vaadin.shared.communication.UidlValue;
-import com.vaadin.shared.ui.ui.UIConstants;
-import com.vaadin.ui.Component;
-import com.vaadin.ui.ConnectorTracker;
-import com.vaadin.ui.HasComponents;
-import com.vaadin.ui.LegacyComponent;
-import com.vaadin.ui.SelectiveRenderer;
-import com.vaadin.ui.UI;
-import com.vaadin.ui.Window;
-
-/**
- * This is a common base class for the server-side implementations of the
- * communication system between the client code (compiled with GWT into
- * JavaScript) and the server side components. Its client side counterpart is
- * {@link com.vaadin.client.ApplicationConnection}.
- * <p>
- * TODO Document better!
- *
- * @deprecated As of 7.0. Will likely change or be removed in a future version
- */
-@Deprecated
-@SuppressWarnings("serial")
-public abstract class AbstractCommunicationManager implements Serializable {
-
- private static final String DASHDASH = "--";
-
- private static final RequestHandler UNSUPPORTED_BROWSER_HANDLER = new UnsupportedBrowserHandler();
-
- private static final RequestHandler CONNECTOR_RESOURCE_HANDLER = new ConnectorResourceHandler();
-
- /**
- * TODO Document me!
- *
- * @author peholmst
- *
- * @deprecated As of 7.0. Will likely change or be removed in a future
- * version
- */
- @Deprecated
- public interface Callback extends Serializable {
-
- public void criticalNotification(VaadinRequest request,
- VaadinResponse response, String cap, String msg,
- String details, String outOfSyncURL) throws IOException;
- }
-
- static class UploadInterruptedException extends Exception {
- public UploadInterruptedException() {
- super("Upload interrupted by other thread");
- }
- }
-
- // flag used in the request to indicate that the security token should be
- // written to the response
- private static final String WRITE_SECURITY_TOKEN_FLAG = "writeSecurityToken";
-
- /* Variable records indexes */
- public static final char VAR_BURST_SEPARATOR = '\u001d';
-
- public static final char VAR_ESCAPE_CHARACTER = '\u001b';
-
- private final HashMap<Integer, ClientCache> uiToClientCache = new HashMap<Integer, ClientCache>();
-
- private static final int MAX_BUFFER_SIZE = 64 * 1024;
-
- /* Same as in apache commons file upload library that was previously used. */
- private static final int MAX_UPLOAD_BUFFER_SIZE = 4 * 1024;
-
- /**
- * The session this communication manager is used for
- */
- private final VaadinSession session;
-
- private List<String> locales;
-
- private int pendingLocalesIndex;
-
- private int timeoutInterval = -1;
-
- private DragAndDropService dragAndDropService;
-
- private String requestThemeName;
-
- private int maxInactiveInterval;
-
- private ClientConnector highlightedConnector;
-
- private Map<String, Class<?>> publishedFileContexts = new HashMap<String, Class<?>>();
-
- /**
- * TODO New constructor - document me!
- *
- * @param session
- */
- public AbstractCommunicationManager(VaadinSession session) {
- this.session = session;
- session.addRequestHandler(getBootstrapHandler());
- session.addRequestHandler(UNSUPPORTED_BROWSER_HANDLER);
- session.addRequestHandler(CONNECTOR_RESOURCE_HANDLER);
- requireLocale(session.getLocale().toString());
- }
-
- protected VaadinSession getSession() {
- return session;
- }
-
- private static final int LF = "\n".getBytes()[0];
-
- private static final String CRLF = "\r\n";
-
- private static final String UTF8 = "UTF-8";
-
- private static String readLine(InputStream stream) throws IOException {
- ByteArrayOutputStream bout = new ByteArrayOutputStream();
- int readByte = stream.read();
- while (readByte != LF) {
- bout.write(readByte);
- readByte = stream.read();
- }
- byte[] bytes = bout.toByteArray();
- return new String(bytes, 0, bytes.length - 1, UTF8);
- }
-
- /**
- * Method used to stream content from a multipart request (either from
- * servlet or portlet request) to given StreamVariable
- *
- *
- * @param request
- * @param response
- * @param streamVariable
- * @param owner
- * @param boundary
- * @throws IOException
- */
- protected void doHandleSimpleMultipartFileUpload(VaadinRequest request,
- VaadinResponse response, StreamVariable streamVariable,
- String variableName, ClientConnector owner, String boundary)
- throws IOException {
- // multipart parsing, supports only one file for request, but that is
- // fine for our current terminal
-
- final InputStream inputStream = request.getInputStream();
-
- int contentLength = request.getContentLength();
-
- boolean atStart = false;
- boolean firstFileFieldFound = false;
-
- String rawfilename = "unknown";
- String rawMimeType = "application/octet-stream";
-
- /*
- * Read the stream until the actual file starts (empty line). Read
- * filename and content type from multipart headers.
- */
- while (!atStart) {
- String readLine = readLine(inputStream);
- contentLength -= (readLine.getBytes(UTF8).length + CRLF.length());
- if (readLine.startsWith("Content-Disposition:")
- && readLine.indexOf("filename=") > 0) {
- rawfilename = readLine.replaceAll(".*filename=", "");
- char quote = rawfilename.charAt(0);
- rawfilename = rawfilename.substring(1);
- rawfilename = rawfilename.substring(0,
- rawfilename.indexOf(quote));
- firstFileFieldFound = true;
- } else if (firstFileFieldFound && readLine.equals("")) {
- atStart = true;
- } else if (readLine.startsWith("Content-Type")) {
- rawMimeType = readLine.split(": ")[1];
- }
- }
-
- contentLength -= (boundary.length() + CRLF.length() + 2
- * DASHDASH.length() + CRLF.length());
-
- /*
- * Reads bytes from the underlying stream. Compares the read bytes to
- * the boundary string and returns -1 if met.
- *
- * The matching happens so that if the read byte equals to the first
- * char of boundary string, the stream goes to "buffering mode". In
- * buffering mode bytes are read until the character does not match the
- * corresponding from boundary string or the full boundary string is
- * found.
- *
- * Note, if this is someday needed elsewhere, don't shoot yourself to
- * foot and split to a top level helper class.
- */
- InputStream simpleMultiPartReader = new SimpleMultiPartInputStream(
- inputStream, boundary);
-
- /*
- * Should report only the filename even if the browser sends the path
- */
- final String filename = removePath(rawfilename);
- final String mimeType = rawMimeType;
-
- try {
- // TODO Shouldn't this check connectorEnabled?
- if (owner == null) {
- throw new UploadException(
- "File upload ignored because the connector for the stream variable was not found");
- }
- if (owner instanceof Component) {
- if (((Component) owner).isReadOnly()) {
- throw new UploadException(
- "Warning: file upload ignored because the componente was read-only");
- }
- }
- boolean forgetVariable = streamToReceiver(simpleMultiPartReader,
- streamVariable, filename, mimeType, contentLength);
- if (forgetVariable) {
- cleanStreamVariable(owner, variableName);
- }
- } catch (Exception e) {
- session.lock();
- try {
- handleConnectorRelatedException(owner, e);
- } finally {
- session.unlock();
- }
- }
- sendUploadResponse(request, response);
-
- }
-
- /**
- * Used to stream plain file post (aka XHR2.post(File))
- *
- * @param request
- * @param response
- * @param streamVariable
- * @param owner
- * @param contentLength
- * @throws IOException
- */
- protected void doHandleXhrFilePost(VaadinRequest request,
- VaadinResponse response, StreamVariable streamVariable,
- String variableName, ClientConnector owner, int contentLength)
- throws IOException {
-
- // These are unknown in filexhr ATM, maybe add to Accept header that
- // is accessible in portlets
- final String filename = "unknown";
- final String mimeType = filename;
- final InputStream stream = request.getInputStream();
- try {
- /*
- * safe cast as in GWT terminal all variable owners are expected to
- * be components.
- */
- Component component = (Component) owner;
- if (component.isReadOnly()) {
- throw new UploadException(
- "Warning: file upload ignored because the component was read-only");
- }
- boolean forgetVariable = streamToReceiver(stream, streamVariable,
- filename, mimeType, contentLength);
- if (forgetVariable) {
- cleanStreamVariable(owner, variableName);
- }
- } catch (Exception e) {
- session.lock();
- try {
- handleConnectorRelatedException(owner, e);
- } finally {
- session.unlock();
- }
- }
- sendUploadResponse(request, response);
- }
-
- /**
- * @param in
- * @param streamVariable
- * @param filename
- * @param type
- * @param contentLength
- * @return true if the streamvariable has informed that the terminal can
- * forget this variable
- * @throws UploadException
- */
- protected final boolean streamToReceiver(final InputStream in,
- StreamVariable streamVariable, String filename, String type,
- int contentLength) throws UploadException {
- if (streamVariable == null) {
- throw new IllegalStateException(
- "StreamVariable for the post not found");
- }
-
- final VaadinSession session = getSession();
-
- OutputStream out = null;
- int totalBytes = 0;
- StreamingStartEventImpl startedEvent = new StreamingStartEventImpl(
- filename, type, contentLength);
- try {
- boolean listenProgress;
- session.lock();
- try {
- streamVariable.streamingStarted(startedEvent);
- out = streamVariable.getOutputStream();
- listenProgress = streamVariable.listenProgress();
- } finally {
- session.unlock();
- }
-
- // Gets the output target stream
- if (out == null) {
- throw new NoOutputStreamException();
- }
-
- if (null == in) {
- // No file, for instance non-existent filename in html upload
- throw new NoInputStreamException();
- }
-
- final byte buffer[] = new byte[MAX_UPLOAD_BUFFER_SIZE];
- int bytesReadToBuffer = 0;
- while ((bytesReadToBuffer = in.read(buffer)) > 0) {
- out.write(buffer, 0, bytesReadToBuffer);
- totalBytes += bytesReadToBuffer;
- if (listenProgress) {
- // update progress if listener set and contentLength
- // received
- session.lock();
- try {
- StreamingProgressEventImpl progressEvent = new StreamingProgressEventImpl(
- filename, type, contentLength, totalBytes);
- streamVariable.onProgress(progressEvent);
- } finally {
- session.unlock();
- }
- }
- if (streamVariable.isInterrupted()) {
- throw new UploadInterruptedException();
- }
- }
-
- // upload successful
- out.close();
- StreamingEndEvent event = new StreamingEndEventImpl(filename, type,
- totalBytes);
- session.lock();
- try {
- streamVariable.streamingFinished(event);
- } finally {
- session.unlock();
- }
-
- } catch (UploadInterruptedException e) {
- // Download interrupted by application code
- tryToCloseStream(out);
- StreamingErrorEvent event = new StreamingErrorEventImpl(filename,
- type, contentLength, totalBytes, e);
- session.lock();
- try {
- streamVariable.streamingFailed(event);
- } finally {
- session.unlock();
- }
- // Note, we are not throwing interrupted exception forward as it is
- // not a terminal level error like all other exception.
- } catch (final Exception e) {
- tryToCloseStream(out);
- session.lock();
- try {
- StreamingErrorEvent event = new StreamingErrorEventImpl(
- filename, type, contentLength, totalBytes, e);
- streamVariable.streamingFailed(event);
- // throw exception for terminal to be handled (to be passed to
- // terminalErrorHandler)
- throw new UploadException(e);
- } finally {
- session.unlock();
- }
- }
- return startedEvent.isDisposed();
- }
-
- static void tryToCloseStream(OutputStream out) {
- try {
- // try to close output stream (e.g. file handle)
- if (out != null) {
- out.close();
- }
- } catch (IOException e1) {
- // NOP
- }
- }
-
- /**
- * Removes any possible path information from the filename and returns the
- * filename. Separators / and \\ are used.
- *
- * @param name
- * @return
- */
- private static String removePath(String filename) {
- if (filename != null) {
- filename = filename.replaceAll("^.*[/\\\\]", "");
- }
-
- return filename;
- }
-
- /**
- * TODO document
- *
- * @param request
- * @param response
- * @throws IOException
- */
- protected void sendUploadResponse(VaadinRequest request,
- VaadinResponse response) throws IOException {
- response.setContentType("text/html");
- final OutputStream out = response.getOutputStream();
- final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
- new OutputStreamWriter(out, "UTF-8")));
- outWriter.print("<html><body>download handled</body></html>");
- outWriter.flush();
- out.close();
- }
-
- /**
- * Internally process a UIDL request from the client.
- *
- * This method calls
- * {@link #handleVariables(VaadinRequest, VaadinResponse, Callback, VaadinSession, UI)}
- * to process any changes to variables by the client and then repaints
- * affected components using {@link #paintAfterVariableChanges()}.
- *
- * Also, some cleanup is done when a request arrives for an session that has
- * already been closed.
- *
- * The method handleUidlRequest(...) in subclasses should call this method.
- *
- * TODO better documentation
- *
- * @param request
- * @param response
- * @param callback
- * @param uI
- * target window for the UIDL request, can be null if target not
- * found
- * @throws IOException
- * @throws InvalidUIDLSecurityKeyException
- * @throws JSONException
- */
- public void handleUidlRequest(VaadinRequest request,
- VaadinResponse response, Callback callback, UI uI)
- throws IOException, InvalidUIDLSecurityKeyException, JSONException {
-
- checkWidgetsetVersion(request);
- requestThemeName = request.getParameter("theme");
- maxInactiveInterval = request.getWrappedSession()
- .getMaxInactiveInterval();
- // repaint requested or session has timed out and new one is created
- boolean repaintAll;
- final OutputStream out;
-
- repaintAll = (request
- .getParameter(ApplicationConstants.URL_PARAMETER_REPAINT_ALL) != null);
- // || (request.getSession().isNew()); FIXME What the h*ll is this??
- out = response.getOutputStream();
-
- boolean analyzeLayouts = false;
- if (repaintAll) {
- // analyzing can be done only with repaintAll
- analyzeLayouts = (request
- .getParameter(ApplicationConstants.PARAM_ANALYZE_LAYOUTS) != null);
-
- String pid = request
- .getParameter(ApplicationConstants.PARAM_HIGHLIGHT_CONNECTOR);
- if (pid != null) {
- highlightedConnector = uI.getConnectorTracker().getConnector(
- pid);
- highlightConnector(highlightedConnector);
- }
- }
-
- final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
- new OutputStreamWriter(out, "UTF-8")));
-
- // The rest of the process is synchronized with the session
- // in order to guarantee that no parallel variable handling is
- // made
- session.lock();
- try {
-
- // Verify that there's an UI
- if (uI == null) {
- // This should not happen, no windows exists but
- // session is still open.
- getLogger().warning("Could not get UI for session");
- return;
- }
-
- session.setLastRequestTimestamp(System.currentTimeMillis());
-
- // Change all variables based on request parameters
- if (!handleVariables(request, response, callback, session, uI)) {
-
- // var inconsistency; the client is probably out-of-sync
- SystemMessages ci = response.getService().getSystemMessages(
- uI.getLocale(), request);
- String msg = ci.getOutOfSyncMessage();
- String cap = ci.getOutOfSyncCaption();
- if (msg != null || cap != null) {
- callback.criticalNotification(request, response, cap, msg,
- null, ci.getOutOfSyncURL());
- // will reload page after this
- return;
- }
- // No message to show, let's just repaint all.
- repaintAll = true;
- }
-
- paintAfterVariableChanges(request, response, callback, repaintAll,
- outWriter, uI, analyzeLayouts);
- postPaint(uI);
- } finally {
- session.unlock();
- }
-
- outWriter.close();
- requestThemeName = null;
- }
-
- /**
- * Checks that the version reported by the client (widgetset) matches that
- * of the server.
- *
- * @param request
- */
- private void checkWidgetsetVersion(VaadinRequest request) {
- String widgetsetVersion = request.getParameter("v-wsver");
- if (widgetsetVersion == null) {
- // Only check when the widgetset version is reported. It is reported
- // in the first UIDL request (not the initial request as it is a
- // plain GET /)
- return;
- }
-
- if (!Version.getFullVersion().equals(widgetsetVersion)) {
- getLogger().warning(
- String.format(Constants.WIDGETSET_MISMATCH_INFO,
- Version.getFullVersion(), widgetsetVersion));
- }
- }
-
- /**
- * Method called after the paint phase while still being synchronized on the
- * session
- *
- * @param uI
- *
- */
- protected void postPaint(UI uI) {
- // Remove connectors that have been detached from the session during
- // handling of the request
- uI.getConnectorTracker().cleanConnectorMap();
- }
-
- protected void highlightConnector(ClientConnector highlightedConnector) {
- StringBuilder sb = new StringBuilder();
- sb.append("*** Debug details of a connector: *** \n");
- sb.append("Type: ");
- sb.append(highlightedConnector.getClass().getName());
- sb.append("\nId:");
- sb.append(highlightedConnector.getConnectorId());
- if (highlightedConnector instanceof Component) {
- Component component = (Component) highlightedConnector;
- if (component.getCaption() != null) {
- sb.append("\nCaption:");
- sb.append(component.getCaption());
- }
- }
- printHighlightedConnectorHierarchy(sb, highlightedConnector);
- getLogger().info(sb.toString());
- }
-
- protected void printHighlightedConnectorHierarchy(StringBuilder sb,
- ClientConnector connector) {
- LinkedList<ClientConnector> h = new LinkedList<ClientConnector>();
- h.add(connector);
- ClientConnector parent = connector.getParent();
- while (parent != null) {
- h.addFirst(parent);
- parent = parent.getParent();
- }
-
- sb.append("\nConnector hierarchy:\n");
- VaadinSession session2 = connector.getUI().getSession();
- sb.append(session2.getClass().getName());
- sb.append("(");
- sb.append(session2.getClass().getSimpleName());
- sb.append(".java");
- sb.append(":1)");
- int l = 1;
- for (ClientConnector connector2 : h) {
- sb.append("\n");
- for (int i = 0; i < l; i++) {
- sb.append(" ");
- }
- l++;
- Class<? extends ClientConnector> connectorClass = connector2
- .getClass();
- Class<?> topClass = connectorClass;
- while (topClass.getEnclosingClass() != null) {
- topClass = topClass.getEnclosingClass();
- }
- sb.append(connectorClass.getName());
- sb.append("(");
- sb.append(topClass.getSimpleName());
- sb.append(".java:1)");
- }
- }
-
- /**
- * TODO document
- *
- * @param request
- * @param response
- * @param callback
- * @param repaintAll
- * @param outWriter
- * @param window
- * @param analyzeLayouts
- * @throws PaintException
- * @throws IOException
- * @throws JSONException
- */
- private void paintAfterVariableChanges(VaadinRequest request,
- VaadinResponse response, Callback callback, boolean repaintAll,
- final PrintWriter outWriter, UI uI, boolean analyzeLayouts)
- throws PaintException, IOException, JSONException {
- openJsonMessage(outWriter, response);
-
- // security key
- Object writeSecurityTokenFlag = request
- .getAttribute(WRITE_SECURITY_TOKEN_FLAG);
-
- if (writeSecurityTokenFlag != null) {
- outWriter.print(getSecurityKeyUIDL(request));
- }
-
- writeUidlResponse(request, repaintAll, outWriter, uI, analyzeLayouts);
-
- closeJsonMessage(outWriter);
-
- outWriter.close();
-
- }
-
- /**
- * Gets the security key (and generates one if needed) as UIDL.
- *
- * @param request
- * @return the security key UIDL or "" if the feature is turned off
- */
- public String getSecurityKeyUIDL(VaadinRequest request) {
- final String seckey = getSecurityKey(request);
- if (seckey != null) {
- return "\"" + ApplicationConstants.UIDL_SECURITY_TOKEN_ID + "\":\""
- + seckey + "\",";
- } else {
- return "";
- }
- }
-
- /**
- * Gets the security key (and generates one if needed).
- *
- * @param request
- * @return the security key
- */
- protected String getSecurityKey(VaadinRequest request) {
- String seckey = null;
- WrappedSession session = request.getWrappedSession();
- seckey = (String) session
- .getAttribute(ApplicationConstants.UIDL_SECURITY_TOKEN_ID);
- if (seckey == null) {
- seckey = UUID.randomUUID().toString();
- session.setAttribute(ApplicationConstants.UIDL_SECURITY_TOKEN_ID,
- seckey);
- }
-
- return seckey;
- }
-
- @SuppressWarnings("unchecked")
- public void writeUidlResponse(VaadinRequest request, boolean repaintAll,
- final PrintWriter outWriter, UI ui, boolean analyzeLayouts)
- throws PaintException, JSONException {
- ArrayList<ClientConnector> dirtyVisibleConnectors = new ArrayList<ClientConnector>();
- VaadinSession session = ui.getSession();
- // Paints components
- ConnectorTracker uiConnectorTracker = ui.getConnectorTracker();
- getLogger().log(Level.FINE, "* Creating response to client");
- if (repaintAll) {
- getClientCache(ui).clear();
- uiConnectorTracker.markAllConnectorsDirty();
- uiConnectorTracker.markAllClientSidesUninitialized();
-
- // Reset sent locales
- locales = null;
- requireLocale(session.getLocale().toString());
- }
-
- dirtyVisibleConnectors
- .addAll(getDirtyVisibleConnectors(uiConnectorTracker));
-
- getLogger().log(Level.FINE, "Found {0} dirty connectors to paint",
- dirtyVisibleConnectors.size());
- for (ClientConnector connector : dirtyVisibleConnectors) {
- boolean initialized = uiConnectorTracker
- .isClientSideInitialized(connector);
- connector.beforeClientResponse(!initialized);
- }
-
- uiConnectorTracker.setWritingResponse(true);
- try {
- outWriter.print("\"changes\":[");
-
- List<InvalidLayout> invalidComponentRelativeSizes = null;
-
- JsonPaintTarget paintTarget = new JsonPaintTarget(this, outWriter,
- !repaintAll);
- legacyPaint(paintTarget, dirtyVisibleConnectors);
-
- if (analyzeLayouts) {
- invalidComponentRelativeSizes = ComponentSizeValidator
- .validateComponentRelativeSizes(ui.getContent(), null,
- null);
-
- // Also check any existing subwindows
- if (ui.getWindows() != null) {
- for (Window subWindow : ui.getWindows()) {
- invalidComponentRelativeSizes = ComponentSizeValidator
- .validateComponentRelativeSizes(
- subWindow.getContent(),
- invalidComponentRelativeSizes, null);
- }
- }
- }
-
- paintTarget.close();
- outWriter.print("], "); // close changes
-
- // send shared state to client
-
- // for now, send the complete state of all modified and new
- // components
-
- // Ideally, all this would be sent before "changes", but that causes
- // complications with legacy components that create sub-components
- // in their paint phase. Nevertheless, this will be processed on the
- // client after component creation but before legacy UIDL
- // processing.
- JSONObject sharedStates = new JSONObject();
- for (ClientConnector connector : dirtyVisibleConnectors) {
- // encode and send shared state
- try {
- JSONObject stateJson = connector.encodeState();
-
- if (stateJson != null && stateJson.length() != 0) {
- sharedStates.put(connector.getConnectorId(), stateJson);
- }
- } catch (JSONException e) {
- throw new PaintException(
- "Failed to serialize shared state for connector "
- + connector.getClass().getName() + " ("
- + connector.getConnectorId() + "): "
- + e.getMessage(), e);
- }
- }
- outWriter.print("\"state\":");
- outWriter.append(sharedStates.toString());
- outWriter.print(", "); // close states
-
- // TODO This should be optimized. The type only needs to be
- // sent once for each connector id + on refresh. Use the same cache
- // as
- // widget mapping
-
- JSONObject connectorTypes = new JSONObject();
- for (ClientConnector connector : dirtyVisibleConnectors) {
- String connectorType = paintTarget.getTag(connector);
- try {
- connectorTypes.put(connector.getConnectorId(),
- connectorType);
- } catch (JSONException e) {
- throw new PaintException(
- "Failed to send connector type for connector "
- + connector.getConnectorId() + ": "
- + e.getMessage(), e);
- }
- }
- outWriter.print("\"types\":");
- outWriter.append(connectorTypes.toString());
- outWriter.print(", "); // close states
-
- // Send update hierarchy information to the client.
-
- // This could be optimized aswell to send only info if hierarchy has
- // actually changed. Much like with the shared state. Note though
- // that an empty hierarchy is information aswell (e.g. change from 1
- // child to 0 children)
-
- outWriter.print("\"hierarchy\":");
-
- JSONObject hierarchyInfo = new JSONObject();
- for (ClientConnector connector : dirtyVisibleConnectors) {
- String connectorId = connector.getConnectorId();
- JSONArray children = new JSONArray();
-
- for (ClientConnector child : AbstractClientConnector
- .getAllChildrenIterable(connector)) {
- if (isConnectorVisibleToClient(child)) {
- children.put(child.getConnectorId());
- }
- }
- try {
- hierarchyInfo.put(connectorId, children);
- } catch (JSONException e) {
- throw new PaintException(
- "Failed to send hierarchy information about "
- + connectorId + " to the client: "
- + e.getMessage(), e);
- }
- }
- outWriter.append(hierarchyInfo.toString());
- outWriter.print(", "); // close hierarchy
-
- uiConnectorTracker.markAllConnectorsClean();
-
- // send server to client RPC calls for components in the UI, in call
- // order
-
- // collect RPC calls from components in the UI in the order in
- // which they were performed, remove the calls from components
-
- LinkedList<ClientConnector> rpcPendingQueue = new LinkedList<ClientConnector>(
- dirtyVisibleConnectors);
- List<ClientMethodInvocation> pendingInvocations = collectPendingRpcCalls(dirtyVisibleConnectors);
-
- JSONArray rpcCalls = new JSONArray();
- for (ClientMethodInvocation invocation : pendingInvocations) {
- // add invocation to rpcCalls
- try {
- JSONArray invocationJson = new JSONArray();
- invocationJson.put(invocation.getConnector()
- .getConnectorId());
- invocationJson.put(invocation.getInterfaceName());
- invocationJson.put(invocation.getMethodName());
- JSONArray paramJson = new JSONArray();
- for (int i = 0; i < invocation.getParameterTypes().length; ++i) {
- Type parameterType = invocation.getParameterTypes()[i];
- Object referenceParameter = null;
- // TODO Use default values for RPC parameter types
- // if (!JsonCodec.isInternalType(parameterType)) {
- // try {
- // referenceParameter = parameterType.newInstance();
- // } catch (Exception e) {
- // logger.log(Level.WARNING,
- // "Error creating reference object for parameter of type "
- // + parameterType.getName());
- // }
- // }
- EncodeResult encodeResult = JsonCodec.encode(
- invocation.getParameters()[i],
- referenceParameter, parameterType,
- ui.getConnectorTracker());
- paramJson.put(encodeResult.getEncodedValue());
- }
- invocationJson.put(paramJson);
- rpcCalls.put(invocationJson);
- } catch (JSONException e) {
- throw new PaintException(
- "Failed to serialize RPC method call parameters for connector "
- + invocation.getConnector()
- .getConnectorId() + " method "
- + invocation.getInterfaceName() + "."
- + invocation.getMethodName() + ": "
- + e.getMessage(), e);
- }
-
- }
-
- if (rpcCalls.length() > 0) {
- outWriter.print("\"rpc\" : ");
- outWriter.append(rpcCalls.toString());
- outWriter.print(", "); // close rpc
- }
-
- outWriter.print("\"meta\" : {");
- boolean metaOpen = false;
-
- if (repaintAll) {
- metaOpen = true;
- outWriter.write("\"repaintAll\":true");
- if (analyzeLayouts) {
- outWriter.write(", \"invalidLayouts\":");
- outWriter.write("[");
- if (invalidComponentRelativeSizes != null) {
- boolean first = true;
- for (InvalidLayout invalidLayout : invalidComponentRelativeSizes) {
- if (!first) {
- outWriter.write(",");
- } else {
- first = false;
- }
- invalidLayout.reportErrors(outWriter, this,
- System.err);
- }
- }
- outWriter.write("]");
- }
- if (highlightedConnector != null) {
- outWriter.write(", \"hl\":\"");
- outWriter.write(highlightedConnector.getConnectorId());
- outWriter.write("\"");
- highlightedConnector = null;
- }
- }
-
- SystemMessages ci = request.getService().getSystemMessages(
- ui.getLocale(), request);
-
- // meta instruction for client to enable auto-forward to
- // sessionExpiredURL after timer expires.
- if (ci != null && ci.getSessionExpiredMessage() == null
- && ci.getSessionExpiredCaption() == null
- && ci.isSessionExpiredNotificationEnabled()) {
- int newTimeoutInterval = getTimeoutInterval();
- if (repaintAll || (timeoutInterval != newTimeoutInterval)) {
- String escapedURL = ci.getSessionExpiredURL() == null ? ""
- : ci.getSessionExpiredURL().replace("/", "\\/");
- if (metaOpen) {
- outWriter.write(",");
- }
- outWriter.write("\"timedRedirect\":{\"interval\":"
- + (newTimeoutInterval + 15) + ",\"url\":\""
- + escapedURL + "\"}");
- metaOpen = true;
- }
- timeoutInterval = newTimeoutInterval;
- }
-
- outWriter.print("}, \"resources\" : {");
-
- // Precache custom layouts
-
- // TODO We should only precache the layouts that are not
- // cached already (plagiate from usedPaintableTypes)
- int resourceIndex = 0;
- for (final Iterator<Object> i = paintTarget.getUsedResources()
- .iterator(); i.hasNext();) {
- final String resource = (String) i.next();
- InputStream is = null;
- try {
- is = getThemeResourceAsStream(ui, getTheme(ui), resource);
- } catch (final Exception e) {
- // FIXME: Handle exception
- getLogger().log(Level.FINER,
- "Failed to get theme resource stream.", e);
- }
- if (is != null) {
-
- outWriter.print((resourceIndex++ > 0 ? ", " : "") + "\""
- + resource + "\" : ");
- final StringBuffer layout = new StringBuffer();
-
- try {
- final InputStreamReader r = new InputStreamReader(is,
- "UTF-8");
- final char[] buffer = new char[20000];
- int charsRead = 0;
- while ((charsRead = r.read(buffer)) > 0) {
- layout.append(buffer, 0, charsRead);
- }
- r.close();
- } catch (final java.io.IOException e) {
- // FIXME: Handle exception
- getLogger().log(Level.INFO, "Resource transfer failed",
- e);
- }
- outWriter.print("\""
- + JsonPaintTarget.escapeJSON(layout.toString())
- + "\"");
- } else {
- // FIXME: Handle exception
- getLogger().severe("CustomLayout not found: " + resource);
- }
- }
- outWriter.print("}");
-
- Collection<Class<? extends ClientConnector>> usedClientConnectors = paintTarget
- .getUsedClientConnectors();
- boolean typeMappingsOpen = false;
- ClientCache clientCache = getClientCache(ui);
-
- List<Class<? extends ClientConnector>> newConnectorTypes = new ArrayList<Class<? extends ClientConnector>>();
-
- for (Class<? extends ClientConnector> class1 : usedClientConnectors) {
- if (clientCache.cache(class1)) {
- // client does not know the mapping key for this type, send
- // mapping to client
- newConnectorTypes.add(class1);
-
- if (!typeMappingsOpen) {
- typeMappingsOpen = true;
- outWriter.print(", \"typeMappings\" : { ");
- } else {
- outWriter.print(" , ");
- }
- String canonicalName = class1.getCanonicalName();
- outWriter.print("\"");
- outWriter.print(canonicalName);
- outWriter.print("\" : ");
- outWriter.print(getTagForType(class1));
- }
- }
- if (typeMappingsOpen) {
- outWriter.print(" }");
- }
-
- boolean typeInheritanceMapOpen = false;
- if (typeMappingsOpen) {
- // send the whole type inheritance map if any new mappings
- for (Class<? extends ClientConnector> class1 : usedClientConnectors) {
- if (!ClientConnector.class.isAssignableFrom(class1
- .getSuperclass())) {
- continue;
- }
- if (!typeInheritanceMapOpen) {
- typeInheritanceMapOpen = true;
- outWriter.print(", \"typeInheritanceMap\" : { ");
- } else {
- outWriter.print(" , ");
- }
- outWriter.print("\"");
- outWriter.print(getTagForType(class1));
- outWriter.print("\" : ");
- outWriter
- .print(getTagForType((Class<? extends ClientConnector>) class1
- .getSuperclass()));
- }
- if (typeInheritanceMapOpen) {
- outWriter.print(" }");
- }
- }
-
- /*
- * Ensure super classes come before sub classes to get script
- * dependency order right. Sub class @JavaScript might assume that
- *
- * @JavaScript defined by super class is already loaded.
- */
- Collections.sort(newConnectorTypes, new Comparator<Class<?>>() {
- @Override
- public int compare(Class<?> o1, Class<?> o2) {
- // TODO optimize using Class.isAssignableFrom?
- return hierarchyDepth(o1) - hierarchyDepth(o2);
- }
-
- private int hierarchyDepth(Class<?> type) {
- if (type == Object.class) {
- return 0;
- } else {
- return hierarchyDepth(type.getSuperclass()) + 1;
- }
- }
- });
-
- List<String> scriptDependencies = new ArrayList<String>();
- List<String> styleDependencies = new ArrayList<String>();
-
- for (Class<? extends ClientConnector> class1 : newConnectorTypes) {
- JavaScript jsAnnotation = class1
- .getAnnotation(JavaScript.class);
- if (jsAnnotation != null) {
- for (String uri : jsAnnotation.value()) {
- scriptDependencies.add(registerDependency(uri, class1));
- }
- }
-
- StyleSheet styleAnnotation = class1
- .getAnnotation(StyleSheet.class);
- if (styleAnnotation != null) {
- for (String uri : styleAnnotation.value()) {
- styleDependencies.add(registerDependency(uri, class1));
- }
- }
- }
-
- // Include script dependencies in output if there are any
- if (!scriptDependencies.isEmpty()) {
- outWriter.print(", \"scriptDependencies\": "
- + new JSONArray(scriptDependencies).toString());
- }
-
- // Include style dependencies in output if there are any
- if (!styleDependencies.isEmpty()) {
- outWriter.print(", \"styleDependencies\": "
- + new JSONArray(styleDependencies).toString());
- }
-
- // add any pending locale definitions requested by the client
- printLocaleDeclarations(outWriter);
-
- if (dragAndDropService != null) {
- dragAndDropService.printJSONResponse(outWriter);
- }
-
- for (ClientConnector connector : dirtyVisibleConnectors) {
- uiConnectorTracker.markClientSideInitialized(connector);
- }
-
- assert (uiConnectorTracker.getDirtyConnectors().isEmpty()) : "Connectors have been marked as dirty during the end of the paint phase. This is most certainly not intended.";
-
- writePerformanceData(outWriter);
- } finally {
- uiConnectorTracker.setWritingResponse(false);
- }
- }
-
- public static JSONObject encodeState(ClientConnector connector,
- SharedState state) throws JSONException {
- UI uI = connector.getUI();
- ConnectorTracker connectorTracker = uI.getConnectorTracker();
- Class<? extends SharedState> stateType = connector.getStateType();
- Object diffState = connectorTracker.getDiffState(connector);
- boolean supportsDiffState = !JavaScriptConnectorState.class
- .isAssignableFrom(stateType);
- if (diffState == null && supportsDiffState) {
- // Use an empty state object as reference for full
- // repaints
-
- try {
- SharedState referenceState = stateType.newInstance();
- EncodeResult encodeResult = JsonCodec.encode(referenceState,
- null, stateType, uI.getConnectorTracker());
- diffState = encodeResult.getEncodedValue();
- } catch (Exception e) {
- getLogger()
- .log(Level.WARNING,
- "Error creating reference object for state of type {0}",
- stateType.getName());
- }
- }
- EncodeResult encodeResult = JsonCodec.encode(state, diffState,
- stateType, uI.getConnectorTracker());
- if (supportsDiffState) {
- connectorTracker.setDiffState(connector,
- (JSONObject) encodeResult.getEncodedValue());
- }
- return (JSONObject) encodeResult.getDiff();
- }
-
- /**
- * Resolves a dependency URI, registering the URI with this
- * {@code AbstractCommunicationManager} if needed and returns a fully
- * qualified URI.
- */
- private String registerDependency(String resourceUri, Class<?> context) {
- try {
- URI uri = new URI(resourceUri);
- String protocol = uri.getScheme();
-
- if (ApplicationConstants.PUBLISHED_PROTOCOL_NAME.equals(protocol)) {
- // Strip initial slash
- String resourceName = uri.getPath().substring(1);
- return registerPublishedFile(resourceName, context);
- }
-
- if (protocol != null || uri.getHost() != null) {
- return resourceUri;
- }
-
- // Bare path interpreted as published file
- return registerPublishedFile(resourceUri, context);
- } catch (URISyntaxException e) {
- getLogger().log(Level.WARNING,
- "Could not parse resource url " + resourceUri, e);
- return resourceUri;
- }
- }
-
- private String registerPublishedFile(String name, Class<?> context) {
- synchronized (publishedFileContexts) {
- // Add to map of names accepted by servePublishedFile
- if (publishedFileContexts.containsKey(name)) {
- Class<?> oldContext = publishedFileContexts.get(name);
- if (oldContext != context) {
- getLogger()
- .log(Level.WARNING,
- "{0} published by both {1} and {2}. File from {2} will be used.",
- new Object[] { name, context, oldContext });
- }
- } else {
- publishedFileContexts.put(name, context);
- }
- }
-
- return ApplicationConstants.PUBLISHED_PROTOCOL_PREFIX + "/" + name;
- }
-
- /**
- * Adds the performance timing data (used by TestBench 3) to the UIDL
- * response.
- */
- private void writePerformanceData(final PrintWriter outWriter) {
- outWriter.write(String.format(", \"timings\":[%d, %d]",
- session.getCumulativeRequestDuration(),
- session.getLastRequestDuration()));
- }
-
- private void legacyPaint(PaintTarget paintTarget,
- ArrayList<ClientConnector> dirtyVisibleConnectors)
- throws PaintException {
- List<LegacyComponent> legacyComponents = new ArrayList<LegacyComponent>();
- for (Connector connector : dirtyVisibleConnectors) {
- // All Components that want to use paintContent must implement
- // LegacyComponent
- if (connector instanceof LegacyComponent) {
- legacyComponents.add((LegacyComponent) connector);
- }
- }
- sortByHierarchy((List) legacyComponents);
- for (LegacyComponent c : legacyComponents) {
- if (getLogger().isLoggable(Level.FINE)) {
- getLogger().log(
- Level.FINE,
- "Painting LegacyComponent {0}@{1}",
- new Object[] { c.getClass().getName(),
- Integer.toHexString(c.hashCode()) });
- }
- paintTarget.startTag("change");
- final String pid = c.getConnectorId();
- paintTarget.addAttribute("pid", pid);
- LegacyPaint.paint(c, paintTarget);
- paintTarget.endTag("change");
- }
-
- }
-
- private void sortByHierarchy(List<Component> paintables) {
- // Vaadin 6 requires parents to be painted before children as component
- // containers rely on that their updateFromUIDL method has been called
- // before children start calling e.g. updateCaption
- Collections.sort(paintables, new Comparator<Component>() {
-
- @Override
- public int compare(Component c1, Component c2) {
- int depth1 = 0;
- while (c1.getParent() != null) {
- depth1++;
- c1 = c1.getParent();
- }
- int depth2 = 0;
- while (c2.getParent() != null) {
- depth2++;
- c2 = c2.getParent();
- }
- if (depth1 < depth2) {
- return -1;
- }
- if (depth1 > depth2) {
- return 1;
- }
- return 0;
- }
- });
-
- }
-
- private ClientCache getClientCache(UI uI) {
- Integer uiId = Integer.valueOf(uI.getUIId());
- ClientCache cache = uiToClientCache.get(uiId);
- if (cache == null) {
- cache = new ClientCache();
- uiToClientCache.put(uiId, cache);
- }
- return cache;
- }
-
- /**
- * Checks if the connector is visible in context. For Components,
- * {@link #isComponentVisibleToClient(Component)} is used. For other types
- * of connectors, the contextual visibility of its first Component ancestor
- * is used. If no Component ancestor is found, the connector is not visible.
- *
- * @param connector
- * The connector to check
- * @return <code>true</code> if the connector is visible to the client,
- * <code>false</code> otherwise
- */
- public static boolean isConnectorVisibleToClient(ClientConnector connector) {
- if (connector instanceof Component) {
- return isComponentVisibleToClient((Component) connector);
- } else {
- ClientConnector parent = connector.getParent();
- if (parent == null) {
- return false;
- } else {
- return isConnectorVisibleToClient(parent);
- }
- }
- }
-
- /**
- * Checks if the component should be visible to the client. Returns false if
- * the child should not be sent to the client, true otherwise.
- *
- * @param child
- * The child to check
- * @return true if the child is visible to the client, false otherwise
- */
- public static boolean isComponentVisibleToClient(Component child) {
- if (!child.isVisible()) {
- return false;
- }
- HasComponents parent = child.getParent();
-
- if (parent instanceof SelectiveRenderer) {
- if (!((SelectiveRenderer) parent).isRendered(child)) {
- return false;
- }
- }
-
- if (parent != null) {
- return isComponentVisibleToClient(parent);
- } else {
- if (child instanceof UI) {
- // UI has no parent and visibility was checked above
- return true;
- } else {
- // Component which is not attached to any UI
- return false;
- }
- }
- }
-
- private static class NullIterator<E> implements Iterator<E> {
-
- @Override
- public boolean hasNext() {
- return false;
- }
-
- @Override
- public E next() {
- return null;
- }
-
- @Override
- public void remove() {
- }
-
- }
-
- /**
- * Collects all pending RPC calls from listed {@link ClientConnector}s and
- * clears their RPC queues.
- *
- * @param rpcPendingQueue
- * list of {@link ClientConnector} of interest
- * @return ordered list of pending RPC calls
- */
- private List<ClientMethodInvocation> collectPendingRpcCalls(
- List<ClientConnector> rpcPendingQueue) {
- List<ClientMethodInvocation> pendingInvocations = new ArrayList<ClientMethodInvocation>();
- for (ClientConnector connector : rpcPendingQueue) {
- List<ClientMethodInvocation> paintablePendingRpc = connector
- .retrievePendingRpcCalls();
- if (null != paintablePendingRpc && !paintablePendingRpc.isEmpty()) {
- List<ClientMethodInvocation> oldPendingRpc = pendingInvocations;
- int totalCalls = pendingInvocations.size()
- + paintablePendingRpc.size();
- pendingInvocations = new ArrayList<ClientMethodInvocation>(
- totalCalls);
-
- // merge two ordered comparable lists
- for (int destIndex = 0, oldIndex = 0, paintableIndex = 0; destIndex < totalCalls; destIndex++) {
- if (paintableIndex >= paintablePendingRpc.size()
- || (oldIndex < oldPendingRpc.size() && ((Comparable<ClientMethodInvocation>) oldPendingRpc
- .get(oldIndex))
- .compareTo(paintablePendingRpc
- .get(paintableIndex)) <= 0)) {
- pendingInvocations.add(oldPendingRpc.get(oldIndex++));
- } else {
- pendingInvocations.add(paintablePendingRpc
- .get(paintableIndex++));
- }
- }
- }
- }
- return pendingInvocations;
- }
-
- protected abstract InputStream getThemeResourceAsStream(UI uI,
- String themeName, String resource);
-
- private int getTimeoutInterval() {
- return maxInactiveInterval;
- }
-
- private String getTheme(UI uI) {
- String themeName = uI.getTheme();
- String requestThemeName = getRequestTheme();
-
- if (requestThemeName != null) {
- themeName = requestThemeName;
- }
- if (themeName == null) {
- themeName = VaadinServlet.getDefaultTheme();
- }
- return themeName;
- }
-
- private String getRequestTheme() {
- return requestThemeName;
- }
-
- /**
- * Returns false if the cross site request forgery protection is turned off.
- *
- * @param session
- * @return false if the XSRF is turned off, true otherwise
- */
- public boolean isXSRFEnabled(VaadinSession session) {
- return session.getConfiguration().isXsrfProtectionEnabled();
- }
-
- /**
- * TODO document
- *
- * If this method returns false, something was submitted that we did not
- * expect; this is probably due to the client being out-of-sync and sending
- * variable changes for non-existing pids
- *
- * @return true if successful, false if there was an inconsistency
- */
- private boolean handleVariables(VaadinRequest request,
- VaadinResponse response, Callback callback, VaadinSession session,
- UI uI) throws IOException, InvalidUIDLSecurityKeyException,
- JSONException {
- boolean success = true;
-
- String changes = getRequestPayload(request);
- if (changes != null) {
-
- // Manage bursts one by one
- final String[] bursts = changes.split(String
- .valueOf(VAR_BURST_SEPARATOR));
-
- // Security: double cookie submission pattern unless disabled by
- // property
- if (isXSRFEnabled(session)) {
- if (bursts.length == 1 && "init".equals(bursts[0])) {
- // init request; don't handle any variables, key sent in
- // response.
- request.setAttribute(WRITE_SECURITY_TOKEN_FLAG, true);
- return true;
- } else {
- // ApplicationServlet has stored the security token in the
- // session; check that it matched the one sent in the UIDL
- String sessId = (String) request
- .getWrappedSession()
- .getAttribute(
- ApplicationConstants.UIDL_SECURITY_TOKEN_ID);
-
- if (sessId == null || !sessId.equals(bursts[0])) {
- throw new InvalidUIDLSecurityKeyException(
- "Security key mismatch");
- }
- }
-
- }
-
- for (int bi = 1; bi < bursts.length; bi++) {
- // unescape any encoded separator characters in the burst
- final String burst = unescapeBurst(bursts[bi]);
- success &= handleBurst(request, uI, burst);
-
- // In case that there were multiple bursts, we know that this is
- // a special synchronous case for closing window. Thus we are
- // not interested in sending any UIDL changes back to client.
- // Still we must clear component tree between bursts to ensure
- // that no removed components are updated. The painting after
- // the last burst is handled normally by the calling method.
- if (bi < bursts.length - 1) {
-
- // We will be discarding all changes
- final PrintWriter outWriter = new PrintWriter(
- new CharArrayWriter());
-
- paintAfterVariableChanges(request, response, callback,
- true, outWriter, uI, false);
-
- }
-
- }
- }
- /*
- * Note that we ignore inconsistencies while handling unload request.
- * The client can't remove invalid variable changes from the burst, and
- * we don't have the required logic implemented on the server side. E.g.
- * a component is removed in a previous burst.
- */
- return success;
- }
-
- /**
- * 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.
- *
- * 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
- * @return true if the processing of the burst was successful and there were
- * no messages to non-existent components
- */
- public boolean handleBurst(VaadinRequest source, UI uI, final String burst) {
- boolean success = true;
- try {
- Set<Connector> enabledConnectors = new HashSet<Connector>();
-
- List<MethodInvocation> invocations = parseInvocations(
- uI.getConnectorTracker(), burst);
- for (MethodInvocation invocation : invocations) {
- final ClientConnector connector = getConnector(uI,
- invocation.getConnectorId());
-
- if (connector != null && connector.isConnectorEnabled()) {
- enabledConnectors.add(connector);
- }
- }
-
- for (int i = 0; i < invocations.size(); i++) {
- MethodInvocation invocation = invocations.get(i);
-
- final ClientConnector connector = getConnector(uI,
- invocation.getConnectorId());
- if (connector == null) {
- getLogger()
- .log(Level.WARNING,
- "Received RPC call for unknown connector with id {0} (tried to invoke {1}.{2})",
- new Object[] { invocation.getConnectorId(),
- invocation.getInterfaceName(),
- invocation.getMethodName() });
- continue;
- }
-
- if (!enabledConnectors.contains(connector)) {
-
- if (invocation instanceof LegacyChangeVariablesInvocation) {
- LegacyChangeVariablesInvocation legacyInvocation = (LegacyChangeVariablesInvocation) invocation;
- // TODO convert window close to a separate RPC call and
- // handle above - not a variable change
-
- // Handle special case where window-close is called
- // after the window has been removed from the
- // application or the application has closed
- Map<String, Object> changes = legacyInvocation
- .getVariableChanges();
- if (changes.size() == 1 && changes.containsKey("close")
- && Boolean.TRUE.equals(changes.get("close"))) {
- // Silently ignore this
- continue;
- }
- }
-
- // Connector is disabled, log a warning and move to the next
- String msg = "Ignoring RPC call for disabled connector "
- + connector.getClass().getName();
- if (connector instanceof Component) {
- String caption = ((Component) connector).getCaption();
- if (caption != null) {
- msg += ", caption=" + caption;
- }
- }
- getLogger().warning(msg);
- continue;
- }
-
- if (invocation instanceof ServerRpcMethodInvocation) {
- try {
- ServerRpcManager.applyInvocation(connector,
- (ServerRpcMethodInvocation) invocation);
- } catch (RpcInvocationException e) {
- handleConnectorRelatedException(connector, e);
- }
- } else {
-
- // All code below is for legacy variable changes
- LegacyChangeVariablesInvocation legacyInvocation = (LegacyChangeVariablesInvocation) invocation;
- Map<String, Object> changes = legacyInvocation
- .getVariableChanges();
- try {
- if (connector instanceof VariableOwner) {
- changeVariables(source, (VariableOwner) connector,
- changes);
- } else {
- throw new IllegalStateException(
- "Received legacy variable change for "
- + connector.getClass().getName()
- + " ("
- + connector.getConnectorId()
- + ") which is not a VariableOwner. The client-side connector sent these legacy varaibles: "
- + changes.keySet());
- }
- } catch (Exception e) {
- handleConnectorRelatedException(connector, e);
- }
- }
- }
- } catch (JSONException e) {
- getLogger().log(Level.WARNING,
- "Unable to parse RPC call from the client: {0}",
- e.getMessage());
- // TODO or return success = false?
- throw new RuntimeException(e);
- }
-
- return success;
- }
-
- /**
- * Handles an exception that occurred when processing Rpc calls or a file
- * upload.
- *
- * @param ui
- * The UI where the exception occured
- * @param throwable
- * The exception
- * @param connector
- * The Rpc target
- */
- private void handleConnectorRelatedException(ClientConnector connector,
- Throwable throwable) {
- ErrorEvent errorEvent = new ConnectorErrorEvent(connector, throwable);
- ErrorHandler handler = ErrorEvent.findErrorHandler(connector);
- handler.error(errorEvent);
- }
-
- /**
- * Parse a message burst from the client into a list of MethodInvocation
- * instances.
- *
- * @param connectorTracker
- * The ConnectorTracker used to lookup connectors
- * @param burst
- * message string (JSON)
- * @return list of MethodInvocation to perform
- * @throws JSONException
- */
- private List<MethodInvocation> parseInvocations(
- ConnectorTracker connectorTracker, final String burst)
- throws JSONException {
- JSONArray invocationsJson = new JSONArray(burst);
-
- ArrayList<MethodInvocation> invocations = new ArrayList<MethodInvocation>();
-
- MethodInvocation previousInvocation = null;
- // parse JSON to MethodInvocations
- for (int i = 0; i < invocationsJson.length(); ++i) {
-
- JSONArray invocationJson = invocationsJson.getJSONArray(i);
-
- MethodInvocation invocation = parseInvocation(invocationJson,
- previousInvocation, connectorTracker);
- 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
- // rejected because of an error.
- invocations.add(invocation);
- previousInvocation = invocation;
- }
- }
- return invocations;
- }
-
- private MethodInvocation parseInvocation(JSONArray invocationJson,
- MethodInvocation previousInvocation,
- ConnectorTracker connectorTracker) throws JSONException {
- String connectorId = invocationJson.getString(0);
- String interfaceName = invocationJson.getString(1);
- String methodName = invocationJson.getString(2);
-
- 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();
- return null;
- }
-
- JSONArray parametersJson = invocationJson.getJSONArray(3);
-
- if (LegacyChangeVariablesInvocation.isLegacyVariableChange(
- interfaceName, methodName)) {
- if (!(previousInvocation instanceof LegacyChangeVariablesInvocation)) {
- previousInvocation = null;
- }
-
- return parseLegacyChangeVariablesInvocation(connectorId,
- interfaceName, methodName,
- (LegacyChangeVariablesInvocation) previousInvocation,
- parametersJson, connectorTracker);
- } else {
- return parseServerRpcInvocation(connectorId, interfaceName,
- methodName, parametersJson, connectorTracker);
- }
-
- }
-
- private LegacyChangeVariablesInvocation parseLegacyChangeVariablesInvocation(
- String connectorId, String interfaceName, String methodName,
- LegacyChangeVariablesInvocation previousInvocation,
- JSONArray parametersJson, ConnectorTracker connectorTracker)
- throws JSONException {
- if (parametersJson.length() != 2) {
- throw new JSONException(
- "Invalid parameters in legacy change variables call. Expected 2, was "
- + parametersJson.length());
- }
- String variableName = parametersJson.getString(0);
- UidlValue uidlValue = (UidlValue) JsonCodec.decodeInternalType(
- UidlValue.class, true, parametersJson.get(1), connectorTracker);
-
- Object value = uidlValue.getValue();
-
- if (previousInvocation != null
- && previousInvocation.getConnectorId().equals(connectorId)) {
- previousInvocation.setVariableChange(variableName, value);
- return null;
- } else {
- return new LegacyChangeVariablesInvocation(connectorId,
- variableName, value);
- }
- }
-
- private ServerRpcMethodInvocation parseServerRpcInvocation(
- String connectorId, String interfaceName, String methodName,
- JSONArray parametersJson, ConnectorTracker connectorTracker)
- throws JSONException {
- ClientConnector connector = connectorTracker.getConnector(connectorId);
-
- ServerRpcManager<?> rpcManager = connector.getRpcManager(interfaceName);
- if (rpcManager == null) {
- /*
- * Security: Don't even decode the json parameters if no RpcManager
- * corresponding to the received method invocation has been
- * registered.
- */
- getLogger()
- .log(Level.WARNING,
- "Ignoring RPC call to {0}.{1} in connector {2} ({3}) as no RPC implementation is regsitered",
- new Object[] { interfaceName, methodName,
- connector.getClass().getName(), connectorId });
- return null;
- }
-
- // Use interface from RpcManager instead of loading the class based on
- // the string name to avoid problems with OSGi
- Class<? extends ServerRpc> rpcInterface = rpcManager.getRpcInterface();
-
- ServerRpcMethodInvocation invocation = new ServerRpcMethodInvocation(
- connectorId, rpcInterface, methodName, parametersJson.length());
-
- Object[] parameters = new Object[parametersJson.length()];
- Type[] declaredRpcMethodParameterTypes = invocation.getMethod()
- .getGenericParameterTypes();
-
- for (int j = 0; j < parametersJson.length(); ++j) {
- Object parameterValue = parametersJson.get(j);
- Type parameterType = declaredRpcMethodParameterTypes[j];
- parameters[j] = JsonCodec.decodeInternalOrCustomType(parameterType,
- parameterValue, connectorTracker);
- }
- invocation.setParameters(parameters);
- return invocation;
- }
-
- protected void changeVariables(Object source, final VariableOwner owner,
- Map<String, Object> m) {
- owner.changeVariables(source, m);
- }
-
- protected ClientConnector getConnector(UI uI, String connectorId) {
- ClientConnector c = uI.getConnectorTracker().getConnector(connectorId);
- if (c == null
- && connectorId.equals(getDragAndDropService().getConnectorId())) {
- return getDragAndDropService();
- }
-
- return c;
- }
-
- private DragAndDropService getDragAndDropService() {
- if (dragAndDropService == null) {
- dragAndDropService = new DragAndDropService(this);
- }
- return dragAndDropService;
- }
-
- /**
- * Reads the request data from the Request and returns it converted to an
- * UTF-8 string.
- *
- * @param request
- * @return
- * @throws IOException
- */
- protected String getRequestPayload(VaadinRequest request)
- throws IOException {
-
- int requestLength = request.getContentLength();
- if (requestLength == 0) {
- return null;
- }
-
- ByteArrayOutputStream bout = requestLength <= 0 ? new ByteArrayOutputStream()
- : new ByteArrayOutputStream(requestLength);
-
- InputStream inputStream = request.getInputStream();
- byte[] buffer = new byte[MAX_BUFFER_SIZE];
-
- while (true) {
- int read = inputStream.read(buffer);
- if (read == -1) {
- break;
- }
- bout.write(buffer, 0, read);
- }
- String result = new String(bout.toByteArray(), "utf-8");
-
- return result;
- }
-
- /**
- * 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();
- }
-
- /**
- * Prints the queued (pending) locale definitions to a {@link PrintWriter}
- * in a (UIDL) format that can be sent to the client and used there in
- * formatting dates, times etc.
- *
- * @param outWriter
- */
- private void printLocaleDeclarations(PrintWriter outWriter) {
- /*
- * ----------------------------- Sending Locale sensitive date
- * -----------------------------
- */
-
- // Send locale informations to client
- outWriter.print(", \"locales\":[");
- for (; pendingLocalesIndex < locales.size(); pendingLocalesIndex++) {
-
- final Locale l = generateLocale(locales.get(pendingLocalesIndex));
- // Locale name
- outWriter.print("{\"name\":\"" + l.toString() + "\",");
-
- /*
- * Month names (both short and full)
- */
- final DateFormatSymbols dfs = new DateFormatSymbols(l);
- final String[] short_months = dfs.getShortMonths();
- final String[] months = dfs.getMonths();
- outWriter.print("\"smn\":[\""
- + // ShortMonthNames
- short_months[0] + "\",\"" + short_months[1] + "\",\""
- + short_months[2] + "\",\"" + short_months[3] + "\",\""
- + short_months[4] + "\",\"" + short_months[5] + "\",\""
- + short_months[6] + "\",\"" + short_months[7] + "\",\""
- + short_months[8] + "\",\"" + short_months[9] + "\",\""
- + short_months[10] + "\",\"" + short_months[11] + "\""
- + "],");
- outWriter.print("\"mn\":[\""
- + // MonthNames
- months[0] + "\",\"" + months[1] + "\",\"" + months[2]
- + "\",\"" + months[3] + "\",\"" + months[4] + "\",\""
- + months[5] + "\",\"" + months[6] + "\",\"" + months[7]
- + "\",\"" + months[8] + "\",\"" + months[9] + "\",\""
- + months[10] + "\",\"" + months[11] + "\"" + "],");
-
- /*
- * Weekday names (both short and full)
- */
- final String[] short_days = dfs.getShortWeekdays();
- final String[] days = dfs.getWeekdays();
- outWriter.print("\"sdn\":[\""
- + // ShortDayNames
- short_days[1] + "\",\"" + short_days[2] + "\",\""
- + short_days[3] + "\",\"" + short_days[4] + "\",\""
- + short_days[5] + "\",\"" + short_days[6] + "\",\""
- + short_days[7] + "\"" + "],");
- outWriter.print("\"dn\":[\""
- + // DayNames
- days[1] + "\",\"" + days[2] + "\",\"" + days[3] + "\",\""
- + days[4] + "\",\"" + days[5] + "\",\"" + days[6] + "\",\""
- + days[7] + "\"" + "],");
-
- /*
- * First day of week (0 = sunday, 1 = monday)
- */
- final Calendar cal = new GregorianCalendar(l);
- outWriter.print("\"fdow\":" + (cal.getFirstDayOfWeek() - 1) + ",");
-
- /*
- * Date formatting (MM/DD/YYYY etc.)
- */
-
- DateFormat dateFormat = DateFormat.getDateTimeInstance(
- DateFormat.SHORT, DateFormat.SHORT, l);
- if (!(dateFormat instanceof SimpleDateFormat)) {
- getLogger().log(Level.WARNING,
- "Unable to get default date pattern for locale {0}", l);
- dateFormat = new SimpleDateFormat();
- }
- final String df = ((SimpleDateFormat) dateFormat).toPattern();
-
- int timeStart = df.indexOf("H");
- if (timeStart < 0) {
- timeStart = df.indexOf("h");
- }
- final int ampm_first = df.indexOf("a");
- // E.g. in Korean locale AM/PM is before h:mm
- // TODO should take that into consideration on client-side as well,
- // now always h:mm a
- if (ampm_first > 0 && ampm_first < timeStart) {
- timeStart = ampm_first;
- }
- // Hebrew locale has time before the date
- final boolean timeFirst = timeStart == 0;
- String dateformat;
- if (timeFirst) {
- int dateStart = df.indexOf(' ');
- if (ampm_first > dateStart) {
- dateStart = df.indexOf(' ', ampm_first);
- }
- dateformat = df.substring(dateStart + 1);
- } else {
- dateformat = df.substring(0, timeStart - 1);
- }
-
- outWriter.print("\"df\":\"" + dateformat.trim() + "\",");
-
- /*
- * Time formatting (24 or 12 hour clock and AM/PM suffixes)
- */
- final String timeformat = df.substring(timeStart, df.length());
- /*
- * Doesn't return second or milliseconds.
- *
- * We use timeformat to determine 12/24-hour clock
- */
- final boolean twelve_hour_clock = timeformat.indexOf("a") > -1;
- // TODO there are other possibilities as well, like 'h' in french
- // (ignore them, too complicated)
- final String hour_min_delimiter = timeformat.indexOf(".") > -1 ? "."
- : ":";
- // outWriter.print("\"tf\":\"" + timeformat + "\",");
- outWriter.print("\"thc\":" + twelve_hour_clock + ",");
- outWriter.print("\"hmd\":\"" + hour_min_delimiter + "\"");
- if (twelve_hour_clock) {
- final String[] ampm = dfs.getAmPmStrings();
- outWriter.print(",\"ampm\":[\"" + ampm[0] + "\",\"" + ampm[1]
- + "\"]");
- }
- outWriter.print("}");
- if (pendingLocalesIndex < locales.size() - 1) {
- outWriter.print(",");
- }
- }
- outWriter.print("]"); // Close locales
- }
-
- protected void closeJsonMessage(PrintWriter outWriter) {
- outWriter.print("}]");
- }
-
- /**
- * Writes the opening of JSON message to be sent to client.
- *
- * @param outWriter
- * @param response
- */
- protected void openJsonMessage(PrintWriter outWriter,
- VaadinResponse response) {
- // Sets the response type
- response.setContentType("application/json; charset=UTF-8");
- // some dirt to prevent cross site scripting
- outWriter.print("for(;;);[{");
- }
-
- /**
- * Returns dirty components which are in given window. Components in an
- * invisible subtrees are omitted.
- *
- * @param w
- * UI window for which dirty components is to be fetched
- * @return
- */
- private ArrayList<ClientConnector> getDirtyVisibleConnectors(
- ConnectorTracker connectorTracker) {
- ArrayList<ClientConnector> dirtyConnectors = new ArrayList<ClientConnector>();
- for (ClientConnector c : connectorTracker.getDirtyConnectors()) {
- if (isConnectorVisibleToClient(c)) {
- dirtyConnectors.add(c);
- }
- }
-
- return dirtyConnectors;
- }
-
- /**
- * Queues a locale to be sent to the client (browser) for date and time
- * entry etc. All locale specific information is derived from server-side
- * {@link Locale} instances and sent to the client when needed, eliminating
- * the need to use the {@link Locale} class and all the framework behind it
- * on the client.
- *
- * @see Locale#toString()
- *
- * @param value
- */
- public void requireLocale(String value) {
- if (locales == null) {
- locales = new ArrayList<String>();
- locales.add(session.getLocale().toString());
- pendingLocalesIndex = 0;
- }
- if (!locales.contains(value)) {
- locales.add(value);
- }
- }
-
- /**
- * Constructs a {@link Locale} instance to be sent to the client based on a
- * short locale description string.
- *
- * @see #requireLocale(String)
- *
- * @param value
- * @return
- */
- private Locale generateLocale(String value) {
- final String[] temp = value.split("_");
- if (temp.length == 1) {
- return new Locale(temp[0]);
- } else if (temp.length == 2) {
- return new Locale(temp[0], temp[1]);
- } else {
- return new Locale(temp[0], temp[1], temp[2]);
- }
- }
-
- protected class InvalidUIDLSecurityKeyException extends
- GeneralSecurityException {
-
- InvalidUIDLSecurityKeyException(String message) {
- super(message);
- }
-
- }
-
- private final HashMap<Class<? extends ClientConnector>, Integer> typeToKey = new HashMap<Class<? extends ClientConnector>, Integer>();
- private int nextTypeKey = 0;
-
- private BootstrapHandler bootstrapHandler;
-
- String getTagForType(Class<? extends ClientConnector> class1) {
- Integer id = typeToKey.get(class1);
- if (id == null) {
- id = nextTypeKey++;
- typeToKey.put(class1, id);
- if (getLogger().isLoggable(Level.FINE)) {
- getLogger().log(Level.FINE, "Mapping {0} to {1}",
- new Object[] { class1.getName(), id });
- }
- }
- return id.toString();
- }
-
- /**
- * Helper class for terminal to keep track of data that client is expected
- * to know.
- *
- * TODO make customlayout templates (from theme) to be cached here.
- */
- class ClientCache implements Serializable {
-
- private final Set<Object> res = new HashSet<Object>();
-
- /**
- *
- * @param paintable
- * @return true if the given class was added to cache
- */
- boolean cache(Object object) {
- return res.add(object);
- }
-
- public void clear() {
- res.clear();
- }
-
- }
-
- public String getStreamVariableTargetUrl(ClientConnector owner,
- String name, StreamVariable value) {
- /*
- * We will use the same APP/* URI space as ApplicationResources but
- * prefix url with UPLOAD
- *
- * eg. APP/UPLOAD/[UIID]/[PID]/[NAME]/[SECKEY]
- *
- * SECKEY is created on each paint to make URL's unpredictable (to
- * prevent CSRF attacks).
- *
- * NAME and PID from URI forms a key to fetch StreamVariable when
- * handling post
- */
- String paintableId = owner.getConnectorId();
- UI ui = owner.getUI();
- int uiId = ui.getUIId();
- String key = uiId + "/" + paintableId + "/" + name;
-
- ConnectorTracker connectorTracker = ui.getConnectorTracker();
- connectorTracker.addStreamVariable(paintableId, name, value);
- String seckey = connectorTracker.getSeckey(value);
-
- return ApplicationConstants.APP_PROTOCOL_PREFIX
- + ServletPortletHelper.UPLOAD_URL_PREFIX + key + "/" + seckey;
-
- }
-
- public void cleanStreamVariable(ClientConnector owner, String name) {
- owner.getUI().getConnectorTracker()
- .cleanStreamVariable(owner.getConnectorId(), name);
- }
-
- /**
- * Gets the bootstrap handler that should be used for generating the pages
- * bootstrapping applications for this communication manager.
- *
- * @return the bootstrap handler to use
- */
- private BootstrapHandler getBootstrapHandler() {
- if (bootstrapHandler == null) {
- bootstrapHandler = createBootstrapHandler();
- }
-
- return bootstrapHandler;
- }
-
- /**
- * @return
- *
- * @deprecated As of 7.0. Will likely change or be removed in a future
- * version
- */
- @Deprecated
- protected abstract BootstrapHandler createBootstrapHandler();
-
- /**
- * Handles a request by passing it to each registered {@link RequestHandler}
- * in turn until one produces a response. This method is used for requests
- * that have not been handled by any specific functionality in the terminal
- * implementation (e.g. {@link VaadinServlet}).
- * <p>
- * The request handlers are invoked in the revere order in which they were
- * added to the session until a response has been produced. This means that
- * the most recently added handler is used first and the first request
- * handler that was added to the session is invoked towards the end unless
- * any previous handler has already produced a response.
- * </p>
- *
- * @param request
- * the Vaadin request to get information from
- * @param response
- * the response to which data can be written
- * @return returns <code>true</code> if a {@link RequestHandler} has
- * produced a response and <code>false</code> if no response has
- * been written.
- * @throws IOException
- * if a handler throws an exception
- *
- * @see VaadinSession#addRequestHandler(RequestHandler)
- * @see RequestHandler
- *
- * @since 7.0
- */
- protected boolean handleOtherRequest(VaadinRequest request,
- VaadinResponse response) throws IOException {
- // Use a copy to avoid ConcurrentModificationException
- for (RequestHandler handler : new ArrayList<RequestHandler>(
- session.getRequestHandlers())) {
- if (handler.handleRequest(session, request, response)) {
- return true;
- }
- }
- // If not handled
- return false;
- }
-
- public void handleBrowserDetailsRequest(VaadinRequest request,
- VaadinResponse response, VaadinSession session) throws IOException {
-
- session.lock();
-
- try {
- assert UI.getCurrent() == null;
-
- response.setContentType("application/json; charset=UTF-8");
-
- UI uI = getBrowserDetailsUI(request, session);
-
- JSONObject params = new JSONObject();
- params.put(UIConstants.UI_ID_PARAMETER, uI.getUIId());
- String initialUIDL = getInitialUIDL(request, uI);
- params.put("uidl", initialUIDL);
-
- // NOTE! GateIn requires, for some weird reason, getOutputStream
- // to be used instead of getWriter() (it seems to interpret
- // application/json as a binary content type)
- final OutputStream out = response.getOutputStream();
- final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
- new OutputStreamWriter(out, "UTF-8")));
-
- outWriter.write(params.toString());
- // NOTE GateIn requires the buffers to be flushed to work
- outWriter.flush();
- out.flush();
- } catch (JSONException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } finally {
- session.unlock();
- }
- }
-
- private UI getBrowserDetailsUI(VaadinRequest request, VaadinSession session) {
- VaadinService vaadinService = request.getService();
-
- List<UIProvider> uiProviders = session.getUIProviders();
-
- UIClassSelectionEvent classSelectionEvent = new UIClassSelectionEvent(
- request);
-
- UIProvider provider = null;
- Class<? extends UI> uiClass = null;
- for (UIProvider p : uiProviders) {
- // Check for existing LegacyWindow
- if (p instanceof LegacyApplicationUIProvider) {
- LegacyApplicationUIProvider legacyProvider = (LegacyApplicationUIProvider) p;
-
- UI existingUi = legacyProvider
- .getExistingUI(classSelectionEvent);
- if (existingUi != null) {
- reinitUI(existingUi, request);
- return existingUi;
- }
- }
-
- uiClass = p.getUIClass(classSelectionEvent);
- if (uiClass != null) {
- provider = p;
- break;
- }
- }
-
- if (provider == null || uiClass == null) {
- 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
-
- Integer retainedUIId = retainOnRefreshUIs.get(windowName);
-
- if (retainedUIId != null) {
- UI retainedUI = session.getUIById(retainedUIId.intValue());
- if (uiClass.isInstance(retainedUI)) {
- reinitUI(retainedUI, request);
- return retainedUI;
- } else {
- getLogger().log(
- Level.INFO,
- "Not using retained UI in {0} because retained UI was of type {1}"
- + " but {2} is expected for the request.",
- new Object[] { windowName, retainedUI.getClass(),
- uiClass });
- }
- }
- }
-
- // No existing UI found - go on by creating and initializing one
-
- Integer uiId = Integer.valueOf(session.getNextUIid());
-
- // Explicit Class.cast to detect if the UIProvider does something
- // unexpected
- UICreateEvent event = new UICreateEvent(request, uiClass, uiId);
- UI ui = uiClass.cast(provider.createInstance(event));
-
- // Initialize some fields for a newly created UI
- if (ui.getSession() != session) {
- // Session already set for LegacyWindow
- ui.setSession(session);
- }
-
- // Set thread local here so it is available in init
- UI.setCurrent(ui);
-
- ui.doInit(request, uiId.intValue());
-
- session.addUI(ui);
-
- // Remember if it should be remembered
- if (vaadinService.preserveUIOnRefresh(provider, event)) {
- // Remember this UI
- if (windowName == null) {
- getLogger()
- .log(Level.WARNING,
- "There is no window.name available for UI {0} that should be preserved.",
- uiClass);
- } else {
- session.getPreserveOnRefreshUIs().put(windowName, uiId);
- }
- }
-
- return ui;
- }
-
- /**
- * Updates a UI that has already been initialized but is now loaded again,
- * e.g. because of {@link PreserveOnRefresh}.
- *
- * @param ui
- * @param request
- */
- 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);
- }
- }
-
- /**
- * Generates the initial UIDL message that can e.g. be included in a html
- * page to avoid a separate round trip just for getting the UIDL.
- *
- * @param request
- * the request that caused the initialization
- * @param uI
- * the UI for which the UIDL should be generated
- * @return a string with the initial UIDL message
- * @throws PaintException
- * if an exception occurs while painting
- * @throws JSONException
- * if an exception occurs while encoding output
- */
- protected String getInitialUIDL(VaadinRequest request, UI uI)
- throws PaintException, JSONException {
- // TODO maybe unify writeUidlResponse()?
- StringWriter sWriter = new StringWriter();
- PrintWriter pWriter = new PrintWriter(sWriter);
- pWriter.print("{");
- if (isXSRFEnabled(uI.getSession())) {
- pWriter.print(getSecurityKeyUIDL(request));
- }
- writeUidlResponse(request, true, pWriter, uI, false);
- pWriter.print("}");
- String initialUIDL = sWriter.toString();
- getLogger().log(Level.FINE, "Initial UIDL:{0}", initialUIDL);
- return initialUIDL;
- }
-
- /**
- * Serve a connector resource from the classpath if the resource has
- * previously been registered by calling
- * {@link #registerPublishedFile(String, Class)}. Sending arbitrary files
- * from the classpath is prevented by only accepting resource names that
- * have explicitly been registered. Resources can currently only be
- * registered by including a {@link JavaScript} or {@link StyleSheet}
- * annotation on a Connector class.
- *
- * @param request
- * @param response
- *
- * @throws IOException
- */
- public void servePublishedFile(VaadinRequest request,
- VaadinResponse response) throws IOException {
-
- String pathInfo = request.getPathInfo();
- // + 2 to also remove beginning and ending slashes
- String fileName = pathInfo
- .substring(ApplicationConstants.PUBLISHED_FILE_PATH.length() + 2);
-
- final String mimetype = response.getService().getMimeType(fileName);
-
- // Security check: avoid accidentally serving from the UI of the
- // classpath instead of relative to the context class
- if (fileName.startsWith("/")) {
- getLogger().log(Level.WARNING,
- "Published file request starting with / rejected: {0}",
- fileName);
- response.sendError(HttpServletResponse.SC_NOT_FOUND, fileName);
- return;
- }
-
- // Check that the resource name has been registered
- Class<?> context;
- synchronized (publishedFileContexts) {
- context = publishedFileContexts.get(fileName);
- }
-
- // Security check: don't serve resource if the name hasn't been
- // registered in the map
- if (context == null) {
- getLogger()
- .log(Level.WARNING,
- "Rejecting published file request for file that has not been published: {0}",
- fileName);
- response.sendError(HttpServletResponse.SC_NOT_FOUND, fileName);
- return;
- }
-
- // Resolve file relative to the location of the context class
- InputStream in = context.getResourceAsStream(fileName);
- if (in == null) {
- getLogger()
- .log(Level.WARNING,
- "{0} published by {1} not found. Verify that the file {2}/{3} is available on the classpath.",
- new Object[] {
- fileName,
- context.getName(),
- context.getPackage().getName()
- .replace('.', '/'), fileName });
- response.sendError(HttpServletResponse.SC_NOT_FOUND, fileName);
- return;
- }
-
- // TODO Check and set cache headers
-
- OutputStream out = null;
- try {
- if (mimetype != null) {
- response.setContentType(mimetype);
- }
-
- out = response.getOutputStream();
-
- final byte[] buffer = new byte[Constants.DEFAULT_BUFFER_SIZE];
-
- int bytesRead = 0;
- while ((bytesRead = in.read(buffer)) > 0) {
- out.write(buffer, 0, bytesRead);
- }
- out.flush();
- } finally {
- try {
- in.close();
- } catch (Exception e) {
- // Do nothing
- }
- if (out != null) {
- try {
- out.close();
- } catch (Exception e) {
- // Do nothing
- }
- }
- }
- }
-
- /**
- * Handles file upload request submitted via Upload component.
- *
- * @param UI
- * The UI for this request
- *
- * @see #getStreamVariableTargetUrl(ReceiverOwner, String, StreamVariable)
- *
- * @param request
- * @param response
- * @throws IOException
- * @throws InvalidUIDLSecurityKeyException
- */
- public void handleFileUpload(VaadinSession session, VaadinRequest request,
- VaadinResponse response) throws IOException,
- InvalidUIDLSecurityKeyException {
-
- /*
- * URI pattern: APP/UPLOAD/[UIID]/[PID]/[NAME]/[SECKEY] See
- * #createReceiverUrl
- */
-
- String pathInfo = request.getPathInfo();
- // strip away part until the data we are interested starts
- int startOfData = pathInfo
- .indexOf(ServletPortletHelper.UPLOAD_URL_PREFIX)
- + ServletPortletHelper.UPLOAD_URL_PREFIX.length();
- String uppUri = pathInfo.substring(startOfData);
- String[] parts = uppUri.split("/", 4); // 0= UIid, 1 = cid, 2= name, 3
- // = sec key
- String uiId = parts[0];
- String connectorId = parts[1];
- String variableName = parts[2];
- UI uI = session.getUIById(Integer.parseInt(uiId));
- UI.setCurrent(uI);
-
- StreamVariable streamVariable = uI.getConnectorTracker()
- .getStreamVariable(connectorId, variableName);
- String secKey = uI.getConnectorTracker().getSeckey(streamVariable);
- if (secKey.equals(parts[3])) {
-
- ClientConnector source = getConnector(uI, connectorId);
- String contentType = request.getContentType();
- if (contentType.contains("boundary")) {
- // Multipart requests contain boundary string
- doHandleSimpleMultipartFileUpload(request, response,
- streamVariable, variableName, source,
- contentType.split("boundary=")[1]);
- } else {
- // if boundary string does not exist, the posted file is from
- // XHR2.post(File)
- doHandleXhrFilePost(request, response, streamVariable,
- variableName, source, request.getContentLength());
- }
- } else {
- throw new InvalidUIDLSecurityKeyException(
- "Security key in upload post did not match!");
- }
-
- }
-
- /**
- * Handles a heartbeat request. Heartbeat requests are periodically sent by
- * the client-side to inform the server that the UI sending the heartbeat is
- * still alive (the browser window is open, the connection is up) even when
- * there are no UIDL requests for a prolonged period of time. UIs that do
- * not receive either heartbeat or UIDL requests are eventually removed from
- * the session and garbage collected.
- *
- * @param request
- * @param response
- * @param session
- * @throws IOException
- */
- public void handleHeartbeatRequest(VaadinRequest request,
- VaadinResponse response, VaadinSession session) throws IOException {
- UI ui = null;
- try {
- int uiId = Integer.parseInt(request
- .getParameter(UIConstants.UI_ID_PARAMETER));
- ui = session.getUIById(uiId);
- } catch (NumberFormatException nfe) {
- // null-check below handles this as well
- }
- if (ui != null) {
- ui.setLastHeartbeatTimestamp(System.currentTimeMillis());
- // Ensure that the browser does not cache heartbeat responses.
- // iOS 6 Safari requires this (#10370)
- response.setHeader("Cache-Control", "no-cache");
- } else {
- response.sendError(HttpServletResponse.SC_NOT_FOUND, "UI not found");
- }
- }
-
- /**
- * Stream that extracts content from another stream until the boundary
- * string is encountered.
- *
- * Public only for unit tests, should be considered private for all other
- * purposes.
- */
- public static class SimpleMultiPartInputStream extends InputStream {
-
- /**
- * Counter of how many characters have been matched to boundary string
- * from the stream
- */
- int matchedCount = -1;
-
- /**
- * Used as pointer when returning bytes after partly matched boundary
- * string.
- */
- int curBoundaryIndex = 0;
- /**
- * The byte found after a "promising start for boundary"
- */
- private int bufferedByte = -1;
- private boolean atTheEnd = false;
-
- private final char[] boundary;
-
- private final InputStream realInputStream;
-
- public SimpleMultiPartInputStream(InputStream realInputStream,
- String boundaryString) {
- boundary = (CRLF + DASHDASH + boundaryString).toCharArray();
- this.realInputStream = realInputStream;
- }
-
- @Override
- public int read() throws IOException {
- if (atTheEnd) {
- // End boundary reached, nothing more to read
- return -1;
- } else if (bufferedByte >= 0) {
- /* Purge partially matched boundary if there was such */
- return getBuffered();
- } else if (matchedCount != -1) {
- /*
- * Special case where last "failed" matching ended with first
- * character from boundary string
- */
- return matchForBoundary();
- } else {
- int fromActualStream = realInputStream.read();
- if (fromActualStream == -1) {
- // unexpected end of stream
- throw new IOException(
- "The multipart stream ended unexpectedly");
- }
- if (boundary[0] == fromActualStream) {
- /*
- * If matches the first character in boundary string, start
- * checking if the boundary is fetched.
- */
- return matchForBoundary();
- }
- return fromActualStream;
- }
- }
-
- /**
- * Reads the input to expect a boundary string. Expects that the first
- * character has already been matched.
- *
- * @return -1 if the boundary was matched, else returns the first byte
- * from boundary
- * @throws IOException
- */
- private int matchForBoundary() throws IOException {
- matchedCount = 0;
- /*
- * Going to "buffered mode". Read until full boundary match or a
- * different character.
- */
- while (true) {
- matchedCount++;
- if (matchedCount == boundary.length) {
- /*
- * The whole boundary matched so we have reached the end of
- * file
- */
- atTheEnd = true;
- return -1;
- }
- int fromActualStream = realInputStream.read();
- if (fromActualStream != boundary[matchedCount]) {
- /*
- * Did not find full boundary, cache the mismatching byte
- * and start returning the partially matched boundary.
- */
- bufferedByte = fromActualStream;
- return getBuffered();
- }
- }
- }
-
- /**
- * Returns the partly matched boundary string and the byte following
- * that.
- *
- * @return
- * @throws IOException
- */
- private int getBuffered() throws IOException {
- int b;
- if (matchedCount == 0) {
- // The boundary has been returned, return the buffered byte.
- b = bufferedByte;
- bufferedByte = -1;
- matchedCount = -1;
- } else {
- b = boundary[curBoundaryIndex++];
- if (curBoundaryIndex == matchedCount) {
- // The full boundary has been returned, remaining is the
- // char that did not match the boundary.
-
- curBoundaryIndex = 0;
- if (bufferedByte != boundary[0]) {
- /*
- * next call for getBuffered will return the
- * bufferedByte that came after the partial boundary
- * match
- */
- matchedCount = 0;
- } else {
- /*
- * Special case where buffered byte again matches the
- * boundaryString. This could be the start of the real
- * end boundary.
- */
- matchedCount = 0;
- bufferedByte = -1;
- }
- }
- }
- if (b == -1) {
- throw new IOException("The multipart stream ended unexpectedly");
- }
- return b;
- }
- }
-
- private static final Logger getLogger() {
- return Logger.getLogger(AbstractCommunicationManager.class.getName());
- }
-}
diff --git a/server/src/com/vaadin/server/BootstrapHandler.java b/server/src/com/vaadin/server/BootstrapHandler.java
index 403fefc0e1..dddfb385a6 100644
--- a/server/src/com/vaadin/server/BootstrapHandler.java
+++ b/server/src/com/vaadin/server/BootstrapHandler.java
@@ -41,6 +41,7 @@ import org.jsoup.parser.Tag;
import com.vaadin.shared.ApplicationConstants;
import com.vaadin.shared.Version;
+import com.vaadin.shared.communication.PushMode;
import com.vaadin.ui.UI;
/**
@@ -51,7 +52,14 @@ import com.vaadin.ui.UI;
* @deprecated As of 7.0. Will likely change or be removed in a future version
*/
@Deprecated
-public abstract class BootstrapHandler implements RequestHandler {
+public abstract class BootstrapHandler extends SynchronizedRequestHandler {
+
+ /**
+ * Parameter that is added to the UI init request if the session has already
+ * been restarted when generating the bootstrap HTML and ?restartApplication
+ * should thus be ignored when handling the UI init request.
+ */
+ public static final String IGNORE_RESTART_PARAM = "ignoreRestart";
protected class BootstrapContext implements Serializable {
@@ -61,6 +69,7 @@ public abstract class BootstrapHandler implements RequestHandler {
private String widgetsetName;
private String themeName;
private String appId;
+ private PushMode pushMode;
public BootstrapContext(VaadinResponse response,
BootstrapFragmentResponse bootstrapResponse) {
@@ -98,6 +107,30 @@ public abstract class BootstrapHandler implements RequestHandler {
return themeName;
}
+ public PushMode getPushMode() {
+ if (pushMode == null) {
+ UICreateEvent event = new UICreateEvent(getRequest(),
+ getUIClass());
+
+ pushMode = getBootstrapResponse().getUIProvider().getPushMode(
+ event);
+ if (pushMode == null) {
+ pushMode = getRequest().getService()
+ .getDeploymentConfiguration().getPushMode();
+ }
+
+ if (pushMode.isEnabled()
+ && !getRequest().getService().ensurePushAvailable()) {
+ /*
+ * Fall back if not supported (ensurePushAvailable will log
+ * information to the developer the first time this happens)
+ */
+ pushMode = PushMode.DISABLED;
+ }
+ }
+ return pushMode;
+ }
+
public String getAppId() {
if (appId == null) {
appId = getRequest().getService().getMainDivId(getSession(),
@@ -113,10 +146,19 @@ public abstract class BootstrapHandler implements RequestHandler {
}
@Override
- public boolean handleRequest(VaadinSession session, VaadinRequest request,
- VaadinResponse response) throws IOException {
+ public boolean synchronizedHandleRequest(VaadinSession session,
+ VaadinRequest request, VaadinResponse response) throws IOException {
+ if (ServletPortletHelper.isAppRequest(request)) {
+ // We do not want to handle /APP requests here, instead let it fall
+ // through and produce a 404
+ return false;
+ }
try {
+ // Update WebBrowser here only to make WebBrowser information
+ // available in init for LegacyApplications
+ session.getBrowser().updateRequestDetails(request);
+
List<UIProvider> uiProviders = session.getUIProviders();
UIClassSelectionEvent classSelectionEvent = new UIClassSelectionEvent(
@@ -241,11 +283,9 @@ public abstract class BootstrapHandler implements RequestHandler {
/*
* Enable Chrome Frame in all versions of IE if installed.
- *
- * Claim IE10 support to avoid using compatibility mode.
*/
head.appendElement("meta").attr("http-equiv", "X-UA-Compatible")
- .attr("content", "IE=9;chrome=1");
+ .attr("content", "IE=10;chrome=1");
String title = response.getUIProvider().getPageTitle(
new UICreateEvent(context.getRequest(), context.getUIClass()));
@@ -334,8 +374,8 @@ public abstract class BootstrapHandler implements RequestHandler {
VaadinRequest request = context.getRequest();
VaadinService vaadinService = request.getService();
- String staticFileLocation = vaadinService
- .getStaticFileLocation(request);
+ String vaadinLocation = vaadinService.getStaticFileLocation(request)
+ + "/VAADIN/";
fragmentNodes
.add(new Element(Tag.valueOf("iframe"), "")
@@ -345,8 +385,14 @@ public abstract class BootstrapHandler implements RequestHandler {
"position:absolute;width:0;height:0;border:0;overflow:hidden")
.attr("src", "javascript:false"));
- String bootstrapLocation = staticFileLocation
- + "/VAADIN/vaadinBootstrap.js";
+ if (context.getPushMode().isEnabled()) {
+ // Load client-side dependencies for push support
+ fragmentNodes.add(new Element(Tag.valueOf("script"), "").attr(
+ "type", "text/javascript").attr("src",
+ vaadinLocation + ApplicationConstants.VAADIN_PUSH_JS));
+ }
+
+ String bootstrapLocation = vaadinLocation + "vaadinBootstrap.js";
fragmentNodes.add(new Element(Tag.valueOf("script"), "").attr("type",
"text/javascript").attr("src", bootstrapLocation));
Element mainScriptTag = new Element(Tag.valueOf("script"), "").attr(
@@ -415,6 +461,12 @@ public abstract class BootstrapHandler implements RequestHandler {
appConfig.put("theme", themeName);
}
+ // Ignore restartApplication that might be passed to UI init
+ if (request
+ .getParameter(VaadinService.URL_PARAMETER_RESTART_APPLICATION) != null) {
+ appConfig.put("extraParams", "&" + IGNORE_RESTART_PARAM + "=1");
+ }
+
JSONObject versionInfo = new JSONObject();
versionInfo.put("vaadinVersion", Version.getFullVersion());
appConfig.put("versionInfo", versionInfo);
diff --git a/server/src/com/vaadin/server/BrowserWindowOpener.java b/server/src/com/vaadin/server/BrowserWindowOpener.java
index 8e049ca454..a6e420f89c 100644
--- a/server/src/com/vaadin/server/BrowserWindowOpener.java
+++ b/server/src/com/vaadin/server/BrowserWindowOpener.java
@@ -38,7 +38,8 @@ public class BrowserWindowOpener extends AbstractExtension {
private final String path;
private final Class<? extends UI> uiClass;
- public BrowserWindowOpenerUIProvider(Class<? extends UI> uiClass, String path) {
+ public BrowserWindowOpenerUIProvider(Class<? extends UI> uiClass,
+ String path) {
this.path = ensureInitialSlash(path);
this.uiClass = uiClass;
}
diff --git a/server/src/com/vaadin/server/ClientConnector.java b/server/src/com/vaadin/server/ClientConnector.java
index 5e95b18281..3b52fbc730 100644
--- a/server/src/com/vaadin/server/ClientConnector.java
+++ b/server/src/com/vaadin/server/ClientConnector.java
@@ -300,7 +300,7 @@ public interface ClientConnector extends Connector {
/**
* Called by the framework to encode the state to a JSONObject. This is
* typically done by calling the static method
- * {@link AbstractCommunicationManager#encodeState(ClientConnector, SharedState)}
+ * {@link LegacyCommunicationManager#encodeState(ClientConnector, SharedState)}
* .
*
* @return a JSON object with the encoded connector state
@@ -318,8 +318,12 @@ public interface ClientConnector extends Connector {
* routed to this method with the remaining part of the requested path
* available in the path parameter.
* <p>
- * {@link DynamicConnectorResource} can be used to easily make an
- * appropriate URL available to the client-side code.
+ * NOTE that the session is not locked when this method is called. It is the
+ * responsibility of the connector to ensure that the session is locked
+ * while handling state or other session related data. For best performance
+ * the session should be unlocked before writing a large response to the
+ * client.
+ * </p>
*
* @param request
* the request that should be handled
diff --git a/server/src/com/vaadin/server/CommunicationManager.java b/server/src/com/vaadin/server/CommunicationManager.java
deleted file mode 100644
index 8b3550481d..0000000000
--- a/server/src/com/vaadin/server/CommunicationManager.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright 2000-2013 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.server;
-
-import java.io.InputStream;
-
-import javax.servlet.ServletContext;
-
-import com.vaadin.ui.UI;
-
-/**
- * Application manager processes changes and paints for single application
- * instance.
- *
- * This class handles applications running as servlets.
- *
- * @see AbstractCommunicationManager
- *
- * @author Vaadin Ltd.
- * @since 5.0
- *
- * @deprecated As of 7.0. Will likely change or be removed in a future version
- */
-@Deprecated
-@SuppressWarnings("serial")
-public class CommunicationManager extends AbstractCommunicationManager {
-
- /**
- * TODO New constructor - document me!
- *
- * @param session
- */
- public CommunicationManager(VaadinSession session) {
- super(session);
- }
-
- @Override
- protected BootstrapHandler createBootstrapHandler() {
- return new BootstrapHandler() {
- @Override
- protected String getServiceUrl(BootstrapContext context) {
- String pathInfo = context.getRequest().getPathInfo();
- if (pathInfo == null) {
- return null;
- } else {
- /*
- * Make a relative URL to the servlet by adding one ../ for
- * each path segment in pathInfo (i.e. the part of the
- * requested path that comes after the servlet mapping)
- */
- return VaadinServletService
- .getCancelingRelativePath(pathInfo);
- }
- }
-
- @Override
- public String getThemeName(BootstrapContext context) {
- String themeName = context.getRequest().getParameter(
- VaadinServlet.URL_PARAMETER_THEME);
- if (themeName == null) {
- themeName = super.getThemeName(context);
- }
- return themeName;
- }
- };
- }
-
- @Override
- protected InputStream getThemeResourceAsStream(UI uI, String themeName,
- String resource) {
- VaadinServletService service = (VaadinServletService) uI.getSession()
- .getService();
- ServletContext servletContext = service.getServlet()
- .getServletContext();
- return servletContext.getResourceAsStream("/"
- + VaadinServlet.THEME_DIR_PATH + '/' + themeName + "/"
- + resource);
- }
-}
diff --git a/server/src/com/vaadin/server/ComponentSizeValidator.java b/server/src/com/vaadin/server/ComponentSizeValidator.java
index f5e2e2fe12..27d087a2b2 100644
--- a/server/src/com/vaadin/server/ComponentSizeValidator.java
+++ b/server/src/com/vaadin/server/ComponentSizeValidator.java
@@ -191,7 +191,6 @@ public class ComponentSizeValidator implements Serializable {
}
public void reportErrors(PrintWriter clientJSON,
- AbstractCommunicationManager communicationManager,
PrintStream serverErrorStream) {
clientJSON.write("{");
@@ -269,8 +268,7 @@ public class ComponentSizeValidator implements Serializable {
} else {
first = false;
}
- subError.reportErrors(clientJSON, communicationManager,
- serverErrorStream);
+ subError.reportErrors(clientJSON, serverErrorStream);
}
clientJSON.write("]");
serverErrorStream.println("<< Sub erros");
diff --git a/server/src/com/vaadin/server/ConnectorResource.java b/server/src/com/vaadin/server/ConnectorResource.java
index 8f8591e6b1..8682f8ce6f 100644
--- a/server/src/com/vaadin/server/ConnectorResource.java
+++ b/server/src/com/vaadin/server/ConnectorResource.java
@@ -30,6 +30,15 @@ public interface ConnectorResource extends Resource {
/**
* Gets resource as stream.
+ * <p>
+ * Note that this method is called while the session is locked to prevent
+ * race conditions but the methods in the returned {@link DownloadStream}
+ * are assumed to be unrelated to the VaadinSession and are called without
+ * holding session locks (to prevent locking the session during long file
+ * downloads).
+ * </p>
+ *
+ * @return A download stream which produces the resource content
*/
public DownloadStream getStream();
diff --git a/server/src/com/vaadin/server/ConnectorResourceHandler.java b/server/src/com/vaadin/server/ConnectorResourceHandler.java
index 03a2fcc115..00d82988d3 100644
--- a/server/src/com/vaadin/server/ConnectorResourceHandler.java
+++ b/server/src/com/vaadin/server/ConnectorResourceHandler.java
@@ -16,6 +16,7 @@
package com.vaadin.server;
import java.io.IOException;
+import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
@@ -25,6 +26,7 @@ import javax.servlet.http.HttpServletResponse;
import com.vaadin.shared.ApplicationConstants;
import com.vaadin.ui.UI;
+import com.vaadin.util.CurrentInstance;
public class ConnectorResourceHandler implements RequestHandler {
// APP/connector/[uiid]/[cid]/[filename.xyz]
@@ -46,28 +48,38 @@ public class ConnectorResourceHandler implements RequestHandler {
return false;
}
Matcher matcher = CONNECTOR_RESOURCE_PATTERN.matcher(requestPath);
- if (matcher.matches()) {
- String uiId = matcher.group(1);
- String cid = matcher.group(2);
- String key = matcher.group(3);
- UI ui = session.getUIById(Integer.parseInt(uiId));
+ if (!matcher.matches()) {
+ return false;
+ }
+ String uiId = matcher.group(1);
+ String cid = matcher.group(2);
+ String key = matcher.group(3);
+
+ session.lock();
+ UI ui;
+ ClientConnector connector;
+ try {
+ ui = session.getUIById(Integer.parseInt(uiId));
if (ui == null) {
return error(request, response,
"Ignoring connector request for no-existent root "
+ uiId);
}
- UI.setCurrent(ui);
- VaadinSession.setCurrent(ui.getSession());
-
- ClientConnector connector = ui.getConnectorTracker().getConnector(
- cid);
+ connector = ui.getConnectorTracker().getConnector(cid);
if (connector == null) {
return error(request, response,
"Ignoring connector request for no-existent connector "
+ cid + " in root " + uiId);
}
+ } finally {
+ session.unlock();
+ }
+
+ Map<Class<?>, CurrentInstance> oldThreadLocals = CurrentInstance
+ .setThreadLocals(ui);
+ try {
if (!connector.handleConnectorRequest(request, response, key)) {
return error(request, response, connector.getClass()
.getSimpleName()
@@ -75,20 +87,11 @@ public class ConnectorResourceHandler implements RequestHandler {
+ connector.getConnectorId()
+ ") did not handle connector request for " + key);
}
-
- return true;
- } else if (requestPath.matches('/' + ApplicationConstants.APP_PATH
- + "(/.*)?")) {
- /*
- * This should be the last request handler before we get to
- * bootstrap logic. Prevent /APP requests from reaching bootstrap
- * handlers to help protect the /APP name space for framework usage.
- */
- return error(request, response,
- "Returning 404 for /APP request not yet handled.");
- } else {
- return false;
+ } finally {
+ CurrentInstance.restoreThreadLocals(oldThreadLocals);
}
+
+ return true;
}
private static boolean error(VaadinRequest request,
diff --git a/server/src/com/vaadin/server/Constants.java b/server/src/com/vaadin/server/Constants.java
index a9bc3e5b9e..f8d8105286 100644
--- a/server/src/com/vaadin/server/Constants.java
+++ b/server/src/com/vaadin/server/Constants.java
@@ -15,6 +15,8 @@
*/
package com.vaadin.server;
+import com.vaadin.shared.communication.PushMode;
+
/**
* TODO Document me!
*
@@ -47,6 +49,13 @@ public interface Constants {
+ "in web.xml. The default of 5min will be used.\n"
+ "===========================================================";
+ static final String WARNING_PUSH_MODE_NOT_RECOGNIZED = "\n"
+ + "===========================================================\n"
+ + "WARNING: pushMode has been set to an unrecognized value\n"
+ + "in web.xml. The permitted values are \"disabled\", \"manual\",\n"
+ + "and \"automatic\". The default of \"disabled\" will be used.\n"
+ + "===========================================================";
+
static final String WIDGETSET_MISMATCH_INFO = "\n"
+ "=================================================================\n"
+ "The widgetset in use does not seem to be built for the Vaadin\n"
@@ -56,6 +65,53 @@ public interface Constants {
+ " Widgetset version: %s\n"
+ "=================================================================";
+ static final String REQUIRED_ATMOSPHERE_VERSION = "1.0.12";
+
+ static final String INVALID_ATMOSPHERE_VERSION_WARNING = "\n"
+ + "=================================================================\n"
+ + "Vaadin depends on Atomsphere {0} but version {1} was found.\n"
+ + "This might cause compatibility problems if push is used.\n"
+ + "=================================================================";
+
+ static final String ATMOSPHERE_MISSING_ERROR = "\n"
+ + "=================================================================\n"
+ + "Atmosphere could not be loaded. When using push with Vaadin, the\n"
+ + "Atmosphere framework must be present on the classpath.\n"
+ + "If using a dependency management system, please add a dependency\n"
+ + "to vaadin-push.\n"
+ + "If managing dependencies manually, please make sure Atmosphere\n"
+ + REQUIRED_ATMOSPHERE_VERSION
+ + " is included on the classpath.\n"
+ + "Will fall back to using "
+ + PushMode.class.getSimpleName()
+ + "."
+ + PushMode.DISABLED.name()
+ + ".\n"
+ + "=================================================================";
+
+ static final String PUSH_NOT_SUPPORTED_ERROR = "\n"
+ + "=================================================================\n"
+ + "Push is not supported for {0}\n"
+ + "Will fall back to using "
+ + PushMode.class.getSimpleName()
+ + "."
+ + PushMode.DISABLED.name()
+ + ".\n"
+ + "=================================================================";
+
+ public static final String WARNING_LEGACY_PROPERTY_TOSTRING = "You are using toString() instead of getValue() to get the value for a Property of type {0}"
+ + ". This is strongly discouraged and only provided for backwards compatibility with Vaadin 6. "
+ + "To disable this warning message and retain the behavior, set the init parameter \""
+ + Constants.SERVLET_PARAMETER_LEGACY_PROPERTY_TOSTRING
+ + "\" to \"true\". To disable the legacy functionality, set \""
+ + Constants.SERVLET_PARAMETER_LEGACY_PROPERTY_TOSTRING
+ + "\" to false."
+ + " (Note that your debugger might call toString() and trigger this message).";
+
+ static final String WARNING_UNKNOWN_LEGACY_PROPERTY_TOSTRING_VALUE = "Unknown value '{0}' for parameter "
+ + Constants.SERVLET_PARAMETER_LEGACY_PROPERTY_TOSTRING
+ + ". Supported values are 'false','warning','true'";
+
static final String URL_PARAMETER_THEME = "theme";
static final String SERVLET_PARAMETER_PRODUCTION_MODE = "productionMode";
@@ -63,7 +119,9 @@ public interface Constants {
static final String SERVLET_PARAMETER_RESOURCE_CACHE_TIME = "resourceCacheTime";
static final String SERVLET_PARAMETER_HEARTBEAT_INTERVAL = "heartbeatInterval";
static final String SERVLET_PARAMETER_CLOSE_IDLE_SESSIONS = "closeIdleSessions";
+ static final String SERVLET_PARAMETER_PUSH_MODE = "pushMode";
static final String SERVLET_PARAMETER_UI_PROVIDER = "UIProvider";
+ static final String SERVLET_PARAMETER_LEGACY_PROPERTY_TOSTRING = "legacyPropertyToString";
// Configurable parameter names
static final String PARAMETER_VAADIN_RESOURCES = "Resources";
diff --git a/server/src/com/vaadin/server/DefaultDeploymentConfiguration.java b/server/src/com/vaadin/server/DefaultDeploymentConfiguration.java
index 5b0c3fe8d1..80c3644d77 100644
--- a/server/src/com/vaadin/server/DefaultDeploymentConfiguration.java
+++ b/server/src/com/vaadin/server/DefaultDeploymentConfiguration.java
@@ -17,8 +17,11 @@
package com.vaadin.server;
import java.util.Properties;
+import java.util.logging.Level;
import java.util.logging.Logger;
+import com.vaadin.shared.communication.PushMode;
+
/**
* The default implementation of {@link DeploymentConfiguration} based on a base
* class for resolving system properties and a set of init parameters.
@@ -33,7 +36,9 @@ public class DefaultDeploymentConfiguration implements DeploymentConfiguration {
private int resourceCacheTime;
private int heartbeatInterval;
private boolean closeIdleSessions;
+ private PushMode pushMode;
private final Class<?> systemPropertyBaseClass;
+ private LegacyProperyToStringMode legacyPropertyToStringMode;
/**
* Create a new deployment configuration instance.
@@ -55,6 +60,27 @@ public class DefaultDeploymentConfiguration implements DeploymentConfiguration {
checkResourceCacheTime();
checkHeartbeatInterval();
checkCloseIdleSessions();
+ checkPushMode();
+ checkLegacyPropertyToString();
+ }
+
+ private void checkLegacyPropertyToString() {
+ String param = getApplicationOrSystemProperty(
+ Constants.SERVLET_PARAMETER_LEGACY_PROPERTY_TOSTRING, "warning");
+ if ("true".equals(param)) {
+ legacyPropertyToStringMode = LegacyProperyToStringMode.ENABLED;
+ } else if ("false".equals(param)) {
+ legacyPropertyToStringMode = LegacyProperyToStringMode.DISABLED;
+ } else {
+ if (!"warning".equals(param)) {
+ getLogger()
+ .log(Level.WARNING,
+ Constants.WARNING_UNKNOWN_LEGACY_PROPERTY_TOSTRING_VALUE,
+ param);
+ }
+ legacyPropertyToStringMode = LegacyProperyToStringMode.WARNING;
+
+ }
}
@Override
@@ -167,12 +193,32 @@ public class DefaultDeploymentConfiguration implements DeploymentConfiguration {
return heartbeatInterval;
}
+ /**
+ * {@inheritDoc}
+ * <p>
+ * The default value is false.
+ */
@Override
public boolean isCloseIdleSessions() {
return closeIdleSessions;
}
/**
+ * {@inheritDoc}
+ * <p>
+ * The default mode is {@link PushMode#DISABLED}.
+ */
+ @Override
+ public PushMode getPushMode() {
+ return pushMode;
+ }
+
+ @Override
+ public Properties getInitParameters() {
+ return initParameters;
+ }
+
+ /**
* Log a warning if Vaadin is not running in production mode.
*/
private void checkProductionMode() {
@@ -231,13 +277,26 @@ public class DefaultDeploymentConfiguration implements DeploymentConfiguration {
.equals("true");
}
+ private void checkPushMode() {
+ String mode = getApplicationOrSystemProperty(
+ Constants.SERVLET_PARAMETER_PUSH_MODE,
+ PushMode.DISABLED.toString());
+ try {
+ pushMode = Enum.valueOf(PushMode.class, mode.toUpperCase());
+ } catch (IllegalArgumentException e) {
+ getLogger().warning(Constants.WARNING_PUSH_MODE_NOT_RECOGNIZED);
+ pushMode = PushMode.DISABLED;
+ }
+ }
+
private Logger getLogger() {
return Logger.getLogger(getClass().getName());
}
@Override
- public Properties getInitParameters() {
- return initParameters;
+ @Deprecated
+ public LegacyProperyToStringMode getLegacyPropertyToStringMode() {
+ return legacyPropertyToStringMode;
}
}
diff --git a/server/src/com/vaadin/server/DeploymentConfiguration.java b/server/src/com/vaadin/server/DeploymentConfiguration.java
index bd4bc928f4..bf9c019b6d 100644
--- a/server/src/com/vaadin/server/DeploymentConfiguration.java
+++ b/server/src/com/vaadin/server/DeploymentConfiguration.java
@@ -19,6 +19,9 @@ package com.vaadin.server;
import java.io.Serializable;
import java.util.Properties;
+import com.vaadin.data.util.AbstractProperty;
+import com.vaadin.shared.communication.PushMode;
+
/**
* A collection of properties configured at deploy time as well as a way of
* accessing third party properties not explicitly supported by this class.
@@ -28,6 +31,23 @@ import java.util.Properties;
* @since 7.0.0
*/
public interface DeploymentConfiguration extends Serializable {
+
+ /**
+ * Determines the mode of the "legacyPropertyToString" parameter.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+ @Deprecated
+ public enum LegacyProperyToStringMode {
+ DISABLED, WARNING, ENABLED;
+
+ public boolean useLegacyMode() {
+ return this == WARNING || this == ENABLED;
+ }
+
+ }
+
/**
* Returns whether Vaadin is in production mode.
*
@@ -78,6 +98,14 @@ public interface DeploymentConfiguration extends Serializable {
public boolean isCloseIdleSessions();
/**
+ * Returns the mode of bidirectional ("push") client-server communication
+ * that should be used.
+ *
+ * @return The push mode in use.
+ */
+ public PushMode getPushMode();
+
+ /**
* Gets the properties configured for the deployment, e.g. as init
* parameters to the servlet or portlet.
*
@@ -101,4 +129,13 @@ public interface DeploymentConfiguration extends Serializable {
public String getApplicationOrSystemProperty(String propertyName,
String defaultValue);
+ /**
+ * Returns to legacy Property.toString() mode used. See
+ * {@link AbstractProperty#isLegacyToStringEnabled()} for more information.
+ *
+ * @return The Property.toString() mode in use.
+ */
+ @Deprecated
+ public LegacyProperyToStringMode getLegacyPropertyToStringMode();
+
}
diff --git a/server/src/com/vaadin/server/DownloadStream.java b/server/src/com/vaadin/server/DownloadStream.java
index e2f9fc5296..4e66831f1d 100644
--- a/server/src/com/vaadin/server/DownloadStream.java
+++ b/server/src/com/vaadin/server/DownloadStream.java
@@ -28,6 +28,11 @@ import javax.servlet.http.HttpServletResponse;
/**
* Downloadable stream.
+ * <p>
+ * Note that the methods in a DownloadStream are called without locking the
+ * session to prevent locking the session during long file downloads. If your
+ * DownloadStream uses anything from the session, you must handle the locking.
+ * </p>
*
* @author Vaadin Ltd.
* @since 3.0
diff --git a/server/src/com/vaadin/server/DragAndDropService.java b/server/src/com/vaadin/server/DragAndDropService.java
index 5a54b5ae3a..a83e83ef7f 100644
--- a/server/src/com/vaadin/server/DragAndDropService.java
+++ b/server/src/com/vaadin/server/DragAndDropService.java
@@ -16,7 +16,7 @@
package com.vaadin.server;
import java.io.IOException;
-import java.io.PrintWriter;
+import java.io.Writer;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -50,13 +50,13 @@ public class DragAndDropService implements VariableOwner, ClientConnector {
private DragAndDropEvent dragEvent;
- private final AbstractCommunicationManager manager;
+ private final LegacyCommunicationManager manager;
private AcceptCriterion acceptCriterion;
private ErrorHandler errorHandler;
- public DragAndDropService(AbstractCommunicationManager manager) {
+ public DragAndDropService(LegacyCommunicationManager manager) {
this.manager = manager;
}
@@ -209,10 +209,10 @@ public class DragAndDropService implements VariableOwner, ClientConnector {
return true;
}
- void printJSONResponse(PrintWriter outWriter) throws PaintException {
+ public void printJSONResponse(Writer outWriter) throws IOException {
if (isDirty()) {
- outWriter.print(", \"dd\":");
+ outWriter.write(", \"dd\":");
JsonPaintTarget jsonPaintTarget = new JsonPaintTarget(manager,
outWriter, false);
diff --git a/server/src/com/vaadin/server/FileDownloader.java b/server/src/com/vaadin/server/FileDownloader.java
index 7cc1fd7cc8..9b49ad8edd 100644
--- a/server/src/com/vaadin/server/FileDownloader.java
+++ b/server/src/com/vaadin/server/FileDownloader.java
@@ -129,10 +129,15 @@ public class FileDownloader extends AbstractExtension {
// Ignore if it isn't for us
return false;
}
+ getSession().lock();
+ DownloadStream stream;
- Resource resource = getFileDownloadResource();
- if (resource instanceof ConnectorResource) {
- DownloadStream stream = ((ConnectorResource) resource).getStream();
+ try {
+ Resource resource = getFileDownloadResource();
+ if (!(resource instanceof ConnectorResource)) {
+ return false;
+ }
+ stream = ((ConnectorResource) resource).getStream();
if (stream.getParameter("Content-Disposition") == null) {
// Content-Disposition: attachment generally forces download
@@ -140,15 +145,15 @@ public class FileDownloader extends AbstractExtension {
"attachment; filename=\"" + stream.getFileName() + "\"");
}
- // Content-Type to block eager browser plug-ins from hijacking the
- // file
+ // Content-Type to block eager browser plug-ins from hijacking
+ // the file
if (isOverrideContentType()) {
stream.setContentType("application/octet-stream;charset=UTF-8");
}
- stream.writeResponse(request, response);
- return true;
- } else {
- return false;
+ } finally {
+ getSession().unlock();
}
+ stream.writeResponse(request, response);
+ return true;
}
}
diff --git a/server/src/com/vaadin/server/GAEVaadinServlet.java b/server/src/com/vaadin/server/GAEVaadinServlet.java
index 0d2063d446..b4a83603b0 100644
--- a/server/src/com/vaadin/server/GAEVaadinServlet.java
+++ b/server/src/com/vaadin/server/GAEVaadinServlet.java
@@ -184,16 +184,14 @@ public class GAEVaadinServlet extends VaadinServlet {
return;
}
- RequestType requestType = getRequestType(request);
-
- if (requestType == RequestType.STATIC_FILE) {
+ if (isStaticResourceRequest(request)) {
// no locking needed, let superclass handle
super.service(request, response);
cleanSession(request);
return;
}
- if (requestType == RequestType.APP) {
+ if (ServletPortletHelper.isAppRequest(request)) {
// no locking needed, let superclass handle
getApplicationContext(request,
MemcacheServiceFactory.getMemcacheService());
@@ -205,7 +203,11 @@ public class GAEVaadinServlet extends VaadinServlet {
final HttpSession session = request.getSession(getService()
.requestCanCreateSession(request));
if (session == null) {
- handleServiceSessionExpired(request, response);
+ try {
+ getService().handleSessionExpired(request, response);
+ } catch (ServiceException e) {
+ throw new ServletException(e);
+ }
cleanSession(request);
return;
}
@@ -218,19 +220,21 @@ public class GAEVaadinServlet extends VaadinServlet {
// try to get lock
long started = new Date().getTime();
// non-UIDL requests will try indefinitely
- while (requestType != RequestType.UIDL
- || new Date().getTime() - started < MAX_UIDL_WAIT_MILLISECONDS) {
- locked = memcache.put(mutex, 1, Expiration.byDeltaSeconds(40),
- MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT);
- if (locked) {
- break;
- }
- try {
- Thread.sleep(RETRY_AFTER_MILLISECONDS);
- } catch (InterruptedException e) {
- getLogger().finer(
- "Thread.sleep() interrupted while waiting for lock. Trying again. "
- + e);
+ if (!ServletPortletHelper.isUIDLRequest(request)) {
+ while (new Date().getTime() - started < MAX_UIDL_WAIT_MILLISECONDS) {
+ locked = memcache.put(mutex, 1,
+ Expiration.byDeltaSeconds(40),
+ MemcacheService.SetPolicy.ADD_ONLY_IF_NOT_PRESENT);
+ if (locked) {
+ break;
+ }
+ try {
+ Thread.sleep(RETRY_AFTER_MILLISECONDS);
+ } catch (InterruptedException e) {
+ getLogger().finer(
+ "Thread.sleep() interrupted while waiting for lock. Trying again. "
+ + e);
+ }
}
}
diff --git a/server/src/com/vaadin/server/GlobalResourceHandler.java b/server/src/com/vaadin/server/GlobalResourceHandler.java
index 0fac14e20c..d411b286d0 100644
--- a/server/src/com/vaadin/server/GlobalResourceHandler.java
+++ b/server/src/com/vaadin/server/GlobalResourceHandler.java
@@ -31,6 +31,7 @@ import javax.servlet.http.HttpServletResponse;
import com.vaadin.shared.ApplicationConstants;
import com.vaadin.ui.LegacyComponent;
import com.vaadin.ui.UI;
+import com.vaadin.util.CurrentInstance;
/**
* A {@link RequestHandler} that takes care of {@link ConnectorResource}s that
@@ -85,30 +86,38 @@ public class GlobalResourceHandler implements RequestHandler {
return error(request, response, pathInfo
+ " is not a valid global resource path");
}
+ session.lock();
+ Map<Class<?>, CurrentInstance> oldThreadLocals = null;
+ DownloadStream stream = null;
+ try {
+ UI ui = session.getUIById(Integer.parseInt(uiid));
+ if (ui == null) {
+ return error(request, response, "No UI found for id " + uiid);
+ }
+ oldThreadLocals = CurrentInstance.setThreadLocals(ui);
+ ConnectorResource resource;
+ if (LEGACY_TYPE.equals(type)) {
+ resource = legacyResources.get(key);
+ } else {
+ return error(request, response, "Unknown global resource type "
+ + type + " in requested path " + pathInfo);
+ }
- UI ui = session.getUIById(Integer.parseInt(uiid));
- if (ui == null) {
- return error(request, response, "No UI found for id " + uiid);
- }
- UI.setCurrent(ui);
-
- ConnectorResource resource;
- if (LEGACY_TYPE.equals(type)) {
- resource = legacyResources.get(key);
- } else {
- return error(request, response, "Unknown global resource type "
- + type + " in requested path " + pathInfo);
- }
-
- if (resource == null) {
- return error(request, response, "Global resource " + key
- + " not found");
- }
+ if (resource == null) {
+ return error(request, response, "Global resource " + key
+ + " not found");
+ }
- DownloadStream stream = resource.getStream();
- if (stream == null) {
- return error(request, response, "Resource " + resource
- + " didn't produce any stream.");
+ stream = resource.getStream();
+ if (stream == null) {
+ return error(request, response, "Resource " + resource
+ + " didn't produce any stream.");
+ }
+ } finally {
+ session.unlock();
+ if (oldThreadLocals != null) {
+ CurrentInstance.restoreThreadLocals(oldThreadLocals);
+ }
}
stream.writeResponse(request, response);
diff --git a/server/src/com/vaadin/server/JsonCodec.java b/server/src/com/vaadin/server/JsonCodec.java
index 9a70efab28..d533ed99f3 100644
--- a/server/src/com/vaadin/server/JsonCodec.java
+++ b/server/src/com/vaadin/server/JsonCodec.java
@@ -667,7 +667,7 @@ public class JsonCodec implements Serializable {
} else if (value instanceof Connector) {
Connector connector = (Connector) value;
if (value instanceof Component
- && !(AbstractCommunicationManager
+ && !(LegacyCommunicationManager
.isComponentVisibleToClient((Component) value))) {
return encodeNull();
}
@@ -871,7 +871,7 @@ public class JsonCodec implements Serializable {
for (Entry<?, ?> entry : map.entrySet()) {
ClientConnector key = (ClientConnector) entry.getKey();
- if (AbstractCommunicationManager.isConnectorVisibleToClient(key)) {
+ if (LegacyCommunicationManager.isConnectorVisibleToClient(key)) {
EncodeResult encodedValue = encode(entry.getValue(), null,
valueType, connectorTracker);
jsonMap.put(key.getConnectorId(),
diff --git a/server/src/com/vaadin/server/JsonPaintTarget.java b/server/src/com/vaadin/server/JsonPaintTarget.java
index 11bfb33fe1..ca70391f64 100644
--- a/server/src/com/vaadin/server/JsonPaintTarget.java
+++ b/server/src/com/vaadin/server/JsonPaintTarget.java
@@ -18,6 +18,7 @@ package com.vaadin.server;
import java.io.PrintWriter;
import java.io.Serializable;
+import java.io.Writer;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
@@ -60,7 +61,7 @@ public class JsonPaintTarget implements PaintTarget {
private boolean closed = false;
- private final AbstractCommunicationManager manager;
+ private final LegacyCommunicationManager manager;
private int changes = 0;
@@ -86,14 +87,13 @@ public class JsonPaintTarget implements PaintTarget {
* @throws PaintException
* if the paint operation failed.
*/
- public JsonPaintTarget(AbstractCommunicationManager manager,
- PrintWriter outWriter, boolean cachingRequired)
- throws PaintException {
+ public JsonPaintTarget(LegacyCommunicationManager manager,
+ Writer outWriter, boolean cachingRequired) throws PaintException {
this.manager = manager;
// Sets the target for UIDL writing
- uidlBuffer = outWriter;
+ uidlBuffer = new PrintWriter(outWriter);
// Initialize tag-writing
mOpenTags = new Stack<String>();
@@ -1007,7 +1007,7 @@ public class JsonPaintTarget implements PaintTarget {
return manager.getTagForType(clientConnectorClass);
}
- Collection<Class<? extends ClientConnector>> getUsedClientConnectors() {
+ public Collection<Class<? extends ClientConnector>> getUsedClientConnectors() {
return usedClientConnectors;
}
diff --git a/server/src/com/vaadin/server/LegacyCommunicationManager.java b/server/src/com/vaadin/server/LegacyCommunicationManager.java
new file mode 100644
index 0000000000..c0194db243
--- /dev/null
+++ b/server/src/com/vaadin/server/LegacyCommunicationManager.java
@@ -0,0 +1,498 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Serializable;
+import java.io.Writer;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import com.vaadin.server.ClientConnector.ConnectorErrorEvent;
+import com.vaadin.server.communication.LocaleWriter;
+import com.vaadin.shared.ApplicationConstants;
+import com.vaadin.shared.JavaScriptConnectorState;
+import com.vaadin.shared.communication.SharedState;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.ConnectorTracker;
+import com.vaadin.ui.HasComponents;
+import com.vaadin.ui.SelectiveRenderer;
+import com.vaadin.ui.UI;
+
+/**
+ * This is a common base class for the server-side implementations of the
+ * communication system between the client code (compiled with GWT into
+ * JavaScript) and the server side components. Its client side counterpart is
+ * {@link com.vaadin.client.ApplicationConnection}.
+ * <p>
+ * TODO Document better!
+ *
+ * @deprecated As of 7.0. Will likely change or be removed in a future version
+ */
+@Deprecated
+@SuppressWarnings("serial")
+public class LegacyCommunicationManager implements Serializable {
+
+ // TODO Refactor (#11410)
+ private final HashMap<Integer, ClientCache> uiToClientCache = new HashMap<Integer, ClientCache>();
+
+ /**
+ * The session this communication manager is used for
+ */
+ private final VaadinSession session;
+
+ // TODO Refactor to UI shared state (#11378)
+ private List<String> locales;
+
+ // TODO Move to VaadinSession (#11409)
+ private DragAndDropService dragAndDropService;
+
+ // TODO Refactor (#11412)
+ private String requestThemeName;
+
+ // TODO Refactor (#11413)
+ private Map<String, Class<?>> publishedFileContexts = new HashMap<String, Class<?>>();
+
+ /**
+ * TODO New constructor - document me!
+ *
+ * @param session
+ */
+ public LegacyCommunicationManager(VaadinSession session) {
+ this.session = session;
+ requireLocale(session.getLocale().toString());
+ }
+
+ protected VaadinSession getSession() {
+ return session;
+ }
+
+ /**
+ * @deprecated As of 7.1. See #11411.
+ */
+ @Deprecated
+ public static JSONObject encodeState(ClientConnector connector,
+ SharedState state) throws JSONException {
+ UI uI = connector.getUI();
+ ConnectorTracker connectorTracker = uI.getConnectorTracker();
+ Class<? extends SharedState> stateType = connector.getStateType();
+ Object diffState = connectorTracker.getDiffState(connector);
+ boolean supportsDiffState = !JavaScriptConnectorState.class
+ .isAssignableFrom(stateType);
+ if (diffState == null && supportsDiffState) {
+ // Use an empty state object as reference for full
+ // repaints
+
+ try {
+ SharedState referenceState = stateType.newInstance();
+ EncodeResult encodeResult = JsonCodec.encode(referenceState,
+ null, stateType, uI.getConnectorTracker());
+ diffState = encodeResult.getEncodedValue();
+ } catch (Exception e) {
+ getLogger()
+ .log(Level.WARNING,
+ "Error creating reference object for state of type {0}",
+ stateType.getName());
+ }
+ }
+ EncodeResult encodeResult = JsonCodec.encode(state, diffState,
+ stateType, uI.getConnectorTracker());
+ if (supportsDiffState) {
+ connectorTracker.setDiffState(connector,
+ (JSONObject) encodeResult.getEncodedValue());
+ }
+ return (JSONObject) encodeResult.getDiff();
+ }
+
+ /**
+ * Resolves a dependency URI, registering the URI with this
+ * {@code LegacyCommunicationManager} if needed and returns a fully
+ * qualified URI.
+ *
+ * @deprecated As of 7.1. See #11413.
+ */
+ @Deprecated
+ public String registerDependency(String resourceUri, Class<?> context) {
+ try {
+ URI uri = new URI(resourceUri);
+ String protocol = uri.getScheme();
+
+ if (ApplicationConstants.PUBLISHED_PROTOCOL_NAME.equals(protocol)) {
+ // Strip initial slash
+ String resourceName = uri.getPath().substring(1);
+ return registerPublishedFile(resourceName, context);
+ }
+
+ if (protocol != null || uri.getHost() != null) {
+ return resourceUri;
+ }
+
+ // Bare path interpreted as published file
+ return registerPublishedFile(resourceUri, context);
+ } catch (URISyntaxException e) {
+ getLogger().log(Level.WARNING,
+ "Could not parse resource url " + resourceUri, e);
+ return resourceUri;
+ }
+ }
+
+ /**
+ * @deprecated As of 7.1. See #11413.
+ */
+ @Deprecated
+ public Map<String, Class<?>> getDependencies() {
+ return publishedFileContexts;
+ }
+
+ private String registerPublishedFile(String name, Class<?> context) {
+ // Add to map of names accepted by servePublishedFile
+ if (publishedFileContexts.containsKey(name)) {
+ Class<?> oldContext = publishedFileContexts.get(name);
+ if (oldContext != context) {
+ getLogger()
+ .log(Level.WARNING,
+ "{0} published by both {1} and {2}. File from {2} will be used.",
+ new Object[] { name, context, oldContext });
+ }
+ } else {
+ publishedFileContexts.put(name, context);
+ }
+
+ return ApplicationConstants.PUBLISHED_PROTOCOL_PREFIX + "/" + name;
+ }
+
+ /**
+ * @deprecated As of 7.1. See #11410.
+ */
+ @Deprecated
+ public ClientCache getClientCache(UI uI) {
+ Integer uiId = Integer.valueOf(uI.getUIId());
+ ClientCache cache = uiToClientCache.get(uiId);
+ if (cache == null) {
+ cache = new ClientCache();
+ uiToClientCache.put(uiId, cache);
+ }
+ return cache;
+ }
+
+ /**
+ * Checks if the connector is visible in context. For Components,
+ * {@link #isComponentVisibleToClient(Component)} is used. For other types
+ * of connectors, the contextual visibility of its first Component ancestor
+ * is used. If no Component ancestor is found, the connector is not visible.
+ *
+ * @deprecated As of 7.1. See #11411.
+ *
+ * @param connector
+ * The connector to check
+ * @return <code>true</code> if the connector is visible to the client,
+ * <code>false</code> otherwise
+ */
+ @Deprecated
+ public static boolean isConnectorVisibleToClient(ClientConnector connector) {
+ if (connector instanceof Component) {
+ return isComponentVisibleToClient((Component) connector);
+ } else {
+ ClientConnector parent = connector.getParent();
+ if (parent == null) {
+ return false;
+ } else {
+ return isConnectorVisibleToClient(parent);
+ }
+ }
+ }
+
+ /**
+ * Checks if the component should be visible to the client. Returns false if
+ * the child should not be sent to the client, true otherwise.
+ *
+ * @deprecated As of 7.1. See #11411.
+ *
+ * @param child
+ * The child to check
+ * @return true if the child is visible to the client, false otherwise
+ */
+ @Deprecated
+ public static boolean isComponentVisibleToClient(Component child) {
+ if (!child.isVisible()) {
+ return false;
+ }
+ HasComponents parent = child.getParent();
+
+ if (parent instanceof SelectiveRenderer) {
+ if (!((SelectiveRenderer) parent).isRendered(child)) {
+ return false;
+ }
+ }
+
+ if (parent != null) {
+ return isComponentVisibleToClient(parent);
+ } else {
+ if (child instanceof UI) {
+ // UI has no parent and visibility was checked above
+ return true;
+ } else {
+ // Component which is not attached to any UI
+ return false;
+ }
+ }
+ }
+
+ /**
+ * @deprecated As of 7.1. See #11412.
+ */
+ @Deprecated
+ public String getTheme(UI uI) {
+ String themeName = uI.getTheme();
+ String requestThemeName = getRequestTheme();
+
+ if (requestThemeName != null) {
+ themeName = requestThemeName;
+ }
+ if (themeName == null) {
+ themeName = VaadinServlet.getDefaultTheme();
+ }
+ return themeName;
+ }
+
+ private String getRequestTheme() {
+ return requestThemeName;
+ }
+
+ /**
+ * @deprecated As of 7.1. See #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;
+ }
+
+ /**
+ * Prints the queued (pending) locale definitions to a {@link PrintWriter}
+ * in a (UIDL) format that can be sent to the client and used there in
+ * formatting dates, times etc.
+ *
+ * @deprecated As of 7.1. See #11378.
+ *
+ * @param outWriter
+ */
+ @Deprecated
+ public void printLocaleDeclarations(Writer writer) throws IOException {
+ new LocaleWriter().write(locales, writer);
+ }
+
+ /**
+ * Queues a locale to be sent to the client (browser) for date and time
+ * entry etc. All locale specific information is derived from server-side
+ * {@link Locale} instances and sent to the client when needed, eliminating
+ * the need to use the {@link Locale} class and all the framework behind it
+ * on the client.
+ *
+ * @deprecated As of 7.1. See #11378.
+ *
+ * @see Locale#toString()
+ *
+ * @param value
+ */
+ @Deprecated
+ public void requireLocale(String value) {
+ if (locales == null) {
+ locales = new ArrayList<String>();
+ locales.add(session.getLocale().toString());
+ }
+ if (!locales.contains(value)) {
+ locales.add(value);
+ }
+ }
+
+ /**
+ * @deprecated As of 7.1. See #11378.
+ */
+ @Deprecated
+ public void resetLocales() {
+ locales = null;
+ }
+
+ /**
+ * @deprecated As of 7.1. Will be removed in the future.
+ */
+ @Deprecated
+ public static class InvalidUIDLSecurityKeyException extends
+ GeneralSecurityException {
+
+ public InvalidUIDLSecurityKeyException(String message) {
+ super(message);
+ }
+ }
+
+ 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.
+ */
+ @Deprecated
+ public String getTagForType(Class<? extends ClientConnector> class1) {
+ Integer id = typeToKey.get(class1);
+ if (id == null) {
+ id = nextTypeKey++;
+ typeToKey.put(class1, id);
+ if (getLogger().isLoggable(Level.FINE)) {
+ getLogger().log(Level.FINE, "Mapping {0} to {1}",
+ new Object[] { class1.getName(), id });
+ }
+ }
+ return id.toString();
+ }
+
+ /**
+ * Helper class for terminal to keep track of data that client is expected
+ * to know.
+ *
+ * TODO make customlayout templates (from theme) to be cached here.
+ *
+ * @deprecated As of 7.1. See #11410.
+ */
+ @Deprecated
+ public class ClientCache implements Serializable {
+
+ private final Set<Object> res = new HashSet<Object>();
+
+ /**
+ *
+ * @param paintable
+ * @return true if the given class was added to cache
+ */
+ public boolean cache(Object object) {
+ return res.add(object);
+ }
+
+ public void clear() {
+ res.clear();
+ }
+
+ }
+
+ /**
+ * @deprecated As of 7.1. See #11411.
+ */
+ @Deprecated
+ public String getStreamVariableTargetUrl(ClientConnector owner,
+ String name, StreamVariable value) {
+ /*
+ * We will use the same APP/* URI space as ApplicationResources but
+ * prefix url with UPLOAD
+ *
+ * eg. APP/UPLOAD/[UIID]/[PID]/[NAME]/[SECKEY]
+ *
+ * SECKEY is created on each paint to make URL's unpredictable (to
+ * prevent CSRF attacks).
+ *
+ * NAME and PID from URI forms a key to fetch StreamVariable when
+ * handling post
+ */
+ String paintableId = owner.getConnectorId();
+ UI ui = owner.getUI();
+ int uiId = ui.getUIId();
+ String key = uiId + "/" + paintableId + "/" + name;
+
+ ConnectorTracker connectorTracker = ui.getConnectorTracker();
+ connectorTracker.addStreamVariable(paintableId, name, value);
+ String seckey = connectorTracker.getSeckey(value);
+
+ return ApplicationConstants.APP_PROTOCOL_PREFIX
+ + ServletPortletHelper.UPLOAD_URL_PREFIX + key + "/" + seckey;
+
+ }
+
+ /**
+ * Handles an exception that occurred when processing RPC calls or a file
+ * upload.
+ *
+ * @deprecated As of 7.1. See #11411.
+ *
+ * @param ui
+ * The UI where the exception occured
+ * @param throwable
+ * The exception
+ * @param connector
+ * The Rpc target
+ */
+ @Deprecated
+ public void handleConnectorRelatedException(ClientConnector connector,
+ Throwable throwable) {
+ ErrorEvent errorEvent = new ConnectorErrorEvent(connector, throwable);
+ ErrorHandler handler = ErrorEvent.findErrorHandler(connector);
+ handler.error(errorEvent);
+ }
+
+ /**
+ * Requests that the given UI should be fully re-rendered on the client
+ * side.
+ *
+ * @since 7.1
+ * @deprecated. As of 7.1. Should be refactored once locales are fixed
+ * (#11378)
+ */
+ @Deprecated
+ public void repaintAll(UI ui) {
+ getClientCache(ui).clear();
+ ui.getConnectorTracker().markAllConnectorsDirty();
+ ui.getConnectorTracker().markAllClientSidesUninitialized();
+
+ // Reset sent locales
+ resetLocales();
+ requireLocale(session.getLocale().toString());
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(LegacyCommunicationManager.class.getName());
+ }
+}
diff --git a/server/src/com/vaadin/server/LegacyPaint.java b/server/src/com/vaadin/server/LegacyPaint.java
index 09477aaf3e..8d59dfd5ea 100644
--- a/server/src/com/vaadin/server/LegacyPaint.java
+++ b/server/src/com/vaadin/server/LegacyPaint.java
@@ -50,7 +50,7 @@ public class LegacyPaint implements Serializable {
public static void paint(Component component, PaintTarget target)
throws PaintException {
// Only paint content of visible components.
- if (!AbstractCommunicationManager.isComponentVisibleToClient(component)) {
+ if (!LegacyCommunicationManager.isComponentVisibleToClient(component)) {
return;
}
diff --git a/server/src/com/vaadin/server/Page.java b/server/src/com/vaadin/server/Page.java
index 8737b478c3..d4c16fe7f7 100644
--- a/server/src/com/vaadin/server/Page.java
+++ b/server/src/com/vaadin/server/Page.java
@@ -21,14 +21,18 @@ import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.EventObject;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import com.vaadin.event.EventRouter;
import com.vaadin.shared.ui.BorderStyle;
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.ui.JavaScript;
import com.vaadin.ui.LegacyWindow;
import com.vaadin.ui.Link;
@@ -218,7 +222,7 @@ public class Page implements Serializable {
}
}
- private static final Method BROWSWER_RESIZE_METHOD = ReflectTools
+ private static final Method BROWSER_RESIZE_METHOD = ReflectTools
.findMethod(BrowserWindowResizeListener.class,
"browserWindowResized", BrowserWindowResizeEvent.class);
@@ -303,6 +307,102 @@ public class Page implements Serializable {
}
}
+ /**
+ * Contains dynamically injected styles injected in the HTML document at
+ * runtime.
+ *
+ * @since 7.1
+ */
+ public static class Styles implements Serializable {
+
+ private final Map<Integer, String> stringInjections = new HashMap<Integer, String>();
+
+ private final Map<Integer, Resource> resourceInjections = new HashMap<Integer, Resource>();
+
+ // The combined injection counter between both string and resource
+ // injections. Used as the key for the injection maps
+ private int injectionCounter = 0;
+
+ // Points to the next injection that has not yet been made into the Page
+ private int nextInjectionPosition = 0;
+
+ private final UI ui;
+
+ private Styles(UI ui) {
+ this.ui = ui;
+ }
+
+ /**
+ * Injects a raw CSS string into the page.
+ *
+ * @param css
+ * The CSS to inject
+ */
+ public void add(String css) {
+ if (css == null) {
+ throw new IllegalArgumentException(
+ "Cannot inject null CSS string");
+ }
+
+ stringInjections.put(injectionCounter++, css);
+ ui.markAsDirty();
+ }
+
+ /**
+ * Injects a CSS resource into the page
+ *
+ * @param resource
+ * The resource to inject.
+ */
+ public void add(Resource resource) {
+ if (resource == null) {
+ throw new IllegalArgumentException(
+ "Cannot inject null resource");
+ }
+
+ resourceInjections.put(injectionCounter++, resource);
+ ui.markAsDirty();
+ }
+
+ private void paint(PaintTarget target) throws PaintException {
+
+ // If full repaint repaint all injections
+ if (target.isFullRepaint()) {
+ nextInjectionPosition = 0;
+ }
+
+ if (injectionCounter > nextInjectionPosition) {
+
+ target.startTag("css-injections");
+
+ while (injectionCounter > nextInjectionPosition) {
+
+ String stringInjection = stringInjections
+ .get(nextInjectionPosition);
+ if (stringInjection != null) {
+ target.startTag("css-string");
+ target.addAttribute("id", nextInjectionPosition);
+ target.addText(stringInjection);
+ target.endTag("css-string");
+ }
+
+ Resource resourceInjection = resourceInjections
+ .get(nextInjectionPosition);
+ if (resourceInjection != null) {
+ target.startTag("css-resource");
+ target.addAttribute("id", nextInjectionPosition);
+ target.addAttribute("url", resourceInjection);
+ target.endTag("css-resource");
+ }
+
+ nextInjectionPosition++;
+ }
+
+ target.endTag("css-injections");
+ }
+ }
+ }
+
private EventRouter eventRouter;
private final UI uI;
@@ -312,13 +412,18 @@ public class Page implements Serializable {
private JavaScript javaScript;
+ private Styles styles;
+
/**
* The current browser location.
*/
private URI location;
- public Page(UI uI) {
+ private final PageState state;
+
+ public Page(UI uI, PageState state) {
this.uI = uI;
+ this.state = state;
}
private void addListener(Class<?> eventType, Object target, Method method) {
@@ -504,20 +609,27 @@ public class Page implements Serializable {
}
/**
- * Adds a new {@link BrowserWindowResizeListener} to this uI. The listener
- * will be notified whenever the browser window within which this uI resides
+ * Adds a new {@link BrowserWindowResizeListener} to this UI. The listener
+ * will be notified whenever the browser window within which this UI resides
* is resized.
+ * <p>
+ * In most cases, the UI should be in lazy resize mode when using browser
+ * window resize listeners. Otherwise, a large number of events can be
+ * received while a resize is being performed. Use
+ * {@link UI#setResizeLazy(boolean)}.
+ * </p>
*
* @param resizeListener
* the listener to add
*
* @see BrowserWindowResizeListener#browserWindowResized(BrowserWindowResizeEvent)
- * @see #setResizeLazy(boolean)
+ * @see UI#setResizeLazy(boolean)
*/
public void addBrowserWindowResizeListener(
BrowserWindowResizeListener resizeListener) {
addListener(BrowserWindowResizeEvent.class, resizeListener,
- BROWSWER_RESIZE_METHOD);
+ BROWSER_RESIZE_METHOD);
+ getState(true).hasResizeListeners = true;
}
/**
@@ -539,7 +651,9 @@ public class Page implements Serializable {
public void removeBrowserWindowResizeListener(
BrowserWindowResizeListener resizeListener) {
removeListener(BrowserWindowResizeEvent.class, resizeListener,
- BROWSWER_RESIZE_METHOD);
+ BROWSER_RESIZE_METHOD);
+ getState(true).hasResizeListeners = eventRouter
+ .hasListeners(BrowserWindowResizeEvent.class);
}
/**
@@ -576,10 +690,23 @@ public class Page implements Serializable {
javaScript = new JavaScript();
javaScript.extend(uI);
}
-
return javaScript;
}
+ /**
+ * Returns that stylesheet associated with this Page. The stylesheet
+ * contains additional styles injected at runtime into the HTML document.
+ *
+ * @since 7.1
+ */
+ public Styles getStyles() {
+
+ if (styles == null) {
+ styles = new Styles(uI);
+ }
+ return styles;
+ }
+
public void paintContent(PaintTarget target) throws PaintException {
if (!openList.isEmpty()) {
for (final Iterator<OpenResource> i = openList.iterator(); i
@@ -637,6 +764,9 @@ public class Page implements Serializable {
location.toString());
}
+ if (styles != null) {
+ styles.paint(target);
+ }
}
/**
@@ -915,4 +1045,36 @@ public class Page implements Serializable {
uI.getRpcProxy(PageClientRpc.class).setTitle(title);
}
+ /**
+ * Reloads the page in the browser.
+ */
+ public void reload() {
+ uI.getRpcProxy(PageClientRpc.class).reload();
+ }
+
+ /**
+ * Returns the page state.
+ * <p>
+ * The page state is transmitted to UIConnector together with
+ * {@link UIState} rather than as an individual entity.
+ * </p>
+ * <p>
+ * The state should be considered an internal detail of Page. Classes
+ * outside of Page should not access it directly but only through public
+ * APIs provided by Page.
+ * </p>
+ *
+ * @since 7.1
+ * @param markAsDirty
+ * true to mark the state as dirty
+ * @return PageState object that can be read in any case and modified if
+ * markAsDirty is true
+ */
+ protected PageState getState(boolean markAsDirty) {
+ if (markAsDirty) {
+ uI.markAsDirty();
+ }
+ return state;
+ }
+
}
diff --git a/server/src/com/vaadin/server/PortletCommunicationManager.java b/server/src/com/vaadin/server/PortletCommunicationManager.java
deleted file mode 100644
index cece75847c..0000000000
--- a/server/src/com/vaadin/server/PortletCommunicationManager.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright 2000-2013 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.vaadin.server;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import javax.portlet.MimeResponse;
-import javax.portlet.PortletContext;
-import javax.portlet.PortletRequest;
-import javax.portlet.PortletResponse;
-import javax.portlet.RenderRequest;
-import javax.portlet.RenderResponse;
-import javax.portlet.ResourceURL;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import com.vaadin.shared.ApplicationConstants;
-import com.vaadin.ui.UI;
-
-/**
- * TODO document me!
- *
- * @author peholmst
- *
- *
- * @deprecated As of 7.0. Will likely change or be removed in a future version
- */
-@Deprecated
-@SuppressWarnings("serial")
-public class PortletCommunicationManager extends AbstractCommunicationManager {
-
- public PortletCommunicationManager(VaadinSession session) {
- super(session);
- }
-
- @Override
- protected BootstrapHandler createBootstrapHandler() {
- return new BootstrapHandler() {
- @Override
- public boolean handleRequest(VaadinSession session,
- VaadinRequest request, VaadinResponse response)
- throws IOException {
- PortletRequest portletRequest = ((VaadinPortletRequest) request)
- .getPortletRequest();
- if (portletRequest instanceof RenderRequest) {
- return super.handleRequest(session, request, response);
- } else {
- return false;
- }
- }
-
- @Override
- protected String getServiceUrl(BootstrapContext context) {
- ResourceURL portletResourceUrl = getRenderResponse(context)
- .createResourceURL();
- portletResourceUrl.setResourceID(VaadinPortlet.RESOURCE_URL_ID);
- return portletResourceUrl.toString();
- }
-
- private RenderResponse getRenderResponse(BootstrapContext context) {
- PortletResponse response = ((VaadinPortletResponse) context
- .getResponse()).getPortletResponse();
-
- RenderResponse renderResponse = (RenderResponse) response;
- return renderResponse;
- }
-
- @Override
- protected void appendMainScriptTagContents(
- BootstrapContext context, StringBuilder builder)
- throws JSONException, IOException {
- // fixed base theme to use - all portal pages with Vaadin
- // applications will load this exactly once
- String portalTheme = ((VaadinPortletRequest) context
- .getRequest())
- .getPortalProperty(VaadinPortlet.PORTAL_PARAMETER_VAADIN_THEME);
- if (portalTheme != null
- && !portalTheme.equals(context.getThemeName())) {
- String portalThemeUri = getThemeUri(context, portalTheme);
- // XSS safe - originates from portal properties
- builder.append("vaadin.loadTheme('" + portalThemeUri
- + "');");
- }
-
- super.appendMainScriptTagContents(context, builder);
- }
-
- @Override
- protected String getMainDivStyle(BootstrapContext context) {
- VaadinService vaadinService = context.getRequest().getService();
- return vaadinService.getDeploymentConfiguration()
- .getApplicationOrSystemProperty(
- VaadinPortlet.PORTLET_PARAMETER_STYLE, null);
- }
-
- @Override
- protected JSONObject getApplicationParameters(
- BootstrapContext context) throws JSONException,
- PaintException {
- JSONObject parameters = super.getApplicationParameters(context);
- VaadinPortletResponse response = (VaadinPortletResponse) context
- .getResponse();
- MimeResponse portletResponse = (MimeResponse) response
- .getPortletResponse();
- ResourceURL resourceURL = portletResponse.createResourceURL();
- resourceURL.setResourceID("v-browserDetails");
- parameters.put("browserDetailsUrl", resourceURL.toString());
-
- // Always send path info as a query parameter
- parameters.put(
- ApplicationConstants.SERVICE_URL_PATH_AS_PARAMETER,
- true);
-
- return parameters;
- }
-
- };
-
- }
-
- @Override
- protected InputStream getThemeResourceAsStream(UI uI, String themeName,
- String resource) {
- VaadinPortletSession session = (VaadinPortletSession) uI.getSession();
- PortletContext portletContext = session.getPortletSession()
- .getPortletContext();
- return portletContext.getResourceAsStream("/"
- + VaadinPortlet.THEME_DIR_PATH + '/' + themeName + "/"
- + resource);
- }
-
-}
diff --git a/server/src/com/vaadin/server/RequestHandler.java b/server/src/com/vaadin/server/RequestHandler.java
index 24107b744b..873752c5f2 100644
--- a/server/src/com/vaadin/server/RequestHandler.java
+++ b/server/src/com/vaadin/server/RequestHandler.java
@@ -19,17 +19,26 @@ package com.vaadin.server;
import java.io.IOException;
import java.io.Serializable;
+import com.vaadin.ui.UI;
+
/**
- * Handler for producing a response to non-UIDL requests. Handlers can be added
- * to service sessions using
- * {@link VaadinSession#addRequestHandler(RequestHandler)}
+ * Handler for producing a response to HTTP requests. Handlers can be either
+ * added on a {@link VaadinService service} level, common for all users, or on a
+ * {@link VaadinSession session} level for only a single user.
*/
public interface RequestHandler extends Serializable {
/**
- * Handles a non-UIDL request. If a response is written, this method should
- * return <code>true</code> to indicate that no more request handlers should
- * be invoked for the request.
+ * Called when a request needs to be handled. If a response is written, this
+ * method should return <code>true</code> to indicate that no more request
+ * handlers should be invoked for the request.
+ * <p>
+ * Note that request handlers by default do not lock the session. If you are
+ * using VaadinSession or anything inside the VaadinSession you must ensure
+ * the session is locked. This can be done by extending
+ * {@link SynchronizedRequestHandler} or by using
+ * {@link VaadinSession#access(Runnable)} or {@link UI#access(Runnable)}.
+ * </p>
*
* @param session
* The session for the request
@@ -40,6 +49,7 @@ public interface RequestHandler extends Serializable {
* @return true if a response has been written and no further request
* handlers should be called, otherwise false
* @throws IOException
+ * If an IO error occurred
*/
boolean handleRequest(VaadinSession session, VaadinRequest request,
VaadinResponse response) throws IOException;
diff --git a/server/src/com/vaadin/server/RequestTimer.java b/server/src/com/vaadin/server/RequestTimer.java
deleted file mode 100644
index 2f91348ce5..0000000000
--- a/server/src/com/vaadin/server/RequestTimer.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2000-2013 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.server;
-
-import java.io.Serializable;
-
-/**
- * Times the handling of requests and stores the information as an attribute in
- * the request. The timing info is later passed on to the client in the UIDL and
- * the client provides JavaScript API for accessing this data from e.g.
- * TestBench.
- *
- * @author Jonatan Kronqvist / Vaadin Ltd
- */
-public class RequestTimer implements Serializable {
- private long requestStartTime = 0;
-
- /**
- * Starts the timing of a request. This should be called before any
- * processing of the request.
- */
- public void start() {
- requestStartTime = System.nanoTime();
- }
-
- /**
- * Stops the timing of a request. This should be called when all processing
- * of a request has finished.
- *
- * @param context
- */
- public void stop(VaadinSession context) {
- // Measure and store the total handling time. This data can be
- // used in TestBench 3 tests.
- long time = (System.nanoTime() - requestStartTime) / 1000000;
-
- // The timings must be stored in the context, since a new
- // RequestTimer is created for every request.
- context.setLastRequestDuration(time);
- }
-}
diff --git a/server/src/com/vaadin/server/ServerRpcManager.java b/server/src/com/vaadin/server/ServerRpcManager.java
index ec25ce83ca..a1682cb453 100644
--- a/server/src/com/vaadin/server/ServerRpcManager.java
+++ b/server/src/com/vaadin/server/ServerRpcManager.java
@@ -139,7 +139,7 @@ public class ServerRpcManager<T extends ServerRpc> implements Serializable {
*
* @return RPC interface type
*/
- protected Class<T> getRpcInterface() {
+ public Class<T> getRpcInterface() {
return rpcInterface;
}
diff --git a/server/src/com/vaadin/server/ServletPortletHelper.java b/server/src/com/vaadin/server/ServletPortletHelper.java
index ce9872f40e..c14467a10e 100644
--- a/server/src/com/vaadin/server/ServletPortletHelper.java
+++ b/server/src/com/vaadin/server/ServletPortletHelper.java
@@ -23,23 +23,14 @@ import com.vaadin.shared.ApplicationConstants;
import com.vaadin.ui.Component;
import com.vaadin.ui.UI;
-/*
- * Copyright 2000-2013 Vaadin Ltd.
+/**
+ * Contains helper methods shared by {@link VaadinServlet} and
+ * {@link VaadinPortlet}.
*
- * 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.
+ * @deprecated As of 7.1. Will be removed or refactored in the future.
*/
-
-class ServletPortletHelper implements Serializable {
+@Deprecated
+public class ServletPortletHelper implements Serializable {
public static final String UPLOAD_URL_PREFIX = "APP/UPLOAD/";
/**
* The default SystemMessages (read-only).
@@ -132,6 +123,10 @@ class ServletPortletHelper implements Serializable {
return hasPathPrefix(request, ApplicationConstants.HEARTBEAT_PATH + '/');
}
+ public static boolean isPushRequest(VaadinRequest request) {
+ return hasPathPrefix(request, ApplicationConstants.PUSH_PATH + '/');
+ }
+
public static void initDefaultUIProvider(VaadinSession session,
VaadinService vaadinService) throws ServiceException {
String uiProperty = vaadinService.getDeploymentConfiguration()
@@ -200,7 +195,7 @@ class ServletPortletHelper implements Serializable {
* <li>{@link Locale#getDefault()}</li>
* </ol>
*/
- static Locale findLocale(Component component, VaadinSession session,
+ public static Locale findLocale(Component component, VaadinSession session,
VaadinRequest request) {
if (component == null) {
component = UI.getCurrent();
@@ -234,5 +229,4 @@ class ServletPortletHelper implements Serializable {
return Locale.getDefault();
}
-
}
diff --git a/server/src/com/vaadin/server/SessionExpiredHandler.java b/server/src/com/vaadin/server/SessionExpiredHandler.java
new file mode 100644
index 0000000000..6a7896f3d1
--- /dev/null
+++ b/server/src/com/vaadin/server/SessionExpiredHandler.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.server;
+
+import java.io.IOException;
+
+/**
+ * A specialized RequestHandler which is capable of sending session expiration
+ * messages to the user.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd
+ */
+public interface SessionExpiredHandler extends RequestHandler {
+
+ /**
+ * Called when the a session expiration has occured and a notification needs
+ * to be sent to the user. If a response is written, this method should
+ * return <code>true</code> to indicate that no more
+ * {@link SessionExpiredHandler} handlers should be invoked for the request.
+ *
+ * @param request
+ * The request to handle
+ * @param response
+ * The response object to which a response can be written.
+ * @return true if a response has been written and no further request
+ * handlers should be called, otherwise false
+ * @throws IOException
+ * If an IO error occurred
+ * @since 7.1
+ */
+ boolean handleSessionExpired(VaadinRequest request, VaadinResponse response)
+ throws IOException;
+
+}
diff --git a/server/src/com/vaadin/server/SynchronizedRequestHandler.java b/server/src/com/vaadin/server/SynchronizedRequestHandler.java
new file mode 100644
index 0000000000..ac730dcecb
--- /dev/null
+++ b/server/src/com/vaadin/server/SynchronizedRequestHandler.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.server;
+
+import java.io.IOException;
+
+/**
+ * RequestHandler which takes care of locking and unlocking of the VaadinSession
+ * automatically. The session is locked before
+ * {@link #synchronizedHandleRequest(VaadinSession, VaadinRequest, VaadinResponse)}
+ * is called and unlocked after it has completed.
+ *
+ * @author Vaadin Ltd
+ * @version @VERSION@
+ * @since 7.1
+ */
+public abstract class SynchronizedRequestHandler implements RequestHandler {
+
+ @Override
+ public boolean handleRequest(VaadinSession session, VaadinRequest request,
+ VaadinResponse response) throws IOException {
+ session.lock();
+ try {
+ return synchronizedHandleRequest(session, request, response);
+ } finally {
+ session.unlock();
+ }
+ }
+
+ /**
+ * Identical to
+ * {@link #handleRequest(VaadinSession, VaadinRequest, VaadinResponse)}
+ * except the {@link VaadinSession} is locked before this is called and
+ * unlocked after this has completed.
+ *
+ * @see #handleRequest(VaadinSession, VaadinRequest, VaadinResponse)
+ * @param session
+ * The session for the request
+ * @param request
+ * The request to handle
+ * @param response
+ * The response object to which a response can be written.
+ * @return true if a response has been written and no further request
+ * handlers should be called, otherwise false
+ *
+ * @throws IOException
+ * If an IO error occurred
+ */
+ public abstract boolean synchronizedHandleRequest(VaadinSession session,
+ VaadinRequest request, VaadinResponse response) throws IOException;
+
+}
diff --git a/server/src/com/vaadin/server/UIProvider.java b/server/src/com/vaadin/server/UIProvider.java
index a91db6b88d..0305b907e6 100644
--- a/server/src/com/vaadin/server/UIProvider.java
+++ b/server/src/com/vaadin/server/UIProvider.java
@@ -20,9 +20,11 @@ import java.io.Serializable;
import java.lang.annotation.Annotation;
import com.vaadin.annotations.PreserveOnRefresh;
+import com.vaadin.annotations.Push;
import com.vaadin.annotations.Theme;
import com.vaadin.annotations.Title;
import com.vaadin.annotations.Widgetset;
+import com.vaadin.shared.communication.PushMode;
import com.vaadin.ui.UI;
public abstract class UIProvider implements Serializable {
@@ -149,4 +151,27 @@ public abstract class UIProvider implements Serializable {
return titleAnnotation.value();
}
}
+
+ /**
+ * Finds the {@link PushMode} to use for a specific UI. If no specific push
+ * mode is required, <code>null</code> is returned.
+ * <p>
+ * The default implementation uses the @{@link Push} annotation if it's
+ * defined for the UI class.
+ *
+ * @param event
+ * the UI create event with information about the UI and the
+ * current request.
+ * @return the push mode to use, or <code>null</code> if the default push
+ * mode should be used
+ *
+ */
+ public PushMode getPushMode(UICreateEvent event) {
+ Push push = getAnnotationFor(event.getUIClass(), Push.class);
+ if (push == null) {
+ return null;
+ } else {
+ return push.value();
+ }
+ }
}
diff --git a/server/src/com/vaadin/server/UnsupportedBrowserHandler.java b/server/src/com/vaadin/server/UnsupportedBrowserHandler.java
index 55d5a5c78f..5fc00408a9 100644
--- a/server/src/com/vaadin/server/UnsupportedBrowserHandler.java
+++ b/server/src/com/vaadin/server/UnsupportedBrowserHandler.java
@@ -24,18 +24,18 @@ import java.io.Writer;
*
* <p>
* This handler is usually added to the application by
- * {@link AbstractCommunicationManager}.
+ * {@link LegacyCommunicationManager}.
* </p>
*/
@SuppressWarnings("serial")
-public class UnsupportedBrowserHandler implements RequestHandler {
+public class UnsupportedBrowserHandler extends SynchronizedRequestHandler {
/** Cookie used to ignore browser checks */
public static final String FORCE_LOAD_COOKIE = "vaadinforceload=1";
@Override
- public boolean handleRequest(VaadinSession session, VaadinRequest request,
- VaadinResponse response) throws IOException {
+ public boolean synchronizedHandleRequest(VaadinSession session,
+ VaadinRequest request, VaadinResponse response) throws IOException {
// Check if the browser is supported
// If Chrome Frame is available we'll assume it's ok
diff --git a/server/src/com/vaadin/server/VaadinPortlet.java b/server/src/com/vaadin/server/VaadinPortlet.java
index ac4e904898..327ce78a6c 100644
--- a/server/src/com/vaadin/server/VaadinPortlet.java
+++ b/server/src/com/vaadin/server/VaadinPortlet.java
@@ -15,16 +15,10 @@
*/
package com.vaadin.server;
-import java.io.BufferedWriter;
import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
-import java.net.MalformedURLException;
-import java.security.GeneralSecurityException;
import java.util.Enumeration;
import java.util.Map;
import java.util.Properties;
@@ -46,12 +40,11 @@ import javax.portlet.ResourceRequest;
import javax.portlet.ResourceResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
-import javax.servlet.http.HttpServletResponse;
import com.liferay.portal.kernel.util.PortalClassLoaderUtil;
import com.liferay.portal.kernel.util.PropsUtil;
-import com.vaadin.server.AbstractCommunicationManager.Callback;
-import com.vaadin.ui.UI;
+import com.vaadin.server.communication.PortletDummyRequestHandler;
+import com.vaadin.server.communication.PortletUIInitHandler;
import com.vaadin.util.CurrentInstance;
/**
@@ -257,24 +250,6 @@ public class VaadinPortlet extends GenericPortlet implements Constants,
}
- public static class AbstractApplicationPortletWrapper implements Callback {
-
- private final VaadinPortlet portlet;
-
- public AbstractApplicationPortletWrapper(VaadinPortlet portlet) {
- this.portlet = portlet;
- }
-
- @Override
- public void criticalNotification(VaadinRequest request,
- VaadinResponse response, String cap, String msg,
- String details, String outOfSyncURL) throws IOException {
- portlet.criticalNotification((VaadinPortletRequest) request,
- (VaadinPortletResponse) response, cap, msg, details,
- outOfSyncURL);
- }
- }
-
/**
* 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.
@@ -332,11 +307,16 @@ public class VaadinPortlet extends GenericPortlet implements Constants,
}
DeploymentConfiguration deploymentConfiguration = createDeploymentConfiguration(initParameters);
- vaadinService = createPortletService(deploymentConfiguration);
+ try {
+ vaadinService = createPortletService(deploymentConfiguration);
+ } catch (ServiceException e) {
+ throw new PortletException("Could not initialized VaadinPortlet", e);
+ }
// Sets current service even though there are no request and response
vaadinService.setCurrentInstances(null, null);
portletInitialized();
+
CurrentInstance.clearAll();
}
@@ -350,15 +330,21 @@ public class VaadinPortlet extends GenericPortlet implements Constants,
}
protected VaadinPortletService createPortletService(
- DeploymentConfiguration deploymentConfiguration) {
- return new VaadinPortletService(this, deploymentConfiguration);
+ DeploymentConfiguration deploymentConfiguration)
+ throws ServiceException {
+ VaadinPortletService service = new VaadinPortletService(this,
+ deploymentConfiguration);
+ service.init();
+ return service;
}
/**
* @author Vaadin Ltd
*
- * @deprecated As of 7.0. Will likely change or be removed in a future
- * version
+ * @deprecated As of 7.0. This is no longer used and only provided for
+ * backwards compatibility. Each {@link RequestHandler} can
+ * individually decide whether it wants to handle a request or
+ * not.
*/
@Deprecated
protected enum RequestType {
@@ -369,8 +355,10 @@ public class VaadinPortlet extends GenericPortlet implements Constants,
* @param vaadinRequest
* @return
*
- * @deprecated As of 7.0. Will likely change or be removed in a future
- * version
+ * @deprecated As of 7.0. This is no longer used and only provided for
+ * backwards compatibility. Each {@link RequestHandler} can
+ * individually decide whether it wants to handle a request or
+ * not.
*/
@Deprecated
protected RequestType getRequestType(VaadinPortletRequest vaadinRequest) {
@@ -381,7 +369,7 @@ public class VaadinPortlet extends GenericPortlet implements Constants,
ResourceRequest resourceRequest = (ResourceRequest) request;
if (ServletPortletHelper.isUIDLRequest(vaadinRequest)) {
return RequestType.UIDL;
- } else if (isBrowserDetailsRequest(resourceRequest)) {
+ } else if (PortletUIInitHandler.isUIInitRequest(vaadinRequest)) {
return RequestType.BROWSER_DETAILS;
} else if (ServletPortletHelper.isFileUploadRequest(vaadinRequest)) {
return RequestType.FILE_UPLOAD;
@@ -392,12 +380,9 @@ public class VaadinPortlet extends GenericPortlet implements Constants,
return RequestType.APP;
} else if (ServletPortletHelper.isHeartbeatRequest(vaadinRequest)) {
return RequestType.HEARTBEAT;
- } else if (isDummyRequest(resourceRequest)) {
+ } else if (PortletDummyRequestHandler.isDummyRequest(vaadinRequest)) {
return RequestType.DUMMY;
} else {
- // these are not served with ResourceRequests, but by a servlet
- // on the portal at portlet root path (configured by default by
- // Liferay at deployment time, similar on other portals)
return RequestType.STATIC_FILE;
}
} else if (request instanceof ActionRequest) {
@@ -408,16 +393,6 @@ public class VaadinPortlet extends GenericPortlet implements Constants,
return RequestType.UNKNOWN;
}
- private boolean isBrowserDetailsRequest(ResourceRequest request) {
- return request.getResourceID() != null
- && request.getResourceID().equals("v-browserDetails");
- }
-
- private boolean isDummyRequest(ResourceRequest request) {
- return request.getResourceID() != null
- && request.getResourceID().equals("DUMMY");
- }
-
/**
* @param request
* @param response
@@ -430,145 +405,14 @@ public class VaadinPortlet extends GenericPortlet implements Constants,
@Deprecated
protected void handleRequest(PortletRequest request,
PortletResponse response) throws PortletException, IOException {
- RequestTimer requestTimer = new RequestTimer();
- requestTimer.start();
CurrentInstance.clearAll();
setCurrent(this);
-
try {
- AbstractApplicationPortletWrapper portletWrapper = new AbstractApplicationPortletWrapper(
- this);
-
- VaadinPortletRequest vaadinRequest = createVaadinRequest(request);
-
- VaadinPortletResponse vaadinResponse = new VaadinPortletResponse(
- response, getService());
-
- getService().setCurrentInstances(vaadinRequest, vaadinResponse);
-
- RequestType requestType = getRequestType(vaadinRequest);
-
- if (requestType == RequestType.UNKNOWN) {
- handleUnknownRequest(request, response);
- } else if (requestType == RequestType.DUMMY) {
- /*
- * This dummy page is used by action responses to redirect to,
- * in order to prevent the boot strap code from being rendered
- * into strange places such as iframes.
- */
- ((ResourceResponse) response).setContentType("text/html");
- final OutputStream out = ((ResourceResponse) response)
- .getPortletOutputStream();
- final PrintWriter outWriter = new PrintWriter(
- new BufferedWriter(new OutputStreamWriter(out, "UTF-8")));
- outWriter.print("<html><body>dummy page</body></html>");
- outWriter.close();
- } else {
- VaadinPortletSession vaadinSession = null;
-
- try {
- // TODO What about PARAM_UNLOADBURST &
- // redirectToApplication??
-
- vaadinSession = (VaadinPortletSession) getService()
- .findVaadinSession(vaadinRequest);
- if (vaadinSession == null) {
- return;
- }
-
- PortletCommunicationManager communicationManager = (PortletCommunicationManager) vaadinSession
- .getCommunicationManager();
-
- if (requestType == RequestType.PUBLISHED_FILE) {
- communicationManager.servePublishedFile(vaadinRequest,
- vaadinResponse);
- return;
- } else if (requestType == RequestType.HEARTBEAT) {
- communicationManager.handleHeartbeatRequest(
- vaadinRequest, vaadinResponse, vaadinSession);
- return;
- }
-
- /* Update browser information from request */
- vaadinSession.getBrowser().updateRequestDetails(
- vaadinRequest);
-
- /* Notify listeners */
-
- // Finds the right UI
- UI uI = null;
- if (requestType == RequestType.UIDL) {
- uI = getService().findUI(vaadinRequest);
- }
-
- // TODO Should this happen before or after the transaction
- // starts?
- if (request instanceof RenderRequest) {
- vaadinSession.firePortletRenderRequest(uI,
- (RenderRequest) request,
- (RenderResponse) response);
- } else if (request instanceof ActionRequest) {
- vaadinSession.firePortletActionRequest(uI,
- (ActionRequest) request,
- (ActionResponse) response);
- } else if (request instanceof EventRequest) {
- vaadinSession.firePortletEventRequest(uI,
- (EventRequest) request,
- (EventResponse) response);
- } else if (request instanceof ResourceRequest) {
- vaadinSession.firePortletResourceRequest(uI,
- (ResourceRequest) request,
- (ResourceResponse) response);
- }
-
- /* Handle the request */
- if (requestType == RequestType.FILE_UPLOAD) {
- // UI is resolved in handleFileUpload by
- // PortletCommunicationManager
- communicationManager.handleFileUpload(vaadinSession,
- vaadinRequest, vaadinResponse);
- return;
- } else if (requestType == RequestType.BROWSER_DETAILS) {
- communicationManager.handleBrowserDetailsRequest(
- vaadinRequest, vaadinResponse, vaadinSession);
- return;
- } else if (requestType == RequestType.UIDL) {
- // Handles AJAX UIDL requests
- communicationManager.handleUidlRequest(vaadinRequest,
- vaadinResponse, portletWrapper, uI);
-
- // Ensure that the browser does not cache UIDL
- // responses.
- // iOS 6 Safari requires this (#9732)
- response.setProperty("Cache-Control", "no-cache");
- return;
- } else {
- handleOtherRequest(vaadinRequest, vaadinResponse,
- requestType, vaadinSession,
- communicationManager);
- }
- } catch (final SessionExpiredException e) {
- // TODO Figure out a better way to deal with
- // SessionExpiredExceptions
- getLogger().finest("A user session has expired");
- } catch (final GeneralSecurityException e) {
- // TODO Figure out a better way to deal with
- // GeneralSecurityExceptions
- getLogger()
- .fine("General security exception, the security key was probably incorrect.");
- } catch (final Throwable e) {
- handleServiceException(vaadinRequest, vaadinResponse,
- vaadinSession, e);
- } finally {
- if (vaadinSession != null) {
- getService().cleanupSession(vaadinSession);
- requestTimer.stop(vaadinSession);
- }
- }
- }
- } finally {
- CurrentInstance.clearAll();
+ getService().handleRequest(createVaadinRequest(request),
+ createVaadinResponse(response));
+ } catch (ServiceException e) {
+ throw new PortletException(e);
}
}
@@ -592,50 +436,12 @@ public class VaadinPortlet extends GenericPortlet implements Constants,
}
- protected VaadinPortletService getService() {
- return vaadinService;
- }
-
- private void handleUnknownRequest(PortletRequest request,
- PortletResponse response) {
- getLogger().warning("Unknown request type");
+ private VaadinPortletResponse createVaadinResponse(PortletResponse response) {
+ return new VaadinPortletResponse(response, getService());
}
- /**
- * Handle a portlet request that is not for static files, UIDL or upload.
- * Also render requests are handled here.
- *
- * This method is called after starting the application and calling portlet
- * and transaction listeners.
- *
- * @param request
- * @param response
- * @param requestType
- * @param vaadinSession
- * @param vaadinSession
- * @param communicationManager
- * @throws PortletException
- * @throws IOException
- * @throws MalformedURLException
- */
- private void handleOtherRequest(VaadinPortletRequest request,
- VaadinResponse response, RequestType requestType,
- VaadinSession vaadinSession,
- PortletCommunicationManager communicationManager)
- throws PortletException, IOException, MalformedURLException {
- if (requestType == RequestType.APP || requestType == RequestType.RENDER) {
- if (!communicationManager.handleOtherRequest(request, response)) {
- response.sendError(HttpServletResponse.SC_NOT_FOUND,
- "Not found");
- }
- } else if (requestType == RequestType.EVENT) {
- // nothing to do, listeners do all the work
- } else if (requestType == RequestType.ACTION) {
- // nothing to do, listeners do all the work
- } else {
- throw new IllegalStateException(
- "handleRequest() without anything to do - should never happen!");
- }
+ protected VaadinPortletService getService() {
+ return vaadinService;
}
@Override
@@ -678,98 +484,6 @@ public class VaadinPortlet extends GenericPortlet implements Constants,
handleRequest(request, response);
}
- private void handleServiceException(VaadinPortletRequest request,
- VaadinPortletResponse response, VaadinSession vaadinSession,
- Throwable e) throws IOException, PortletException {
- // TODO Check that this error handler is working when running inside a
- // portlet
-
- // if this was an UIDL request, response UIDL back to client
- ErrorHandler errorHandler = ErrorEvent.findErrorHandler(vaadinSession);
- if (getRequestType(request) == RequestType.UIDL) {
- SystemMessages ci = getService().getSystemMessages(
- ServletPortletHelper.findLocale(null, vaadinSession,
- request), request);
- criticalNotification(request, response,
- ci.getInternalErrorCaption(), ci.getInternalErrorMessage(),
- null, ci.getInternalErrorURL());
- if (errorHandler != null) {
- errorHandler.error(new ErrorEvent(e));
- }
- } else {
- if (errorHandler != null) {
- errorHandler.error(new ErrorEvent(e));
- } else {
- // Re-throw other exceptions
- throw new PortletException(e);
- }
- }
- }
-
- /**
- * Send notification to client's application. Used to notify client of
- * critical errors and session expiration due to long inactivity. Server has
- * no knowledge of what application client refers to.
- *
- * @param request
- * the Portlet request instance.
- * @param response
- * the Portlet response to write to.
- * @param caption
- * for the notification
- * @param message
- * for the notification
- * @param details
- * a detail message to show in addition to the passed message.
- * Currently shown directly but could be hidden behind a details
- * drop down.
- * @param url
- * url to load after message, null for current page
- * @throws IOException
- * if the writing failed due to input/output error.
- *
- * @deprecated As of 7.0. Will likely change or be removed in a future
- * version
- */
- @Deprecated
- void criticalNotification(VaadinPortletRequest request,
- VaadinPortletResponse response, String caption, String message,
- String details, String url) throws IOException {
-
- // clients JS app is still running, but server application either
- // no longer exists or it might fail to perform reasonably.
- // send a notification to client's application and link how
- // to "restart" application.
-
- if (caption != null) {
- caption = "\"" + caption + "\"";
- }
- if (details != null) {
- if (message == null) {
- message = details;
- } else {
- message += "<br/><br/>" + details;
- }
- }
- if (message != null) {
- message = "\"" + message + "\"";
- }
- if (url != null) {
- url = "\"" + url + "\"";
- }
-
- // Set the response type
- response.setContentType("application/json; charset=UTF-8");
- final OutputStream out = response.getOutputStream();
- final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
- new OutputStreamWriter(out, "UTF-8")));
- outWriter.print("for(;;);[{\"changes\":[], \"meta\" : {"
- + "\"appError\": {" + "\"caption\":" + caption + ","
- + "\"message\" : " + message + "," + "\"url\" : " + url
- + "}}, \"resources\": {}, \"locales\":[]}]");
- outWriter.close();
- }
-
private static final Logger getLogger() {
return Logger.getLogger(VaadinPortlet.class.getName());
}
diff --git a/server/src/com/vaadin/server/VaadinPortletService.java b/server/src/com/vaadin/server/VaadinPortletService.java
index e59ea7fd5e..2eca07dd4a 100644
--- a/server/src/com/vaadin/server/VaadinPortletService.java
+++ b/server/src/com/vaadin/server/VaadinPortletService.java
@@ -17,21 +17,30 @@
package com.vaadin.server;
import java.io.File;
+import java.io.InputStream;
import java.net.URL;
+import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
+import javax.portlet.EventRequest;
import javax.portlet.PortletContext;
import javax.portlet.PortletRequest;
+import javax.portlet.RenderRequest;
import com.vaadin.server.VaadinPortlet.RequestType;
+import com.vaadin.server.communication.PortletBootstrapHandler;
+import com.vaadin.server.communication.PortletDummyRequestHandler;
+import com.vaadin.server.communication.PortletListenerNotifier;
+import com.vaadin.server.communication.PortletUIInitHandler;
import com.vaadin.ui.UI;
public class VaadinPortletService extends VaadinService {
private final VaadinPortlet portlet;
public VaadinPortletService(VaadinPortlet portlet,
- DeploymentConfiguration deploymentConfiguration) {
+ DeploymentConfiguration deploymentConfiguration)
+ throws ServiceException {
super(deploymentConfiguration);
this.portlet = portlet;
@@ -46,7 +55,25 @@ public class VaadinPortletService extends VaadinService {
}
}
- protected VaadinPortlet getPortlet() {
+ @Override
+ protected List<RequestHandler> createRequestHandlers()
+ throws ServiceException {
+ List<RequestHandler> handlers = super.createRequestHandlers();
+
+ handlers.add(new PortletUIInitHandler());
+ handlers.add(new PortletListenerNotifier());
+ handlers.add(0, new PortletDummyRequestHandler());
+ handlers.add(0, new PortletBootstrapHandler());
+
+ return handlers;
+ }
+
+ /**
+ * Retrieves a reference to the portlet associated with this service.
+ *
+ * @return A reference to the VaadinPortlet this service is using
+ */
+ public VaadinPortlet getPortlet() {
return portlet;
}
@@ -155,13 +182,19 @@ public class VaadinPortletService extends VaadinService {
@Override
protected boolean requestCanCreateSession(VaadinRequest request) {
- RequestType requestType = getRequestType(request);
- if (requestType == RequestType.RENDER) {
+ if (!(request instanceof VaadinPortletRequest)) {
+ throw new IllegalArgumentException(
+ "Request is not a VaadinPortletRequest");
+ }
+
+ PortletRequest portletRequest = ((VaadinPortletRequest) request)
+ .getPortletRequest();
+ if (portletRequest instanceof RenderRequest) {
// In most cases the first request is a render request that
// renders the HTML fragment. This should create a Vaadin
// session unless there is already one.
return true;
- } else if (requestType == RequestType.EVENT) {
+ } else if (portletRequest instanceof EventRequest) {
// A portlet can also be sent an event even though it has not
// been rendered, e.g. portlet on one page sends an event to a
// portlet on another page and then moves the user to that page.
@@ -191,12 +224,6 @@ public class VaadinPortletService extends VaadinService {
return type;
}
- @Override
- protected AbstractCommunicationManager createCommunicationManager(
- VaadinSession session) {
- return new PortletCommunicationManager(session);
- }
-
public static PortletRequest getCurrentPortletRequest() {
VaadinRequest currentRequest = VaadinService.getCurrentRequest();
if (currentRequest instanceof VaadinPortletRequest) {
@@ -230,6 +257,17 @@ public class VaadinPortletService extends VaadinService {
}
@Override
+ public InputStream getThemeResourceAsStream(UI uI, String themeName,
+ String resource) {
+ VaadinPortletSession session = (VaadinPortletSession) uI.getSession();
+ PortletContext portletContext = session.getPortletSession()
+ .getPortletContext();
+ return portletContext.getResourceAsStream("/"
+ + VaadinPortlet.THEME_DIR_PATH + '/' + themeName + "/"
+ + resource);
+ }
+
+ @Override
public String getMainDivId(VaadinSession session, VaadinRequest request,
Class<? extends UI> uiClass) {
PortletRequest portletRequest = ((VaadinPortletRequest) request)
@@ -240,4 +278,20 @@ public class VaadinPortletService extends VaadinService {
*/
return "v-" + portletRequest.getWindowID();
}
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.server.VaadinService#handleSessionExpired(com.vaadin.server
+ * .VaadinRequest, com.vaadin.server.VaadinResponse)
+ */
+ @Override
+ protected void handleSessionExpired(VaadinRequest request,
+ VaadinResponse response) {
+ // TODO Figure out a better way to deal with
+ // SessionExpiredExceptions
+ getLogger().finest("A user session has expired");
+ }
+
}
diff --git a/server/src/com/vaadin/server/VaadinService.java b/server/src/com/vaadin/server/VaadinService.java
index ada0fac107..af0c280c19 100644
--- a/server/src/com/vaadin/server/VaadinService.java
+++ b/server/src/com/vaadin/server/VaadinService.java
@@ -16,24 +16,43 @@
package com.vaadin.server;
+import java.io.BufferedWriter;
import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.Locale;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.portlet.PortletContext;
import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+
+import org.json.JSONException;
+import org.json.JSONObject;
import com.vaadin.annotations.PreserveOnRefresh;
import com.vaadin.event.EventRouter;
+import com.vaadin.server.communication.FileUploadHandler;
+import com.vaadin.server.communication.HeartbeatHandler;
+import com.vaadin.server.communication.PublishedFileHandler;
+import com.vaadin.server.communication.SessionRequestHandler;
+import com.vaadin.server.communication.UidlRequestHandler;
+import com.vaadin.shared.JsonConstants;
import com.vaadin.shared.ui.ui.UIConstants;
import com.vaadin.ui.UI;
import com.vaadin.util.CurrentInstance;
@@ -70,6 +89,8 @@ public abstract class VaadinService implements Serializable {
@Deprecated
public static final String URL_PARAMETER_CLOSE_APPLICATION = "closeApplication";
+ private static final String REQUEST_START_TIME_ATTRIBUTE = "requestStartTime";
+
private final DeploymentConfiguration deploymentConfiguration;
private final EventRouter eventRouter = new EventRouter();
@@ -79,6 +100,15 @@ public abstract class VaadinService implements Serializable {
private ClassLoader classLoader;
+ private Iterable<RequestHandler> requestHandlers;
+
+ /**
+ * Keeps track of whether a warning about missing push support has already
+ * been logged. This is used to avoid spamming the log with the same message
+ * every time a new UI is bootstrapped.
+ */
+ private boolean pushWarningEmitted = false;
+
/**
* Creates a new vaadin service based on a deployment configuration
*
@@ -107,6 +137,45 @@ public abstract class VaadinService implements Serializable {
}
/**
+ * Initializes this service. The service should be initialized before it is
+ * used.
+ *
+ * @since 7.1
+ * @throws ServiceException
+ * if a problem occurs when creating the service
+ */
+ public void init() throws ServiceException {
+ List<RequestHandler> handlers = createRequestHandlers();
+ Collections.reverse(handlers);
+ requestHandlers = Collections.unmodifiableCollection(handlers);
+ }
+
+ /**
+ * Called during initialization to add the request handlers for the service.
+ * Note that the returned list will be reversed so the last handler will be
+ * called first. This enables overriding this method and using add on the
+ * returned list to add a custom request handler which overrides any
+ * predefined handler.
+ *
+ * @return The list of request handlers used by this service.
+ * @throws ServiceException
+ * if a problem occurs when creating the request handlers
+ */
+ protected List<RequestHandler> createRequestHandlers()
+ throws ServiceException {
+ ArrayList<RequestHandler> handlers = new ArrayList<RequestHandler>();
+ handlers.add(new SessionRequestHandler());
+ handlers.add(new PublishedFileHandler());
+ handlers.add(new HeartbeatHandler());
+ handlers.add(new FileUploadHandler());
+ handlers.add(new UidlRequestHandler());
+ handlers.add(new UnsupportedBrowserHandler());
+ handlers.add(new ConnectorResourceHandler());
+
+ return handlers;
+ }
+
+ /**
* Return the URL from where static files, e.g. the widgetset and the theme,
* are served. In a standard configuration the VAADIN folder inside the
* returned folder is what is used for widgetsets and themes.
@@ -329,18 +398,40 @@ public abstract class VaadinService implements Serializable {
SESSION_DESTROY_METHOD);
}
+ /**
+ * Handles destruction of the given session. Internally ensures proper
+ * locking is done.
+ *
+ * @param vaadinSession
+ * The session to destroy
+ */
public void fireSessionDestroy(VaadinSession vaadinSession) {
- for (UI ui : new ArrayList<UI>(vaadinSession.getUIs())) {
- // close() called here for consistency so that it is always called
- // before a UI is removed. UI.isClosing() is thus always true in
- // UI.detach() and associated detach listeners.
- if (!ui.isClosing()) {
- ui.close();
+ final VaadinSession session = vaadinSession;
+ session.access(new Runnable() {
+ @Override
+ public void run() {
+ ArrayList<UI> uis = new ArrayList<UI>(session.getUIs());
+ for (final UI ui : uis) {
+ ui.access(new Runnable() {
+ @Override
+ public void run() {
+ /*
+ * close() called here for consistency so that it is
+ * always called before a UI is removed.
+ * UI.isClosing() is thus always true in UI.detach()
+ * and associated detach listeners.
+ */
+ if (!ui.isClosing()) {
+ ui.close();
+ }
+ session.removeUI(ui);
+ }
+ });
+ }
+ eventRouter.fireEvent(new SessionDestroyEvent(
+ VaadinService.this, session));
}
- vaadinSession.removeUI(ui);
- }
-
- eventRouter.fireEvent(new SessionDestroyEvent(this, vaadinSession));
+ });
}
/**
@@ -358,6 +449,10 @@ public abstract class VaadinService implements Serializable {
/**
* Attempts to find a Vaadin service session associated with this request.
+ * <p>
+ * Handles locking of the session internally to avoid creation of duplicate
+ * sessions by two threads simultaneously.
+ * </p>
*
* @param request
* the request to get a vaadin service session for.
@@ -381,9 +476,135 @@ public abstract class VaadinService implements Serializable {
return vaadinSession;
}
+ /**
+ * Associates the given lock with this service and the given wrapped
+ * session. This method should not be called more than once when the lock is
+ * initialized for the session.
+ *
+ * @see #getSessionLock(WrappedSession)
+ * @param wrappedSession
+ * The wrapped session the lock is associated with
+ * @param lock
+ * The lock object
+ */
+ private void setSessionLock(WrappedSession wrappedSession, Lock lock) {
+ assert wrappedSession != null : "Can't set a lock for a null session";
+ assert wrappedSession.getAttribute(getLockAttributeName()) == null : "Changing the lock for a session is not allowed";
+
+ wrappedSession.setAttribute(getLockAttributeName(), lock);
+ }
+
+ /**
+ * Returns the name used to store the lock in the HTTP session.
+ *
+ * @return The attribute name for the lock
+ */
+ private String getLockAttributeName() {
+ return getServiceName() + ".lock";
+ }
+
+ /**
+ * Gets the lock instance used to lock the VaadinSession associated with the
+ * given wrapped session.
+ * <p>
+ * This method uses the wrapped session instead of VaadinSession to be able
+ * to lock even before the VaadinSession has been initialized.
+ * </p>
+ *
+ * @param wrappedSession
+ * The wrapped session
+ * @return A lock instance used for locking access to the wrapped session
+ */
+ protected Lock getSessionLock(WrappedSession wrappedSession) {
+ Object lock = wrappedSession.getAttribute(getLockAttributeName());
+
+ if (lock instanceof ReentrantLock) {
+ return (ReentrantLock) lock;
+ }
+
+ if (lock == null) {
+ return null;
+ }
+
+ throw new RuntimeException(
+ "Something else than a ReentrantLock was stored in the "
+ + getLockAttributeName() + " in the session");
+ }
+
+ /**
+ * Locks the given session for this service instance. Typically you want to
+ * call {@link VaadinSession#lock()} instead of this method.
+ *
+ * @param wrappedSession
+ * The session to lock
+ */
+ protected void lockSession(WrappedSession wrappedSession) {
+ Lock lock = getSessionLock(wrappedSession);
+ if (lock == null) {
+ /*
+ * No lock found in the session attribute. Ensure only one lock is
+ * created and used by everybody by doing double checked locking.
+ * Assumes there is a memory barrier for the attribute (i.e. that
+ * the CPU flushes its caches and reads the value directly from main
+ * memory).
+ */
+ synchronized (VaadinService.class) {
+ lock = getSessionLock(wrappedSession);
+ if (lock == null) {
+ lock = new ReentrantLock();
+ setSessionLock(wrappedSession, lock);
+ }
+ }
+ }
+ lock.lock();
+ }
+
+ /**
+ * Releases the lock for the given session for this service instance.
+ * Typically you want to call {@link VaadinSession#unlock()} instead of this
+ * method.
+ *
+ * @param wrappedSession
+ * The session to unlock
+ */
+ protected void unlockSession(WrappedSession wrappedSession) {
+ assert getSessionLock(wrappedSession) != null;
+ assert ((ReentrantLock) getSessionLock(wrappedSession))
+ .isHeldByCurrentThread() : "Trying to unlock the session but it has not been locked by this thread";
+ getSessionLock(wrappedSession).unlock();
+ }
+
private VaadinSession findOrCreateVaadinSession(VaadinRequest request)
throws SessionExpiredException, ServiceException {
boolean requestCanCreateSession = requestCanCreateSession(request);
+ WrappedSession wrappedSession = getWrappedSession(request,
+ requestCanCreateSession);
+
+ lockSession(wrappedSession);
+ try {
+ return doFindOrCreateVaadinSession(request, requestCanCreateSession);
+ } finally {
+ unlockSession(wrappedSession);
+ }
+
+ }
+
+ /**
+ * Finds or creates a Vaadin session. Assumes necessary synchronization has
+ * been done by the caller to ensure this is not called simultaneously by
+ * several threads.
+ *
+ * @param request
+ * @param requestCanCreateSession
+ * @return
+ * @throws SessionExpiredException
+ * @throws ServiceException
+ */
+ private VaadinSession doFindOrCreateVaadinSession(VaadinRequest request,
+ boolean requestCanCreateSession) throws SessionExpiredException,
+ ServiceException {
+ assert ((ReentrantLock) getSessionLock(request.getWrappedSession()))
+ .isHeldByCurrentThread() : "Session has not been locked by this thread";
/* Find an existing session for this request. */
VaadinSession session = getExistingSession(request,
@@ -395,10 +616,12 @@ public abstract class VaadinService implements Serializable {
* not specifically requested to close or restart it.
*/
- final boolean restartApplication = (request
- .getParameter(URL_PARAMETER_RESTART_APPLICATION) != null);
- final boolean closeApplication = (request
- .getParameter(URL_PARAMETER_CLOSE_APPLICATION) != null);
+ final boolean restartApplication = hasParameter(request,
+ URL_PARAMETER_RESTART_APPLICATION)
+ && !hasParameter(request,
+ BootstrapHandler.IGNORE_RESTART_PARAM);
+ final boolean closeApplication = hasParameter(request,
+ URL_PARAMETER_CLOSE_APPLICATION);
if (restartApplication) {
closeSession(session, request.getWrappedSession(false));
@@ -429,8 +652,26 @@ public abstract class VaadinService implements Serializable {
}
+ private static boolean hasParameter(VaadinRequest request,
+ String parameterName) {
+ return request.getParameter(parameterName) != null;
+ }
+
+ /**
+ * Creates and registers a new VaadinSession for this service. Assumes
+ * proper locking has been taken care of by the caller.
+ *
+ *
+ * @param request
+ * The request which triggered session creation.
+ * @return A new VaadinSession instance
+ * @throws ServiceException
+ */
private VaadinSession createAndRegisterSession(VaadinRequest request)
throws ServiceException {
+ assert ((ReentrantLock) getSessionLock(request.getWrappedSession()))
+ .isHeldByCurrentThread() : "Session has not been locked by this thread";
+
VaadinSession session = createVaadinSession(request);
VaadinSession.setCurrent(session);
@@ -441,7 +682,7 @@ public abstract class VaadinService implements Serializable {
Locale locale = request.getLocale();
session.setLocale(locale);
session.setConfiguration(getDeploymentConfiguration());
- session.setCommunicationManager(createCommunicationManager(session));
+ session.setCommunicationManager(new LegacyCommunicationManager(session));
ServletPortletHelper.initDefaultUIProvider(session, this);
onVaadinSessionStarted(request, session);
@@ -468,23 +709,13 @@ public abstract class VaadinService implements Serializable {
}
/**
- * Create a communication manager to use for the given service session.
- *
- * @param session
- * the service session for which a new communication manager is
- * needed
- * @return a new communication manager
- */
- protected abstract AbstractCommunicationManager createCommunicationManager(
- VaadinSession session);
-
- /**
- * Creates a new Vaadin service session.
+ * Creates a new Vaadin session for this service and request
*
* @param request
- * @return
- * @throws ServletException
- * @throws MalformedURLException
+ * The request for which to create a VaadinSession
+ * @return A new VaadinSession
+ * @throws ServiceException
+ *
*/
protected VaadinSession createVaadinSession(VaadinRequest request)
throws ServiceException {
@@ -512,12 +743,8 @@ public abstract class VaadinService implements Serializable {
protected VaadinSession getExistingSession(VaadinRequest request,
boolean allowSessionCreation) throws SessionExpiredException {
- // Ensures that the session is still valid
- final WrappedSession session = request
- .getWrappedSession(allowSessionCreation);
- if (session == null) {
- throw new SessionExpiredException();
- }
+ final WrappedSession session = getWrappedSession(request,
+ allowSessionCreation);
VaadinSession vaadinSession = VaadinSession
.getForSession(this, session);
@@ -530,6 +757,28 @@ public abstract class VaadinService implements Serializable {
}
/**
+ * Retrieves the wrapped session for the request.
+ *
+ * @param request
+ * The request for which to retrieve a session
+ * @param requestCanCreateSession
+ * true to create a new session if one currently does not exist
+ * @return The retrieved (or created) wrapped session
+ * @throws SessionExpiredException
+ * If the request is not associated to a session and new session
+ * creation is not allowed
+ */
+ private WrappedSession getWrappedSession(VaadinRequest request,
+ boolean requestCanCreateSession) throws SessionExpiredException {
+ final WrappedSession session = request
+ .getWrappedSession(requestCanCreateSession);
+ if (session == null) {
+ throw new SessionExpiredException();
+ }
+ return session;
+ }
+
+ /**
* Checks whether it's valid to create a new service session as a result of
* the given request.
*
@@ -582,12 +831,21 @@ public abstract class VaadinService implements Serializable {
*/
public void setCurrentInstances(VaadinRequest request,
VaadinResponse response) {
- CurrentInstance.setInheritable(VaadinService.class, this);
+ setCurrent(this);
CurrentInstance.set(VaadinRequest.class, request);
CurrentInstance.set(VaadinResponse.class, response);
}
/**
+ * Sets the given Vaadin service as the current service.
+ *
+ * @param service
+ */
+ public static void setCurrent(VaadinService service) {
+ CurrentInstance.setInheritable(VaadinService.class, service);
+ }
+
+ /**
* Gets the currently processed Vaadin request. The current request is
* automatically defined when the request is started. The current request
* can not be used in e.g. background threads because of the way server
@@ -640,6 +898,7 @@ public abstract class VaadinService implements Serializable {
*
*/
public UI findUI(VaadinRequest request) {
+ // getForSession asserts that the lock is held
VaadinSession session = VaadinSession.getForSession(this,
request.getWrappedSession());
@@ -647,16 +906,10 @@ public abstract class VaadinService implements Serializable {
String uiIdString = request.getParameter(UIConstants.UI_ID_PARAMETER);
int uiId = Integer.parseInt(uiIdString);
- // Get lock before accessing data in session
- session.lock();
- try {
- UI ui = session.getUIById(uiId);
+ UI ui = session.getUIById(uiId);
- UI.setCurrent(ui);
- return ui;
- } finally {
- session.unlock();
- }
+ UI.setCurrent(ui);
+ return ui;
}
/**
@@ -725,8 +978,12 @@ public abstract class VaadinService implements Serializable {
// Ensure VaadinServiceSession knows where it's stored
if (value instanceof VaadinSession) {
VaadinSession serviceSession = (VaadinSession) value;
- serviceSession.storeInSession(serviceSession.getService(),
- newSession);
+ VaadinService service = serviceSession.getService();
+ // Use the same lock instance in the new session
+ service.setSessionLock(newSession,
+ serviceSession.getLockInstance());
+
+ serviceSession.storeInSession(service, newSession);
serviceSession
.setAttribute(REINITIALIZING_SESSION_MARKER, null);
}
@@ -735,6 +992,19 @@ public abstract class VaadinService implements Serializable {
}
/**
+ * TODO PUSH Document
+ *
+ * TODO Pass UI or VaadinSession?
+ *
+ * @param uI
+ * @param themeName
+ * @param resource
+ * @return
+ */
+ public abstract InputStream getThemeResourceAsStream(UI uI,
+ String themeName, String resource);
+
+ /**
* Creates and returns a unique ID for the DIV where the UI is to be
* rendered.
*
@@ -814,13 +1084,19 @@ public abstract class VaadinService implements Serializable {
*
* @param session
*/
- private void removeClosedUIs(VaadinSession session) {
- for (UI ui : new ArrayList<UI>(session.getUIs())) {
- if (ui.isClosing()) {
- getLogger().log(Level.FINER, "Removing closed UI {0}",
- ui.getUIId());
- session.removeUI(ui);
- }
+ private void removeClosedUIs(final VaadinSession session) {
+ ArrayList<UI> uis = new ArrayList<UI>(session.getUIs());
+ for (final UI ui : uis) {
+ ui.access(new Runnable() {
+ @Override
+ public void run() {
+ if (ui.isClosing()) {
+ getLogger().log(Level.FINER, "Removing closed UI {0}",
+ ui.getUIId());
+ session.removeUI(ui);
+ }
+ }
+ });
}
}
@@ -938,4 +1214,387 @@ public abstract class VaadinService implements Serializable {
private static final Logger getLogger() {
return Logger.getLogger(VaadinService.class.getName());
}
+
+ /**
+ * Called before the framework starts handling a request
+ *
+ * @param request
+ * The request
+ * @param response
+ * The response
+ */
+ public void requestStart(VaadinRequest request, VaadinResponse response) {
+ setCurrentInstances(request, response);
+ request.setAttribute(REQUEST_START_TIME_ATTRIBUTE, System.nanoTime());
+ }
+
+ /**
+ * Called after the framework has handled a request and the response has
+ * been written.
+ *
+ * @param request
+ * The request object
+ * @param response
+ * The response object
+ * @param session
+ * The session which was used during the request or null if the
+ * request did not use a session
+ */
+ public void requestEnd(VaadinRequest request, VaadinResponse response,
+ VaadinSession session) {
+ if (session != null) {
+ final VaadinSession finalSession = session;
+
+ session.access(new Runnable() {
+ @Override
+ public void run() {
+ cleanupSession(finalSession);
+ }
+ });
+
+ final long duration = (System.nanoTime() - (Long) request
+ .getAttribute(REQUEST_START_TIME_ATTRIBUTE)) / 1000000;
+ session.access(new Runnable() {
+ @Override
+ public void run() {
+ finalSession.setLastRequestDuration(duration);
+ }
+ });
+ }
+ CurrentInstance.clearAll();
+ }
+
+ /**
+ * Returns the request handlers that are registered with this service. The
+ * iteration order of the returned collection is the same as the order in
+ * which the request handlers will be invoked when a request is handled.
+ *
+ * @return a collection of request handlers in the order they are invoked
+ *
+ * @see #createRequestHandlers()
+ *
+ * @since 7.1
+ */
+ public Iterable<RequestHandler> getRequestHandlers() {
+ return requestHandlers;
+ }
+
+ /**
+ * Handles the incoming request and writes the response into the response
+ * object. Uses {@link #getRequestHandlers()} for handling the request.
+ * <p>
+ * If a session expiration is detected during request handling then each
+ * {@link RequestHandler request handler} has an opportunity to handle the
+ * expiration event if it implements {@link SessionExpiredHandler}. If no
+ * request handler handles session expiration a default expiration message
+ * will be written.
+ * </p>
+ *
+ * @param request
+ * The incoming request
+ * @param response
+ * The outgoing response
+ * @throws ServiceException
+ * Any exception that occurs during response handling will be
+ * wrapped in a ServiceException
+ */
+ public void handleRequest(VaadinRequest request, VaadinResponse response)
+ throws ServiceException {
+ requestStart(request, response);
+
+ VaadinSession vaadinSession = null;
+ try {
+ // Find out the service session this request is related to
+ vaadinSession = findVaadinSession(request);
+ if (vaadinSession == null) {
+ return;
+ }
+
+ for (RequestHandler handler : getRequestHandlers()) {
+ if (handler.handleRequest(vaadinSession, request, response)) {
+ return;
+ }
+ }
+
+ // Request not handled by any RequestHandler
+ response.sendError(HttpServletResponse.SC_NOT_FOUND,
+ "Request was not handled by any registered handler.");
+
+ } catch (final SessionExpiredException e) {
+ handleSessionExpired(request, response);
+ } catch (final Throwable e) {
+ handleExceptionDuringRequest(request, response, vaadinSession, e);
+ } finally {
+ requestEnd(request, response, vaadinSession);
+ }
+ }
+
+ private void handleExceptionDuringRequest(VaadinRequest request,
+ VaadinResponse response, VaadinSession vaadinSession, Throwable t)
+ throws ServiceException {
+ if (vaadinSession != null) {
+ vaadinSession.lock();
+ }
+ try {
+ ErrorHandler errorHandler = ErrorEvent
+ .findErrorHandler(vaadinSession);
+
+ // if this was an UIDL request, send UIDL back to the client
+ if (ServletPortletHelper.isUIDLRequest(request)) {
+ SystemMessages ci = getSystemMessages(
+ ServletPortletHelper.findLocale(null, vaadinSession,
+ request), request);
+ try {
+ writeStringResponse(
+ response,
+ JsonConstants.JSON_CONTENT_TYPE,
+ createCriticalNotificationJSON(
+ ci.getInternalErrorCaption(),
+ ci.getInternalErrorMessage(), null,
+ ci.getInternalErrorURL()));
+ } catch (IOException e) {
+ // An exception occured while writing the response. Log
+ // it and continue handling only the original error.
+ getLogger()
+ .log(Level.WARNING,
+ "Failed to write critical notification response to the client",
+ e);
+ }
+ if (errorHandler != null) {
+ errorHandler.error(new ErrorEvent(t));
+ }
+ } else {
+ if (errorHandler != null) {
+ errorHandler.error(new ErrorEvent(t));
+ }
+
+ // Re-throw other exceptions
+ throw new ServiceException(t);
+ }
+ } finally {
+ if (vaadinSession != null) {
+ vaadinSession.unlock();
+ }
+ }
+
+ }
+
+ /**
+ * Writes the given string as a response using the given content type.
+ *
+ * @param response
+ * The response reference
+ * @param contentType
+ * The content type of the response
+ * @param reponseString
+ * The actual response
+ * @throws IOException
+ * If an error occured while writing the response
+ */
+ public void writeStringResponse(VaadinResponse response,
+ String contentType, String reponseString) throws IOException {
+
+ response.setContentType(contentType);
+
+ final OutputStream out = response.getOutputStream();
+ final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
+ new OutputStreamWriter(out, "UTF-8")));
+ outWriter.print(reponseString);
+ outWriter.close();
+ }
+
+ /**
+ * Called when the session has expired and the request handling is therefore
+ * aborted.
+ *
+ * @param request
+ * The request
+ * @param response
+ * The response
+ * @throws ServiceException
+ * Thrown if there was any problem handling the expiration of
+ * the session
+ */
+ protected void handleSessionExpired(VaadinRequest request,
+ VaadinResponse response) throws ServiceException {
+ for (RequestHandler handler : getRequestHandlers()) {
+ if (handler instanceof SessionExpiredHandler) {
+ try {
+ if (((SessionExpiredHandler) handler).handleSessionExpired(
+ request, response)) {
+ return;
+ }
+ } catch (IOException e) {
+ throw new ServiceException(
+ "Handling of session expired failed", e);
+ }
+ }
+ }
+
+ // No request handlers handled the request. Write a normal HTTP response
+
+ try {
+ // If there is a URL, try to redirect there
+ SystemMessages systemMessages = getSystemMessages(
+ ServletPortletHelper.findLocale(null, null, request),
+ request);
+ String sessionExpiredURL = systemMessages.getSessionExpiredURL();
+ if (sessionExpiredURL != null
+ && (response instanceof VaadinServletResponse)) {
+ ((VaadinServletResponse) response)
+ .sendRedirect(sessionExpiredURL);
+ } else {
+ /*
+ * Session expired as a result of a standard http request and we
+ * have nowhere to redirect. Reloading would likely cause an
+ * endless loop. This can at least happen if refreshing a
+ * resource when the session has expired.
+ */
+ response.sendError(HttpServletResponse.SC_GONE,
+ "Session expired");
+ }
+ } catch (IOException e) {
+ throw new ServiceException(e);
+ }
+ }
+
+ /**
+ * Creates a JSON message which, when sent to client as-is, will cause a
+ * critical error to be shown with the given details.
+ *
+ * @param caption
+ * The caption of the error or null to omit
+ * @param message
+ * The error message or null to omit
+ * @param details
+ * Additional error details or null to omit
+ * @param url
+ * A url to redirect to. If no other details are given then the
+ * user will be immediately redirected to this URL. Otherwise the
+ * message will be shown and the browser will redirect to the
+ * given URL only after the user acknowledges the message. If
+ * null then the browser will refresh the current page.
+ * @return A JSON string to be sent to the client
+ */
+ public static String createCriticalNotificationJSON(String caption,
+ String message, String details, String url) {
+ String returnString = "";
+ try {
+ if (message == null) {
+ message = details;
+ } else if (details != null) {
+ message += "<br/><br/>" + details;
+ }
+
+ JSONObject appError = new JSONObject();
+ appError.put("caption", caption);
+ appError.put("message", message);
+ appError.put("url", url);
+
+ JSONObject meta = new JSONObject();
+ 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("meta", meta);
+ returnString = json.toString();
+ } catch (JSONException e) {
+ getLogger().log(Level.WARNING,
+ "Error creating critical notification JSON message", e);
+ }
+
+ return "for(;;);[" + returnString + "]";
+ }
+
+ /**
+ * @deprecated As of 7.0. Will likely change or be removed in a future
+ * version
+ */
+ @Deprecated
+ public void criticalNotification(VaadinRequest request,
+ VaadinResponse response, String caption, String message,
+ String details, String url) throws IOException {
+ writeStringResponse(response, JsonConstants.JSON_CONTENT_TYPE,
+ createCriticalNotificationJSON(caption, message, details, url));
+ }
+
+ /**
+ * Enables push if push support is available and push has not yet been
+ * enabled.
+ *
+ * If push support is not available, a warning explaining the situation will
+ * be logged at least the first time this method is invoked.
+ *
+ * @return <code>true</code> if push can be used; <code>false</code> if push
+ * is not available.
+ */
+ public boolean ensurePushAvailable() {
+ if (!pushWarningEmitted) {
+ pushWarningEmitted = true;
+ getLogger().log(Level.WARNING, Constants.PUSH_NOT_SUPPORTED_ERROR,
+ getClass().getSimpleName());
+ }
+ // Not supported by default for now, sublcasses may override
+ return false;
+ }
+
+ /**
+ * Checks that another {@link VaadinSession} instance is not locked. This is
+ * internally used by {@link VaadinSession#access(Runnable)} and
+ * {@link UI#access(Runnable)} to help avoid causing deadlocks.
+ *
+ * @since 7.1
+ * @param session
+ * the session that is being locked
+ * @throws IllegalStateException
+ * 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()) {
+ throw new IllegalStateException(
+ "Can't access session while another session is locked by the same thread. This restriction is intended to help avoid deadlocks.");
+ }
+ }
+
+ /**
+ * 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.
+ * <p>
+ * This protection is enabled by default, but it might need to be disabled
+ * to allow a certain type of testing. For these cases, the check can be
+ * disabled by setting the init parameter
+ * {@value Constants#SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION} to
+ * <code>true</code>.
+ *
+ * @see DeploymentConfiguration#isXsrfProtectionEnabled()
+ *
+ * @since 7.1
+ *
+ * @param session
+ * the vaadin session for which the check should be done
+ * @param requestToken
+ * the CSRF token provided in the request
+ * @return <code>true</code> if the token is valid or if the protection is
+ * disabled; <code>false</code> if protection is enabled and the
+ * token is invalid
+ */
+ public static boolean isCsrfTokenValid(VaadinSession session,
+ String requestToken) {
+
+ if (session.getService().getDeploymentConfiguration()
+ .isXsrfProtectionEnabled()) {
+ String sessionToken = session.getCsrfToken();
+
+ if (sessionToken == null || !sessionToken.equals(requestToken)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
}
diff --git a/server/src/com/vaadin/server/VaadinServlet.java b/server/src/com/vaadin/server/VaadinServlet.java
index 35d5fd7cc1..de074941c1 100644
--- a/server/src/com/vaadin/server/VaadinServlet.java
+++ b/server/src/com/vaadin/server/VaadinServlet.java
@@ -24,7 +24,6 @@ import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
-import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
@@ -35,43 +34,18 @@ import java.util.logging.Logger;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
-import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.vaadin.sass.internal.ScssStylesheet;
-import com.vaadin.server.AbstractCommunicationManager.Callback;
-import com.vaadin.shared.ApplicationConstants;
-import com.vaadin.ui.UI;
+import com.vaadin.server.communication.ServletUIInitHandler;
+import com.vaadin.shared.JsonConstants;
import com.vaadin.util.CurrentInstance;
@SuppressWarnings("serial")
public class VaadinServlet extends HttpServlet implements Constants {
- private static class AbstractApplicationServletWrapper implements Callback {
-
- private final VaadinServlet servlet;
-
- public AbstractApplicationServletWrapper(VaadinServlet servlet) {
- this.servlet = servlet;
- }
-
- @Override
- public void criticalNotification(VaadinRequest request,
- VaadinResponse response, String cap, String msg,
- String details, String outOfSyncURL) throws IOException {
- servlet.criticalNotification((VaadinServletRequest) request,
- ((VaadinServletResponse) response), cap, msg, details,
- outOfSyncURL);
- }
- }
-
- // TODO Move some (all?) of the constants to a separate interface (shared
- // with portlet)
-
- private final String resourcePath = null;
-
private VaadinServletService servletService;
/**
@@ -110,7 +84,11 @@ public class VaadinServlet extends HttpServlet implements Constants {
}
DeploymentConfiguration deploymentConfiguration = createDeploymentConfiguration(initParameters);
- servletService = createServletService(deploymentConfiguration);
+ try {
+ servletService = createServletService(deploymentConfiguration);
+ } catch (ServiceException e) {
+ throw new ServletException("Could not initialize VaadinServlet", e);
+ }
// Sets current service even though there are no request and response
servletService.setCurrentInstances(null, null);
@@ -168,8 +146,12 @@ public class VaadinServlet extends HttpServlet implements Constants {
}
protected VaadinServletService createServletService(
- DeploymentConfiguration deploymentConfiguration) {
- return new VaadinServletService(this, deploymentConfiguration);
+ DeploymentConfiguration deploymentConfiguration)
+ throws ServiceException {
+ VaadinServletService service = new VaadinServletService(this,
+ deploymentConfiguration);
+ service.init();
+ return service;
}
/**
@@ -198,7 +180,23 @@ public class VaadinServlet extends HttpServlet implements Constants {
}
CurrentInstance.clearAll();
setCurrent(this);
- service(createVaadinRequest(request), createVaadinResponse(response));
+
+ VaadinServletRequest vaadinRequest = createVaadinRequest(request);
+ VaadinServletResponse vaadinResponse = createVaadinResponse(response);
+ if (!ensureCookiesEnabled(vaadinRequest, vaadinResponse)) {
+ return;
+ }
+
+ if (isStaticResourceRequest(request)) {
+ serveStaticResources(request, response);
+ return;
+ }
+ try {
+ getService().handleRequest(vaadinRequest, vaadinResponse);
+ } catch (ServiceException e) {
+ throw new ServletException(e);
+ }
+
}
/**
@@ -218,7 +216,7 @@ public class VaadinServlet extends HttpServlet implements Constants {
*/
protected boolean handleContextRootWithoutSlash(HttpServletRequest request,
HttpServletResponse response) throws IOException {
- if ("/".equals(request.getPathInfo())
+ if ((request.getPathInfo() == null || "/".equals(request.getPathInfo()))
&& "".equals(request.getServletPath())
&& !request.getRequestURI().endsWith("/")) {
/*
@@ -237,119 +235,6 @@ public class VaadinServlet extends HttpServlet implements Constants {
}
}
- private void service(VaadinServletRequest request,
- VaadinServletResponse response) throws ServletException,
- IOException {
- RequestTimer requestTimer = new RequestTimer();
- requestTimer.start();
-
- getService().setCurrentInstances(request, response);
-
- AbstractApplicationServletWrapper servletWrapper = new AbstractApplicationServletWrapper(
- this);
-
- RequestType requestType = getRequestType(request);
- if (!ensureCookiesEnabled(requestType, request, response)) {
- return;
- }
-
- if (requestType == RequestType.STATIC_FILE) {
- serveStaticResources(request, response);
- return;
- }
-
- VaadinSession vaadinSession = null;
-
- try {
- // If a duplicate "close application" URL is received for an
- // application that is not open, redirect to the application's main
- // page.
- // This is needed as e.g. Spring Security remembers the last
- // URL from the application, which is the logout URL, and repeats
- // it.
- // We can tell apart a real onunload request from a repeated one
- // based on the real one having content (at least the UIDL security
- // key).
- if (requestType == RequestType.UIDL
- && request.getParameterMap().containsKey(
- ApplicationConstants.PARAM_UNLOADBURST)
- && request.getContentLength() < 1
- && getService().getExistingSession(request, false) == null) {
- redirectToApplication(request, response);
- return;
- }
-
- // Find out the service session this request is related to
- vaadinSession = getService().findVaadinSession(request);
- if (vaadinSession == null) {
- return;
- }
-
- CommunicationManager communicationManager = (CommunicationManager) vaadinSession
- .getCommunicationManager();
-
- if (requestType == RequestType.PUBLISHED_FILE) {
- communicationManager.servePublishedFile(request, response);
- return;
- } else if (requestType == RequestType.HEARTBEAT) {
- communicationManager.handleHeartbeatRequest(request, response,
- vaadinSession);
- return;
- }
-
- /* Update browser information from the request */
- vaadinSession.getBrowser().updateRequestDetails(request);
-
- /* Handle the request */
- if (requestType == RequestType.FILE_UPLOAD) {
- // UI is resolved in communication manager
- communicationManager.handleFileUpload(vaadinSession, request,
- response);
- return;
- } else if (requestType == RequestType.UIDL) {
- UI uI = getService().findUI(request);
- if (uI == null) {
- throw new ServletException(ERROR_NO_UI_FOUND);
- }
- // Handles AJAX UIDL requests
- communicationManager.handleUidlRequest(request, response,
- servletWrapper, uI);
-
- // Ensure that the browser does not cache UIDL responses.
- // iOS 6 Safari requires this (#9732)
- response.setHeader("Cache-Control", "no-cache");
-
- return;
- } else if (requestType == RequestType.BROWSER_DETAILS) {
- // Browser details - not related to a specific UI
- communicationManager.handleBrowserDetailsRequest(request,
- response, vaadinSession);
- return;
- }
-
- if (communicationManager.handleOtherRequest(request, response)) {
- return;
- }
-
- // Request not handled by any RequestHandler -> 404
- response.sendError(HttpServletResponse.SC_NOT_FOUND);
-
- } catch (final SessionExpiredException e) {
- // Session has expired, notify user
- handleServiceSessionExpired(request, response);
- } catch (final GeneralSecurityException e) {
- handleServiceSecurityException(request, response);
- } catch (final Throwable e) {
- handleServiceException(request, response, vaadinSession, e);
- } finally {
- if (vaadinSession != null) {
- getService().cleanupSession(vaadinSession);
- requestTimer.stop(vaadinSession);
- }
- CurrentInstance.clearAll();
- }
- }
-
private VaadinServletResponse createVaadinResponse(
HttpServletResponse response) {
return new VaadinServletResponse(response, getService());
@@ -391,10 +276,9 @@ public class VaadinServlet extends HttpServlet implements Constants {
* @return false if cookies are disabled, true otherwise
* @throws IOException
*/
- private boolean ensureCookiesEnabled(RequestType requestType,
- VaadinServletRequest request, VaadinServletResponse response)
- throws IOException {
- if (requestType == RequestType.UIDL) {
+ private boolean ensureCookiesEnabled(VaadinServletRequest request,
+ VaadinServletResponse response) throws IOException {
+ if (ServletPortletHelper.isUIDLRequest(request)) {
// In all other but the first UIDL request a cookie should be
// returned by the browser.
// This can be removed if cookieless mode (#3228) is supported
@@ -403,10 +287,13 @@ public class VaadinServlet extends HttpServlet implements Constants {
SystemMessages systemMessages = getService().getSystemMessages(
ServletPortletHelper.findLocale(null, null, request),
request);
- criticalNotification(request, response,
- systemMessages.getCookiesDisabledCaption(),
- systemMessages.getCookiesDisabledMessage(), null,
- systemMessages.getCookiesDisabledURL());
+ getService().writeStringResponse(
+ response,
+ JsonConstants.JSON_CONTENT_TYPE,
+ VaadinService.createCriticalNotificationJSON(
+ systemMessages.getCookiesDisabledCaption(),
+ systemMessages.getCookiesDisabledMessage(),
+ null, systemMessages.getCookiesDisabledURL()));
return false;
}
}
@@ -437,39 +324,19 @@ public class VaadinServlet extends HttpServlet implements Constants {
* @throws IOException
* if the writing failed due to input/output error.
*
- * @deprecated As of 7.0. Will likely change or be removed in a future
- * version
+ * @deprecated As of 7.0. This method is retained only for backwards
+ * compatibility and for {@link GAEVaadinServlet}.
*/
@Deprecated
protected void criticalNotification(VaadinServletRequest request,
- HttpServletResponse response, String caption, String message,
+ VaadinServletResponse response, String caption, String message,
String details, String url) throws IOException {
if (ServletPortletHelper.isUIDLRequest(request)) {
-
- if (caption != null) {
- caption = "\"" + JsonPaintTarget.escapeJSON(caption) + "\"";
- }
- if (details != null) {
- if (message == null) {
- message = details;
- } else {
- message += "<br/><br/>" + details;
- }
- }
-
- if (message != null) {
- message = "\"" + JsonPaintTarget.escapeJSON(message) + "\"";
- }
- if (url != null) {
- url = "\"" + JsonPaintTarget.escapeJSON(url) + "\"";
- }
-
- String output = "for(;;);[{\"changes\":[], \"meta\" : {"
- + "\"appError\": {" + "\"caption\":" + caption + ","
- + "\"message\" : " + message + "," + "\"url\" : " + url
- + "}}, \"resources\": {}, \"locales\":[]}]";
- writeResponse(response, "application/json; charset=UTF-8", output);
+ String output = VaadinService.createCriticalNotificationJSON(
+ caption, message, details, url);
+ getService().writeStringResponse(response,
+ JsonConstants.JSON_CONTENT_TYPE, output);
} else {
// Create an HTML reponse with the error
String output = "";
@@ -492,10 +359,9 @@ public class VaadinServlet extends HttpServlet implements Constants {
if (url != null) {
output += "</a>";
}
- writeResponse(response, "text/html; charset=UTF-8", output);
-
+ getService().writeStringResponse(response,
+ "text/html; charset=UTF-8", output);
}
-
}
/**
@@ -511,7 +377,7 @@ public class VaadinServlet extends HttpServlet implements Constants {
private void writeResponse(HttpServletResponse response,
String contentType, String output) throws IOException {
response.setContentType(contentType);
- final ServletOutputStream out = response.getOutputStream();
+ final OutputStream out = response.getOutputStream();
// Set the response type
final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(out, "UTF-8")));
@@ -555,33 +421,6 @@ public class VaadinServlet extends HttpServlet implements Constants {
return resultPath;
}
- private void handleServiceException(VaadinServletRequest request,
- VaadinServletResponse response, VaadinSession vaadinSession,
- Throwable e) throws IOException, ServletException {
- ErrorHandler errorHandler = ErrorEvent.findErrorHandler(vaadinSession);
-
- // if this was an UIDL request, response UIDL back to client
- if (getRequestType(request) == RequestType.UIDL) {
- SystemMessages ci = getService().getSystemMessages(
- ServletPortletHelper.findLocale(null, vaadinSession,
- request), request);
- criticalNotification(request, response,
- ci.getInternalErrorCaption(), ci.getInternalErrorMessage(),
- null, ci.getInternalErrorURL());
- if (errorHandler != null) {
- errorHandler.error(new ErrorEvent(e));
- }
- } else {
- if (errorHandler != null) {
- errorHandler.error(new ErrorEvent(e));
- }
-
- // Re-throw other exceptions
- throw new ServletException(e);
- }
-
- }
-
/**
* A helper method to strip away characters that might somehow be used for
* XSS attacs. Leaves at least alphanumeric characters intact. Also removes
@@ -626,74 +465,9 @@ public class VaadinServlet extends HttpServlet implements Constants {
return DEFAULT_THEME_NAME;
}
- /**
- * @param request
- * @param response
- * @throws IOException
- * @throws ServletException
- *
- * @deprecated As of 7.0. Will likely change or be removed in a future
- * version
- */
- @Deprecated
- void handleServiceSessionExpired(VaadinServletRequest request,
- VaadinServletResponse response) throws IOException,
- ServletException {
-
- if (isOnUnloadRequest(request)) {
- /*
- * Request was an unload request (e.g. window close event) and the
- * client expects no response if it fails.
- */
- return;
- }
-
- try {
- SystemMessages ci = getService().getSystemMessages(
- ServletPortletHelper.findLocale(null, null, request),
- request);
- RequestType requestType = getRequestType(request);
- if (requestType == RequestType.UIDL) {
- /*
- * Invalidate session (weird to have session if we're saying
- * that it's expired, and worse: portal integration will fail
- * since the session is not created by the portal.
- *
- * Session must be invalidated before criticalNotification as it
- * commits the response.
- */
- request.getSession().invalidate();
-
- // send uidl redirect
- criticalNotification(request, response,
- ci.getSessionExpiredCaption(),
- ci.getSessionExpiredMessage(), null,
- ci.getSessionExpiredURL());
-
- } else if (requestType == RequestType.HEARTBEAT) {
- response.sendError(HttpServletResponse.SC_GONE,
- "Session expired");
- } else {
- // 'plain' http req - e.g. browser reload;
- // just go ahead redirect the browser
- response.sendRedirect(ci.getSessionExpiredURL());
- }
- } catch (SystemMessageException ee) {
- throw new ServletException(ee);
- }
-
- }
-
private void handleServiceSecurityException(VaadinServletRequest request,
VaadinServletResponse response) throws IOException,
ServletException {
- if (isOnUnloadRequest(request)) {
- /*
- * Request was an unload request (e.g. window close event) and the
- * client expects no response if it fails.
- */
- return;
- }
try {
/*
@@ -702,20 +476,17 @@ public class VaadinServlet extends HttpServlet implements Constants {
*/
SystemMessages ci = getService().getSystemMessages(
request.getLocale(), request);
- RequestType requestType = getRequestType(request);
- if (requestType == RequestType.UIDL) {
+ if (ServletPortletHelper.isUIDLRequest(request)) {
// send uidl redirect
- criticalNotification(request, response,
- ci.getCommunicationErrorCaption(),
- ci.getCommunicationErrorMessage(),
- INVALID_SECURITY_KEY_MSG, ci.getCommunicationErrorURL());
- /*
- * Invalidate session. Portal integration will fail otherwise
- * since the session is not created by the portal.
- */
- request.getSession().invalidate();
-
- } else if (requestType == RequestType.HEARTBEAT) {
+ getService().writeStringResponse(
+ response,
+ JsonConstants.JSON_CONTENT_TYPE,
+ VaadinService.createCriticalNotificationJSON(
+ ci.getCommunicationErrorCaption(),
+ ci.getCommunicationErrorMessage(),
+ INVALID_SECURITY_KEY_MSG,
+ ci.getCommunicationErrorURL()));
+ } else if (ServletPortletHelper.isHeartbeatRequest(request)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN,
"Forbidden");
} else {
@@ -744,9 +515,8 @@ public class VaadinServlet extends HttpServlet implements Constants {
private boolean serveStaticResources(HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
- // FIXME What does 10 refer to?
String pathInfo = request.getPathInfo();
- if (pathInfo == null || pathInfo.length() <= 10) {
+ if (pathInfo == null) {
return false;
}
@@ -1118,10 +888,12 @@ public class VaadinServlet extends HttpServlet implements Constants {
/**
*
* @author Vaadin Ltd
- * @since 7.0.0
+ * @since 7.0
*
- * @deprecated As of 7.0. Will likely change or be removed in a future
- * version
+ * @deprecated As of 7.0. This is no longer used and only provided for
+ * backwards compatibility. Each {@link RequestHandler} can
+ * individually decide whether it wants to handle a request or
+ * not.
*/
@Deprecated
protected enum RequestType {
@@ -1132,8 +904,10 @@ public class VaadinServlet extends HttpServlet implements Constants {
* @param request
* @return
*
- * @deprecated As of 7.0. Will likely change or be removed in a future
- * version
+ * @deprecated As of 7.0. This is no longer used and only provided for
+ * backwards compatibility. Each {@link RequestHandler} can
+ * individually decide whether it wants to handle a request or
+ * not.
*/
@Deprecated
protected RequestType getRequestType(VaadinServletRequest request) {
@@ -1141,7 +915,7 @@ public class VaadinServlet extends HttpServlet implements Constants {
return RequestType.FILE_UPLOAD;
} else if (ServletPortletHelper.isPublishedFileRequest(request)) {
return RequestType.PUBLISHED_FILE;
- } else if (isBrowserDetailsRequest(request)) {
+ } else if (ServletUIInitHandler.isUIInitRequest(request)) {
return RequestType.BROWSER_DETAILS;
} else if (ServletPortletHelper.isUIDLRequest(request)) {
return RequestType.UIDL;
@@ -1156,14 +930,9 @@ public class VaadinServlet extends HttpServlet implements Constants {
}
- private static boolean isBrowserDetailsRequest(HttpServletRequest request) {
- return "POST".equals(request.getMethod())
- && request.getParameter("v-browserDetails") != null;
- }
-
- private boolean isStaticResourceRequest(HttpServletRequest request) {
+ protected boolean isStaticResourceRequest(HttpServletRequest request) {
String pathInfo = request.getPathInfo();
- if (pathInfo == null || pathInfo.length() <= 10) {
+ if (pathInfo == null) {
return false;
}
@@ -1178,10 +947,6 @@ public class VaadinServlet extends HttpServlet implements Constants {
return false;
}
- private boolean isOnUnloadRequest(HttpServletRequest request) {
- return request.getParameter(ApplicationConstants.PARAM_UNLOADBURST) != null;
- }
-
/**
* Remove any heading or trailing "what" from the "string".
*
@@ -1301,4 +1066,5 @@ public class VaadinServlet extends HttpServlet implements Constants {
private static final Logger getLogger() {
return Logger.getLogger(VaadinServlet.class.getName());
}
+
}
diff --git a/server/src/com/vaadin/server/VaadinServletService.java b/server/src/com/vaadin/server/VaadinServletService.java
index 71f47ea217..3b39f17849 100644
--- a/server/src/com/vaadin/server/VaadinServletService.java
+++ b/server/src/com/vaadin/server/VaadinServletService.java
@@ -17,19 +17,38 @@
package com.vaadin.server;
import java.io.File;
+import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
-import com.vaadin.server.VaadinServlet.RequestType;
+import org.atmosphere.util.Version;
+
+import com.vaadin.server.communication.PushRequestHandler;
+import com.vaadin.server.communication.ServletBootstrapHandler;
+import com.vaadin.server.communication.ServletUIInitHandler;
import com.vaadin.ui.UI;
public class VaadinServletService extends VaadinService {
private final VaadinServlet servlet;
+ private final static boolean atmosphereAvailable = checkAtmosphereSupport();
+
+ /**
+ * Keeps track of whether a warning about missing push support has already
+ * been logged. This is used to avoid spamming the log with the same message
+ * every time a new UI is bootstrapped.
+ */
+ private boolean pushWarningLogged = false;
+
public VaadinServletService(VaadinServlet servlet,
- DeploymentConfiguration deploymentConfiguration) {
+ DeploymentConfiguration deploymentConfiguration)
+ throws ServiceException {
super(deploymentConfiguration);
this.servlet = servlet;
@@ -44,7 +63,40 @@ public class VaadinServletService extends VaadinService {
}
}
- protected VaadinServlet getServlet() {
+ private static boolean checkAtmosphereSupport() {
+ try {
+ String rawVersion = Version.getRawVersion();
+ if (!Constants.REQUIRED_ATMOSPHERE_VERSION.equals(rawVersion)) {
+ getLogger().log(
+ Level.WARNING,
+ Constants.INVALID_ATMOSPHERE_VERSION_WARNING,
+ new Object[] { Constants.REQUIRED_ATMOSPHERE_VERSION,
+ rawVersion });
+ }
+ return true;
+ } catch (NoClassDefFoundError e) {
+ return false;
+ }
+ }
+
+ @Override
+ protected List<RequestHandler> createRequestHandlers()
+ throws ServiceException {
+ List<RequestHandler> handlers = super.createRequestHandlers();
+ handlers.add(0, new ServletBootstrapHandler());
+ handlers.add(new ServletUIInitHandler());
+ if (atmosphereAvailable) {
+ handlers.add(new PushRequestHandler(this));
+ }
+ return handlers;
+ }
+
+ /**
+ * Retrieves a reference to the servlet associated with this service.
+ *
+ * @return A reference to the VaadinServlet this service is using
+ */
+ public VaadinServlet getServlet() {
return servlet;
}
@@ -127,12 +179,11 @@ public class VaadinServletService extends VaadinService {
@Override
protected boolean requestCanCreateSession(VaadinRequest request) {
- RequestType requestType = getRequestType(request);
- if (requestType == RequestType.BROWSER_DETAILS) {
+ if (ServletUIInitHandler.isUIInitRequest(request)) {
// This is the first request if you are embedding by writing the
// embedding code yourself
return true;
- } else if (requestType == RequestType.OTHER) {
+ } else if (isOtherRequest(request)) {
/*
* I.e URIs that are not RPC calls or static (theme) files.
*/
@@ -142,25 +193,16 @@ public class VaadinServletService extends VaadinService {
return false;
}
- /**
- * Gets the request type for the request.
- *
- * @param request
- * the request to get a request type for
- * @return the request type
- *
- * @deprecated As of 7.0. Will likely change or be removed in a future
- * version
- */
- @Deprecated
- protected RequestType getRequestType(VaadinRequest request) {
- RequestType type = (RequestType) request.getAttribute(RequestType.class
- .getName());
- if (type == null) {
- type = getServlet().getRequestType((VaadinServletRequest) request);
- request.setAttribute(RequestType.class.getName(), type);
- }
- return type;
+ private boolean isOtherRequest(VaadinRequest request) {
+ // TODO This should be refactored in some way. It should not be
+ // necessary to check all these types.
+ return (!ServletPortletHelper.isAppRequest(request)
+ && !ServletUIInitHandler.isUIInitRequest(request)
+ && !ServletPortletHelper.isFileUploadRequest(request)
+ && !ServletPortletHelper.isHeartbeatRequest(request)
+ && !ServletPortletHelper.isPublishedFileRequest(request)
+ && !ServletPortletHelper.isUIDLRequest(request) && !ServletPortletHelper
+ .isPushRequest(request));
}
@Override
@@ -169,12 +211,6 @@ public class VaadinServletService extends VaadinService {
return getServlet().getApplicationUrl((VaadinServletRequest) request);
}
- @Override
- protected AbstractCommunicationManager createCommunicationManager(
- VaadinSession session) {
- return new CommunicationManager(session);
- }
-
public static HttpServletRequest getCurrentServletRequest() {
VaadinRequest currentRequest = VaadinService.getCurrentRequest();
if (currentRequest instanceof VaadinServletRequest) {
@@ -194,6 +230,18 @@ public class VaadinServletService extends VaadinService {
}
@Override
+ public InputStream getThemeResourceAsStream(UI uI, String themeName,
+ String resource) {
+ VaadinServletService service = (VaadinServletService) uI.getSession()
+ .getService();
+ ServletContext servletContext = service.getServlet()
+ .getServletContext();
+ return servletContext.getResourceAsStream("/"
+ + VaadinServlet.THEME_DIR_PATH + '/' + themeName + "/"
+ + resource);
+ }
+
+ @Override
public String getMainDivId(VaadinSession session, VaadinRequest request,
Class<? extends UI> uiClass) {
String appId = null;
@@ -220,4 +268,22 @@ public class VaadinServletService extends VaadinService {
appId = appId + "-" + hashCode;
return appId;
}
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(VaadinServletService.class.getName());
+ }
+
+ @Override
+ public boolean ensurePushAvailable() {
+ if (atmosphereAvailable) {
+ return true;
+ } else {
+ if (!pushWarningLogged) {
+ pushWarningLogged = true;
+ getLogger().log(Level.WARNING,
+ Constants.ATMOSPHERE_MISSING_ERROR);
+ }
+ return false;
+ }
+ }
}
diff --git a/server/src/com/vaadin/server/VaadinSession.java b/server/src/com/vaadin/server/VaadinSession.java
index d3619ebabf..317ea6cf7b 100644
--- a/server/src/com/vaadin/server/VaadinSession.java
+++ b/server/src/com/vaadin/server/VaadinSession.java
@@ -25,6 +25,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.UUID;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;
@@ -39,6 +40,7 @@ import com.vaadin.data.util.converter.Converter;
import com.vaadin.data.util.converter.ConverterFactory;
import com.vaadin.data.util.converter.DefaultConverterFactory;
import com.vaadin.event.EventRouter;
+import com.vaadin.shared.communication.PushMode;
import com.vaadin.ui.AbstractField;
import com.vaadin.ui.Table;
import com.vaadin.ui.UI;
@@ -73,8 +75,6 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
.findMethod(BootstrapListener.class, "modifyBootstrapPage",
BootstrapPageResponse.class);
- private final Lock lock = new ReentrantLock();
-
/**
* Configuration for the session.
*/
@@ -110,7 +110,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
protected WebBrowser browser = new WebBrowser();
- private AbstractCommunicationManager communicationManager;
+ private LegacyCommunicationManager communicationManager;
private long cumulativeRequestDuration = 0;
@@ -128,6 +128,8 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
private transient VaadinService service;
+ private transient Lock lock;
+
/**
* Create a new service session tied to a Vaadin service
*
@@ -162,6 +164,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
+ "This might happen if a session is deserialized but never used before it expires.");
} else if (VaadinService.getCurrentRequest() != null
&& getCurrent() == this) {
+ assert hasLock();
// Ignore if the session is being moved to a different backing
// session
if (getAttribute(VaadinService.REINITIALIZING_SESSION_MARKER) == Boolean.TRUE) {
@@ -192,6 +195,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
*/
@Deprecated
public WebBrowser getBrowser() {
+ assert hasLock();
return browser;
}
@@ -200,6 +204,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* milliseconds.
*/
public long getCumulativeRequestDuration() {
+ assert hasLock();
return cumulativeRequestDuration;
}
@@ -211,6 +216,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* The time spent in the last request, in milliseconds.
*/
public void setLastRequestDuration(long time) {
+ assert hasLock();
lastRequestDuration = time;
cumulativeRequestDuration += time;
}
@@ -220,6 +226,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* milliseconds.
*/
public long getLastRequestDuration() {
+ assert hasLock();
return lastRequestDuration;
}
@@ -232,6 +239,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
*
*/
public void setLastRequestTimestamp(long timestamp) {
+ assert hasLock();
lastRequestTimestamp = timestamp;
}
@@ -242,6 +250,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* the epoch.
*/
public long getLastRequestTimestamp() {
+ assert hasLock();
return lastRequestTimestamp;
}
@@ -252,6 +261,11 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* @return the wrapped session for this context
*/
public WrappedSession getSession() {
+ /*
+ * This is used to fetch the underlying session and there is no need for
+ * having a lock when doing this. On the contrary this is sometimes done
+ * to be able to lock the session.
+ */
return session;
}
@@ -262,65 +276,97 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* version
*/
@Deprecated
- public AbstractCommunicationManager getCommunicationManager() {
+ public LegacyCommunicationManager getCommunicationManager() {
+ assert hasLock();
return communicationManager;
}
/**
+ * Loads the VaadinSession for the given service and WrappedSession from the
+ * HTTP session.
+ *
* @param service
- * TODO
+ * The service the VaadinSession is associated with
* @param underlyingSession
- * @return
- *
- * @deprecated As of 7.0. Will likely change or be removed in a future
- * version
+ * The wrapped HTTP session for the user
+ * @return A VaadinSession instance for the service, session combination or
+ * null if none was found.
+ * @deprecated As of 7.0. Should be moved to a separate session storage
+ * class some day.
*/
@Deprecated
public static VaadinSession getForSession(VaadinService service,
WrappedSession underlyingSession) {
- Object attribute = underlyingSession.getAttribute(VaadinSession.class
- .getName() + "." + service.getServiceName());
- if (attribute instanceof VaadinSession) {
- VaadinSession vaadinSession = (VaadinSession) attribute;
- vaadinSession.session = underlyingSession;
- vaadinSession.service = service;
- return vaadinSession;
+ assert hasLock(service, underlyingSession);
+
+ VaadinSession vaadinSession = (VaadinSession) underlyingSession
+ .getAttribute(getSessionAttributeName(service));
+ if (vaadinSession == null) {
+ return null;
}
- return null;
+ vaadinSession.session = underlyingSession;
+ vaadinSession.service = service;
+ vaadinSession.refreshLock();
+ return vaadinSession;
}
/**
+ * Removes this VaadinSession from the HTTP session.
*
* @param service
- * TODO
- * @deprecated As of 7.0. Will likely change or be removed in a future
- * version
+ * The service this session is associated with
+ * @deprecated As of 7.0. Should be moved to a separate session storage
+ * class some day.
*/
@Deprecated
public void removeFromSession(VaadinService service) {
- assert (getForSession(service, session) == this);
- session.setAttribute(
- VaadinSession.class.getName() + "." + service.getServiceName(),
- null);
+ assert hasLock();
+ session.removeAttribute(getSessionAttributeName(service));
}
/**
- * @param session
+ * Retrieves the name of the attribute used for storing a VaadinSession for
+ * the given service.
*
- * @deprecated As of 7.0. Will likely change or be removed in a future
- * version
+ * @param service
+ * The service associated with the sessio
+ * @return The attribute name used for storing the session
+ */
+ private static String getSessionAttributeName(VaadinService service) {
+ return VaadinSession.class.getName() + "." + service.getServiceName();
+ }
+
+ /**
+ * Stores this VaadinSession in the HTTP session.
+ *
+ * @param service
+ * The service this session is associated with
+ * @param session
+ * The HTTP session this VaadinSession should be stored in
+ * @deprecated As of 7.0. Should be moved to a separate session storage
+ * class some day.
*/
@Deprecated
public void storeInSession(VaadinService service, WrappedSession session) {
- session.setAttribute(
- VaadinSession.class.getName() + "." + service.getServiceName(),
- this);
+ assert hasLock(service, session);
+ session.setAttribute(getSessionAttributeName(service), this);
this.session = session;
+ refreshLock();
+ }
+
+ /**
+ * Updates the transient session lock from VaadinService.
+ */
+ private void refreshLock() {
+ assert lock == null || lock == service.getSessionLock(session) : "Cannot change the lock from one instance to another";
+ assert hasLock(service, session);
+ lock = service.getSessionLock(session);
}
public void setCommunicationManager(
- AbstractCommunicationManager communicationManager) {
+ LegacyCommunicationManager communicationManager) {
+ assert hasLock();
if (communicationManager == null) {
throw new IllegalArgumentException("Can not set to null");
}
@@ -329,6 +375,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
}
public void setConfiguration(DeploymentConfiguration configuration) {
+ assert hasLock();
if (configuration == null) {
throw new IllegalArgumentException("Can not set to null");
}
@@ -342,6 +389,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* @return the deployment configuration
*/
public DeploymentConfiguration getConfiguration() {
+ assert hasLock();
return configuration;
}
@@ -354,6 +402,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* @return the locale of this session.
*/
public Locale getLocale() {
+ assert hasLock();
if (locale != null) {
return locale;
}
@@ -371,6 +420,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
*
*/
public void setLocale(Locale locale) {
+ assert hasLock();
this.locale = locale;
}
@@ -380,6 +430,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* @return the current error handler
*/
public ErrorHandler getErrorHandler() {
+ assert hasLock();
return errorHandler;
}
@@ -389,6 +440,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* @param errorHandler
*/
public void setErrorHandler(ErrorHandler errorHandler) {
+ assert hasLock();
this.errorHandler = errorHandler;
}
@@ -401,6 +453,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* @return The converter factory used in the session
*/
public ConverterFactory getConverterFactory() {
+ assert hasLock();
return converterFactory;
}
@@ -426,6 +479,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* The converter factory used in the session
*/
public void setConverterFactory(ConverterFactory converterFactory) {
+ assert hasLock();
this.converterFactory = converterFactory;
}
@@ -446,6 +500,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* @since 7.0
*/
public void addRequestHandler(RequestHandler handler) {
+ assert hasLock();
requestHandlers.addFirst(handler);
}
@@ -458,6 +513,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* @since 7.0
*/
public void removeRequestHandler(RequestHandler handler) {
+ assert hasLock();
requestHandlers.remove(handler);
}
@@ -475,6 +531,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* @since 7.0
*/
public Collection<RequestHandler> getRequestHandlers() {
+ assert hasLock();
return Collections.unmodifiableCollection(requestHandlers);
}
@@ -528,11 +585,14 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* @since 7.0
*/
public Collection<UI> getUIs() {
+ assert hasLock();
return Collections.unmodifiableCollection(uIs.values());
}
private int connectorIdSequence = 0;
+ private final String csrfToken = UUID.randomUUID().toString();
+
/**
* Generate an id for the given Connector. Connectors must not call this
* method more than once, the first time they need an id.
@@ -546,6 +606,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
*/
@Deprecated
public String createConnectorId(ClientConnector connector) {
+ assert hasLock();
return String.valueOf(connectorIdSequence++);
}
@@ -560,10 +621,32 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* @return The UI with the given id or null if not found
*/
public UI getUIById(int uiId) {
+ assert hasLock();
return uIs.get(uiId);
}
/**
+ * Checks if the current thread has exclusive access to this VaadinSession
+ *
+ * @return true if the thread has exclusive access, false otherwise
+ */
+ public boolean hasLock() {
+ ReentrantLock l = ((ReentrantLock) getLockInstance());
+ return l.isHeldByCurrentThread();
+ }
+
+ /**
+ * Checks if the current thread has exclusive access to the given
+ * WrappedSession.
+ *
+ * @return true if this thread has exclusive access, false otherwise
+ */
+ private static boolean hasLock(VaadinService service, WrappedSession session) {
+ ReentrantLock l = (ReentrantLock) service.getSessionLock(session);
+ return l.isHeldByCurrentThread();
+ }
+
+ /**
* Adds a listener that will be invoked when the bootstrap HTML is about to
* be generated. This can be used to modify the contents of the HTML that
* loads the Vaadin application in the browser and the HTTP headers that are
@@ -576,6 +659,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* the bootstrap listener to add
*/
public void addBootstrapListener(BootstrapListener listener) {
+ assert hasLock();
eventRouter.addListener(BootstrapFragmentResponse.class, listener,
BOOTSTRAP_FRAGMENT_METHOD);
eventRouter.addListener(BootstrapPageResponse.class, listener,
@@ -591,6 +675,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* the bootstrap listener to remove
*/
public void removeBootstrapListener(BootstrapListener listener) {
+ assert hasLock();
eventRouter.removeListener(BootstrapFragmentResponse.class, listener,
BOOTSTRAP_FRAGMENT_METHOD);
eventRouter.removeListener(BootstrapPageResponse.class, listener,
@@ -611,6 +696,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
*/
@Deprecated
public void modifyBootstrapResponse(BootstrapResponse response) {
+ assert hasLock();
eventRouter.fireEvent(response);
}
@@ -622,6 +708,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* the UI to remove
*/
public void removeUI(UI ui) {
+ assert hasLock();
int id = ui.getUIId();
ui.setSession(null);
uIs.remove(id);
@@ -646,6 +733,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* @since 7.0.0
*/
public GlobalResourceHandler getGlobalResourceHandler(boolean createOnDemand) {
+ assert hasLock();
if (globalResourceHandler == null && createOnDemand) {
globalResourceHandler = new GlobalResourceHandler();
addRequestHandler(globalResourceHandler);
@@ -677,9 +765,24 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
/**
* Locks this session to protect its data from concurrent access. Accessing
* the UI state from outside the normal request handling should always lock
- * the session and unlock it when done. To ensure that the lock is always
- * released, you should typically wrap the code in a <code>try</code> block
- * and unlock the session in <code>finally</code>:
+ * the session and unlock it when done. The preferred way to ensure locking
+ * is done correctly is to wrap your code using {@link UI#access(Runnable)}
+ * (or {@link VaadinSession#access(Runnable)} if you are only touching the
+ * session and not any UI), e.g.:
+ *
+ * <pre>
+ * myUI.access(new Runnable() {
+ * &#064;Override
+ * public void run() {
+ * // Here it is safe to update the UI.
+ * // UI.getCurrent can also be used
+ * myUI.getContent().setCaption(&quot;Changed safely&quot;);
+ * }
+ * });
+ * </pre>
+ *
+ * If you for whatever reason want to do locking manually, you should do it
+ * like:
*
* <pre>
* session.lock();
@@ -689,7 +792,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* session.unlock();
* }
* </pre>
- * <p>
+ *
* This method will block until the lock can be retrieved.
* <p>
* {@link #getLockInstance()} can be used if more control over the locking
@@ -697,6 +800,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
*
* @see #unlock()
* @see #getLockInstance()
+ * @see #hasLock()
*/
public void lock() {
getLockInstance().lock();
@@ -705,11 +809,29 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
/**
* Unlocks this session. This method should always be used in a finally
* block after {@link #lock()} to ensure that the lock is always released.
+ * <p>
+ * If {@link #getPushMode() the push mode} is {@link PushMode#AUTOMATIC
+ * automatic}, pushes the changes in all UIs in this session to their
+ * respective clients.
*
- * @see #unlock()
+ * @see #lock()
+ * @see UI#push()
*/
public void unlock() {
- getLockInstance().unlock();
+ assert hasLock();
+ try {
+ if (((ReentrantLock) getLockInstance()).getHoldCount() == 1) {
+ // Only push if the reentrant lock will actually be released by
+ // this unlock() invocation.
+ for (UI ui : getUIs()) {
+ if (ui.getPushMode() == PushMode.AUTOMATIC) {
+ ui.push();
+ }
+ }
+ }
+ } finally {
+ getLockInstance().unlock();
+ }
}
/**
@@ -728,6 +850,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* remove a previous association.
*/
public void setAttribute(String name, Object value) {
+ assert hasLock();
if (name == null) {
throw new IllegalArgumentException("name can not be null");
}
@@ -759,6 +882,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* remove a previous association.
*/
public <T> void setAttribute(Class<T> type, T value) {
+ assert hasLock();
if (type == null) {
throw new IllegalArgumentException("type can not be null");
}
@@ -783,6 +907,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* it has been set to null.
*/
public Object getAttribute(String name) {
+ assert hasLock();
if (name == null) {
throw new IllegalArgumentException("name can not be null");
}
@@ -808,6 +933,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* it has been set to null.
*/
public <T> T getAttribute(Class<T> type) {
+ assert hasLock();
if (type == null) {
throw new IllegalArgumentException("type can not be null");
}
@@ -825,6 +951,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* @return a unique UI id
*/
public int getNextUIid() {
+ assert hasLock();
return nextUIId++;
}
@@ -838,6 +965,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* @return the mapping between window names and UI ids for this session.
*/
public Map<String, Integer> getPreserveOnRefreshUIs() {
+ assert hasLock();
return retainOnRefreshUIs;
}
@@ -848,6 +976,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* the initialized UI to add.
*/
public void addUI(UI ui) {
+ assert hasLock();
if (ui.getUIId() == -1) {
throw new IllegalArgumentException(
"Can not add an UI that has not been initialized.");
@@ -867,6 +996,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* the UI provider that should be added
*/
public void addUIProvider(UIProvider uiProvider) {
+ assert hasLock();
uiProviders.addFirst(uiProvider);
}
@@ -877,6 +1007,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* the UI provider that should be removed
*/
public void removeUIProvider(UIProvider uiProvider) {
+ assert hasLock();
uiProviders.remove(uiProvider);
}
@@ -886,6 +1017,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
* @return an unmodifiable list of UI providers
*/
public List<UIProvider> getUIProviders() {
+ assert hasLock();
return Collections.unmodifiableList(uiProviders);
}
@@ -910,6 +1042,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
*
*/
public void close() {
+ assert hasLock();
closing = true;
}
@@ -918,13 +1051,84 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
*
* @see #close()
*
- * @return
+ * @return true if this session is marked to be closed, false otherwise
*/
public boolean isClosing() {
+ assert hasLock();
return closing;
}
private static final Logger getLogger() {
return Logger.getLogger(VaadinSession.class.getName());
}
+
+ /**
+ * Provides exclusive access to this session from outside a request handling
+ * thread.
+ * <p>
+ * The given runnable is executed while holding the session lock to ensure
+ * exclusive access to this session. The session and related thread locals
+ * are set properly before executing the runnable.
+ * </p>
+ * <p>
+ * RPC handlers for components inside this session do not need this method
+ * as the session is automatically locked by the framework during request
+ * handling.
+ * </p>
+ * <p>
+ * Note that calling this method while another session is locked by the
+ * current thread will cause an exception. This is to prevent deadlock
+ * situations when two threads have locked one session each and are both
+ * waiting for the lock for the other session.
+ * </p>
+ *
+ * @param runnable
+ * the runnable which accesses the session
+ *
+ * @throws IllegalStateException
+ * if the current thread holds the lock for another session
+ *
+ *
+ * @see #lock()
+ * @see #getCurrent()
+ * @see UI#access(Runnable)
+ */
+ public void access(Runnable runnable) {
+ VaadinService.verifyNoOtherSessionLocked(this);
+
+ Map<Class<?>, CurrentInstance> old = null;
+ lock();
+ try {
+ old = CurrentInstance.setThreadLocals(this);
+ runnable.run();
+ } finally {
+ unlock();
+ if (old != null) {
+ CurrentInstance.restoreThreadLocals(old);
+ }
+ }
+
+ }
+
+ /**
+ * @deprecated As of 7.1.0.beta1, use {@link #access(Runnable)} instead.
+ * This method will be removed before the final 7.1.0 release.
+ */
+ @Deprecated
+ public void runSafely(Runnable runnable) {
+ access(runnable);
+ }
+
+ /**
+ * Gets the CSRF token (aka double submit cookie) that is used to protect
+ * against Cross Site Request Forgery attacks.
+ *
+ * @since 7.1
+ * @return the csrf token string
+ */
+ public String getCsrfToken() {
+ assert hasLock();
+ return csrfToken;
+ }
+
}
diff --git a/server/src/com/vaadin/server/WebBrowser.java b/server/src/com/vaadin/server/WebBrowser.java
index 4122f053ae..8038bbc207 100644
--- a/server/src/com/vaadin/server/WebBrowser.java
+++ b/server/src/com/vaadin/server/WebBrowser.java
@@ -319,6 +319,17 @@ public class WebBrowser implements Serializable {
* entirely accurate due to varying network latencies, but should provide a
* close-enough value for most cases. Also note that the returned Date
* object uses servers default time zone, not the clients.
+ * <p>
+ * To get the actual date and time shown in the end users computer, you can
+ * do something like:
+ *
+ * <pre>
+ * WebBrowser browser = ...;
+ * SimpleTimeZone timeZone = new SimpleTimeZone(browser.getTimezoneOffset(), "Fake client time zone");
+ * DateFormat format = DateFormat.getDateTimeInstance();
+ * format.setTimeZone(timeZone);
+ * myLabel.setValue(format.format(browser.getCurrentDate()));
+ * </pre>
*
* @return the current date and time of the browser.
* @see #isDSTInEffect()
@@ -413,7 +424,7 @@ public class WebBrowser implements Serializable {
* @param request
* the Vaadin request to read the information from
*/
- void updateRequestDetails(VaadinRequest request) {
+ public void updateRequestDetails(VaadinRequest request) {
locale = request.getLocale();
address = request.getRemoteAddr();
secureConnection = request.isSecure();
diff --git a/server/src/com/vaadin/server/AbstractStreamingEvent.java b/server/src/com/vaadin/server/communication/AbstractStreamingEvent.java
index b7bf4e042f..b97a60fd56 100644
--- a/server/src/com/vaadin/server/AbstractStreamingEvent.java
+++ b/server/src/com/vaadin/server/communication/AbstractStreamingEvent.java
@@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package com.vaadin.server;
+package com.vaadin.server.communication;
import com.vaadin.server.StreamVariable.StreamingEvent;
diff --git a/server/src/com/vaadin/server/communication/AtmospherePushConnection.java b/server/src/com/vaadin/server/communication/AtmospherePushConnection.java
new file mode 100644
index 0000000000..0bba65ff1d
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/AtmospherePushConnection.java
@@ -0,0 +1,247 @@
+/*
+ * 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.io.IOException;
+import java.io.Reader;
+import java.io.Serializable;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.atmosphere.cpr.AtmosphereResource;
+import org.atmosphere.cpr.AtmosphereResource.TRANSPORT;
+import org.json.JSONException;
+
+import com.vaadin.shared.ApplicationConstants;
+import com.vaadin.ui.UI;
+
+/**
+ * {@link PushConnection} implementation using the Atmosphere push support that
+ * is by default included in Vaadin.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class AtmospherePushConnection implements Serializable, PushConnection {
+
+ /**
+ * Represents a message that can arrive as multiple fragments.
+ */
+ protected static class FragmentedMessage {
+ private final StringBuilder message = new StringBuilder();
+ private final int messageLength;
+
+ public FragmentedMessage(Reader reader) throws IOException {
+ // Messages are prefixed by the total message length plus '|'
+ String length = "";
+ int c;
+ while ((c = reader.read()) != -1
+ && c != ApplicationConstants.WEBSOCKET_MESSAGE_DELIMITER) {
+ length += (char) c;
+ }
+ try {
+ messageLength = Integer.parseInt(length);
+ } catch (NumberFormatException e) {
+ throw new IOException("Invalid message length " + length, e);
+ }
+ }
+
+ /**
+ * Appends all the data from the given Reader to this message and
+ * returns whether the message was completed.
+ *
+ * @param reader
+ * The Reader from which to read.
+ * @return true if this message is complete, false otherwise.
+ * @throws IOException
+ */
+ public boolean append(Reader reader) throws IOException {
+ char[] buffer = new char[ApplicationConstants.WEBSOCKET_BUFFER_SIZE];
+ int read;
+ while ((read = reader.read(buffer)) != -1) {
+ message.append(buffer, 0, read);
+ assert message.length() <= messageLength : "Received message "
+ + message.length() + "chars, expected " + messageLength;
+ }
+ return message.length() == messageLength;
+ }
+
+ public Reader getReader() {
+ return new StringReader(message.toString());
+ }
+ }
+
+ private UI ui;
+ private transient AtmosphereResource resource;
+ private transient Future<String> outgoingMessage;
+ private transient FragmentedMessage incomingMessage;
+
+ public AtmospherePushConnection(UI ui) {
+ this.ui = ui;
+ }
+
+ @Override
+ public void push() {
+ assert isConnected();
+ try {
+ push(true);
+ } catch (IOException e) {
+ // TODO Error handling
+ throw new RuntimeException("Push failed", e);
+ }
+ }
+
+ /**
+ * Pushes pending state changes and client RPC calls to the client.
+ *
+ * @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, false, async);
+ } catch (JSONException e) {
+ throw new IOException("Error writing UIDL", e);
+ }
+ sendMessage("for(;;);[{" + writer.toString() + "}]");
+ }
+
+ /**
+ * Sends the given message to the current client.
+ *
+ * @param message
+ * The message to send
+ */
+ void sendMessage(String message) {
+ // "Broadcast" the changes to the single client only
+ outgoingMessage = getResource().getBroadcaster().broadcast(message,
+ getResource());
+ }
+
+ /**
+ * Reads and buffers a (possibly partial) message. If a complete message was
+ * received, or if the call resulted in the completion of a partially
+ * received message, returns a {@link Reader} yielding the complete message.
+ * Otherwise, returns null.
+ *
+ * @param reader
+ * A Reader from which to read the (partial) message
+ * @return A Reader yielding a complete message or null if the message is
+ * not yet complete.
+ * @throws IOException
+ */
+ protected Reader receiveMessage(Reader reader) throws IOException {
+
+ if (resource.transport() != TRANSPORT.WEBSOCKET) {
+ return reader;
+ }
+
+ if (incomingMessage == null) {
+ // No existing partially received message
+ incomingMessage = new FragmentedMessage(reader);
+ }
+
+ if (incomingMessage.append(reader)) {
+ // Message is complete
+ Reader completeReader = incomingMessage.getReader();
+ incomingMessage = null;
+ return completeReader;
+ } else {
+ // Only received a partial message
+ return null;
+ }
+ }
+
+ /**
+ * Associates this connection with the given AtmosphereResource. If there is
+ * a push pending, commits it.
+ *
+ * @param resource
+ * The AtmosphereResource representing the push channel.
+ * @throws IOException
+ */
+ protected void connect(AtmosphereResource resource) throws IOException {
+ this.resource = resource;
+ }
+
+ /**
+ * Returns whether this connection is currently open.
+ */
+ @Override
+ public boolean isConnected() {
+ return resource != null
+ && resource.getBroadcaster().getAtmosphereResources()
+ .contains(resource);
+ }
+
+ /**
+ * @return the UI associated with this connection.
+ */
+ protected UI getUI() {
+ return ui;
+ }
+
+ /**
+ * @return The AtmosphereResource associated with this connection or null if
+ * connection not open.
+ */
+ protected AtmosphereResource getResource() {
+ return resource;
+ }
+
+ @Override
+ public void disconnect() {
+ if (outgoingMessage != null) {
+ // Wait for the last message to be sent before closing the
+ // connection (assumes that futures are completed in order)
+ try {
+ outgoingMessage.get(1000, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ getLogger()
+ .log(Level.INFO,
+ "Timeout waiting for messages to be sent to client before disconnect");
+ } catch (Exception e) {
+ getLogger()
+ .log(Level.INFO,
+ "Error waiting for messages to be sent to client before disconnect");
+ }
+ outgoingMessage = null;
+ }
+
+ resource.resume();
+ assert !resource.getBroadcaster().getAtmosphereResources()
+ .contains(resource);
+ resource = null;
+ }
+
+ /**
+ * @since
+ * @return
+ */
+ private static Logger getLogger() {
+ return Logger.getLogger(AtmospherePushConnection.class.getName());
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/ClientRpcWriter.java b/server/src/com/vaadin/server/communication/ClientRpcWriter.java
new file mode 100644
index 0000000000..285adac7a5
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/ClientRpcWriter.java
@@ -0,0 +1,141 @@
+/*
+ * 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.io.IOException;
+import java.io.Serializable;
+import java.io.Writer;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import com.vaadin.server.ClientConnector;
+import com.vaadin.server.ClientMethodInvocation;
+import com.vaadin.server.EncodeResult;
+import com.vaadin.server.JsonCodec;
+import com.vaadin.server.PaintException;
+import com.vaadin.shared.communication.ClientRpc;
+import com.vaadin.ui.UI;
+
+/**
+ * Serializes {@link ClientRpc client RPC} invocations to JSON.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class ClientRpcWriter implements Serializable {
+
+ /**
+ * Writes a JSON object containing all pending client RPC invocations in the
+ * given UI.
+ *
+ * @param ui
+ * The {@link UI} whose RPC calls to write.
+ * @param writer
+ * The {@link Writer} used to write the JSON.
+ * @throws IOException
+ * If the serialization fails.
+ */
+ public void write(UI ui, Writer writer) throws IOException {
+
+ Collection<ClientMethodInvocation> pendingInvocations = collectPendingRpcCalls(ui
+ .getConnectorTracker().getDirtyVisibleConnectors());
+
+ JSONArray rpcCalls = new JSONArray();
+ for (ClientMethodInvocation invocation : pendingInvocations) {
+ // add invocation to rpcCalls
+ try {
+ JSONArray invocationJson = new JSONArray();
+ invocationJson.put(invocation.getConnector().getConnectorId());
+ invocationJson.put(invocation.getInterfaceName());
+ invocationJson.put(invocation.getMethodName());
+ JSONArray paramJson = new JSONArray();
+ for (int i = 0; i < invocation.getParameterTypes().length; ++i) {
+ Type parameterType = invocation.getParameterTypes()[i];
+ Object referenceParameter = null;
+ // TODO Use default values for RPC parameter types
+ // if (!JsonCodec.isInternalType(parameterType)) {
+ // try {
+ // referenceParameter = parameterType.newInstance();
+ // } catch (Exception e) {
+ // logger.log(Level.WARNING,
+ // "Error creating reference object for parameter of type "
+ // + parameterType.getName());
+ // }
+ // }
+ EncodeResult encodeResult = JsonCodec.encode(
+ invocation.getParameters()[i], referenceParameter,
+ parameterType, ui.getConnectorTracker());
+ paramJson.put(encodeResult.getEncodedValue());
+ }
+ invocationJson.put(paramJson);
+ rpcCalls.put(invocationJson);
+ } catch (JSONException e) {
+ throw new PaintException(
+ "Failed to serialize RPC method call parameters for connector "
+ + invocation.getConnector().getConnectorId()
+ + " method " + invocation.getInterfaceName()
+ + "." + invocation.getMethodName() + ": "
+ + e.getMessage(), e);
+ }
+ }
+ writer.write(rpcCalls.toString());
+ }
+
+ /**
+ * Collects all pending RPC calls from listed {@link ClientConnector}s and
+ * clears their RPC queues.
+ *
+ * @param rpcPendingQueue
+ * list of {@link ClientConnector} of interest
+ * @return ordered list of pending RPC calls
+ */
+ private Collection<ClientMethodInvocation> collectPendingRpcCalls(
+ Collection<ClientConnector> rpcPendingQueue) {
+ List<ClientMethodInvocation> pendingInvocations = new ArrayList<ClientMethodInvocation>();
+ for (ClientConnector connector : rpcPendingQueue) {
+ List<ClientMethodInvocation> paintablePendingRpc = connector
+ .retrievePendingRpcCalls();
+ if (null != paintablePendingRpc && !paintablePendingRpc.isEmpty()) {
+ List<ClientMethodInvocation> oldPendingRpc = pendingInvocations;
+ int totalCalls = pendingInvocations.size()
+ + paintablePendingRpc.size();
+ pendingInvocations = new ArrayList<ClientMethodInvocation>(
+ totalCalls);
+
+ // merge two ordered comparable lists
+ for (int destIndex = 0, oldIndex = 0, paintableIndex = 0; destIndex < totalCalls; destIndex++) {
+ if (paintableIndex >= paintablePendingRpc.size()
+ || (oldIndex < oldPendingRpc.size() && ((Comparable<ClientMethodInvocation>) oldPendingRpc
+ .get(oldIndex))
+ .compareTo(paintablePendingRpc
+ .get(paintableIndex)) <= 0)) {
+ pendingInvocations.add(oldPendingRpc.get(oldIndex++));
+ } else {
+ pendingInvocations.add(paintablePendingRpc
+ .get(paintableIndex++));
+ }
+ }
+ }
+ }
+ return pendingInvocations;
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/ConnectorHierarchyWriter.java b/server/src/com/vaadin/server/communication/ConnectorHierarchyWriter.java
new file mode 100644
index 0000000000..467bddbdce
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/ConnectorHierarchyWriter.java
@@ -0,0 +1,81 @@
+/*
+ * 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.io.IOException;
+import java.io.Serializable;
+import java.io.Writer;
+import java.util.Collection;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import com.vaadin.server.AbstractClientConnector;
+import com.vaadin.server.ClientConnector;
+import com.vaadin.server.LegacyCommunicationManager;
+import com.vaadin.server.PaintException;
+import com.vaadin.ui.UI;
+
+/**
+ * Serializes a connector hierarchy to JSON.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class ConnectorHierarchyWriter implements Serializable {
+
+ /**
+ * Writes a JSON object containing the connector hierarchy (parent-child
+ * mappings) of the dirty connectors in the given UI.
+ *
+ * @param ui
+ * The {@link UI} whose hierarchy to write.
+ * @param writer
+ * The {@link Writer} used to write the JSON.
+ * @throws IOException
+ * If the serialization fails.
+ */
+ public void write(UI ui, Writer writer) throws IOException {
+
+ Collection<ClientConnector> dirtyVisibleConnectors = ui
+ .getConnectorTracker().getDirtyVisibleConnectors();
+
+ JSONObject hierarchyInfo = new JSONObject();
+ for (ClientConnector connector : dirtyVisibleConnectors) {
+ String connectorId = connector.getConnectorId();
+ JSONArray children = new JSONArray();
+
+ for (ClientConnector child : AbstractClientConnector
+ .getAllChildrenIterable(connector)) {
+ if (LegacyCommunicationManager
+ .isConnectorVisibleToClient(child)) {
+ children.put(child.getConnectorId());
+ }
+ }
+ try {
+ hierarchyInfo.put(connectorId, children);
+ } catch (JSONException e) {
+ throw new PaintException(
+ "Failed to send hierarchy information about "
+ + connectorId + " to the client: "
+ + e.getMessage(), e);
+ }
+ }
+ writer.write(hierarchyInfo.toString());
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/ConnectorTypeWriter.java b/server/src/com/vaadin/server/communication/ConnectorTypeWriter.java
new file mode 100644
index 0000000000..eaa1c83ff2
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/ConnectorTypeWriter.java
@@ -0,0 +1,73 @@
+/*
+ * 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.io.IOException;
+import java.io.Serializable;
+import java.io.Writer;
+import java.util.Collection;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import com.vaadin.server.ClientConnector;
+import com.vaadin.server.PaintException;
+import com.vaadin.server.PaintTarget;
+import com.vaadin.ui.UI;
+
+/**
+ * Serializes connector type mappings to JSON.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class ConnectorTypeWriter implements Serializable {
+
+ /**
+ * Writes a JSON object containing connector-ID-to-type-ID mappings for each
+ * dirty Connector in the given UI.
+ *
+ * @param ui
+ * The {@link UI} containing dirty connectors
+ * @param writer
+ * The {@link Writer} used to write the JSON.
+ * @param target
+ * The paint target containing the connector type IDs.
+ * @throws IOException
+ * If the serialization fails.
+ */
+ public void write(UI ui, Writer writer, PaintTarget target)
+ throws IOException {
+
+ Collection<ClientConnector> dirtyVisibleConnectors = ui
+ .getConnectorTracker().getDirtyVisibleConnectors();
+
+ JSONObject connectorTypes = new JSONObject();
+ for (ClientConnector connector : dirtyVisibleConnectors) {
+ String connectorType = target.getTag(connector);
+ try {
+ connectorTypes.put(connector.getConnectorId(), connectorType);
+ } catch (JSONException e) {
+ throw new PaintException(
+ "Failed to send connector type for connector "
+ + connector.getConnectorId() + ": "
+ + e.getMessage(), e);
+ }
+ }
+ writer.write(connectorTypes.toString());
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/FileUploadHandler.java b/server/src/com/vaadin/server/communication/FileUploadHandler.java
new file mode 100644
index 0000000000..e875a4e861
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/FileUploadHandler.java
@@ -0,0 +1,645 @@
+/*
+ * 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.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+
+import com.vaadin.server.ClientConnector;
+import com.vaadin.server.NoInputStreamException;
+import com.vaadin.server.NoOutputStreamException;
+import com.vaadin.server.RequestHandler;
+import com.vaadin.server.ServletPortletHelper;
+import com.vaadin.server.StreamVariable;
+import com.vaadin.server.StreamVariable.StreamingEndEvent;
+import com.vaadin.server.StreamVariable.StreamingErrorEvent;
+import com.vaadin.server.UploadException;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.server.VaadinResponse;
+import com.vaadin.server.VaadinSession;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.UI;
+
+/**
+ * Handles a file upload request submitted via an Upload component.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class FileUploadHandler implements RequestHandler {
+
+ /**
+ * Stream that extracts content from another stream until the boundary
+ * string is encountered.
+ *
+ * Public only for unit tests, should be considered private for all other
+ * purposes.
+ */
+ public static class SimpleMultiPartInputStream extends InputStream {
+
+ /**
+ * Counter of how many characters have been matched to boundary string
+ * from the stream
+ */
+ int matchedCount = -1;
+
+ /**
+ * Used as pointer when returning bytes after partly matched boundary
+ * string.
+ */
+ int curBoundaryIndex = 0;
+ /**
+ * The byte found after a "promising start for boundary"
+ */
+ private int bufferedByte = -1;
+ private boolean atTheEnd = false;
+
+ private final char[] boundary;
+
+ private final InputStream realInputStream;
+
+ public SimpleMultiPartInputStream(InputStream realInputStream,
+ String boundaryString) {
+ boundary = (CRLF + DASHDASH + boundaryString).toCharArray();
+ this.realInputStream = realInputStream;
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (atTheEnd) {
+ // End boundary reached, nothing more to read
+ return -1;
+ } else if (bufferedByte >= 0) {
+ /* Purge partially matched boundary if there was such */
+ return getBuffered();
+ } else if (matchedCount != -1) {
+ /*
+ * Special case where last "failed" matching ended with first
+ * character from boundary string
+ */
+ return matchForBoundary();
+ } else {
+ int fromActualStream = realInputStream.read();
+ if (fromActualStream == -1) {
+ // unexpected end of stream
+ throw new IOException(
+ "The multipart stream ended unexpectedly");
+ }
+ if (boundary[0] == fromActualStream) {
+ /*
+ * If matches the first character in boundary string, start
+ * checking if the boundary is fetched.
+ */
+ return matchForBoundary();
+ }
+ return fromActualStream;
+ }
+ }
+
+ /**
+ * Reads the input to expect a boundary string. Expects that the first
+ * character has already been matched.
+ *
+ * @return -1 if the boundary was matched, else returns the first byte
+ * from boundary
+ * @throws IOException
+ */
+ private int matchForBoundary() throws IOException {
+ matchedCount = 0;
+ /*
+ * Going to "buffered mode". Read until full boundary match or a
+ * different character.
+ */
+ while (true) {
+ matchedCount++;
+ if (matchedCount == boundary.length) {
+ /*
+ * The whole boundary matched so we have reached the end of
+ * file
+ */
+ atTheEnd = true;
+ return -1;
+ }
+ int fromActualStream = realInputStream.read();
+ if (fromActualStream != boundary[matchedCount]) {
+ /*
+ * Did not find full boundary, cache the mismatching byte
+ * and start returning the partially matched boundary.
+ */
+ bufferedByte = fromActualStream;
+ return getBuffered();
+ }
+ }
+ }
+
+ /**
+ * Returns the partly matched boundary string and the byte following
+ * that.
+ *
+ * @return
+ * @throws IOException
+ */
+ private int getBuffered() throws IOException {
+ int b;
+ if (matchedCount == 0) {
+ // The boundary has been returned, return the buffered byte.
+ b = bufferedByte;
+ bufferedByte = -1;
+ matchedCount = -1;
+ } else {
+ b = boundary[curBoundaryIndex++];
+ if (curBoundaryIndex == matchedCount) {
+ // The full boundary has been returned, remaining is the
+ // char that did not match the boundary.
+
+ curBoundaryIndex = 0;
+ if (bufferedByte != boundary[0]) {
+ /*
+ * next call for getBuffered will return the
+ * bufferedByte that came after the partial boundary
+ * match
+ */
+ matchedCount = 0;
+ } else {
+ /*
+ * Special case where buffered byte again matches the
+ * boundaryString. This could be the start of the real
+ * end boundary.
+ */
+ matchedCount = 0;
+ bufferedByte = -1;
+ }
+ }
+ }
+ if (b == -1) {
+ throw new IOException("The multipart stream ended unexpectedly");
+ }
+ return b;
+ }
+ }
+
+ private static class UploadInterruptedException extends Exception {
+ public UploadInterruptedException() {
+ super("Upload interrupted by other thread");
+ }
+ }
+
+ private static final int LF = "\n".getBytes()[0];
+
+ private static final String CRLF = "\r\n";
+
+ private static final String UTF8 = "UTF-8";
+
+ private static final String DASHDASH = "--";
+
+ /* Same as in apache commons file upload library that was previously used. */
+ private static final int MAX_UPLOAD_BUFFER_SIZE = 4 * 1024;
+
+ @Override
+ public boolean handleRequest(VaadinSession session, VaadinRequest request,
+ VaadinResponse response) throws IOException {
+ if (!ServletPortletHelper.isFileUploadRequest(request)) {
+ return false;
+ }
+
+ /*
+ * URI pattern: APP/UPLOAD/[UIID]/[PID]/[NAME]/[SECKEY] See
+ * #createReceiverUrl
+ */
+
+ String pathInfo = request.getPathInfo();
+ // strip away part until the data we are interested starts
+ int startOfData = pathInfo
+ .indexOf(ServletPortletHelper.UPLOAD_URL_PREFIX)
+ + ServletPortletHelper.UPLOAD_URL_PREFIX.length();
+ String uppUri = pathInfo.substring(startOfData);
+ String[] parts = uppUri.split("/", 4); // 0= UIid, 1 = cid, 2= name, 3
+ // = sec key
+ String uiId = parts[0];
+ String connectorId = parts[1];
+ String variableName = parts[2];
+
+ // These are retrieved while session is locked
+ ClientConnector source;
+ StreamVariable streamVariable;
+
+ session.lock();
+ try {
+ UI uI = session.getUIById(Integer.parseInt(uiId));
+ UI.setCurrent(uI);
+
+ streamVariable = uI.getConnectorTracker().getStreamVariable(
+ connectorId, variableName);
+ String secKey = uI.getConnectorTracker().getSeckey(streamVariable);
+ if (!secKey.equals(parts[3])) {
+ // TODO Should rethink error handling
+ return true;
+ }
+
+ source = session.getCommunicationManager().getConnector(uI,
+ connectorId);
+ } finally {
+ session.unlock();
+ }
+
+ String contentType = request.getContentType();
+ if (contentType.contains("boundary")) {
+ // Multipart requests contain boundary string
+ doHandleSimpleMultipartFileUpload(session, request, response,
+ streamVariable, variableName, source,
+ contentType.split("boundary=")[1]);
+ } else {
+ // if boundary string does not exist, the posted file is from
+ // XHR2.post(File)
+ doHandleXhrFilePost(session, request, response, streamVariable,
+ variableName, source, request.getContentLength());
+ }
+ return true;
+ }
+
+ private static String readLine(InputStream stream) throws IOException {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ int readByte = stream.read();
+ while (readByte != LF) {
+ bout.write(readByte);
+ readByte = stream.read();
+ }
+ byte[] bytes = bout.toByteArray();
+ return new String(bytes, 0, bytes.length - 1, UTF8);
+ }
+
+ /**
+ * Method used to stream content from a multipart request (either from
+ * servlet or portlet request) to given StreamVariable.
+ * <p>
+ * This method takes care of locking the session as needed and does not
+ * assume the caller has locked the session. This allows the session to be
+ * locked only when needed and not when handling the upload data.
+ * </p>
+ *
+ * @param session
+ * The session containing the stream variable
+ * @param request
+ * The upload request
+ * @param response
+ * The upload response
+ * @param streamVariable
+ * The destination stream variable
+ * @param variableName
+ * The name of the destination stream variable
+ * @param owner
+ * The owner of the stream variable
+ * @param boundary
+ * The mime boundary used in the upload request
+ * @throws IOException
+ * If there is a problem reading the request or writing the
+ * response
+ */
+ protected void doHandleSimpleMultipartFileUpload(VaadinSession session,
+ VaadinRequest request, VaadinResponse response,
+ StreamVariable streamVariable, String variableName,
+ ClientConnector owner, String boundary) throws IOException {
+ // multipart parsing, supports only one file for request, but that is
+ // fine for our current terminal
+
+ final InputStream inputStream = request.getInputStream();
+
+ int contentLength = request.getContentLength();
+
+ boolean atStart = false;
+ boolean firstFileFieldFound = false;
+
+ String rawfilename = "unknown";
+ String rawMimeType = "application/octet-stream";
+
+ /*
+ * Read the stream until the actual file starts (empty line). Read
+ * filename and content type from multipart headers.
+ */
+ while (!atStart) {
+ String readLine = readLine(inputStream);
+ contentLength -= (readLine.getBytes(UTF8).length + CRLF.length());
+ if (readLine.startsWith("Content-Disposition:")
+ && readLine.indexOf("filename=") > 0) {
+ rawfilename = readLine.replaceAll(".*filename=", "");
+ char quote = rawfilename.charAt(0);
+ rawfilename = rawfilename.substring(1);
+ rawfilename = rawfilename.substring(0,
+ rawfilename.indexOf(quote));
+ firstFileFieldFound = true;
+ } else if (firstFileFieldFound && readLine.equals("")) {
+ atStart = true;
+ } else if (readLine.startsWith("Content-Type")) {
+ rawMimeType = readLine.split(": ")[1];
+ }
+ }
+
+ contentLength -= (boundary.length() + CRLF.length() + 2
+ * DASHDASH.length() + CRLF.length());
+
+ /*
+ * Reads bytes from the underlying stream. Compares the read bytes to
+ * the boundary string and returns -1 if met.
+ *
+ * The matching happens so that if the read byte equals to the first
+ * char of boundary string, the stream goes to "buffering mode". In
+ * buffering mode bytes are read until the character does not match the
+ * corresponding from boundary string or the full boundary string is
+ * found.
+ *
+ * Note, if this is someday needed elsewhere, don't shoot yourself to
+ * foot and split to a top level helper class.
+ */
+ InputStream simpleMultiPartReader = new SimpleMultiPartInputStream(
+ inputStream, boundary);
+
+ /*
+ * Should report only the filename even if the browser sends the path
+ */
+ final String filename = removePath(rawfilename);
+ final String mimeType = rawMimeType;
+
+ try {
+ handleFileUploadValidationAndData(session, simpleMultiPartReader,
+ streamVariable, filename, mimeType, contentLength, owner,
+ variableName);
+ } catch (UploadException e) {
+ session.getCommunicationManager().handleConnectorRelatedException(
+ owner, e);
+ }
+ sendUploadResponse(request, response);
+
+ }
+
+ private void handleFileUploadValidationAndData(VaadinSession session,
+ InputStream inputStream, StreamVariable streamVariable,
+ String filename, String mimeType, int contentLength,
+ ClientConnector connector, String variableName)
+ throws UploadException {
+ session.lock();
+ try {
+ if (connector == null) {
+ throw new UploadException(
+ "File upload ignored because the connector for the stream variable was not found");
+ }
+ if (!connector.isConnectorEnabled()) {
+ throw new UploadException("Warning: file upload ignored for "
+ + connector.getConnectorId()
+ + " because the component was disabled");
+ }
+ if ((connector instanceof Component)
+ && ((Component) connector).isReadOnly()) {
+ // Only checked for legacy reasons
+ throw new UploadException(
+ "File upload ignored because the component is read-only");
+ }
+ } finally {
+ session.unlock();
+ }
+ try {
+ boolean forgetVariable = streamToReceiver(session, inputStream,
+ streamVariable, filename, mimeType, contentLength);
+ if (forgetVariable) {
+ cleanStreamVariable(session, connector, variableName);
+ }
+ } catch (Exception e) {
+ session.lock();
+ try {
+ session.getCommunicationManager()
+ .handleConnectorRelatedException(connector, e);
+ } finally {
+ session.unlock();
+ }
+ }
+ }
+
+ /**
+ * Used to stream plain file post (aka XHR2.post(File))
+ * <p>
+ * This method takes care of locking the session as needed and does not
+ * assume the caller has locked the session. This allows the session to be
+ * locked only when needed and not when handling the upload data.
+ * </p>
+ *
+ * @param session
+ * The session containing the stream variable
+ * @param request
+ * The upload request
+ * @param response
+ * The upload response
+ * @param streamVariable
+ * The destination stream variable
+ * @param variableName
+ * The name of the destination stream variable
+ * @param owner
+ * The owner of the stream variable
+ * @param contentLength
+ * The length of the request content
+ * @throws IOException
+ * If there is a problem reading the request or writing the
+ * response
+ */
+ protected void doHandleXhrFilePost(VaadinSession session,
+ VaadinRequest request, VaadinResponse response,
+ StreamVariable streamVariable, String variableName,
+ ClientConnector owner, int contentLength) throws IOException {
+
+ // These are unknown in filexhr ATM, maybe add to Accept header that
+ // is accessible in portlets
+ final String filename = "unknown";
+ final String mimeType = filename;
+ final InputStream stream = request.getInputStream();
+
+ try {
+ handleFileUploadValidationAndData(session, stream, streamVariable,
+ filename, mimeType, contentLength, owner, variableName);
+ } catch (UploadException e) {
+ session.getCommunicationManager().handleConnectorRelatedException(
+ owner, e);
+ }
+ sendUploadResponse(request, response);
+ }
+
+ /**
+ * @param in
+ * @param streamVariable
+ * @param filename
+ * @param type
+ * @param contentLength
+ * @return true if the streamvariable has informed that the terminal can
+ * forget this variable
+ * @throws UploadException
+ */
+ protected final boolean streamToReceiver(VaadinSession session,
+ final InputStream in, StreamVariable streamVariable,
+ String filename, String type, int contentLength)
+ throws UploadException {
+ if (streamVariable == null) {
+ throw new IllegalStateException(
+ "StreamVariable for the post not found");
+ }
+
+ OutputStream out = null;
+ int totalBytes = 0;
+ StreamingStartEventImpl startedEvent = new StreamingStartEventImpl(
+ filename, type, contentLength);
+ try {
+ boolean listenProgress;
+ session.lock();
+ try {
+ streamVariable.streamingStarted(startedEvent);
+ out = streamVariable.getOutputStream();
+ listenProgress = streamVariable.listenProgress();
+ } finally {
+ session.unlock();
+ }
+
+ // Gets the output target stream
+ if (out == null) {
+ throw new NoOutputStreamException();
+ }
+
+ if (null == in) {
+ // No file, for instance non-existent filename in html upload
+ throw new NoInputStreamException();
+ }
+
+ final byte buffer[] = new byte[MAX_UPLOAD_BUFFER_SIZE];
+ int bytesReadToBuffer = 0;
+ while ((bytesReadToBuffer = in.read(buffer)) > 0) {
+ out.write(buffer, 0, bytesReadToBuffer);
+ totalBytes += bytesReadToBuffer;
+ if (listenProgress) {
+ // update progress if listener set and contentLength
+ // received
+ session.lock();
+ try {
+ StreamingProgressEventImpl progressEvent = new StreamingProgressEventImpl(
+ filename, type, contentLength, totalBytes);
+ streamVariable.onProgress(progressEvent);
+ } finally {
+ session.unlock();
+ }
+ }
+ if (streamVariable.isInterrupted()) {
+ throw new UploadInterruptedException();
+ }
+ }
+
+ // upload successful
+ out.close();
+ StreamingEndEvent event = new StreamingEndEventImpl(filename, type,
+ totalBytes);
+ session.lock();
+ try {
+ streamVariable.streamingFinished(event);
+ } finally {
+ session.unlock();
+ }
+
+ } catch (UploadInterruptedException e) {
+ // Download interrupted by application code
+ tryToCloseStream(out);
+ StreamingErrorEvent event = new StreamingErrorEventImpl(filename,
+ type, contentLength, totalBytes, e);
+ session.lock();
+ try {
+ streamVariable.streamingFailed(event);
+ } finally {
+ session.unlock();
+ }
+ // Note, we are not throwing interrupted exception forward as it is
+ // not a terminal level error like all other exception.
+ } catch (final Exception e) {
+ tryToCloseStream(out);
+ session.lock();
+ try {
+ StreamingErrorEvent event = new StreamingErrorEventImpl(
+ filename, type, contentLength, totalBytes, e);
+ streamVariable.streamingFailed(event);
+ // throw exception for terminal to be handled (to be passed to
+ // terminalErrorHandler)
+ throw new UploadException(e);
+ } finally {
+ session.unlock();
+ }
+ }
+ return startedEvent.isDisposed();
+ }
+
+ static void tryToCloseStream(OutputStream out) {
+ try {
+ // try to close output stream (e.g. file handle)
+ if (out != null) {
+ out.close();
+ }
+ } catch (IOException e1) {
+ // NOP
+ }
+ }
+
+ /**
+ * Removes any possible path information from the filename and returns the
+ * filename. Separators / and \\ are used.
+ *
+ * @param name
+ * @return
+ */
+ private static String removePath(String filename) {
+ if (filename != null) {
+ filename = filename.replaceAll("^.*[/\\\\]", "");
+ }
+
+ return filename;
+ }
+
+ /**
+ * TODO document
+ *
+ * @param request
+ * @param response
+ * @throws IOException
+ */
+ protected void sendUploadResponse(VaadinRequest request,
+ VaadinResponse response) throws IOException {
+ response.setContentType("text/html");
+ final OutputStream out = response.getOutputStream();
+ final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
+ new OutputStreamWriter(out, "UTF-8")));
+ outWriter.print("<html><body>download handled</body></html>");
+ outWriter.flush();
+ out.close();
+ }
+
+ private void cleanStreamVariable(VaadinSession session,
+ final ClientConnector owner, final String variableName) {
+ session.access(new Runnable() {
+ @Override
+ public void run() {
+ owner.getUI()
+ .getConnectorTracker()
+ .cleanStreamVariable(owner.getConnectorId(),
+ variableName);
+ }
+ });
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/HeartbeatHandler.java b/server/src/com/vaadin/server/communication/HeartbeatHandler.java
new file mode 100644
index 0000000000..16c21224ab
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/HeartbeatHandler.java
@@ -0,0 +1,90 @@
+/*
+ * 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.io.IOException;
+
+import javax.servlet.http.HttpServletResponse;
+
+import com.vaadin.server.ServletPortletHelper;
+import com.vaadin.server.SessionExpiredHandler;
+import com.vaadin.server.SynchronizedRequestHandler;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.server.VaadinResponse;
+import com.vaadin.server.VaadinSession;
+import com.vaadin.shared.ui.ui.UIConstants;
+import com.vaadin.ui.UI;
+
+/**
+ * Handles heartbeat requests. Heartbeat requests are periodically sent by the
+ * client-side to inform the server that the UI sending the heartbeat is still
+ * alive (the browser window is open, the connection is up) even when there are
+ * no UIDL requests for a prolonged period of time. UIs that do not receive
+ * either heartbeat or UIDL requests are eventually removed from the session and
+ * garbage collected.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class HeartbeatHandler extends SynchronizedRequestHandler implements
+ SessionExpiredHandler {
+
+ /**
+ * Handles a heartbeat request for the given session. Reads the GET
+ * parameter named {@link UIConstants#UI_ID_PARAMETER} to identify the UI.
+ * If the UI is found in the session, sets it
+ * {@link UI#getLastHeartbeatTimestamp() heartbeat timestamp} to the current
+ * time. Otherwise, writes a HTTP Not Found error to the response.
+ */
+ @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());
+ // Ensure that the browser does not cache heartbeat responses.
+ // iOS 6 Safari requires this (#10370)
+ response.setHeader("Cache-Control", "no-cache");
+ } else {
+ response.sendError(HttpServletResponse.SC_NOT_FOUND, "UI not found");
+ }
+
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.server.SessionExpiredHandler#handleSessionExpired(com.vaadin
+ * .server.VaadinRequest, com.vaadin.server.VaadinResponse)
+ */
+ @Override
+ public boolean handleSessionExpired(VaadinRequest request,
+ VaadinResponse response) throws IOException {
+ if (!ServletPortletHelper.isHeartbeatRequest(request)) {
+ return false;
+ }
+
+ response.sendError(HttpServletResponse.SC_GONE, "Session expired");
+ return true;
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/LegacyUidlWriter.java b/server/src/com/vaadin/server/communication/LegacyUidlWriter.java
new file mode 100644
index 0000000000..ad99a2d8b5
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/LegacyUidlWriter.java
@@ -0,0 +1,118 @@
+/*
+ * 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.io.IOException;
+import java.io.Serializable;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.logging.Logger;
+
+import com.vaadin.server.ClientConnector;
+import com.vaadin.server.LegacyPaint;
+import com.vaadin.server.PaintTarget;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.LegacyComponent;
+import com.vaadin.ui.UI;
+
+/**
+ * Serializes legacy UIDL changes to JSON.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class LegacyUidlWriter implements Serializable {
+
+ /**
+ * Writes a JSON array containing the changes of all dirty
+ * {@link LegacyComponent}s in the given UI.
+ *
+ * @param ui
+ * The {@link UI} whose legacy changes to write
+ * @param writer
+ * The {@link Writer} to write the JSON with
+ * @param target
+ * The {@link PaintTarget} to use
+ * @throws IOException
+ * If the serialization fails.
+ */
+ public void write(UI ui, Writer writer, PaintTarget target)
+ throws IOException {
+
+ Collection<ClientConnector> dirtyVisibleConnectors = ui
+ .getConnectorTracker().getDirtyVisibleConnectors();
+
+ List<Component> legacyComponents = new ArrayList<Component>();
+ for (ClientConnector connector : dirtyVisibleConnectors) {
+ // All Components that want to use paintContent must implement
+ // LegacyComponent
+ if (connector instanceof LegacyComponent) {
+ legacyComponents.add((Component) connector);
+ }
+ }
+ sortByHierarchy(legacyComponents);
+
+ writer.write("[");
+ for (Component c : legacyComponents) {
+ getLogger().fine(
+ "Painting LegacyComponent " + c.getClass().getName() + "@"
+ + Integer.toHexString(c.hashCode()));
+ target.startTag("change");
+ final String pid = c.getConnectorId();
+ target.addAttribute("pid", pid);
+ LegacyPaint.paint(c, target);
+ target.endTag("change");
+ }
+ writer.write("]");
+ }
+
+ private void sortByHierarchy(List<Component> paintables) {
+ // Vaadin 6 requires parents to be painted before children as component
+ // containers rely on that their updateFromUIDL method has been called
+ // before children start calling e.g. updateCaption
+ Collections.sort(paintables, new Comparator<Component>() {
+ @Override
+ public int compare(Component c1, Component c2) {
+ int depth1 = 0;
+ while (c1.getParent() != null) {
+ depth1++;
+ c1 = c1.getParent();
+ }
+ int depth2 = 0;
+ while (c2.getParent() != null) {
+ depth2++;
+ c2 = c2.getParent();
+ }
+ if (depth1 < depth2) {
+ return -1;
+ }
+ if (depth1 > depth2) {
+ return 1;
+ }
+ return 0;
+ }
+ });
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(LegacyUidlWriter.class.getName());
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/LocaleWriter.java b/server/src/com/vaadin/server/communication/LocaleWriter.java
new file mode 100644
index 0000000000..c05649da19
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/LocaleWriter.java
@@ -0,0 +1,204 @@
+/*
+ * 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.io.IOException;
+import java.io.Serializable;
+import java.io.Writer;
+import java.text.DateFormat;
+import java.text.DateFormatSymbols;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.Locale;
+import java.util.logging.Logger;
+
+/**
+ * Serializes locale information to JSON.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ * @deprecated See <a href="http://dev.vaadin.com/ticket/11378">ticket
+ * #11378</a>.
+ */
+@Deprecated
+public class LocaleWriter implements Serializable {
+
+ /**
+ * Writes a JSON object containing localized strings of the given locales.
+ *
+ * @param locales
+ * The list of {@link Locale}s to write.
+ * @param writer
+ * The {@link Writer} used to write the JSON.
+ * @throws IOException
+ * If the serialization fails.
+ *
+ */
+ public void write(List<String> locales, Writer writer) throws IOException {
+
+ // Send locale informations to client
+ writer.write("[");
+ // TODO locales are currently sent on each request; this will be fixed
+ // by implementing #11378.
+ for (int pendingLocalesIndex = 0; pendingLocalesIndex < locales.size(); pendingLocalesIndex++) {
+
+ final Locale l = generateLocale(locales.get(pendingLocalesIndex));
+ // Locale name
+ writer.write("{\"name\":\"" + l.toString() + "\",");
+
+ /*
+ * Month names (both short and full)
+ */
+ final DateFormatSymbols dfs = new DateFormatSymbols(l);
+ final String[] short_months = dfs.getShortMonths();
+ final String[] months = dfs.getMonths();
+ writer.write("\"smn\":[\""
+ + // ShortMonthNames
+ short_months[0] + "\",\"" + short_months[1] + "\",\""
+ + short_months[2] + "\",\"" + short_months[3] + "\",\""
+ + short_months[4] + "\",\"" + short_months[5] + "\",\""
+ + short_months[6] + "\",\"" + short_months[7] + "\",\""
+ + short_months[8] + "\",\"" + short_months[9] + "\",\""
+ + short_months[10] + "\",\"" + short_months[11] + "\""
+ + "],");
+ writer.write("\"mn\":[\""
+ + // MonthNames
+ months[0] + "\",\"" + months[1] + "\",\"" + months[2]
+ + "\",\"" + months[3] + "\",\"" + months[4] + "\",\""
+ + months[5] + "\",\"" + months[6] + "\",\"" + months[7]
+ + "\",\"" + months[8] + "\",\"" + months[9] + "\",\""
+ + months[10] + "\",\"" + months[11] + "\"" + "],");
+
+ /*
+ * Weekday names (both short and full)
+ */
+ final String[] short_days = dfs.getShortWeekdays();
+ final String[] days = dfs.getWeekdays();
+ writer.write("\"sdn\":[\""
+ + // ShortDayNames
+ short_days[1] + "\",\"" + short_days[2] + "\",\""
+ + short_days[3] + "\",\"" + short_days[4] + "\",\""
+ + short_days[5] + "\",\"" + short_days[6] + "\",\""
+ + short_days[7] + "\"" + "],");
+ writer.write("\"dn\":[\""
+ + // DayNames
+ days[1] + "\",\"" + days[2] + "\",\"" + days[3] + "\",\""
+ + days[4] + "\",\"" + days[5] + "\",\"" + days[6] + "\",\""
+ + days[7] + "\"" + "],");
+
+ /*
+ * First day of week (0 = sunday, 1 = monday)
+ */
+ final Calendar cal = new GregorianCalendar(l);
+ writer.write("\"fdow\":" + (cal.getFirstDayOfWeek() - 1) + ",");
+
+ /*
+ * Date formatting (MM/DD/YYYY etc.)
+ */
+
+ DateFormat dateFormat = DateFormat.getDateTimeInstance(
+ DateFormat.SHORT, DateFormat.SHORT, l);
+ if (!(dateFormat instanceof SimpleDateFormat)) {
+ getLogger().warning(
+ "Unable to get default date pattern for locale "
+ + l.toString());
+ dateFormat = new SimpleDateFormat();
+ }
+ final String df = ((SimpleDateFormat) dateFormat).toPattern();
+
+ int timeStart = df.indexOf("H");
+ if (timeStart < 0) {
+ timeStart = df.indexOf("h");
+ }
+ final int ampm_first = df.indexOf("a");
+ // E.g. in Korean locale AM/PM is before h:mm
+ // TODO should take that into consideration on client-side as well,
+ // now always h:mm a
+ if (ampm_first > 0 && ampm_first < timeStart) {
+ timeStart = ampm_first;
+ }
+ // Hebrew locale has time before the date
+ final boolean timeFirst = timeStart == 0;
+ String dateformat;
+ if (timeFirst) {
+ int dateStart = df.indexOf(' ');
+ if (ampm_first > dateStart) {
+ dateStart = df.indexOf(' ', ampm_first);
+ }
+ dateformat = df.substring(dateStart + 1);
+ } else {
+ dateformat = df.substring(0, timeStart - 1);
+ }
+
+ writer.write("\"df\":\"" + dateformat.trim() + "\",");
+
+ /*
+ * Time formatting (24 or 12 hour clock and AM/PM suffixes)
+ */
+ final String timeformat = df.substring(timeStart, df.length());
+ /*
+ * Doesn't return second or milliseconds.
+ *
+ * We use timeformat to determine 12/24-hour clock
+ */
+ final boolean twelve_hour_clock = timeformat.indexOf("a") > -1;
+ // TODO there are other possibilities as well, like 'h' in french
+ // (ignore them, too complicated)
+ final String hour_min_delimiter = timeformat.indexOf(".") > -1 ? "."
+ : ":";
+ // outWriter.print("\"tf\":\"" + timeformat + "\",");
+ writer.write("\"thc\":" + twelve_hour_clock + ",");
+ writer.write("\"hmd\":\"" + hour_min_delimiter + "\"");
+ if (twelve_hour_clock) {
+ final String[] ampm = dfs.getAmPmStrings();
+ writer.write(",\"ampm\":[\"" + ampm[0] + "\",\"" + ampm[1]
+ + "\"]");
+ }
+ writer.write("}");
+ if (pendingLocalesIndex < locales.size() - 1) {
+ writer.write(",");
+ }
+ }
+ writer.write("]"); // Close locales
+ }
+
+ /**
+ * Constructs a {@link Locale} instance to be sent to the client based on a
+ * short locale description string.
+ *
+ * @see #requireLocale(String)
+ *
+ * @param value
+ * @return
+ */
+ private Locale generateLocale(String value) {
+ final String[] temp = value.split("_");
+ if (temp.length == 1) {
+ return new Locale(temp[0]);
+ } else if (temp.length == 2) {
+ return new Locale(temp[0], temp[1]);
+ } else {
+ return new Locale(temp[0], temp[1], temp[2]);
+ }
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(LocaleWriter.class.getName());
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/MetadataWriter.java b/server/src/com/vaadin/server/communication/MetadataWriter.java
new file mode 100644
index 0000000000..1a3f0e946a
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/MetadataWriter.java
@@ -0,0 +1,148 @@
+/*
+ * 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.io.IOException;
+import java.io.PrintWriter;
+import java.io.Serializable;
+import java.io.Writer;
+import java.util.List;
+
+import com.vaadin.server.ClientConnector;
+import com.vaadin.server.ComponentSizeValidator;
+import com.vaadin.server.ComponentSizeValidator.InvalidLayout;
+import com.vaadin.server.SystemMessages;
+import com.vaadin.ui.UI;
+import com.vaadin.ui.Window;
+
+/**
+ * Serializes miscellaneous metadata to JSON.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class MetadataWriter implements Serializable {
+
+ private int timeoutInterval = -1;
+
+ /**
+ * Writes a JSON object containing metadata related to the given UI.
+ *
+ * @param ui
+ * The UI whose metadata to write.
+ * @param writer
+ * The writer used.
+ * @param repaintAll
+ * Whether the client should repaint everything.
+ * @param analyzeLayouts
+ * Whether detected layout problems should be reported in client
+ * and server console.
+ * @param async
+ * True if this message is sent by the server asynchronously,
+ * false if it is a response to a client message.
+ * @param hilightedConnector
+ * The connector that should be highlighted on the client or null
+ * if none.
+ * @param messages
+ * a {@link SystemMessages} containing client-side error
+ * messages.
+ * @throws IOException
+ * If the serialization fails.
+ *
+ */
+ public void write(UI ui, Writer writer, boolean repaintAll,
+ boolean analyzeLayouts, boolean async,
+ ClientConnector hilightedConnector, SystemMessages messages)
+ throws IOException {
+
+ List<InvalidLayout> invalidComponentRelativeSizes = null;
+
+ if (analyzeLayouts) {
+ invalidComponentRelativeSizes = ComponentSizeValidator
+ .validateComponentRelativeSizes(ui.getContent(), null, null);
+
+ // Also check any existing subwindows
+ if (ui.getWindows() != null) {
+ for (Window subWindow : ui.getWindows()) {
+ invalidComponentRelativeSizes = ComponentSizeValidator
+ .validateComponentRelativeSizes(
+ subWindow.getContent(),
+ invalidComponentRelativeSizes, null);
+ }
+ }
+ }
+
+ writer.write("{");
+
+ boolean metaOpen = false;
+ if (repaintAll) {
+ metaOpen = true;
+ writer.write("\"repaintAll\":true");
+ if (analyzeLayouts) {
+ writer.write(", \"invalidLayouts\":");
+ writer.write("[");
+ if (invalidComponentRelativeSizes != null) {
+ boolean first = true;
+ for (InvalidLayout invalidLayout : invalidComponentRelativeSizes) {
+ if (!first) {
+ writer.write(",");
+ } else {
+ first = false;
+ }
+ invalidLayout.reportErrors(new PrintWriter(writer),
+ System.err);
+ }
+ }
+ writer.write("]");
+ }
+ if (hilightedConnector != null) {
+ writer.write(", \"hl\":\"");
+ writer.write(hilightedConnector.getConnectorId());
+ writer.write("\"");
+ }
+ }
+
+ if (async) {
+ if (metaOpen) {
+ writer.write(", ");
+ }
+ writer.write("\"async\":true");
+ }
+
+ // meta instruction for client to enable auto-forward to
+ // sessionExpiredURL after timer expires.
+ if (messages != null && messages.getSessionExpiredMessage() == null
+ && messages.getSessionExpiredCaption() == null
+ && messages.isSessionExpiredNotificationEnabled()) {
+ int newTimeoutInterval = ui.getSession().getSession()
+ .getMaxInactiveInterval();
+ if (repaintAll || (timeoutInterval != newTimeoutInterval)) {
+ String escapedURL = messages.getSessionExpiredURL() == null ? ""
+ : messages.getSessionExpiredURL().replace("/", "\\/");
+ if (metaOpen) {
+ writer.write(",");
+ }
+ writer.write("\"timedRedirect\":{\"interval\":"
+ + (newTimeoutInterval + 15) + ",\"url\":\""
+ + escapedURL + "\"}");
+ metaOpen = true;
+ }
+ timeoutInterval = newTimeoutInterval;
+ }
+ writer.write("}");
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/PortletBootstrapHandler.java b/server/src/com/vaadin/server/communication/PortletBootstrapHandler.java
new file mode 100644
index 0000000000..2458951ada
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/PortletBootstrapHandler.java
@@ -0,0 +1,113 @@
+/*
+ * 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.io.IOException;
+
+import javax.portlet.MimeResponse;
+import javax.portlet.PortletRequest;
+import javax.portlet.PortletResponse;
+import javax.portlet.RenderRequest;
+import javax.portlet.RenderResponse;
+import javax.portlet.ResourceURL;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import com.vaadin.server.BootstrapHandler;
+import com.vaadin.server.PaintException;
+import com.vaadin.server.VaadinPortlet;
+import com.vaadin.server.VaadinPortletRequest;
+import com.vaadin.server.VaadinPortletResponse;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.server.VaadinResponse;
+import com.vaadin.server.VaadinService;
+import com.vaadin.server.VaadinSession;
+import com.vaadin.shared.ApplicationConstants;
+
+public class PortletBootstrapHandler extends BootstrapHandler {
+ @Override
+ public boolean handleRequest(VaadinSession session, VaadinRequest request,
+ VaadinResponse response) throws IOException {
+ PortletRequest portletRequest = ((VaadinPortletRequest) request)
+ .getPortletRequest();
+ if (portletRequest instanceof RenderRequest) {
+ return super.handleRequest(session, request, response);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ protected String getServiceUrl(BootstrapContext context) {
+ ResourceURL portletResourceUrl = getRenderResponse(context)
+ .createResourceURL();
+ portletResourceUrl.setResourceID(VaadinPortlet.RESOURCE_URL_ID);
+ return portletResourceUrl.toString();
+ }
+
+ private RenderResponse getRenderResponse(BootstrapContext context) {
+ PortletResponse response = ((VaadinPortletResponse) context
+ .getResponse()).getPortletResponse();
+
+ RenderResponse renderResponse = (RenderResponse) response;
+ return renderResponse;
+ }
+
+ @Override
+ protected void appendMainScriptTagContents(BootstrapContext context,
+ StringBuilder builder) throws JSONException, IOException {
+ // fixed base theme to use - all portal pages with Vaadin
+ // applications will load this exactly once
+ String portalTheme = ((VaadinPortletRequest) context.getRequest())
+ .getPortalProperty(VaadinPortlet.PORTAL_PARAMETER_VAADIN_THEME);
+ if (portalTheme != null && !portalTheme.equals(context.getThemeName())) {
+ String portalThemeUri = getThemeUri(context, portalTheme);
+ // XSS safe - originates from portal properties
+ builder.append("vaadin.loadTheme('" + portalThemeUri + "');");
+ }
+
+ super.appendMainScriptTagContents(context, builder);
+ }
+
+ @Override
+ protected String getMainDivStyle(BootstrapContext context) {
+ VaadinService vaadinService = context.getRequest().getService();
+ return vaadinService.getDeploymentConfiguration()
+ .getApplicationOrSystemProperty(
+ VaadinPortlet.PORTLET_PARAMETER_STYLE, null);
+ }
+
+ @Override
+ protected JSONObject getApplicationParameters(BootstrapContext context)
+ throws JSONException, PaintException {
+ JSONObject parameters = super.getApplicationParameters(context);
+ VaadinPortletResponse response = (VaadinPortletResponse) context
+ .getResponse();
+ MimeResponse portletResponse = (MimeResponse) response
+ .getPortletResponse();
+ ResourceURL resourceURL = portletResponse.createResourceURL();
+ resourceURL.setResourceID("v-browserDetails");
+ parameters.put("browserDetailsUrl", resourceURL.toString());
+
+ // Always send path info as a query parameter
+ parameters
+ .put(ApplicationConstants.SERVICE_URL_PATH_AS_PARAMETER, true);
+
+ return parameters;
+ }
+} \ No newline at end of file
diff --git a/server/src/com/vaadin/server/communication/PortletDummyRequestHandler.java b/server/src/com/vaadin/server/communication/PortletDummyRequestHandler.java
new file mode 100644
index 0000000000..8383cf607b
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/PortletDummyRequestHandler.java
@@ -0,0 +1,82 @@
+/*
+ * 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.io.BufferedWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+
+import javax.portlet.PortletResponse;
+import javax.portlet.ResourceRequest;
+import javax.portlet.ResourceResponse;
+
+import com.vaadin.server.RequestHandler;
+import com.vaadin.server.VaadinPortletResponse;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.server.VaadinResponse;
+import com.vaadin.server.VaadinSession;
+
+/**
+ * Request handler which provides a dummy HTML response to any resource request
+ * with the resource id DUMMY.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class PortletDummyRequestHandler implements RequestHandler {
+
+ @Override
+ public boolean handleRequest(VaadinSession session, VaadinRequest request,
+ VaadinResponse response) throws IOException {
+ if (!isDummyRequest(request)) {
+ return false;
+ }
+
+ /*
+ * This dummy page is used by action responses to redirect to, in order
+ * to prevent the boot strap code from being rendered into strange
+ * places such as iframes.
+ */
+ PortletResponse portletResponse = ((VaadinPortletResponse) response)
+ .getPortletResponse();
+ if (portletResponse instanceof ResourceResponse) {
+ ((ResourceResponse) portletResponse).setContentType("text/html");
+ }
+
+ final OutputStream out = ((ResourceResponse) response)
+ .getPortletOutputStream();
+ final PrintWriter outWriter = new PrintWriter(new BufferedWriter(
+ new OutputStreamWriter(out, "UTF-8")));
+ outWriter.print("<html><body>dummy page</body></html>");
+ outWriter.close();
+
+ return true;
+ }
+
+ public static boolean isDummyRequest(VaadinRequest request) {
+ ResourceRequest resourceRequest = PortletUIInitHandler
+ .getResourceRequest(request);
+ if (resourceRequest == null) {
+ return false;
+ }
+
+ return resourceRequest.getResourceID() != null
+ && resourceRequest.getResourceID().equals("DUMMY");
+ }
+
+}
diff --git a/server/src/com/vaadin/server/communication/PortletListenerNotifier.java b/server/src/com/vaadin/server/communication/PortletListenerNotifier.java
new file mode 100644
index 0000000000..5c03a6f4dc
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/PortletListenerNotifier.java
@@ -0,0 +1,89 @@
+/*
+ * 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.io.IOException;
+
+import javax.portlet.ActionRequest;
+import javax.portlet.ActionResponse;
+import javax.portlet.EventRequest;
+import javax.portlet.EventResponse;
+import javax.portlet.PortletRequest;
+import javax.portlet.PortletResponse;
+import javax.portlet.RenderRequest;
+import javax.portlet.RenderResponse;
+import javax.portlet.ResourceRequest;
+import javax.portlet.ResourceResponse;
+
+import com.vaadin.server.ServletPortletHelper;
+import com.vaadin.server.SynchronizedRequestHandler;
+import com.vaadin.server.VaadinPortletRequest;
+import com.vaadin.server.VaadinPortletResponse;
+import com.vaadin.server.VaadinPortletSession;
+import com.vaadin.server.VaadinPortletSession.PortletListener;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.server.VaadinResponse;
+import com.vaadin.server.VaadinSession;
+import com.vaadin.ui.UI;
+
+/**
+ * Notifies {@link PortletListener}s of a received portlet request.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class PortletListenerNotifier extends SynchronizedRequestHandler {
+
+ /**
+ * Fires portlet request events to any {@link PortletListener}s registered
+ * to the given session using
+ * {@link VaadinPortletSession#addPortletListener(PortletListener)}. The
+ * PortletListener method corresponding to the request type is invoked.
+ */
+ @Override
+ public boolean synchronizedHandleRequest(VaadinSession session,
+ VaadinRequest request, VaadinResponse response) throws IOException {
+
+ VaadinPortletSession sess = (VaadinPortletSession) session;
+ PortletRequest req = ((VaadinPortletRequest) request)
+ .getPortletRequest();
+ PortletResponse resp = ((VaadinPortletResponse) response)
+ .getPortletResponse();
+
+ // Finds the right UI
+ UI uI = null;
+ if (ServletPortletHelper.isUIDLRequest(request)) {
+ uI = session.getService().findUI(request);
+ }
+
+ if (request instanceof RenderRequest) {
+ sess.firePortletRenderRequest(uI, (RenderRequest) req,
+ (RenderResponse) resp);
+ } else if (request instanceof ActionRequest) {
+ sess.firePortletActionRequest(uI, (ActionRequest) req,
+ (ActionResponse) resp);
+ } else if (request instanceof EventRequest) {
+ sess.firePortletEventRequest(uI, (EventRequest) req,
+ (EventResponse) resp);
+ } else if (request instanceof ResourceRequest) {
+ sess.firePortletResourceRequest(uI, (ResourceRequest) req,
+ (ResourceResponse) resp);
+ }
+
+ return false;
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/PortletUIInitHandler.java b/server/src/com/vaadin/server/communication/PortletUIInitHandler.java
new file mode 100644
index 0000000000..d5d1e6b98d
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/PortletUIInitHandler.java
@@ -0,0 +1,63 @@
+/*
+ * 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 javax.portlet.PortletRequest;
+import javax.portlet.ResourceRequest;
+
+import com.vaadin.server.VaadinPortletRequest;
+import com.vaadin.server.VaadinRequest;
+
+public class PortletUIInitHandler extends UIInitHandler {
+
+ @Override
+ protected boolean isInitRequest(VaadinRequest request) {
+ return isUIInitRequest(request);
+ }
+
+ public static boolean isUIInitRequest(VaadinRequest request) {
+ ResourceRequest resourceRequest = getResourceRequest(request);
+ if (resourceRequest == null) {
+ return false;
+ }
+
+ return UIInitHandler.BROWSER_DETAILS_PARAMETER.equals(resourceRequest
+ .getResourceID());
+ }
+
+ /**
+ * Returns the {@link ResourceRequest} for the given request or null if none
+ * could be found.
+ *
+ * @param request
+ * The original request, must be a {@link VaadinPortletRequest}
+ * @return The resource request from the request parameter or null
+ */
+ static ResourceRequest getResourceRequest(VaadinRequest request) {
+ if (!(request instanceof VaadinPortletRequest)) {
+ throw new IllegalArgumentException(
+ "Request must a VaadinPortletRequest");
+ }
+ PortletRequest portletRequest = ((VaadinPortletRequest) request)
+ .getPortletRequest();
+ if (!(portletRequest instanceof ResourceRequest)) {
+ return null;
+ }
+
+ return (ResourceRequest) portletRequest;
+
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/PublishedFileHandler.java b/server/src/com/vaadin/server/communication/PublishedFileHandler.java
new file mode 100644
index 0000000000..8fe0f7085f
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/PublishedFileHandler.java
@@ -0,0 +1,151 @@
+/*
+ * 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.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.logging.Logger;
+
+import javax.servlet.http.HttpServletResponse;
+
+import com.vaadin.annotations.JavaScript;
+import com.vaadin.annotations.StyleSheet;
+import com.vaadin.server.Constants;
+import com.vaadin.server.LegacyCommunicationManager;
+import com.vaadin.server.RequestHandler;
+import com.vaadin.server.ServletPortletHelper;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.server.VaadinResponse;
+import com.vaadin.server.VaadinSession;
+import com.vaadin.shared.ApplicationConstants;
+
+/**
+ * Serves a connector resource from the classpath if the resource has previously
+ * been registered by calling
+ * {@link LegacyCommunicationManager#registerDependency(String, Class)}. Sending
+ * arbitrary files from the classpath is prevented by only accepting resource
+ * names that have explicitly been registered. Resources can currently only be
+ * registered by including a {@link JavaScript} or {@link StyleSheet} annotation
+ * on a Connector class.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class PublishedFileHandler implements RequestHandler {
+
+ /**
+ * Writes the connector resource identified by the request URI to the
+ * response. If a published resource corresponding to the URI path is not
+ * found, writes a HTTP Not Found error to the response.
+ */
+ @Override
+ public boolean handleRequest(VaadinSession session, VaadinRequest request,
+ VaadinResponse response) throws IOException {
+ if (!ServletPortletHelper.isPublishedFileRequest(request)) {
+ return false;
+ }
+
+ String pathInfo = request.getPathInfo();
+ // + 2 to also remove beginning and ending slashes
+ String fileName = pathInfo
+ .substring(ApplicationConstants.PUBLISHED_FILE_PATH.length() + 2);
+
+ final String mimetype = response.getService().getMimeType(fileName);
+
+ // Security check: avoid accidentally serving from the UI of the
+ // classpath instead of relative to the context class
+ if (fileName.startsWith("/")) {
+ getLogger().warning(
+ "Published file request starting with / rejected: "
+ + fileName);
+ response.sendError(HttpServletResponse.SC_NOT_FOUND, fileName);
+ return true;
+ }
+
+ // Check that the resource name has been registered
+ session.lock();
+ Class<?> context;
+ try {
+ context = session.getCommunicationManager().getDependencies()
+ .get(fileName);
+ } finally {
+ session.unlock();
+ }
+
+ // Security check: don't serve resource if the name hasn't been
+ // registered in the map
+ if (context == null) {
+ getLogger().warning(
+ "Rejecting published file request for file that has not been published: "
+ + fileName);
+ response.sendError(HttpServletResponse.SC_NOT_FOUND, fileName);
+ return true;
+ }
+
+ // Resolve file relative to the location of the context class
+ InputStream in = context.getResourceAsStream(fileName);
+ if (in == null) {
+ getLogger().warning(
+ fileName + " published by " + context.getName()
+ + " not found. Verify that the file "
+ + context.getPackage().getName().replace('.', '/')
+ + '/' + fileName
+ + " is available on the classpath.");
+ response.sendError(HttpServletResponse.SC_NOT_FOUND, fileName);
+ return true;
+ }
+
+ // TODO Check and set cache headers
+
+ OutputStream out = null;
+ try {
+ if (mimetype != null) {
+ response.setContentType(mimetype);
+ }
+
+ out = response.getOutputStream();
+
+ final byte[] buffer = new byte[Constants.DEFAULT_BUFFER_SIZE];
+
+ int bytesRead = 0;
+ while ((bytesRead = in.read(buffer)) > 0) {
+ out.write(buffer, 0, bytesRead);
+ }
+ out.flush();
+ } finally {
+ try {
+ in.close();
+ } catch (Exception e) {
+ // Do nothing
+ }
+ if (out != null) {
+ try {
+ out.close();
+ } catch (Exception e) {
+ // Do nothing
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(PublishedFileHandler.class.getName());
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/PushConnection.java b/server/src/com/vaadin/server/communication/PushConnection.java
new file mode 100644
index 0000000000..4e043f565f
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/PushConnection.java
@@ -0,0 +1,48 @@
+/*
+ * 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 com.vaadin.ui.UI;
+
+/**
+ * Represents a bidirectional ("push") connection between a single UI and its
+ * client-side.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public interface PushConnection {
+
+ /**
+ * Pushes pending state changes and client RPC calls to the client. 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.
+ */
+ public void push();
+
+ /**
+ * Disconnects the connection.
+ */
+ public void disconnect();
+
+ /**
+ * Returns whether this connection is currently open.
+ */
+ public boolean isConnected();
+
+} \ No newline at end of file
diff --git a/server/src/com/vaadin/server/communication/PushHandler.java b/server/src/com/vaadin/server/communication/PushHandler.java
new file mode 100644
index 0000000000..e740db410d
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/PushHandler.java
@@ -0,0 +1,380 @@
+/*
+ * 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.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.Arrays;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.atmosphere.cpr.AtmosphereHandler;
+import org.atmosphere.cpr.AtmosphereRequest;
+import org.atmosphere.cpr.AtmosphereResource;
+import org.atmosphere.cpr.AtmosphereResource.TRANSPORT;
+import org.atmosphere.cpr.AtmosphereResourceEvent;
+import org.json.JSONException;
+
+import com.vaadin.server.LegacyCommunicationManager.InvalidUIDLSecurityKeyException;
+import com.vaadin.server.ServiceException;
+import com.vaadin.server.ServletPortletHelper;
+import com.vaadin.server.SessionExpiredException;
+import com.vaadin.server.SystemMessages;
+import com.vaadin.server.VaadinRequest;
+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;
+
+/**
+ * Establishes bidirectional ("push") communication channels
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class PushHandler implements AtmosphereHandler {
+
+ /**
+ * Callback interface used internally to process an event with the
+ * corresponding UI properly locked.
+ */
+ private interface PushEventCallback {
+ public void run(AtmosphereResource resource, UI ui) throws IOException;
+ }
+
+ /**
+ * Callback used when we receive a request to establish a push channel for a
+ * UI. Associate the AtmosphereResource with the UI and leave the connection
+ * open by calling resource.suspend(). If there is a pending push, send it
+ * now.
+ */
+ private static PushEventCallback establishCallback = new PushEventCallback() {
+ @Override
+ public void run(AtmosphereResource resource, UI ui) throws IOException {
+ getLogger().log(Level.FINER,
+ "New push connection with transport {0}",
+ resource.transport());
+ resource.getResponse().setContentType("text/plain; charset=UTF-8");
+
+ 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);
+ }
+ }
+
+ String requestToken = resource.getRequest().getParameter(
+ ApplicationConstants.CSRF_TOKEN_PARAMETER);
+ if (!VaadinService.isCsrfTokenValid(session, requestToken)) {
+ getLogger()
+ .log(Level.WARNING,
+ "Invalid CSRF token in new connection received from {0}",
+ resource.getRequest().getRemoteHost());
+ // Refresh on client side, create connection just for
+ // sending a message
+ sendRefreshAndDisconnect(resource);
+ return;
+ }
+
+ resource.suspend();
+
+ AtmospherePushConnection connection = new AtmospherePushConnection(
+ ui);
+ connection.connect(resource);
+
+ ui.setPushConnection(connection);
+ }
+ };
+
+ /**
+ * Callback used when we receive a UIDL request through Atmosphere. If the
+ * push channel is bidirectional (websockets), the request was sent via the
+ * same channel. Otherwise, the client used a separate AJAX request. Handle
+ * the request and send changed UI state via the push channel (we do not
+ * respond to the request directly.)
+ */
+ private static PushEventCallback receiveCallback = new PushEventCallback() {
+ @Override
+ public void run(AtmosphereResource resource, UI ui) throws IOException {
+ AtmosphereRequest req = resource.getRequest();
+
+ AtmospherePushConnection connection = getConnectionForUI(ui);
+
+ assert connection != null : "Got push from the client "
+ + "even though the connection does not seem to be "
+ + "valid. This might happen if a HttpSession is "
+ + "serialized and deserialized while the push "
+ + "connection is kept open or if the UI has a "
+ + "connection of unexpected type.";
+
+ Reader reader = connection.receiveMessage(req.getReader());
+ if (reader == null) {
+ // The whole message was not yet received
+ return;
+ }
+
+ // Should be set up by caller
+ VaadinRequest vaadinRequest = VaadinService.getCurrentRequest();
+ assert vaadinRequest != null;
+
+ try {
+ new ServerRpcHandler().handleRpc(ui, reader, vaadinRequest);
+ connection.push(false);
+ } catch (JSONException e) {
+ getLogger().log(Level.SEVERE, "Error writing JSON to response",
+ e);
+ // Refresh on client side
+ sendRefreshAndDisconnect(resource);
+ } catch (InvalidUIDLSecurityKeyException e) {
+ getLogger().log(Level.WARNING,
+ "Invalid security key received from {0}",
+ resource.getRequest().getRemoteHost());
+ // Refresh on client side
+ sendRefreshAndDisconnect(resource);
+ }
+ }
+ };
+
+ /**
+ * Callback used when a connection is closed by the client.
+ */
+ PushEventCallback disconnectCallback = new PushEventCallback() {
+ @Override
+ public void run(AtmosphereResource resource, UI ui) throws IOException {
+ PushMode pushMode = ui.getPushMode();
+ AtmospherePushConnection pushConnection = getConnectionForUI(ui);
+
+ String id = resource.uuid();
+
+ if (pushConnection == 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, just clean up some stuff
+ * and be done with it
+ */
+ getLogger().log(Level.FINEST,
+ "Connection closed for resource {0}", id);
+ } else {
+ /*
+ * Unexpected cancel, e.g. if the user closes the browser
+ * tab.
+ */
+ getLogger()
+ .log(Level.FINE,
+ "Connection unexpectedly closed for resource {0} with transport {1}",
+ new Object[] { id, resource.transport() });
+ }
+ ui.setPushConnection(null);
+ }
+ }
+ };
+
+ private static final String LONG_PADDING;
+
+ static {
+ char[] array = new char[4096];
+ Arrays.fill(array, '-');
+ LONG_PADDING = String.copyValueOf(array);
+
+ }
+ private VaadinServletService service;
+
+ public PushHandler(VaadinServletService service) {
+ this.service = service;
+ }
+
+ /**
+ * Find the UI for the atmosphere resource, lock it and invoke the callback.
+ *
+ * @param resource
+ * the atmosphere resource for the current request
+ * @param callback
+ * the push callback to call when a UI is found and locked
+ */
+ private void callWithUi(final AtmosphereResource resource,
+ final PushEventCallback callback) {
+ AtmosphereRequest req = resource.getRequest();
+ VaadinServletRequest vaadinRequest = new VaadinServletRequest(req,
+ service);
+ VaadinSession session = null;
+
+ service.requestStart(vaadinRequest, null);
+ try {
+ try {
+ session = service.findVaadinSession(vaadinRequest);
+ } catch (ServiceException e) {
+ getLogger().log(Level.SEVERE,
+ "Could not get session. This should never happen", e);
+ } catch (SessionExpiredException e) {
+ SystemMessages msg = service.getSystemMessages(
+ ServletPortletHelper.findLocale(null, null,
+ vaadinRequest), vaadinRequest);
+ try {
+ resource.getResponse()
+ .getWriter()
+ .write(VaadinService
+ .createCriticalNotificationJSON(
+ msg.getSessionExpiredCaption(),
+ msg.getSessionExpiredMessage(),
+ null, msg.getSessionExpiredURL()));
+ } catch (IOException e1) {
+ getLogger()
+ .log(Level.WARNING,
+ "Failed to notify client about unavailable session",
+ e);
+ }
+ return;
+ }
+
+ session.lock();
+ try {
+ VaadinSession.setCurrent(session);
+ // Sets UI.currentInstance
+ final UI ui = service.findUI(vaadinRequest);
+ if (ui == null) {
+ // This a request through an already open push connection to
+ // a UI which no longer exists.
+ resource.getResponse()
+ .getWriter()
+ .write(UidlRequestHandler.getUINotFoundErrorJSON(
+ service, vaadinRequest));
+ // End the connection
+ resource.resume();
+ return;
+ }
+
+ callback.run(resource, ui);
+ } catch (IOException e) {
+ getLogger().log(Level.INFO,
+ "An error occured while writing a push response", e);
+ } finally {
+ session.unlock();
+ }
+ } finally {
+ service.requestEnd(vaadinRequest, null, session);
+ }
+ }
+
+ @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) {
+ AtmospherePushConnection apc = (AtmospherePushConnection) pushConnection;
+ if (apc.isConnected()) {
+ return apc;
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public void onStateChange(AtmosphereResourceEvent event) throws IOException {
+ AtmosphereResource resource = event.getResource();
+
+ String id = resource.uuid();
+ if (event.isCancelled()) {
+ callWithUi(resource, disconnectCallback);
+ } 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 SSE:
+ case WEBSOCKET:
+ break;
+ case STREAMING:
+ writer.flush();
+ break;
+ case JSONP:
+ case LONG_POLLING:
+ resource.resume();
+ break;
+ default:
+ getLogger().log(Level.SEVERE, "Unknown transport {0}",
+ resource.transport());
+ }
+ }
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+ /**
+ * Sends a refresh message to the given atmosphere resource. Uses an
+ * AtmosphereResource instead of an AtmospherePushConnection even though it
+ * might be possible to look up the AtmospherePushConnection from the UI to
+ * ensure border cases work correctly, especially when there temporarily are
+ * two push connections which try to use the same UI. Using the
+ * AtmosphereResource directly guarantees the message goes to the correct
+ * recipient.
+ *
+ * @param resource
+ * The atmosphere resource to send refresh to
+ *
+ */
+ private static void sendRefreshAndDisconnect(AtmosphereResource resource)
+ throws IOException {
+ AtmospherePushConnection connection = new AtmospherePushConnection(null);
+ connection.connect(resource);
+ connection.sendMessage(VaadinService.createCriticalNotificationJSON(
+ null, null, null, null));
+ connection.disconnect();
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(PushHandler.class.getName());
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/PushRequestHandler.java b/server/src/com/vaadin/server/communication/PushRequestHandler.java
new file mode 100644
index 0000000000..8360e08af9
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/PushRequestHandler.java
@@ -0,0 +1,134 @@
+/*
+ * 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.io.IOException;
+
+import javax.servlet.ServletException;
+
+import org.atmosphere.client.TrackMessageSizeInterceptor;
+import org.atmosphere.cpr.ApplicationConfig;
+import org.atmosphere.cpr.AtmosphereFramework;
+import org.atmosphere.cpr.AtmosphereRequest;
+import org.atmosphere.cpr.AtmosphereResponse;
+
+import com.vaadin.server.RequestHandler;
+import com.vaadin.server.ServiceException;
+import com.vaadin.server.ServletPortletHelper;
+import com.vaadin.server.SessionExpiredHandler;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.server.VaadinResponse;
+import com.vaadin.server.VaadinServletRequest;
+import com.vaadin.server.VaadinServletResponse;
+import com.vaadin.server.VaadinServletService;
+import com.vaadin.server.VaadinSession;
+import com.vaadin.shared.ApplicationConstants;
+
+/**
+ * Handles requests to open a push (bidirectional) communication channel between
+ * the client and the server. After the initial request, communication through
+ * the push channel is managed by {@link PushHandler}.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class PushRequestHandler implements RequestHandler,
+ SessionExpiredHandler {
+
+ private AtmosphereFramework atmosphere;
+ private PushHandler pushHandler;
+
+ public PushRequestHandler(VaadinServletService service)
+ throws ServiceException {
+
+ atmosphere = new AtmosphereFramework();
+
+ pushHandler = new PushHandler(service);
+ atmosphere.addAtmosphereHandler("/*", pushHandler);
+ atmosphere.addInitParameter(ApplicationConfig.PROPERTY_SESSION_SUPPORT,
+ "true");
+
+ final String bufferSize = String
+ .valueOf(ApplicationConstants.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");
+
+ // Required to ensure the client-side knows at which points to split the
+ // message stream into individual messages when using certain transports
+ atmosphere.interceptor(new TrackMessageSizeInterceptor());
+
+ try {
+ atmosphere.init(service.getServlet().getServletConfig());
+ } catch (ServletException e) {
+ throw new ServiceException("Could not read atmosphere settings", e);
+ }
+ }
+
+ @Override
+ public boolean handleRequest(VaadinSession session, VaadinRequest request,
+ VaadinResponse response) throws IOException {
+
+ if (!ServletPortletHelper.isPushRequest(request)) {
+ return false;
+ }
+
+ if (request instanceof VaadinServletRequest) {
+ try {
+ atmosphere.doCometSupport(AtmosphereRequest
+ .wrap((VaadinServletRequest) request),
+ AtmosphereResponse
+ .wrap((VaadinServletResponse) response));
+ } catch (ServletException e) {
+ // TODO PUSH decide how to handle
+ throw new RuntimeException(e);
+ }
+ } else {
+ throw new IllegalArgumentException(
+ "Portlets not currently supported");
+ }
+
+ return true;
+ }
+
+ public void destroy() {
+ atmosphere.destroy();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.server.SessionExpiredHandler#handleSessionExpired(com.vaadin
+ * .server.VaadinRequest, com.vaadin.server.VaadinResponse)
+ */
+ @Override
+ public boolean handleSessionExpired(VaadinRequest request,
+ VaadinResponse response) throws IOException {
+ // Websockets request must be handled by accepting the websocket
+ // connection and then sending session expired so we let
+ // PushRequestHandler handle it
+ return handleRequest(null, request, response);
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/ResourceWriter.java b/server/src/com/vaadin/server/communication/ResourceWriter.java
new file mode 100644
index 0000000000..080027943f
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/ResourceWriter.java
@@ -0,0 +1,113 @@
+/*
+ * 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.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Serializable;
+import java.io.Writer;
+import java.util.Iterator;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.vaadin.server.JsonPaintTarget;
+import com.vaadin.server.LegacyCommunicationManager;
+import com.vaadin.ui.CustomLayout;
+import com.vaadin.ui.UI;
+
+/**
+ * Serializes resources to JSON. Currently only used for {@link CustomLayout}
+ * templates.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class ResourceWriter implements Serializable {
+
+ /**
+ * Writes a JSON object containing registered resources.
+ *
+ * @param ui
+ * The {@link UI} whose resources to write.
+ * @param writer
+ * The {@link Writer} to use.
+ * @param target
+ * The {@link JsonPaintTarget} containing the resources.
+ * @throws IOException
+ */
+ public void write(UI ui, Writer writer, JsonPaintTarget target)
+ throws IOException {
+
+ // TODO PUSH Refactor so that this is not needed
+ LegacyCommunicationManager manager = ui.getSession()
+ .getCommunicationManager();
+
+ // Precache custom layouts
+
+ // TODO We should only precache the layouts that are not
+ // cached already (plagiate from usedPaintableTypes)
+
+ writer.write("{");
+ int resourceIndex = 0;
+ for (final Iterator<Object> i = target.getUsedResources().iterator(); i
+ .hasNext();) {
+ final String resource = (String) i.next();
+ InputStream is = null;
+ try {
+ is = ui.getSession()
+ .getService()
+ .getThemeResourceAsStream(ui, manager.getTheme(ui),
+ resource);
+ } catch (final Exception e) {
+ // FIXME: Handle exception
+ getLogger().log(Level.FINER,
+ "Failed to get theme resource stream.", e);
+ }
+ if (is != null) {
+
+ writer.write((resourceIndex++ > 0 ? ", " : "") + "\""
+ + resource + "\" : ");
+ final StringBuffer layout = new StringBuffer();
+
+ try {
+ final InputStreamReader r = new InputStreamReader(is,
+ "UTF-8");
+ final char[] buffer = new char[20000];
+ int charsRead = 0;
+ while ((charsRead = r.read(buffer)) > 0) {
+ layout.append(buffer, 0, charsRead);
+ }
+ r.close();
+ } catch (final java.io.IOException e) {
+ // FIXME: Handle exception
+ getLogger().log(Level.INFO, "Resource transfer failed", e);
+ }
+ writer.write("\""
+ + JsonPaintTarget.escapeJSON(layout.toString()) + "\"");
+ } else {
+ // FIXME: Handle exception
+ getLogger().severe("CustomLayout not found: " + resource);
+ }
+ }
+ writer.write("}");
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(ResourceWriter.class.getName());
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/ServerRpcHandler.java b/server/src/com/vaadin/server/communication/ServerRpcHandler.java
new file mode 100644
index 0000000000..3cc85909ee
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/ServerRpcHandler.java
@@ -0,0 +1,469 @@
+/*
+ * 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.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;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import com.vaadin.server.ClientConnector;
+import com.vaadin.server.JsonCodec;
+import com.vaadin.server.LegacyCommunicationManager;
+import com.vaadin.server.LegacyCommunicationManager.InvalidUIDLSecurityKeyException;
+import com.vaadin.server.ServerRpcManager;
+import com.vaadin.server.ServerRpcManager.RpcInvocationException;
+import com.vaadin.server.ServerRpcMethodInvocation;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.server.VaadinService;
+import com.vaadin.server.VariableOwner;
+import com.vaadin.shared.ApplicationConstants;
+import com.vaadin.shared.Connector;
+import com.vaadin.shared.communication.LegacyChangeVariablesInvocation;
+import com.vaadin.shared.communication.MethodInvocation;
+import com.vaadin.shared.communication.ServerRpc;
+import com.vaadin.shared.communication.UidlValue;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.ConnectorTracker;
+import com.vaadin.ui.UI;
+
+/**
+ * Handles a client-to-server message containing serialized {@link ServerRpc
+ * server RPC} invocations.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class ServerRpcHandler implements Serializable {
+
+ /* Variable records indexes */
+ public static final char VAR_BURST_SEPARATOR = '\u001d';
+
+ public static final char VAR_ESCAPE_CHARACTER = '\u001b';
+
+ private static final int MAX_BUFFER_SIZE = 64 * 1024;
+
+ /**
+ * Reads JSON containing zero or more serialized RPC calls (including legacy
+ * variable changes) and executes the calls.
+ *
+ * @param ui
+ * The {@link UI} receiving the calls. Cannot be null.
+ * @param reader
+ * The {@link Reader} used to read the JSON.
+ * @param request
+ * @throws IOException
+ * If reading the message fails.
+ * @throws InvalidUIDLSecurityKeyException
+ * If the received security key does not match the one stored in
+ * the session.
+ * @throws JSONException
+ * If deserializing the JSON fails.
+ */
+ public void handleRpc(UI ui, Reader reader, VaadinRequest request)
+ throws IOException, InvalidUIDLSecurityKeyException, JSONException {
+ ui.getSession().setLastRequestTimestamp(System.currentTimeMillis());
+
+ String changes = 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) {
+ // The client sometimes sends empty messages, this is probably a bug
+ return;
+ }
+
+ // Security: double cookie submission pattern unless disabled by
+ // property
+ if (!VaadinService.isCsrfTokenValid(ui.getSession(), bursts[0])) {
+ throw new InvalidUIDLSecurityKeyException("");
+ }
+ handleBurst(ui, unescapeBurst(bursts[1]));
+ }
+
+ /**
+ * 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.
+ *
+ * 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
+ */
+ private void handleBurst(UI uI, String burst) {
+ // TODO PUSH Refactor so that this is not needed
+ LegacyCommunicationManager manager = uI.getSession()
+ .getCommunicationManager();
+
+ try {
+ Set<Connector> enabledConnectors = new HashSet<Connector>();
+
+ List<MethodInvocation> invocations = parseInvocations(
+ uI.getConnectorTracker(), burst);
+ for (MethodInvocation invocation : invocations) {
+ final ClientConnector connector = manager.getConnector(uI,
+ invocation.getConnectorId());
+
+ if (connector != null && connector.isConnectorEnabled()) {
+ enabledConnectors.add(connector);
+ }
+ }
+
+ for (int i = 0; i < invocations.size(); i++) {
+ MethodInvocation invocation = invocations.get(i);
+
+ final ClientConnector connector = manager.getConnector(uI,
+ invocation.getConnectorId());
+ if (connector == null) {
+ getLogger()
+ .log(Level.WARNING,
+ "Received RPC call for unknown connector with id {0} (tried to invoke {1}.{2})",
+ new Object[] { invocation.getConnectorId(),
+ invocation.getInterfaceName(),
+ invocation.getMethodName() });
+ continue;
+ }
+
+ if (!enabledConnectors.contains(connector)) {
+
+ if (invocation instanceof LegacyChangeVariablesInvocation) {
+ LegacyChangeVariablesInvocation legacyInvocation = (LegacyChangeVariablesInvocation) invocation;
+ // TODO convert window close to a separate RPC call and
+ // handle above - not a variable change
+
+ // Handle special case where window-close is called
+ // after the window has been removed from the
+ // application or the application has closed
+ Map<String, Object> changes = legacyInvocation
+ .getVariableChanges();
+ if (changes.size() == 1 && changes.containsKey("close")
+ && Boolean.TRUE.equals(changes.get("close"))) {
+ // Silently ignore this
+ continue;
+ }
+ }
+
+ // Connector is disabled, log a warning and move to the next
+ String msg = "Ignoring RPC call for disabled connector "
+ + connector.getClass().getName();
+ if (connector instanceof Component) {
+ String caption = ((Component) connector).getCaption();
+ if (caption != null) {
+ msg += ", caption=" + caption;
+ }
+ }
+ getLogger().warning(msg);
+ continue;
+ }
+ // DragAndDropService has null UI
+ if (connector.getUI() != null && connector.getUI().isClosing()) {
+ String msg = "Ignoring RPC call for connector "
+ + connector.getClass().getName();
+ if (connector instanceof Component) {
+ String caption = ((Component) connector).getCaption();
+ if (caption != null) {
+ msg += ", caption=" + caption;
+ }
+ }
+ msg += " in closed UI";
+ getLogger().warning(msg);
+ continue;
+
+ }
+
+ if (invocation instanceof ServerRpcMethodInvocation) {
+ try {
+ ServerRpcManager.applyInvocation(connector,
+ (ServerRpcMethodInvocation) invocation);
+ } catch (RpcInvocationException e) {
+ manager.handleConnectorRelatedException(connector, e);
+ }
+ } else {
+
+ // All code below is for legacy variable changes
+ LegacyChangeVariablesInvocation legacyInvocation = (LegacyChangeVariablesInvocation) invocation;
+ Map<String, Object> changes = legacyInvocation
+ .getVariableChanges();
+ try {
+ if (connector instanceof VariableOwner) {
+ // The source parameter is never used anywhere
+ changeVariables(null, (VariableOwner) connector,
+ changes);
+ } else {
+ throw new IllegalStateException(
+ "Received legacy variable change for "
+ + connector.getClass().getName()
+ + " ("
+ + connector.getConnectorId()
+ + ") which is not a VariableOwner. The client-side connector sent these legacy varaibles: "
+ + changes.keySet());
+ }
+ } catch (Exception e) {
+ manager.handleConnectorRelatedException(connector, e);
+ }
+ }
+ }
+ } catch (JSONException e) {
+ getLogger().warning(
+ "Unable to parse RPC call from the client: "
+ + e.getMessage());
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Parse a message burst from the client into a list of MethodInvocation
+ * instances.
+ *
+ * @param connectorTracker
+ * The ConnectorTracker used to lookup connectors
+ * @param burst
+ * message string (JSON)
+ * @return list of MethodInvocation to perform
+ * @throws JSONException
+ */
+ private List<MethodInvocation> parseInvocations(
+ ConnectorTracker connectorTracker, String burst)
+ throws JSONException {
+ JSONArray invocationsJson = new JSONArray(burst);
+
+ ArrayList<MethodInvocation> invocations = new ArrayList<MethodInvocation>();
+
+ MethodInvocation previousInvocation = null;
+ // parse JSON to MethodInvocations
+ for (int i = 0; i < invocationsJson.length(); ++i) {
+
+ JSONArray invocationJson = invocationsJson.getJSONArray(i);
+
+ MethodInvocation invocation = parseInvocation(invocationJson,
+ previousInvocation, connectorTracker);
+ 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
+ // rejected because of an error.
+ invocations.add(invocation);
+ previousInvocation = invocation;
+ }
+ }
+ return invocations;
+ }
+
+ private MethodInvocation parseInvocation(JSONArray invocationJson,
+ MethodInvocation previousInvocation,
+ ConnectorTracker connectorTracker) throws JSONException {
+ String connectorId = invocationJson.getString(0);
+ String interfaceName = invocationJson.getString(1);
+ String methodName = invocationJson.getString(2);
+
+ 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();
+ return null;
+ }
+
+ JSONArray parametersJson = invocationJson.getJSONArray(3);
+
+ if (LegacyChangeVariablesInvocation.isLegacyVariableChange(
+ interfaceName, methodName)) {
+ if (!(previousInvocation instanceof LegacyChangeVariablesInvocation)) {
+ previousInvocation = null;
+ }
+
+ return parseLegacyChangeVariablesInvocation(connectorId,
+ interfaceName, methodName,
+ (LegacyChangeVariablesInvocation) previousInvocation,
+ parametersJson, connectorTracker);
+ } else {
+ return parseServerRpcInvocation(connectorId, interfaceName,
+ methodName, parametersJson, connectorTracker);
+ }
+
+ }
+
+ private LegacyChangeVariablesInvocation parseLegacyChangeVariablesInvocation(
+ String connectorId, String interfaceName, String methodName,
+ LegacyChangeVariablesInvocation previousInvocation,
+ JSONArray parametersJson, ConnectorTracker connectorTracker)
+ throws JSONException {
+ if (parametersJson.length() != 2) {
+ throw new JSONException(
+ "Invalid parameters in legacy change variables call. Expected 2, was "
+ + parametersJson.length());
+ }
+ String variableName = parametersJson.getString(0);
+ UidlValue uidlValue = (UidlValue) JsonCodec.decodeInternalType(
+ UidlValue.class, true, parametersJson.get(1), connectorTracker);
+
+ Object value = uidlValue.getValue();
+
+ if (previousInvocation != null
+ && previousInvocation.getConnectorId().equals(connectorId)) {
+ previousInvocation.setVariableChange(variableName, value);
+ return null;
+ } else {
+ return new LegacyChangeVariablesInvocation(connectorId,
+ variableName, value);
+ }
+ }
+
+ private ServerRpcMethodInvocation parseServerRpcInvocation(
+ String connectorId, String interfaceName, String methodName,
+ JSONArray parametersJson, ConnectorTracker connectorTracker)
+ throws JSONException {
+ ClientConnector connector = connectorTracker.getConnector(connectorId);
+
+ ServerRpcManager<?> rpcManager = connector.getRpcManager(interfaceName);
+ if (rpcManager == null) {
+ /*
+ * Security: Don't even decode the json parameters if no RpcManager
+ * corresponding to the received method invocation has been
+ * registered.
+ */
+ getLogger().warning(
+ "Ignoring RPC call to " + interfaceName + "." + methodName
+ + " in connector " + connector.getClass().getName()
+ + "(" + connectorId
+ + ") as no RPC implementation is regsitered");
+ return null;
+ }
+
+ // Use interface from RpcManager instead of loading the class based on
+ // the string name to avoid problems with OSGi
+ Class<? extends ServerRpc> rpcInterface = rpcManager.getRpcInterface();
+
+ ServerRpcMethodInvocation invocation = new ServerRpcMethodInvocation(
+ connectorId, rpcInterface, methodName, parametersJson.length());
+
+ Object[] parameters = new Object[parametersJson.length()];
+ Type[] declaredRpcMethodParameterTypes = invocation.getMethod()
+ .getGenericParameterTypes();
+
+ for (int j = 0; j < parametersJson.length(); ++j) {
+ Object parameterValue = parametersJson.get(j);
+ Type parameterType = declaredRpcMethodParameterTypes[j];
+ parameters[j] = JsonCodec.decodeInternalOrCustomType(parameterType,
+ parameterValue, connectorTracker);
+ }
+ invocation.setParameters(parameters);
+ return invocation;
+ }
+
+ protected void changeVariables(Object source, VariableOwner owner,
+ Map<String, Object> m) {
+ 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);
+ char[] buffer = new char[MAX_BUFFER_SIZE];
+
+ while (true) {
+ int read = reader.read(buffer);
+ if (read == -1) {
+ break;
+ }
+ sb.append(buffer, 0, read);
+ }
+
+ return sb.toString();
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(ServerRpcHandler.class.getName());
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/ServletBootstrapHandler.java b/server/src/com/vaadin/server/communication/ServletBootstrapHandler.java
new file mode 100644
index 0000000000..4b6517c30f
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/ServletBootstrapHandler.java
@@ -0,0 +1,48 @@
+/*
+ * 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 com.vaadin.server.BootstrapHandler;
+import com.vaadin.server.VaadinServlet;
+import com.vaadin.server.VaadinServletService;
+
+public class ServletBootstrapHandler extends BootstrapHandler {
+ @Override
+ protected String getServiceUrl(BootstrapContext context) {
+ String pathInfo = context.getRequest().getPathInfo();
+ if (pathInfo == null) {
+ return null;
+ } else {
+ /*
+ * Make a relative URL to the servlet by adding one ../ for each
+ * path segment in pathInfo (i.e. the part of the requested path
+ * that comes after the servlet mapping)
+ */
+ return VaadinServletService.getCancelingRelativePath(pathInfo);
+ }
+ }
+
+ @Override
+ public String getThemeName(BootstrapContext context) {
+ String themeName = context.getRequest().getParameter(
+ VaadinServlet.URL_PARAMETER_THEME);
+ if (themeName == null) {
+ themeName = super.getThemeName(context);
+ }
+ return themeName;
+ }
+} \ No newline at end of file
diff --git a/server/src/com/vaadin/server/communication/ServletUIInitHandler.java b/server/src/com/vaadin/server/communication/ServletUIInitHandler.java
new file mode 100644
index 0000000000..6286c161f2
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/ServletUIInitHandler.java
@@ -0,0 +1,33 @@
+/*
+ * 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 com.vaadin.server.VaadinRequest;
+
+public class ServletUIInitHandler extends UIInitHandler {
+
+ @Override
+ protected boolean isInitRequest(VaadinRequest request) {
+ return isUIInitRequest(request);
+ }
+
+ public static boolean isUIInitRequest(VaadinRequest request) {
+ return "POST".equals(request.getMethod())
+ && request
+ .getParameter(UIInitHandler.BROWSER_DETAILS_PARAMETER) != null;
+ }
+
+}
diff --git a/server/src/com/vaadin/server/communication/SessionRequestHandler.java b/server/src/com/vaadin/server/communication/SessionRequestHandler.java
new file mode 100644
index 0000000000..244cb0121d
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/SessionRequestHandler.java
@@ -0,0 +1,70 @@
+/*
+ * 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.io.IOException;
+import java.util.ArrayList;
+
+import com.vaadin.server.RequestHandler;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.server.VaadinResponse;
+import com.vaadin.server.VaadinSession;
+
+/**
+ * Handles a request by passing it to each registered {@link RequestHandler} in
+ * the session in turn until one produces a response. This method is used for
+ * requests that have not been handled by any specific functionality in the
+ * servlet/portlet.
+ * <p>
+ * The request handlers are invoked in the reverse order in which they were
+ * added to the session until a response has been produced. This means that the
+ * most recently added handler is used first and the first request handler that
+ * was added to the session is invoked towards the end unless any previous
+ * handler has already produced a response.
+ * </p>
+ * <p>
+ * The session is not locked during execution of the request handlers. The
+ * request handler can itself decide if it needs to lock the session or not.
+ * </p>
+ *
+ * @see VaadinSession#addRequestHandler(RequestHandler)
+ * @see RequestHandler
+ *
+ * @since 7.1
+ */
+public class SessionRequestHandler implements RequestHandler {
+
+ @Override
+ public boolean handleRequest(VaadinSession session, VaadinRequest request,
+ VaadinResponse response) throws IOException {
+ // Use a copy to avoid ConcurrentModificationException
+ session.lock();
+ ArrayList<RequestHandler> requestHandlers;
+ try {
+ requestHandlers = new ArrayList<RequestHandler>(
+ session.getRequestHandlers());
+ } finally {
+ session.unlock();
+ }
+ for (RequestHandler handler : requestHandlers) {
+ if (handler.handleRequest(session, request, response)) {
+ return true;
+ }
+ }
+ // If not handled
+ return false;
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/SharedStateWriter.java b/server/src/com/vaadin/server/communication/SharedStateWriter.java
new file mode 100644
index 0000000000..fdf834387f
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/SharedStateWriter.java
@@ -0,0 +1,75 @@
+/*
+ * 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.io.IOException;
+import java.io.Serializable;
+import java.io.Writer;
+import java.util.Collection;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import com.vaadin.server.ClientConnector;
+import com.vaadin.server.PaintException;
+import com.vaadin.shared.communication.SharedState;
+import com.vaadin.ui.UI;
+
+/**
+ * Serializes {@link SharedState shared state} changes to JSON.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class SharedStateWriter implements Serializable {
+
+ /**
+ * Writes a JSON object containing the pending state changes of the dirty
+ * connectors of the given UI.
+ *
+ * @param ui
+ * The UI whose state changes should be written.
+ * @param writer
+ * The writer to use.
+ * @throws IOException
+ * If the serialization fails.
+ */
+ public void write(UI ui, Writer writer) throws IOException {
+
+ Collection<ClientConnector> dirtyVisibleConnectors = ui
+ .getConnectorTracker().getDirtyVisibleConnectors();
+
+ JSONObject sharedStates = new JSONObject();
+ for (ClientConnector connector : dirtyVisibleConnectors) {
+ // encode and send shared state
+ try {
+ JSONObject stateJson = connector.encodeState();
+
+ if (stateJson != null && stateJson.length() != 0) {
+ sharedStates.put(connector.getConnectorId(), stateJson);
+ }
+ } catch (JSONException e) {
+ throw new PaintException(
+ "Failed to serialize shared state for connector "
+ + connector.getClass().getName() + " ("
+ + connector.getConnectorId() + "): "
+ + e.getMessage(), e);
+ }
+ }
+ writer.write(sharedStates.toString());
+ }
+}
diff --git a/server/src/com/vaadin/server/StreamingEndEventImpl.java b/server/src/com/vaadin/server/communication/StreamingEndEventImpl.java
index 756cadee6b..f8cfb160be 100644
--- a/server/src/com/vaadin/server/StreamingEndEventImpl.java
+++ b/server/src/com/vaadin/server/communication/StreamingEndEventImpl.java
@@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package com.vaadin.server;
+package com.vaadin.server.communication;
import com.vaadin.server.StreamVariable.StreamingEndEvent;
diff --git a/server/src/com/vaadin/server/StreamingErrorEventImpl.java b/server/src/com/vaadin/server/communication/StreamingErrorEventImpl.java
index 53e25399cd..9d9a19e4fe 100644
--- a/server/src/com/vaadin/server/StreamingErrorEventImpl.java
+++ b/server/src/com/vaadin/server/communication/StreamingErrorEventImpl.java
@@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package com.vaadin.server;
+package com.vaadin.server.communication;
import com.vaadin.server.StreamVariable.StreamingErrorEvent;
diff --git a/server/src/com/vaadin/server/StreamingProgressEventImpl.java b/server/src/com/vaadin/server/communication/StreamingProgressEventImpl.java
index 610cd30c13..69f3bfb29c 100644
--- a/server/src/com/vaadin/server/StreamingProgressEventImpl.java
+++ b/server/src/com/vaadin/server/communication/StreamingProgressEventImpl.java
@@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package com.vaadin.server;
+package com.vaadin.server.communication;
import com.vaadin.server.StreamVariable.StreamingProgressEvent;
diff --git a/server/src/com/vaadin/server/StreamingStartEventImpl.java b/server/src/com/vaadin/server/communication/StreamingStartEventImpl.java
index 3cd41bbb6d..bd16f08801 100644
--- a/server/src/com/vaadin/server/StreamingStartEventImpl.java
+++ b/server/src/com/vaadin/server/communication/StreamingStartEventImpl.java
@@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package com.vaadin.server;
+package com.vaadin.server.communication;
import com.vaadin.server.StreamVariable.StreamingStartEvent;
diff --git a/server/src/com/vaadin/server/communication/UIInitHandler.java b/server/src/com/vaadin/server/communication/UIInitHandler.java
new file mode 100644
index 0000000000..e4b5360b49
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/UIInitHandler.java
@@ -0,0 +1,304 @@
+/*
+ * 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.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;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import com.vaadin.annotations.PreserveOnRefresh;
+import com.vaadin.server.LegacyApplicationUIProvider;
+import com.vaadin.server.SynchronizedRequestHandler;
+import com.vaadin.server.UIClassSelectionEvent;
+import com.vaadin.server.UICreateEvent;
+import com.vaadin.server.UIProvider;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.server.VaadinResponse;
+import com.vaadin.server.VaadinService;
+import com.vaadin.server.VaadinSession;
+import com.vaadin.shared.ApplicationConstants;
+import com.vaadin.shared.communication.PushMode;
+import com.vaadin.shared.ui.ui.UIConstants;
+import com.vaadin.ui.UI;
+
+/**
+ * Handles an initial request from the client to initialize a {@link UI}.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public abstract class UIInitHandler extends SynchronizedRequestHandler {
+
+ public static final String BROWSER_DETAILS_PARAMETER = "v-browserDetails";
+
+ protected abstract boolean isInitRequest(VaadinRequest request);
+
+ @Override
+ public boolean synchronizedHandleRequest(VaadinSession session,
+ VaadinRequest request, VaadinResponse response) throws IOException {
+ if (!isInitRequest(request)) {
+ return false;
+ }
+
+ StringWriter stringWriter = new StringWriter();
+
+ try {
+ assert UI.getCurrent() == null;
+
+ // Set browser information from the request
+ session.getBrowser().updateRequestDetails(request);
+
+ UI uI = getBrowserDetailsUI(request, session);
+
+ session.getCommunicationManager().repaintAll(uI);
+
+ JSONObject params = new JSONObject();
+ params.put(UIConstants.UI_ID_PARAMETER, uI.getUIId());
+ String initialUIDL = getInitialUidl(request, uI);
+ params.put("uidl", initialUIDL);
+
+ stringWriter.write(params.toString());
+ } catch (JSONException e) {
+ throw new IOException("Error producing initial UIDL", e);
+ } finally {
+ stringWriter.close();
+ }
+
+ return commitJsonResponse(request, response, stringWriter.toString());
+ }
+
+ /**
+ * Commit the JSON response. We can't write immediately to the output stream
+ * as we want to write only a critical notification if something goes wrong
+ * during the response handling.
+ *
+ * @param request
+ * The request that resulted in this response
+ * @param response
+ * The response to write to
+ * @param json
+ * The JSON to write
+ * @return true if the JSON was written successfully, false otherwise
+ * @throws IOException
+ * If there was an exception while writing to the output
+ */
+ 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");
+
+ // Ensure that the browser does not cache UIDL responses.
+ // iOS 6 Safari requires this (#9732)
+ response.setHeader("Cache-Control", "no-cache");
+
+ // NOTE! GateIn requires, for some weird reason, getOutputStream
+ // to be used instead of getWriter() (it seems to interpret
+ // application/json as a binary content type)
+ OutputStreamWriter outputWriter = new OutputStreamWriter(
+ response.getOutputStream(), "UTF-8");
+ try {
+ outputWriter.write(json);
+ // NOTE GateIn requires the buffers to be flushed to work
+ outputWriter.flush();
+ } finally {
+ outputWriter.close();
+ }
+
+ return true;
+ }
+
+ private UI getBrowserDetailsUI(VaadinRequest request, VaadinSession session) {
+ VaadinService vaadinService = request.getService();
+
+ List<UIProvider> uiProviders = session.getUIProviders();
+
+ UIClassSelectionEvent classSelectionEvent = new UIClassSelectionEvent(
+ request);
+
+ UIProvider provider = null;
+ Class<? extends UI> uiClass = null;
+ for (UIProvider p : uiProviders) {
+ // Check for existing LegacyWindow
+ if (p instanceof LegacyApplicationUIProvider) {
+ LegacyApplicationUIProvider legacyProvider = (LegacyApplicationUIProvider) p;
+
+ UI existingUi = legacyProvider
+ .getExistingUI(classSelectionEvent);
+ if (existingUi != null) {
+ reinitUI(existingUi, request);
+ return existingUi;
+ }
+ }
+
+ uiClass = p.getUIClass(classSelectionEvent);
+ if (uiClass != null) {
+ provider = p;
+ break;
+ }
+ }
+
+ if (provider == null || uiClass == null) {
+ 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
+
+ Integer retainedUIId = retainOnRefreshUIs.get(windowName);
+
+ if (retainedUIId != null) {
+ UI retainedUI = session.getUIById(retainedUIId.intValue());
+ if (uiClass.isInstance(retainedUI)) {
+ reinitUI(retainedUI, request);
+ return retainedUI;
+ } else {
+ getLogger().info(
+ "Not using retained UI in " + windowName
+ + " because retained UI was of type "
+ + retainedUI.getClass() + " but " + uiClass
+ + " is expected for the request.");
+ }
+ }
+ }
+
+ // No existing UI found - go on by creating and initializing one
+
+ Integer uiId = Integer.valueOf(session.getNextUIid());
+
+ // Explicit Class.cast to detect if the UIProvider does something
+ // unexpected
+ UICreateEvent event = new UICreateEvent(request, uiClass, uiId);
+ UI ui = uiClass.cast(provider.createInstance(event));
+
+ // Initialize some fields for a newly created UI
+ if (ui.getSession() != session) {
+ // Session already set for LegacyWindow
+ ui.setSession(session);
+ }
+
+ PushMode pushMode = provider.getPushMode(event);
+ if (pushMode == null) {
+ pushMode = session.getService().getDeploymentConfiguration()
+ .getPushMode();
+ }
+ ui.setPushMode(pushMode);
+
+ // Set thread local here so it is available in init
+ UI.setCurrent(ui);
+
+ ui.doInit(request, uiId.intValue());
+
+ 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);
+ }
+ }
+
+ return ui;
+ }
+
+ /**
+ * Updates a UI that has already been initialized but is now loaded again,
+ * e.g. because of {@link PreserveOnRefresh}.
+ *
+ * @param ui
+ * @param request
+ */
+ 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);
+ }
+ }
+
+ /**
+ * Generates the initial UIDL message that can e.g. be included in a html
+ * page to avoid a separate round trip just for getting the UIDL.
+ *
+ * @param request
+ * the request that caused the initialization
+ * @param uI
+ * the UI for which the UIDL should be generated
+ * @return a string with the initial UIDL message
+ * @throws JSONException
+ * if an exception occurs while encoding output
+ * @throws IOException
+ */
+ protected String getInitialUidl(VaadinRequest request, UI uI)
+ throws JSONException, IOException {
+ StringWriter writer = new StringWriter();
+ try {
+ writer.write("{");
+
+ VaadinSession session = uI.getSession();
+ if (session.getConfiguration().isXsrfProtectionEnabled()) {
+ writer.write(getSecurityKeyUIDL(session));
+ }
+ new UidlWriter().write(uI, writer, true, false, false);
+ writer.write("}");
+
+ String initialUIDL = writer.toString();
+ getLogger().log(Level.FINE, "Initial UIDL:" + initialUIDL);
+ return initialUIDL;
+ } finally {
+ writer.close();
+ }
+ }
+
+ /**
+ * Gets the security key (and generates one if needed) as UIDL.
+ *
+ * @param session
+ * the vaadin session to which the security key belongs
+ * @return the security key UIDL or "" if the feature is turned off
+ */
+ private static String getSecurityKeyUIDL(VaadinSession session) {
+ String seckey = session.getCsrfToken();
+
+ return "\"" + ApplicationConstants.UIDL_SECURITY_TOKEN_ID + "\":\""
+ + seckey + "\",";
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(UIInitHandler.class.getName());
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/UidlRequestHandler.java b/server/src/com/vaadin/server/communication/UidlRequestHandler.java
new file mode 100644
index 0000000000..73ff92f8bd
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/UidlRequestHandler.java
@@ -0,0 +1,306 @@
+/*
+ * 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.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.LinkedList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.json.JSONException;
+
+import com.vaadin.server.ClientConnector;
+import com.vaadin.server.Constants;
+import com.vaadin.server.LegacyCommunicationManager.InvalidUIDLSecurityKeyException;
+import com.vaadin.server.ServletPortletHelper;
+import com.vaadin.server.SessionExpiredHandler;
+import com.vaadin.server.SynchronizedRequestHandler;
+import com.vaadin.server.SystemMessages;
+import com.vaadin.server.VaadinRequest;
+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.Version;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.UI;
+
+/**
+ * Processes a UIDL request from the client.
+ *
+ * Uses {@link ServerRpcHandler} to execute client-to-server RPC invocations and
+ * {@link UidlWriter} to write state changes and client RPC calls back to the
+ * client.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class UidlRequestHandler extends SynchronizedRequestHandler implements
+ SessionExpiredHandler {
+
+ public static final String UIDL_PATH = "UIDL/";
+
+ private ServerRpcHandler rpcHandler = new ServerRpcHandler();
+
+ public UidlRequestHandler() {
+ }
+
+ @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
+ // really don't want to see it in the server logs though
+ response.getWriter().write(
+ getUINotFoundErrorJSON(session.getService(), request));
+ return true;
+ }
+
+ checkWidgetsetVersion(request);
+ String requestThemeName = request.getParameter("theme");
+ ClientConnector highlightedConnector;
+ // repaint requested or session has timed out and new one is created
+ boolean repaintAll;
+
+ // TODO PUSH repaintAll, analyzeLayouts, highlightConnector should be
+ // part of the message payload to make the functionality transport
+ // agnostic
+
+ repaintAll = (request
+ .getParameter(ApplicationConstants.URL_PARAMETER_REPAINT_ALL) != null);
+
+ boolean analyzeLayouts = false;
+ if (repaintAll) {
+ // analyzing can be done only with repaintAll
+ analyzeLayouts = (request
+ .getParameter(ApplicationConstants.PARAM_ANALYZE_LAYOUTS) != null);
+
+ String pid = request
+ .getParameter(ApplicationConstants.PARAM_HIGHLIGHT_CONNECTOR);
+ if (pid != null) {
+ highlightedConnector = uI.getConnectorTracker().getConnector(
+ pid);
+ highlightConnector(highlightedConnector);
+ }
+ }
+
+ StringWriter stringWriter = new StringWriter();
+
+ try {
+ rpcHandler.handleRpc(uI, request.getReader(), request);
+
+ if (repaintAll) {
+ session.getCommunicationManager().repaintAll(uI);
+ }
+
+ writeUidl(request, response, uI, stringWriter, repaintAll,
+ analyzeLayouts);
+ } catch (JSONException e) {
+ getLogger().log(Level.SEVERE, "Error writing JSON to response", e);
+ // Refresh on client side
+ response.getWriter().write(
+ VaadinService.createCriticalNotificationJSON(null, null,
+ null, null));
+ return true;
+ } catch (InvalidUIDLSecurityKeyException e) {
+ getLogger().log(Level.WARNING,
+ "Invalid security key received from {0}",
+ request.getRemoteHost());
+ // Refresh on client side
+ response.getWriter().write(
+ VaadinService.createCriticalNotificationJSON(null, null,
+ null, null));
+ return true;
+ } finally {
+ stringWriter.close();
+ requestThemeName = null;
+ }
+
+ return UIInitHandler.commitJsonResponse(request, response,
+ stringWriter.toString());
+ }
+
+ /**
+ * Checks that the version reported by the client (widgetset) matches that
+ * of the server.
+ *
+ * @param request
+ */
+ private void checkWidgetsetVersion(VaadinRequest request) {
+ String widgetsetVersion = request.getParameter("v-wsver");
+ if (widgetsetVersion == null) {
+ // Only check when the widgetset version is reported. It is reported
+ // in the first UIDL request (not the initial request as it is a
+ // plain GET /)
+ return;
+ }
+
+ if (!Version.getFullVersion().equals(widgetsetVersion)) {
+ getLogger().warning(
+ String.format(Constants.WIDGETSET_MISMATCH_INFO,
+ Version.getFullVersion(), widgetsetVersion));
+ }
+ }
+
+ private void writeUidl(VaadinRequest request, VaadinResponse response,
+ UI ui, Writer writer, boolean repaintAll, boolean analyzeLayouts)
+ throws IOException, JSONException {
+ openJsonMessage(writer, response);
+
+ new UidlWriter().write(ui, writer, repaintAll, analyzeLayouts, false);
+
+ closeJsonMessage(writer);
+ }
+
+ protected void closeJsonMessage(Writer outWriter) throws IOException {
+ outWriter.write("}]");
+ }
+
+ /**
+ * Writes the opening of JSON message to be sent to client.
+ *
+ * @param outWriter
+ * @param response
+ * @throws IOException
+ */
+ protected void openJsonMessage(Writer outWriter, VaadinResponse response)
+ throws IOException {
+ // some dirt to prevent cross site scripting
+ outWriter.write("for(;;);[{");
+ }
+
+ // TODO Does this belong here?
+ protected void highlightConnector(ClientConnector highlightedConnector) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("*** Debug details of a connector: *** \n");
+ sb.append("Type: ");
+ sb.append(highlightedConnector.getClass().getName());
+ sb.append("\nId:");
+ sb.append(highlightedConnector.getConnectorId());
+ if (highlightedConnector instanceof Component) {
+ Component component = (Component) highlightedConnector;
+ if (component.getCaption() != null) {
+ sb.append("\nCaption:");
+ sb.append(component.getCaption());
+ }
+ }
+ printHighlightedConnectorHierarchy(sb, highlightedConnector);
+ getLogger().info(sb.toString());
+ }
+
+ // TODO Does this belong here?
+ protected void printHighlightedConnectorHierarchy(StringBuilder sb,
+ ClientConnector connector) {
+ LinkedList<ClientConnector> h = new LinkedList<ClientConnector>();
+ h.add(connector);
+ ClientConnector parent = connector.getParent();
+ while (parent != null) {
+ h.addFirst(parent);
+ parent = parent.getParent();
+ }
+
+ sb.append("\nConnector hierarchy:\n");
+ VaadinSession session2 = connector.getUI().getSession();
+ sb.append(session2.getClass().getName());
+ sb.append("(");
+ sb.append(session2.getClass().getSimpleName());
+ sb.append(".java");
+ sb.append(":1)");
+ int l = 1;
+ for (ClientConnector connector2 : h) {
+ sb.append("\n");
+ for (int i = 0; i < l; i++) {
+ sb.append(" ");
+ }
+ l++;
+ Class<? extends ClientConnector> connectorClass = connector2
+ .getClass();
+ Class<?> topClass = connectorClass;
+ while (topClass.getEnclosingClass() != null) {
+ topClass = topClass.getEnclosingClass();
+ }
+ sb.append(connectorClass.getName());
+ sb.append("(");
+ sb.append(topClass.getSimpleName());
+ sb.append(".java:1)");
+ }
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(UidlRequestHandler.class.getName());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.server.SessionExpiredHandler#handleSessionExpired(com.vaadin
+ * .server.VaadinRequest, com.vaadin.server.VaadinResponse)
+ */
+ @Override
+ public boolean handleSessionExpired(VaadinRequest request,
+ VaadinResponse response) throws IOException {
+ if (!ServletPortletHelper.isUIDLRequest(request)) {
+ return false;
+ }
+ VaadinService service = request.getService();
+ SystemMessages systemMessages = service.getSystemMessages(
+ ServletPortletHelper.findLocale(null, null, request), request);
+
+ service.writeStringResponse(response, JsonConstants.JSON_CONTENT_TYPE,
+ VaadinService.createCriticalNotificationJSON(
+ systemMessages.getSessionExpiredCaption(),
+ systemMessages.getSessionExpiredMessage(), null,
+ systemMessages.getSessionExpiredURL()));
+
+ return true;
+ }
+
+ /**
+ * Returns the JSON which should be returned to the client when a request
+ * for a non-existent UI arrives.
+ *
+ * @param service
+ * The VaadinService
+ * @param vaadinRequest
+ * The request which triggered this, or null if not available
+ * @since 7.1
+ * @return A JSON string
+ */
+ static String getUINotFoundErrorJSON(VaadinService service,
+ VaadinRequest vaadinRequest) {
+ SystemMessages ci = service.getSystemMessages(
+ vaadinRequest.getLocale(), vaadinRequest);
+ // Session Expired is not really the correct message as the
+ // session exists but the requested UI does not.
+ // Using Communication Error for now.
+ String json = VaadinService.createCriticalNotificationJSON(
+ ci.getCommunicationErrorCaption(),
+ ci.getCommunicationErrorMessage(), null,
+ ci.getCommunicationErrorURL());
+
+ return json;
+ }
+
+}
diff --git a/server/src/com/vaadin/server/communication/UidlWriter.java b/server/src/com/vaadin/server/communication/UidlWriter.java
new file mode 100644
index 0000000000..fbe2fb86d5
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/UidlWriter.java
@@ -0,0 +1,317 @@
+/*
+ * 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.io.IOException;
+import java.io.Serializable;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import com.vaadin.annotations.JavaScript;
+import com.vaadin.annotations.StyleSheet;
+import com.vaadin.server.ClientConnector;
+import com.vaadin.server.JsonPaintTarget;
+import com.vaadin.server.LegacyCommunicationManager;
+import com.vaadin.server.LegacyCommunicationManager.ClientCache;
+import com.vaadin.server.SystemMessages;
+import com.vaadin.server.VaadinSession;
+import com.vaadin.ui.ConnectorTracker;
+import com.vaadin.ui.UI;
+
+/**
+ * Serializes pending server-side changes to UI state to JSON. This includes
+ * shared state, client RPC invocations, connector hierarchy changes, connector
+ * type information among others.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class UidlWriter implements Serializable {
+
+ /**
+ * Writes a JSON object containing all pending changes to the given UI.
+ *
+ * @param ui
+ * The {@link UI} whose changes to write
+ * @param writer
+ * The writer to use
+ * @param repaintAll
+ * Whether the client should re-render the whole UI.
+ * @param analyzeLayouts
+ * Whether detected layout problems should be logged.
+ * @param async
+ * True if this message is sent by the server asynchronously,
+ * false if it is a response to a client message.
+ *
+ * @throws IOException
+ * If the writing fails.
+ * @throws JSONException
+ * If the JSON serialization fails.
+ */
+ public void write(UI ui, Writer writer, boolean repaintAll,
+ boolean analyzeLayouts, boolean async) throws IOException,
+ JSONException {
+ ArrayList<ClientConnector> dirtyVisibleConnectors = ui
+ .getConnectorTracker().getDirtyVisibleConnectors();
+ VaadinSession session = ui.getSession();
+ LegacyCommunicationManager manager = session.getCommunicationManager();
+ // Paints components
+ ConnectorTracker uiConnectorTracker = ui.getConnectorTracker();
+ getLogger().log(Level.FINE, "* Creating response to client");
+
+ getLogger().log(
+ Level.FINE,
+ "Found " + dirtyVisibleConnectors.size()
+ + " dirty connectors to paint");
+ for (ClientConnector connector : dirtyVisibleConnectors) {
+ boolean initialized = uiConnectorTracker
+ .isClientSideInitialized(connector);
+ connector.beforeClientResponse(!initialized);
+ }
+
+ uiConnectorTracker.setWritingResponse(true);
+ try {
+ writer.write("\"changes\" : ");
+
+ JsonPaintTarget paintTarget = new JsonPaintTarget(manager, writer,
+ !repaintAll);
+
+ new LegacyUidlWriter().write(ui, writer, paintTarget);
+
+ paintTarget.close();
+ writer.write(", "); // close changes
+
+ // send shared state to client
+
+ // for now, send the complete state of all modified and new
+ // components
+
+ // Ideally, all this would be sent before "changes", but that causes
+ // complications with legacy components that create sub-components
+ // in their paint phase. Nevertheless, this will be processed on the
+ // client after component creation but before legacy UIDL
+ // processing.
+
+ writer.write("\"state\":");
+ new SharedStateWriter().write(ui, writer);
+ writer.write(", "); // close states
+
+ // TODO This should be optimized. The type only needs to be
+ // sent once for each connector id + on refresh. Use the same cache
+ // as
+ // widget mapping
+
+ writer.write("\"types\":");
+ new ConnectorTypeWriter().write(ui, writer, paintTarget);
+ writer.write(", "); // close states
+
+ // Send update hierarchy information to the client.
+
+ // This could be optimized aswell to send only info if hierarchy has
+ // actually changed. Much like with the shared state. Note though
+ // that an empty hierarchy is information aswell (e.g. change from 1
+ // child to 0 children)
+
+ writer.write("\"hierarchy\":");
+ new ConnectorHierarchyWriter().write(ui, writer);
+ writer.write(", "); // close hierarchy
+
+ // send server to client RPC calls for components in the UI, in call
+ // order
+
+ // collect RPC calls from components in the UI in the order in
+ // which they were performed, remove the calls from components
+
+ writer.write("\"rpc\" : ");
+ new ClientRpcWriter().write(ui, writer);
+ writer.write(", "); // close rpc
+
+ uiConnectorTracker.markAllConnectorsClean();
+
+ writer.write("\"meta\" : ");
+
+ SystemMessages messages = ui.getSession().getService()
+ .getSystemMessages(ui.getLocale(), null);
+ // TODO hilightedConnector
+ new MetadataWriter().write(ui, writer, repaintAll, analyzeLayouts,
+ async, null, messages);
+ writer.write(", ");
+
+ writer.write("\"resources\" : ");
+ new ResourceWriter().write(ui, writer, paintTarget);
+
+ Collection<Class<? extends ClientConnector>> usedClientConnectors = paintTarget
+ .getUsedClientConnectors();
+ boolean typeMappingsOpen = false;
+ ClientCache clientCache = manager.getClientCache(ui);
+
+ List<Class<? extends ClientConnector>> newConnectorTypes = new ArrayList<Class<? extends ClientConnector>>();
+
+ for (Class<? extends ClientConnector> class1 : usedClientConnectors) {
+ if (clientCache.cache(class1)) {
+ // client does not know the mapping key for this type, send
+ // mapping to client
+ newConnectorTypes.add(class1);
+
+ if (!typeMappingsOpen) {
+ typeMappingsOpen = true;
+ writer.write(", \"typeMappings\" : { ");
+ } else {
+ writer.write(" , ");
+ }
+ String canonicalName = class1.getCanonicalName();
+ writer.write("\"");
+ writer.write(canonicalName);
+ writer.write("\" : ");
+ writer.write(manager.getTagForType(class1));
+ }
+ }
+ if (typeMappingsOpen) {
+ writer.write(" }");
+ }
+
+ // TODO PUSH Refactor to TypeInheritanceWriter or something
+ boolean typeInheritanceMapOpen = false;
+ if (typeMappingsOpen) {
+ // send the whole type inheritance map if any new mappings
+ for (Class<? extends ClientConnector> class1 : usedClientConnectors) {
+ if (!ClientConnector.class.isAssignableFrom(class1
+ .getSuperclass())) {
+ continue;
+ }
+ if (!typeInheritanceMapOpen) {
+ typeInheritanceMapOpen = true;
+ writer.write(", \"typeInheritanceMap\" : { ");
+ } else {
+ writer.write(" , ");
+ }
+ writer.write("\"");
+ writer.write(manager.getTagForType(class1));
+ writer.write("\" : ");
+ writer.write(manager
+ .getTagForType((Class<? extends ClientConnector>) class1
+ .getSuperclass()));
+ }
+ if (typeInheritanceMapOpen) {
+ writer.write(" }");
+ }
+ }
+
+ // TODO Refactor to DependencyWriter or something
+ /*
+ * Ensure super classes come before sub classes to get script
+ * dependency order right. Sub class @JavaScript might assume that
+ *
+ * @JavaScript defined by super class is already loaded.
+ */
+ Collections.sort(newConnectorTypes, new Comparator<Class<?>>() {
+ @Override
+ public int compare(Class<?> o1, Class<?> o2) {
+ // TODO optimize using Class.isAssignableFrom?
+ return hierarchyDepth(o1) - hierarchyDepth(o2);
+ }
+
+ private int hierarchyDepth(Class<?> type) {
+ if (type == Object.class) {
+ return 0;
+ } else {
+ return hierarchyDepth(type.getSuperclass()) + 1;
+ }
+ }
+ });
+
+ List<String> scriptDependencies = new ArrayList<String>();
+ List<String> styleDependencies = new ArrayList<String>();
+
+ for (Class<? extends ClientConnector> class1 : newConnectorTypes) {
+ JavaScript jsAnnotation = class1
+ .getAnnotation(JavaScript.class);
+ if (jsAnnotation != null) {
+ for (String uri : jsAnnotation.value()) {
+ scriptDependencies.add(manager.registerDependency(uri,
+ class1));
+ }
+ }
+
+ StyleSheet styleAnnotation = class1
+ .getAnnotation(StyleSheet.class);
+ if (styleAnnotation != null) {
+ for (String uri : styleAnnotation.value()) {
+ styleDependencies.add(manager.registerDependency(uri,
+ class1));
+ }
+ }
+ }
+
+ // Include script dependencies in output if there are any
+ if (!scriptDependencies.isEmpty()) {
+ writer.write(", \"scriptDependencies\": "
+ + new JSONArray(scriptDependencies).toString());
+ }
+
+ // Include style dependencies in output if there are any
+ if (!styleDependencies.isEmpty()) {
+ writer.write(", \"styleDependencies\": "
+ + new JSONArray(styleDependencies).toString());
+ }
+
+ // add any pending locale definitions requested by the client
+ writer.write(", \"locales\": ");
+ manager.printLocaleDeclarations(writer);
+
+ if (manager.getDragAndDropService() != null) {
+ manager.getDragAndDropService().printJSONResponse(writer);
+ }
+
+ for (ClientConnector connector : dirtyVisibleConnectors) {
+ uiConnectorTracker.markClientSideInitialized(connector);
+ }
+
+ assert (uiConnectorTracker.getDirtyConnectors().isEmpty()) : "Connectors have been marked as dirty during the end of the paint phase. This is most certainly not intended.";
+
+ writePerformanceData(ui, writer);
+ } finally {
+ uiConnectorTracker.setWritingResponse(false);
+ uiConnectorTracker.cleanConnectorMap();
+ }
+ }
+
+ /**
+ * Adds the performance timing data (used by TestBench 3) to the UIDL
+ * response.
+ *
+ * @throws IOException
+ */
+ private void writePerformanceData(UI ui, Writer writer) throws IOException {
+ writer.write(String.format(", \"timings\":[%d, %d]", ui.getSession()
+ .getCumulativeRequestDuration(), ui.getSession()
+ .getLastRequestDuration()));
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(UidlWriter.class.getName());
+ }
+}
diff --git a/server/src/com/vaadin/server/themeutils/SASSAddonImportFileCreator.java b/server/src/com/vaadin/server/themeutils/SASSAddonImportFileCreator.java
new file mode 100644
index 0000000000..f199c347eb
--- /dev/null
+++ b/server/src/com/vaadin/server/themeutils/SASSAddonImportFileCreator.java
@@ -0,0 +1,200 @@
+/*
+ * 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.themeutils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+import com.vaadin.server.widgetsetutils.ClassPathExplorer;
+import com.vaadin.server.widgetsetutils.ClassPathExplorer.LocationInfo;
+
+/**
+ * Helper class for managing the addon imports and creating an a SCSS file for
+ * importing all your addon themes. The helper method searches the classpath for
+ * Vaadin addons and uses the 'Vaadin-Themes' metadata to create the imports.
+ *
+ * <p>
+ * The addons.scss is always overwritten when this tool is invoked.
+ * </p>
+ *
+ * @since 7.1
+ */
+public class SASSAddonImportFileCreator {
+
+ private static final String ADDON_IMPORTS_FILE = "addons.scss";
+
+ private static final String ADDON_IMPORTS_FILE_TEXT = "This file is automatically managed and "
+ + "will be overwritten from time to time.";
+
+ /**
+ *
+ * @param args
+ * Theme directory where the addons.scss file should be created
+ */
+ public static void main(String[] args) throws IOException {
+ if (args.length == 0) {
+ printUsage();
+ } else {
+ String themeDirectory = args[0];
+ updateTheme(themeDirectory);
+ }
+ }
+
+ /**
+ * Updates a themes addons.scss with the addon themes found on the classpath
+ *
+ * @param themeDirectory
+ * The target theme directory
+ */
+ public static void updateTheme(String themeDirectory) throws IOException {
+
+ File addonImports = new File(themeDirectory, ADDON_IMPORTS_FILE);
+
+ if (!addonImports.exists()) {
+
+ // Ensure directory exists
+ addonImports.getParentFile().mkdirs();
+
+ // Ensure file exists
+ addonImports.createNewFile();
+ }
+
+ LocationInfo info = ClassPathExplorer
+ .getAvailableWidgetSetsAndStylesheets();
+
+ try {
+ PrintStream printStream = new PrintStream(new FileOutputStream(
+ addonImports));
+
+ printStream.println("/* " + ADDON_IMPORTS_FILE_TEXT + " */");
+
+ printStream.println("/* Do not manually edit this file. */");
+
+ printStream.println();
+
+ Map<String, URL> addonThemes = info.getAddonStyles();
+
+ // Sort addon styles so that CSS imports are first and SCSS import
+ // last
+ List<String> paths = new ArrayList<String>(addonThemes.keySet());
+ Collections.sort(paths, new Comparator<String>() {
+
+ @Override
+ public int compare(String path1, String path2) {
+ if (path1.toLowerCase().endsWith(".css")
+ && path2.toLowerCase().endsWith(".scss")) {
+ return -1;
+ }
+ if (path1.toLowerCase().endsWith(".scss")
+ && path2.toLowerCase().endsWith(".css")) {
+ return 1;
+ }
+ return 0;
+ }
+ });
+
+ List<String> mixins = new ArrayList<String>();
+ for (String path : paths) {
+ mixins.addAll(addImport(printStream, path,
+ addonThemes.get(path)));
+ printStream.println();
+ }
+
+ createAddonsMixin(printStream, mixins);
+
+ } catch (FileNotFoundException e) {
+ // Should not happen since file is checked before this
+ e.printStackTrace();
+ }
+ }
+
+ private static List<String> addImport(PrintStream stream, String file,
+ URL location) {
+
+ // Add import comment
+ printImportComment(stream, location);
+
+ List<String> foundMixins = new ArrayList<String>();
+
+ if (file.endsWith(".css")) {
+ stream.print("@import url(\"../../../" + file + "\");\n");
+ } else {
+ // Assume SASS
+ stream.print("@import \"../../../" + file + "\";\n");
+
+ // Convention is to name the mixin after the stylesheet. Strip
+ // .scss from filename
+ String mixin = file.substring(file.lastIndexOf("/") + 1,
+ file.length() - ".scss".length());
+
+ foundMixins.add(mixin);
+ }
+
+ stream.println();
+
+ return foundMixins;
+ }
+
+ private static void printImportComment(PrintStream stream, URL location) {
+
+ // file:/absolute/path/to/addon.jar!/
+ String path = location.getPath();
+
+ try {
+ // Try to parse path for better readability
+ path = path.substring(path.lastIndexOf(":") + 1,
+ path.lastIndexOf("!"));
+
+ // Extract jar archive filename
+ path = path.substring(path.lastIndexOf("/") + 1);
+
+ } catch (Exception e) {
+ // Parsing failed but no worries, we then use whatever
+ // location.getPath() returns
+ }
+
+ stream.println("/* Provided by " + path + " */");
+ }
+
+ private static void createAddonsMixin(PrintStream stream,
+ List<String> mixins) {
+
+ stream.println("/* Import and include this mixin into your project theme to include the addon themes */");
+ stream.println("@mixin addons {");
+ for (String addon : mixins) {
+ stream.println("\t@include " + addon + ";");
+ }
+ stream.println("}");
+ stream.println();
+ }
+
+ private static void printUsage() {
+ String className = SASSAddonImportFileCreator.class.getSimpleName();
+ PrintStream o = System.out;
+ o.println(className + " usage:");
+ o.println();
+ o.println("./" + className + " [Path to target theme folder]");
+ }
+}
diff --git a/server/src/com/vaadin/server/widgetsetutils/ClassPathExplorer.java b/server/src/com/vaadin/server/widgetsetutils/ClassPathExplorer.java
new file mode 100644
index 0000000000..cc04e50b3c
--- /dev/null
+++ b/server/src/com/vaadin/server/widgetsetutils/ClassPathExplorer.java
@@ -0,0 +1,538 @@
+/*
+ * 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.widgetsetutils;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.net.JarURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Utility class to collect widgetset related information from classpath.
+ * Utility will seek all directories from classpaths, and jar files having
+ * "Vaadin-Widgetsets" key in their manifest file.
+ * <p>
+ * Used by WidgetMapGenerator and ide tools to implement some monkey coding for
+ * you.
+ * <p>
+ * Developer notice: If you end up reading this comment, I guess you have faced
+ * a sluggish performance of widget compilation or unreliable detection of
+ * components in your classpaths. The thing you might be able to do is to use
+ * annotation processing tool like apt to generate the needed information. Then
+ * either use that information in {@link WidgetMapGenerator} or create the
+ * appropriate monkey code for gwt directly in annotation processor and get rid
+ * of {@link WidgetMapGenerator}. Using annotation processor might be a good
+ * idea when dropping Java 1.5 support (integrated to javac in 6).
+ *
+ */
+public class ClassPathExplorer {
+
+ private static final String VAADIN_ADDON_VERSION_ATTRIBUTE = "Vaadin-Package-Version";
+
+ /**
+ * File filter that only accepts directories.
+ */
+ private final static FileFilter DIRECTORIES_ONLY = new FileFilter() {
+ @Override
+ public boolean accept(File f) {
+ if (f.exists() && f.isDirectory()) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ };
+
+ /**
+ * Contains information about widgetsets and themes found on the classpath
+ *
+ * @since 7.1
+ */
+ public static class LocationInfo {
+
+ private final Map<String, URL> widgetsets;
+
+ private final Map<String, URL> addonStyles;
+
+ public LocationInfo(Map<String, URL> widgetsets, Map<String, URL> themes) {
+ this.widgetsets = widgetsets;
+ addonStyles = themes;
+ }
+
+ public Map<String, URL> getWidgetsets() {
+ return widgetsets;
+ }
+
+ public Map<String, URL> getAddonStyles() {
+ return addonStyles;
+ }
+
+ }
+
+ /**
+ * Raw class path entries as given in the java class path string. Only
+ * entries that could include widgets/widgetsets are listed (primarily
+ * directories, Vaadin JARs and add-on JARs).
+ */
+ private static List<String> rawClasspathEntries = getRawClasspathEntries();
+
+ /**
+ * Map from identifiers (either a package name preceded by the path and a
+ * slash, or a URL for a JAR file) to the corresponding URLs. This is
+ * constructed from the class path.
+ */
+ private static Map<String, URL> classpathLocations = getClasspathLocations(rawClasspathEntries);
+
+ /**
+ * No instantiation from outside, callable methods are static.
+ */
+ private ClassPathExplorer() {
+ }
+
+ /**
+ * Finds the names and locations of widgetsets available on the class path.
+ *
+ * @return map from widgetset classname to widgetset location URL
+ * @deprecated Use {@link #getAvailableWidgetSetsAndStylesheets()} instead
+ */
+ @Deprecated
+ public static Map<String, URL> getAvailableWidgetSets() {
+ return getAvailableWidgetSetsAndStylesheets().getWidgetsets();
+ }
+
+ /**
+ * Finds the names and locations of widgetsets and themes available on the
+ * class path.
+ *
+ * @return
+ */
+ public static LocationInfo getAvailableWidgetSetsAndStylesheets() {
+ long start = System.currentTimeMillis();
+ Map<String, URL> widgetsets = new HashMap<String, URL>();
+ Map<String, URL> themes = new HashMap<String, URL>();
+ Set<String> keySet = classpathLocations.keySet();
+ for (String location : keySet) {
+ searchForWidgetSetsAndAddonStyles(location, widgetsets, themes);
+ }
+ long end = System.currentTimeMillis();
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("Widgetsets found from classpath:\n");
+ for (String ws : widgetsets.keySet()) {
+ sb.append("\t");
+ sb.append(ws);
+ sb.append(" in ");
+ sb.append(widgetsets.get(ws));
+ sb.append("\n");
+ }
+
+ sb.append("Addon styles found from classpath:\n");
+ for (String theme : themes.keySet()) {
+ sb.append("\t");
+ sb.append(theme);
+ sb.append(" in ");
+ sb.append(themes.get(theme));
+ sb.append("\n");
+ }
+
+ final Logger logger = getLogger();
+ logger.info(sb.toString());
+ logger.info("Search took " + (end - start) + "ms");
+ return new LocationInfo(widgetsets, themes);
+ }
+
+ /**
+ * Finds all GWT modules / Vaadin widgetsets and Addon styles in a valid
+ * location.
+ *
+ * If the location is a directory, all GWT modules (files with the
+ * ".gwt.xml" extension) are added to widgetsets.
+ *
+ * If the location is a JAR file, the comma-separated values of the
+ * "Vaadin-Widgetsets" attribute in its manifest are added to widgetsets.
+ *
+ * @param locationString
+ * an entry in {@link #classpathLocations}
+ * @param widgetsets
+ * a map from widgetset name (including package, with dots as
+ * separators) to a URL (see {@link #classpathLocations}) - new
+ * entries are added to this map
+ */
+ private static void searchForWidgetSetsAndAddonStyles(
+ String locationString, Map<String, URL> widgetsets,
+ Map<String, URL> addonStyles) {
+
+ URL location = classpathLocations.get(locationString);
+ File directory = new File(location.getFile());
+
+ if (directory.exists() && !directory.isHidden()) {
+ // Get the list of the files contained in the directory
+ String[] files = directory.list();
+ for (int i = 0; i < files.length; i++) {
+ // we are only interested in .gwt.xml files
+ if (!files[i].endsWith(".gwt.xml")) {
+ continue;
+ }
+
+ // remove the .gwt.xml extension
+ String classname = files[i].substring(0, files[i].length() - 8);
+ String packageName = locationString.substring(locationString
+ .lastIndexOf("/") + 1);
+ classname = packageName + "." + classname;
+
+ if (!WidgetSetBuilder.isWidgetset(classname)) {
+ // Only return widgetsets and not GWT modules to avoid
+ // comparing modules and widgetsets
+ continue;
+ }
+
+ if (!widgetsets.containsKey(classname)) {
+ String packagePath = packageName.replaceAll("\\.", "/");
+ String basePath = location.getFile().replaceAll(
+ "/" + packagePath + "$", "");
+ try {
+ URL url = new URL(location.getProtocol(),
+ location.getHost(), location.getPort(),
+ basePath);
+ widgetsets.put(classname, url);
+ } catch (MalformedURLException e) {
+ // should never happen as based on an existing URL,
+ // only changing end of file name/path part
+ getLogger().log(Level.SEVERE,
+ "Error locating the widgetset " + classname, e);
+ }
+ }
+ }
+ } else {
+
+ try {
+ // check files in jar file, entries will list all directories
+ // and files in jar
+
+ URLConnection openConnection = location.openConnection();
+ if (openConnection instanceof JarURLConnection) {
+ JarURLConnection conn = (JarURLConnection) openConnection;
+
+ JarFile jarFile = conn.getJarFile();
+
+ Manifest manifest = jarFile.getManifest();
+ if (manifest == null) {
+ // No manifest so this is not a Vaadin Add-on
+ return;
+ }
+
+ // Check for widgetset attribute
+ String value = manifest.getMainAttributes().getValue(
+ "Vaadin-Widgetsets");
+ if (value != null) {
+ String[] widgetsetNames = value.split(",");
+ for (int i = 0; i < widgetsetNames.length; i++) {
+ String widgetsetname = widgetsetNames[i].trim();
+ if (!widgetsetname.equals("")) {
+ widgetsets.put(widgetsetname, location);
+ }
+ }
+ }
+
+ // Check for theme attribute
+ value = manifest.getMainAttributes().getValue(
+ "Vaadin-Stylesheets");
+ if (value != null) {
+ String[] stylesheets = value.split(",");
+ for (int i = 0; i < stylesheets.length; i++) {
+ String stylesheet = stylesheets[i].trim();
+ if (!stylesheet.equals("")) {
+ addonStyles.put(stylesheet, location);
+ }
+ }
+ }
+ }
+ } catch (IOException e) {
+ getLogger().log(Level.WARNING, "Error parsing jar file", e);
+ }
+
+ }
+ }
+
+ /**
+ * Splits the current class path into entries, and filters them accepting
+ * directories, Vaadin add-on JARs with widgetsets and Vaadin JARs.
+ *
+ * Some other non-JAR entries may also be included in the result.
+ *
+ * @return filtered list of class path entries
+ */
+ private final static List<String> getRawClasspathEntries() {
+ // try to keep the order of the classpath
+ List<String> locations = new ArrayList<String>();
+
+ String pathSep = System.getProperty("path.separator");
+ String classpath = System.getProperty("java.class.path");
+
+ if (classpath.startsWith("\"")) {
+ classpath = classpath.substring(1);
+ }
+ if (classpath.endsWith("\"")) {
+ classpath = classpath.substring(0, classpath.length() - 1);
+ }
+
+ getLogger().log(Level.FINE, "Classpath: {0}", classpath);
+
+ String[] split = classpath.split(pathSep);
+ for (int i = 0; i < split.length; i++) {
+ String classpathEntry = split[i];
+ if (acceptClassPathEntry(classpathEntry)) {
+ locations.add(classpathEntry);
+ }
+ }
+
+ return locations;
+ }
+
+ /**
+ * Determine every URL location defined by the current classpath, and it's
+ * associated package name.
+ *
+ * See {@link #classpathLocations} for information on output format.
+ *
+ * @param rawClasspathEntries
+ * raw class path entries as split from the Java class path
+ * string
+ * @return map of classpath locations, see {@link #classpathLocations}
+ */
+ private final static Map<String, URL> getClasspathLocations(
+ List<String> rawClasspathEntries) {
+ long start = System.currentTimeMillis();
+ // try to keep the order of the classpath
+ Map<String, URL> locations = new LinkedHashMap<String, URL>();
+ for (String classpathEntry : rawClasspathEntries) {
+ File file = new File(classpathEntry);
+ include(null, file, locations);
+ }
+ long end = System.currentTimeMillis();
+ Logger logger = getLogger();
+ if (logger.isLoggable(Level.FINE)) {
+ logger.fine("getClassPathLocations took " + (end - start) + "ms");
+ }
+ return locations;
+ }
+
+ /**
+ * Checks a class path entry to see whether it can contain widgets and
+ * widgetsets.
+ *
+ * All directories are automatically accepted. JARs are accepted if they
+ * have the "Vaadin-Widgetsets" attribute in their manifest or the JAR file
+ * name contains "vaadin-" or ".vaadin.".
+ *
+ * Also other non-JAR entries may be accepted, the caller should be prepared
+ * to handle them.
+ *
+ * @param classpathEntry
+ * class path entry string as given in the Java class path
+ * @return true if the entry should be considered when looking for widgets
+ * or widgetsets
+ */
+ private static boolean acceptClassPathEntry(String classpathEntry) {
+ if (!classpathEntry.endsWith(".jar")) {
+ // accept all non jars (practically directories)
+ return true;
+ } else {
+ // accepts jars that comply with vaadin-component packaging
+ // convention (.vaadin. or vaadin- as distribution packages),
+ if (classpathEntry.contains("vaadin-")
+ || classpathEntry.contains(".vaadin.")) {
+ return true;
+ } else {
+ URL url;
+ try {
+ url = new URL("file:"
+ + new File(classpathEntry).getCanonicalPath());
+ url = new URL("jar:" + url.toExternalForm() + "!/");
+ JarURLConnection conn = (JarURLConnection) url
+ .openConnection();
+ getLogger().fine(url.toString());
+ JarFile jarFile = conn.getJarFile();
+ Manifest manifest = jarFile.getManifest();
+ if (manifest != null) {
+ Attributes mainAttributes = manifest
+ .getMainAttributes();
+ if (mainAttributes.getValue("Vaadin-Widgetsets") != null) {
+ return true;
+ }
+ if (mainAttributes.getValue("Vaadin-Stylesheets") != null) {
+ return true;
+ }
+ }
+ } catch (MalformedURLException e) {
+ getLogger().log(Level.FINEST, "Failed to inspect JAR file",
+ e);
+ } catch (IOException e) {
+ getLogger().log(Level.FINEST, "Failed to inspect JAR file",
+ e);
+ }
+
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Recursively add subdirectories and jar files to locations - see
+ * {@link #classpathLocations}.
+ *
+ * @param name
+ * @param file
+ * @param locations
+ */
+ private final static void include(String name, File file,
+ Map<String, URL> locations) {
+ if (!file.exists()) {
+ return;
+ }
+ if (!file.isDirectory()) {
+ // could be a JAR file
+ includeJar(file, locations);
+ return;
+ }
+
+ if (file.isHidden() || file.getPath().contains(File.separator + ".")) {
+ return;
+ }
+
+ if (name == null) {
+ name = "";
+ } else {
+ name += ".";
+ }
+
+ // add all directories recursively
+ File[] dirs = file.listFiles(DIRECTORIES_ONLY);
+ for (int i = 0; i < dirs.length; i++) {
+ try {
+ // add the present directory
+ if (!dirs[i].isHidden()
+ && !dirs[i].getPath().contains(File.separator + ".")) {
+ String key = dirs[i].getCanonicalPath() + "/" + name
+ + dirs[i].getName();
+ locations.put(key,
+ new URL("file://" + dirs[i].getCanonicalPath()));
+ }
+ } catch (Exception ioe) {
+ return;
+ }
+ include(name + dirs[i].getName(), dirs[i], locations);
+ }
+ }
+
+ /**
+ * Add a jar file to locations - see {@link #classpathLocations}.
+ *
+ * @param name
+ * @param locations
+ */
+ private static void includeJar(File file, Map<String, URL> locations) {
+ try {
+ URL url = new URL("file:" + file.getCanonicalPath());
+ url = new URL("jar:" + url.toExternalForm() + "!/");
+ JarURLConnection conn = (JarURLConnection) url.openConnection();
+ JarFile jarFile = conn.getJarFile();
+ if (jarFile != null) {
+ // the key does not matter here as long as it is unique
+ locations.put(url.toString(), url);
+ }
+ } catch (Exception e) {
+ // e.printStackTrace();
+ return;
+ }
+
+ }
+
+ /**
+ * Find and return the default source directory where to create new
+ * widgetsets.
+ *
+ * Return the first directory (not a JAR file etc.) on the classpath by
+ * default.
+ *
+ * TODO this could be done better...
+ *
+ * @return URL
+ */
+ public static URL getDefaultSourceDirectory() {
+
+ final Logger logger = getLogger();
+
+ if (logger.isLoggable(Level.FINE)) {
+ logger.fine("classpathLocations values:");
+ ArrayList<String> locations = new ArrayList<String>(
+ classpathLocations.keySet());
+ for (String location : locations) {
+ logger.fine(String.valueOf(classpathLocations.get(location)));
+ }
+ }
+
+ Iterator<String> it = rawClasspathEntries.iterator();
+ while (it.hasNext()) {
+ String entry = it.next();
+
+ File directory = new File(entry);
+ if (directory.exists() && !directory.isHidden()
+ && directory.isDirectory()) {
+ try {
+ return new URL("file://" + directory.getCanonicalPath());
+ } catch (MalformedURLException e) {
+ logger.log(Level.FINEST, "Ignoring exception", e);
+ // ignore: continue to the next classpath entry
+ } catch (IOException e) {
+ logger.log(Level.FINEST, "Ignoring exception", e);
+ // ignore: continue to the next classpath entry
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Test method for helper tool
+ */
+ public static void main(String[] args) {
+ getLogger().info(
+ "Searching for available widgetsets and stylesheets...");
+
+ ClassPathExplorer.getAvailableWidgetSetsAndStylesheets();
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(ClassPathExplorer.class.getName());
+ }
+
+}
diff --git a/server/src/com/vaadin/server/widgetsetutils/WidgetSetBuilder.java b/server/src/com/vaadin/server/widgetsetutils/WidgetSetBuilder.java
new file mode 100644
index 0000000000..3a0e59df71
--- /dev/null
+++ b/server/src/com/vaadin/server/widgetsetutils/WidgetSetBuilder.java
@@ -0,0 +1,213 @@
+/*
+ * 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.widgetsetutils;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintStream;
+import java.io.Reader;
+import java.net.URL;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Helper class to update widgetsets GWT module configuration file. Can be used
+ * command line or via IDE tools.
+ *
+ * <p>
+ * If module definition file contains text "WS Compiler: manually edited", tool
+ * will skip editing file.
+ *
+ */
+public class WidgetSetBuilder {
+
+ public static void main(String[] args) throws IOException {
+ if (args.length == 0) {
+ printUsage();
+ } else {
+ String widgetsetname = args[0];
+ updateWidgetSet(widgetsetname);
+
+ }
+ }
+
+ public static void updateWidgetSet(final String widgetset)
+ throws IOException, FileNotFoundException {
+ boolean changed = false;
+
+ Map<String, URL> availableWidgetSets = ClassPathExplorer
+ .getAvailableWidgetSets();
+
+ URL sourceUrl = availableWidgetSets.get(widgetset);
+ if (sourceUrl == null) {
+ // find first/default source directory
+ sourceUrl = ClassPathExplorer.getDefaultSourceDirectory();
+ }
+
+ String widgetsetfilename = sourceUrl.getFile() + "/"
+ + widgetset.replace(".", "/") + ".gwt.xml";
+
+ File widgetsetFile = new File(widgetsetfilename);
+ if (!widgetsetFile.exists()) {
+ // create empty gwt module file
+ File parent = widgetsetFile.getParentFile();
+ if (parent != null && !parent.exists()) {
+ if (!parent.mkdirs()) {
+ throw new IOException(
+ "Could not create directory for the widgetset: "
+ + parent.getPath());
+ }
+ }
+ widgetsetFile.createNewFile();
+ PrintStream printStream = new PrintStream(new FileOutputStream(
+ widgetsetFile));
+ printStream.print("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<!DOCTYPE module PUBLIC \"-//Google Inc.//DTD "
+ + "Google Web Toolkit 1.7.0//EN\" \"http://google"
+ + "-web-toolkit.googlecode.com/svn/tags/1.7.0/dis"
+ + "tro-source/core/src/gwt-module.dtd\">\n");
+ printStream.print("<module>\n");
+ printStream
+ .print(" <!--\n"
+ + " Uncomment the following to compile the widgetset for one browser only.\n"
+ + " This can reduce the GWT compilation time significantly when debugging.\n"
+ + " The line should be commented out before deployment to production\n"
+ + " environments.\n\n"
+ + " Multiple browsers can be specified for GWT 1.7 as a comma separated\n"
+ + " list. The supported user agents at the moment of writing were:\n"
+ + " ie6,ie8,gecko,gecko1_8,safari,opera\n\n"
+ + " The value gecko1_8 is used for Firefox 3 and later and safari is used for\n"
+ + " webkit based browsers including Google Chrome.\n"
+ + " -->\n"
+ + " <!-- <set-property name=\"user.agent\" value=\"gecko1_8\"/> -->\n");
+ printStream.print("\n</module>\n");
+ printStream.close();
+ changed = true;
+ }
+
+ String content = readFile(widgetsetFile);
+ if (isEditable(content)) {
+ String originalContent = content;
+
+ Collection<String> oldInheritedWidgetsets = getCurrentInheritedWidgetsets(content);
+
+ // add widgetsets that do not exist
+ Iterator<String> i = availableWidgetSets.keySet().iterator();
+ while (i.hasNext()) {
+ String ws = i.next();
+ if (ws.equals(widgetset)) {
+ // do not inherit the module itself
+ continue;
+ }
+ if (!oldInheritedWidgetsets.contains(ws)) {
+ content = addWidgetSet(ws, content);
+ }
+ }
+
+ for (String ws : oldInheritedWidgetsets) {
+ if (!availableWidgetSets.containsKey(ws)) {
+ // widgetset not available in classpath
+ content = removeWidgetSet(ws, content);
+ }
+ }
+
+ changed = changed || !content.equals(originalContent);
+ if (changed) {
+ commitChanges(widgetsetfilename, content);
+ }
+ } else {
+ System.out
+ .println("Widgetset is manually edited. Skipping updates.");
+ }
+ }
+
+ private static boolean isEditable(String content) {
+ return !content.contains("WS Compiler: manually edited");
+ }
+
+ private static String removeWidgetSet(String ws, String content) {
+ return content.replaceFirst("<inherits name=\"" + ws + "\"[^/]*/>", "");
+ }
+
+ private static void commitChanges(String widgetsetfilename, String content)
+ throws IOException {
+ BufferedWriter bufferedWriter = new BufferedWriter(
+ new OutputStreamWriter(new FileOutputStream(widgetsetfilename)));
+ bufferedWriter.write(content);
+ bufferedWriter.close();
+ }
+
+ private static String addWidgetSet(String ws, String content) {
+ return content.replace("</module>", "\n <inherits name=\"" + ws
+ + "\" />" + "\n</module>");
+ }
+
+ private static Collection<String> getCurrentInheritedWidgetsets(
+ String content) {
+ HashSet<String> hashSet = new HashSet<String>();
+ Pattern inheritsPattern = Pattern.compile(" name=\"([^\"]*)\"");
+
+ Matcher matcher = inheritsPattern.matcher(content);
+
+ while (matcher.find()) {
+ String gwtModule = matcher.group(1);
+ if (isWidgetset(gwtModule)) {
+ hashSet.add(gwtModule);
+ }
+ }
+ return hashSet;
+ }
+
+ static boolean isWidgetset(String gwtModuleName) {
+ return gwtModuleName.toLowerCase().contains("widgetset");
+ }
+
+ private static String readFile(File widgetsetFile) throws IOException {
+ Reader fi = new FileReader(widgetsetFile);
+ BufferedReader bufferedReader = new BufferedReader(fi);
+ StringBuilder sb = new StringBuilder();
+ String line;
+ while ((line = bufferedReader.readLine()) != null) {
+ sb.append(line);
+ sb.append("\n");
+ }
+ fi.close();
+ return sb.toString();
+ }
+
+ private static void printUsage() {
+ PrintStream o = System.out;
+ o.println(WidgetSetBuilder.class.getSimpleName() + " usage:");
+ o.println(" 1. Set the same classpath as you will "
+ + "have for the GWT compiler.");
+ o.println(" 2. Give the widgetsetname (to be created or updated)"
+ + " as first parameter");
+ o.println();
+ o.println("All found vaadin widgetsets will be inherited in given widgetset");
+
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/AbstractColorPicker.java b/server/src/com/vaadin/ui/AbstractColorPicker.java
index d7037e366d..c3bdd49155 100644
--- a/server/src/com/vaadin/ui/AbstractColorPicker.java
+++ b/server/src/com/vaadin/ui/AbstractColorPicker.java
@@ -405,6 +405,7 @@ public abstract class AbstractColorPicker extends AbstractComponent implements
window.setImmediate(true);
window.addCloseListener(this);
window.addColorChangeListener(new ColorChangeListener() {
+ @Override
public void colorChanged(ColorChangeEvent event) {
AbstractColorPicker.this.colorChanged(event);
}
diff --git a/server/src/com/vaadin/ui/AbstractField.java b/server/src/com/vaadin/ui/AbstractField.java
index 623dc5dbc3..3bca63a3b7 100644
--- a/server/src/com/vaadin/ui/AbstractField.java
+++ b/server/src/com/vaadin/ui/AbstractField.java
@@ -25,13 +25,13 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
-import java.util.logging.Logger;
import com.vaadin.data.Buffered;
import com.vaadin.data.Property;
import com.vaadin.data.Validatable;
import com.vaadin.data.Validator;
import com.vaadin.data.Validator.InvalidValueException;
+import com.vaadin.data.util.LegacyPropertyHelper;
import com.vaadin.data.util.converter.Converter;
import com.vaadin.data.util.converter.Converter.ConversionException;
import com.vaadin.data.util.converter.ConverterUtil;
@@ -42,6 +42,7 @@ import com.vaadin.server.AbstractErrorMessage;
import com.vaadin.server.CompositeErrorMessage;
import com.vaadin.server.ErrorMessage;
import com.vaadin.shared.AbstractFieldState;
+import com.vaadin.shared.util.SharedUtil;
/**
* <p>
@@ -74,9 +75,6 @@ public abstract class AbstractField<T> extends AbstractComponent implements
/* Private members */
- private static final Logger logger = Logger.getLogger(AbstractField.class
- .getName());
-
/**
* Value of the abstract field.
*/
@@ -370,13 +368,24 @@ public abstract class AbstractField<T> extends AbstractComponent implements
return buffered;
}
- /* Property interface implementation */
-
/**
- * Returns the (field) value converted to a String using toString().
+ * Returns a string representation of this object. The returned string
+ * representation depends on if the legacy Property toString mode is enabled
+ * or disabled.
+ * <p>
+ * If legacy Property toString mode is enabled, returns the value of this
+ * <code>Field</code> converted to a String.
+ * </p>
+ * <p>
+ * If legacy Property toString mode is disabled, the string representation
+ * has no special meaning
+ * </p>
+ *
+ * @see LegacyPropertyHelper#isLegacyToStringEnabled()
*
- * @see java.lang.Object#toString()
- * @deprecated As of 7.0, use {@link #getValue()} to get the value of the
+ * @return A string representation of the value value stored in the Property
+ * or a string representation of the Property object.
+ * @deprecated As of 7.0. Use {@link #getValue()} to get the value of the
* field, {@link #getConvertedValue()} to get the field value
* converted to the data model type or
* {@link #getPropertyDataSource()} .getValue() to get the value
@@ -385,17 +394,15 @@ public abstract class AbstractField<T> extends AbstractComponent implements
@Deprecated
@Override
public String toString() {
- logger.warning("You are using AbstractField.toString() to get the value for a "
- + getClass().getSimpleName()
- + ". This will not be supported starting from Vaadin 7.1 "
- + "(your debugger might call toString() and cause this message to appear).");
- final Object value = getFieldValue();
- if (value == null) {
- return null;
+ if (!LegacyPropertyHelper.isLegacyToStringEnabled()) {
+ return super.toString();
+ } else {
+ return LegacyPropertyHelper.legacyPropertyToString(this);
}
- return value.toString();
}
+ /* Property interface implementation */
+
/**
* Gets the current value of the field.
*
@@ -451,7 +458,7 @@ public abstract class AbstractField<T> extends AbstractComponent implements
throws Property.ReadOnlyException, Converter.ConversionException,
InvalidValueException {
- if (!equals(newFieldValue, getInternalValue())) {
+ if (!SharedUtil.equals(newFieldValue, getInternalValue())) {
// Read only fields can not be changed
if (isReadOnly()) {
@@ -459,7 +466,8 @@ public abstract class AbstractField<T> extends AbstractComponent implements
}
try {
T doubleConvertedFieldValue = convertFromModel(convertToModel(newFieldValue));
- if (!equals(newFieldValue, doubleConvertedFieldValue)) {
+ if (!SharedUtil
+ .equals(newFieldValue, doubleConvertedFieldValue)) {
newFieldValue = doubleConvertedFieldValue;
repaintIsNotNeeded = false;
}
@@ -536,11 +544,9 @@ public abstract class AbstractField<T> extends AbstractComponent implements
}
}
+ @Deprecated
static boolean equals(Object value1, Object value2) {
- if (value1 == null) {
- return value2 == null;
- }
- return value1.equals(value2);
+ return SharedUtil.equals(value1, value2);
}
/* External data source */
@@ -1228,8 +1234,8 @@ public abstract class AbstractField<T> extends AbstractComponent implements
public void valueChange(Property.ValueChangeEvent event) {
if (!isBuffered()) {
if (committingValueToDataSource) {
- boolean propertyNotifiesOfTheBufferedValue = equals(event
- .getProperty().getValue(), getInternalValue());
+ boolean propertyNotifiesOfTheBufferedValue = SharedUtil.equals(
+ event.getProperty().getValue(), getInternalValue());
if (!propertyNotifiesOfTheBufferedValue) {
/*
* Property (or chained property like PropertyFormatter) now
@@ -1345,15 +1351,33 @@ public abstract class AbstractField<T> extends AbstractComponent implements
}
private void localeMightHaveChanged() {
- if (!equals(valueLocale, getLocale()) && dataSource != null
- && !isModified()) {
- // When we have a data source and the internal value is directly
- // read from that we want to update the value
- T newInternalValue = convertFromModel(getPropertyDataSource()
- .getValue());
- if (!equals(newInternalValue, getInternalValue())) {
- setInternalValue(newInternalValue);
- fireValueChange(false);
+ if (!SharedUtil.equals(valueLocale, getLocale())) {
+ // The locale HAS actually changed
+
+ if (dataSource != null && !isModified()) {
+ // When we have a data source and the internal value is directly
+ // read from that we want to update the value
+ T newInternalValue = convertFromModel(getPropertyDataSource()
+ .getValue());
+ if (!SharedUtil.equals(newInternalValue, getInternalValue())) {
+ setInternalValue(newInternalValue);
+ fireValueChange(false);
+ }
+ } else if (dataSource == null && converter != null) {
+ /*
+ * No data source but a converter has been set. The same issues
+ * as above but we cannot use propertyDataSource. Convert the
+ * current value back to a model value using the old locale and
+ * then convert back using the new locale. If this does not
+ * match the field value we need to set the converted value
+ * again.
+ */
+ Object convertedValue = convertToModel(getInternalValue(),
+ valueLocale);
+ T newinternalValue = convertFromModel(convertedValue);
+ if (!SharedUtil.equals(getInternalValue(), newinternalValue)) {
+ setConvertedValue(convertedValue);
+ }
}
}
}
@@ -1600,7 +1624,7 @@ public abstract class AbstractField<T> extends AbstractComponent implements
setModified(false);
// If the new value differs from the previous one
- if (!equals(newFieldValue, getInternalValue())) {
+ if (!SharedUtil.equals(newFieldValue, getInternalValue())) {
setInternalValue(newFieldValue);
fireValueChange(false);
} else if (wasModified) {
diff --git a/server/src/com/vaadin/ui/AbstractMedia.java b/server/src/com/vaadin/ui/AbstractMedia.java
index 41677467bb..97947b568d 100644
--- a/server/src/com/vaadin/ui/AbstractMedia.java
+++ b/server/src/com/vaadin/ui/AbstractMedia.java
@@ -25,6 +25,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.vaadin.server.ConnectorResource;
+import com.vaadin.server.DownloadStream;
import com.vaadin.server.Resource;
import com.vaadin.server.ResourceReference;
import com.vaadin.server.VaadinRequest;
@@ -83,7 +84,14 @@ public abstract class AbstractMedia extends AbstractComponent {
public boolean handleConnectorRequest(VaadinRequest request,
VaadinResponse response, String path) throws IOException {
Matcher matcher = Pattern.compile("(\\d+)(/.*)?").matcher(path);
- if (matcher.matches()) {
+ if (!matcher.matches()) {
+ return super.handleConnectorRequest(request, response, path);
+ }
+
+ DownloadStream stream;
+
+ getSession().lock();
+ try {
List<URLReference> sources = getState().sources;
int sourceIndex = Integer.parseInt(matcher.group(1));
@@ -98,11 +106,13 @@ public abstract class AbstractMedia extends AbstractComponent {
URLReference reference = sources.get(sourceIndex);
ConnectorResource resource = (ConnectorResource) ResourceReference
.getResource(reference);
- resource.getStream().writeResponse(request, response);
- return true;
- } else {
- return super.handleConnectorRequest(request, response, path);
+ stream = resource.getStream();
+ } finally {
+ getSession().unlock();
}
+
+ stream.writeResponse(request, response);
+ return true;
}
private Logger getLogger() {
diff --git a/server/src/com/vaadin/ui/AbstractOrderedLayout.java b/server/src/com/vaadin/ui/AbstractOrderedLayout.java
index 8c2f86926d..c9eb756daa 100644
--- a/server/src/com/vaadin/ui/AbstractOrderedLayout.java
+++ b/server/src/com/vaadin/ui/AbstractOrderedLayout.java
@@ -53,6 +53,8 @@ public abstract class AbstractOrderedLayout extends AbstractLayout implements
*/
protected LinkedList<Component> components = new LinkedList<Component>();
+ private Alignment defaultComponentAlignment = Alignment.TOP_LEFT;
+
/* Child component alignments */
/**
@@ -147,7 +149,9 @@ public abstract class AbstractOrderedLayout extends AbstractLayout implements
}
private void componentAdded(Component c) {
- getState().childData.put(c, new ChildComponentData());
+ ChildComponentData ccd = new ChildComponentData();
+ ccd.alignmentBitmask = getDefaultComponentAlignment().getBitMask();
+ getState().childData.put(c, ccd);
}
/**
@@ -417,4 +421,27 @@ public abstract class AbstractOrderedLayout extends AbstractLayout implements
public void setMargin(MarginInfo marginInfo) {
getState().marginsBitmask = marginInfo.getBitMask();
}
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Layout.AlignmentHandler#getDefaultComponentAlignment()
+ */
+ @Override
+ public Alignment getDefaultComponentAlignment() {
+ return defaultComponentAlignment;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.ui.Layout.AlignmentHandler#setDefaultComponentAlignment(com
+ * .vaadin.ui.Alignment)
+ */
+ @Override
+ public void setDefaultComponentAlignment(Alignment defaultAlignment) {
+ defaultComponentAlignment = defaultAlignment;
+ }
+
}
diff --git a/server/src/com/vaadin/ui/Button.java b/server/src/com/vaadin/ui/Button.java
index fcfc55aadc..1bcf802f12 100644
--- a/server/src/com/vaadin/ui/Button.java
+++ b/server/src/com/vaadin/ui/Button.java
@@ -32,6 +32,7 @@ import com.vaadin.event.ShortcutAction;
import com.vaadin.event.ShortcutAction.KeyCode;
import com.vaadin.event.ShortcutAction.ModifierKey;
import com.vaadin.event.ShortcutListener;
+import com.vaadin.server.Resource;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.ui.button.ButtonServerRpc;
import com.vaadin.shared.ui.button.ButtonState;
@@ -581,6 +582,35 @@ public class Button extends AbstractComponent implements
}
/**
+ * Sets the component's icon and alt text.
+ *
+ * An alt text is shown when an image could not be loaded, and read by
+ * assisitve devices.
+ *
+ * @param icon
+ * the icon to be shown with the component's caption.
+ * @param iconAltText
+ * String to use as alt text
+ */
+ public void setIcon(Resource icon, String iconAltText) {
+ super.setIcon(icon);
+ getState().iconAltText = iconAltText == null ? "" : iconAltText;
+ }
+
+ /**
+ * Returns the icon's alt text.
+ *
+ * @return String with the alt text
+ */
+ public String getIconAlternateText() {
+ return getState().iconAltText;
+ }
+
+ public void setIconAlternateText(String iconAltText) {
+ getState().iconAltText = iconAltText;
+ }
+
+ /**
* Set whether the caption text is rendered as HTML or not. You might need
* to retheme button to allow higher content than the original text style.
*
diff --git a/server/src/com/vaadin/ui/Calendar.java b/server/src/com/vaadin/ui/Calendar.java
new file mode 100644
index 0000000000..38fa355dd8
--- /dev/null
+++ b/server/src/com/vaadin/ui/Calendar.java
@@ -0,0 +1,1845 @@
+/*
+ * 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.lang.reflect.Method;
+import java.text.DateFormat;
+import java.text.DateFormatSymbols;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.EventListener;
+import java.util.GregorianCalendar;
+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.Map.Entry;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.util.BeanItemContainer;
+import com.vaadin.event.Action;
+import com.vaadin.event.Action.Handler;
+import com.vaadin.event.dd.DropHandler;
+import com.vaadin.event.dd.DropTarget;
+import com.vaadin.event.dd.TargetDetails;
+import com.vaadin.server.KeyMapper;
+import com.vaadin.shared.ui.calendar.CalendarEventId;
+import com.vaadin.shared.ui.calendar.CalendarServerRpc;
+import com.vaadin.shared.ui.calendar.CalendarState;
+import com.vaadin.shared.ui.calendar.DateConstants;
+import com.vaadin.ui.components.calendar.CalendarComponentEvent;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.BackwardEvent;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.BackwardHandler;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.DateClickEvent;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.DateClickHandler;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.EventClick;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.EventClickHandler;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.EventMoveHandler;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.EventResize;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.EventResizeHandler;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.ForwardEvent;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.ForwardHandler;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.MoveEvent;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.RangeSelectEvent;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.RangeSelectHandler;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.WeekClick;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.WeekClickHandler;
+import com.vaadin.ui.components.calendar.CalendarDateRange;
+import com.vaadin.ui.components.calendar.CalendarTargetDetails;
+import com.vaadin.ui.components.calendar.ContainerEventProvider;
+import com.vaadin.ui.components.calendar.event.BasicEventProvider;
+import com.vaadin.ui.components.calendar.event.CalendarEditableEventProvider;
+import com.vaadin.ui.components.calendar.event.CalendarEvent;
+import com.vaadin.ui.components.calendar.event.CalendarEvent.EventChangeEvent;
+import com.vaadin.ui.components.calendar.event.CalendarEvent.EventChangeListener;
+import com.vaadin.ui.components.calendar.event.CalendarEventProvider;
+import com.vaadin.ui.components.calendar.handler.BasicBackwardHandler;
+import com.vaadin.ui.components.calendar.handler.BasicDateClickHandler;
+import com.vaadin.ui.components.calendar.handler.BasicEventMoveHandler;
+import com.vaadin.ui.components.calendar.handler.BasicEventResizeHandler;
+import com.vaadin.ui.components.calendar.handler.BasicForwardHandler;
+import com.vaadin.ui.components.calendar.handler.BasicWeekClickHandler;
+
+/**
+ * <p>
+ * Vaadin Calendar is for visualizing events in a calendar. Calendar events can
+ * be visualized in the variable length view depending on the start and end
+ * dates.
+ * </p>
+ *
+ * <li>You can set the viewable date range with the {@link #setStartDate(Date)}
+ * and {@link #setEndDate(Date)} methods. Calendar has a default date range of
+ * one week</li>
+ *
+ * <li>Calendar has two kind of views: monthly and weekly view</li>
+ *
+ * <li>If date range is seven days or shorter, the weekly view is used.</li>
+ *
+ * <li>Calendar queries its events by using a
+ * {@link com.vaadin.addon.calendar.event.CalendarEventProvider
+ * CalendarEventProvider}. By default, a
+ * {@link com.vaadin.addon.calendar.event.BasicEventProvider BasicEventProvider}
+ * is used.</li>
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+@SuppressWarnings("serial")
+public class Calendar extends AbstractComponent implements
+ CalendarComponentEvents.NavigationNotifier,
+ CalendarComponentEvents.EventMoveNotifier,
+ CalendarComponentEvents.RangeSelectNotifier,
+ CalendarComponentEvents.EventResizeNotifier,
+ CalendarEventProvider.EventSetChangeListener, DropTarget,
+ CalendarEditableEventProvider, Action.Container {
+
+ /**
+ * Calendar can use either 12 hours clock or 24 hours clock.
+ */
+ public enum TimeFormat {
+
+ Format12H(), Format24H();
+ }
+
+ /** Defines currently active format for time. 12H/24H. */
+ protected TimeFormat currentTimeFormat;
+
+ /** Internal calendar data source. */
+ protected java.util.Calendar currentCalendar = java.util.Calendar
+ .getInstance();
+
+ /** Defines the component's active time zone. */
+ protected TimeZone timezone;
+
+ /** Defines the calendar's date range starting point. */
+ protected Date startDate = null;
+
+ /** Defines the calendar's date range ending point. */
+ protected Date endDate = null;
+
+ /** Event provider. */
+ private CalendarEventProvider calendarEventProvider;
+
+ /**
+ * Internal buffer for the events that are retrieved from the event
+ * provider.
+ */
+ protected List<CalendarEvent> events;
+
+ /** Date format that will be used in the UIDL for dates. */
+ protected DateFormat df_date = new SimpleDateFormat("yyyy-MM-dd");
+
+ /** Time format that will be used in the UIDL for time. */
+ protected DateFormat df_time = new SimpleDateFormat("HH:mm:ss");
+
+ /** Date format that will be used in the UIDL for both date and time. */
+ protected DateFormat df_date_time = new SimpleDateFormat(
+ DateConstants.CLIENT_DATE_FORMAT + "-"
+ + DateConstants.CLIENT_TIME_FORMAT);
+
+ /**
+ * Week view's scroll position. Client sends updates to this value so that
+ * scroll position wont reset all the time.
+ */
+ private int scrollTop = 0;
+
+ /** Caption format for the weekly view */
+ private String weeklyCaptionFormat = null;
+
+ /** Map from event ids to event handlers */
+ private final Map<String, EventListener> handlers;
+
+ /**
+ * Drop Handler for Vaadin DD. By default null.
+ */
+ private DropHandler dropHandler;
+
+ /**
+ * First day to show for a week
+ */
+ private int firstDay = 1;
+
+ /**
+ * Last day to show for a week
+ */
+ private int lastDay = 7;
+
+ /**
+ * First hour to show for a day
+ */
+ private int firstHour = 0;
+
+ /**
+ * Last hour to show for a day
+ */
+ private int lastHour = 23;
+
+ /**
+ * List of action handlers.
+ */
+ private LinkedList<Action.Handler> actionHandlers = null;
+
+ /**
+ * Action mapper.
+ */
+ private KeyMapper<Action> actionMapper = null;
+
+ /**
+ *
+ */
+ private CalendarServerRpcImpl rpc = new CalendarServerRpcImpl();
+
+ /**
+ * Returns the logger for the calendar
+ */
+ protected Logger getLogger() {
+ return Logger.getLogger(Calendar.class.getName());
+ }
+
+ /**
+ * Construct a Vaadin Calendar with a BasicEventProvider and no caption.
+ * Default date range is one week.
+ */
+ public Calendar() {
+ this(null, new BasicEventProvider());
+ }
+
+ /**
+ * Construct a Vaadin Calendar with a BasicEventProvider and the provided
+ * caption. Default date range is one week.
+ *
+ * @param caption
+ */
+ public Calendar(String caption) {
+ this(caption, new BasicEventProvider());
+ }
+
+ /**
+ * <p>
+ * Construct a Vaadin Calendar with event provider. Event provider is
+ * obligatory, because calendar component will query active events through
+ * it.
+ * </p>
+ *
+ * <p>
+ * By default, Vaadin Calendar will show dates from the start of the current
+ * week to the end of the current week. Use {@link #setStartDate(Date)} and
+ * {@link #setEndDate(Date)} to change this.
+ * </p>
+ *
+ * @param eventProvider
+ * Event provider, cannot be null.
+ */
+ public Calendar(CalendarEventProvider eventProvider) {
+ this(null, eventProvider);
+ }
+
+ /**
+ * <p>
+ * Construct a Vaadin Calendar with event provider and a caption. Event
+ * provider is obligatory, because calendar component will query active
+ * events through it.
+ * </p>
+ *
+ * <p>
+ * By default, Vaadin Calendar will show dates from the start of the current
+ * week to the end of the current week. Use {@link #setStartDate(Date)} and
+ * {@link #setEndDate(Date)} to change this.
+ * </p>
+ *
+ * @param eventProvider
+ * Event provider, cannot be null.
+ */
+ // this is the constructor every other constructor calls
+ public Calendar(String caption, CalendarEventProvider eventProvider) {
+ registerRpc(rpc);
+ setCaption(caption);
+ handlers = new HashMap<String, EventListener>();
+ setDefaultHandlers();
+ currentCalendar.setTime(new Date());
+ setEventProvider(eventProvider);
+ getState().firstDayOfWeek = firstDay;
+ getState().lastVisibleDayOfWeek = lastDay;
+ getState().firstHourOfDay = firstHour;
+ getState().lastHourOfDay = lastHour;
+ setTimeFormat(null);
+
+ }
+
+ @Override
+ public CalendarState getState() {
+ return (CalendarState) super.getState();
+ }
+
+ @Override
+ public void beforeClientResponse(boolean initial) {
+ super.beforeClientResponse(initial);
+
+ initCalendarWithLocale();
+
+ getState().format24H = TimeFormat.Format24H == getTimeFormat();
+ setupDaysAndActions();
+ setupCalendarEvents();
+ rpc.scroll(scrollTop);
+ }
+
+ /**
+ * Set all the wanted default handlers here. This is always called after
+ * constructing this object. All other events have default handlers except
+ * range and event click.
+ */
+ protected void setDefaultHandlers() {
+ setHandler(new BasicBackwardHandler());
+ setHandler(new BasicForwardHandler());
+ setHandler(new BasicWeekClickHandler());
+ setHandler(new BasicDateClickHandler());
+ setHandler(new BasicEventMoveHandler());
+ setHandler(new BasicEventResizeHandler());
+ }
+
+ /**
+ * Gets the calendar's start date.
+ *
+ * @return First visible date.
+ */
+ public Date getStartDate() {
+ if (startDate == null) {
+ currentCalendar.set(java.util.Calendar.DAY_OF_WEEK,
+ currentCalendar.getFirstDayOfWeek());
+ return currentCalendar.getTime();
+ }
+ return startDate;
+ }
+
+ /**
+ * Sets start date for the calendar. This and {@link #setEndDate(Date)}
+ * control the range of dates visible on the component. The default range is
+ * one week.
+ *
+ * @param date
+ * First visible date to show.
+ */
+ public void setStartDate(Date date) {
+ if (!date.equals(startDate)) {
+ startDate = date;
+ markAsDirty();
+ }
+ }
+
+ /**
+ * Gets the calendar's end date.
+ *
+ * @return Last visible date.
+ */
+ public Date getEndDate() {
+ if (endDate == null) {
+ currentCalendar.set(java.util.Calendar.DAY_OF_WEEK,
+ currentCalendar.getFirstDayOfWeek() + 6);
+ return currentCalendar.getTime();
+ }
+ return endDate;
+ }
+
+ /**
+ * Sets end date for the calendar. Starting from startDate, only six weeks
+ * will be shown if duration to endDate is longer than six weeks.
+ *
+ * This and {@link #setStartDate(Date)} control the range of dates visible
+ * on the component. The default range is one week.
+ *
+ * @param date
+ * Last visible date to show.
+ */
+ public void setEndDate(Date date) {
+ if (startDate != null && startDate.after(date)) {
+ startDate = (Date) date.clone();
+ markAsDirty();
+ } else if (!date.equals(endDate)) {
+ endDate = date;
+ markAsDirty();
+ }
+ }
+
+ /**
+ * Sets the locale to be used in the Calendar component.
+ *
+ * @see com.vaadin.ui.AbstractComponent#setLocale(java.util.Locale)
+ */
+ @Override
+ public void setLocale(Locale newLocale) {
+ super.setLocale(newLocale);
+ initCalendarWithLocale();
+ }
+
+ /**
+ * Initialize the java calendar instance with the current locale and
+ * timezone.
+ */
+ private void initCalendarWithLocale() {
+ if (timezone != null) {
+ currentCalendar = java.util.Calendar.getInstance(timezone,
+ getLocale());
+
+ } else {
+ currentCalendar = java.util.Calendar.getInstance(getLocale());
+ }
+ }
+
+ private void setupCalendarEvents() {
+ int durationInDays = (int) (((endDate.getTime()) - startDate.getTime()) / DateConstants.DAYINMILLIS);
+ durationInDays++;
+ if (durationInDays > 60) {
+ throw new RuntimeException("Daterange is too big (max 60) = "
+ + durationInDays);
+ }
+
+ Date firstDateToShow = expandStartDate(startDate, durationInDays > 7);
+ Date lastDateToShow = expandEndDate(endDate, durationInDays > 7);
+
+ currentCalendar.setTime(firstDateToShow);
+ events = getEventProvider().getEvents(firstDateToShow, lastDateToShow);
+
+ List<CalendarState.Event> calendarStateEvents = new ArrayList<CalendarState.Event>();
+ if (events != null) {
+ for (int i = 0; i < events.size(); i++) {
+ CalendarEvent e = events.get(i);
+ CalendarState.Event event = new CalendarState.Event();
+ event.index = i;
+ event.caption = e.getCaption() == null ? "" : e.getCaption();
+ event.dateFrom = df_date.format(e.getStart());
+ event.dateTo = df_date.format(e.getEnd());
+ event.timeFrom = df_time.format(e.getStart());
+ event.timeTo = df_time.format(e.getEnd());
+ event.description = e.getDescription() == null ? "" : e
+ .getDescription();
+ event.styleName = e.getStyleName() == null ? "" : e
+ .getStyleName();
+ event.allDay = e.isAllDay();
+ calendarStateEvents.add(event);
+ }
+ }
+ getState().events = calendarStateEvents;
+ }
+
+ private void setupDaysAndActions() {
+ // Make sure we have a up-to-date locale
+ initCalendarWithLocale();
+
+ CalendarState state = getState();
+
+ state.firstDayOfWeek = currentCalendar.getFirstDayOfWeek();
+
+ // If only one is null, throw exception
+ // If both are null, set defaults
+ if (startDate == null ^ endDate == null) {
+ String message = "Schedule cannot be painted without a proper date range.\n";
+ if (startDate == null) {
+ throw new IllegalStateException(message
+ + "You must set a start date using setStartDate(Date).");
+
+ } else {
+ throw new IllegalStateException(message
+ + "You must set an end date using setEndDate(Date).");
+ }
+
+ } else if (startDate == null && endDate == null) {
+ // set defaults
+ startDate = getStartDate();
+ endDate = getEndDate();
+ }
+
+ int durationInDays = (int) (((endDate.getTime()) - startDate.getTime()) / DateConstants.DAYINMILLIS);
+ durationInDays++;
+ if (durationInDays > 60) {
+ throw new RuntimeException("Daterange is too big (max 60) = "
+ + durationInDays);
+ }
+
+ state.dayNames = getDayNamesShort();
+ state.monthNames = getMonthNamesShort();
+
+ // Use same timezone in all dates this component handles.
+ // Show "now"-marker in browser within given timezone.
+ Date now = new Date();
+ currentCalendar.setTime(now);
+ now = currentCalendar.getTime();
+
+ // Reset time zones for custom date formats
+ df_date.setTimeZone(currentCalendar.getTimeZone());
+ df_time.setTimeZone(currentCalendar.getTimeZone());
+
+ state.now = (df_date.format(now) + " " + df_time.format(now));
+
+ Date firstDateToShow = expandStartDate(startDate, durationInDays > 7);
+ Date lastDateToShow = expandEndDate(endDate, durationInDays > 7);
+
+ currentCalendar.setTime(firstDateToShow);
+
+ DateFormat weeklyCaptionFormatter = getWeeklyCaptionFormatter();
+ weeklyCaptionFormatter.setTimeZone(currentCalendar.getTimeZone());
+
+ Map<CalendarDateRange, Set<Action>> actionMap = new HashMap<CalendarDateRange, Set<Action>>();
+
+ List<CalendarState.Day> days = new ArrayList<CalendarState.Day>();
+
+ // Send all dates to client from server. This
+ // approach was taken because gwt doesn't
+ // support date localization properly.
+ while (currentCalendar.getTime().compareTo(lastDateToShow) < 1) {
+ final Date date = currentCalendar.getTime();
+ final CalendarState.Day day = new CalendarState.Day();
+ day.date = df_date.format(date);
+ day.localizedDateFormat = weeklyCaptionFormatter.format(date);
+ day.dayOfWeek = getDowByLocale(currentCalendar);
+ day.week = currentCalendar.get(java.util.Calendar.WEEK_OF_YEAR);
+
+ days.add(day);
+
+ // Get actions for a specific date
+ if (actionHandlers != null) {
+ for (Action.Handler actionHandler : actionHandlers) {
+
+ // Create calendar which omits time
+ GregorianCalendar cal = new GregorianCalendar(
+ getTimeZone(), getLocale());
+ cal.clear();
+ cal.set(currentCalendar.get(java.util.Calendar.YEAR),
+ currentCalendar.get(java.util.Calendar.MONTH),
+ currentCalendar.get(java.util.Calendar.DATE));
+
+ // Get day start and end times
+ Date start = cal.getTime();
+ cal.add(java.util.Calendar.DATE, 1);
+ Date end = cal.getTime();
+
+ boolean monthView = (durationInDays > 7);
+
+ /**
+ * If in day or week view add actions for each half-an-hour.
+ * If in month view add actions for each day
+ */
+ if (monthView) {
+ setActionsForDay(actionMap, start, end, actionHandler);
+ } else {
+ setActionsForEachHalfHour(actionMap, start, end,
+ actionHandler);
+ }
+
+ }
+ }
+
+ currentCalendar.add(java.util.Calendar.DATE, 1);
+ }
+ state.days = days;
+ state.actions = createActionsList(actionMap);
+ }
+
+ private void setActionsForEachHalfHour(
+ Map<CalendarDateRange, Set<Action>> actionMap, Date start,
+ Date end, Action.Handler actionHandler) {
+ GregorianCalendar cal = new GregorianCalendar(getTimeZone(),
+ getLocale());
+ cal.setTime(start);
+ while (cal.getTime().before(end)) {
+ Date s = cal.getTime();
+ cal.add(java.util.Calendar.MINUTE, 30);
+ Date e = cal.getTime();
+ CalendarDateRange range = new CalendarDateRange(s, e, getTimeZone());
+ Action[] actions = actionHandler.getActions(range, this);
+ if (actions != null) {
+ Set<Action> actionSet = new HashSet<Action>(
+ Arrays.asList(actions));
+ actionMap.put(range, actionSet);
+ }
+ }
+ }
+
+ private void setActionsForDay(
+ Map<CalendarDateRange, Set<Action>> actionMap, Date start,
+ Date end, Action.Handler actionHandler) {
+ CalendarDateRange range = new CalendarDateRange(start, end,
+ getTimeZone());
+ Action[] actions = actionHandler.getActions(range, this);
+ if (actions != null) {
+ Set<Action> actionSet = new HashSet<Action>(Arrays.asList(actions));
+ actionMap.put(range, actionSet);
+ }
+ }
+
+ private List<CalendarState.Action> createActionsList(
+ Map<CalendarDateRange, Set<Action>> actionMap) {
+ if (actionMap.isEmpty()) {
+ return null;
+ }
+
+ List<CalendarState.Action> calendarActions = new ArrayList<CalendarState.Action>();
+
+ SimpleDateFormat formatter = new SimpleDateFormat(
+ DateConstants.ACTION_DATE_FORMAT_PATTERN);
+ formatter.setTimeZone(getTimeZone());
+
+ for (Entry<CalendarDateRange, Set<Action>> entry : actionMap.entrySet()) {
+ CalendarDateRange range = entry.getKey();
+ Set<Action> actions = entry.getValue();
+ for (Action action : actions) {
+ String key = actionMapper.key(action);
+ CalendarState.Action calendarAction = new CalendarState.Action();
+ calendarAction.actionKey = key;
+ calendarAction.caption = action.getCaption();
+ setResource(key, action.getIcon());
+ calendarAction.iconKey = key;
+ calendarAction.startDate = formatter.format(range.getStart());
+ calendarAction.endDate = formatter.format(range.getEnd());
+ calendarActions.add(calendarAction);
+ }
+ }
+
+ return calendarActions;
+ }
+
+ /**
+ * Gets currently active time format. Value is either TimeFormat.Format12H
+ * or TimeFormat.Format24H.
+ *
+ * @return TimeFormat Format for the time.
+ */
+ public TimeFormat getTimeFormat() {
+ if (currentTimeFormat == null) {
+ SimpleDateFormat f = (SimpleDateFormat) SimpleDateFormat
+ .getTimeInstance(SimpleDateFormat.SHORT, getLocale());
+ String p = f.toPattern();
+ if (p.indexOf("HH") != -1 || p.indexOf("H") != -1) {
+ return TimeFormat.Format24H;
+ }
+ return TimeFormat.Format12H;
+ }
+ return currentTimeFormat;
+ }
+
+ /**
+ * Example: <code>setTimeFormat(TimeFormat.Format12H);</code></br> Set to
+ * null, if you want the format being defined by the locale.
+ *
+ * @param format
+ * Set 12h or 24h format. Default is defined by the locale.
+ */
+ public void setTimeFormat(TimeFormat format) {
+ currentTimeFormat = format;
+ markAsDirty();
+ }
+
+ /**
+ * Returns a time zone that is currently used by this component.
+ *
+ * @return Component's Time zone
+ */
+ public TimeZone getTimeZone() {
+ if (timezone == null) {
+ return currentCalendar.getTimeZone();
+ }
+ return timezone;
+ }
+
+ /**
+ * Set time zone that this component will use. Null value sets the default
+ * time zone.
+ *
+ * @param zone
+ * Time zone to use
+ */
+ public void setTimeZone(TimeZone zone) {
+ timezone = zone;
+ if (!currentCalendar.getTimeZone().equals(zone)) {
+ if (zone == null) {
+ zone = TimeZone.getDefault();
+ }
+ currentCalendar.setTimeZone(zone);
+ df_date_time.setTimeZone(zone);
+ markAsDirty();
+ }
+ }
+
+ /**
+ * Get the internally used Calendar instance. This is the currently used
+ * instance of {@link java.util.Calendar} but is bound to change during the
+ * lifetime of the component.
+ *
+ * @return the currently used java calendar
+ */
+ public java.util.Calendar getInternalCalendar() {
+ return currentCalendar;
+ }
+
+ /**
+ * <p>
+ * This method restricts the weekdays that are shown. This affects both the
+ * monthly and the weekly view. The general contract is that <b>firstDay <
+ * lastDay</b>.
+ * </p>
+ *
+ * <p>
+ * Note that this only affects the rendering process. Events are still
+ * requested by the dates set by {@link #setStartDate(Date)} and
+ * {@link #setEndDate(Date)}.
+ * </p>
+ *
+ * @param firstDay
+ * the first day of the week to show, between 1 and 7
+ */
+ public void setFirstVisibleDayOfWeek(int firstDay) {
+ if (this.firstDay != firstDay && firstDay >= 1 && firstDay <= 7
+ && getLastVisibleDayOfWeek() >= firstDay) {
+ this.firstDay = firstDay;
+ getState().firstVisibleDayOfWeek = firstDay;
+ }
+ }
+
+ /**
+ * Get the first visible day of the week. Returns the weekdays as integers
+ * represented by {@link java.util.Calendar#DAY_OF_WEEK}
+ *
+ * @return An integer representing the week day according to
+ * {@link java.util.Calendar#DAY_OF_WEEK}
+ */
+ public int getFirstVisibleDayOfWeek() {
+ return firstDay;
+ }
+
+ /**
+ * <p>
+ * This method restricts the weekdays that are shown. This affects both the
+ * monthly and the weekly view. The general contract is that <b>firstDay <
+ * lastDay</b>.
+ * </p>
+ *
+ * <p>
+ * Note that this only affects the rendering process. Events are still
+ * requested by the dates set by {@link #setStartDate(Date)} and
+ * {@link #setEndDate(Date)}.
+ * </p>
+ *
+ * @param lastDay
+ * the first day of the week to show, between 1 and 7
+ */
+ public void setLastVisibleDayOfWeek(int lastDay) {
+ if (this.lastDay != lastDay && lastDay >= 1 && lastDay <= 7
+ && getFirstVisibleDayOfWeek() <= lastDay) {
+ this.lastDay = lastDay;
+ getState().lastVisibleDayOfWeek = lastDay;
+ }
+ }
+
+ /**
+ * Get the last visible day of the week. Returns the weekdays as integers
+ * represented by {@link java.util.Calendar#DAY_OF_WEEK}
+ *
+ * @return An integer representing the week day according to
+ * {@link java.util.Calendar#DAY_OF_WEEK}
+ */
+ public int getLastVisibleDayOfWeek() {
+ return lastDay;
+ }
+
+ /**
+ * <p>
+ * This method restricts the hours that are shown per day. This affects the
+ * weekly view. The general contract is that <b>firstHour < lastHour</b>.
+ * </p>
+ *
+ * <p>
+ * Note that this only affects the rendering process. Events are still
+ * requested by the dates set by {@link #setStartDate(Date)} and
+ * {@link #setEndDate(Date)}.
+ * </p>
+ *
+ * @param firstHour
+ * the first hour of the day to show, between 0 and 23
+ */
+ public void setFirstVisibleHourOfDay(int firstHour) {
+ if (this.firstHour != firstHour && firstHour >= 0 && firstHour <= 23
+ && firstHour <= getLastVisibleHourOfDay()) {
+ this.firstHour = firstHour;
+ getState().firstHourOfDay = firstHour;
+ }
+ }
+
+ /**
+ * Returns the first visible hour in the week view. Returns the hour using a
+ * 24h time format
+ *
+ */
+ public int getFirstVisibleHourOfDay() {
+ return firstHour;
+ }
+
+ /**
+ * <p>
+ * This method restricts the hours that are shown per day. This affects the
+ * weekly view. The general contract is that <b>firstHour < lastHour</b>.
+ * </p>
+ *
+ * <p>
+ * Note that this only affects the rendering process. Events are still
+ * requested by the dates set by {@link #setStartDate(Date)} and
+ * {@link #setEndDate(Date)}.
+ * </p>
+ *
+ * @param lastHour
+ * the first hour of the day to show, between 0 and 23
+ */
+ public void setLastVisibleHourOfDay(int lastHour) {
+ if (this.lastHour != lastHour && lastHour >= 0 && lastHour <= 23
+ && lastHour >= getFirstVisibleHourOfDay()) {
+ this.lastHour = lastHour;
+ getState().lastHourOfDay = lastHour;
+ }
+ }
+
+ /**
+ * Returns the last visible hour in the week view. Returns the hour using a
+ * 24h time format
+ *
+ */
+ public int getLastVisibleHourOfDay() {
+ return lastHour;
+ }
+
+ /**
+ * Gets the date caption format for the weekly view.
+ *
+ * @return The pattern used in caption of dates in weekly view.
+ */
+ public String getWeeklyCaptionFormat() {
+ return weeklyCaptionFormat;
+ }
+
+ /**
+ * Sets custom date format for the weekly view. This is the caption of the
+ * date. Format could be like "mmm MM/dd".
+ *
+ * @param dateFormatPattern
+ * The date caption pattern.
+ */
+ public void setWeeklyCaptionFormat(String dateFormatPattern) {
+ if ((weeklyCaptionFormat == null && dateFormatPattern != null)
+ || (weeklyCaptionFormat != null && !weeklyCaptionFormat
+ .equals(dateFormatPattern))) {
+ weeklyCaptionFormat = dateFormatPattern;
+ markAsDirty();
+ }
+ }
+
+ private DateFormat getWeeklyCaptionFormatter() {
+ if (weeklyCaptionFormat != null) {
+ return new SimpleDateFormat(weeklyCaptionFormat, getLocale());
+ } else {
+ return SimpleDateFormat.getDateInstance(SimpleDateFormat.SHORT,
+ getLocale());
+ }
+ }
+
+ /**
+ * Get the day of week by the given calendar and its locale
+ *
+ * @param calendar
+ * The calendar to use
+ * @return
+ */
+ private static int getDowByLocale(java.util.Calendar calendar) {
+ int fow = calendar.get(java.util.Calendar.DAY_OF_WEEK);
+
+ // monday first
+ if (calendar.getFirstDayOfWeek() == java.util.Calendar.MONDAY) {
+ fow = (fow == java.util.Calendar.SUNDAY) ? 7 : fow - 1;
+ }
+
+ return fow;
+ }
+
+ /**
+ * Is the user allowed to trigger events which alters the events
+ *
+ * @return true if the client is allowed to send changes to server
+ * @see #isEventClickAllowed()
+ */
+ protected boolean isClientChangeAllowed() {
+ return !isReadOnly() && isEnabled();
+ }
+
+ /**
+ * Is the user allowed to trigger click events
+ *
+ * @return true if the client is allowed to click events
+ * @see #isClientChangeAllowed()
+ */
+ protected boolean isEventClickAllowed() {
+ return isEnabled();
+ }
+
+ /**
+ * Fires an event when the user selecing moving forward/backward in the
+ * calendar.
+ *
+ * @param forward
+ * True if the calendar moved forward else backward is assumed.
+ */
+ protected void fireNavigationEvent(boolean forward) {
+ if (forward) {
+ fireEvent(new ForwardEvent(this));
+ } else {
+ fireEvent(new BackwardEvent(this));
+ }
+ }
+
+ /**
+ * Fires an event move event to all server side move listerners
+ *
+ * @param index
+ * The index of the event in the events list
+ * @param newFromDatetime
+ * The changed from date time
+ */
+ protected void fireEventMove(int index, Date newFromDatetime) {
+ MoveEvent event = new MoveEvent(this, events.get(index),
+ newFromDatetime);
+
+ if (calendarEventProvider instanceof EventMoveHandler) {
+ // Notify event provider if it is an event move handler
+ ((EventMoveHandler) calendarEventProvider).eventMove(event);
+ }
+
+ // Notify event move handler attached by using the
+ // setHandler(EventMoveHandler) method
+ fireEvent(event);
+ }
+
+ /**
+ * Fires event when a week was clicked in the calendar.
+ *
+ * @param week
+ * The week that was clicked
+ * @param year
+ * The year of the week
+ */
+ protected void fireWeekClick(int week, int year) {
+ fireEvent(new WeekClick(this, week, year));
+ }
+
+ /**
+ * Fires event when a date was clicked in the calendar. Uses an existing
+ * event from the event cache.
+ *
+ * @param index
+ * The index of the event in the event cache.
+ */
+ protected void fireEventClick(Integer index) {
+ fireEvent(new EventClick(this, events.get(index)));
+ }
+
+ /**
+ * Fires event when a date was clicked in the calendar. Creates a new event
+ * for the date and passes it to the listener.
+ *
+ * @param date
+ * The date and time that was clicked
+ */
+ protected void fireDateClick(Date date) {
+ fireEvent(new DateClickEvent(this, date));
+ }
+
+ /**
+ * Fires an event range selected event. The event is fired when a user
+ * highlights an area in the calendar. The highlighted areas start and end
+ * dates are returned as arguments.
+ *
+ * @param from
+ * The start date and time of the highlighted area
+ * @param to
+ * The end date and time of the highlighted area
+ * @param monthlyMode
+ * Is the calendar in monthly mode
+ */
+ protected void fireRangeSelect(Date from, Date to, boolean monthlyMode) {
+ fireEvent(new RangeSelectEvent(this, from, to, monthlyMode));
+ }
+
+ /**
+ * Fires an event resize event. The event is fired when a user resizes the
+ * event in the calendar causing the time range of the event to increase or
+ * decrease. The new start and end times are returned as arguments to this
+ * method.
+ *
+ * @param index
+ * The index of the event in the event cache
+ * @param startTime
+ * The new start date and time of the event
+ * @param endTime
+ * The new end date and time of the event
+ */
+ protected void fireEventResize(int index, Date startTime, Date endTime) {
+ EventResize event = new EventResize(this, events.get(index), startTime,
+ endTime);
+
+ if (calendarEventProvider instanceof EventResizeHandler) {
+ // Notify event provider if it is an event resize handler
+ ((EventResizeHandler) calendarEventProvider).eventResize(event);
+ }
+
+ // Notify event resize handler attached by using the
+ // setHandler(EventMoveHandler) method
+ fireEvent(event);
+ }
+
+ /**
+ * Localized display names for week days starting from sunday. Returned
+ * array's length is always 7.
+ *
+ * @return Array of localized weekday names.
+ */
+ protected String[] getDayNamesShort() {
+ DateFormatSymbols s = new DateFormatSymbols(getLocale());
+ return Arrays.copyOfRange(s.getWeekdays(), 1, 8);
+ }
+
+ /**
+ * Localized display names for months starting from January. Returned
+ * array's length is always 12.
+ *
+ * @return Array of localized month names.
+ */
+ protected String[] getMonthNamesShort() {
+ DateFormatSymbols s = new DateFormatSymbols(getLocale());
+ return Arrays.copyOf(s.getShortMonths(), 12);
+ }
+
+ /**
+ * Gets a date that is first day in the week that target given date belongs
+ * to.
+ *
+ * @param date
+ * Target date
+ * @return Date that is first date in same week that given date is.
+ */
+ protected Date getFirstDateForWeek(Date date) {
+ int firstDayOfWeek = currentCalendar.getFirstDayOfWeek();
+ currentCalendar.setTime(date);
+ while (firstDayOfWeek != currentCalendar
+ .get(java.util.Calendar.DAY_OF_WEEK)) {
+ currentCalendar.add(java.util.Calendar.DATE, -1);
+ }
+ return currentCalendar.getTime();
+ }
+
+ /**
+ * Gets a date that is last day in the week that target given date belongs
+ * to.
+ *
+ * @param date
+ * Target date
+ * @return Date that is last date in same week that given date is.
+ */
+ protected Date getLastDateForWeek(Date date) {
+ currentCalendar.setTime(date);
+ currentCalendar.add(java.util.Calendar.DATE, 1);
+ int firstDayOfWeek = currentCalendar.getFirstDayOfWeek();
+ // Roll to weeks last day using firstdayofweek. Roll until FDofW is
+ // found and then roll back one day.
+ while (firstDayOfWeek != currentCalendar
+ .get(java.util.Calendar.DAY_OF_WEEK)) {
+ currentCalendar.add(java.util.Calendar.DATE, 1);
+ }
+ currentCalendar.add(java.util.Calendar.DATE, -1);
+ return currentCalendar.getTime();
+ }
+
+ /**
+ * Calculates the end time of the day using the given calendar and date
+ *
+ * @param date
+ * @param calendar
+ * the calendar instance to be used in the calculation. The given
+ * instance is unchanged in this operation.
+ * @return the given date, with time set to the end of the day
+ */
+ private static Date getEndOfDay(java.util.Calendar calendar, Date date) {
+ java.util.Calendar calendarClone = (java.util.Calendar) calendar
+ .clone();
+
+ calendarClone.setTime(date);
+ calendarClone.set(java.util.Calendar.MILLISECOND,
+ calendarClone.getActualMaximum(java.util.Calendar.MILLISECOND));
+ calendarClone.set(java.util.Calendar.SECOND,
+ calendarClone.getActualMaximum(java.util.Calendar.SECOND));
+ calendarClone.set(java.util.Calendar.MINUTE,
+ calendarClone.getActualMaximum(java.util.Calendar.MINUTE));
+ calendarClone.set(java.util.Calendar.HOUR,
+ calendarClone.getActualMaximum(java.util.Calendar.HOUR));
+ calendarClone.set(java.util.Calendar.HOUR_OF_DAY,
+ calendarClone.getActualMaximum(java.util.Calendar.HOUR_OF_DAY));
+
+ return calendarClone.getTime();
+ }
+
+ /**
+ * Calculates the end time of the day using the given calendar and date
+ *
+ * @param date
+ * @param calendar
+ * the calendar instance to be used in the calculation. The given
+ * instance is unchanged in this operation.
+ * @return the given date, with time set to the end of the day
+ */
+ private static Date getStartOfDay(java.util.Calendar calendar, Date date) {
+ java.util.Calendar calendarClone = (java.util.Calendar) calendar
+ .clone();
+
+ calendarClone.setTime(date);
+ calendarClone.set(java.util.Calendar.MILLISECOND, 0);
+ calendarClone.set(java.util.Calendar.SECOND, 0);
+ calendarClone.set(java.util.Calendar.MINUTE, 0);
+ calendarClone.set(java.util.Calendar.HOUR, 0);
+ calendarClone.set(java.util.Calendar.HOUR_OF_DAY, 0);
+
+ return calendarClone.getTime();
+ }
+
+ /**
+ * Finds the first day of the week and returns a day representing the start
+ * of that day
+ *
+ * @param start
+ * The actual date
+ * @param expandToFullWeek
+ * Should the returned date be moved to the start of the week
+ * @return If expandToFullWeek is set then it returns the first day of the
+ * week, else it returns a clone of the actual date with the time
+ * set to the start of the day
+ */
+ protected Date expandStartDate(Date start, boolean expandToFullWeek) {
+ // If the duration is more than week, use monthly view and get startweek
+ // and endweek. Example if views daterange is from tuesday to next weeks
+ // wednesday->expand to monday to nextweeks sunday. If firstdayofweek =
+ // monday
+ if (expandToFullWeek) {
+ start = getFirstDateForWeek(start);
+
+ } else {
+ start = (Date) start.clone();
+ }
+
+ // Always expand to the start of the first day to the end of the last
+ // day
+ start = getStartOfDay(currentCalendar, start);
+
+ return start;
+ }
+
+ /**
+ * Finds the last day of the week and returns a day representing the end of
+ * that day
+ *
+ * @param end
+ * The actual date
+ * @param expandToFullWeek
+ * Should the returned date be moved to the end of the week
+ * @return If expandToFullWeek is set then it returns the last day of the
+ * week, else it returns a clone of the actual date with the time
+ * set to the end of the day
+ */
+ protected Date expandEndDate(Date end, boolean expandToFullWeek) {
+ // If the duration is more than week, use monthly view and get startweek
+ // and endweek. Example if views daterange is from tuesday to next weeks
+ // wednesday->expand to monday to nextweeks sunday. If firstdayofweek =
+ // monday
+ if (expandToFullWeek) {
+ end = getLastDateForWeek(end);
+
+ } else {
+ end = (Date) end.clone();
+ }
+
+ // Always expand to the start of the first day to the end of the last
+ // day
+ end = getEndOfDay(currentCalendar, end);
+
+ return end;
+ }
+
+ /**
+ * Set the {@link com.vaadin.addon.calendar.event.CalendarEventProvider
+ * CalendarEventProvider} to be used with this calendar. The EventProvider
+ * is used to query for events to show, and must be non-null. By default a
+ * {@link com.vaadin.addon.calendar.event.BasicEventProvider
+ * BasicEventProvider} is used.
+ *
+ * @param calendarEventProvider
+ * the calendarEventProvider to set. Cannot be null.
+ */
+ public void setEventProvider(CalendarEventProvider calendarEventProvider) {
+ if (calendarEventProvider == null) {
+ throw new IllegalArgumentException(
+ "Calendar event provider cannot be null");
+ }
+
+ // remove old listener
+ if (getEventProvider() instanceof EventSetChangeNotifier) {
+ ((EventSetChangeNotifier) getEventProvider())
+ .removeEventSetChangeListener(this);
+ }
+
+ this.calendarEventProvider = calendarEventProvider;
+
+ // add new listener
+ if (calendarEventProvider instanceof EventSetChangeNotifier) {
+ ((EventSetChangeNotifier) calendarEventProvider)
+ .addEventSetChangeListener(this);
+ }
+ }
+
+ /**
+ * @return the {@link com.vaadin.addon.calendar.event.CalendarEventProvider
+ * CalendarEventProvider} currently used
+ */
+ public CalendarEventProvider getEventProvider() {
+ return calendarEventProvider;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarEvents.EventChangeListener#eventChange
+ * (com.vaadin.addon.calendar.ui.CalendarEvents.EventChange)
+ */
+ @Override
+ public void eventSetChange(EventSetChangeEvent changeEvent) {
+ // sanity check
+ if (calendarEventProvider == changeEvent.getProvider()) {
+ markAsDirty();
+ }
+ }
+
+ /**
+ * Set the handler for the given type information. Mirrors
+ * {@link #addListener(String, Class, Object, Method) addListener} from
+ * AbstractComponent
+ *
+ * @param eventId
+ * A unique id for the event. Usually one of
+ * {@link CalendarEventId}
+ * @param eventType
+ * The class of the event, most likely a subclass of
+ * {@link CalendarComponentEvent}
+ * @param listener
+ * A listener that listens to the given event
+ * @param listenerMethod
+ * The method on the lister to call when the event is triggered
+ */
+ protected void setHandler(String eventId, Class<?> eventType,
+ EventListener listener, Method listenerMethod) {
+ if (handlers.get(eventId) != null) {
+ removeListener(eventId, eventType, handlers.get(eventId));
+ handlers.remove(eventId);
+ }
+
+ if (listener != null) {
+ addListener(eventId, eventType, listener, listenerMethod);
+ handlers.put(eventId, listener);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.NavigationNotifier
+ * #addListener
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.ForwardHandler)
+ */
+ @Override
+ public void setHandler(ForwardHandler listener) {
+ setHandler(ForwardEvent.EVENT_ID, ForwardEvent.class, listener,
+ ForwardHandler.forwardMethod);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.NavigationNotifier
+ * #addListener
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.BackwardHandler)
+ */
+ @Override
+ public void setHandler(BackwardHandler listener) {
+ setHandler(BackwardEvent.EVENT_ID, BackwardEvent.class, listener,
+ BackwardHandler.backwardMethod);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.NavigationNotifier
+ * #addListener
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.DateClickHandler)
+ */
+ @Override
+ public void setHandler(DateClickHandler listener) {
+ setHandler(DateClickEvent.EVENT_ID, DateClickEvent.class, listener,
+ DateClickHandler.dateClickMethod);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.NavigationNotifier
+ * #addListener
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventClickHandler)
+ */
+ @Override
+ public void setHandler(EventClickHandler listener) {
+ setHandler(EventClick.EVENT_ID, EventClick.class, listener,
+ EventClickHandler.eventClickMethod);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.NavigationNotifier
+ * #addListener
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.WeekClickHandler)
+ */
+ @Override
+ public void setHandler(WeekClickHandler listener) {
+ setHandler(WeekClick.EVENT_ID, WeekClick.class, listener,
+ WeekClickHandler.weekClickMethod);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventResizeNotifier
+ * #addListener
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventResizeHandler
+ * )
+ */
+ @Override
+ public void setHandler(EventResizeHandler listener) {
+ setHandler(EventResize.EVENT_ID, EventResize.class, listener,
+ EventResizeHandler.eventResizeMethod);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.RangeSelectNotifier
+ * #addListener
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.RangeSelectHandler
+ * )
+ */
+ @Override
+ public void setHandler(RangeSelectHandler listener) {
+ setHandler(RangeSelectEvent.EVENT_ID, RangeSelectEvent.class, listener,
+ RangeSelectHandler.rangeSelectMethod);
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventMoveNotifier
+ * #addListener
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventMoveHandler)
+ */
+ @Override
+ public void setHandler(EventMoveHandler listener) {
+ setHandler(MoveEvent.EVENT_ID, MoveEvent.class, listener,
+ EventMoveHandler.eventMoveMethod);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.CalendarEventNotifier
+ * #getHandler(java.lang.String)
+ */
+ @Override
+ public EventListener getHandler(String eventId) {
+ return handlers.get(eventId);
+ }
+
+ /**
+ * Get the currently active drop handler
+ */
+ @Override
+ public DropHandler getDropHandler() {
+ return dropHandler;
+ }
+
+ /**
+ * Set the drop handler for the calendar See {@link DropHandler} for
+ * implementation details.
+ *
+ * @param dropHandler
+ * The drop handler to set
+ */
+ public void setDropHandler(DropHandler dropHandler) {
+ this.dropHandler = dropHandler;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.event.dd.DropTarget#translateDropTargetDetails(java.util.Map)
+ */
+ @Override
+ public TargetDetails translateDropTargetDetails(
+ Map<String, Object> clientVariables) {
+ Map<String, Object> serverVariables = new HashMap<String, Object>(1);
+
+ if (clientVariables.containsKey("dropSlotIndex")) {
+ int slotIndex = (Integer) clientVariables.get("dropSlotIndex");
+ int dayIndex = (Integer) clientVariables.get("dropDayIndex");
+
+ currentCalendar.setTime(getStartOfDay(currentCalendar, startDate));
+ currentCalendar.add(java.util.Calendar.DATE, dayIndex);
+
+ // change this if slot length is modified
+ currentCalendar.add(java.util.Calendar.MINUTE, slotIndex * 30);
+
+ serverVariables.put("dropTime", currentCalendar.getTime());
+
+ } else {
+ int dayIndex = (Integer) clientVariables.get("dropDayIndex");
+ currentCalendar.setTime(expandStartDate(startDate, true));
+ currentCalendar.add(java.util.Calendar.DATE, dayIndex);
+ serverVariables.put("dropDay", currentCalendar.getTime());
+ }
+
+ CalendarTargetDetails td = new CalendarTargetDetails(serverVariables,
+ this);
+ td.setHasDropTime(clientVariables.containsKey("dropSlotIndex"));
+
+ return td;
+ }
+
+ /**
+ * Sets a container as a data source for the events in the calendar.
+ * Equivalent for doing
+ * <code>Calendar.setEventProvider(new ContainerEventProvider(container))</code>
+ *
+ * Use this method if you are adding a container which uses the default
+ * property ids like {@link BeanItemContainer} for instance. If you are
+ * using custom properties instead use
+ * {@link Calendar#setContainerDataSource(com.vaadin.data.Container.Indexed, Object, Object, Object, Object, Object)}
+ *
+ * Please note that the container must be sorted by date!
+ *
+ * @param container
+ * The container to use as a datasource
+ */
+ public void setContainerDataSource(Container.Indexed container) {
+ ContainerEventProvider provider = new ContainerEventProvider(container);
+ provider.addEventSetChangeListener(new CalendarEventProvider.EventSetChangeListener() {
+ @Override
+ public void eventSetChange(EventSetChangeEvent changeEvent) {
+ // Repaint if events change
+ markAsDirty();
+ }
+ });
+ provider.addEventChangeListener(new EventChangeListener() {
+ @Override
+ public void eventChange(EventChangeEvent changeEvent) {
+ // Repaint if event changes
+ markAsDirty();
+ }
+ });
+ setEventProvider(provider);
+ }
+
+ /**
+ * Sets a container as a data source for the events in the calendar.
+ * Equivalent for doing
+ * <code>Calendar.setEventProvider(new ContainerEventProvider(container))</code>
+ *
+ * Please note that the container must be sorted by date!
+ *
+ * @param container
+ * The container to use as a data source
+ * @param captionProperty
+ * The property that has the caption, null if no caption property
+ * is present
+ * @param descriptionProperty
+ * The property that has the description, null if no description
+ * property is present
+ * @param startDateProperty
+ * The property that has the starting date
+ * @param endDateProperty
+ * The property that has the ending date
+ * @param styleNameProperty
+ * The property that has the stylename, null if no stylname
+ * property is present
+ */
+ public void setContainerDataSource(Container.Indexed container,
+ Object captionProperty, Object descriptionProperty,
+ Object startDateProperty, Object endDateProperty,
+ Object styleNameProperty) {
+ ContainerEventProvider provider = new ContainerEventProvider(container);
+ provider.setCaptionProperty(captionProperty);
+ provider.setDescriptionProperty(descriptionProperty);
+ provider.setStartDateProperty(startDateProperty);
+ provider.setEndDateProperty(endDateProperty);
+ provider.setStyleNameProperty(styleNameProperty);
+ provider.addEventSetChangeListener(new CalendarEventProvider.EventSetChangeListener() {
+ @Override
+ public void eventSetChange(EventSetChangeEvent changeEvent) {
+ // Repaint if events change
+ markAsDirty();
+ }
+ });
+ provider.addEventChangeListener(new EventChangeListener() {
+ @Override
+ public void eventChange(EventChangeEvent changeEvent) {
+ // Repaint if event changes
+ markAsDirty();
+ }
+ });
+ setEventProvider(provider);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEventProvider#getEvents(java.
+ * util.Date, java.util.Date)
+ */
+ @Override
+ public List<CalendarEvent> getEvents(Date startDate, Date endDate) {
+ return getEventProvider().getEvents(startDate, endDate);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEditableEventProvider#addEvent
+ * (com.vaadin.addon.calendar.event.CalendarEvent)
+ */
+ @Override
+ public void addEvent(CalendarEvent event) {
+ if (getEventProvider() instanceof CalendarEditableEventProvider) {
+ CalendarEditableEventProvider provider = (CalendarEditableEventProvider) getEventProvider();
+ provider.addEvent(event);
+ markAsDirty();
+ } else {
+ throw new UnsupportedOperationException(
+ "Event provider does not support adding events");
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEditableEventProvider#removeEvent
+ * (com.vaadin.addon.calendar.event.CalendarEvent)
+ */
+ @Override
+ public void removeEvent(CalendarEvent event) {
+ if (getEventProvider() instanceof CalendarEditableEventProvider) {
+ CalendarEditableEventProvider provider = (CalendarEditableEventProvider) getEventProvider();
+ provider.removeEvent(event);
+ markAsDirty();
+ } else {
+ throw new UnsupportedOperationException(
+ "Event provider does not support removing events");
+ }
+ }
+
+ /**
+ * Adds an action handler to the calender that handles event produced by the
+ * context menu.
+ *
+ * <p>
+ * The {@link Handler#getActions(Object, Object)} parameters depend on what
+ * view the Calendar is in:
+ * <ul>
+ * <li>If the Calendar is in <i>Day or Week View</i> then the target
+ * parameter will be a {@link CalendarDateRange} with a range of
+ * half-an-hour. The {@link Handler#getActions(Object, Object)} method will
+ * be called once per half-hour slot.</li>
+ * <li>If the Calendar is in <i>Month View</i> then the target parameter
+ * will be a {@link CalendarDateRange} with a range of one day. The
+ * {@link Handler#getActions(Object, Object)} will be called once for each
+ * day.
+ * </ul>
+ * The Dates passed into the {@link CalendarDateRange} are in the same
+ * timezone as the calendar is.
+ * </p>
+ *
+ * <p>
+ * The {@link Handler#handleAction(Action, Object, Object)} parameters
+ * depend on what the context menu is called upon:
+ * <ul>
+ * <li>If the context menu is called upon an event then the target parameter
+ * is the event, i.e. instanceof {@link CalendarEvent}</li>
+ * <li>If the context menu is called upon an empty slot then the target is a
+ * {@link Date} representing that slot
+ * </ul>
+ * </p>
+ */
+ @Override
+ public void addActionHandler(Handler actionHandler) {
+ if (actionHandler != null) {
+ if (actionHandlers == null) {
+ actionHandlers = new LinkedList<Action.Handler>();
+ actionMapper = new KeyMapper<Action>();
+ }
+ if (!actionHandlers.contains(actionHandler)) {
+ actionHandlers.add(actionHandler);
+ markAsDirty();
+ }
+ }
+ }
+
+ /**
+ * Is the calendar in a mode where all days of the month is shown
+ *
+ * @return Returns true if calendar is in monthly mode and false if it is in
+ * weekly mode
+ */
+ public boolean isMonthlyMode() {
+ CalendarState state = (CalendarState) getState(false);
+ if (state.days != null) {
+ return state.days.size() > 7;
+ } else {
+ // Default mode
+ return true;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.event.Action.Container#removeActionHandler(com.vaadin.event
+ * .Action.Handler)
+ */
+ @Override
+ public void removeActionHandler(Handler actionHandler) {
+ if (actionHandlers != null && actionHandlers.contains(actionHandler)) {
+ actionHandlers.remove(actionHandler);
+ if (actionHandlers.isEmpty()) {
+ actionHandlers = null;
+ actionMapper = null;
+ }
+ markAsDirty();
+ }
+ }
+
+ private class CalendarServerRpcImpl implements CalendarServerRpc {
+
+ @Override
+ public void eventMove(int eventIndex, String newDate) {
+ if (!isClientChangeAllowed()) {
+ return;
+ }
+ if (newDate != null) {
+ try {
+ Date d = df_date_time.parse(newDate);
+ if (eventIndex >= 0 && eventIndex < events.size()
+ && events.get(eventIndex) != null) {
+ fireEventMove(eventIndex, d);
+ }
+ } catch (ParseException e) {
+ getLogger().log(Level.WARNING, e.getMessage());
+ }
+ }
+ }
+
+ @Override
+ public void rangeSelect(String range) {
+ if (!isClientChangeAllowed()) {
+ return;
+ }
+
+ if (range != null && range.length() > 14 && range.contains("TO")) {
+ String[] dates = range.split("TO");
+ try {
+ Date d1 = df_date.parse(dates[0]);
+ Date d2 = df_date.parse(dates[1]);
+
+ fireRangeSelect(d1, d2, true);
+
+ } catch (ParseException e) {
+ // NOP
+ }
+ } else if (range != null && range.length() > 12
+ && range.contains(":")) {
+ String[] dates = range.split(":");
+ if (dates.length == 3) {
+ try {
+ Date d = df_date.parse(dates[0]);
+ currentCalendar.setTime(d);
+ int startMinutes = Integer.parseInt(dates[1]);
+ int endMinutes = Integer.parseInt(dates[2]);
+ currentCalendar.add(java.util.Calendar.MINUTE,
+ startMinutes);
+ Date start = currentCalendar.getTime();
+ currentCalendar.add(java.util.Calendar.MINUTE,
+ endMinutes - startMinutes);
+ Date end = currentCalendar.getTime();
+ fireRangeSelect(start, end, false);
+ } catch (ParseException e) {
+ // NOP
+ } catch (NumberFormatException e) {
+ // NOP
+ }
+ }
+ }
+ }
+
+ @Override
+ public void forward() {
+ fireEvent(new ForwardEvent(Calendar.this));
+ }
+
+ @Override
+ public void backward() {
+ fireEvent(new BackwardEvent(Calendar.this));
+ }
+
+ @Override
+ public void dateClick(String date) {
+ if (!isClientChangeAllowed()) {
+ return;
+ }
+ if (date != null && date.length() > 6) {
+ try {
+ Date d = df_date.parse(date);
+ fireDateClick(d);
+ } catch (ParseException e) {
+ }
+ }
+ }
+
+ @Override
+ public void weekClick(String event) {
+ if (!isClientChangeAllowed()) {
+ return;
+ }
+ if (event.length() > 0 && event.contains("w")) {
+ String[] splitted = event.split("w");
+ if (splitted.length == 2) {
+ try {
+ int yr = 1900 + Integer.parseInt(splitted[0]);
+ int week = Integer.parseInt(splitted[1]);
+ fireWeekClick(week, yr);
+ } catch (NumberFormatException e) {
+ // NOP
+ }
+ }
+ }
+ }
+
+ @Override
+ public void eventClick(int eventIndex) {
+ if (!isEventClickAllowed()) {
+ return;
+ }
+ if (eventIndex >= 0 && eventIndex < events.size()
+ && events.get(eventIndex) != null) {
+ fireEventClick(eventIndex);
+ }
+ }
+
+ @Override
+ public void eventResize(int eventIndex, String newStartDate,
+ String newEndDate) {
+ if (!isClientChangeAllowed()) {
+ return;
+ }
+ if (newStartDate != null && !"".equals(newStartDate)
+ && newEndDate != null && !"".equals(newEndDate)) {
+ try {
+ Date newStartTime = df_date_time.parse(newStartDate);
+ Date newEndTime = df_date_time.parse(newEndDate);
+
+ fireEventResize(eventIndex, newStartTime, newEndTime);
+ } catch (ParseException e) {
+ // NOOP
+ }
+ }
+ }
+
+ @Override
+ public void scroll(int scrollPosition) {
+ scrollTop = scrollPosition;
+ markAsDirty();
+ }
+
+ @Override
+ public void actionOnEmptyCell(String actionKey, String startDate,
+ String endDate) {
+ Action action = actionMapper.get(actionKey);
+ SimpleDateFormat formatter = new SimpleDateFormat(
+ DateConstants.ACTION_DATE_FORMAT_PATTERN);
+ formatter.setTimeZone(getTimeZone());
+ try {
+ Date start = formatter.parse(startDate);
+ for (Action.Handler ah : actionHandlers) {
+ ah.handleAction(action, this, start);
+ }
+
+ } catch (ParseException e) {
+ getLogger().log(Level.WARNING,
+ "Could not parse action date string");
+ }
+
+ }
+
+ @Override
+ public void actionOnEvent(String actionKey, String startDate,
+ String endDate, int eventIndex) {
+ Action action = actionMapper.get(actionKey);
+ SimpleDateFormat formatter = new SimpleDateFormat(
+ DateConstants.ACTION_DATE_FORMAT_PATTERN);
+ formatter.setTimeZone(getTimeZone());
+ for (Action.Handler ah : actionHandlers) {
+ ah.handleAction(action, this, events.get(eventIndex));
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/server/src/com/vaadin/ui/ConnectorTracker.java b/server/src/com/vaadin/ui/ConnectorTracker.java
index a229003224..85cdcdf65c 100644
--- a/server/src/com/vaadin/ui/ConnectorTracker.java
+++ b/server/src/com/vaadin/ui/ConnectorTracker.java
@@ -17,6 +17,7 @@ package com.vaadin.ui;
import java.io.IOException;
import java.io.Serializable;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
@@ -31,9 +32,9 @@ import org.json.JSONException;
import org.json.JSONObject;
import com.vaadin.server.AbstractClientConnector;
-import com.vaadin.server.AbstractCommunicationManager;
import com.vaadin.server.ClientConnector;
import com.vaadin.server.GlobalResourceHandler;
+import com.vaadin.server.LegacyCommunicationManager;
import com.vaadin.server.StreamVariable;
/**
@@ -295,7 +296,7 @@ public class ConnectorTracker implements Serializable {
uninitializedConnectors.remove(connector);
diffStates.remove(connector);
iterator.remove();
- } else if (!AbstractCommunicationManager
+ } else if (!LegacyCommunicationManager
.isConnectorVisibleToClient(connector)
&& !uninitializedConnectors.contains(connector)) {
uninitializedConnectors.add(connector);
@@ -463,6 +464,31 @@ public class ConnectorTracker implements Serializable {
return dirtyConnectors;
}
+ /**
+ * Checks if there a dirty connectors.
+ *
+ * @return true if there are dirty connectors, false otherwise
+ */
+ public boolean hasDirtyConnectors() {
+ return !getDirtyConnectors().isEmpty();
+ }
+
+ /**
+ * Returns a collection of those {@link #getDirtyConnectors() dirty
+ * connectors} that are actually visible to the client.
+ *
+ * @return A list of dirty and visible connectors.
+ */
+ public ArrayList<ClientConnector> getDirtyVisibleConnectors() {
+ ArrayList<ClientConnector> dirtyConnectors = new ArrayList<ClientConnector>();
+ for (ClientConnector c : getDirtyConnectors()) {
+ if (LegacyCommunicationManager.isConnectorVisibleToClient(c)) {
+ dirtyConnectors.add(c);
+ }
+ }
+ return dirtyConnectors;
+ }
+
public JSONObject getDiffState(ClientConnector connector) {
assert getConnector(connector.getConnectorId()) == connector;
return diffStates.get(connector);
diff --git a/server/src/com/vaadin/ui/DateField.java b/server/src/com/vaadin/ui/DateField.java
index 1a8955801b..5017fac993 100644
--- a/server/src/com/vaadin/ui/DateField.java
+++ b/server/src/com/vaadin/ui/DateField.java
@@ -19,10 +19,8 @@ package com.vaadin.ui;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collection;
-import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
-import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
@@ -31,6 +29,7 @@ import com.vaadin.data.Property;
import com.vaadin.data.Validator;
import com.vaadin.data.Validator.InvalidValueException;
import com.vaadin.data.util.converter.Converter;
+import com.vaadin.data.validator.DateRangeValidator;
import com.vaadin.event.FieldEvents;
import com.vaadin.event.FieldEvents.BlurEvent;
import com.vaadin.event.FieldEvents.BlurListener;
@@ -40,6 +39,7 @@ import com.vaadin.server.PaintException;
import com.vaadin.server.PaintTarget;
import com.vaadin.shared.ui.datefield.DateFieldConstants;
import com.vaadin.shared.ui.datefield.Resolution;
+import com.vaadin.shared.ui.datefield.TextualDateFieldState;
/**
* <p>
@@ -148,6 +148,10 @@ public class DateField extends AbstractField<Date> implements
private TimeZone timeZone = null;
private static Map<Resolution, String> variableNameForResolution = new HashMap<Resolution, String>();
+
+ private String dateOutOfRangeMessage = "Date is out of allowed range";
+
+ private DateRangeValidator currentRangeValidator;
{
variableNameForResolution.put(Resolution.SECOND, "sec");
variableNameForResolution.put(Resolution.MINUTE, "min");
@@ -279,6 +283,174 @@ public class DateField extends AbstractField<Date> implements
return super.shouldHideErrors() && uiHasValidDateString;
}
+ @Override
+ protected TextualDateFieldState getState() {
+ return (TextualDateFieldState) super.getState();
+ }
+
+ @Override
+ protected TextualDateFieldState getState(boolean markAsDirty) {
+ return (TextualDateFieldState) super.getState(markAsDirty);
+ }
+
+ /**
+ * Sets the start range for this component. If the value is set before this
+ * date (taking the resolution into account), the component will not
+ * validate. If <code>startDate</code> is set to <code>null</code>, any
+ * value before <code>endDate</code> will be accepted by the range
+ *
+ * @param startDate
+ * - the allowed range's start date
+ */
+ public void setRangeStart(Date startDate) {
+ if (startDate != null && getState().rangeEnd != null
+ && startDate.after(getState().rangeEnd)) {
+ throw new IllegalStateException(
+ "startDate cannot be later than endDate");
+ }
+ getState().rangeStart = startDate;
+ // rangeStart = startDate;
+ // This has to be done to correct for the resolution
+ // updateRangeState();
+ updateRangeValidator();
+ }
+
+ /**
+ * Sets the current error message if the range validation fails.
+ *
+ * @param dateOutOfRangeMessage
+ * - Localizable message which is shown when value (the date) is
+ * set outside allowed range
+ */
+ public void setDateOutOfRangeMessage(String dateOutOfRangeMessage) {
+ this.dateOutOfRangeMessage = dateOutOfRangeMessage;
+ updateRangeValidator();
+ }
+
+ /**
+ * Gets the end range for a certain resolution. The range is inclusive, so
+ * if rangeEnd is set to zero milliseconds past year n and resolution is set
+ * to YEAR, any date in year n will be accepted. Resolutions lower than DAY
+ * will be interpreted on a DAY level. That is, everything below DATE is
+ * cleared
+ *
+ * @param forResolution
+ * - the range conforms to the resolution
+ * @return
+ */
+ private Date getRangeEnd(Resolution forResolution) {
+ // We need to set the correct resolution for the dates,
+ // otherwise the range validator will complain
+
+ Date rangeEnd = getState(false).rangeEnd;
+ if (rangeEnd == null) {
+ return null;
+ }
+
+ Calendar endCal = Calendar.getInstance();
+ endCal.setTime(rangeEnd);
+
+ if (forResolution == Resolution.YEAR) {
+ // Adding one year (minresolution) and clearing the rest.
+ endCal.set(endCal.get(Calendar.YEAR) + 1, 0, 1, 0, 0, 0);
+ } else if (forResolution == Resolution.MONTH) {
+ // Adding one month (minresolution) and clearing the rest.
+ endCal.set(endCal.get(Calendar.YEAR),
+ endCal.get(Calendar.MONTH) + 1, 1, 0, 0, 0);
+ } else {
+ endCal.set(endCal.get(Calendar.YEAR), endCal.get(Calendar.MONTH),
+ endCal.get(Calendar.DATE) + 1, 0, 0, 0);
+ }
+ // removing one millisecond will now get the endDate to return to
+ // current resolution's set time span (year or month)
+ endCal.set(Calendar.MILLISECOND, -1);
+ return endCal.getTime();
+ }
+
+ /**
+ * Gets the start range for a certain resolution. The range is inclusive, so
+ * if <code>rangeStart</code> is set to one millisecond before year n and
+ * resolution is set to YEAR, any date in year n - 1 will be accepted.
+ * Lowest supported resolution is DAY.
+ *
+ * @param forResolution
+ * - the range conforms to the resolution
+ * @return
+ */
+ private Date getRangeStart(Resolution forResolution) {
+ if (getState(false).rangeStart == null) {
+ return null;
+ }
+ Calendar startCal = Calendar.getInstance();
+ startCal.setTime(getState(false).rangeStart);
+
+ if (forResolution == Resolution.YEAR) {
+ startCal.set(startCal.get(Calendar.YEAR), 0, 1, 0, 0, 0);
+ } else if (forResolution == Resolution.MONTH) {
+ startCal.set(startCal.get(Calendar.YEAR),
+ startCal.get(Calendar.MONTH), 1, 0, 0, 0);
+ } else {
+ startCal.set(startCal.get(Calendar.YEAR),
+ startCal.get(Calendar.MONTH), startCal.get(Calendar.DATE),
+ 0, 0, 0);
+ }
+
+ startCal.set(Calendar.MILLISECOND, 0);
+ return startCal.getTime();
+ }
+
+ private void updateRangeValidator() {
+ if (currentRangeValidator != null) {
+ removeValidator(currentRangeValidator);
+ }
+
+ currentRangeValidator = new DateRangeValidator(dateOutOfRangeMessage,
+ getRangeStart(resolution), getRangeEnd(resolution), null);
+
+ addValidator(currentRangeValidator);
+
+ }
+
+ /**
+ * Sets the end range for this component. If the value is set after this
+ * date (taking the resolution into account), the component will not
+ * validate. If <code>endDate</code> is set to <code>null</code>, any value
+ * after <code>startDate</code> will be accepted by the range.
+ *
+ * @param endDate
+ * - the allowed range's end date (inclusive, based on the
+ * current resolution)
+ */
+ public void setRangeEnd(Date endDate) {
+ if (endDate != null && getState().rangeStart != null
+ && getState().rangeStart.after(endDate)) {
+ throw new IllegalStateException(
+ "endDate cannot be earlier than startDate");
+ }
+ // rangeEnd = endDate;
+ getState().rangeEnd = endDate;
+ updateRangeValidator();
+ }
+
+ /**
+ * Returns the precise rangeStart used.
+ *
+ * @param startDate
+ *
+ */
+ public Date getRangeStart() {
+ return getState(false).rangeStart;
+ }
+
+ /**
+ * Returns the precise rangeEnd used.
+ *
+ * @param startDate
+ */
+ public Date getRangeEnd() {
+ return getState(false).rangeEnd;
+ }
+
/*
* Invoked when a variable of the component changes. Don't add a JavaDoc
* comment here, we use the default documentation from implemented
@@ -574,6 +746,7 @@ public class DateField extends AbstractField<Date> implements
*/
public void setResolution(Resolution resolution) {
this.resolution = resolution;
+ updateRangeValidator();
markAsDirty();
}
diff --git a/server/src/com/vaadin/ui/GridLayout.java b/server/src/com/vaadin/ui/GridLayout.java
index e60d9c676a..53a25c1c83 100644
--- a/server/src/com/vaadin/ui/GridLayout.java
+++ b/server/src/com/vaadin/ui/GridLayout.java
@@ -92,6 +92,7 @@ public class GridLayout extends AbstractLayout implements
private Map<Integer, Float> columnExpandRatio = new HashMap<Integer, Float>();
private Map<Integer, Float> rowExpandRatio = new HashMap<Integer, Float>();
+ private Alignment defaultComponentAlignment = Alignment.TOP_LEFT;
/**
* Constructor for a grid of given size (number of columns and rows).
@@ -573,6 +574,7 @@ public class GridLayout extends AbstractLayout implements
int row2) {
this.component = component;
childData = new ChildComponentData();
+ childData.alignment = getDefaultComponentAlignment().getBitMask();
childData.column1 = column1;
childData.row1 = row1;
childData.column2 = column2;
@@ -1226,4 +1228,26 @@ public class GridLayout extends AbstractLayout implements
return new MarginInfo(getState().marginsBitmask);
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Layout.AlignmentHandler#getDefaultComponentAlignment()
+ */
+ @Override
+ public Alignment getDefaultComponentAlignment() {
+ return defaultComponentAlignment;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.ui.Layout.AlignmentHandler#setDefaultComponentAlignment(com
+ * .vaadin.ui.Alignment)
+ */
+ @Override
+ public void setDefaultComponentAlignment(Alignment defaultAlignment) {
+ defaultComponentAlignment = defaultAlignment;
+ }
+
}
diff --git a/server/src/com/vaadin/ui/Label.java b/server/src/com/vaadin/ui/Label.java
index f49a1403cf..d037652a09 100644
--- a/server/src/com/vaadin/ui/Label.java
+++ b/server/src/com/vaadin/ui/Label.java
@@ -21,10 +21,13 @@ import java.util.Locale;
import java.util.logging.Logger;
import com.vaadin.data.Property;
+import com.vaadin.data.util.AbstractProperty;
+import com.vaadin.data.util.LegacyPropertyHelper;
import com.vaadin.data.util.converter.Converter;
import com.vaadin.data.util.converter.ConverterUtil;
import com.vaadin.shared.ui.label.ContentMode;
import com.vaadin.shared.ui.label.LabelState;
+import com.vaadin.shared.util.SharedUtil;
/**
* Label component for showing non-editable short texts.
@@ -203,23 +206,6 @@ public class Label extends AbstractComponent implements Property<String>,
}
/**
- * Returns the value displayed by this label.
- *
- * @see java.lang.Object#toString()
- * @deprecated As of 7.0, use {@link #getValue()} to get the value of the
- * label or {@link #getPropertyDataSource()} .getValue() to get
- * the value of the data source.
- */
- @Deprecated
- @Override
- public String toString() {
- logger.warning("You are using Label.toString() to get the value for a "
- + getClass().getSimpleName()
- + ". This will not be supported starting from Vaadin 7.1 (your debugger might call toString() and cause this message to appear).");
- return getValue();
- }
-
- /**
* Gets the type of the Property.
*
* @see com.vaadin.data.Property#getType()
@@ -419,7 +405,7 @@ public class Label extends AbstractComponent implements Property<String>,
private void updateValueFromDataSource() {
// Update the internal value from the data source
String newConvertedValue = getDataSourceValue();
- if (!AbstractField.equals(newConvertedValue, getState().text)) {
+ if (!SharedUtil.equals(newConvertedValue, getState().text)) {
getState().text = newConvertedValue;
fireValueChange();
}
@@ -541,4 +527,35 @@ public class Label extends AbstractComponent implements Property<String>,
markAsDirty();
}
+ /**
+ * Returns a string representation of this object. The returned string
+ * representation depends on if the legacy Property toString mode is enabled
+ * or disabled.
+ * <p>
+ * If legacy Property toString mode is enabled, returns the value displayed
+ * by this label.
+ * </p>
+ * <p>
+ * If legacy Property toString mode is disabled, the string representation
+ * has no special meaning
+ * </p>
+ *
+ * @see AbstractProperty#isLegacyToStringEnabled()
+ *
+ * @return The value displayed by this label or a string representation of
+ * this Label object.
+ *
+ * @deprecated As of 7.0, use {@link #getValue()} to get the value of the
+ * label or {@link #getPropertyDataSource()}.getValue() to get
+ * the value of the data source.
+ */
+ @Deprecated
+ @Override
+ public String toString() {
+ if (!LegacyPropertyHelper.isLegacyToStringEnabled()) {
+ return super.toString();
+ } else {
+ return LegacyPropertyHelper.legacyPropertyToString(this);
+ }
+ }
}
diff --git a/server/src/com/vaadin/ui/Layout.java b/server/src/com/vaadin/ui/Layout.java
index cd6ffc42d2..dc16b186f2 100644
--- a/server/src/com/vaadin/ui/Layout.java
+++ b/server/src/com/vaadin/ui/Layout.java
@@ -61,6 +61,23 @@ public interface Layout extends ComponentContainer, Serializable {
*/
public Alignment getComponentAlignment(Component childComponent);
+ /**
+ * Sets the alignment used for new components added to this layout. The
+ * default is {@link Alignment#TOP_LEFT}.
+ *
+ * @param defaultComponentAlignment
+ * The new default alignment
+ */
+ public void setDefaultComponentAlignment(
+ Alignment defaultComponentAlignment);
+
+ /**
+ * Returns the alignment used for new components added to this layout
+ *
+ * @return The default alignment
+ */
+ public Alignment getDefaultComponentAlignment();
+
}
/**
diff --git a/server/src/com/vaadin/ui/LoadingIndicatorConfiguration.java b/server/src/com/vaadin/ui/LoadingIndicatorConfiguration.java
new file mode 100644
index 0000000000..57ccdc1b64
--- /dev/null
+++ b/server/src/com/vaadin/ui/LoadingIndicatorConfiguration.java
@@ -0,0 +1,160 @@
+/*
+ * 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.UIState.LoadingIndicatorConfigurationState;
+
+/**
+ * Provides method for configuring the loading indicator.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public interface LoadingIndicatorConfiguration extends Serializable {
+ /**
+ * Sets the delay before the loading indicator is shown. The default is
+ * 300ms.
+ *
+ * @param firstDelay
+ * The first delay (in ms)
+ */
+ public void setFirstDelay(int firstDelay);
+
+ /**
+ * Returns the delay before the loading indicator is shown.
+ *
+ * @return The first delay (in ms)
+ */
+ public int getFirstDelay();
+
+ /**
+ * Sets the delay before the loading indicator goes into the "second" state.
+ * The delay is calculated from the time when the loading indicator was
+ * triggered. The default is 1500ms.
+ *
+ * @param secondDelay
+ * The delay before going into the "second" state (in ms)
+ */
+ public void setSecondDelay(int secondDelay);
+
+ /**
+ * Returns the delay before the loading indicator goes into the "second"
+ * state. The delay is calculated from the time when the loading indicator
+ * was triggered.
+ *
+ * @return The delay before going into the "second" state (in ms)
+ */
+ public int getSecondDelay();
+
+ /**
+ * Sets the delay before the loading indicator goes into the "third" state.
+ * The delay is calculated from the time when the loading indicator was
+ * triggered. The default is 5000ms.
+ *
+ * @param thirdDelay
+ * The delay before going into the "third" state (in ms)
+ */
+ public void setThirdDelay(int thirdDelay);
+
+ /**
+ * Returns the delay before the loading indicator goes into the "third"
+ * state. The delay is calculated from the time when the loading indicator
+ * was triggered.
+ *
+ * @return The delay before going into the "third" state (in ms)
+ */
+ public int getThirdDelay();
+}
+
+class LoadingIndicatorConfigurationImpl implements
+ LoadingIndicatorConfiguration {
+ private UI ui;
+
+ public LoadingIndicatorConfigurationImpl(UI ui) {
+ this.ui = ui;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.LoadingIndicator#setFirstDelay(int)
+ */
+ @Override
+ public void setFirstDelay(int firstDelay) {
+ getState().firstDelay = firstDelay;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.LoadingIndicator#getFirstDelay()
+ */
+ @Override
+ public int getFirstDelay() {
+ return getState(false).firstDelay;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.LoadingIndicator#setSecondDelay(int)
+ */
+ @Override
+ public void setSecondDelay(int secondDelay) {
+ getState().secondDelay = secondDelay;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.LoadingIndicator#getSecondDelay()
+ */
+ @Override
+ public int getSecondDelay() {
+ return getState(false).secondDelay;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.LoadingIndicator#setThirdDelay(int)
+ */
+ @Override
+ public void setThirdDelay(int thirdDelay) {
+ getState().thirdDelay = thirdDelay;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.LoadingIndicator#getThirdDelay()
+ */
+ @Override
+ public int getThirdDelay() {
+ return getState(false).thirdDelay;
+ }
+
+ private LoadingIndicatorConfigurationState getState() {
+ return ui.getState().loadingIndicatorConfiguration;
+ }
+
+ private LoadingIndicatorConfigurationState getState(boolean markAsDirty) {
+ return ui.getState(markAsDirty).loadingIndicatorConfiguration;
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/LoginForm.java b/server/src/com/vaadin/ui/LoginForm.java
index 11ae1b379b..d06882927e 100644
--- a/server/src/com/vaadin/ui/LoginForm.java
+++ b/server/src/com/vaadin/ui/LoginForm.java
@@ -61,24 +61,30 @@ public class LoginForm extends CustomComponent {
private Embedded iframe = new Embedded();
@Override
- public boolean handleConnectorRequest(VaadinRequest request,
- VaadinResponse response, String path) throws IOException {
- String method = VaadinServletService.getCurrentServletRequest()
- .getMethod();
+ public boolean handleConnectorRequest(final VaadinRequest request,
+ final VaadinResponse response, String path) throws IOException {
if (!path.equals("login")) {
return super.handleConnectorRequest(request, response, path);
}
- String responseString = null;
- if (method.equalsIgnoreCase("post")) {
- responseString = handleLogin(request);
- } else {
- responseString = getLoginHTML();
- }
+ final StringBuilder responseBuilder = new StringBuilder();
+
+ getUI().access(new Runnable() {
+ @Override
+ public void run() {
+ String method = VaadinServletService.getCurrentServletRequest()
+ .getMethod();
+ if (method.equalsIgnoreCase("post")) {
+ responseBuilder.append(handleLogin(request));
+ } else {
+ responseBuilder.append(getLoginHTML());
+ }
+ }
+ });
- if (responseString != null) {
+ if (responseBuilder.length() > 0) {
response.setContentType("text/html; charset=utf-8");
response.setCacheTime(-1);
- response.getWriter().write(responseString);
+ response.getWriter().write(responseBuilder.toString());
return true;
} else {
return false;
diff --git a/server/src/com/vaadin/ui/PopupDateField.java b/server/src/com/vaadin/ui/PopupDateField.java
index ae33493c89..f0bb0d74fe 100644
--- a/server/src/com/vaadin/ui/PopupDateField.java
+++ b/server/src/com/vaadin/ui/PopupDateField.java
@@ -118,4 +118,24 @@ public class PopupDateField extends DateField {
getState().textFieldEnabled = state;
}
+ /**
+ * Set a description that explains the usage of the Widget for users of
+ * assistive devices.
+ *
+ * @param description
+ * String with the description
+ */
+ public void setAssistiveText(String description) {
+ getState().descriptionForAssistiveDevices = description;
+ }
+
+ /**
+ * Get the description that explains the usage of the Widget for users of
+ * assistive devices.
+ *
+ * @return String with the description
+ */
+ public String getAssistiveText() {
+ return getState().descriptionForAssistiveDevices;
+ }
}
diff --git a/server/src/com/vaadin/ui/TooltipConfiguration.java b/server/src/com/vaadin/ui/TooltipConfiguration.java
new file mode 100644
index 0000000000..f9120aa18d
--- /dev/null
+++ b/server/src/com/vaadin/ui/TooltipConfiguration.java
@@ -0,0 +1,240 @@
+/*
+ * 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.UIState.TooltipConfigurationState;
+
+/**
+ * Provides method for configuring the tooltip.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public interface TooltipConfiguration extends Serializable {
+ /**
+ * Returns the time (in ms) the tooltip should be displayed after an event
+ * that will cause it to be closed (e.g. mouse click outside the component,
+ * key down).
+ *
+ * @return The close timeout
+ */
+ public int getCloseTimeout();
+
+ /**
+ * Sets the time (in ms) the tooltip should be displayed after an event that
+ * will cause it to be closed (e.g. mouse click outside the component, key
+ * down).
+ *
+ * @param closeTimeout
+ * The close timeout
+ */
+ public void setCloseTimeout(int closeTimeout);
+
+ /**
+ * Returns the time (in ms) during which {@link #getQuickOpenDelay()} should
+ * be used instead of {@link #getOpenDelay()}. The quick open delay is used
+ * when the tooltip has very recently been shown, is currently hidden but
+ * about to be shown again.
+ *
+ * @return The quick open timeout
+ */
+ public int getQuickOpenTimeout();
+
+ /**
+ * Sets the time (in ms) that determines when {@link #getQuickOpenDelay()}
+ * should be used instead of {@link #getOpenDelay()}. The quick open delay
+ * is used when the tooltip has very recently been shown, is currently
+ * hidden but about to be shown again.
+ *
+ * @param quickOpenTimeout
+ * The quick open timeout
+ */
+ public void setQuickOpenTimeout(int quickOpenTimeout);
+
+ /**
+ * Returns the time (in ms) that should elapse before a tooltip will be
+ * shown, in the situation when a tooltip has very recently been shown
+ * (within {@link #getQuickOpenDelay()} ms).
+ *
+ * @return The quick open delay
+ */
+ public int getQuickOpenDelay();
+
+ /**
+ * Sets the time (in ms) that should elapse before a tooltip will be shown,
+ * in the situation when a tooltip has very recently been shown (within
+ * {@link #getQuickOpenDelay()} ms).
+ *
+ * @param quickOpenDelay
+ * The quick open delay
+ */
+ public void setQuickOpenDelay(int quickOpenDelay);
+
+ /**
+ * Returns the time (in ms) that should elapse after an event triggering
+ * tooltip showing has occurred (e.g. mouse over) before the tooltip is
+ * shown. If a tooltip has recently been shown, then
+ * {@link #getQuickOpenDelay()} is used instead of this.
+ *
+ * @return The open delay
+ */
+ public int getOpenDelay();
+
+ /**
+ * Sets the time (in ms) that should elapse after an event triggering
+ * tooltip showing has occurred (e.g. mouse over) before the tooltip is
+ * shown. If a tooltip has recently been shown, then
+ * {@link #getQuickOpenDelay()} is used instead of this.
+ *
+ * @param openDelay
+ * The open delay
+ */
+ public void setOpenDelay(int openDelay);
+
+ /**
+ * Returns the maximum width of the tooltip popup.
+ *
+ * @return The maximum width the tooltip popup
+ */
+ public int getMaxWidth();
+
+ /**
+ * Sets the maximum width of the tooltip popup.
+ *
+ * @param maxWidth
+ * The maximum width the tooltip popup
+ */
+ public void setMaxWidth(int maxWidth);
+}
+
+class TooltipConfigurationImpl implements TooltipConfiguration {
+ private UI ui;
+
+ public TooltipConfigurationImpl(UI ui) {
+ this.ui = ui;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.UI.Tooltip#getCloseTimeout()
+ */
+ @Override
+ public int getCloseTimeout() {
+ return getState(false).closeTimeout;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Tooltip#setCloseTimeout(int)
+ */
+ @Override
+ public void setCloseTimeout(int closeTimeout) {
+ getState().closeTimeout = closeTimeout;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Tooltip#getQuickOpenTimeout()
+ */
+ @Override
+ public int getQuickOpenTimeout() {
+ return getState(false).quickOpenTimeout;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Tooltip#setQuickOpenTimeout(int)
+ */
+ @Override
+ public void setQuickOpenTimeout(int quickOpenTimeout) {
+ getState().quickOpenTimeout = quickOpenTimeout;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Tooltip#getQuickOpenDelay()
+ */
+ @Override
+ public int getQuickOpenDelay() {
+ return getState(false).quickOpenDelay;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Tooltip#setQuickOpenDelay(int)
+ */
+ @Override
+ public void setQuickOpenDelay(int quickOpenDelay) {
+ getState().quickOpenDelay = quickOpenDelay;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Tooltip#getOpenDelay()
+ */
+ @Override
+ public int getOpenDelay() {
+ return getState(false).openDelay;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Tooltip#setOpenDelay(int)
+ */
+ @Override
+ public void setOpenDelay(int openDelay) {
+ getState().openDelay = openDelay;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Tooltip#getMaxWidth()
+ */
+ @Override
+ public int getMaxWidth() {
+ return getState(false).maxWidth;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Tooltip#setMaxWidth(int)
+ */
+ @Override
+ public void setMaxWidth(int maxWidth) {
+ getState().maxWidth = maxWidth;
+ }
+
+ private TooltipConfigurationState getState() {
+ return ui.getState().tooltipConfiguration;
+ }
+
+ private TooltipConfigurationState getState(boolean markAsDirty) {
+ return ui.getState(markAsDirty).tooltipConfiguration;
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/Tree.java b/server/src/com/vaadin/ui/Tree.java
index a6dbea51ba..15175b5a8b 100644
--- a/server/src/com/vaadin/ui/Tree.java
+++ b/server/src/com/vaadin/ui/Tree.java
@@ -72,6 +72,13 @@ public class Tree extends AbstractSelect implements Container.Hierarchical,
/* Private members */
+ private static final String NULL_ALT_EXCEPTION_MESSAGE = "Parameter 'altText' needs to be non null";
+
+ /**
+ * Item icons alt texts.
+ */
+ private final HashMap<Object, String> itemIconAlts = new HashMap<Object, String>();
+
/**
* Set of expanded nodes.
*/
@@ -163,6 +170,70 @@ public class Tree extends AbstractSelect implements Container.Hierarchical,
super(caption, dataSource);
}
+ @Override
+ public void setItemIcon(Object itemId, Resource icon) {
+ setItemIcon(itemId, icon, "");
+ }
+
+ /**
+ * Sets the icon for an item.
+ *
+ * @param itemId
+ * the id of the item to be assigned an icon.
+ * @param icon
+ * the icon to use or null.
+ *
+ * @param altText
+ * the alternative text for the icon
+ */
+ public void setItemIcon(Object itemId, Resource icon, String altText) {
+ if (itemId != null) {
+ super.setItemIcon(itemId, icon);
+
+ if (icon == null) {
+ itemIconAlts.remove(itemId);
+ } else if (altText == null) {
+ throw new IllegalArgumentException(NULL_ALT_EXCEPTION_MESSAGE);
+ } else {
+ itemIconAlts.put(itemId, altText);
+ }
+ markAsDirty();
+ }
+ }
+
+ /**
+ * Set the alternate text for an item.
+ *
+ * Used when the item has an icon.
+ *
+ * @param itemId
+ * the id of the item to be assigned an icon.
+ * @param altText
+ * the alternative text for the icon
+ */
+ public void setItemIconAlternateText(Object itemId, String altText) {
+ if (itemId != null) {
+ if (altText == null) {
+ throw new IllegalArgumentException(NULL_ALT_EXCEPTION_MESSAGE);
+ } else {
+ itemIconAlts.put(itemId, altText);
+ }
+ }
+ }
+
+ /**
+ * Return the alternate text of an icon in a tree item.
+ *
+ * @param itemId
+ * Object with the ID of the item
+ * @return String with the alternate text of the icon, or null when no icon
+ * was set
+ */
+ public String getItemIconAlternateText(Object itemId) {
+ String storedAlt = itemIconAlts.get(itemId);
+ return storedAlt == null ? "" : storedAlt;
+ }
+
/* Expanding and collapsing */
/**
@@ -638,6 +709,8 @@ public class Tree extends AbstractSelect implements Container.Hierarchical,
if (icon != null) {
target.addAttribute(TreeConstants.ATTRIBUTE_NODE_ICON,
getItemIcon(itemId));
+ target.addAttribute(TreeConstants.ATTRIBUTE_NODE_ICON_ALT,
+ getItemIconAlternateText(itemId));
}
final String key = itemIdMapper.key(itemId);
target.addAttribute("key", key);
@@ -861,6 +934,37 @@ public class Tree extends AbstractSelect implements Container.Hierarchical,
}
+ @Override
+ public void containerItemSetChange(
+ com.vaadin.data.Container.ItemSetChangeEvent event) {
+ super.containerItemSetChange(event);
+ if (getContainerDataSource() instanceof Filterable) {
+ boolean hasFilters = !((Filterable) getContainerDataSource())
+ .getContainerFilters().isEmpty();
+ if (!hasFilters) {
+ /*
+ * If Container is not filtered then the itemsetchange is caused
+ * by either adding or removing items to the container. To
+ * prevent a memory leak we should cleanup the expanded list
+ * from items which was removed.
+ *
+ * However, there will still be a leak if the container is
+ * filtered to show only a subset of the items in the tree and
+ * later unfiltered items are removed from the container. In
+ * that case references to the unfiltered item ids will remain
+ * in the expanded list until the Tree instance is removed and
+ * the list is destroyed, or the container data source is
+ * replaced/updated. To force the removal of the removed items
+ * the application developer needs to a) remove the container
+ * filters temporarly or b) re-apply the container datasource
+ * using setContainerDataSource(getContainerDataSource())
+ */
+ cleanupExpandedItems();
+ }
+ }
+
+ }
+
/* Expand event and listener */
/**
diff --git a/server/src/com/vaadin/ui/UI.java b/server/src/com/vaadin/ui/UI.java
index 796d1f08ea..e077b003b8 100644
--- a/server/src/com/vaadin/ui/UI.java
+++ b/server/src/com/vaadin/ui/UI.java
@@ -37,9 +37,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.communication.PushConnection;
import com.vaadin.shared.EventId;
import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.shared.communication.PushMode;
import com.vaadin.shared.ui.ui.ScrollClientRpc;
+import com.vaadin.shared.ui.ui.UIClientRpc;
import com.vaadin.shared.ui.ui.UIConstants;
import com.vaadin.shared.ui.ui.UIServerRpc;
import com.vaadin.shared.ui.ui.UIState;
@@ -114,7 +117,10 @@ public abstract class UI extends AbstractSingleComponentContainer implements
/** Identifies the click event */
private ConnectorTracker connectorTracker = new ConnectorTracker(this);
- private Page page = new Page(this);
+ private Page page = new Page(this, getState(false).pageState);
+
+ private LoadingIndicatorConfiguration loadingIndicatorConfiguration = new LoadingIndicatorConfigurationImpl(
+ this);
/**
* Scroll Y position.
@@ -144,6 +150,14 @@ public abstract class UI extends AbstractSingleComponentContainer implements
UI.this.scrollTop = scrollTop;
UI.this.scrollLeft = scrollLeft;
}
+
+ @Override
+ public void poll() {
+ /*
+ * No-op. This is only called to cause a server visit to check for
+ * changes.
+ */
+ }
};
/**
@@ -155,6 +169,9 @@ public abstract class UI extends AbstractSingleComponentContainer implements
private boolean closing = false;
+ private TooltipConfiguration tooltipConfiguration = new TooltipConfigurationImpl(
+ 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.
@@ -347,6 +364,12 @@ public abstract class UI extends AbstractSingleComponentContainer implements
} else {
if (session == null) {
detach();
+ if (pushConnection != null && pushConnection.isConnected()) {
+ // Close the push connection when UI is detached. Otherwise
+ // the push connection and possibly VaadinSession will live
+ // on.
+ pushConnection.disconnect();
+ }
}
this.session = session;
}
@@ -466,6 +489,10 @@ public abstract class UI extends AbstractSingleComponentContainer implements
private Navigator navigator;
+ private PushConnection pushConnection = null;
+
+ private boolean hasPendingPush = false;
+
/**
* This method is used by Component.Focusable objects to request focus to
* themselves. Focus renders must be handled at window level (instead of
@@ -659,10 +686,16 @@ public abstract class UI extends AbstractSingleComponentContainer implements
/**
* Should resize operations be lazy, i.e. should there be a delay before
- * layout sizes are recalculated. Speeds up resize operations in slow UIs
- * with the penalty of slightly decreased usability.
+ * layout sizes are recalculated and resize events are sent to the server.
+ * Speeds up resize operations in slow UIs with the penalty of slightly
+ * decreased usability.
* <p>
* Default value: <code>false</code>
+ * </p>
+ * <p>
+ * When there are active window resize listeners, lazy resize mode should be
+ * used to avoid a large number of events during resize.
+ * </p>
*
* @param resizeLazy
* true to use a delay before recalculating sizes, false to
@@ -986,6 +1019,15 @@ public abstract class UI extends AbstractSingleComponentContainer implements
*/
public void close() {
closing = true;
+
+ boolean sessionExpired = (session == null || session.isClosing());
+ getRpcProxy(UIClientRpc.class).uiClosed(sessionExpired);
+ if (getPushConnection() != null && getPushConnection().isConnected()) {
+ // Push the Rpc to the client. The connection will be closed when
+ // the UI is detached and cleaned up.
+ getPushConnection().push();
+ }
+
}
/**
@@ -1054,4 +1096,265 @@ public abstract class UI extends AbstractSingleComponentContainer implements
public int getTabIndex() {
return getState(false).tabIndex;
}
+
+ /**
+ * Provides exclusive access to this UI from outside a request handling
+ * thread.
+ * <p>
+ * The given runnable is executed while holding the session lock to ensure
+ * exclusive access to this UI and its session. The UI and related thread
+ * locals are set properly before executing the runnable.
+ * </p>
+ * <p>
+ * RPC handlers for components inside this UI do not need this method as the
+ * session is automatically locked by the framework during request handling.
+ * </p>
+ * <p>
+ * Note that calling this method while another session is locked by the
+ * current thread will cause an exception. This is to prevent deadlock
+ * situations when two threads have locked one session each and are both
+ * waiting for the lock for the other session.
+ * </p>
+ *
+ * @param runnable
+ * the runnable which accesses the UI
+ * @throws UIDetachedException
+ * if the UI is not attached to a session (and locking can
+ * therefore not be done)
+ * @throws IllegalStateException
+ * if the current thread holds the lock for another session
+ *
+ * @see #getCurrent()
+ * @see VaadinSession#access(Runnable)
+ * @see VaadinSession#lock()
+ */
+ public void access(Runnable runnable) throws UIDetachedException {
+ Map<Class<?>, CurrentInstance> old = null;
+
+ VaadinSession session = getSession();
+
+ if (session == null) {
+ throw new UIDetachedException();
+ }
+
+ VaadinService.verifyNoOtherSessionLocked(session);
+
+ session.lock();
+ try {
+ if (getSession() == null) {
+ // UI was detached after fetching the session but before we
+ // acquired the lock.
+ throw new UIDetachedException();
+ }
+ old = CurrentInstance.setThreadLocals(this);
+ runnable.run();
+ } finally {
+ session.unlock();
+ if (old != null) {
+ CurrentInstance.restoreThreadLocals(old);
+ }
+ }
+
+ }
+
+ /**
+ * @deprecated As of 7.1.0.beta1, use {@link #access(Runnable)} instead.
+ * This method will be removed before the final 7.1.0 release.
+ */
+ @Deprecated
+ public void runSafely(Runnable runnable) throws UIDetachedException {
+ access(runnable);
+ }
+
+ /**
+ * Retrieves the object used for configuring tooltips.
+ *
+ * @return The instance used for tooltip configuration
+ */
+ public TooltipConfiguration getTooltipConfiguration() {
+ return tooltipConfiguration;
+ }
+
+ /**
+ * Retrieves the object used for configuring the loading indicator.
+ *
+ * @return The instance used for configuring the loading indicator
+ */
+ public LoadingIndicatorConfiguration getLoadingIndicatorConfiguration() {
+ return loadingIndicatorConfiguration;
+ }
+
+ /**
+ * Pushes the pending changes and client RPC invocations of this UI to the
+ * client-side.
+ * <p>
+ * As with all UI methods, it is not safe to call push() without holding the
+ * {@link VaadinSession#lock() session lock}.
+ *
+ * @throws IllegalStateException
+ * if push is disabled.
+ * @throws UIDetachedException
+ * if this UI is not attached to a session.
+ *
+ * @see #getPushMode()
+ *
+ * @since 7.1
+ */
+ public void push() {
+ VaadinSession session = getSession();
+ if (session != null) {
+ assert session.hasLock();
+ if (!getConnectorTracker().hasDirtyConnectors()) {
+ // Do not push if there is nothing to push
+ return;
+ }
+
+ if (!getPushMode().isEnabled()) {
+ throw new IllegalStateException("Push not enabled");
+ }
+
+ if (pushConnection == null) {
+ hasPendingPush = true;
+ } else {
+ pushConnection.push();
+ }
+ } else {
+ throw new UIDetachedException("Trying to push a detached UI");
+ }
+ }
+
+ /**
+ * Returns the internal push connection object used by this UI. This method
+ * should only be called by the framework.
+ */
+ public PushConnection getPushConnection() {
+ return pushConnection;
+ }
+
+ /**
+ * Sets the internal push connection object used by this UI. This method
+ * should only be called by the framework.
+ */
+ public void setPushConnection(PushConnection pushConnection) {
+ // If pushMode is disabled then there should never be a pushConnection
+ assert (getPushMode().isEnabled() || pushConnection == null);
+
+ if (pushConnection == this.pushConnection) {
+ return;
+ }
+
+ if (this.pushConnection != null) {
+ this.pushConnection.disconnect();
+ }
+
+ this.pushConnection = pushConnection;
+ if (pushConnection != null && hasPendingPush) {
+ hasPendingPush = false;
+ pushConnection.push();
+ }
+ }
+
+ /**
+ * Sets the interval with which the UI should poll the server to see if
+ * there are any changes. Polling is disabled by default.
+ * <p>
+ * Note that it is possible to enable push and polling at the same time but
+ * it should not be done to avoid excessive server traffic.
+ * </p>
+ * <p>
+ * Add-on developers should note that this method is only meant for the
+ * application developer. An add-on should not set the poll interval
+ * directly, rather instruct the user to set it.
+ * </p>
+ *
+ * @param intervalInMillis
+ * The interval (in ms) with which the UI should poll the server
+ * or -1 to disable polling
+ */
+ public void setPollInterval(int intervalInMillis) {
+ getState().pollInterval = intervalInMillis;
+ }
+
+ /**
+ * Returns the interval with which the UI polls the server.
+ *
+ * @return The interval (in ms) with which the UI polls the server or -1 if
+ * polling is disabled
+ */
+ public int getPollInterval() {
+ return getState(false).pollInterval;
+ }
+
+ /**
+ * Returns the mode of bidirectional ("push") communication that is used in
+ * this UI.
+ *
+ * @return The push mode.
+ */
+ public PushMode getPushMode() {
+ return getState(false).pushMode;
+ }
+
+ /**
+ * Sets the mode of bidirectional ("push") communication that should be used
+ * in this UI.
+ * <p>
+ * Add-on developers should note that this method is only meant for the
+ * application developer. An add-on should not set the push mode directly,
+ * rather instruct the user to set it.
+ * </p>
+ *
+ * @param pushMode
+ * The push mode to use.
+ *
+ * @throws IllegalArgumentException
+ * if the argument is null.
+ * @throws IllegalStateException
+ * if push support is not available.
+ */
+ public void setPushMode(PushMode pushMode) {
+ if (pushMode == null) {
+ throw new IllegalArgumentException("Push mode cannot be null");
+ }
+
+ if (pushMode.isEnabled()) {
+ VaadinSession session = getSession();
+ if (session != null && !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().pushMode = pushMode;
+ }
+
+ /**
+ * Get the label that is added to the container element, where tooltip,
+ * notification and dialogs are added to.
+ *
+ * @return the label of the container
+ */
+ public String getOverlayContainerLabel() {
+ return getState().overlayContainerLabel;
+ }
+
+ /**
+ * Sets the label that is added to the container element, where tooltip,
+ * notifications and dialogs are added to.
+ * <p>
+ * This is helpful for users of assistive devices, as this element is
+ * reachable for them.
+ * </p>
+ *
+ * @param overlayContainerLabel
+ * label to use for the container
+ */
+ public void setOverlayContainerLabel(String overlayContainerLabel) {
+ getState().overlayContainerLabel = overlayContainerLabel;
+ }
}
diff --git a/server/src/com/vaadin/ui/UIDetachedException.java b/server/src/com/vaadin/ui/UIDetachedException.java
new file mode 100644
index 0000000000..07207b0bf3
--- /dev/null
+++ b/server/src/com/vaadin/ui/UIDetachedException.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui;
+
+/**
+ * Exception thrown if the UI has been detached when it should not be.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class UIDetachedException extends RuntimeException {
+
+ public UIDetachedException() {
+ super();
+ }
+
+ public UIDetachedException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public UIDetachedException(String message) {
+ super(message);
+ }
+
+ public UIDetachedException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/Window.java b/server/src/com/vaadin/ui/Window.java
index d8b33e6b25..9f64c9118e 100644
--- a/server/src/com/vaadin/ui/Window.java
+++ b/server/src/com/vaadin/ui/Window.java
@@ -35,8 +35,10 @@ import com.vaadin.server.ClientConnector;
import com.vaadin.server.PaintException;
import com.vaadin.server.PaintTarget;
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.util.ReflectTools;
/**
* A component that represents a floating popup window that can be added to a
@@ -71,6 +73,11 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier,
public void click(MouseEventDetails mouseDetails) {
fireEvent(new ClickEvent(Window.this, mouseDetails));
}
+
+ @Override
+ public void windowModeChanged(WindowMode newState) {
+ setWindowMode(newState);
+ }
};
/**
@@ -234,10 +241,10 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier,
/**
* Gets the distance of Window left border in pixels from left border of the
- * containing (main window).
+ * containing (main window) when the window is in {@link WindowMode#NORMAL}.
*
* @return the Distance of Window left border in pixels from left border of
- * the containing (main window). or -1 if unspecified.
+ * the containing (main window).or -1 if unspecified
* @since 4.0.0
*/
public int getPositionX() {
@@ -246,7 +253,8 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier,
/**
* Sets the distance of Window left border in pixels from left border of the
- * containing (main window).
+ * containing (main window). Has effect only if in {@link WindowMode#NORMAL}
+ * mode.
*
* @param positionX
* the Distance of Window left border in pixels from left border
@@ -260,10 +268,11 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier,
/**
* Gets the distance of Window top border in pixels from top border of the
- * containing (main window).
+ * containing (main window) when the window is in {@link WindowMode#NORMAL}
+ * state, or when next set to that state.
*
* @return Distance of Window top border in pixels from top border of the
- * containing (main window). or -1 if unspecified .
+ * containing (main window). or -1 if unspecified
*
* @since 4.0.0
*/
@@ -273,7 +282,8 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier,
/**
* Sets the distance of Window top border in pixels from top border of the
- * containing (main window).
+ * containing (main window). Has effect only if in {@link WindowMode#NORMAL}
+ * mode.
*
* @param positionY
* the Distance of Window top border in pixels from top border of
@@ -402,6 +412,101 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier,
}
/**
+ * Event which is fired when the mode of the Window changes.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ *
+ */
+ public static class WindowModeChangeEvent extends Component.Event {
+
+ private final WindowMode windowMode;
+
+ /**
+ *
+ * @param source
+ */
+ public WindowModeChangeEvent(Component source, WindowMode windowMode) {
+ super(source);
+ this.windowMode = windowMode;
+ }
+
+ /**
+ * Gets the Window.
+ *
+ * @return the window
+ */
+ public Window getWindow() {
+ return (Window) getSource();
+ }
+
+ /**
+ * Gets the new window mode.
+ *
+ * @return the new mode
+ */
+ public WindowMode getWindowMode() {
+ return windowMode;
+ }
+ }
+
+ /**
+ * An interface used for listening to Window maximize / restore events. Add
+ * the WindowModeChangeListener to a window and
+ * {@link WindowModeChangeListener#windowModeChanged(WindowModeChangeEvent)}
+ * will be called whenever the window is maximized (
+ * {@link WindowMode#MAXIMIZED}) or restored ({@link WindowMode#NORMAL} ).
+ */
+ public interface WindowModeChangeListener extends Serializable {
+
+ public static final Method windowModeChangeMethod = ReflectTools
+ .findMethod(WindowModeChangeListener.class,
+ "windowModeChanged", WindowModeChangeEvent.class);
+
+ /**
+ * Called when the user maximizes / restores a window. Use
+ * {@link WindowModeChangeEvent#getWindow()} to get a reference to the
+ * {@link Window} that was maximized / restored. Use
+ * {@link WindowModeChangeEvent#getWindowMode()} to get a reference to
+ * the new state.
+ *
+ * @param event
+ */
+ public void windowModeChanged(WindowModeChangeEvent event);
+ }
+
+ /**
+ * Adds a WindowModeChangeListener to the window.
+ *
+ * The WindowModeChangeEvent is fired when the user changed the display
+ * state by clicking the maximize/restore button or by double clicking on
+ * the window header. The event is also fired if the state is changed using
+ * {@link #setWindowMode(WindowMode)}.
+ *
+ * @param listener
+ * the WindowModeChangeListener to add.
+ */
+ public void addWindowModeChangeListener(WindowModeChangeListener listener) {
+ addListener(WindowModeChangeEvent.class, listener,
+ WindowModeChangeListener.windowModeChangeMethod);
+ }
+
+ /**
+ * Removes the WindowModeChangeListener from the window.
+ *
+ * @param listener
+ * the WindowModeChangeListener to remove.
+ */
+ public void removeWindowModeChangeListener(WindowModeChangeListener listener) {
+ removeListener(WindowModeChangeEvent.class, listener,
+ WindowModeChangeListener.windowModeChangeMethod);
+ }
+
+ protected void fireWindowWindowModeChange() {
+ fireEvent(new Window.WindowModeChangeEvent(this, getState().windowMode));
+ }
+
+ /**
* Method for the resize event.
*/
private static final Method WINDOW_RESIZE_METHOD;
@@ -670,6 +775,30 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier,
getState().draggable = draggable;
}
+ /**
+ * Gets the current mode of the window.
+ *
+ * @see WindowMode
+ * @return the mode of the window.
+ */
+ public WindowMode getWindowMode() {
+ return getState(false).windowMode;
+ }
+
+ /**
+ * Sets the mode for the window
+ *
+ * @see WindowMode
+ * @param windowMode
+ * The new mode
+ */
+ public void setWindowMode(WindowMode windowMode) {
+ if (windowMode != getWindowMode()) {
+ getState().windowMode = windowMode;
+ fireWindowWindowModeChange();
+ }
+ }
+
/*
* Actions
*/
@@ -873,4 +1002,9 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier,
protected WindowState getState() {
return (WindowState) super.getState();
}
+
+ @Override
+ protected WindowState getState(boolean markAsDirty) {
+ return (WindowState) super.getState(markAsDirty);
+ }
}
diff --git a/server/src/com/vaadin/ui/components/calendar/CalendarComponentEvent.java b/server/src/com/vaadin/ui/components/calendar/CalendarComponentEvent.java
new file mode 100644
index 0000000000..1f012157b5
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/calendar/CalendarComponentEvent.java
@@ -0,0 +1,51 @@
+/*
+ * 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.components.calendar;
+
+import com.vaadin.ui.Calendar;
+import com.vaadin.ui.Component;
+
+/**
+ * All Calendar events extends this class.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ *
+ */
+@SuppressWarnings("serial")
+public class CalendarComponentEvent extends Component.Event {
+
+ /**
+ * Set the source of the event
+ *
+ * @param source
+ * The source calendar
+ *
+ */
+ public CalendarComponentEvent(Calendar source) {
+ super(source);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Component.Event#getComponent()
+ */
+ @Override
+ public Calendar getComponent() {
+ return (Calendar) super.getComponent();
+ }
+}
diff --git a/server/src/com/vaadin/ui/components/calendar/CalendarComponentEvents.java b/server/src/com/vaadin/ui/components/calendar/CalendarComponentEvents.java
new file mode 100644
index 0000000000..1904d69898
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/calendar/CalendarComponentEvents.java
@@ -0,0 +1,603 @@
+/*
+ * 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.components.calendar;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.Date;
+import java.util.EventListener;
+
+import com.vaadin.shared.ui.calendar.CalendarEventId;
+import com.vaadin.ui.Calendar;
+import com.vaadin.ui.components.calendar.event.CalendarEvent;
+import com.vaadin.util.ReflectTools;
+
+/**
+ * Interface for all Vaadin Calendar events.
+ *
+ * @since 7.1.0
+ * @author Vaadin Ltd.
+ */
+public interface CalendarComponentEvents extends Serializable {
+
+ /**
+ * Notifier interface for notifying listener of calendar events
+ */
+ public interface CalendarEventNotifier extends Serializable {
+ /**
+ * Get the assigned event handler for the given eventId.
+ *
+ * @param eventId
+ * @return the assigned eventHandler, or null if no handler is assigned
+ */
+ public EventListener getHandler(String eventId);
+ }
+
+ /**
+ * Notifier interface for event drag & drops.
+ */
+ public interface EventMoveNotifier extends CalendarEventNotifier {
+
+ /**
+ * Set the EventMoveHandler.
+ *
+ * @param listener
+ * EventMoveHandler to be added
+ */
+ public void setHandler(EventMoveHandler listener);
+
+ }
+
+ /**
+ * MoveEvent is sent when existing event is dragged to a new position.
+ */
+ @SuppressWarnings("serial")
+ public class MoveEvent extends CalendarComponentEvent {
+
+ public static final String EVENT_ID = CalendarEventId.EVENTMOVE;
+
+ /** Index for the moved Schedule.Event. */
+ private CalendarEvent calendarEvent;
+
+ /** New starting date for the moved Calendar.Event. */
+ private Date newStart;
+
+ /**
+ * MoveEvent needs the target event and new start date.
+ *
+ * @param source
+ * Calendar component.
+ * @param calendarEvent
+ * Target event.
+ * @param newStart
+ * Target event's new start date.
+ */
+ public MoveEvent(Calendar source, CalendarEvent calendarEvent,
+ Date newStart) {
+ super(source);
+
+ this.calendarEvent = calendarEvent;
+ this.newStart = newStart;
+ }
+
+ /**
+ * Get target event.
+ *
+ * @return Target event.
+ */
+ public CalendarEvent getCalendarEvent() {
+ return calendarEvent;
+ }
+
+ /**
+ * Get new start date.
+ *
+ * @return New start date.
+ */
+ public Date getNewStart() {
+ return newStart;
+ }
+ }
+
+ /**
+ * Handler interface for when events are being dragged on the calendar
+ *
+ */
+ public interface EventMoveHandler extends EventListener, Serializable {
+
+ /** Trigger method for the MoveEvent. */
+ public static final Method eventMoveMethod = ReflectTools.findMethod(
+ EventMoveHandler.class, "eventMove", MoveEvent.class);
+
+ /**
+ * This method will be called when event has been moved to a new
+ * position.
+ *
+ * @param event
+ * MoveEvent containing specific information of the new
+ * position and target event.
+ */
+ public void eventMove(MoveEvent event);
+ }
+
+ /**
+ * Handler interface for day or time cell drag-marking with mouse.
+ */
+ public interface RangeSelectNotifier extends Serializable,
+ CalendarEventNotifier {
+
+ /**
+ * Set the RangeSelectHandler that listens for drag-marking.
+ *
+ * @param listener
+ * RangeSelectHandler to be added.
+ */
+ public void setHandler(RangeSelectHandler listener);
+ }
+
+ /**
+ * RangeSelectEvent is sent when day or time cells are drag-marked with
+ * mouse.
+ */
+ @SuppressWarnings("serial")
+ public class RangeSelectEvent extends CalendarComponentEvent {
+
+ public static final String EVENT_ID = CalendarEventId.RANGESELECT;
+
+ /** Calendar event's start date. */
+ private Date start;
+
+ /** Calendar event's end date. */
+ private Date end;
+
+ /**
+ * Defines the event's view mode.
+ */
+ private boolean monthlyMode;
+
+ /**
+ * RangeSelectEvent needs a start and end date.
+ *
+ * @param source
+ * Calendar component.
+ * @param start
+ * Start date.
+ * @param end
+ * End date.
+ * @param monthlyMode
+ * Calendar view mode.
+ */
+ public RangeSelectEvent(Calendar source, Date start, Date end,
+ boolean monthlyMode) {
+ super(source);
+ this.start = start;
+ this.end = end;
+ this.monthlyMode = monthlyMode;
+ }
+
+ /**
+ * Get start date.
+ *
+ * @return Start date.
+ */
+ public Date getStart() {
+ return start;
+ }
+
+ /**
+ * Get end date.
+ *
+ * @return End date.
+ */
+ public Date getEnd() {
+ return end;
+ }
+
+ /**
+ * Gets the event's view mode. Calendar can be be either in monthly or
+ * weekly mode, depending on the active date range.
+ *
+ * @deprecated User {@link Calendar#isMonthlyMode()} instead
+ *
+ * @return Returns true when monthly view is active.
+ */
+ @Deprecated
+ public boolean isMonthlyMode() {
+ return monthlyMode;
+ }
+ }
+
+ /** RangeSelectHandler handles RangeSelectEvent. */
+ public interface RangeSelectHandler extends EventListener, Serializable {
+
+ /** Trigger method for the RangeSelectEvent. */
+ public static final Method rangeSelectMethod = ReflectTools
+ .findMethod(RangeSelectHandler.class, "rangeSelect",
+ RangeSelectEvent.class);
+
+ /**
+ * This method will be called when day or time cells are drag-marked
+ * with mouse.
+ *
+ * @param event
+ * RangeSelectEvent that contains range start and end date.
+ */
+ public void rangeSelect(RangeSelectEvent event);
+ }
+
+ /** Notifier interface for navigation listening. */
+ public interface NavigationNotifier extends Serializable {
+ /**
+ * Add a forward navigation listener.
+ *
+ * @param handler
+ * ForwardHandler to be added.
+ */
+ public void setHandler(ForwardHandler handler);
+
+ /**
+ * Add a backward navigation listener.
+ *
+ * @param handler
+ * BackwardHandler to be added.
+ */
+ public void setHandler(BackwardHandler handler);
+
+ /**
+ * Add a date click listener.
+ *
+ * @param handler
+ * DateClickHandler to be added.
+ */
+ public void setHandler(DateClickHandler handler);
+
+ /**
+ * Add a event click listener.
+ *
+ * @param handler
+ * EventClickHandler to be added.
+ */
+ public void setHandler(EventClickHandler handler);
+
+ /**
+ * Add a week click listener.
+ *
+ * @param handler
+ * WeekClickHandler to be added.
+ */
+ public void setHandler(WeekClickHandler handler);
+ }
+
+ /**
+ * ForwardEvent is sent when forward navigation button is clicked.
+ */
+ @SuppressWarnings("serial")
+ public class ForwardEvent extends CalendarComponentEvent {
+
+ public static final String EVENT_ID = CalendarEventId.FORWARD;
+
+ /**
+ * ForwardEvent needs only the source component.
+ *
+ * @param source
+ * Calendar component.
+ */
+ public ForwardEvent(Calendar source) {
+ super(source);
+ }
+ }
+
+ /** ForwardHandler handles ForwardEvent. */
+ public interface ForwardHandler extends EventListener, Serializable {
+
+ /** Trigger method for the ForwardEvent. */
+ public static final Method forwardMethod = ReflectTools.findMethod(
+ ForwardHandler.class, "forward", ForwardEvent.class);
+
+ /**
+ * This method will be called when date range is moved forward.
+ *
+ * @param event
+ * ForwardEvent
+ */
+ public void forward(ForwardEvent event);
+ }
+
+ /**
+ * BackwardEvent is sent when backward navigation button is clicked.
+ */
+ @SuppressWarnings("serial")
+ public class BackwardEvent extends CalendarComponentEvent {
+
+ public static final String EVENT_ID = CalendarEventId.BACKWARD;
+
+ /**
+ * BackwardEvent needs only the source source component.
+ *
+ * @param source
+ * Calendar component.
+ */
+ public BackwardEvent(Calendar source) {
+ super(source);
+ }
+ }
+
+ /** BackwardHandler handles BackwardEvent. */
+ public interface BackwardHandler extends EventListener, Serializable {
+
+ /** Trigger method for the BackwardEvent. */
+ public static final Method backwardMethod = ReflectTools.findMethod(
+ BackwardHandler.class, "backward", BackwardEvent.class);
+
+ /**
+ * This method will be called when date range is moved backwards.
+ *
+ * @param event
+ * BackwardEvent
+ */
+ public void backward(BackwardEvent event);
+ }
+
+ /**
+ * DateClickEvent is sent when a date is clicked.
+ */
+ @SuppressWarnings("serial")
+ public class DateClickEvent extends CalendarComponentEvent {
+
+ public static final String EVENT_ID = CalendarEventId.DATECLICK;
+
+ /** Date that was clicked. */
+ private Date date;
+
+ /** DateClickEvent needs the target date that was clicked. */
+ public DateClickEvent(Calendar source, Date date) {
+ super(source);
+ this.date = date;
+ }
+
+ /**
+ * Get clicked date.
+ *
+ * @return Clicked date.
+ */
+ public Date getDate() {
+ return date;
+ }
+ }
+
+ /** DateClickHandler handles DateClickEvent. */
+ public interface DateClickHandler extends EventListener, Serializable {
+
+ /** Trigger method for the DateClickEvent. */
+ public static final Method dateClickMethod = ReflectTools.findMethod(
+ DateClickHandler.class, "dateClick", DateClickEvent.class);
+
+ /**
+ * This method will be called when a date is clicked.
+ *
+ * @param event
+ * DateClickEvent containing the target date.
+ */
+ public void dateClick(DateClickEvent event);
+ }
+
+ /**
+ * EventClick is sent when an event is clicked.
+ */
+ @SuppressWarnings("serial")
+ public class EventClick extends CalendarComponentEvent {
+
+ public static final String EVENT_ID = CalendarEventId.EVENTCLICK;
+
+ /** Clicked source event. */
+ private CalendarEvent calendarEvent;
+
+ /** Target source event is needed for the EventClick. */
+ public EventClick(Calendar source, CalendarEvent calendarEvent) {
+ super(source);
+ this.calendarEvent = calendarEvent;
+ }
+
+ /**
+ * Get the clicked event.
+ *
+ * @return Clicked event.
+ */
+ public CalendarEvent getCalendarEvent() {
+ return calendarEvent;
+ }
+ }
+
+ /** EventClickHandler handles EventClick. */
+ public interface EventClickHandler extends EventListener, Serializable {
+
+ /** Trigger method for the EventClick. */
+ public static final Method eventClickMethod = ReflectTools.findMethod(
+ EventClickHandler.class, "eventClick", EventClick.class);
+
+ /**
+ * This method will be called when an event is clicked.
+ *
+ * @param event
+ * EventClick containing the target event.
+ */
+ public void eventClick(EventClick event);
+ }
+
+ /**
+ * WeekClick is sent when week is clicked.
+ */
+ @SuppressWarnings("serial")
+ public class WeekClick extends CalendarComponentEvent {
+
+ public static final String EVENT_ID = CalendarEventId.WEEKCLICK;
+
+ /** Target week. */
+ private int week;
+
+ /** Target year. */
+ private int year;
+
+ /**
+ * WeekClick needs a target year and week.
+ *
+ * @param source
+ * Target source.
+ * @param week
+ * Target week.
+ * @param year
+ * Target year.
+ */
+ public WeekClick(Calendar source, int week, int year) {
+ super(source);
+ this.week = week;
+ this.year = year;
+ }
+
+ /**
+ * Get week as a integer. See {@link java.util.Calendar} for the allowed
+ * values.
+ *
+ * @return Week as a integer.
+ */
+ public int getWeek() {
+ return week;
+ }
+
+ /**
+ * Get year as a integer. See {@link java.util.Calendar} for the allowed
+ * values.
+ *
+ * @return Year as a integer
+ */
+ public int getYear() {
+ return year;
+ }
+ }
+
+ /** WeekClickHandler handles WeekClicks. */
+ public interface WeekClickHandler extends EventListener, Serializable {
+
+ /** Trigger method for the WeekClick. */
+ public static final Method weekClickMethod = ReflectTools.findMethod(
+ WeekClickHandler.class, "weekClick", WeekClick.class);
+
+ /**
+ * This method will be called when a week is clicked.
+ *
+ * @param event
+ * WeekClick containing the target week and year.
+ */
+ public void weekClick(WeekClick event);
+ }
+
+ /**
+ * EventResize is sent when an event is resized
+ */
+ @SuppressWarnings("serial")
+ public class EventResize extends CalendarComponentEvent {
+
+ public static final String EVENT_ID = CalendarEventId.EVENTRESIZE;
+
+ private CalendarEvent calendarEvent;
+
+ private Date startTime;
+
+ private Date endTime;
+
+ public EventResize(Calendar source, CalendarEvent calendarEvent,
+ Date startTime, Date endTime) {
+ super(source);
+ this.calendarEvent = calendarEvent;
+ this.startTime = startTime;
+ this.endTime = endTime;
+ }
+
+ /**
+ * Get target event.
+ *
+ * @return Target event.
+ */
+ public CalendarEvent getCalendarEvent() {
+ return calendarEvent;
+ }
+
+ /**
+ * @deprecated Use {@link #getNewStart()} instead
+ *
+ * @return the new start time
+ */
+ @Deprecated
+ public Date getNewStartTime() {
+ return startTime;
+ }
+
+ /**
+ * Returns the updated start date/time of the event
+ *
+ * @return The new date for the event
+ */
+ public Date getNewStart() {
+ return startTime;
+ }
+
+ /**
+ * @deprecated Use {@link #getNewEnd()} instead
+ *
+ * @return the new end time
+ */
+ @Deprecated
+ public Date getNewEndTime() {
+ return endTime;
+ }
+
+ /**
+ * Returns the updates end date/time of the event
+ *
+ * @return The new date for the event
+ */
+ public Date getNewEnd() {
+ return endTime;
+ }
+ }
+
+ /**
+ * Notifier interface for event resizing.
+ */
+ public interface EventResizeNotifier extends Serializable {
+
+ /**
+ * Set a EventResizeHandler.
+ *
+ * @param handler
+ * EventResizeHandler to be set
+ */
+ public void setHandler(EventResizeHandler handler);
+ }
+
+ /**
+ * Handler for EventResize event.
+ */
+ public interface EventResizeHandler extends EventListener, Serializable {
+
+ /** Trigger method for the EventResize. */
+ public static final Method eventResizeMethod = ReflectTools.findMethod(
+ EventResizeHandler.class, "eventResize", EventResize.class);
+
+ void eventResize(EventResize event);
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/components/calendar/CalendarDateRange.java b/server/src/com/vaadin/ui/components/calendar/CalendarDateRange.java
new file mode 100644
index 0000000000..01b766a6db
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/calendar/CalendarDateRange.java
@@ -0,0 +1,86 @@
+/*
+ * 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.components.calendar;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * Class for representing a date range.
+ *
+ * @since 7.1.0
+ * @author Vaadin Ltd.
+ *
+ */
+@SuppressWarnings("serial")
+public class CalendarDateRange implements Serializable {
+
+ private Date start;
+
+ private Date end;
+
+ private final transient TimeZone tz;
+
+ /**
+ * Constructor
+ *
+ * @param start
+ * The start date and time of the date range
+ * @param end
+ * The end date and time of the date range
+ */
+ public CalendarDateRange(Date start, Date end, TimeZone tz) {
+ super();
+ this.start = start;
+ this.end = end;
+ this.tz = tz;
+ }
+
+ /**
+ * Get the start date of the date range
+ *
+ * @return the start Date of the range
+ */
+ public Date getStart() {
+ return start;
+ }
+
+ /**
+ * Get the end date of the date range
+ *
+ * @return the end Date of the range
+ */
+ public Date getEnd() {
+ return end;
+ }
+
+ /**
+ * Is a date in the date range
+ *
+ * @param date
+ * The date to check
+ * @return true if the date range contains a date start and end of range
+ * inclusive; false otherwise
+ */
+ public boolean inRange(Date date) {
+ if (date == null) {
+ return false;
+ }
+
+ return date.compareTo(start) >= 0 && date.compareTo(end) <= 0;
+ }
+}
diff --git a/server/src/com/vaadin/ui/components/calendar/CalendarTargetDetails.java b/server/src/com/vaadin/ui/components/calendar/CalendarTargetDetails.java
new file mode 100644
index 0000000000..1a3ef67377
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/calendar/CalendarTargetDetails.java
@@ -0,0 +1,80 @@
+/*
+ * 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.components.calendar;
+
+import java.util.Date;
+import java.util.Map;
+
+import com.vaadin.event.dd.DropTarget;
+import com.vaadin.event.dd.TargetDetailsImpl;
+import com.vaadin.ui.Calendar;
+
+/**
+ * Drop details for {@link com.vaadin.ui.addon.calendar.ui.Calendar Calendar}.
+ * When something is dropped on the Calendar, this class contains the specific
+ * details of the drop point. Specifically, this class gives access to the date
+ * where the drop happened. If the Calendar was in weekly mode, the date also
+ * includes the start time of the slot.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+@SuppressWarnings("serial")
+public class CalendarTargetDetails extends TargetDetailsImpl {
+
+ private boolean hasDropTime;
+
+ public CalendarTargetDetails(Map<String, Object> rawDropData,
+ DropTarget dropTarget) {
+ super(rawDropData, dropTarget);
+ }
+
+ /**
+ * @return true if {@link #getDropTime()} will return a date object with the
+ * time set to the start of the time slot where the drop happened
+ */
+ public boolean hasDropTime() {
+ return hasDropTime;
+ }
+
+ /**
+ * Does the dropped item have a time associated with it
+ *
+ * @param hasDropTime
+ */
+ public void setHasDropTime(boolean hasDropTime) {
+ this.hasDropTime = hasDropTime;
+ }
+
+ /**
+ * @return the date where the drop happened
+ */
+ public Date getDropTime() {
+ if (hasDropTime) {
+ return (Date) getData("dropTime");
+ } else {
+ return (Date) getData("dropDay");
+ }
+ }
+
+ /**
+ * @return the {@link com.vaadin.ui.addon.calendar.ui.Calendar Calendar}
+ * instance which was the target of the drop
+ */
+ public Calendar getTargetCalendar() {
+ return (Calendar) getTarget();
+ }
+}
diff --git a/server/src/com/vaadin/ui/components/calendar/ContainerEventProvider.java b/server/src/com/vaadin/ui/components/calendar/ContainerEventProvider.java
new file mode 100644
index 0000000000..37ea255d27
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/calendar/ContainerEventProvider.java
@@ -0,0 +1,577 @@
+/*
+ * 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.components.calendar;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.Container.Indexed;
+import com.vaadin.data.Container.ItemSetChangeEvent;
+import com.vaadin.data.Container.ItemSetChangeNotifier;
+import com.vaadin.data.Item;
+import com.vaadin.data.Property;
+import com.vaadin.data.Property.ValueChangeEvent;
+import com.vaadin.data.Property.ValueChangeNotifier;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.EventMoveHandler;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.EventResize;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.EventResizeHandler;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.MoveEvent;
+import com.vaadin.ui.components.calendar.event.BasicEvent;
+import com.vaadin.ui.components.calendar.event.CalendarEditableEventProvider;
+import com.vaadin.ui.components.calendar.event.CalendarEvent;
+import com.vaadin.ui.components.calendar.event.CalendarEvent.EventChangeListener;
+import com.vaadin.ui.components.calendar.event.CalendarEvent.EventChangeNotifier;
+import com.vaadin.ui.components.calendar.event.CalendarEventProvider;
+import com.vaadin.ui.components.calendar.event.CalendarEventProvider.EventSetChangeNotifier;
+
+/**
+ * A event provider which uses a {@link Container} as a datasource. Container
+ * used as data source.
+ *
+ * NOTE: The data source must be sorted by date!
+ *
+ * @since 7.1.0
+ * @author Vaadin Ltd.
+ */
+@SuppressWarnings("serial")
+public class ContainerEventProvider implements CalendarEditableEventProvider,
+ EventSetChangeNotifier, EventChangeNotifier, EventMoveHandler,
+ EventResizeHandler, Container.ItemSetChangeListener,
+ Property.ValueChangeListener {
+
+ // Default property ids
+ public static final String CAPTION_PROPERTY = "caption";
+ public static final String DESCRIPTION_PROPERTY = "description";
+ public static final String STARTDATE_PROPERTY = "start";
+ public static final String ENDDATE_PROPERTY = "end";
+ public static final String STYLENAME_PROPERTY = "styleName";
+
+ /**
+ * Internal class to keep the container index which item this event
+ * represents
+ *
+ */
+ private class ContainerCalendarEvent extends BasicEvent {
+ private final int index;
+
+ public ContainerCalendarEvent(int containerIndex) {
+ super();
+ index = containerIndex;
+ }
+
+ public int getContainerIndex() {
+ return index;
+ }
+ }
+
+ /**
+ * Listeners attached to the container
+ */
+ private final List<EventSetChangeListener> eventSetChangeListeners = new LinkedList<CalendarEventProvider.EventSetChangeListener>();
+ private final List<EventChangeListener> eventChangeListeners = new LinkedList<CalendarEvent.EventChangeListener>();
+
+ /**
+ * The event cache contains the events previously created by
+ * {@link #getEvents(Date, Date)}
+ */
+ private final List<CalendarEvent> eventCache = new LinkedList<CalendarEvent>();
+
+ /**
+ * The container used as datasource
+ */
+ private Indexed container;
+
+ /**
+ * Container properties. Defaults based on using the {@link BasicEvent}
+ * helper class.
+ */
+ private Object captionProperty = CAPTION_PROPERTY;
+ private Object descriptionProperty = DESCRIPTION_PROPERTY;
+ private Object startDateProperty = STARTDATE_PROPERTY;
+ private Object endDateProperty = ENDDATE_PROPERTY;
+ private Object styleNameProperty = STYLENAME_PROPERTY;
+
+ /**
+ * Constructor
+ *
+ * @param container
+ * Container to use as a data source.
+ */
+ public ContainerEventProvider(Container.Indexed container) {
+ this.container = container;
+ listenToContainerEvents();
+ }
+
+ /**
+ * Set the container data source
+ *
+ * @param container
+ * The container to use as datasource
+ *
+ */
+ public void setContainerDataSource(Container.Indexed container) {
+ // Detach the previous container
+ detachContainerDataSource();
+
+ this.container = container;
+ listenToContainerEvents();
+ }
+
+ /**
+ * Returns the container used as data source
+ *
+ */
+ public Container.Indexed getContainerDataSource() {
+ return container;
+ }
+
+ /**
+ * Attaches listeners to the container so container events can be processed
+ */
+ private void listenToContainerEvents() {
+ if (container instanceof ItemSetChangeNotifier) {
+ ((ItemSetChangeNotifier) container).addItemSetChangeListener(this);
+ }
+ if (container instanceof ValueChangeNotifier) {
+ ((ValueChangeNotifier) container).addValueChangeListener(this);
+ }
+ }
+
+ /**
+ * Removes listeners from the container so no events are processed
+ */
+ private void ignoreContainerEvents() {
+ if (container instanceof ItemSetChangeNotifier) {
+ ((ItemSetChangeNotifier) container)
+ .removeItemSetChangeListener(this);
+ }
+ if (container instanceof ValueChangeNotifier) {
+ ((ValueChangeNotifier) container).removeValueChangeListener(this);
+ }
+ }
+
+ /**
+ * Converts an event in the container to an {@link CalendarEvent}
+ *
+ * @param index
+ * The index of the item in the container to get the event for
+ * @return
+ */
+ private CalendarEvent getEvent(int index) {
+
+ // Check the event cache first
+ for (CalendarEvent e : eventCache) {
+ if (e instanceof ContainerCalendarEvent
+ && ((ContainerCalendarEvent) e).getContainerIndex() == index) {
+ return e;
+ } else if (container.getIdByIndex(index) == e) {
+ return e;
+ }
+ }
+
+ final Object id = container.getIdByIndex(index);
+ Item item = container.getItem(id);
+ CalendarEvent event;
+ if (id instanceof CalendarEvent) {
+ /*
+ * If we are using the BeanItemContainer or another container which
+ * stores the objects as ids then just return the instances
+ */
+ event = (CalendarEvent) id;
+
+ } else {
+ /*
+ * Else we use the properties to create the event
+ */
+ BasicEvent basicEvent = new ContainerCalendarEvent(index);
+
+ // Set values from property values
+ if (captionProperty != null
+ && item.getItemPropertyIds().contains(captionProperty)) {
+ basicEvent.setCaption(String.valueOf(item.getItemProperty(
+ captionProperty).getValue()));
+ }
+ if (descriptionProperty != null
+ && item.getItemPropertyIds().contains(descriptionProperty)) {
+ basicEvent.setDescription(String.valueOf(item.getItemProperty(
+ descriptionProperty).getValue()));
+ }
+ if (startDateProperty != null
+ && item.getItemPropertyIds().contains(startDateProperty)) {
+ basicEvent.setStart((Date) item.getItemProperty(
+ startDateProperty).getValue());
+ }
+ if (endDateProperty != null
+ && item.getItemPropertyIds().contains(endDateProperty)) {
+ basicEvent.setEnd((Date) item.getItemProperty(endDateProperty)
+ .getValue());
+ }
+ if (styleNameProperty != null
+ && item.getItemPropertyIds().contains(styleNameProperty)) {
+ basicEvent.setDescription(String.valueOf(item.getItemProperty(
+ descriptionProperty).getValue()));
+ }
+ event = basicEvent;
+ }
+ return event;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEventProvider#getEvents(java.
+ * util.Date, java.util.Date)
+ */
+ @Override
+ public List<CalendarEvent> getEvents(Date startDate, Date endDate) {
+ eventCache.clear();
+
+ int[] rangeIndexes = getFirstAndLastEventIndex(startDate, endDate);
+ for (int i = rangeIndexes[0]; i <= rangeIndexes[1]
+ && i < container.size(); i++) {
+ eventCache.add(getEvent(i));
+ }
+ return Collections.unmodifiableList(eventCache);
+ }
+
+ /**
+ * Get the first event for a date
+ *
+ * @param date
+ * The date to search for, NUll returns first event in container
+ * @return Returns an array where the first item is the start index and the
+ * second item is the end item
+ */
+ private int[] getFirstAndLastEventIndex(Date start, Date end) {
+ int startIndex = 0;
+ int size = container.size();
+ int endIndex = size - 1;
+
+ if (start != null) {
+ /*
+ * Iterating from the start of the container, if range is in the end
+ * of the container then this will be slow TODO This could be
+ * improved by using some sort of divide and conquer algorithm
+ */
+ while (startIndex < size) {
+ Object id = container.getIdByIndex(startIndex);
+ Item item = container.getItem(id);
+ Date d = (Date) item.getItemProperty(startDateProperty)
+ .getValue();
+ if (d.compareTo(start) >= 0) {
+ break;
+ }
+ startIndex++;
+ }
+ }
+
+ if (end != null) {
+ /*
+ * Iterate from the start index until range ends
+ */
+ endIndex = startIndex;
+ while (endIndex < size - 1) {
+ Object id = container.getIdByIndex(endIndex);
+ Item item = container.getItem(id);
+ Date d = (Date) item.getItemProperty(endDateProperty)
+ .getValue();
+ if (d == null) {
+ // No end date present, use start date
+ d = (Date) item.getItemProperty(startDateProperty)
+ .getValue();
+ }
+ if (d.compareTo(end) >= 0) {
+ endIndex--;
+ break;
+ }
+ endIndex++;
+ }
+ }
+
+ return new int[] { startIndex, endIndex };
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEventProvider.EventSetChangeNotifier
+ * #addListener(com.vaadin.addon.calendar.event.CalendarEventProvider.
+ * EventSetChangeListener)
+ */
+ @Override
+ public void addEventSetChangeListener(EventSetChangeListener listener) {
+ if (!eventSetChangeListeners.contains(listener)) {
+ eventSetChangeListeners.add(listener);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEventProvider.EventSetChangeNotifier
+ * #removeListener(com.vaadin.addon.calendar.event.CalendarEventProvider.
+ * EventSetChangeListener)
+ */
+ @Override
+ public void removeEventSetChangeListener(EventSetChangeListener listener) {
+ eventSetChangeListeners.remove(listener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEvent.EventChangeNotifier#addListener
+ * (com.vaadin.addon.calendar.event.CalendarEvent.EventChangeListener)
+ */
+ @Override
+ public void addEventChangeListener(EventChangeListener listener) {
+ if (eventChangeListeners.contains(listener)) {
+ eventChangeListeners.add(listener);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.calendar.event.CalendarEvent.EventChangeNotifier#
+ * removeListener
+ * (com.vaadin.addon.calendar.event.CalendarEvent.EventChangeListener)
+ */
+ @Override
+ public void removeEventChangeListener(EventChangeListener listener) {
+ eventChangeListeners.remove(listener);
+ }
+
+ /**
+ * Get the property which provides the caption of the event
+ */
+ public Object getCaptionProperty() {
+ return captionProperty;
+ }
+
+ /**
+ * Set the property which provides the caption of the event
+ */
+ public void setCaptionProperty(Object captionProperty) {
+ this.captionProperty = captionProperty;
+ }
+
+ /**
+ * Get the property which provides the description of the event
+ */
+ public Object getDescriptionProperty() {
+ return descriptionProperty;
+ }
+
+ /**
+ * Set the property which provides the description of the event
+ */
+ public void setDescriptionProperty(Object descriptionProperty) {
+ this.descriptionProperty = descriptionProperty;
+ }
+
+ /**
+ * Get the property which provides the starting date and time of the event
+ */
+ public Object getStartDateProperty() {
+ return startDateProperty;
+ }
+
+ /**
+ * Set the property which provides the starting date and time of the event
+ */
+ public void setStartDateProperty(Object startDateProperty) {
+ this.startDateProperty = startDateProperty;
+ }
+
+ /**
+ * Get the property which provides the ending date and time of the event
+ */
+ public Object getEndDateProperty() {
+ return endDateProperty;
+ }
+
+ /**
+ * Set the property which provides the ending date and time of the event
+ */
+ public void setEndDateProperty(Object endDateProperty) {
+ this.endDateProperty = endDateProperty;
+ }
+
+ /**
+ * Get the property which provides the style name for the event
+ */
+ public Object getStyleNameProperty() {
+ return styleNameProperty;
+ }
+
+ /**
+ * Set the property which provides the style name for the event
+ */
+ public void setStyleNameProperty(Object styleNameProperty) {
+ this.styleNameProperty = styleNameProperty;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container.ItemSetChangeListener#containerItemSetChange
+ * (com.vaadin.data.Container.ItemSetChangeEvent)
+ */
+ @Override
+ public void containerItemSetChange(ItemSetChangeEvent event) {
+ if (event.getContainer() == container) {
+ // Trigger an eventset change event when the itemset changes
+ for (EventSetChangeListener listener : eventSetChangeListeners) {
+ listener.eventSetChange(new EventSetChangeEvent(this));
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Property.ValueChangeListener#valueChange(com.vaadin.data
+ * .Property.ValueChangeEvent)
+ */
+ @Override
+ public void valueChange(ValueChangeEvent event) {
+ /*
+ * TODO Need to figure out how to get the item which triggered the the
+ * valuechange event and then trigger a EventChange event to the
+ * listeners
+ */
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventMoveHandler
+ * #eventMove
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.MoveEvent)
+ */
+ @Override
+ public void eventMove(MoveEvent event) {
+ CalendarEvent ce = event.getCalendarEvent();
+ if (eventCache.contains(ce)) {
+ int index;
+ if (ce instanceof ContainerCalendarEvent) {
+ index = ((ContainerCalendarEvent) ce).getContainerIndex();
+ } else {
+ index = container.indexOfId(ce);
+ }
+
+ long eventLength = ce.getEnd().getTime() - ce.getStart().getTime();
+ Date newEnd = new Date(event.getNewStart().getTime() + eventLength);
+
+ ignoreContainerEvents();
+ Item item = container.getItem(container.getIdByIndex(index));
+ item.getItemProperty(startDateProperty).setValue(
+ event.getNewStart());
+ item.getItemProperty(endDateProperty).setValue(newEnd);
+ listenToContainerEvents();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventResizeHandler
+ * #eventResize
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventResize)
+ */
+ @Override
+ public void eventResize(EventResize event) {
+ CalendarEvent ce = event.getCalendarEvent();
+ if (eventCache.contains(ce)) {
+ int index;
+ if (ce instanceof ContainerCalendarEvent) {
+ index = ((ContainerCalendarEvent) ce).getContainerIndex();
+ } else {
+ index = container.indexOfId(ce);
+ }
+ ignoreContainerEvents();
+ Item item = container.getItem(container.getIdByIndex(index));
+ item.getItemProperty(startDateProperty).setValue(
+ event.getNewStart());
+ item.getItemProperty(endDateProperty).setValue(event.getNewEnd());
+ listenToContainerEvents();
+ }
+ }
+
+ /**
+ * If you are reusing the container which previously have been attached to
+ * this ContainerEventProvider call this method to remove this event
+ * providers container listeners before attaching it to an other
+ * ContainerEventProvider
+ */
+ public void detachContainerDataSource() {
+ ignoreContainerEvents();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEditableEventProvider#addEvent
+ * (com.vaadin.addon.calendar.event.CalendarEvent)
+ */
+ @Override
+ public void addEvent(CalendarEvent event) {
+ Item item;
+ try {
+ item = container.addItem(event);
+ } catch (UnsupportedOperationException uop) {
+ // Thrown if container does not support adding items with custom
+ // ids. JPAContainer for example.
+ item = container.getItem(container.addItem());
+ }
+ if (item != null) {
+ item.getItemProperty(getCaptionProperty()).setValue(
+ event.getCaption());
+ item.getItemProperty(getStartDateProperty()).setValue(
+ event.getStart());
+ item.getItemProperty(getEndDateProperty()).setValue(event.getEnd());
+ item.getItemProperty(getStyleNameProperty()).setValue(
+ event.getStyleName());
+ item.getItemProperty(getDescriptionProperty()).setValue(
+ event.getDescription());
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEditableEventProvider#removeEvent
+ * (com.vaadin.addon.calendar.event.CalendarEvent)
+ */
+ @Override
+ public void removeEvent(CalendarEvent event) {
+ container.removeItem(event);
+ }
+}
diff --git a/server/src/com/vaadin/ui/components/calendar/event/BasicEvent.java b/server/src/com/vaadin/ui/components/calendar/event/BasicEvent.java
new file mode 100644
index 0000000000..3f14145f0c
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/calendar/event/BasicEvent.java
@@ -0,0 +1,265 @@
+/*
+ * 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.components.calendar.event;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import com.vaadin.ui.components.calendar.event.CalendarEvent.EventChangeNotifier;
+
+/**
+ * Simple implementation of
+ * {@link com.vaadin.addon.calendar.event.CalendarEvent CalendarEvent}. Has
+ * setters for all required fields and fires events when this event is changed.
+ *
+ * @since 7.1.0
+ * @author Vaadin Ltd.
+ */
+@SuppressWarnings("serial")
+public class BasicEvent implements EditableCalendarEvent, EventChangeNotifier {
+
+ private String caption;
+ private String description;
+ private Date end;
+ private Date start;
+ private String styleName;
+ private transient List<EventChangeListener> listeners = new ArrayList<EventChangeListener>();
+
+ private boolean isAllDay;
+
+ /**
+ * Default constructor
+ */
+ public BasicEvent() {
+
+ }
+
+ /**
+ * Constructor for creating an event with the same start and end date
+ *
+ * @param caption
+ * The caption for the event
+ * @param description
+ * The description for the event
+ * @param date
+ * The date the event occurred
+ */
+ public BasicEvent(String caption, String description, Date date) {
+ this.caption = caption;
+ this.description = description;
+ start = date;
+ end = date;
+ }
+
+ /**
+ * Constructor for creating an event with a start date and an end date.
+ * Start date should be before the end date
+ *
+ * @param caption
+ * The caption for the event
+ * @param description
+ * The description for the event
+ * @param startDate
+ * The start date of the event
+ * @param endDate
+ * The end date of the event
+ */
+ public BasicEvent(String caption, String description, Date startDate,
+ Date endDate) {
+ this.caption = caption;
+ this.description = description;
+ start = startDate;
+ end = endDate;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.calendar.event.CalendarEvent#getCaption()
+ */
+ @Override
+ public String getCaption() {
+ return caption;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.calendar.event.CalendarEvent#getDescription()
+ */
+ @Override
+ public String getDescription() {
+ return description;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.calendar.event.CalendarEvent#getEnd()
+ */
+ @Override
+ public Date getEnd() {
+ return end;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.calendar.event.CalendarEvent#getStart()
+ */
+ @Override
+ public Date getStart() {
+ return start;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.calendar.event.CalendarEvent#getStyleName()
+ */
+ @Override
+ public String getStyleName() {
+ return styleName;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.calendar.event.CalendarEvent#isAllDay()
+ */
+ @Override
+ public boolean isAllDay() {
+ return isAllDay;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEventEditor#setCaption(java.lang
+ * .String)
+ */
+ @Override
+ public void setCaption(String caption) {
+ this.caption = caption;
+ fireEventChange();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEventEditor#setDescription(java
+ * .lang.String)
+ */
+ @Override
+ public void setDescription(String description) {
+ this.description = description;
+ fireEventChange();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEventEditor#setEnd(java.util.
+ * Date)
+ */
+ @Override
+ public void setEnd(Date end) {
+ this.end = end;
+ fireEventChange();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEventEditor#setStart(java.util
+ * .Date)
+ */
+ @Override
+ public void setStart(Date start) {
+ this.start = start;
+ fireEventChange();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEventEditor#setStyleName(java
+ * .lang.String)
+ */
+ @Override
+ public void setStyleName(String styleName) {
+ this.styleName = styleName;
+ fireEventChange();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEventEditor#setAllDay(boolean)
+ */
+ @Override
+ public void setAllDay(boolean isAllDay) {
+ this.isAllDay = isAllDay;
+ fireEventChange();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventChangeNotifier
+ * #addListener
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventChangeListener
+ * )
+ */
+ @Override
+ public void addEventChangeListener(EventChangeListener listener) {
+ listeners.add(listener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventChangeNotifier
+ * #removeListener
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventChangeListener
+ * )
+ */
+ @Override
+ public void removeEventChangeListener(EventChangeListener listener) {
+ listeners.remove(listener);
+ }
+
+ /**
+ * Fires an event change event to the listeners. Should be triggered when
+ * some property of the event changes.
+ */
+ protected void fireEventChange() {
+ EventChangeEvent event = new EventChangeEvent(this);
+
+ for (EventChangeListener listener : listeners) {
+ listener.eventChange(event);
+ }
+ }
+}
diff --git a/server/src/com/vaadin/ui/components/calendar/event/BasicEventProvider.java b/server/src/com/vaadin/ui/components/calendar/event/BasicEventProvider.java
new file mode 100644
index 0000000000..b2b74a5e52
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/calendar/event/BasicEventProvider.java
@@ -0,0 +1,179 @@
+/*
+ * 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.components.calendar.event;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import com.vaadin.ui.components.calendar.event.CalendarEvent.EventChangeEvent;
+import com.vaadin.ui.components.calendar.event.CalendarEventProvider.EventSetChangeNotifier;
+
+/**
+ * <p>
+ * Simple implementation of
+ * {@link com.vaadin.addon.calendar.event.CalendarEventProvider
+ * CalendarEventProvider}. Use {@link #addEvent(CalendarEvent)} and
+ * {@link #removeEvent(CalendarEvent)} to add / remove events.
+ * </p>
+ *
+ * <p>
+ * {@link com.vaadin.addon.calendar.event.CalendarEventProvider.EventSetChangeNotifier
+ * EventSetChangeNotifier} and
+ * {@link com.vaadin.addon.calendar.event.CalendarEvent.EventChangeListener
+ * EventChangeListener} are also implemented, so the Calendar is notified when
+ * an event is added, changed or removed.
+ * </p>
+ *
+ * @since 7.1.0
+ * @author Vaadin Ltd.
+ */
+@SuppressWarnings("serial")
+public class BasicEventProvider implements CalendarEditableEventProvider,
+ EventSetChangeNotifier, CalendarEvent.EventChangeListener {
+
+ protected List<CalendarEvent> eventList = new ArrayList<CalendarEvent>();
+
+ private List<EventSetChangeListener> listeners = new ArrayList<EventSetChangeListener>();
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEventProvider#getEvents(java.
+ * util.Date, java.util.Date)
+ */
+ @Override
+ public List<CalendarEvent> getEvents(Date startDate, Date endDate) {
+ ArrayList<CalendarEvent> activeEvents = new ArrayList<CalendarEvent>();
+
+ for (CalendarEvent ev : eventList) {
+ long from = startDate.getTime();
+ long to = endDate.getTime();
+
+ if (ev.getStart() != null && ev.getEnd() != null) {
+ long f = ev.getStart().getTime();
+ long t = ev.getEnd().getTime();
+ // Select only events that overlaps with startDate and
+ // endDate.
+ if ((f <= to && f >= from) || (t >= from && t <= to)
+ || (f <= from && t >= to)) {
+ activeEvents.add(ev);
+ }
+ }
+ }
+
+ return activeEvents;
+ }
+
+ /**
+ * Does this event provider container this event
+ *
+ * @param event
+ * The event to check for
+ * @return If this provider has the event then true is returned, else false
+ */
+ public boolean containsEvent(BasicEvent event) {
+ return eventList.contains(event);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventSetChangeNotifier
+ * #addListener
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventSetChangeListener
+ * )
+ */
+ @Override
+ public void addEventSetChangeListener(EventSetChangeListener listener) {
+ listeners.add(listener);
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventSetChangeNotifier
+ * #removeListener
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventSetChangeListener
+ * )
+ */
+ @Override
+ public void removeEventSetChangeListener(EventSetChangeListener listener) {
+ listeners.remove(listener);
+ }
+
+ /**
+ * Fires a eventsetchange event. The event is fired when either an event is
+ * added or removed to the event provider
+ */
+ protected void fireEventSetChange() {
+ EventSetChangeEvent event = new EventSetChangeEvent(this);
+
+ for (EventSetChangeListener listener : listeners) {
+ listener.eventSetChange(event);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventChangeListener
+ * #eventChange
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventSetChange)
+ */
+ @Override
+ public void eventChange(EventChangeEvent changeEvent) {
+ // naive implementation
+ fireEventSetChange();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEditableEventProvider#addEvent
+ * (com.vaadin.addon.calendar.event.CalendarEvent)
+ */
+ @Override
+ public void addEvent(CalendarEvent event) {
+ eventList.add(event);
+ if (event instanceof BasicEvent) {
+ ((BasicEvent) event).addEventChangeListener(this);
+ }
+ fireEventSetChange();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEditableEventProvider#removeEvent
+ * (com.vaadin.addon.calendar.event.CalendarEvent)
+ */
+ @Override
+ public void removeEvent(CalendarEvent event) {
+ eventList.remove(event);
+ if (event instanceof BasicEvent) {
+ ((BasicEvent) event).removeEventChangeListener(this);
+ }
+ fireEventSetChange();
+ }
+}
diff --git a/server/src/com/vaadin/ui/components/calendar/event/CalendarEditableEventProvider.java b/server/src/com/vaadin/ui/components/calendar/event/CalendarEditableEventProvider.java
new file mode 100644
index 0000000000..13e84df666
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/calendar/event/CalendarEditableEventProvider.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.ui.components.calendar.event;
+
+/**
+ * An event provider which allows adding and removing events
+ *
+ * @since 7.1.0
+ * @author Vaadin Ltd.
+ */
+public interface CalendarEditableEventProvider extends CalendarEventProvider {
+
+ /**
+ * Adds an event to the event provider
+ *
+ * @param event
+ * The event to add
+ */
+ void addEvent(CalendarEvent event);
+
+ /**
+ * Removes an event from the event provider
+ *
+ * @param event
+ * The event
+ */
+ void removeEvent(CalendarEvent event);
+}
diff --git a/server/src/com/vaadin/ui/components/calendar/event/CalendarEvent.java b/server/src/com/vaadin/ui/components/calendar/event/CalendarEvent.java
new file mode 100644
index 0000000000..531ee72c7f
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/calendar/event/CalendarEvent.java
@@ -0,0 +1,146 @@
+/*
+ * 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.components.calendar.event;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * <p>
+ * Event in the calendar. Customize your own event by implementing this
+ * interface.
+ * </p>
+ *
+ * <li>Start and end fields are mandatory.</li>
+ *
+ * <li>In "allDay" events longer than one day, starting and ending clock times
+ * are omitted in UI and only dates are shown.</li>
+ *
+ * @since 7.1.0
+ * @author Vaadin Ltd.
+ *
+ */
+public interface CalendarEvent extends Serializable {
+
+ /**
+ * Gets start date of event.
+ *
+ * @return Start date.
+ */
+ public Date getStart();
+
+ /**
+ * Get end date of event.
+ *
+ * @return End date;
+ */
+ public Date getEnd();
+
+ /**
+ * Gets caption of event.
+ *
+ * @return Caption text
+ */
+ public String getCaption();
+
+ /**
+ * Gets description of event. Shown as a tooltip over the event.
+ *
+ * @return Description text.
+ */
+ public String getDescription();
+
+ /**
+ * <p>
+ * Gets style name of event. In the client, style name will be set to the
+ * event's element class name and can be styled by CSS
+ * </p>
+ * Styling example:</br> <code>Java code: </br>
+ * event.setStyleName("color1");
+ * </br></br>
+ * CSS:</br>
+ * .v-calendar-event-color1 {</br>
+ * &nbsp;&nbsp;&nbsp;background-color: #9effae;</br>}</code>
+ *
+ * @return Style name.
+ */
+ public String getStyleName();
+
+ /**
+ * An all-day event typically does not occur at a specific time but targets
+ * a whole day or days. The rendering of all-day events differs from normal
+ * events.
+ *
+ * @return true if this event is an all-day event, false otherwise
+ */
+ public boolean isAllDay();
+
+ /**
+ * Event to signal that an event has changed.
+ */
+ @SuppressWarnings("serial")
+ public class EventChangeEvent implements Serializable {
+
+ private CalendarEvent source;
+
+ public EventChangeEvent(CalendarEvent source) {
+ this.source = source;
+ }
+
+ /**
+ * @return the {@link com.vaadin.addon.calendar.event.CalendarEvent
+ * CalendarEvent} that has changed
+ */
+ public CalendarEvent getCalendarEvent() {
+ return source;
+ }
+ }
+
+ /**
+ * Listener for EventSetChange events.
+ */
+ public interface EventChangeListener extends Serializable {
+
+ /**
+ * Called when an Event has changed.
+ */
+ public void eventChange(EventChangeEvent eventChangeEvent);
+ }
+
+ /**
+ * Notifier interface for EventChange events.
+ */
+ public interface EventChangeNotifier extends Serializable {
+
+ /**
+ * Add a listener to listen for EventChangeEvents. These events are
+ * fired when a events properties are changed.
+ *
+ * @param listener
+ * The listener to add
+ */
+ void addEventChangeListener(EventChangeListener listener);
+
+ /**
+ * Remove a listener from the event provider.
+ *
+ * @param listener
+ * The listener to remove
+ */
+ void removeEventChangeListener(EventChangeListener listener);
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/components/calendar/event/CalendarEventProvider.java b/server/src/com/vaadin/ui/components/calendar/event/CalendarEventProvider.java
new file mode 100644
index 0000000000..fefb2ca9b6
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/calendar/event/CalendarEventProvider.java
@@ -0,0 +1,112 @@
+/*
+ * 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.components.calendar.event;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Interface for querying events. The Vaadin Calendar always has a
+ * CalendarEventProvider set.
+ *
+ * @since 7.1.0
+ * @author Vaadin Ltd.
+ */
+public interface CalendarEventProvider extends Serializable {
+ /**
+ * <p>
+ * Gets all available events in the target date range between startDate and
+ * endDate. The Vaadin Calendar queries the events from the range that is
+ * shown, which is not guaranteed to be the same as the date range that is
+ * set.
+ * </p>
+ *
+ * <p>
+ * For example, if you set the date range to be monday 22.2.2010 - wednesday
+ * 24.2.2000, the used Event Provider will be queried for events between
+ * monday 22.2.2010 00:00 and sunday 28.2.2010 23:59. Generally you can
+ * expect the date range to be expanded to whole days and whole weeks.
+ * </p>
+ *
+ * @param startDate
+ * Start date
+ * @param endDate
+ * End date
+ * @return List of events
+ */
+ public List<CalendarEvent> getEvents(Date startDate, Date endDate);
+
+ /**
+ * Event to signal that the set of events has changed and the calendar
+ * should refresh its view from the
+ * {@link com.vaadin.addon.calendar.event.CalendarEventProvider
+ * CalendarEventProvider} .
+ *
+ */
+ @SuppressWarnings("serial")
+ public class EventSetChangeEvent implements Serializable {
+
+ private CalendarEventProvider source;
+
+ public EventSetChangeEvent(CalendarEventProvider source) {
+ this.source = source;
+ }
+
+ /**
+ * @return the
+ * {@link com.vaadin.addon.calendar.event.CalendarEventProvider
+ * CalendarEventProvider} that has changed
+ */
+ public CalendarEventProvider getProvider() {
+ return source;
+ }
+ }
+
+ /**
+ * Listener for EventSetChange events.
+ */
+ public interface EventSetChangeListener extends Serializable {
+
+ /**
+ * Called when the set of Events has changed.
+ */
+ public void eventSetChange(EventSetChangeEvent changeEvent);
+ }
+
+ /**
+ * Notifier interface for EventSetChange events.
+ */
+ public interface EventSetChangeNotifier extends Serializable {
+
+ /**
+ * Add a listener for listening to when new events are adding or removed
+ * from the event provider.
+ *
+ * @param listener
+ * The listener to add
+ */
+ void addEventSetChangeListener(EventSetChangeListener listener);
+
+ /**
+ * Remove a listener which listens to {@link EventSetChangeEvent}-events
+ *
+ * @param listener
+ * The listener to remove
+ */
+ void removeEventSetChangeListener(EventSetChangeListener listener);
+ }
+}
diff --git a/server/src/com/vaadin/ui/components/calendar/event/EditableCalendarEvent.java b/server/src/com/vaadin/ui/components/calendar/event/EditableCalendarEvent.java
new file mode 100644
index 0000000000..e8a27ad50f
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/calendar/event/EditableCalendarEvent.java
@@ -0,0 +1,91 @@
+/*
+ * 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.components.calendar.event;
+
+import java.util.Date;
+
+/**
+ * <p>
+ * Extension to the basic {@link com.vaadin.addon.calendar.event.CalendarEvent
+ * CalendarEvent}. This interface provides setters (and thus editing
+ * capabilities) for all {@link com.vaadin.addon.calendar.event.CalendarEvent
+ * CalendarEvent} fields. For descriptions on the fields, refer to the extended
+ * interface.
+ * </p>
+ *
+ * <p>
+ * This interface is used by some of the basic Calendar event handlers in the
+ * <code>com.vaadin.addon.calendar.ui.handler</code> package to determine
+ * whether an event can be edited.
+ * </p>
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+public interface EditableCalendarEvent extends CalendarEvent {
+
+ /**
+ * Set the visible text in the calendar for the event.
+ *
+ * @param caption
+ * The text to show in the calendar
+ */
+ void setCaption(String caption);
+
+ /**
+ * Set the description of the event. This is shown in the calendar when
+ * hoovering over the event.
+ *
+ * @param description
+ * The text which describes the event
+ */
+ void setDescription(String description);
+
+ /**
+ * Set the end date of the event. Must be after the start date.
+ *
+ * @param end
+ * The end date to set
+ */
+ void setEnd(Date end);
+
+ /**
+ * Set the start date for the event. Must be before the end date
+ *
+ * @param start
+ * The start date of the event
+ */
+ void setStart(Date start);
+
+ /**
+ * Set the style name for the event used for styling the event cells
+ *
+ * @param styleName
+ * The stylename to use
+ *
+ */
+ void setStyleName(String styleName);
+
+ /**
+ * Does the event span the whole day. If so then set this to true
+ *
+ * @param isAllDay
+ * True if the event spans the whole day. In this case the start
+ * and end times are ignored.
+ */
+ void setAllDay(boolean isAllDay);
+
+}
diff --git a/server/src/com/vaadin/ui/components/calendar/handler/BasicBackwardHandler.java b/server/src/com/vaadin/ui/components/calendar/handler/BasicBackwardHandler.java
new file mode 100644
index 0000000000..65e9c94dec
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/calendar/handler/BasicBackwardHandler.java
@@ -0,0 +1,79 @@
+/*
+ * 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.components.calendar.handler;
+
+import java.util.Calendar;
+import java.util.Date;
+
+import com.vaadin.shared.ui.calendar.DateConstants;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.BackwardEvent;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.BackwardHandler;
+
+/**
+ * Implements basic functionality needed to enable backwards navigation.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+@SuppressWarnings("serial")
+public class BasicBackwardHandler implements BackwardHandler {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.BackwardHandler#
+ * backward
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.BackwardEvent)
+ */
+ @Override
+ public void backward(BackwardEvent event) {
+ Date start = event.getComponent().getStartDate();
+ Date end = event.getComponent().getEndDate();
+
+ // calculate amount to move back
+ int durationInDays = (int) (((end.getTime()) - start.getTime()) / DateConstants.DAYINMILLIS);
+ durationInDays++;
+ durationInDays = -durationInDays;
+
+ // set new start and end times
+ Calendar javaCalendar = event.getComponent().getInternalCalendar();
+ javaCalendar.setTime(start);
+ javaCalendar.add(java.util.Calendar.DATE, durationInDays);
+ Date newStart = javaCalendar.getTime();
+
+ javaCalendar.setTime(end);
+ javaCalendar.add(java.util.Calendar.DATE, durationInDays);
+ Date newEnd = javaCalendar.getTime();
+
+ setDates(event, newStart, newEnd);
+ }
+
+ /**
+ * Set the start and end dates for the event
+ *
+ * @param event
+ * The event that the start and end dates should be set
+ * @param start
+ * The start date
+ * @param end
+ * The end date
+ */
+ protected void setDates(BackwardEvent event, Date start, Date end) {
+ event.getComponent().setStartDate(start);
+ event.getComponent().setEndDate(end);
+ }
+}
diff --git a/server/src/com/vaadin/ui/components/calendar/handler/BasicDateClickHandler.java b/server/src/com/vaadin/ui/components/calendar/handler/BasicDateClickHandler.java
new file mode 100644
index 0000000000..ac2470e008
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/calendar/handler/BasicDateClickHandler.java
@@ -0,0 +1,70 @@
+/*
+ * 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.components.calendar.handler;
+
+import java.util.Calendar;
+import java.util.Date;
+
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.DateClickEvent;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.DateClickHandler;
+
+/**
+ * Implements basic functionality needed to switch to day view when a single day
+ * is clicked.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+@SuppressWarnings("serial")
+public class BasicDateClickHandler implements DateClickHandler {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.DateClickHandler
+ * #dateClick
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.DateClickEvent)
+ */
+ @Override
+ public void dateClick(DateClickEvent event) {
+ Date clickedDate = event.getDate();
+
+ Calendar javaCalendar = event.getComponent().getInternalCalendar();
+ javaCalendar.setTime(clickedDate);
+
+ // as times are expanded, this is all that is needed to show one day
+ Date start = javaCalendar.getTime();
+ Date end = javaCalendar.getTime();
+
+ setDates(event, start, end);
+ }
+
+ /**
+ * Set the start and end dates for the event
+ *
+ * @param event
+ * The event that the start and end dates should be set
+ * @param start
+ * The start date
+ * @param end
+ * The end date
+ */
+ protected void setDates(DateClickEvent event, Date start, Date end) {
+ event.getComponent().setStartDate(start);
+ event.getComponent().setEndDate(end);
+ }
+}
diff --git a/server/src/com/vaadin/ui/components/calendar/handler/BasicEventMoveHandler.java b/server/src/com/vaadin/ui/components/calendar/handler/BasicEventMoveHandler.java
new file mode 100644
index 0000000000..ae4c5fcc12
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/calendar/handler/BasicEventMoveHandler.java
@@ -0,0 +1,74 @@
+/*
+ * 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.components.calendar.handler;
+
+import java.util.Date;
+
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.EventMoveHandler;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.MoveEvent;
+import com.vaadin.ui.components.calendar.event.CalendarEvent;
+import com.vaadin.ui.components.calendar.event.EditableCalendarEvent;
+
+/**
+ * Implements basic functionality needed to enable moving events.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+@SuppressWarnings("serial")
+public class BasicEventMoveHandler implements EventMoveHandler {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventMoveHandler
+ * #eventMove
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.MoveEvent)
+ */
+ @Override
+ public void eventMove(MoveEvent event) {
+ CalendarEvent calendarEvent = event.getCalendarEvent();
+
+ if (calendarEvent instanceof EditableCalendarEvent) {
+
+ EditableCalendarEvent editableEvent = (EditableCalendarEvent) calendarEvent;
+
+ Date newFromTime = event.getNewStart();
+
+ // Update event dates
+ long length = editableEvent.getEnd().getTime()
+ - editableEvent.getStart().getTime();
+ setDates(editableEvent, newFromTime, new Date(newFromTime.getTime()
+ + length));
+ }
+ }
+
+ /**
+ * Set the start and end dates for the event
+ *
+ * @param event
+ * The event that the start and end dates should be set
+ * @param start
+ * The start date
+ * @param end
+ * The end date
+ */
+ protected void setDates(EditableCalendarEvent event, Date start, Date end) {
+ event.setStart(start);
+ event.setEnd(end);
+ }
+}
diff --git a/server/src/com/vaadin/ui/components/calendar/handler/BasicEventResizeHandler.java b/server/src/com/vaadin/ui/components/calendar/handler/BasicEventResizeHandler.java
new file mode 100644
index 0000000000..ee7fc27360
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/calendar/handler/BasicEventResizeHandler.java
@@ -0,0 +1,70 @@
+/*
+ * 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.components.calendar.handler;
+
+import java.util.Date;
+
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.EventResize;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.EventResizeHandler;
+import com.vaadin.ui.components.calendar.event.CalendarEvent;
+import com.vaadin.ui.components.calendar.event.EditableCalendarEvent;
+
+/**
+ * Implements basic functionality needed to enable event resizing.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+@SuppressWarnings("serial")
+public class BasicEventResizeHandler implements EventResizeHandler {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventResizeHandler
+ * #eventResize
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventResize)
+ */
+ @Override
+ public void eventResize(EventResize event) {
+ CalendarEvent calendarEvent = event.getCalendarEvent();
+
+ if (calendarEvent instanceof EditableCalendarEvent) {
+ Date newStartTime = event.getNewStart();
+ Date newEndTime = event.getNewEnd();
+
+ EditableCalendarEvent editableEvent = (EditableCalendarEvent) calendarEvent;
+
+ setDates(editableEvent, newStartTime, newEndTime);
+ }
+ }
+
+ /**
+ * Set the start and end dates for the event
+ *
+ * @param event
+ * The event that the start and end dates should be set
+ * @param start
+ * The start date
+ * @param end
+ * The end date
+ */
+ protected void setDates(EditableCalendarEvent event, Date start, Date end) {
+ event.setStart(start);
+ event.setEnd(end);
+ }
+}
diff --git a/server/src/com/vaadin/ui/components/calendar/handler/BasicForwardHandler.java b/server/src/com/vaadin/ui/components/calendar/handler/BasicForwardHandler.java
new file mode 100644
index 0000000000..e36c9e5756
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/calendar/handler/BasicForwardHandler.java
@@ -0,0 +1,77 @@
+/*
+ * 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.components.calendar.handler;
+
+import java.util.Calendar;
+import java.util.Date;
+
+import com.vaadin.shared.ui.calendar.DateConstants;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.ForwardEvent;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.ForwardHandler;
+
+/**
+ * Implements basic functionality needed to enable forward navigation.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+@SuppressWarnings("serial")
+public class BasicForwardHandler implements ForwardHandler {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.ForwardHandler#forward
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.ForwardEvent)
+ */
+ @Override
+ public void forward(ForwardEvent event) {
+ Date start = event.getComponent().getStartDate();
+ Date end = event.getComponent().getEndDate();
+
+ // calculate amount to move forward
+ int durationInDays = (int) (((end.getTime()) - start.getTime()) / DateConstants.DAYINMILLIS);
+ durationInDays++;
+
+ // set new start and end times
+ Calendar javaCalendar = Calendar.getInstance();
+ javaCalendar.setTime(start);
+ javaCalendar.add(java.util.Calendar.DATE, durationInDays);
+ Date newStart = javaCalendar.getTime();
+
+ javaCalendar.setTime(end);
+ javaCalendar.add(java.util.Calendar.DATE, durationInDays);
+ Date newEnd = javaCalendar.getTime();
+
+ setDates(event, newStart, newEnd);
+ }
+
+ /**
+ * Set the start and end dates for the event
+ *
+ * @param event
+ * The event that the start and end dates should be set
+ * @param start
+ * The start date
+ * @param end
+ * The end date
+ */
+ protected void setDates(ForwardEvent event, Date start, Date end) {
+ event.getComponent().setStartDate(start);
+ event.getComponent().setEndDate(end);
+ }
+}
diff --git a/server/src/com/vaadin/ui/components/calendar/handler/BasicWeekClickHandler.java b/server/src/com/vaadin/ui/components/calendar/handler/BasicWeekClickHandler.java
new file mode 100644
index 0000000000..846fd7dd53
--- /dev/null
+++ b/server/src/com/vaadin/ui/components/calendar/handler/BasicWeekClickHandler.java
@@ -0,0 +1,82 @@
+/*
+ * 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.components.calendar.handler;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.WeekClick;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.WeekClickHandler;
+
+/**
+ * Implements basic functionality needed to change to week view when a week
+ * number is clicked.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+@SuppressWarnings("serial")
+public class BasicWeekClickHandler implements WeekClickHandler {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.WeekClickHandler
+ * #weekClick
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.WeekClick)
+ */
+ @Override
+ public void weekClick(WeekClick event) {
+ int week = event.getWeek();
+ int year = event.getYear();
+
+ // set correct year and month
+ Calendar javaCalendar = event.getComponent().getInternalCalendar();
+ javaCalendar.set(GregorianCalendar.YEAR, year);
+ javaCalendar.set(GregorianCalendar.WEEK_OF_YEAR, week);
+
+ // starting at the beginning of the week
+ javaCalendar.set(GregorianCalendar.DAY_OF_WEEK,
+ javaCalendar.getFirstDayOfWeek());
+ Date start = javaCalendar.getTime();
+
+ // ending at the end of the week
+ javaCalendar.add(GregorianCalendar.DATE, 6);
+ Date end = javaCalendar.getTime();
+
+ setDates(event, start, end);
+
+ // times are automatically expanded, no need to worry about them
+ }
+
+ /**
+ * Set the start and end dates for the event
+ *
+ * @param event
+ * The event that the start and end dates should be set
+ * @param start
+ * The start date
+ * @param end
+ * The end date
+ */
+ protected void setDates(WeekClick event, Date start, Date end) {
+ event.getComponent().setStartDate(start);
+ event.getComponent().setEndDate(end);
+ }
+
+}
diff --git a/server/src/com/vaadin/ui/components/colorpicker/ColorPickerGrid.java b/server/src/com/vaadin/ui/components/colorpicker/ColorPickerGrid.java
index 9e9855afdd..9123245033 100644
--- a/server/src/com/vaadin/ui/components/colorpicker/ColorPickerGrid.java
+++ b/server/src/com/vaadin/ui/components/colorpicker/ColorPickerGrid.java
@@ -187,6 +187,7 @@ public class ColorPickerGrid extends AbstractComponent implements ColorSelector
* @param listener
* The color change listener
*/
+ @Override
public void addColorChangeListener(ColorChangeListener listener) {
addListener(ColorChangeEvent.class, listener, COLOR_CHANGE_METHOD);
}
@@ -202,6 +203,7 @@ public class ColorPickerGrid extends AbstractComponent implements ColorSelector
* @param listener
* The listener
*/
+ @Override
public void removeColorChangeListener(ColorChangeListener listener) {
removeListener(ColorChangeEvent.class, listener);
}
diff --git a/server/src/com/vaadin/ui/components/colorpicker/ColorPickerHistory.java b/server/src/com/vaadin/ui/components/colorpicker/ColorPickerHistory.java
index e6edbcf40e..2902585f56 100644
--- a/server/src/com/vaadin/ui/components/colorpicker/ColorPickerHistory.java
+++ b/server/src/com/vaadin/ui/components/colorpicker/ColorPickerHistory.java
@@ -194,6 +194,7 @@ public class ColorPickerHistory extends CustomComponent implements
* @param listener
* The listener
*/
+ @Override
public void addColorChangeListener(ColorChangeListener listener) {
addListener(ColorChangeEvent.class, listener, COLOR_CHANGE_METHOD);
}
@@ -204,6 +205,7 @@ public class ColorPickerHistory extends CustomComponent implements
* @param listener
* The listener
*/
+ @Override
public void removeColorChangeListener(ColorChangeListener listener) {
removeListener(ColorChangeEvent.class, listener);
}
diff --git a/server/src/com/vaadin/ui/components/colorpicker/ColorPickerPopup.java b/server/src/com/vaadin/ui/components/colorpicker/ColorPickerPopup.java
index c06ae9f6ff..fee52d1a24 100644
--- a/server/src/com/vaadin/ui/components/colorpicker/ColorPickerPopup.java
+++ b/server/src/com/vaadin/ui/components/colorpicker/ColorPickerPopup.java
@@ -283,6 +283,7 @@ public class ColorPickerPopup extends Window implements ClickListener,
}
redSlider.addValueChangeListener(new ValueChangeListener() {
+ @Override
public void valueChange(ValueChangeEvent event) {
double red = (Double) event.getProperty().getValue();
if (!updatingColors) {
@@ -303,6 +304,7 @@ public class ColorPickerPopup extends Window implements ClickListener,
}
greenSlider.addValueChangeListener(new ValueChangeListener() {
+ @Override
public void valueChange(ValueChangeEvent event) {
double green = (Double) event.getProperty().getValue();
if (!updatingColors) {
@@ -322,6 +324,7 @@ public class ColorPickerPopup extends Window implements ClickListener,
}
blueSlider.addValueChangeListener(new ValueChangeListener() {
+ @Override
public void valueChange(ValueChangeEvent event) {
double blue = (Double) event.getProperty().getValue();
if (!updatingColors) {
@@ -380,6 +383,7 @@ public class ColorPickerPopup extends Window implements ClickListener,
hueSlider.setWidth("220px");
hueSlider.setImmediate(true);
hueSlider.addValueChangeListener(new ValueChangeListener() {
+ @Override
public void valueChange(ValueChangeEvent event) {
if (!updatingColors) {
float hue = (Float.parseFloat(event.getProperty()
@@ -417,6 +421,7 @@ public class ColorPickerPopup extends Window implements ClickListener,
saturationSlider.setWidth("220px");
saturationSlider.setImmediate(true);
saturationSlider.addValueChangeListener(new ValueChangeListener() {
+ @Override
public void valueChange(ValueChangeEvent event) {
if (!updatingColors) {
float hue = (Float.parseFloat(hueSlider.getValue()
@@ -444,6 +449,7 @@ public class ColorPickerPopup extends Window implements ClickListener,
valueSlider.setWidth("220px");
valueSlider.setImmediate(true);
valueSlider.addValueChangeListener(new ValueChangeListener() {
+ @Override
public void valueChange(ValueChangeEvent event) {
if (!updatingColors) {
float hue = (Float.parseFloat(hueSlider.getValue()
@@ -754,6 +760,7 @@ public class ColorPickerPopup extends Window implements ClickListener,
/** HSV color converter */
Coordinates2Color HSVConverter = new Coordinates2Color() {
+ @Override
public int[] calculate(Color color) {
float[] hsv = color.getHSV();
@@ -769,6 +776,7 @@ public class ColorPickerPopup extends Window implements ClickListener,
return new int[] { x, y };
}
+ @Override
public Color calculate(int x, int y) {
float saturation = 1f - (y / 220.0f);
float value = (x / 220.0f);
diff --git a/server/src/com/vaadin/util/CurrentInstance.java b/server/src/com/vaadin/util/CurrentInstance.java
index 595c162e7e..60489d596e 100644
--- a/server/src/com/vaadin/util/CurrentInstance.java
+++ b/server/src/com/vaadin/util/CurrentInstance.java
@@ -21,8 +21,28 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
+import com.vaadin.server.VaadinPortlet;
+import com.vaadin.server.VaadinPortletService;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.server.VaadinResponse;
+import com.vaadin.server.VaadinService;
+import com.vaadin.server.VaadinServlet;
+import com.vaadin.server.VaadinServletService;
+import com.vaadin.server.VaadinSession;
+import com.vaadin.ui.UI;
+
/**
* Keeps track of various thread local instances used by the framework.
+ * <p>
+ * Currently the framework uses the following instances:
+ * </p>
+ * <p>
+ * Inheritable: {@link UI}, {@link VaadinPortlet}, {@link VaadinService},
+ * {@link VaadinServlet}, {@link VaadinSession}.
+ * </p>
+ * <p>
+ * Non-inheritable: {@link VaadinRequest}, {@link VaadinResponse}.
+ * </p>
*
* @author Vaadin Ltd
* @version @VERSION@
@@ -32,6 +52,18 @@ public class CurrentInstance implements Serializable {
private final Object instance;
private final boolean inheritable;
+ private static boolean portletAvailable = false;
+ {
+ try {
+ /*
+ * VaadinPortlet depends on portlet API which is available only if
+ * running in a portal.
+ */
+ portletAvailable = (VaadinPortlet.class.getName() != null);
+ } catch (Throwable t) {
+ }
+ }
+
private static InheritableThreadLocal<Map<Class<?>, CurrentInstance>> instances = new InheritableThreadLocal<Map<Class<?>, CurrentInstance>>() {
@Override
protected Map<Class<?>, CurrentInstance> childValue(
@@ -135,7 +167,11 @@ public class CurrentInstance implements Serializable {
new CurrentInstance(instance, inheritable));
if (previousInstance != null) {
assert previousInstance.inheritable == inheritable : "Inheritable status mismatch for "
- + type;
+ + type
+ + " (previous was "
+ + previousInstance.inheritable
+ + ", new is "
+ + inheritable + ")";
}
}
}
@@ -146,4 +182,72 @@ public class CurrentInstance implements Serializable {
public static void clearAll() {
instances.remove();
}
+
+ /**
+ * Restores the given thread locals to the given values. Note that this
+ * should only be used internally to restore Vaadin classes.
+ *
+ * @param old
+ * A Class -> Object map to set as thread locals
+ */
+ public static void restoreThreadLocals(Map<Class<?>, CurrentInstance> old) {
+ for (Class c : old.keySet()) {
+ CurrentInstance ci = old.get(c);
+ set(c, ci.instance, ci.inheritable);
+ }
+ }
+
+ /**
+ * Sets thread locals for the UI and all related classes
+ *
+ * @param ui
+ * The UI
+ * @return A map containing the old values of the thread locals this method
+ * updated.
+ */
+ public static Map<Class<?>, CurrentInstance> setThreadLocals(UI ui) {
+ Map<Class<?>, CurrentInstance> old = new HashMap<Class<?>, CurrentInstance>();
+ old.put(UI.class, new CurrentInstance(UI.getCurrent(), true));
+ UI.setCurrent(ui);
+ old.putAll(setThreadLocals(ui.getSession()));
+ return old;
+ }
+
+ /**
+ * Sets thread locals for the {@link VaadinSession} and all related classes
+ *
+ * @param session
+ * The VaadinSession
+ * @return A map containing the old values of the thread locals this method
+ * updated.
+ */
+ public static Map<Class<?>, CurrentInstance> setThreadLocals(
+ VaadinSession session) {
+ Map<Class<?>, CurrentInstance> old = new HashMap<Class<?>, CurrentInstance>();
+ old.put(VaadinSession.class,
+ new CurrentInstance(VaadinSession.getCurrent(), true));
+ old.put(VaadinService.class,
+ new CurrentInstance(VaadinService.getCurrent(), true));
+ VaadinService service = null;
+ if (session != null) {
+ service = session.getService();
+ }
+
+ VaadinSession.setCurrent(session);
+ VaadinService.setCurrent(service);
+
+ if (service instanceof VaadinServletService) {
+ old.put(VaadinServlet.class,
+ new CurrentInstance(VaadinServlet.getCurrent(), true));
+ VaadinServlet.setCurrent(((VaadinServletService) service)
+ .getServlet());
+ } else if (portletAvailable && service instanceof VaadinPortletService) {
+ old.put(VaadinPortlet.class,
+ new CurrentInstance(VaadinPortlet.getCurrent(), true));
+ VaadinPortlet.setCurrent(((VaadinPortletService) service)
+ .getPortlet());
+ }
+
+ return old;
+ }
}