diff options
Diffstat (limited to 'server/src')
56 files changed, 2098 insertions, 1078 deletions
diff --git a/server/src/com/vaadin/annotations/Push.java b/server/src/com/vaadin/annotations/Push.java index 58e70acf21..d5e42d6f60 100644 --- a/server/src/com/vaadin/annotations/Push.java +++ b/server/src/com/vaadin/annotations/Push.java @@ -22,6 +22,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import com.vaadin.shared.communication.PushMode; +import com.vaadin.shared.ui.ui.Transport; import com.vaadin.ui.UI; /** @@ -46,4 +47,13 @@ public @interface Push { */ public PushMode value() default PushMode.AUTOMATIC; + /** + * Returns the transport type used for the push for the annotated UI. The + * default transport type when this annotation is present is + * {@link Transport#WEBSOCKET}. + * + * @return the transport type to use + */ + public Transport transport() default Transport.DEFAULT; + } diff --git a/server/src/com/vaadin/annotations/VaadinServletConfiguration.java b/server/src/com/vaadin/annotations/VaadinServletConfiguration.java new file mode 100644 index 0000000000..38e3ff2ef0 --- /dev/null +++ b/server/src/com/vaadin/annotations/VaadinServletConfiguration.java @@ -0,0 +1,143 @@ +/* + * 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.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.vaadin.server.Constants; +import com.vaadin.server.DefaultDeploymentConfiguration; +import com.vaadin.server.DeploymentConfiguration; +import com.vaadin.server.DeploymentConfiguration.LegacyProperyToStringMode; +import com.vaadin.server.VaadinServlet; +import com.vaadin.server.VaadinSession; +import com.vaadin.ui.UI; + +/** + * Annotation for configuring subclasses of {@link VaadinServlet}. For a + * {@link VaadinServlet} class that has this annotation, the defined values are + * read during initialization and will be available using + * {@link DeploymentConfiguration#getApplicationOrSystemProperty(String, String)} + * as well as from specific methods in {@link DeploymentConfiguration}. Init + * params defined in <code>web.xml</code> or the <code>@WebServlet</code> + * annotation take precedence over values defined in this annotation. + * + * @since 7.1 + * @author Vaadin Ltd + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface VaadinServletConfiguration { + /** + * Defines the init parameter name for methods in + * {@link VaadinServletConfiguration}. + * + * @since 7.1 + * @author Vaadin Ltd + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + @Documented + public @interface InitParameterName { + /** + * The name of the init parameter that the annotated method controls. + * + * @return the parameter name + */ + public String value(); + } + + /** + * Whether Vaadin is in production mode. + * + * @return true if in production mode, false otherwise. + * + * @see DeploymentConfiguration#isProductionMode() + */ + @InitParameterName(Constants.SERVLET_PARAMETER_PRODUCTION_MODE) + public boolean productionMode(); + + /** + * Gets the default UI class to use for the servlet. + * + * @return the default UI class + */ + @InitParameterName(VaadinSession.UI_PARAMETER) + public Class<? extends UI> ui(); + + /** + * The time resources can be cached in the browser, in seconds. The default + * value is + * {@value DefaultDeploymentConfiguration#DEFAULT_RESOURCE_CACHE_TIME}. + * + * @return the resource cache time + * + * @see DeploymentConfiguration#getResourceCacheTime() + */ + @InitParameterName(Constants.SERVLET_PARAMETER_RESOURCE_CACHE_TIME) + public int resourceCacheTime() default DefaultDeploymentConfiguration.DEFAULT_RESOURCE_CACHE_TIME; + + /** + * The number of seconds between heartbeat requests of a UI, or a + * non-positive number if heartbeat is disabled. The default value is + * {@value DefaultDeploymentConfiguration#DEFAULT_HEARTBEAT_INTERVAL}. + * + * @return the time between heartbeats + * + * @see DeploymentConfiguration#getHeartbeatInterval() + */ + @InitParameterName(Constants.SERVLET_PARAMETER_HEARTBEAT_INTERVAL) + public int heartbeatInterval() default DefaultDeploymentConfiguration.DEFAULT_HEARTBEAT_INTERVAL; + + /** + * Whether a session should be closed when all its open UIs have been idle + * for longer than its configured maximum inactivity time. The default value + * is {@value DefaultDeploymentConfiguration#DEFAULT_CLOSE_IDLE_SESSIONS}. + * + * @return true if UIs and sessions receiving only heartbeat requests are + * eventually closed; false if heartbeat requests extend UI and + * session lifetime indefinitely + * + * @see DeploymentConfiguration#isCloseIdleSessions() + */ + @InitParameterName(Constants.SERVLET_PARAMETER_CLOSE_IDLE_SESSIONS) + public boolean closeIdleSessions() default DefaultDeploymentConfiguration.DEFAULT_CLOSE_IDLE_SESSIONS; + + /** + * The default widgetset to use for the servlet. The default value is + * {@value VaadinServlet#DEFAULT_WIDGETSET}. + * + * @return the default widgetset name + */ + @InitParameterName(VaadinServlet.PARAMETER_WIDGETSET) + public String widgetset() default VaadinServlet.DEFAULT_WIDGETSET; + + /** + * The legacy Property.toString() mode used. The default value is + * {@link LegacyProperyToStringMode#DISABLED} + * + * @return The Property.toString() mode in use. + * + * @deprecated as of 7.1, should only be used to ease migration + */ + @Deprecated + @InitParameterName(Constants.SERVLET_PARAMETER_LEGACY_PROPERTY_TOSTRING) + public LegacyProperyToStringMode legacyPropertyToStringMode() default LegacyProperyToStringMode.DISABLED; +} diff --git a/server/src/com/vaadin/data/util/LegacyPropertyHelper.java b/server/src/com/vaadin/data/util/LegacyPropertyHelper.java index 0276e35dbf..76bd57117d 100644 --- a/server/src/com/vaadin/data/util/LegacyPropertyHelper.java +++ b/server/src/com/vaadin/data/util/LegacyPropertyHelper.java @@ -15,6 +15,7 @@ */ package com.vaadin.data.util; +import java.io.Serializable; import java.util.logging.Level; import java.util.logging.Logger; @@ -32,7 +33,7 @@ import com.vaadin.server.VaadinService; * @deprecated This is only used internally for backwards compatibility */ @Deprecated -public class LegacyPropertyHelper { +public class LegacyPropertyHelper implements Serializable { /** * Returns the property value converted to a String. @@ -59,6 +60,11 @@ public class LegacyPropertyHelper { getLogger().log(Level.WARNING, Constants.WARNING_LEGACY_PROPERTY_TOSTRING, p.getClass().getName()); + if (getLogger().isLoggable(Level.FINE)) { + getLogger().log(Level.FINE, + "Strack trace for legacy toString to ease debugging", + new Throwable()); + } } /** @@ -76,8 +82,8 @@ public class LegacyPropertyHelper { */ 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. + // This will happen at least in JUnit tests. We do not what the real + // value should be but it seems more safe to use the legacy mode. return true; } return VaadinService.getCurrent().getDeploymentConfiguration() @@ -86,9 +92,9 @@ public class LegacyPropertyHelper { 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; + // This will happen at least in JUnit tests. We do not want to spam + // the log with these messages in this case. + return false; } return VaadinService.getCurrent().getDeploymentConfiguration() .getLegacyPropertyToStringMode() == LegacyProperyToStringMode.WARNING; diff --git a/server/src/com/vaadin/data/util/converter/AbstractStringToNumberConverter.java b/server/src/com/vaadin/data/util/converter/AbstractStringToNumberConverter.java new file mode 100644 index 0000000000..5999d850b4 --- /dev/null +++ b/server/src/com/vaadin/data/util/converter/AbstractStringToNumberConverter.java @@ -0,0 +1,120 @@ +/* + * Copyright 2000-2013 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.data.util.converter; + +import java.text.NumberFormat; +import java.text.ParsePosition; +import java.util.Locale; + +/** + * A converter that converts from the number type T to {@link String} and back. + * Uses the given locale and {@link NumberFormat} for formatting and parsing. + * Automatically trims the input string, removing any leading and trailing white + * space. + * <p> + * Override and overwrite {@link #getFormat(Locale)} to use a different format. + * </p> + * + * @author Vaadin Ltd + * @since 7.1 + */ +public abstract class AbstractStringToNumberConverter<T> implements + Converter<String, T> { + + /** + * Returns the format used by {@link #convertToPresentation(Object, Locale)} + * and {@link #convertToModel(Object, Locale)}. + * + * @param locale + * The locale to use + * @return A NumberFormat instance + * @since 7.1 + */ + protected NumberFormat getFormat(Locale locale) { + if (locale == null) { + locale = Locale.getDefault(); + } + + return NumberFormat.getNumberInstance(locale); + } + + /** + * Convert the value to a Number using the given locale and + * {@link #getFormat(Locale)}. + * + * @param value + * The value to convert + * @param locale + * The locale to use for conversion + * @return The converted value + * @throws ConversionException + * If there was a problem converting the value + * @since 7.1 + */ + protected Number convertToNumber(String value, Locale locale) + throws ConversionException { + if (value == null) { + return null; + } + + // Remove leading and trailing white space + value = value.trim(); + + // Parse and detect errors. If the full string was not used, it is + // an error. + ParsePosition parsePosition = new ParsePosition(0); + Number parsedValue = getFormat(locale).parse(value, parsePosition); + if (parsePosition.getIndex() != value.length()) { + throw new ConversionException("Could not convert '" + value + + "' to " + getModelType().getName()); + } + + if (parsedValue == null) { + // Convert "" to null + return null; + } + return parsedValue; + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.data.util.converter.Converter#convertToPresentation(java.lang + * .Object, java.util.Locale) + */ + @Override + public String convertToPresentation(T value, Locale locale) + throws ConversionException { + if (value == null) { + return null; + } + + return getFormat(locale).format(value); + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.data.util.converter.Converter#getPresentationType() + */ + @Override + public Class<String> getPresentationType() { + return String.class; + } + +} diff --git a/server/src/com/vaadin/data/util/converter/Converter.java b/server/src/com/vaadin/data/util/converter/Converter.java index be9bb32413..ded7da7fb5 100644 --- a/server/src/com/vaadin/data/util/converter/Converter.java +++ b/server/src/com/vaadin/data/util/converter/Converter.java @@ -38,12 +38,12 @@ import java.util.Locale; * If conversion of a value fails, a {@link ConversionException} is thrown. * </p> * - * @param <MODEL> - * The model type. Must be compatible with what - * {@link #getModelType()} returns. * @param <PRESENTATION> * The presentation type. Must be compatible with what * {@link #getPresentationType()} returns. + * @param <MODEL> + * The model type. Must be compatible with what + * {@link #getModelType()} returns. * @author Vaadin Ltd. * @since 7.0 */ diff --git a/server/src/com/vaadin/data/util/converter/ConverterUtil.java b/server/src/com/vaadin/data/util/converter/ConverterUtil.java index 61d155bc9a..08d7363084 100644 --- a/server/src/com/vaadin/data/util/converter/ConverterUtil.java +++ b/server/src/com/vaadin/data/util/converter/ConverterUtil.java @@ -151,10 +151,14 @@ public class ConverterUtil implements Serializable { /** * Checks if the given converter can handle conversion between the given - * presentation and model type + * presentation and model type. Does strict type checking and only returns + * true if the converter claims it can handle exactly the given types. + * + * @see #canConverterPossiblyHandle(Converter, Class, Class) * * @param converter - * The converter to check + * The converter to check. If this is null the result is always + * false. * @param presentationType * The presentation type * @param modelType @@ -168,10 +172,48 @@ public class ConverterUtil implements Serializable { return false; } - if (!modelType.isAssignableFrom(converter.getModelType())) { + if (modelType != converter.getModelType()) { + return false; + } + if (presentationType != converter.getPresentationType()) { return false; } - if (!presentationType.isAssignableFrom(converter.getPresentationType())) { + + return true; + } + + /** + * Checks if it possible that the given converter can handle conversion + * between the given presentation and model type somehow. + * + * @param converter + * The converter to check. If this is null the result is always + * false. + * @param presentationType + * The presentation type + * @param modelType + * The model type + * @return true if the converter possibly support conversion between the + * given presentation and model type, false otherwise + */ + public static boolean canConverterPossiblyHandle(Converter<?, ?> converter, + Class<?> presentationType, Class<?> modelType) { + if (converter == null) { + return false; + } + Class<?> converterModelType = converter.getModelType(); + + if (!modelType.isAssignableFrom(converterModelType) + && !converterModelType.isAssignableFrom(modelType)) { + // model types are not compatible in any way + return false; + } + + Class<?> converterPresentationType = converter.getPresentationType(); + if (!presentationType.isAssignableFrom(converterPresentationType) + && !converterPresentationType + .isAssignableFrom(presentationType)) { + // presentation types are not compatible in any way return false; } diff --git a/server/src/com/vaadin/data/util/converter/DateToSqlDateConverter.java b/server/src/com/vaadin/data/util/converter/DateToSqlDateConverter.java new file mode 100644 index 0000000000..97027cc05b --- /dev/null +++ b/server/src/com/vaadin/data/util/converter/DateToSqlDateConverter.java @@ -0,0 +1,59 @@ +/* + * Copyright 2000-2013 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +/** + * + */ +package com.vaadin.data.util.converter; + +import java.util.Date; +import java.util.Locale; + +/** + * Converter for handling conversion between {@link java.util.Date} and + * {@link java.sql.Date}. This is used when a PopupDateField or InlineDateField + * is connected to a java.sql.Date property, typically through a JPAContainer or + * SQLContainer. Note that information (time information) is lost when + * converting from {@link java.util.Date} to {@link java.sql.Date}. + * + * @since 7.1 + * @author Vaadin Ltd + */ +public class DateToSqlDateConverter implements Converter<Date, java.sql.Date> { + + @Override + public java.sql.Date convertToModel(Date value, Locale locale) + throws ConversionException { + return new java.sql.Date(value.getTime()); + } + + @Override + public Date convertToPresentation(java.sql.Date value, Locale locale) + throws ConversionException { + return new Date(value.getTime()); + } + + @Override + public Class<java.sql.Date> getModelType() { + return java.sql.Date.class; + } + + @Override + public Class<Date> getPresentationType() { + return Date.class; + } + +} diff --git a/server/src/com/vaadin/data/util/converter/DefaultConverterFactory.java b/server/src/com/vaadin/data/util/converter/DefaultConverterFactory.java index de183dd342..bbd3945a37 100644 --- a/server/src/com/vaadin/data/util/converter/DefaultConverterFactory.java +++ b/server/src/com/vaadin/data/util/converter/DefaultConverterFactory.java @@ -87,6 +87,8 @@ public class DefaultConverterFactory implements ConverterFactory { protected Converter<Date, ?> createDateConverter(Class<?> sourceType) { if (Long.class.isAssignableFrom(sourceType)) { return new DateToLongConverter(); + } else if (java.sql.Date.class.isAssignableFrom(sourceType)) { + return new DateToSqlDateConverter(); } else { return null; } diff --git a/server/src/com/vaadin/data/util/converter/StringToDoubleConverter.java b/server/src/com/vaadin/data/util/converter/StringToDoubleConverter.java index 69a0faf8f4..8bb82498b9 100644 --- a/server/src/com/vaadin/data/util/converter/StringToDoubleConverter.java +++ b/server/src/com/vaadin/data/util/converter/StringToDoubleConverter.java @@ -17,7 +17,6 @@ package com.vaadin.data.util.converter; import java.text.NumberFormat; -import java.text.ParsePosition; import java.util.Locale; /** @@ -34,23 +33,8 @@ import java.util.Locale; * @author Vaadin Ltd * @since 7.0 */ -public class StringToDoubleConverter implements Converter<String, Double> { - - /** - * Returns the format used by {@link #convertToPresentation(Double, Locale)} - * and {@link #convertToModel(String, Locale)}. - * - * @param locale - * The locale to use - * @return A NumberFormat instance - */ - protected NumberFormat getFormat(Locale locale) { - if (locale == null) { - locale = Locale.getDefault(); - } - - return NumberFormat.getNumberInstance(locale); - } +public class StringToDoubleConverter extends + AbstractStringToNumberConverter<Double> { /* * (non-Javadoc) @@ -62,42 +46,8 @@ public class StringToDoubleConverter implements Converter<String, Double> { @Override public Double convertToModel(String value, Locale locale) throws ConversionException { - if (value == null) { - return null; - } - - // Remove leading and trailing white space - value = value.trim(); - - ParsePosition parsePosition = new ParsePosition(0); - Number parsedValue = getFormat(locale).parse(value, parsePosition); - if (parsePosition.getIndex() != value.length()) { - throw new ConversionException("Could not convert '" + value - + "' to " + getModelType().getName()); - } - if (parsedValue == null) { - // Convert "" to null - return null; - } - - return parsedValue.doubleValue(); - } - - /* - * (non-Javadoc) - * - * @see - * com.vaadin.data.util.converter.Converter#convertToPresentation(java.lang - * .Object, java.util.Locale) - */ - @Override - public String convertToPresentation(Double value, Locale locale) - throws ConversionException { - if (value == null) { - return null; - } - - return getFormat(locale).format(value); + Number n = convertToNumber(value, locale); + return n == null ? null : n.doubleValue(); } /* @@ -110,13 +60,4 @@ public class StringToDoubleConverter implements Converter<String, Double> { return Double.class; } - /* - * (non-Javadoc) - * - * @see com.vaadin.data.util.converter.Converter#getPresentationType() - */ - @Override - public Class<String> getPresentationType() { - return String.class; - } } diff --git a/server/src/com/vaadin/data/util/converter/StringToFloatConverter.java b/server/src/com/vaadin/data/util/converter/StringToFloatConverter.java index 1adfd87565..a207654358 100644 --- a/server/src/com/vaadin/data/util/converter/StringToFloatConverter.java +++ b/server/src/com/vaadin/data/util/converter/StringToFloatConverter.java @@ -17,7 +17,6 @@ package com.vaadin.data.util.converter; import java.text.NumberFormat; -import java.text.ParsePosition; import java.util.Locale; /** @@ -34,23 +33,8 @@ import java.util.Locale; * @author Vaadin Ltd * @since 7.0 */ -public class StringToFloatConverter implements Converter<String, Float> { - - /** - * Returns the format used by {@link #convertToPresentation(Float, Locale)} - * and {@link #convertToModel(String, Locale)}. - * - * @param locale - * The locale to use - * @return A NumberFormat instance - */ - protected NumberFormat getFormat(Locale locale) { - if (locale == null) { - locale = Locale.getDefault(); - } - - return NumberFormat.getNumberInstance(locale); - } +public class StringToFloatConverter extends + AbstractStringToNumberConverter<Float> { /* * (non-Javadoc) @@ -62,42 +46,8 @@ public class StringToFloatConverter implements Converter<String, Float> { @Override public Float convertToModel(String value, Locale locale) throws ConversionException { - if (value == null) { - return null; - } - - // Remove leading and trailing white space - value = value.trim(); - - ParsePosition parsePosition = new ParsePosition(0); - Number parsedValue = getFormat(locale).parse(value, parsePosition); - if (parsePosition.getIndex() != value.length()) { - throw new ConversionException("Could not convert '" + value - + "' to " + getModelType().getName()); - } - if (parsedValue == null) { - // Convert "" to null - return null; - } - - return parsedValue.floatValue(); - } - - /* - * (non-Javadoc) - * - * @see - * com.vaadin.data.util.converter.Converter#convertToPresentation(java.lang - * .Object, java.util.Locale) - */ - @Override - public String convertToPresentation(Float value, Locale locale) - throws ConversionException { - if (value == null) { - return null; - } - - return getFormat(locale).format(value); + Number n = convertToNumber(value, locale); + return n == null ? null : n.floatValue(); } /* @@ -110,13 +60,4 @@ public class StringToFloatConverter implements Converter<String, Float> { return Float.class; } - /* - * (non-Javadoc) - * - * @see com.vaadin.data.util.converter.Converter#getPresentationType() - */ - @Override - public Class<String> getPresentationType() { - return String.class; - } } diff --git a/server/src/com/vaadin/data/util/converter/StringToIntegerConverter.java b/server/src/com/vaadin/data/util/converter/StringToIntegerConverter.java index 4bb933bcc8..4f34cf1cd3 100644 --- a/server/src/com/vaadin/data/util/converter/StringToIntegerConverter.java +++ b/server/src/com/vaadin/data/util/converter/StringToIntegerConverter.java @@ -17,7 +17,6 @@ package com.vaadin.data.util.converter; import java.text.NumberFormat; -import java.text.ParsePosition; import java.util.Locale; /** @@ -31,7 +30,8 @@ import java.util.Locale; * @author Vaadin Ltd * @since 7.0 */ -public class StringToIntegerConverter implements Converter<String, Integer> { +public class StringToIntegerConverter extends + AbstractStringToNumberConverter<Integer> { /** * Returns the format used by @@ -42,6 +42,7 @@ public class StringToIntegerConverter implements Converter<String, Integer> { * The locale to use * @return A NumberFormat instance */ + @Override protected NumberFormat getFormat(Locale locale) { if (locale == null) { locale = Locale.getDefault(); @@ -49,50 +50,29 @@ public class StringToIntegerConverter implements Converter<String, Integer> { return NumberFormat.getIntegerInstance(locale); } + /* + * (non-Javadoc) + * + * @see + * com.vaadin.data.util.converter.Converter#convertToModel(java.lang.Object, + * java.util.Locale) + */ @Override public Integer convertToModel(String value, Locale locale) throws ConversionException { - if (value == null) { - return null; - } - - // Remove leading and trailing white space - value = value.trim(); + Number n = convertToNumber(value, locale); + return n == null ? null : n.intValue(); - // Parse and detect errors. If the full string was not used, it is - // an error. - ParsePosition parsePosition = new ParsePosition(0); - Number parsedValue = getFormat(locale).parse(value, parsePosition); - if (parsePosition.getIndex() != value.length()) { - throw new ConversionException("Could not convert '" + value - + "' to " + getModelType().getName()); - } - - if (parsedValue == null) { - // Convert "" to null - return null; - } - return parsedValue.intValue(); - } - - @Override - public String convertToPresentation(Integer value, Locale locale) - throws ConversionException { - if (value == null) { - return null; - } - - return getFormat(locale).format(value); } + /* + * (non-Javadoc) + * + * @see com.vaadin.data.util.converter.Converter#getModelType() + */ @Override public Class<Integer> getModelType() { return Integer.class; } - @Override - public Class<String> getPresentationType() { - return String.class; - } - } diff --git a/server/src/com/vaadin/data/util/converter/StringToNumberConverter.java b/server/src/com/vaadin/data/util/converter/StringToNumberConverter.java index 99ff7007ad..eae73e4cfa 100644 --- a/server/src/com/vaadin/data/util/converter/StringToNumberConverter.java +++ b/server/src/com/vaadin/data/util/converter/StringToNumberConverter.java @@ -17,7 +17,6 @@ package com.vaadin.data.util.converter; import java.text.NumberFormat; -import java.text.ParsePosition; import java.util.Locale; /** @@ -30,23 +29,8 @@ import java.util.Locale; * @author Vaadin Ltd * @since 7.0 */ -public class StringToNumberConverter implements Converter<String, Number> { - - /** - * Returns the format used by {@link #convertToPresentation(Number, Locale)} - * and {@link #convertToModel(String, Locale)}. - * - * @param locale - * The locale to use - * @return A NumberFormat instance - */ - protected NumberFormat getFormat(Locale locale) { - if (locale == null) { - locale = Locale.getDefault(); - } - - return NumberFormat.getNumberInstance(locale); - } +public class StringToNumberConverter extends + AbstractStringToNumberConverter<Number> { /* * (non-Javadoc) @@ -58,44 +42,7 @@ public class StringToNumberConverter implements Converter<String, Number> { @Override public Number convertToModel(String value, Locale locale) throws ConversionException { - if (value == null) { - return null; - } - - // Remove leading and trailing white space - value = value.trim(); - - // Parse and detect errors. If the full string was not used, it is - // an error. - ParsePosition parsePosition = new ParsePosition(0); - Number parsedValue = getFormat(locale).parse(value, parsePosition); - if (parsePosition.getIndex() != value.length()) { - throw new ConversionException("Could not convert '" + value - + "' to " + getModelType().getName()); - } - - if (parsedValue == null) { - // Convert "" to null - return null; - } - return parsedValue; - } - - /* - * (non-Javadoc) - * - * @see - * com.vaadin.data.util.converter.Converter#convertToPresentation(java.lang - * .Object, java.util.Locale) - */ - @Override - public String convertToPresentation(Number value, Locale locale) - throws ConversionException { - if (value == null) { - return null; - } - - return getFormat(locale).format(value); + return convertToNumber(value, locale); } /* @@ -108,14 +55,4 @@ public class StringToNumberConverter implements Converter<String, Number> { return Number.class; } - /* - * (non-Javadoc) - * - * @see com.vaadin.data.util.converter.Converter#getPresentationType() - */ - @Override - public Class<String> getPresentationType() { - return String.class; - } - } diff --git a/server/src/com/vaadin/event/dd/acceptcriteria/SourceIs.java b/server/src/com/vaadin/event/dd/acceptcriteria/SourceIs.java index 65c1050fb0..0cc2f0a1a5 100644 --- a/server/src/com/vaadin/event/dd/acceptcriteria/SourceIs.java +++ b/server/src/com/vaadin/event/dd/acceptcriteria/SourceIs.java @@ -48,7 +48,7 @@ public class SourceIs extends ClientSideCriterion { int paintedComponents = 0; for (int i = 0; i < components.length; i++) { Component c = components[i]; - if (c.getUI() != null && c.getUI().getSession() != null) { + if (c.isAttached()) { target.addAttribute("component" + paintedComponents++, c); } else { Logger.getLogger(SourceIs.class.getName()) diff --git a/server/src/com/vaadin/navigator/Navigator.java b/server/src/com/vaadin/navigator/Navigator.java index df05a9fbce..540a3ee302 100644 --- a/server/src/com/vaadin/navigator/Navigator.java +++ b/server/src/com/vaadin/navigator/Navigator.java @@ -497,19 +497,22 @@ public class Navigator implements Serializable { */ public void navigateTo(String navigationState) { String longestViewName = null; + ViewProvider longestViewNameProvider = null; View viewWithLongestName = null; for (ViewProvider provider : providers) { String viewName = provider.getViewName(navigationState); if (null != viewName && (longestViewName == null || viewName.length() > longestViewName .length())) { - View view = provider.getView(viewName); - if (null != view) { - longestViewName = viewName; - viewWithLongestName = view; - } + longestViewName = viewName; + longestViewNameProvider = provider; } } + if (longestViewName != null) { + viewWithLongestName = longestViewNameProvider + .getView(longestViewName); + } + if (viewWithLongestName == null && errorProvider != null) { longestViewName = errorProvider.getViewName(navigationState); viewWithLongestName = errorProvider.getView(longestViewName); diff --git a/server/src/com/vaadin/server/AbstractClientConnector.java b/server/src/com/vaadin/server/AbstractClientConnector.java index e998b8ed55..01f7d9af42 100644 --- a/server/src/com/vaadin/server/AbstractClientConnector.java +++ b/server/src/com/vaadin/server/AbstractClientConnector.java @@ -577,7 +577,7 @@ public abstract class AbstractClientConnector implements ClientConnector, } // Send detach event if the component have been connected to a window - if (getSession() != null) { + if (isAttached()) { detach(); } @@ -585,7 +585,7 @@ public abstract class AbstractClientConnector implements ClientConnector, this.parent = parent; // Send attach event if connected to an application - if (getSession() != null) { + if (isAttached()) { attach(); } } @@ -595,6 +595,16 @@ public abstract class AbstractClientConnector implements ClientConnector, return parent; } + /* + * (non-Javadoc) + * + * @see com.vaadin.server.ClientConnector#isAttached() + */ + @Override + public boolean isAttached() { + return getSession() != null; + } + @Override public void attach() { markAsDirty(); diff --git a/server/src/com/vaadin/server/ClientConnector.java b/server/src/com/vaadin/server/ClientConnector.java index 3b52fbc730..9e328bb6ef 100644 --- a/server/src/com/vaadin/server/ClientConnector.java +++ b/server/src/com/vaadin/server/ClientConnector.java @@ -226,14 +226,22 @@ public interface ClientConnector extends Connector { public void setParent(ClientConnector parent); /** - * Notifies the connector that it is connected to an application. + * Checks if the connector is attached to a VaadinSession. * + * @since 7.1 + * @return true if the connector is attached to a session, false otherwise + */ + public boolean isAttached(); + + /** + * Notifies the connector that it is connected to a VaadinSession (and + * therefore also to a UI). * <p> * The caller of this method is {@link #setParent(ClientConnector)} if the - * parent is itself already attached to the application. If not, the parent - * will call the {@link #attach()} for all its children when it is attached - * to the application. This method is always called before the connector's - * data is sent to the client-side for the first time. + * parent is itself already attached to the session. If not, the parent will + * call the {@link #attach()} for all its children when it is attached to + * the session. This method is always called before the connector's data is + * sent to the client-side for the first time. * </p> * * <p> @@ -243,13 +251,13 @@ public interface ClientConnector extends Connector { public void attach(); /** - * Notifies the connector that it is detached from the application. + * Notifies the connector that it is detached from its VaadinSession. * * <p> * The caller of this method is {@link #setParent(ClientConnector)} if the - * parent is in the application. When the parent is detached from the - * application it is its responsibility to call {@link #detach()} for each - * of its children. + * parent is in the session. When the parent is detached from the session it + * is its responsibility to call {@link #detach()} for each of its children. + * * </p> */ public void detach(); diff --git a/server/src/com/vaadin/server/ComponentSizeValidator.java b/server/src/com/vaadin/server/ComponentSizeValidator.java index 27d087a2b2..07c195a1c1 100644 --- a/server/src/com/vaadin/server/ComponentSizeValidator.java +++ b/server/src/com/vaadin/server/ComponentSizeValidator.java @@ -16,8 +16,8 @@ package com.vaadin.server; import java.io.PrintStream; -import java.io.PrintWriter; import java.io.Serializable; +import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; @@ -40,6 +40,7 @@ import com.vaadin.ui.GridLayout.Area; import com.vaadin.ui.Layout; import com.vaadin.ui.Panel; import com.vaadin.ui.TabSheet; +import com.vaadin.ui.UI; import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.Window; @@ -190,14 +191,14 @@ public class ComponentSizeValidator implements Serializable { subErrors.add(error); } - public void reportErrors(PrintWriter clientJSON, + public void reportErrors(StringBuilder clientJSON, PrintStream serverErrorStream) { - clientJSON.write("{"); + clientJSON.append("{"); Component parent = component.getParent(); String paintableId = component.getConnectorId(); - clientJSON.print("id:\"" + paintableId + "\""); + clientJSON.append("\"id\":\"" + paintableId + "\""); if (invalidHeight) { Stack<ComponentInfo> attributes = null; @@ -227,7 +228,7 @@ public class ComponentSizeValidator implements Serializable { attributes = getHeightAttributes(component); } printServerError(msg, attributes, false, serverErrorStream); - clientJSON.print(",\"heightMsg\":\"" + msg + "\""); + clientJSON.append(",\"heightMsg\":\"" + msg + "\""); } if (invalidWidth) { Stack<ComponentInfo> attributes = null; @@ -255,25 +256,25 @@ public class ComponentSizeValidator implements Serializable { msg = "A component with relative width needs a parent with defined width."; attributes = getWidthAttributes(component); } - clientJSON.print(",\"widthMsg\":\"" + msg + "\""); + clientJSON.append(",\"widthMsg\":\"" + msg + "\""); printServerError(msg, attributes, true, serverErrorStream); } if (subErrors.size() > 0) { serverErrorStream.println("Sub errors >>"); - clientJSON.write(", \"subErrors\" : ["); + clientJSON.append(", \"subErrors\" : ["); boolean first = true; for (InvalidLayout subError : subErrors) { if (!first) { - clientJSON.print(","); + clientJSON.append(","); } else { first = false; } subError.reportErrors(clientJSON, serverErrorStream); } - clientJSON.write("]"); + clientJSON.append("]"); serverErrorStream.println("<< Sub erros"); } - clientJSON.write("}"); + clientJSON.append("}"); } } @@ -673,4 +674,31 @@ public class ComponentSizeValidator implements Serializable { return Logger.getLogger(ComponentSizeValidator.class.getName()); } + /** + * Validates the layout and returns a collection of errors + * + * @since 7.1 + * @param ui + * The UI to validate + * @return A collection of errors. An empty collection if there are no + * errors. + */ + public static List<InvalidLayout> validateLayouts(UI ui) { + List<InvalidLayout> invalidRelativeSizes = ComponentSizeValidator + .validateComponentRelativeSizes(ui.getContent(), + new ArrayList<ComponentSizeValidator.InvalidLayout>(), + null); + + // Also check any existing subwindows + if (ui.getWindows() != null) { + for (Window subWindow : ui.getWindows()) { + invalidRelativeSizes = ComponentSizeValidator + .validateComponentRelativeSizes(subWindow.getContent(), + invalidRelativeSizes, null); + } + } + return invalidRelativeSizes; + + } + } diff --git a/server/src/com/vaadin/server/ConnectorResourceHandler.java b/server/src/com/vaadin/server/ConnectorResourceHandler.java index 00d82988d3..3f3f41a179 100644 --- a/server/src/com/vaadin/server/ConnectorResourceHandler.java +++ b/server/src/com/vaadin/server/ConnectorResourceHandler.java @@ -77,8 +77,8 @@ public class ConnectorResourceHandler implements RequestHandler { session.unlock(); } - Map<Class<?>, CurrentInstance> oldThreadLocals = CurrentInstance - .setThreadLocals(ui); + Map<Class<?>, CurrentInstance> oldInstances = CurrentInstance + .setCurrent(ui); try { if (!connector.handleConnectorRequest(request, response, key)) { return error(request, response, connector.getClass() @@ -88,7 +88,7 @@ public class ConnectorResourceHandler implements RequestHandler { + ") did not handle connector request for " + key); } } finally { - CurrentInstance.restoreThreadLocals(oldThreadLocals); + CurrentInstance.restoreInstances(oldInstances); } return true; diff --git a/server/src/com/vaadin/server/Constants.java b/server/src/com/vaadin/server/Constants.java index f8d8105286..2c041e3cf8 100644 --- a/server/src/com/vaadin/server/Constants.java +++ b/server/src/com/vaadin/server/Constants.java @@ -65,11 +65,11 @@ public interface Constants { + " Widgetset version: %s\n" + "================================================================="; - static final String REQUIRED_ATMOSPHERE_VERSION = "1.0.12"; + static final String REQUIRED_ATMOSPHERE_VERSION = "1.0.13"; static final String INVALID_ATMOSPHERE_VERSION_WARNING = "\n" + "=================================================================\n" - + "Vaadin depends on Atomsphere {0} but version {1} was found.\n" + + "Vaadin depends on Atmosphere {0} but version {1} was found.\n" + "This might cause compatibility problems if push is used.\n" + "================================================================="; @@ -106,7 +106,8 @@ public interface Constants { + "\" 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)."; + + " (Note that your debugger might call toString() and trigger this message)." + + " To find out who is calling toString(), enable FINE level logging."; static final String WARNING_UNKNOWN_LEGACY_PROPERTY_TOSTRING_VALUE = "Unknown value '{0}' for parameter " + Constants.SERVLET_PARAMETER_LEGACY_PROPERTY_TOSTRING diff --git a/server/src/com/vaadin/server/DefaultDeploymentConfiguration.java b/server/src/com/vaadin/server/DefaultDeploymentConfiguration.java index 80c3644d77..a55c3231f3 100644 --- a/server/src/com/vaadin/server/DefaultDeploymentConfiguration.java +++ b/server/src/com/vaadin/server/DefaultDeploymentConfiguration.java @@ -30,6 +30,27 @@ import com.vaadin.shared.communication.PushMode; * @since 7.0.0 */ public class DefaultDeploymentConfiguration implements DeploymentConfiguration { + /** + * Default value for {@link #getResourceCacheTime()} = {@value} . + */ + public static final int DEFAULT_RESOURCE_CACHE_TIME = 3600; + + /** + * Default value for {@link #getHeartbeatInterval()} = {@value} . + */ + public static final int DEFAULT_HEARTBEAT_INTERVAL = 300; + + /** + * Default value for {@link #isCloseIdleSessions()} = {@value} . + */ + public static final boolean DEFAULT_CLOSE_IDLE_SESSIONS = false; + + /** + * Default value for {@link #getLegacyPropertyToStringMode()} = + * {@link LegacyProperyToStringMode#WARNING}. + */ + public static final LegacyProperyToStringMode DEFAULT_LEGACY_PROPERTY_TO_STRING = LegacyProperyToStringMode.WARNING; + private final Properties initParameters; private boolean productionMode; private boolean xsrfProtectionEnabled; @@ -66,20 +87,17 @@ public class DefaultDeploymentConfiguration implements DeploymentConfiguration { 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; + Constants.SERVLET_PARAMETER_LEGACY_PROPERTY_TOSTRING, + DEFAULT_LEGACY_PROPERTY_TO_STRING.name().toLowerCase()); + try { + legacyPropertyToStringMode = LegacyProperyToStringMode + .valueOf(param.toUpperCase()); + } catch (IllegalArgumentException e) { + getLogger().log(Level.WARNING, + Constants.WARNING_UNKNOWN_LEGACY_PROPERTY_TOSTRING_VALUE, + param); + legacyPropertyToStringMode = DEFAULT_LEGACY_PROPERTY_TO_STRING; } } @@ -250,11 +268,11 @@ public class DefaultDeploymentConfiguration implements DeploymentConfiguration { resourceCacheTime = Integer .parseInt(getApplicationOrSystemProperty( Constants.SERVLET_PARAMETER_RESOURCE_CACHE_TIME, - "3600")); + Integer.toString(DEFAULT_RESOURCE_CACHE_TIME))); } catch (NumberFormatException e) { getLogger().warning( Constants.WARNING_RESOURCE_CACHING_TIME_NOT_NUMERIC); - resourceCacheTime = 3600; + resourceCacheTime = DEFAULT_RESOURCE_CACHE_TIME; } } @@ -263,18 +281,18 @@ public class DefaultDeploymentConfiguration implements DeploymentConfiguration { heartbeatInterval = Integer .parseInt(getApplicationOrSystemProperty( Constants.SERVLET_PARAMETER_HEARTBEAT_INTERVAL, - "300")); + Integer.toString(DEFAULT_HEARTBEAT_INTERVAL))); } catch (NumberFormatException e) { getLogger().warning( Constants.WARNING_HEARTBEAT_INTERVAL_NOT_NUMERIC); - heartbeatInterval = 300; + heartbeatInterval = DEFAULT_HEARTBEAT_INTERVAL; } } private void checkCloseIdleSessions() { closeIdleSessions = getApplicationOrSystemProperty( - Constants.SERVLET_PARAMETER_CLOSE_IDLE_SESSIONS, "false") - .equals("true"); + Constants.SERVLET_PARAMETER_CLOSE_IDLE_SESSIONS, + Boolean.toString(DEFAULT_CLOSE_IDLE_SESSIONS)).equals("true"); } private void checkPushMode() { diff --git a/server/src/com/vaadin/server/DragAndDropService.java b/server/src/com/vaadin/server/DragAndDropService.java index a83e83ef7f..cc571853fe 100644 --- a/server/src/com/vaadin/server/DragAndDropService.java +++ b/server/src/com/vaadin/server/DragAndDropService.java @@ -375,4 +375,14 @@ public class DragAndDropService implements VariableOwner, ClientConnector { @Override public void removeDetachListener(DetachListener listener) { } + + /* + * (non-Javadoc) + * + * @see com.vaadin.server.ClientConnector#isAttached() + */ + @Override + public boolean isAttached() { + return true; + } } diff --git a/server/src/com/vaadin/server/GlobalResourceHandler.java b/server/src/com/vaadin/server/GlobalResourceHandler.java index d411b286d0..4235d85024 100644 --- a/server/src/com/vaadin/server/GlobalResourceHandler.java +++ b/server/src/com/vaadin/server/GlobalResourceHandler.java @@ -87,14 +87,14 @@ public class GlobalResourceHandler implements RequestHandler { + " is not a valid global resource path"); } session.lock(); - Map<Class<?>, CurrentInstance> oldThreadLocals = null; + Map<Class<?>, CurrentInstance> oldInstances = 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); + oldInstances = CurrentInstance.setCurrent(ui); ConnectorResource resource; if (LEGACY_TYPE.equals(type)) { resource = legacyResources.get(key); @@ -115,8 +115,8 @@ public class GlobalResourceHandler implements RequestHandler { } } finally { session.unlock(); - if (oldThreadLocals != null) { - CurrentInstance.restoreThreadLocals(oldThreadLocals); + if (oldInstances != null) { + CurrentInstance.restoreInstances(oldInstances); } } diff --git a/server/src/com/vaadin/server/JsonPaintTarget.java b/server/src/com/vaadin/server/JsonPaintTarget.java index ca70391f64..cd09b2a44b 100644 --- a/server/src/com/vaadin/server/JsonPaintTarget.java +++ b/server/src/com/vaadin/server/JsonPaintTarget.java @@ -388,10 +388,6 @@ public class JsonPaintTarget implements PaintTarget { getUsedResources().add("layouts/" + value + ".html"); } - if (name.equals("locale")) { - manager.requireLocale(value); - } - } @Override diff --git a/server/src/com/vaadin/server/LegacyApplication.java b/server/src/com/vaadin/server/LegacyApplication.java index 54550ce134..44649067c5 100644 --- a/server/src/com/vaadin/server/LegacyApplication.java +++ b/server/src/com/vaadin/server/LegacyApplication.java @@ -64,7 +64,7 @@ public abstract class LegacyApplication implements ErrorHandler { if (this.mainWindow != null) { throw new IllegalStateException("mainWindow has already been set"); } - if (mainWindow.getSession() != null) { + if (mainWindow.isAttached()) { throw new IllegalStateException( "mainWindow is attached to another application"); } diff --git a/server/src/com/vaadin/server/LegacyCommunicationManager.java b/server/src/com/vaadin/server/LegacyCommunicationManager.java index c0194db243..ad662cf6df 100644 --- a/server/src/com/vaadin/server/LegacyCommunicationManager.java +++ b/server/src/com/vaadin/server/LegacyCommunicationManager.java @@ -16,18 +16,12 @@ 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; @@ -37,7 +31,6 @@ 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; @@ -69,9 +62,6 @@ public class LegacyCommunicationManager implements Serializable { */ private final VaadinSession session; - // TODO Refactor to UI shared state (#11378) - private List<String> locales; - // TODO Move to VaadinSession (#11409) private DragAndDropService dragAndDropService; @@ -88,7 +78,6 @@ public class LegacyCommunicationManager implements Serializable { */ public LegacyCommunicationManager(VaadinSession session) { this.session = session; - requireLocale(session.getLocale().toString()); } protected VaadinSession getSession() { @@ -313,52 +302,6 @@ public class LegacyCommunicationManager implements Serializable { } /** - * 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 @@ -486,10 +429,6 @@ public class LegacyCommunicationManager implements Serializable { getClientCache(ui).clear(); ui.getConnectorTracker().markAllConnectorsDirty(); ui.getConnectorTracker().markAllClientSidesUninitialized(); - - // Reset sent locales - resetLocales(); - requireLocale(session.getLocale().toString()); } private static final Logger getLogger() { diff --git a/server/src/com/vaadin/server/LocaleService.java b/server/src/com/vaadin/server/LocaleService.java new file mode 100644 index 0000000000..347c4da5c6 --- /dev/null +++ b/server/src/com/vaadin/server/LocaleService.java @@ -0,0 +1,211 @@ +/* + * 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.text.DateFormat; +import java.text.DateFormatSymbols; +import java.text.SimpleDateFormat; +import java.util.GregorianCalendar; +import java.util.Locale; +import java.util.logging.Logger; + +import com.vaadin.shared.ui.ui.UIState.LocaleData; +import com.vaadin.shared.ui.ui.UIState.LocaleServiceState; +import com.vaadin.ui.UI; + +/** + * Server side service which handles locale and the transmission of locale date + * to the client side LocaleService. + * + * @since 7.1 + * @author Vaadin Ltd + */ +public class LocaleService { + + private UI ui; + + private LocaleServiceState state; + + /** + * Creates a LocaleService bound to the given UI + * + * @since 7.1 + * @param ui + * The UI which owns the LocaleService + */ + public LocaleService(UI ui, LocaleServiceState state) { + this.ui = ui; + this.state = state; + } + + /** + * Retrieves the UI this service is bound to + * + * @since 7.1 + * @return the UI for this service + */ + public UI getUI() { + return ui; + } + + /** + * Adds 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. + * + * @param locale + * The locale which is required on the client side + */ + public void addLocale(Locale locale) { + for (LocaleData data : getState(false).localeData) { + if (data.name.equals(locale.toString())) { + // Already there + return; + } + } + + getState(true).localeData.add(createLocaleData(locale)); + } + + /** + * Returns the state for this service + * <p> + * The state is transmitted inside the UI state rather than as an individual + * entity. + * </p> + * + * @since 7.1 + * @param markAsDirty + * true to mark the state as dirty + * @return a LocaleServiceState object that can be read in any case and + * modified if markAsDirty is true + */ + private LocaleServiceState getState(boolean markAsDirty) { + if (markAsDirty) { + getUI().markAsDirty(); + } + + return state; + } + + /** + * Creates a LocaleData instance for transportation to the client + * + * @since 7.1 + * @param locale + * The locale for which to create a LocaleData object + * @return A LocaleData object with information about the given locale + */ + protected LocaleData createLocaleData(Locale locale) { + LocaleData localeData = new LocaleData(); + localeData.name = locale.toString(); + + final DateFormatSymbols dfs = new DateFormatSymbols(locale); + localeData.shortMonthNames = dfs.getShortMonths(); + localeData.monthNames = dfs.getMonths(); + // Client expects 0 based indexing, DateFormatSymbols use 1 based + localeData.shortDayNames = new String[7]; + localeData.dayNames = new String[7]; + String[] sDayNames = dfs.getShortWeekdays(); + String[] lDayNames = dfs.getWeekdays(); + for (int i = 0; i < 7; i++) { + localeData.shortDayNames[i] = sDayNames[i + 1]; + localeData.dayNames[i] = lDayNames[i + 1]; + } + + /* + * First day of week (0 = sunday, 1 = monday) + */ + final java.util.Calendar cal = new GregorianCalendar(locale); + localeData.firstDayOfWeek = cal.getFirstDayOfWeek() - 1; + + /* + * Date formatting (MM/DD/YYYY etc.) + */ + + DateFormat dateFormat = DateFormat.getDateTimeInstance( + DateFormat.SHORT, DateFormat.SHORT, locale); + if (!(dateFormat instanceof SimpleDateFormat)) { + getLogger().warning( + "Unable to get default date pattern for locale " + + locale.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); + } + + localeData.dateFormat = 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 + "\","); + localeData.twelveHourClock = twelve_hour_clock; + localeData.hourMinuteDelimiter = hour_min_delimiter; + if (twelve_hour_clock) { + final String[] ampm = dfs.getAmPmStrings(); + localeData.am = ampm[0]; + localeData.pm = ampm[1]; + } + + return localeData; + } + + private static Logger getLogger() { + return Logger.getLogger(LocaleService.class.getName()); + } + +} diff --git a/server/src/com/vaadin/server/Page.java b/server/src/com/vaadin/server/Page.java index d4c16fe7f7..11553527e0 100644 --- a/server/src/com/vaadin/server/Page.java +++ b/server/src/com/vaadin/server/Page.java @@ -21,11 +21,10 @@ 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.LinkedHashSet; import java.util.LinkedList; import java.util.List; -import java.util.Map; import com.vaadin.event.EventRouter; import com.vaadin.shared.ui.BorderStyle; @@ -307,6 +306,61 @@ public class Page implements Serializable { } } + private static interface InjectedStyle { + public void paint(int id, PaintTarget target) throws PaintException; + } + + private static class InjectedStyleString implements InjectedStyle { + + private String css; + + public InjectedStyleString(String css) { + this.css = css; + } + + @Override + public void paint(int id, PaintTarget target) throws PaintException { + target.startTag("css-string"); + target.addAttribute("id", id); + target.addText(css); + target.endTag("css-string"); + } + } + + private static class InjectedStyleResource implements InjectedStyle { + + private final Resource resource; + + public InjectedStyleResource(Resource resource) { + this.resource = resource; + } + + @Override + public void paint(int id, PaintTarget target) throws PaintException { + target.startTag("css-resource"); + target.addAttribute("id", id); + target.addAttribute("url", resource); + target.endTag("css-resource"); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } else if (obj instanceof InjectedStyleResource) { + InjectedStyleResource that = (InjectedStyleResource) obj; + return resource.equals(that.resource); + } else { + return false; + } + } + + @Override + public int hashCode() { + return resource.hashCode(); + } + } + /** * Contains dynamically injected styles injected in the HTML document at * runtime. @@ -315,16 +369,9 @@ public class Page implements Serializable { */ 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>(); + private LinkedHashSet<InjectedStyle> injectedStyles = new LinkedHashSet<InjectedStyle>(); - // 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 LinkedHashSet<InjectedStyle> pendingInjections = new LinkedHashSet<InjectedStyle>(); private final UI ui; @@ -344,7 +391,7 @@ public class Page implements Serializable { "Cannot inject null CSS string"); } - stringInjections.put(injectionCounter++, css); + pendingInjections.add(new InjectedStyleString(css)); ui.markAsDirty(); } @@ -360,43 +407,33 @@ public class Page implements Serializable { "Cannot inject null resource"); } - resourceInjections.put(injectionCounter++, resource); - ui.markAsDirty(); + InjectedStyleResource injection = new InjectedStyleResource( + resource); + if (!injectedStyles.contains(injection) + && pendingInjections.add(injection)) { + ui.markAsDirty(); + } } private void paint(PaintTarget target) throws PaintException { // If full repaint repaint all injections if (target.isFullRepaint()) { - nextInjectionPosition = 0; + injectedStyles.addAll(pendingInjections); + pendingInjections = injectedStyles; + injectedStyles = new LinkedHashSet<InjectedStyle>(); } - if (injectionCounter > nextInjectionPosition) { + if (!pendingInjections.isEmpty()) { 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++; + for (InjectedStyle pending : pendingInjections) { + int id = injectedStyles.size(); + pending.paint(id, target); + injectedStyles.add(pending); } + pendingInjections.clear(); target.endTag("css-injections"); } diff --git a/server/src/com/vaadin/server/RequestHandler.java b/server/src/com/vaadin/server/RequestHandler.java index 873752c5f2..097a3e034b 100644 --- a/server/src/com/vaadin/server/RequestHandler.java +++ b/server/src/com/vaadin/server/RequestHandler.java @@ -37,7 +37,8 @@ public interface RequestHandler extends Serializable { * 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)}. + * {@link VaadinSession#accessSynchronously(Runnable)} or + * {@link UI#accessSynchronously(Runnable)}. * </p> * * @param session diff --git a/server/src/com/vaadin/server/UIProvider.java b/server/src/com/vaadin/server/UIProvider.java index 0305b907e6..3e7c85aea9 100644 --- a/server/src/com/vaadin/server/UIProvider.java +++ b/server/src/com/vaadin/server/UIProvider.java @@ -25,6 +25,7 @@ import com.vaadin.annotations.Theme; import com.vaadin.annotations.Title; import com.vaadin.annotations.Widgetset; import com.vaadin.shared.communication.PushMode; +import com.vaadin.shared.ui.ui.Transport; import com.vaadin.ui.UI; public abstract class UIProvider implements Serializable { @@ -174,4 +175,27 @@ public abstract class UIProvider implements Serializable { return push.value(); } } + + /** + * Finds the {@link Transport} to use for a specific UI. If no transport is + * defined, <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 transport type to use, or <code>null</code> if the default + * transport type should be used + */ + public Transport getPushTransport(UICreateEvent event) { + Push push = getAnnotationFor(event.getUIClass(), Push.class); + if (push == null) { + return null; + } else { + return push.transport(); + } + } + } diff --git a/server/src/com/vaadin/server/VaadinPortlet.java b/server/src/com/vaadin/server/VaadinPortlet.java index 327ce78a6c..d86e5e6507 100644 --- a/server/src/com/vaadin/server/VaadinPortlet.java +++ b/server/src/com/vaadin/server/VaadinPortlet.java @@ -287,7 +287,6 @@ public class VaadinPortlet extends GenericPortlet implements Constants, @Override public void init(PortletConfig config) throws PortletException { CurrentInstance.clearAll(); - setCurrent(this); super.init(config); Properties initParameters = new Properties(); @@ -407,7 +406,6 @@ public class VaadinPortlet extends GenericPortlet implements Constants, PortletResponse response) throws PortletException, IOException { CurrentInstance.clearAll(); - setCurrent(this); try { getService().handleRequest(createVaadinRequest(request), createVaadinResponse(response)); @@ -495,36 +493,23 @@ public class VaadinPortlet extends GenericPortlet implements Constants, * portlet is defined (see {@link InheritableThreadLocal}). In other cases, * (e.g. from background threads started in some other way), the current * portlet is not automatically defined. + * <p> + * The current portlet is derived from the current service using + * {@link VaadinService#getCurrent()} * * @return the current vaadin portlet instance if available, otherwise * <code>null</code> * - * @see #setCurrent(VaadinPortlet) - * * @since 7.0 */ public static VaadinPortlet getCurrent() { - return CurrentInstance.get(VaadinPortlet.class); - } - - /** - * Sets the current Vaadin portlet. This method is used by the framework to - * set the current portlet whenever a new request is processed and it is - * cleared when the request has been processed. - * <p> - * The application developer can also use this method to define the current - * portlet outside the normal request handling, e.g. when initiating custom - * background threads. - * </p> - * - * @param portlet - * the Vaadin portlet to register as the current portlet - * - * @see #getCurrent() - * @see InheritableThreadLocal - */ - public static void setCurrent(VaadinPortlet portlet) { - CurrentInstance.setInheritable(VaadinPortlet.class, portlet); + VaadinService vaadinService = CurrentInstance.get(VaadinService.class); + if (vaadinService instanceof VaadinPortletService) { + VaadinPortletService vps = (VaadinPortletService) vaadinService; + return vps.getPortlet(); + } else { + return null; + } } } diff --git a/server/src/com/vaadin/server/VaadinService.java b/server/src/com/vaadin/server/VaadinService.java index af0c280c19..a040c72175 100644 --- a/server/src/com/vaadin/server/VaadinService.java +++ b/server/src/com/vaadin/server/VaadinService.java @@ -33,6 +33,9 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; @@ -47,6 +50,7 @@ import org.json.JSONObject; import com.vaadin.annotations.PreserveOnRefresh; import com.vaadin.event.EventRouter; +import com.vaadin.server.VaadinSession.FutureAccess; import com.vaadin.server.communication.FileUploadHandler; import com.vaadin.server.communication.HeartbeatHandler; import com.vaadin.server.communication.PublishedFileHandler; @@ -110,6 +114,11 @@ public abstract class VaadinService implements Serializable { private boolean pushWarningEmitted = false; /** + * Has {@link #init()} been run? + */ + private boolean initialized = false; + + /** * Creates a new vaadin service based on a deployment configuration * * @param deploymentConfiguration @@ -148,6 +157,8 @@ public abstract class VaadinService implements Serializable { List<RequestHandler> handlers = createRequestHandlers(); Collections.reverse(handlers); requestHandlers = Collections.unmodifiableCollection(handlers); + + initialized = true; } /** @@ -407,12 +418,12 @@ public abstract class VaadinService implements Serializable { */ public void fireSessionDestroy(VaadinSession vaadinSession) { final VaadinSession session = vaadinSession; - session.access(new Runnable() { + session.accessSynchronously(new Runnable() { @Override public void run() { ArrayList<UI> uis = new ArrayList<UI>(session.getUIs()); for (final UI ui : uis) { - ui.access(new Runnable() { + ui.accessSynchronously(new Runnable() { @Override public void run() { /* @@ -1087,7 +1098,7 @@ public abstract class VaadinService implements Serializable { private void removeClosedUIs(final VaadinSession session) { ArrayList<UI> uis = new ArrayList<UI>(session.getUIs()); for (final UI ui : uis) { - ui.access(new Runnable() { + ui.accessSynchronously(new Runnable() { @Override public void run() { if (ui.isClosing()) { @@ -1224,6 +1235,10 @@ public abstract class VaadinService implements Serializable { * The response */ public void requestStart(VaadinRequest request, VaadinResponse response) { + if (!initialized) { + throw new IllegalStateException( + "Can not process requests before init() has been called"); + } setCurrentInstances(request, response); request.setAttribute(REQUEST_START_TIME_ATTRIBUTE, System.nanoTime()); } @@ -1245,7 +1260,7 @@ public abstract class VaadinService implements Serializable { if (session != null) { final VaadinSession finalSession = session; - session.access(new Runnable() { + session.accessSynchronously(new Runnable() { @Override public void run() { cleanupSession(finalSession); @@ -1254,7 +1269,7 @@ public abstract class VaadinService implements Serializable { final long duration = (System.nanoTime() - (Long) request .getAttribute(REQUEST_START_TIME_ATTRIBUTE)) / 1000000; - session.access(new Runnable() { + session.accessSynchronously(new Runnable() { @Override public void run() { finalSession.setLastRequestDuration(duration); @@ -1542,8 +1557,9 @@ public abstract class VaadinService implements Serializable { /** * 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. + * internally used by {@link VaadinSession#accessSynchronously(Runnable)} + * and {@link UI#accessSynchronously(Runnable)} to help avoid causing + * deadlocks. * * @since 7.1 * @param session @@ -1597,4 +1613,88 @@ public abstract class VaadinService implements Serializable { return true; } + /** + * Implementation for {@link VaadinSession#access(Runnable)}. This method is + * implemented here instead of in {@link VaadinSession} to enable overriding + * the implementation without using a custom subclass of VaadinSession. + * + * @since 7.1 + * @see VaadinSession#access(Runnable) + * + * @param session + * the vaadin session to access + * @param runnable + * the runnable to run with the session locked + * + * @return a future that can be used to check for task completion and to + * cancel the task + */ + public Future<Void> accessSession(VaadinSession session, Runnable runnable) { + FutureAccess future = new FutureAccess(session, runnable); + session.getPendingAccessQueue().add(future); + + /* + * If no thread is currently holding the lock, pending changes for UIs + * with automatic push would not be processed and pushed until the next + * time there is a request or someone does an explicit push call. + * + * To remedy this, we try to get the lock at this point. If the lock is + * currently held by another thread, we just back out as the queue will + * get purged once it is released. If the lock is held by the current + * thread, we just release it knowing that the queue gets purged once + * the lock is ultimately released. If the lock is not held by any + * thread and we acquire it, we just release it again to purge the queue + * right away. + */ + try { + // tryLock() would be shorter, but it does not guarantee fairness + if (session.getLockInstance().tryLock(0, TimeUnit.SECONDS)) { + // unlock triggers runPendingAccessTasks + session.unlock(); + } + } catch (InterruptedException e) { + // Just ignore + } + + return future; + } + + /** + * Purges the queue of pending access invocations enqueued with + * {@link VaadinSession#access(Runnable)}. + * <p> + * This method is automatically run by the framework at appropriate + * situations and is not intended to be used by application developers. + * + * @param session + * the vaadin session to purge the queue for + * @since 7.1 + */ + public void runPendingAccessTasks(VaadinSession session) { + assert session.hasLock(); + + if (session.getPendingAccessQueue().isEmpty()) { + return; + } + + Map<Class<?>, CurrentInstance> oldInstances = CurrentInstance + .getInstances(false); + + FutureAccess pendingAccess; + try { + while ((pendingAccess = session.getPendingAccessQueue().poll()) != null) { + if (!pendingAccess.isCancelled()) { + CurrentInstance.clearAll(); + CurrentInstance.restoreInstances(pendingAccess + .getCurrentInstances()); + CurrentInstance.setCurrent(session); + pendingAccess.run(); + } + } + } finally { + CurrentInstance.clearAll(); + CurrentInstance.restoreInstances(oldInstances); + } + } + } diff --git a/server/src/com/vaadin/server/VaadinServlet.java b/server/src/com/vaadin/server/VaadinServlet.java index de074941c1..05e3335c00 100644 --- a/server/src/com/vaadin/server/VaadinServlet.java +++ b/server/src/com/vaadin/server/VaadinServlet.java @@ -21,6 +21,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; +import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; @@ -38,6 +39,8 @@ import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import com.vaadin.annotations.VaadinServletConfiguration; +import com.vaadin.annotations.VaadinServletConfiguration.InitParameterName; import com.vaadin.sass.internal.ScssStylesheet; import com.vaadin.server.communication.ServletUIInitHandler; import com.vaadin.shared.JsonConstants; @@ -63,10 +66,11 @@ public class VaadinServlet extends HttpServlet implements Constants { public void init(javax.servlet.ServletConfig servletConfig) throws ServletException { CurrentInstance.clearAll(); - setCurrent(this); super.init(servletConfig); Properties initParameters = new Properties(); + readConfigurationAnnotation(initParameters); + // Read default parameters from server.xml final ServletContext context = servletConfig.getServletContext(); for (final Enumeration<String> e = context.getInitParameterNames(); e @@ -97,6 +101,39 @@ public class VaadinServlet extends HttpServlet implements Constants { CurrentInstance.clearAll(); } + private void readConfigurationAnnotation(Properties initParameters) + throws ServletException { + VaadinServletConfiguration configAnnotation = UIProvider + .getAnnotationFor(getClass(), VaadinServletConfiguration.class); + if (configAnnotation != null) { + Method[] methods = VaadinServletConfiguration.class + .getDeclaredMethods(); + for (Method method : methods) { + InitParameterName name = method + .getAnnotation(InitParameterName.class); + assert name != null : "All methods declared in VaadinServletConfiguration should have a @InitParameterName annotation"; + + try { + Object value = method.invoke(configAnnotation); + + String stringValue; + if (value instanceof Class<?>) { + stringValue = ((Class<?>) value).getName(); + } else { + stringValue = value.toString(); + } + + initParameters.setProperty(name.value(), stringValue); + } catch (Exception e) { + // This should never happen + throw new ServletException( + "Could not read @VaadinServletConfiguration value " + + method.getName(), e); + } + } + } + } + protected void servletInitialized() throws ServletException { // Empty by default } @@ -108,36 +145,23 @@ public class VaadinServlet extends HttpServlet implements Constants { * servlet is defined (see {@link InheritableThreadLocal}). In other cases, * (e.g. from background threads started in some other way), the current * servlet is not automatically defined. + * <p> + * The current servlet is derived from the current service using + * {@link VaadinService#getCurrent()} * * @return the current Vaadin servlet instance if available, otherwise * <code>null</code> * - * @see #setCurrent(VaadinServlet) - * * @since 7.0 */ public static VaadinServlet getCurrent() { - return CurrentInstance.get(VaadinServlet.class); - } - - /** - * Sets the current Vaadin servlet. This method is used by the framework to - * set the current servlet whenever a new request is processed and it is - * cleared when the request has been processed. - * <p> - * The application developer can also use this method to define the current - * servlet outside the normal request handling, e.g. when initiating custom - * background threads. - * </p> - * - * @param servlet - * the Vaadin servlet to register as the current servlet - * - * @see #getCurrent() - * @see InheritableThreadLocal - */ - public static void setCurrent(VaadinServlet servlet) { - CurrentInstance.setInheritable(VaadinServlet.class, servlet); + VaadinService vaadinService = CurrentInstance.get(VaadinService.class); + if (vaadinService instanceof VaadinServletService) { + VaadinServletService vss = (VaadinServletService) vaadinService; + return vss.getServlet(); + } else { + return null; + } } protected DeploymentConfiguration createDeploymentConfiguration( @@ -179,7 +203,6 @@ public class VaadinServlet extends HttpServlet implements Constants { return; } CurrentInstance.clearAll(); - setCurrent(this); VaadinServletRequest vaadinRequest = createVaadinRequest(request); VaadinServletResponse vaadinResponse = createVaadinResponse(response); @@ -188,8 +211,14 @@ public class VaadinServlet extends HttpServlet implements Constants { } if (isStaticResourceRequest(request)) { - serveStaticResources(request, response); - return; + // Define current servlet and service, but no request and response + getService().setCurrentInstances(null, null); + try { + serveStaticResources(request, response); + return; + } finally { + CurrentInstance.clearAll(); + } } try { getService().handleRequest(vaadinRequest, vaadinResponse); diff --git a/server/src/com/vaadin/server/VaadinSession.java b/server/src/com/vaadin/server/VaadinSession.java index 317ea6cf7b..e0a5b51baa 100644 --- a/server/src/com/vaadin/server/VaadinSession.java +++ b/server/src/com/vaadin/server/VaadinSession.java @@ -25,7 +25,12 @@ import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Queue; import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Logger; @@ -63,6 +68,68 @@ import com.vaadin.util.ReflectTools; public class VaadinSession implements HttpSessionBindingListener, Serializable { /** + * Encapsulates a {@link Runnable} submitted using + * {@link VaadinSession#access(Runnable)}. This class is used internally by + * the framework and is not intended to be directly used by application + * developers. + * + * @since 7.1 + * @author Vaadin Ltd + */ + public static class FutureAccess extends FutureTask<Void> { + /** + * Snapshot of all non-inheritable current instances at the time this + * object was created. + */ + private final Map<Class<?>, CurrentInstance> instances = CurrentInstance + .getInstances(true); + private final VaadinSession session; + + /** + * Creates an instance for the given runnable + * + * @param session + * the session to which the task belongs + * + * @param runnable + * the runnable to run when this task is purged from the + * queue + */ + public FutureAccess(VaadinSession session, Runnable runnable) { + super(runnable, null); + this.session = session; + } + + @Override + public Void get() throws InterruptedException, ExecutionException { + /* + * Help the developer avoid programming patterns that cause + * deadlocks unless implemented very carefully. get(long, TimeUnit) + * does not have the same detection since a sensible timeout should + * avoid completely locking up the application. + * + * Even though no deadlock could occur after the runnable has been + * run, the check is always done as the deterministic behavior makes + * it easier to detect potential problems. + */ + VaadinService.verifyNoOtherSessionLocked(session); + return super.get(); + } + + /** + * Gets the current instance values that should be used when running + * this task. + * + * @see CurrentInstance#restoreInstances(Map) + * + * @return a map of current instances. + */ + public Map<Class<?>, CurrentInstance> getCurrentInstances() { + return instances; + } + } + + /** * The name of the parameter that is by default used in e.g. web.xml to * define the name of the default {@link UI} class. */ @@ -130,6 +197,13 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable { private transient Lock lock; + /* + * Pending tasks can't be serialized and the queue should be empty when the + * session is serialized as long as it doesn't happen while some other + * thread has the lock. + */ + private transient final ConcurrentLinkedQueue<FutureAccess> pendingAccessQueue = new ConcurrentLinkedQueue<FutureAccess>(); + /** * Create a new service session tied to a Vaadin service * @@ -189,9 +263,9 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable { /** * Get the web browser associated with this session. * - * @return - * @deprecated As of 7.0. Will likely change or be removed in a future - * version + * @return the web browser object + * + * @deprecated As of 7.0, use {@link Page#getWebBrowser()} instead. */ @Deprecated public WebBrowser getBrowser() { @@ -820,11 +894,15 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable { public void unlock() { assert hasLock(); try { + /* + * Run pending tasks and push if the reentrant lock will actually be + * released by this unlock() invocation. + */ if (((ReentrantLock) getLockInstance()).getHoldCount() == 1) { - // Only push if the reentrant lock will actually be released by - // this unlock() invocation. + getService().runPendingAccessTasks(this); + for (UI ui : getUIs()) { - if (ui.getPushMode() == PushMode.AUTOMATIC) { + if (ui.getPushConfiguration().getPushMode() == PushMode.AUTOMATIC) { ui.push(); } } @@ -1063,23 +1141,26 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable { } /** - * Provides exclusive access to this session from outside a request handling - * thread. + * Locks this session and runs the provided Runnable right away. * <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. + * It is generally recommended to use {@link #access(Runnable)} instead of + * this method for accessing a session from a different thread as + * {@link #access(Runnable)} can be used while holding the lock of another + * session. To avoid causing deadlocks, this methods throws an exception if + * it is detected than another session is also locked by the current thread. * </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. + * This method behaves differently than {@link #access(Runnable)} in some + * situations: + * <ul> + * <li>If the current thread is currently holding the lock of this session, + * {@link #accessSynchronously(Runnable)} runs the task right away whereas + * {@link #access(Runnable)} defers the task to a later point in time.</li> + * <li>If some other thread is currently holding the lock for this session, + * {@link #accessSynchronously(Runnable)} blocks while waiting for the lock + * to be available whereas {@link #access(Runnable)} defers the task to a + * later point in time.</li> + * </ul> * </p> * * @param runnable @@ -1088,35 +1169,87 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable { * @throws IllegalStateException * if the current thread holds the lock for another session * + * @since 7.1 * * @see #lock() * @see #getCurrent() - * @see UI#access(Runnable) + * @see #access(Runnable) + * @see UI#accessSynchronously(Runnable) */ - public void access(Runnable runnable) { + public void accessSynchronously(Runnable runnable) { VaadinService.verifyNoOtherSessionLocked(this); Map<Class<?>, CurrentInstance> old = null; lock(); try { - old = CurrentInstance.setThreadLocals(this); + old = CurrentInstance.setCurrent(this); runnable.run(); } finally { unlock(); if (old != null) { - CurrentInstance.restoreThreadLocals(old); + CurrentInstance.restoreInstances(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. + * 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. If this session is not locked, the lock + * will be acquired and the runnable is run right away. If this session is + * currently locked, the runnable will be run before that lock is released. + * </p> + * <p> + * RPC handlers for components inside this session do not need to use this + * method as the session is automatically locked by the framework during RPC + * handling. + * </p> + * <p> + * Please note that the runnable might be invoked on a different thread or + * later on the current thread, which means that custom thread locals might + * not have the expected values when the runnable is executed. Inheritable + * values in {@link CurrentInstance} will have the same values as when this + * method was invoked. {@link VaadinSession#getCurrent()} and + * {@link VaadinService#getCurrent()} are set according to this session + * before executing the runnable. Non-inheritable CurrentInstance values + * including {@link VaadinService#getCurrentRequest()} and + * {@link VaadinService#getCurrentResponse()} will not be defined. + * </p> + * <p> + * The returned future can be used to check for task completion and to + * cancel the task. To help avoiding deadlocks, {@link Future#get()} throws + * an exception if it is detected that the current thread holds the lock for + * some other session. + * </p> + * + * @see #lock() + * @see #getCurrent() + * @see #accessSynchronously(Runnable) + * @see UI#access(Runnable) + * + * @since 7.1 + * + * @param runnable + * the runnable which accesses the session + * @return a future that can be used to check for task completion and to + * cancel the task */ - @Deprecated - public void runSafely(Runnable runnable) { - access(runnable); + public Future<Void> access(Runnable runnable) { + return getService().accessSession(this, runnable); + } + + /** + * Gets the queue of tasks submitted using {@link #access(Runnable)}. + * + * @since 7.1 + * + * @return the pending access queue + */ + public Queue<FutureAccess> getPendingAccessQueue() { + return pendingAccessQueue; } /** diff --git a/server/src/com/vaadin/server/communication/AtmospherePushConnection.java b/server/src/com/vaadin/server/communication/AtmospherePushConnection.java index 0bba65ff1d..9e57ccb45d 100644 --- a/server/src/com/vaadin/server/communication/AtmospherePushConnection.java +++ b/server/src/com/vaadin/server/communication/AtmospherePushConnection.java @@ -32,7 +32,7 @@ import org.atmosphere.cpr.AtmosphereResource; import org.atmosphere.cpr.AtmosphereResource.TRANSPORT; import org.json.JSONException; -import com.vaadin.shared.ApplicationConstants; +import com.vaadin.shared.communication.PushConstants; import com.vaadin.ui.UI; /** @@ -42,21 +42,22 @@ import com.vaadin.ui.UI; * @author Vaadin Ltd * @since 7.1 */ -public class AtmospherePushConnection implements Serializable, PushConnection { +public class AtmospherePushConnection implements PushConnection { /** * Represents a message that can arrive as multiple fragments. */ - protected static class FragmentedMessage { + protected static class FragmentedMessage implements Serializable { 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 '|' + // Messages are prefixed by the total message length plus a + // delimiter String length = ""; int c; while ((c = reader.read()) != -1 - && c != ApplicationConstants.WEBSOCKET_MESSAGE_DELIMITER) { + && c != PushConstants.MESSAGE_DELIMITER) { length += (char) c; } try { @@ -76,7 +77,7 @@ public class AtmospherePushConnection implements Serializable, PushConnection { * @throws IOException */ public boolean append(Reader reader) throws IOException { - char[] buffer = new char[ApplicationConstants.WEBSOCKET_BUFFER_SIZE]; + char[] buffer = new char[PushConstants.WEBSOCKET_BUFFER_SIZE]; int read; while ((read = reader.read(buffer)) != -1) { message.append(buffer, 0, read); @@ -122,7 +123,7 @@ public class AtmospherePushConnection implements Serializable, PushConnection { protected void push(boolean async) throws IOException { Writer writer = new StringWriter(); try { - new UidlWriter().write(getUI(), writer, false, false, async); + new UidlWriter().write(getUI(), writer, false, async); } catch (JSONException e) { throw new IOException("Error writing UIDL", e); } diff --git a/server/src/com/vaadin/server/communication/FileUploadHandler.java b/server/src/com/vaadin/server/communication/FileUploadHandler.java index e875a4e861..e9569d45a1 100644 --- a/server/src/com/vaadin/server/communication/FileUploadHandler.java +++ b/server/src/com/vaadin/server/communication/FileUploadHandler.java @@ -632,7 +632,7 @@ public class FileUploadHandler implements RequestHandler { private void cleanStreamVariable(VaadinSession session, final ClientConnector owner, final String variableName) { - session.access(new Runnable() { + session.accessSynchronously(new Runnable() { @Override public void run() { owner.getUI() diff --git a/server/src/com/vaadin/server/communication/LocaleWriter.java b/server/src/com/vaadin/server/communication/LocaleWriter.java deleted file mode 100644 index c05649da19..0000000000 --- a/server/src/com/vaadin/server/communication/LocaleWriter.java +++ /dev/null @@ -1,204 +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.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 index 1a3f0e946a..5ad7186c24 100644 --- a/server/src/com/vaadin/server/communication/MetadataWriter.java +++ b/server/src/com/vaadin/server/communication/MetadataWriter.java @@ -17,17 +17,11 @@ 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. @@ -54,9 +48,6 @@ public class MetadataWriter implements Serializable { * @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. @@ -64,27 +55,8 @@ public class MetadataWriter implements Serializable { * 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); - } - } - } + public void write(UI ui, Writer writer, boolean repaintAll, boolean async, + SystemMessages messages) throws IOException { writer.write("{"); @@ -92,28 +64,6 @@ public class MetadataWriter implements Serializable { 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) { diff --git a/server/src/com/vaadin/server/communication/PushConnection.java b/server/src/com/vaadin/server/communication/PushConnection.java index 4e043f565f..bb88998402 100644 --- a/server/src/com/vaadin/server/communication/PushConnection.java +++ b/server/src/com/vaadin/server/communication/PushConnection.java @@ -16,6 +16,8 @@ package com.vaadin.server.communication; +import java.io.Serializable; + import com.vaadin.ui.UI; /** @@ -25,7 +27,7 @@ import com.vaadin.ui.UI; * @author Vaadin Ltd * @since 7.1 */ -public interface PushConnection { +public interface PushConnection extends Serializable { /** * Pushes pending state changes and client RPC calls to the client. It is diff --git a/server/src/com/vaadin/server/communication/PushHandler.java b/server/src/com/vaadin/server/communication/PushHandler.java index e740db410d..7efcb8fd8c 100644 --- a/server/src/com/vaadin/server/communication/PushHandler.java +++ b/server/src/com/vaadin/server/communication/PushHandler.java @@ -84,6 +84,13 @@ public class PushHandler implements AtmosphereHandler { if (browser.isIE() && browser.getBrowserMajorVersion() == 8) { resource.padding(LONG_PADDING); } + + // Must ensure that the streaming response contains + // "Connection: close", otherwise iOS 6 will wait for the + // response to this request before sending another request to + // the same server (as it will apparently try to reuse the same + // connection) + resource.getResponse().addHeader("Connection", "close"); } String requestToken = resource.getRequest().getParameter( @@ -164,7 +171,7 @@ public class PushHandler implements AtmosphereHandler { PushEventCallback disconnectCallback = new PushEventCallback() { @Override public void run(AtmosphereResource resource, UI ui) throws IOException { - PushMode pushMode = ui.getPushMode(); + PushMode pushMode = ui.getPushConfiguration().getPushMode(); AtmospherePushConnection pushConnection = getConnectionForUI(ui); String id = resource.uuid(); @@ -331,9 +338,9 @@ public class PushHandler implements AtmosphereHandler { writer.write(event.getMessage().toString()); switch (resource.transport()) { - case SSE: case WEBSOCKET: break; + case SSE: case STREAMING: writer.flush(); break; diff --git a/server/src/com/vaadin/server/communication/PushRequestHandler.java b/server/src/com/vaadin/server/communication/PushRequestHandler.java index 8360e08af9..8d0da24896 100644 --- a/server/src/com/vaadin/server/communication/PushRequestHandler.java +++ b/server/src/com/vaadin/server/communication/PushRequestHandler.java @@ -23,6 +23,7 @@ import javax.servlet.ServletException; import org.atmosphere.client.TrackMessageSizeInterceptor; import org.atmosphere.cpr.ApplicationConfig; import org.atmosphere.cpr.AtmosphereFramework; +import org.atmosphere.cpr.AtmosphereInterceptor; import org.atmosphere.cpr.AtmosphereRequest; import org.atmosphere.cpr.AtmosphereResponse; @@ -36,7 +37,7 @@ 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; +import com.vaadin.shared.communication.PushConstants; /** * Handles requests to open a push (bidirectional) communication channel between @@ -55,15 +56,22 @@ public class PushRequestHandler implements RequestHandler, public PushRequestHandler(VaadinServletService service) throws ServiceException { - atmosphere = new AtmosphereFramework(); + atmosphere = new AtmosphereFramework() { + @Override + protected void analytics() { + // Overridden to disable version number check + } + }; pushHandler = new PushHandler(service); atmosphere.addAtmosphereHandler("/*", pushHandler); atmosphere.addInitParameter(ApplicationConfig.PROPERTY_SESSION_SUPPORT, "true"); + atmosphere.addInitParameter(ApplicationConfig.MESSAGE_DELIMITER, + String.valueOf(PushConstants.MESSAGE_DELIMITER)); final String bufferSize = String - .valueOf(ApplicationConstants.WEBSOCKET_BUFFER_SIZE); + .valueOf(PushConstants.WEBSOCKET_BUFFER_SIZE); atmosphere.addInitParameter(ApplicationConfig.WEBSOCKET_BUFFER_SIZE, bufferSize); atmosphere.addInitParameter(ApplicationConfig.WEBSOCKET_MAXTEXTSIZE, @@ -75,12 +83,14 @@ public class PushRequestHandler implements RequestHandler, 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()); + + // Ensure the client-side knows how to split the message stream + // into individual messages when using certain transports + AtmosphereInterceptor trackMessageSize = new TrackMessageSizeInterceptor(); + trackMessageSize.configure(atmosphere.getAtmosphereConfig()); + atmosphere.interceptor(trackMessageSize); } catch (ServletException e) { throw new ServiceException("Could not read atmosphere settings", e); } diff --git a/server/src/com/vaadin/server/communication/UIInitHandler.java b/server/src/com/vaadin/server/communication/UIInitHandler.java index e4b5360b49..d4b0bc709f 100644 --- a/server/src/com/vaadin/server/communication/UIInitHandler.java +++ b/server/src/com/vaadin/server/communication/UIInitHandler.java @@ -39,6 +39,7 @@ 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.Transport; import com.vaadin.shared.ui.ui.UIConstants; import com.vaadin.ui.UI; @@ -209,7 +210,12 @@ public abstract class UIInitHandler extends SynchronizedRequestHandler { pushMode = session.getService().getDeploymentConfiguration() .getPushMode(); } - ui.setPushMode(pushMode); + ui.getPushConfiguration().setPushMode(pushMode); + + Transport transport = provider.getPushTransport(event); + if (transport != null) { + ui.getPushConfiguration().setTransport(transport); + } // Set thread local here so it is available in init UI.setCurrent(ui); @@ -273,7 +279,7 @@ public abstract class UIInitHandler extends SynchronizedRequestHandler { if (session.getConfiguration().isXsrfProtectionEnabled()) { writer.write(getSecurityKeyUIDL(session)); } - new UidlWriter().write(uI, writer, true, false, false); + new UidlWriter().write(uI, writer, true, false); writer.write("}"); String initialUIDL = writer.toString(); diff --git a/server/src/com/vaadin/server/communication/UidlRequestHandler.java b/server/src/com/vaadin/server/communication/UidlRequestHandler.java index 73ff92f8bd..3564aa65b5 100644 --- a/server/src/com/vaadin/server/communication/UidlRequestHandler.java +++ b/server/src/com/vaadin/server/communication/UidlRequestHandler.java @@ -19,13 +19,11 @@ 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; @@ -39,7 +37,6 @@ 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; /** @@ -79,32 +76,16 @@ public class UidlRequestHandler extends SynchronizedRequestHandler implements 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 + // TODO PUSH repaintAll, analyzeLayouts 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 { @@ -114,8 +95,7 @@ public class UidlRequestHandler extends SynchronizedRequestHandler implements session.getCommunicationManager().repaintAll(uI); } - writeUidl(request, response, uI, stringWriter, repaintAll, - analyzeLayouts); + writeUidl(request, response, uI, stringWriter, repaintAll); } catch (JSONException e) { getLogger().log(Level.SEVERE, "Error writing JSON to response", e); // Refresh on client side @@ -164,11 +144,11 @@ public class UidlRequestHandler extends SynchronizedRequestHandler implements } private void writeUidl(VaadinRequest request, VaadinResponse response, - UI ui, Writer writer, boolean repaintAll, boolean analyzeLayouts) - throws IOException, JSONException { + UI ui, Writer writer, boolean repaintAll) throws IOException, + JSONException { openJsonMessage(writer, response); - new UidlWriter().write(ui, writer, repaintAll, analyzeLayouts, false); + new UidlWriter().write(ui, writer, repaintAll, false); closeJsonMessage(writer); } @@ -190,63 +170,6 @@ public class UidlRequestHandler extends SynchronizedRequestHandler implements 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()); } diff --git a/server/src/com/vaadin/server/communication/UidlWriter.java b/server/src/com/vaadin/server/communication/UidlWriter.java index fbe2fb86d5..60933a75c2 100644 --- a/server/src/com/vaadin/server/communication/UidlWriter.java +++ b/server/src/com/vaadin/server/communication/UidlWriter.java @@ -71,12 +71,16 @@ public class UidlWriter implements Serializable { * @throws JSONException * If the JSON serialization fails. */ - public void write(UI ui, Writer writer, boolean repaintAll, - boolean analyzeLayouts, boolean async) throws IOException, - JSONException { + public void write(UI ui, Writer writer, boolean repaintAll, boolean async) + throws IOException, JSONException { + VaadinSession session = ui.getSession(); + + // Purge pending access calls as they might produce additional changes + // to write out + session.getService().runPendingAccessTasks(session); + ArrayList<ClientConnector> dirtyVisibleConnectors = ui .getConnectorTracker().getDirtyVisibleConnectors(); - VaadinSession session = ui.getSession(); LegacyCommunicationManager manager = session.getCommunicationManager(); // Paints components ConnectorTracker uiConnectorTracker = ui.getConnectorTracker(); @@ -156,8 +160,7 @@ public class UidlWriter implements Serializable { SystemMessages messages = ui.getSession().getService() .getSystemMessages(ui.getLocale(), null); // TODO hilightedConnector - new MetadataWriter().write(ui, writer, repaintAll, analyzeLayouts, - async, null, messages); + new MetadataWriter().write(ui, writer, repaintAll, async, messages); writer.write(", "); writer.write("\"resources\" : "); @@ -278,10 +281,6 @@ public class UidlWriter implements Serializable { + 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); } diff --git a/server/src/com/vaadin/ui/AbstractComponent.java b/server/src/com/vaadin/ui/AbstractComponent.java index 06060dbf91..9ff36a42d2 100644 --- a/server/src/com/vaadin/ui/AbstractComponent.java +++ b/server/src/com/vaadin/ui/AbstractComponent.java @@ -291,7 +291,10 @@ public abstract class AbstractComponent extends AbstractClientConnector public void setLocale(Locale locale) { this.locale = locale; - // FIXME: Reload value if there is a converter + if (locale != null && isAttached()) { + getUI().getLocaleService().addLocale(locale); + } + markAsDirty(); } @@ -556,6 +559,10 @@ public abstract class AbstractComponent extends AbstractClientConnector focus(); } setActionManagerViewer(); + if (locale != null) { + getUI().getLocaleService().addLocale(locale); + } + } /* diff --git a/server/src/com/vaadin/ui/AbstractField.java b/server/src/com/vaadin/ui/AbstractField.java index 3bca63a3b7..606bf5fb21 100644 --- a/server/src/com/vaadin/ui/AbstractField.java +++ b/server/src/com/vaadin/ui/AbstractField.java @@ -616,17 +616,14 @@ public abstract class AbstractField<T> extends AbstractComponent implements // Check if the current converter is compatible. if (newDataSource != null - && !ConverterUtil.canConverterHandle(getConverter(), getType(), - newDataSource.getType())) { - // Changing from e.g. Number -> Double should set a new converter, - // changing from Double -> Number can keep the old one (Property - // accepts Number) - - // Set a new converter if there is a new data source and - // there is no old converter or the old is incompatible. + && !ConverterUtil.canConverterPossiblyHandle(getConverter(), + getType(), newDataSource.getType())) { + // There is no converter set or there is no way the current + // converter can be compatible. setConverter(newDataSource.getType()); } - // Gets the value from source + // Gets the value from source. This requires that a valid converter has + // been set. try { if (dataSource != null) { T fieldValue = convertFromModel(getDataSourceValue()); diff --git a/server/src/com/vaadin/ui/Calendar.java b/server/src/com/vaadin/ui/Calendar.java index 38fa355dd8..c3385baa2c 100644 --- a/server/src/com/vaadin/ui/Calendar.java +++ b/server/src/com/vaadin/ui/Calendar.java @@ -45,6 +45,8 @@ 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.server.PaintException; +import com.vaadin.server.PaintTarget; import com.vaadin.shared.ui.calendar.CalendarEventId; import com.vaadin.shared.ui.calendar.CalendarServerRpc; import com.vaadin.shared.ui.calendar.CalendarState; @@ -114,7 +116,7 @@ public class Calendar extends AbstractComponent implements CalendarComponentEvents.RangeSelectNotifier, CalendarComponentEvents.EventResizeNotifier, CalendarEventProvider.EventSetChangeListener, DropTarget, - CalendarEditableEventProvider, Action.Container { + CalendarEditableEventProvider, Action.Container, LegacyComponent { /** * Calendar can use either 12 hours clock or 24 hours clock. @@ -1842,4 +1844,31 @@ public class Calendar extends AbstractComponent implements } } } + + /* + * (non-Javadoc) + * + * @see com.vaadin.server.VariableOwner#changeVariables(java.lang.Object, + * java.util.Map) + */ + @Override + public void changeVariables(Object source, Map<String, Object> variables) { + /* + * Only defined to fulfill the LegacyComponent interface used for + * calendar drag & drop. No implementation required. + */ + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.ui.LegacyComponent#paintContent(com.vaadin.server.PaintTarget) + */ + @Override + public void paintContent(PaintTarget target) throws PaintException { + if (dropHandler != null) { + dropHandler.getAcceptCriterion().paint(target); + } + } }
\ No newline at end of file diff --git a/server/src/com/vaadin/ui/Label.java b/server/src/com/vaadin/ui/Label.java index d037652a09..d7cee2a80d 100644 --- a/server/src/com/vaadin/ui/Label.java +++ b/server/src/com/vaadin/ui/Label.java @@ -242,14 +242,17 @@ public class Label extends AbstractComponent implements Property<String>, ((Property.ValueChangeNotifier) dataSource).removeListener(this); } + // Check if the current converter is compatible. if (newDataSource != null - && !ConverterUtil.canConverterHandle(getConverter(), - String.class, newDataSource.getType())) { - // Try to find a converter + && !ConverterUtil.canConverterPossiblyHandle(getConverter(), + getType(), newDataSource.getType())) { + // There is no converter set or there is no way the current + // converter can be compatible. Converter<String, ?> c = ConverterUtil.getConverter(String.class, newDataSource.getType(), getSession()); setConverter(c); } + dataSource = newDataSource; if (dataSource != null) { // Update the value from the data source. If data source was set to diff --git a/server/src/com/vaadin/ui/LegacyWindow.java b/server/src/com/vaadin/ui/LegacyWindow.java index 1b66b608c1..458b09390d 100644 --- a/server/src/com/vaadin/ui/LegacyWindow.java +++ b/server/src/com/vaadin/ui/LegacyWindow.java @@ -125,7 +125,7 @@ public class LegacyWindow extends UI { public void setName(String name) { this.name = name; // The name can not be changed in application - if (getSession() != null) { + if (isAttached()) { throw new IllegalStateException( "Window name can not be changed while " + "the window is in application"); diff --git a/server/src/com/vaadin/ui/LoginForm.java b/server/src/com/vaadin/ui/LoginForm.java index d06882927e..67d7182ecb 100644 --- a/server/src/com/vaadin/ui/LoginForm.java +++ b/server/src/com/vaadin/ui/LoginForm.java @@ -68,7 +68,7 @@ public class LoginForm extends CustomComponent { } final StringBuilder responseBuilder = new StringBuilder(); - getUI().access(new Runnable() { + getUI().accessSynchronously(new Runnable() { @Override public void run() { String method = VaadinServletService.getCurrentServletRequest() diff --git a/server/src/com/vaadin/ui/ProgressBar.java b/server/src/com/vaadin/ui/ProgressBar.java new file mode 100644 index 0000000000..3f8aab6d7c --- /dev/null +++ b/server/src/com/vaadin/ui/ProgressBar.java @@ -0,0 +1,152 @@ +/* + * 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 com.vaadin.data.Property; +import com.vaadin.shared.ui.progressindicator.ProgressBarState; + +/** + * Shows the current progress of a long running task. + * <p> + * The default mode is to show the current progress internally represented by a + * floating point value between 0 and 1 (inclusive). The progress bar can also + * be in an indeterminate mode showing an animation indicating that the task is + * running but without providing any information about the current progress. + * + * @since 7.1 + * @author Vaadin Ltd + */ +public class ProgressBar extends AbstractField<Float> implements + Property.Viewer, Property.ValueChangeListener { + + /** + * Creates a new progress bar initially set to 0% progress. + */ + public ProgressBar() { + this(0); + } + + /** + * Creates a new progress bar with the given initial value. + * + * @param progress + * the initial progress value + */ + public ProgressBar(float progress) { + setValue(Float.valueOf(progress)); + } + + /** + * Creates a new progress bar bound to the given data source. + * + * @param dataSource + * the property to bind this progress bar to + */ + public ProgressBar(Property<?> dataSource) { + setPropertyDataSource(dataSource); + } + + @Override + public void beforeClientResponse(boolean initial) { + super.beforeClientResponse(initial); + + // Update value in state even if the property hasn't fired any event + getState().state = getValue(); + } + + /** + * Gets the value of this progress bar. The value is a <code>float</code> + * between 0 and 1 where 0 represents no progress at all and 1 represents + * fully completed. + * + * @return the current progress value + */ + @Override + public Float getValue() { + return super.getValue(); + } + + /** + * Sets the value of this progress bar. The value is a <code>float</code> + * between 0 and 1 where 0 represents no progress at all and 1 represents + * fully completed. + * + * @param newValue + * the current progress value + */ + @Override + public void setValue(Float newValue) { + super.setValue(newValue); + } + + @Override + public Class<Float> getType() { + return Float.class; + } + + @Override + protected ProgressBarState getState() { + return (ProgressBarState) super.getState(); + } + + @Override + protected ProgressBarState getState(boolean markAsDirty) { + return (ProgressBarState) super.getState(markAsDirty); + } + + /** + * Sets whether or not this progress indicator is indeterminate. In + * indeterminate mode there is an animation indicating that the task is + * running but without providing any information about the current progress. + * + * @param indeterminate + * <code>true</code> to set to indeterminate mode; otherwise + * <code>false</code> + */ + public void setIndeterminate(boolean indeterminate) { + getState().indeterminate = indeterminate; + } + + /** + * Gets whether or not this progress indicator is indeterminate. In + * indeterminate mode there is an animation indicating that the task is + * running but without providing any information about the current progress. + * + * @return <code>true</code> if set to indeterminate mode; otherwise + * <code>false</code> + */ + public boolean isIndeterminate() { + return getState(false).indeterminate; + } + + /* + * Overridden to keep the shared state in sync with the AbstractField + * internal value. Should be removed once AbstractField is refactored to use + * shared state. + * + * See tickets #10921 and #11064. + */ + @Override + protected void setInternalValue(Float newValue) { + super.setInternalValue(newValue); + if (newValue == null) { + newValue = Float.valueOf(0); + } + getState().state = newValue; + } + +}
\ No newline at end of file diff --git a/server/src/com/vaadin/ui/ProgressIndicator.java b/server/src/com/vaadin/ui/ProgressIndicator.java index c481aa1e8f..6da18fc29d 100644 --- a/server/src/com/vaadin/ui/ProgressIndicator.java +++ b/server/src/com/vaadin/ui/ProgressIndicator.java @@ -17,24 +17,27 @@ package com.vaadin.ui; import com.vaadin.data.Property; +import com.vaadin.shared.communication.PushMode; import com.vaadin.shared.ui.progressindicator.ProgressIndicatorServerRpc; import com.vaadin.shared.ui.progressindicator.ProgressIndicatorState; /** - * <code>ProgressIndicator</code> is component that shows user state of a - * process (like long computing or file upload) - * - * <code>ProgressIndicator</code> has two main modes. One for indeterminate - * processes and other (default) for processes which progress can be measured - * - * May view an other property that indicates progress 0...1 + * A {@link ProgressBar} which polls the server for updates. + * <p> + * Polling in this way is generally not recommended since there is no + * centralized management of when messages are sent to the server. Furthermore, + * polling might not be needed at all if {@link UI#setPushMode(PushMode)} or + * {@link UI#setPollInterval(int)} is used. * * @author Vaadin Ltd. * @since 4 + * @deprecated as of 7.1, use {@link ProgressBar} combined with + * {@link UI#setPushMode(PushMode)} or + * {@link UI#setPollInterval(int)} instead. */ +@Deprecated @SuppressWarnings("serial") -public class ProgressIndicator extends AbstractField<Float> implements - Property.Viewer, Property.ValueChangeListener { +public class ProgressIndicator extends ProgressBar { private ProgressIndicatorServerRpc rpc = new ProgressIndicatorServerRpc() { @@ -57,7 +60,7 @@ public class ProgressIndicator extends AbstractField<Float> implements * @param value */ public ProgressIndicator(float value) { - setValue(value); + super(value); registerRpc(rpc); } @@ -68,74 +71,18 @@ public class ProgressIndicator extends AbstractField<Float> implements * @param contentSource */ public ProgressIndicator(Property contentSource) { - setPropertyDataSource(contentSource); + super(contentSource); registerRpc(rpc); } @Override - public void beforeClientResponse(boolean initial) { - super.beforeClientResponse(initial); - - getState().state = getValue(); - } - - /** - * Gets the value of the ProgressIndicator. Value of the ProgressIndicator - * is Float between 0 and 1. - * - * @return the Value of the ProgressIndicator. - * @see com.vaadin.ui.AbstractField#getValue() - */ - @Override - public Float getValue() { - return super.getValue(); - } - - /** - * Sets the value of the ProgressIndicator. Value of the ProgressIndicator - * is the Float between 0 and 1. - * - * @param newValue - * the New value of the ProgressIndicator. - * @see com.vaadin.ui.AbstractField#setValue() - */ - @Override - public void setValue(Float newValue) { - super.setValue(newValue); - } - - /* - * (non-Javadoc) - * - * @see com.vaadin.ui.AbstractField#getType() - */ - @Override - public Class<Float> getType() { - return Float.class; - } - - @Override protected ProgressIndicatorState getState() { return (ProgressIndicatorState) super.getState(); } - /** - * Sets whether or not the ProgressIndicator is indeterminate. - * - * @param indeterminate - * true to set to indeterminate mode. - */ - public void setIndeterminate(boolean indeterminate) { - getState().indeterminate = indeterminate; - } - - /** - * Gets whether or not the ProgressIndicator is indeterminate. - * - * @return true to set to indeterminate mode. - */ - public boolean isIndeterminate() { - return getState().indeterminate; + @Override + protected ProgressIndicatorState getState(boolean markAsDirty) { + return (ProgressIndicatorState) super.getState(markAsDirty); } /** @@ -154,22 +101,6 @@ public class ProgressIndicator extends AbstractField<Float> implements * @return the interval in milliseconds. */ public int getPollingInterval() { - return getState().pollingInterval; - } - - /* - * Overridden to keep the shared state in sync with the AbstractField - * internal value. Should be removed once AbstractField is refactored to use - * shared state. - * - * See tickets #10921 and #11064. - */ - @Override - protected void setInternalValue(Float newValue) { - super.setInternalValue(newValue); - if (newValue == null) { - newValue = 0.0f; - } - getState().state = newValue; + return getState(false).pollingInterval; } } diff --git a/server/src/com/vaadin/ui/PushConfiguration.java b/server/src/com/vaadin/ui/PushConfiguration.java new file mode 100644 index 0000000000..a592b39bef --- /dev/null +++ b/server/src/com/vaadin/ui/PushConfiguration.java @@ -0,0 +1,282 @@ +/* + * 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 java.util.Collection; +import java.util.Collections; + +import com.vaadin.server.VaadinSession; +import com.vaadin.shared.communication.PushMode; +import com.vaadin.shared.ui.ui.Transport; +import com.vaadin.shared.ui.ui.UIState.PushConfigurationState; + +/** + * Provides method for configuring the push channel. + * + * @since 7.1 + * @author Vaadin Ltd + */ +public interface PushConfiguration extends Serializable { + + /** + * Returns the mode of bidirectional ("push") communication that is used. + * + * @return The push mode. + */ + public PushMode getPushMode(); + + /** + * Sets the mode of bidirectional ("push") communication that should be + * used. + * <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); + + /** + * Returns the primary transport type for push. + * <p> + * Note that if you set the transport type using + * {@link #setParameter(String, String)} to an unsupported type this method + * will return null. Supported types are defined by {@link Transport}. + * + * @return The primary transport type + */ + public Transport getTransport(); + + /** + * Sets the primary transport type for push. + * <p> + * Note that the new transport type will not be used until the push channel + * is disconnected and reconnected if already active. + * + * @param transport + * The primary transport type + */ + public void setTransport(Transport transport); + + /** + * Returns the fallback transport type for push. + * <p> + * Note that if you set the transport type using + * {@link #setParameter(String, String)} to an unsupported type this method + * will return null. Supported types are defined by {@link Transport}. + * + * @return The fallback transport type + */ + public Transport getFallbackTransport(); + + /** + * Sets the fallback transport type for push. + * <p> + * Note that the new transport type will not be used until the push channel + * is disconnected and reconnected if already active. + * + * @param fallbackTransport + * The fallback transport type + */ + public void setFallbackTransport(Transport fallbackTransport); + + /** + * Returns the given parameter, if set. + * <p> + * This method provides low level access to push parameters and is typically + * not needed for normal application development. + * + * @since 7.1 + * @param parameter + * The parameter name + * @return The parameter value or null if not set + */ + public String getParameter(String parameter); + + /** + * Returns the parameters which have been defined. + * + * @since 7.1 + * @return A collection of parameter names + */ + public Collection<String> getParameterNames(); + + /** + * Sets the given parameter. + * <p> + * This method provides low level access to push parameters and is typically + * not needed for normal application development. + * + * @since 7.1 + * @param parameter + * The parameter name + * @param value + * The value + */ + public void setParameter(String parameter, String value); + +} + +class PushConfigurationImpl implements PushConfiguration { + private UI ui; + + public PushConfigurationImpl(UI ui) { + this.ui = ui; + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.ui.PushConfiguration#getPushMode() + */ + @Override + public PushMode getPushMode() { + return getState(false).mode; + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.ui.PushConfiguration#setPushMode(com.vaadin.shared.communication + * .PushMode) + */ + @Override + public void setPushMode(PushMode pushMode) { + if (pushMode == null) { + throw new IllegalArgumentException("Push mode cannot be null"); + } + + if (pushMode.isEnabled()) { + VaadinSession session = ui.getSession(); + if (session != null && !session.getService().ensurePushAvailable()) { + throw new IllegalStateException( + "Push is not available. See previous log messages for more information."); + } + } + + /* + * Client-side will open a new connection or disconnect the old + * connection, so there's nothing more to do on the server at this + * point. + */ + getState().mode = pushMode; + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.ui.PushConfiguration#getTransport() + */ + @Override + public Transport getTransport() { + try { + return Transport + .valueOf(getParameter(PushConfigurationState.TRANSPORT_PARAM)); + } catch (IllegalArgumentException e) { + return null; + } + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.ui.PushConfiguration#setTransport(com.vaadin.shared.ui.ui. + * Transport) + */ + @Override + public void setTransport(Transport transport) { + setParameter(PushConfigurationState.TRANSPORT_PARAM, + transport.getIdentifier()); + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.ui.PushConfiguration#getFallbackTransport() + */ + @Override + public Transport getFallbackTransport() { + try { + return Transport + .valueOf(getParameter(PushConfigurationState.FALLBACK_TRANSPORT_PARAM)); + } catch (IllegalArgumentException e) { + return null; + } + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.ui.PushConfiguration#setFallbackTransport(com.vaadin.shared + * .ui.ui.Transport) + */ + @Override + public void setFallbackTransport(Transport fallbackTransport) { + setParameter(PushConfigurationState.FALLBACK_TRANSPORT_PARAM, + fallbackTransport.getIdentifier()); + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.ui.PushConfiguration#getParameter(java.lang.String) + */ + @Override + public String getParameter(String parameter) { + return getState(false).parameters.get(parameter); + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.ui.PushConfiguration#setParameter(java.lang.String, + * java.lang.String) + */ + @Override + public void setParameter(String parameter, String value) { + getState().parameters.put(parameter, value); + + } + + private PushConfigurationState getState() { + return ui.getState().pushConfiguration; + } + + private PushConfigurationState getState(boolean markAsDirty) { + return ui.getState(markAsDirty).pushConfiguration; + } + + @Override + public Collection<String> getParameterNames() { + return Collections + .unmodifiableCollection(ui.getState(false).pushConfiguration.parameters + .keySet()); + } + +} diff --git a/server/src/com/vaadin/ui/UI.java b/server/src/com/vaadin/ui/UI.java index 0a4ed7c491..9135151089 100644 --- a/server/src/com/vaadin/ui/UI.java +++ b/server/src/com/vaadin/ui/UI.java @@ -21,7 +21,10 @@ import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; +import java.util.concurrent.Future; +import java.util.logging.Logger; import com.vaadin.event.Action; import com.vaadin.event.Action.Handler; @@ -29,6 +32,10 @@ import com.vaadin.event.ActionManager; import com.vaadin.event.MouseEvents.ClickEvent; import com.vaadin.event.MouseEvents.ClickListener; import com.vaadin.navigator.Navigator; +import com.vaadin.server.ClientConnector; +import com.vaadin.server.ComponentSizeValidator; +import com.vaadin.server.ComponentSizeValidator.InvalidLayout; +import com.vaadin.server.LocaleService; import com.vaadin.server.Page; import com.vaadin.server.PaintException; import com.vaadin.server.PaintTarget; @@ -38,15 +45,18 @@ 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.Connector; import com.vaadin.shared.EventId; import com.vaadin.shared.MouseEventDetails; -import com.vaadin.shared.communication.PushMode; +import com.vaadin.shared.ui.ui.DebugWindowClientRpc; +import com.vaadin.shared.ui.ui.DebugWindowServerRpc; import com.vaadin.shared.ui.ui.ScrollClientRpc; 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; import com.vaadin.ui.Component.Focusable; +import com.vaadin.util.ConnectorHelper; import com.vaadin.util.CurrentInstance; /** @@ -86,7 +96,7 @@ public abstract class UI extends AbstractSingleComponentContainer implements /** * The application to which this UI belongs */ - private VaadinSession session; + private volatile VaadinSession session; /** * List of windows in this UI. @@ -159,6 +169,40 @@ public abstract class UI extends AbstractSingleComponentContainer implements */ } }; + private DebugWindowServerRpc debugRpc = new DebugWindowServerRpc() { + @Override + public void showServerDebugInfo(Connector connector) { + String info = ConnectorHelper + .getDebugInformation((ClientConnector) connector); + getLogger().info(info); + } + + @Override + public void analyzeLayouts() { + // TODO Move to client side + List<InvalidLayout> invalidSizes = ComponentSizeValidator + .validateLayouts(UI.this); + StringBuilder json = new StringBuilder(); + json.append("{\"invalidLayouts\":"); + json.append("["); + + if (invalidSizes != null) { + boolean first = true; + for (InvalidLayout invalidSize : invalidSizes) { + if (!first) { + json.append(","); + } else { + first = false; + } + invalidSize.reportErrors(json, System.err); + } + } + json.append("]}"); + getRpcProxy(DebugWindowClientRpc.class).reportLayoutProblems( + json.toString()); + } + + }; /** * Timestamp keeping track of the last heartbeat of this UI. Updated to the @@ -171,6 +215,8 @@ public abstract class UI extends AbstractSingleComponentContainer implements private TooltipConfiguration tooltipConfiguration = new TooltipConfigurationImpl( this); + private PushConfiguration pushConfiguration = new PushConfigurationImpl( + this); /** * Creates a new empty UI without a caption. The content of the UI must be @@ -191,6 +237,7 @@ public abstract class UI extends AbstractSingleComponentContainer implements */ public UI(Component content) { registerRpc(rpc); + registerRpc(debugRpc); setSizeFull(); setContent(content); } @@ -418,7 +465,7 @@ public abstract class UI extends AbstractSingleComponentContainer implements throw new NullPointerException("Argument must not be null"); } - if (window.getUI() != null && window.getUI().getSession() != null) { + if (window.isAttached()) { throw new IllegalArgumentException( "Window is already attached to an application."); } @@ -493,6 +540,9 @@ public abstract class UI extends AbstractSingleComponentContainer implements private boolean hasPendingPush = false; + private LocaleService localeService = new LocaleService(this, + getState(false).localeServiceState); + /** * This method is used by Component.Focusable objects to request focus to * themselves. Focus renders must be handled at window level (instead of @@ -1051,6 +1101,7 @@ public abstract class UI extends AbstractSingleComponentContainer implements @Override public void attach() { super.attach(); + getLocaleService().addLocale(getLocale()); } /** @@ -1098,24 +1149,30 @@ public abstract class UI extends AbstractSingleComponentContainer implements } /** - * 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> + * Locks the session of this UI and runs the provided Runnable right away. * <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. + * It is generally recommended to use {@link #access(Runnable)} instead of + * this method for accessing a session from a different thread as + * {@link #access(Runnable)} can be used while holding the lock of another + * session. To avoid causing deadlocks, this methods throws an exception if + * it is detected than another session is also locked by the current thread. * </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. + * This method behaves differently than {@link #access(Runnable)} in some + * situations: + * <ul> + * <li>If the current thread is currently holding the lock of the session, + * {@link #accessSynchronously(Runnable)} runs the task right away whereas + * {@link #access(Runnable)} defers the task to a later point in time.</li> + * <li>If some other thread is currently holding the lock for the session, + * {@link #accessSynchronously(Runnable)} blocks while waiting for the lock + * to be available whereas {@link #access(Runnable)} defers the task to a + * later point in time.</li> + * </ul> * </p> * + * @since 7.1 + * * @param runnable * the runnable which accesses the UI * @throws UIDetachedException @@ -1124,11 +1181,11 @@ public abstract class UI extends AbstractSingleComponentContainer implements * @throws IllegalStateException * if the current thread holds the lock for another session * - * @see #getCurrent() - * @see VaadinSession#access(Runnable) - * @see VaadinSession#lock() + * @see #access(Runnable) + * @see VaadinSession#accessSynchronously(Runnable) */ - public void access(Runnable runnable) throws UIDetachedException { + public void accessSynchronously(Runnable runnable) + throws UIDetachedException { Map<Class<?>, CurrentInstance> old = null; VaadinSession session = getSession(); @@ -1146,24 +1203,76 @@ public abstract class UI extends AbstractSingleComponentContainer implements // acquired the lock. throw new UIDetachedException(); } - old = CurrentInstance.setThreadLocals(this); + old = CurrentInstance.setCurrent(this); runnable.run(); } finally { session.unlock(); if (old != null) { - CurrentInstance.restoreThreadLocals(old); + CurrentInstance.restoreInstances(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. + * 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. If the session is not locked, the lock will + * be acquired and the runnable is run right away. If the session is + * currently locked, the runnable will be run before that lock is released. + * </p> + * <p> + * RPC handlers for components inside this UI do not need to use this method + * as the session is automatically locked by the framework during RPC + * handling. + * </p> + * <p> + * Please note that the runnable might be invoked on a different thread or + * later on the current thread, which means that custom thread locals might + * not have the expected values when the runnable is executed. Inheritable + * values in {@link CurrentInstance} will have the same values as when this + * method was invoked. {@link UI#getCurrent()}, + * {@link VaadinSession#getCurrent()} and {@link VaadinService#getCurrent()} + * are set according to this UI before executing the runnable. + * Non-inheritable CurrentInstance values including + * {@link VaadinService#getCurrentRequest()} and + * {@link VaadinService#getCurrentResponse()} will not be defined. + * </p> + * <p> + * The returned future can be used to check for task completion and to + * cancel the task. + * </p> + * + * @see #getCurrent() + * @see #accessSynchronously(Runnable) + * @see VaadinSession#access(Runnable) + * @see VaadinSession#lock() + * + * @since 7.1 + * + * @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) + * @return a future that can be used to check for task completion and to + * cancel the task */ - @Deprecated - public void runSafely(Runnable runnable) throws UIDetachedException { - access(runnable); + public Future<Void> access(final Runnable runnable) { + VaadinSession session = getSession(); + + if (session == null) { + throw new UIDetachedException(); + } + + return session.access(new Runnable() { + @Override + public void run() { + accessSynchronously(runnable); + } + }); } /** @@ -1204,12 +1313,20 @@ public abstract class UI extends AbstractSingleComponentContainer implements VaadinSession session = getSession(); if (session != null) { assert session.hasLock(); + + /* + * Purge the pending access queue as it might mark a connector as + * dirty when the push would otherwise be ignored because there are + * no changes to push. + */ + session.getService().runPendingAccessTasks(session); + if (!getConnectorTracker().hasDirtyConnectors()) { // Do not push if there is nothing to push return; } - if (!getPushMode().isEnabled()) { + if (!getPushConfiguration().getPushMode().isEnabled()) { throw new IllegalStateException("Push not enabled"); } @@ -1237,7 +1354,7 @@ public abstract class UI extends AbstractSingleComponentContainer implements */ public void setPushConnection(PushConnection pushConnection) { // If pushMode is disabled then there should never be a pushConnection - assert (getPushMode().isEnabled() || pushConnection == null); + assert (getPushConfiguration().getPushMode().isEnabled() || pushConnection == null); if (pushConnection == this.pushConnection) { return; @@ -1286,51 +1403,13 @@ public abstract class UI extends AbstractSingleComponentContainer implements } /** - * 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. + * Retrieves the object used for configuring the push channel. * - * @throws IllegalArgumentException - * if the argument is null. - * @throws IllegalStateException - * if push support is not available. + * @since 7.1 + * @return The instance used for push configuration */ - 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; + public PushConfiguration getPushConfiguration() { + return pushConfiguration; } /** @@ -1357,4 +1436,19 @@ public abstract class UI extends AbstractSingleComponentContainer implements public void setOverlayContainerLabel(String overlayContainerLabel) { getState().overlayContainerLabel = overlayContainerLabel; } + + /** + * Returns the locale service which handles transmission of Locale data to + * the client. + * + * @since 7.1 + * @return The LocaleService for this UI + */ + public LocaleService getLocaleService() { + return localeService; + } + + private static Logger getLogger() { + return Logger.getLogger(UI.class.getName()); + } } diff --git a/server/src/com/vaadin/ui/components/colorpicker/ColorPickerHistory.java b/server/src/com/vaadin/ui/components/colorpicker/ColorPickerHistory.java index 2902585f56..de8c5db195 100644 --- a/server/src/com/vaadin/ui/components/colorpicker/ColorPickerHistory.java +++ b/server/src/com/vaadin/ui/components/colorpicker/ColorPickerHistory.java @@ -95,7 +95,7 @@ public class ColorPickerHistory extends CustomComponent implements @SuppressWarnings("unchecked") private ArrayBlockingQueue<Color> getColorHistory() { - if (getSession() != null) { + if (isAttached()) { Object colorHistory = getSession().getAttribute( "colorPickerHistory"); if (colorHistory instanceof ArrayBlockingQueue<?>) { diff --git a/server/src/com/vaadin/util/ConnectorHelper.java b/server/src/com/vaadin/util/ConnectorHelper.java new file mode 100644 index 0000000000..e698e9222a --- /dev/null +++ b/server/src/com/vaadin/util/ConnectorHelper.java @@ -0,0 +1,101 @@ +/* + * 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.util; + +import java.util.LinkedList; + +import com.vaadin.server.ClientConnector; +import com.vaadin.ui.Component; + +/** + * Provides various helper methods for connectors. Meant for internal use. + * + * @since 7.1 + * @author Vaadin Ltd + */ +public class ConnectorHelper { + + /** + * Creates a string containing debug info for the connector + * + * @since 7.1 + * @param connector + * The connector to print debug info about + * @return A string with debug information + */ + public static String getDebugInformation(ClientConnector connector) { + StringBuilder sb = new StringBuilder(); + sb.append("*** Debug details of a connector: *** \n"); + sb.append("Type: "); + sb.append(connector.getClass().getName()); + sb.append("\nId:"); + sb.append(connector.getConnectorId()); + if (connector instanceof Component) { + Component component = (Component) connector; + if (component.getCaption() != null) { + sb.append("\nCaption:"); + sb.append(component.getCaption()); + } + } + writeHierarchyInformation(connector, sb); + return sb.toString(); + } + + /** + * Creates a string containing hierarchy information for the connector + * + * @since 7.1 + * @param connector + * The connector to get hierarchy information for + * @param builder + * The StringBuilder where the information should be written + */ + public static void writeHierarchyInformation(ClientConnector connector, + StringBuilder builder) { + LinkedList<ClientConnector> h = new LinkedList<ClientConnector>(); + h.add(connector); + ClientConnector parent = connector.getParent(); + while (parent != null) { + h.addFirst(parent); + parent = parent.getParent(); + } + + builder.append("\nConnector hierarchy:\n"); + + int l = 0; + for (ClientConnector connector2 : h) { + if (l != 0) { + builder.append("\n"); + for (int i = 0; i < l; i++) { + builder.append(" "); + } + } + l++; + Class<? extends ClientConnector> connectorClass = connector2 + .getClass(); + Class<?> topClass = connectorClass; + while (topClass.getEnclosingClass() != null) { + topClass = topClass.getEnclosingClass(); + } + builder.append(connectorClass.getName()); + builder.append("("); + builder.append(topClass.getSimpleName()); + builder.append(".java:1)"); + } + } + +} diff --git a/server/src/com/vaadin/util/CurrentInstance.java b/server/src/com/vaadin/util/CurrentInstance.java index 60489d596e..b97bab3d8a 100644 --- a/server/src/com/vaadin/util/CurrentInstance.java +++ b/server/src/com/vaadin/util/CurrentInstance.java @@ -17,53 +17,41 @@ package com.vaadin.util; import java.io.Serializable; +import java.util.Collections; 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. + * Keeps track of various current instances for the current thread. All the + * instances are automatically cleared after handling a request from the client + * to avoid leaking memory. The inheritable values are also maintained when + * execution is moved to another thread, both when a new thread is created and + * when {@link VaadinSession#access(Runnable)} or {@link UI#access(Runnable)} is + * used. * <p> * Currently the framework uses the following instances: * </p> * <p> - * Inheritable: {@link UI}, {@link VaadinPortlet}, {@link VaadinService}, - * {@link VaadinServlet}, {@link VaadinSession}. + * Inheritable: {@link UI}, {@link VaadinService}, {@link VaadinSession}. * </p> * <p> * Non-inheritable: {@link VaadinRequest}, {@link VaadinResponse}. * </p> * * @author Vaadin Ltd - * @version @VERSION@ * @since 7.0.0 */ 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( @@ -129,7 +117,9 @@ public class CurrentInstance implements Serializable { /** * Sets the current inheritable instance of the given type. A current - * instance that is inheritable will be available for child threads. + * instance that is inheritable will be available for child threads and in + * code run by {@link VaadinSession#access(Runnable)} and + * {@link UI#access(Runnable)}. * * @see #set(Class, Object) * @see InheritableThreadLocal @@ -184,13 +174,15 @@ public class CurrentInstance implements Serializable { } /** - * Restores the given thread locals to the given values. Note that this - * should only be used internally to restore Vaadin classes. + * Restores the given instances to the given values. Note that this should + * only be used internally to restore Vaadin classes. + * + * @since 7.1 * * @param old - * A Class -> Object map to set as thread locals + * A Class -> CurrentInstance map to set as current instances */ - public static void restoreThreadLocals(Map<Class<?>, CurrentInstance> old) { + public static void restoreInstances(Map<Class<?>, CurrentInstance> old) { for (Class c : old.keySet()) { CurrentInstance ci = old.get(c); set(c, ci.instance, ci.inheritable); @@ -198,30 +190,66 @@ public class CurrentInstance implements Serializable { } /** - * Sets thread locals for the UI and all related classes + * Gets the currently set instances so that they can later be restored using + * {@link #restoreInstances(Map)}. + * + * @since 7.1 + * + * @param onlyInheritable + * <code>true</code> if only the inheritable instances should be + * included; <code>false</code> to get all instances. + * @return a map containing the current instances + */ + public static Map<Class<?>, CurrentInstance> getInstances( + boolean onlyInheritable) { + Map<Class<?>, CurrentInstance> map = instances.get(); + if (map == null) { + return Collections.emptyMap(); + } else { + Map<Class<?>, CurrentInstance> copy = new HashMap<Class<?>, CurrentInstance>(); + for (Class<?> c : map.keySet()) { + CurrentInstance ci = map.get(c); + if (ci.inheritable || !onlyInheritable) { + copy.put(c, ci); + } + } + return copy; + } + } + + /** + * Sets current instances for the UI and all related classes. The previously + * defined values can be restored by passing the returned map to + * {@link #restoreInstances(Map)}. + * + * @since 7.1 * * @param ui * The UI - * @return A map containing the old values of the thread locals this method + * @return A map containing the old values of the instances that this method * updated. */ - public static Map<Class<?>, CurrentInstance> setThreadLocals(UI ui) { + public static Map<Class<?>, CurrentInstance> setCurrent(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())); + old.putAll(setCurrent(ui.getSession())); return old; } /** - * Sets thread locals for the {@link VaadinSession} and all related classes + * Sets current instances for the {@link VaadinSession} and all related + * classes. The previously defined values can be restored by passing the + * returned map to {@link #restoreInstances(Map)}. + * + * @since 7.1 * * @param session * The VaadinSession - * @return A map containing the old values of the thread locals this method + * @return A map containing the old values of the instances this method * updated. */ - public static Map<Class<?>, CurrentInstance> setThreadLocals( + public static Map<Class<?>, CurrentInstance> setCurrent( VaadinSession session) { Map<Class<?>, CurrentInstance> old = new HashMap<Class<?>, CurrentInstance>(); old.put(VaadinSession.class, @@ -236,18 +264,6 @@ public class CurrentInstance implements Serializable { 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; } } |