diff options
Diffstat (limited to 'server/src/com/vaadin')
17 files changed, 380 insertions, 129 deletions
diff --git a/server/src/com/vaadin/data/RpcDataProviderExtension.java b/server/src/com/vaadin/data/RpcDataProviderExtension.java index 354bfe336c..991cb0537d 100644 --- a/server/src/com/vaadin/data/RpcDataProviderExtension.java +++ b/server/src/com/vaadin/data/RpcDataProviderExtension.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; @@ -325,10 +326,10 @@ public class RpcDataProviderExtension extends AbstractExtension { */ private class ActiveRowHandler implements Serializable { /** - * A map from itemId to the value change listener used for all of its + * A map from index to the value change listener used for all of column * properties */ - private final Map<Object, GridValueChangeListener> valueChangeListeners = new HashMap<Object, GridValueChangeListener>(); + private final Map<Integer, GridValueChangeListener> valueChangeListeners = new HashMap<Integer, GridValueChangeListener>(); /** * The currently active range. Practically, it's the range of row @@ -388,36 +389,27 @@ public class RpcDataProviderExtension extends AbstractExtension { } private void addValueChangeListeners(Range range) { - for (int i = range.getStart(); i < range.getEnd(); i++) { + for (Integer i = range.getStart(); i < range.getEnd(); i++) { final Object itemId = container.getIdByIndex(i); final Item item = container.getItem(itemId); - if (valueChangeListeners.containsKey(itemId)) { - /* - * This might occur when items are removed from above the - * viewport, the escalator scrolls up to compensate, but the - * same items remain in the view: It looks as if one row was - * scrolled, when in fact the whole viewport was shifted up. - */ - continue; - } + assert valueChangeListeners.get(i) == null : "Overwriting existing listener"; GridValueChangeListener listener = new GridValueChangeListener( itemId, item); - valueChangeListeners.put(itemId, listener); + valueChangeListeners.put(i, listener); } } private void removeValueChangeListeners(Range range) { - for (int i = range.getStart(); i < range.getEnd(); i++) { - final Object itemId = container.getIdByIndex(i); + for (Integer i = range.getStart(); i < range.getEnd(); i++) { final GridValueChangeListener listener = valueChangeListeners - .remove(itemId); + .remove(i); - if (listener != null) { - listener.removeListener(); - } + assert listener != null : "Trying to remove nonexisting listener"; + + listener.removeListener(); } } @@ -478,12 +470,16 @@ public class RpcDataProviderExtension extends AbstractExtension { */ public void insertRows(int firstIndex, int count) { if (firstIndex < activeRange.getStart()) { + moveListeners(activeRange, count); activeRange = activeRange.offsetBy(count); } else if (firstIndex < activeRange.getEnd()) { - final Range deprecatedRange = Range.withLength( - activeRange.getEnd(), count); - removeValueChangeListeners(deprecatedRange); - + int end = activeRange.getEnd(); + // Move rows from first added index by count + Range movedRange = Range.between(firstIndex, end); + moveListeners(movedRange, count); + // Remove excess listeners from extra rows + removeValueChangeListeners(Range.withLength(end, count)); + // Add listeners for new rows final Range freshRange = Range.withLength(firstIndex, count); addValueChangeListeners(freshRange); } else { @@ -509,23 +505,52 @@ public class RpcDataProviderExtension extends AbstractExtension { public void removeRows(int firstIndex, int count) { Range removed = Range.withLength(firstIndex, count); if (removed.intersects(activeRange)) { - final Range deprecated = removed.restrictTo(activeRange); - for (int i = deprecated.getStart(); i < deprecated.getEnd(); ++i) { - Object itemId = keyMapper.itemIdAtIndex(i); - // Item doesn't exist anymore. - valueChangeListeners.remove(itemId); - } + final Range[] deprecated = activeRange.partitionWith(removed); + // Remove the listeners that are no longer existing + removeValueChangeListeners(deprecated[1]); + // Move remaining listeners to fill the listener map correctly + moveListeners(deprecated[2], -deprecated[1].length()); activeRange = Range.withLength(activeRange.getStart(), - activeRange.length() - deprecated.length()); + activeRange.length() - deprecated[1].length()); + } else { if (removed.getEnd() < activeRange.getStart()) { /* firstIndex < lastIndex < start */ + moveListeners(activeRange, -count); activeRange = activeRange.offsetBy(-count); } /* else: end <= firstIndex, no need to do anything */ } } + + /** + * Moves value change listeners in map with given index range by count + */ + private void moveListeners(Range movedRange, int diff) { + if (diff < 0) { + for (Integer i = movedRange.getStart(); i < movedRange.getEnd(); ++i) { + moveListener(i, i + diff); + } + } else if (diff > 0) { + for (Integer i = movedRange.getEnd() - 1; i >= movedRange + .getStart(); --i) { + moveListener(i, i + diff); + } + } else { + // diff == 0 should not happen. If it does, should be no-op + return; + } + } + + private void moveListener(Integer oldIndex, Integer newIndex) { + assert valueChangeListeners.get(newIndex) == null : "Overwriting existing listener"; + + GridValueChangeListener listener = valueChangeListeners + .remove(oldIndex); + assert listener != null : "Moving nonexisting listener."; + valueChangeListeners.put(newIndex, listener); + } } /** @@ -663,7 +688,7 @@ public class RpcDataProviderExtension extends AbstractExtension { * taking all the corner cases into account. */ - Map<Object, GridValueChangeListener> listeners = activeRowHandler.valueChangeListeners; + Map<Integer, GridValueChangeListener> listeners = activeRowHandler.valueChangeListeners; for (GridValueChangeListener listener : listeners.values()) { listener.removeListener(); } @@ -691,7 +716,7 @@ public class RpcDataProviderExtension extends AbstractExtension { private CellReference cellReference; /** Set of updated item ids */ - private Set<Object> updatedItemIds = new HashSet<Object>(); + private Set<Object> updatedItemIds = new LinkedHashSet<Object>(); /** * Queued RPC calls for adding and removing rows. Queue will be handled in diff --git a/server/src/com/vaadin/server/Constants.java b/server/src/com/vaadin/server/Constants.java index 8036490333..b9a43a98de 100644 --- a/server/src/com/vaadin/server/Constants.java +++ b/server/src/com/vaadin/server/Constants.java @@ -67,7 +67,7 @@ public interface Constants { // Keep the version number in sync with push/build.xml and other locations // listed in that file - static final String REQUIRED_ATMOSPHERE_RUNTIME_VERSION = "2.2.4.vaadin2"; + static final String REQUIRED_ATMOSPHERE_RUNTIME_VERSION = "2.2.4.vaadin4"; static final String INVALID_ATMOSPHERE_VERSION_WARNING = "\n" + "=================================================================\n" @@ -132,11 +132,12 @@ public interface Constants { static final String SERVLET_PARAMETER_RESOURCE_CACHE_TIME = "resourceCacheTime"; static final String SERVLET_PARAMETER_HEARTBEAT_INTERVAL = "heartbeatInterval"; static final String SERVLET_PARAMETER_CLOSE_IDLE_SESSIONS = "closeIdleSessions"; - static final String SERVLET_PARAMETER_PUSH_MODE = "pushMode"; static final String SERVLET_PARAMETER_UI_PROVIDER = "UIProvider"; static final String SERVLET_PARAMETER_LEGACY_PROPERTY_TOSTRING = "legacyPropertyToString"; static final String SERVLET_PARAMETER_SYNC_ID_CHECK = "syncIdCheck"; static final String SERVLET_PARAMETER_SENDURLSASPARAMETERS = "sendUrlsAsParameters"; + static final String SERVLET_PARAMETER_PUSH_MODE = "pushMode"; + static final String SERVLET_PARAMETER_PUSH_PATH = "pushPath"; // Configurable parameter names static final String PARAMETER_VAADIN_RESOURCES = "Resources"; diff --git a/server/src/com/vaadin/server/DefaultDeploymentConfiguration.java b/server/src/com/vaadin/server/DefaultDeploymentConfiguration.java index b26e048431..5402979be8 100644 --- a/server/src/com/vaadin/server/DefaultDeploymentConfiguration.java +++ b/server/src/com/vaadin/server/DefaultDeploymentConfiguration.java @@ -61,6 +61,13 @@ public class DefaultDeploymentConfiguration extends public static final boolean DEFAULT_SEND_URLS_AS_PARAMETERS = true; + /** + * Default value for {@link #getPushPath()} = {@value} . + * + * @since 7.4.1 + */ + public static final String DEFAULT_PUSH_PATH = "PUSH"; + private final Properties initParameters; private boolean productionMode; private boolean xsrfProtectionEnabled; @@ -285,6 +292,18 @@ public class DefaultDeploymentConfiguration extends } /** + * {@inheritDoc} + * <p> + * The default path {@link DEFAULT_PUSH_PATH} can be changed by using init + * parameter {@link Constants.SERVLET_PARAMETER_PUSH_PATH}. + */ + @Override + public String getPushPath() { + return getApplicationOrSystemProperty( + Constants.SERVLET_PARAMETER_PUSH_PATH, DEFAULT_PUSH_PATH); + } + + /** * Log a warning if Vaadin is not running in production mode. */ private void checkProductionMode() { diff --git a/server/src/com/vaadin/server/DeploymentConfiguration.java b/server/src/com/vaadin/server/DeploymentConfiguration.java index 968ec7c0c3..06556e28a7 100644 --- a/server/src/com/vaadin/server/DeploymentConfiguration.java +++ b/server/src/com/vaadin/server/DeploymentConfiguration.java @@ -195,7 +195,7 @@ public interface DeploymentConfiguration extends Serializable { * * @since 7.4 * - * @return UI class name + * @return the name of the widgetset */ public String getWidgetset(String defaultValue); @@ -214,6 +214,14 @@ public interface DeploymentConfiguration extends Serializable { public String getClassLoaderName(); /** + * Returns the push path configuration option value. Should never be null. + * + * @since 7.4.1 + * @return the path used with server push + */ + public String getPushPath(); + + /** * Returns to legacy Property.toString() mode used. See * {@link AbstractProperty#isLegacyToStringEnabled()} for more information. * diff --git a/server/src/com/vaadin/server/ServletPortletHelper.java b/server/src/com/vaadin/server/ServletPortletHelper.java index 197d9fe416..1f0c7f02b9 100644 --- a/server/src/com/vaadin/server/ServletPortletHelper.java +++ b/server/src/com/vaadin/server/ServletPortletHelper.java @@ -124,7 +124,8 @@ public class ServletPortletHelper implements Serializable { } public static boolean isPushRequest(VaadinRequest request) { - return hasPathPrefix(request, ApplicationConstants.PUSH_PATH + '/'); + return hasPathPrefix(request, request.getService() + .getDeploymentConfiguration().getPushPath() + '/'); } public static void initDefaultUIProvider(VaadinSession session, diff --git a/server/src/com/vaadin/server/VaadinService.java b/server/src/com/vaadin/server/VaadinService.java index 36d6910a7a..74f0051e30 100644 --- a/server/src/com/vaadin/server/VaadinService.java +++ b/server/src/com/vaadin/server/VaadinService.java @@ -1433,6 +1433,10 @@ public abstract class VaadinService implements Serializable { ErrorHandler errorHandler = ErrorEvent .findErrorHandler(vaadinSession); + if (errorHandler != null) { + errorHandler.error(new ErrorEvent(t)); + } + // if this was an UIDL request, send UIDL back to the client if (ServletPortletHelper.isUIDLRequest(request)) { SystemMessages ci = getSystemMessages( @@ -1454,14 +1458,7 @@ public abstract class VaadinService implements Serializable { "Failed to write critical notification response to the client", e); } - if (errorHandler != null) { - errorHandler.error(new ErrorEvent(t)); - } } else { - if (errorHandler != null) { - errorHandler.error(new ErrorEvent(t)); - } - // Re-throw other exceptions throw new ServiceException(t); } @@ -1574,20 +1571,11 @@ public abstract class VaadinService implements Serializable { String message, String details, String url) { String returnString = ""; try { - if (message == null) { - message = details; - } else if (details != null) { - message += "<br/><br/>" + details; - } - JsonObject appError = Json.createObject(); - appError.put("caption", caption); - appError.put("message", message); - if (url == null) { - appError.put("url", Json.createNull()); - } else { - appError.put("url", url); - } + putValueOrJsonNull(appError, "caption", caption); + putValueOrJsonNull(appError, "url", url); + putValueOrJsonNull(appError, "message", + createCriticalNotificationMessage(message, details)); JsonObject meta = Json.createObject(); meta.put("appError", appError); @@ -1607,6 +1595,26 @@ public abstract class VaadinService implements Serializable { return "for(;;);[" + returnString + "]"; } + private static String createCriticalNotificationMessage(String message, + String details) { + if (message == null) { + return details; + } else if (details != null) { + return message + "<br/><br/>" + details; + } + + return message; + } + + private static void putValueOrJsonNull(JsonObject json, String key, + String value) { + if (value == null) { + json.put(key, Json.createNull()); + } else { + json.put(key, value); + } + } + /** * @deprecated As of 7.0. Will likely change or be removed in a future * version diff --git a/server/src/com/vaadin/server/communication/AtmospherePushConnection.java b/server/src/com/vaadin/server/communication/AtmospherePushConnection.java index 0819a24ee9..357278f411 100644 --- a/server/src/com/vaadin/server/communication/AtmospherePushConnection.java +++ b/server/src/com/vaadin/server/communication/AtmospherePushConnection.java @@ -275,12 +275,10 @@ public class AtmospherePushConnection implements PushConnection { assert isConnected(); if (resource.isResumed()) { - // Calling disconnect may end up invoking it again via - // resource.resume and PushHandler.onResume. Bail out here if - // the resource is already resumed; this is a bit hacky and should - // be implemented in a better way in 7.2. - resource = null; - state = State.DISCONNECTED; + // This can happen for long polling because of + // http://dev.vaadin.com/ticket/16919 + // Once that is fixed, this should never happen + connectionLost(); return; } @@ -307,8 +305,23 @@ public class AtmospherePushConnection implements PushConnection { getLogger() .log(Level.INFO, "Error when closing push connection", e); } + connectionLost(); + } + + /** + * Called when the connection to the client has been lost. + * + * @since 7.4.1 + */ + public void connectionLost() { resource = null; - state = State.DISCONNECTED; + if (state == State.CONNECTED) { + // Guard against connectionLost being (incorrectly) called when + // state is PUSH_PENDING or RESPONSE_PENDING + // (http://dev.vaadin.com/ticket/16919) + state = State.DISCONNECTED; + } + } /** diff --git a/server/src/com/vaadin/server/communication/PushHandler.java b/server/src/com/vaadin/server/communication/PushHandler.java index 6ee81270cd..22eee70aa0 100644 --- a/server/src/com/vaadin/server/communication/PushHandler.java +++ b/server/src/com/vaadin/server/communication/PushHandler.java @@ -28,6 +28,7 @@ import org.atmosphere.cpr.AtmosphereResource; import org.atmosphere.cpr.AtmosphereResource.TRANSPORT; import org.atmosphere.cpr.AtmosphereResourceEvent; import org.atmosphere.cpr.AtmosphereResourceEventListenerAdapter; +import org.atmosphere.cpr.AtmosphereResourceImpl; import org.atmosphere.handler.AbstractReflectorAtmosphereHandler; import com.vaadin.server.ErrorEvent; @@ -45,6 +46,7 @@ import com.vaadin.server.VaadinSession; import com.vaadin.shared.ApplicationConstants; import com.vaadin.shared.communication.PushMode; import com.vaadin.ui.UI; + import elemental.json.JsonException; /** @@ -62,7 +64,7 @@ public class PushHandler extends AtmosphereResourceEventListenerAdapter { throws IOException { super.onStateChange(event); if (event.isCancelled() || event.isResumedOnTimeout()) { - disconnect(event); + connectionLost(event); } } @@ -327,17 +329,17 @@ public class PushHandler extends AtmosphereResourceEventListenerAdapter { public void onDisconnect(AtmosphereResourceEvent event) { // Log event on trace level super.onDisconnect(event); - disconnect(event); + connectionLost(event); } @Override public void onThrowable(AtmosphereResourceEvent event) { getLogger().log(Level.SEVERE, "Exception in push connection", event.throwable()); - disconnect(event); + connectionLost(event); } - private void disconnect(AtmosphereResourceEvent event) { + private void connectionLost(AtmosphereResourceEvent event) { // We don't want to use callWithUi here, as it assumes there's a client // request active and does requestStart and requestEnd among other // things. @@ -423,12 +425,8 @@ public class PushHandler extends AtmosphereResourceEventListenerAdapter { "Connection unexpectedly closed for resource {0} with transport {1}", new Object[] { id, resource.transport() }); } - if (pushConnection.isConnected()) { - // disconnect() assumes the push connection is connected but - // this method can currently be called more than once during - // disconnect, depending on the situation - pushConnection.disconnect(); - } + + pushConnection.connectionLost(); } } catch (final Exception e) { @@ -472,6 +470,15 @@ public class PushHandler extends AtmosphereResourceEventListenerAdapter { */ private static void sendRefreshAndDisconnect(AtmosphereResource resource) throws IOException { + if (resource instanceof AtmosphereResourceImpl + && !((AtmosphereResourceImpl) resource).isInScope()) { + // The resource is no longer valid so we should not write + // anything to it + getLogger() + .fine("sendRefreshAndDisconnect called for resource no longer in scope"); + return; + } + AtmospherePushConnection connection = new AtmospherePushConnection(null); connection.connect(resource); try { @@ -490,6 +497,14 @@ public class PushHandler extends AtmosphereResourceEventListenerAdapter { AtmosphereResource resource, String notificationJson) { // TODO Implemented differently from sendRefreshAndDisconnect try { + if (resource instanceof AtmosphereResourceImpl + && !((AtmosphereResourceImpl) resource).isInScope()) { + // The resource is no longer valid so we should not write + // anything to it + getLogger() + .fine("sendNotificationAndDisconnect called for resource no longer in scope"); + return; + } resource.getResponse().getWriter().write(notificationJson); resource.resume(); } catch (Exception e) { diff --git a/server/src/com/vaadin/server/communication/UIInitHandler.java b/server/src/com/vaadin/server/communication/UIInitHandler.java index 3a6dc1e55f..02b4e64159 100644 --- a/server/src/com/vaadin/server/communication/UIInitHandler.java +++ b/server/src/com/vaadin/server/communication/UIInitHandler.java @@ -198,10 +198,11 @@ public abstract class UIInitHandler extends SynchronizedRequestHandler { PushMode pushMode = provider.getPushMode(event); if (pushMode == null) { - pushMode = session.getService().getDeploymentConfiguration() - .getPushMode(); + pushMode = session.getConfiguration().getPushMode(); } ui.getPushConfiguration().setPushMode(pushMode); + ui.getPushConfiguration().setPushPath( + session.getConfiguration().getPushPath()); Transport transport = provider.getPushTransport(event); if (transport != null) { diff --git a/server/src/com/vaadin/ui/Flash.java b/server/src/com/vaadin/ui/Flash.java index cd7c00087e..2d0f188b84 100644 --- a/server/src/com/vaadin/ui/Flash.java +++ b/server/src/com/vaadin/ui/Flash.java @@ -97,7 +97,7 @@ public class Flash extends AbstractEmbedded { * Returns the codebase. * * @see #setCodebase(String) - * @since 7.4 + * @since 7.4.1 * @return Current codebase. */ public String getCodebase() { @@ -126,7 +126,7 @@ public class Flash extends AbstractEmbedded { * Returns the current codetype. * * @see #setCodetype(String) - * @since 7.4 + * @since 7.4.1 * @return Current codetype. */ public String getCodetype() { @@ -157,7 +157,7 @@ public class Flash extends AbstractEmbedded { * Returns current archive. * * @see #setArchive(String) - * @since 7.4 + * @since 7.4.1 * @return Current archive. */ public String getArchive() { @@ -181,7 +181,7 @@ public class Flash extends AbstractEmbedded { /** * Returns standby. * - * @since + * @since 7.4.1 * @return Standby string. */ public String getStandby() { @@ -247,7 +247,7 @@ public class Flash extends AbstractEmbedded { * * @see #setParameter(String, String) * @see #getParameter(String) - * @since 7.4 + * @since 7.4.1 * @return An iterable with declared parameter names. */ public Iterable<String> getParameterNames() { diff --git a/server/src/com/vaadin/ui/Grid.java b/server/src/com/vaadin/ui/Grid.java index df64ee85ed..77b57bceda 100644 --- a/server/src/com/vaadin/ui/Grid.java +++ b/server/src/com/vaadin/ui/Grid.java @@ -2251,7 +2251,7 @@ public class Grid extends AbstractComponent implements SelectionNotifier, public Column setLastFrozenColumn() { checkColumnIsAttached(); grid.setFrozenColumnCount(grid.getState(false).columnOrder - .indexOf(this) + 1); + .indexOf(getState().id) + 1); return this; } @@ -5229,4 +5229,17 @@ public class Grid extends AbstractComponent implements SelectionNotifier, public void removeListener(ItemClickListener listener) { removeItemClickListener(listener); } + + /** + * Requests that the column widths should be recalculated. + * <p> + * In most cases Grid will know when column widths need to be recalculated + * but this method can be used to force recalculation in situations when + * grid does not recalculate automatically. + * + * @since 7.4.1 + */ + public void recalculateColumnWidths() { + getRpcProxy(GridClientRpc.class).recalculateColumnWidths(); + } } diff --git a/server/src/com/vaadin/ui/JavaScriptFunction.java b/server/src/com/vaadin/ui/JavaScriptFunction.java index c006a36d58..071c88350b 100644 --- a/server/src/com/vaadin/ui/JavaScriptFunction.java +++ b/server/src/com/vaadin/ui/JavaScriptFunction.java @@ -19,6 +19,7 @@ package com.vaadin.ui; import java.io.Serializable; import com.vaadin.server.AbstractJavaScriptExtension; + import elemental.json.JsonArray; /** @@ -26,9 +27,9 @@ import elemental.json.JsonArray; * the corresponding JavaScript function is called, the {@link #call(JsonArray)} * method is invoked. * - * @see JavaScript#addFunction(String, JavaScriptCallback) - * @see AbstractJavaScriptComponent#addFunction(String, JavaScriptCallback) - * @see AbstractJavaScriptExtension#addFunction(String, JavaScriptCallback) + * @see JavaScript#addFunction(String, JavaScriptFunction) + * @see AbstractJavaScriptComponent#addFunction(String, JavaScriptFunction) + * @see AbstractJavaScriptExtension#addFunction(String, JavaScriptFunction) * * @author Vaadin Ltd * @since 7.0.0 diff --git a/server/src/com/vaadin/ui/PushConfiguration.java b/server/src/com/vaadin/ui/PushConfiguration.java index 90ad28542c..d5e89b4b14 100644 --- a/server/src/com/vaadin/ui/PushConfiguration.java +++ b/server/src/com/vaadin/ui/PushConfiguration.java @@ -105,6 +105,26 @@ public interface PushConfiguration extends Serializable { public void setFallbackTransport(Transport fallbackTransport); /** + * Sets the path that is used with push. + * + * @since 7.4.1 + * @param pushPath + * The path to be used with push + * + * @throws IllegalArgumentException + * if the argument is null or empty. + */ + public void setPushPath(String pushPath); + + /** + * Returns the path used with push. + * + * @since 7.4.1 + * @return The path that is used with push + */ + public String getPushPath(); + + /** * Returns the given parameter, if set. * <p> * This method provides low level access to push parameters and is typically @@ -258,6 +278,32 @@ class PushConfigurationImpl implements PushConfiguration { /* * (non-Javadoc) * + * @see com.vaadin.ui.PushConfiguration#setPushPath(java.lang.String) + */ + @Override + public void setPushPath(String pushPath) { + if (pushPath != null && !pushPath.isEmpty()) { + getState().pushPath = pushPath; + } else { + throw new IllegalArgumentException( + "Push path can't be empty or null"); + } + + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.ui.PushConfiguration#getPushPath() + */ + @Override + public String getPushPath() { + return getState(false).pushPath; + } + + /* + * (non-Javadoc) + * * @see com.vaadin.ui.PushConfiguration#getParameter(java.lang.String) */ @Override @@ -290,5 +336,4 @@ class PushConfigurationImpl implements PushConfiguration { return Collections.unmodifiableCollection(getState(false).parameters .keySet()); } - } diff --git a/server/src/com/vaadin/ui/Slider.java b/server/src/com/vaadin/ui/Slider.java index 66ed1a48f4..dad4d295bf 100644 --- a/server/src/com/vaadin/ui/Slider.java +++ b/server/src/com/vaadin/ui/Slider.java @@ -161,6 +161,11 @@ public class Slider extends AbstractField<Double> { */ public void setMax(double max) { getState().maxValue = max; + + if (getMin() > max) { + getState().minValue = max; + } + if (getValue() > max) { setValue(max); } @@ -179,11 +184,16 @@ public class Slider extends AbstractField<Double> { * Set the minimum slider value. If the current value of the slider is * smaller than this, the value is set to the new minimum. * - * @param max + * @param min * The new minimum slider value */ public void setMin(double min) { getState().minValue = min; + + if (getMax() < min) { + getState().maxValue = min; + } + if (getValue() < min) { setValue(min); } @@ -260,12 +270,12 @@ public class Slider extends AbstractField<Double> { newValue = (int) (v * Math.pow(10, resolution)); newValue = newValue / Math.pow(10, resolution); if (getMin() > newValue || getMax() < newValue) { - throw new ValueOutOfBoundsException(value); + throw new ValueOutOfBoundsException(newValue); } } else { newValue = (int) v; if (getMin() > newValue || getMax() < newValue) { - throw new ValueOutOfBoundsException(value); + throw new ValueOutOfBoundsException(newValue); } } @@ -313,6 +323,8 @@ public class Slider extends AbstractField<Double> { * @param valueOutOfBounds */ public ValueOutOfBoundsException(Double valueOutOfBounds) { + super(String.format("Value %s is out of bounds: [%s, %s]", + valueOutOfBounds, getMin(), getMax())); value = valueOutOfBounds; } diff --git a/server/src/com/vaadin/ui/UI.java b/server/src/com/vaadin/ui/UI.java index 66f893e04a..8dd600ddd0 100644 --- a/server/src/com/vaadin/ui/UI.java +++ b/server/src/com/vaadin/ui/UI.java @@ -425,7 +425,12 @@ public abstract class UI extends AbstractSingleComponentContainer implements + "."); } else { if (session == null) { - detach(); + try { + detach(); + } catch (Exception e) { + getLogger().log(Level.WARNING, + "Error while detaching UI from session", e); + } // Disable push when the UI is detached. Otherwise the // push connection and possibly VaadinSession will live on. getPushConfiguration().setPushMode(PushMode.DISABLED); diff --git a/server/src/com/vaadin/ui/declarative/Design.java b/server/src/com/vaadin/ui/declarative/Design.java index dc96e789bf..1b8585e6f6 100644 --- a/server/src/com/vaadin/ui/declarative/Design.java +++ b/server/src/com/vaadin/ui/declarative/Design.java @@ -57,6 +57,119 @@ import com.vaadin.ui.declarative.DesignContext.ComponentCreationListener; * @author Vaadin Ltd */ public class Design implements Serializable { + + /** + * Callback for creating instances of a given component class when reading + * designs. The default implementation, {@link DefaultComponentFactory} will + * use <code>Class.forName(className).newInstance()</code>, which might not + * be suitable e.g. in an OSGi environment or if the Component instances + * should be created as managed CDI beans. + * <p> + * Use {@link Design#setComponentFactory(ComponentFactory)} to configure + * Vaadin to use a custom component factory. + * + * + * @since 7.4.1 + */ + public interface ComponentFactory extends Serializable { + /** + * Creates a component based on the fully qualified name derived from + * the tag name in the design. + * + * @param fullyQualifiedClassName + * the fully qualified name of the component to create + * @param context + * the design context for which the component is created + * + * @return a newly created component + */ + public Component createComponent(String fullyQualifiedClassName, + DesignContext context); + } + + /** + * Default implementation of {@link ComponentFactory}, using + * <code>Class.forName(className).newInstance()</code> for finding the + * component class and creating a component instance. + * + * @since 7.4.1 + */ + public static class DefaultComponentFactory implements ComponentFactory { + @Override + public Component createComponent(String fullyQualifiedClassName, + DesignContext context) { + Class<? extends Component> componentClass = resolveComponentClass( + fullyQualifiedClassName, context); + + assert Component.class.isAssignableFrom(componentClass) : "resolveComponentClass returned " + + componentClass + " which is not a Vaadin Component class"; + + try { + return componentClass.newInstance(); + } catch (Exception e) { + throw new DesignException("Could not create component " + + fullyQualifiedClassName, e); + } + } + + /** + * Resolves a component class based on the fully qualified name of the + * class. + * + * @param qualifiedClassName + * the fully qualified name of the resolved class + * @param context + * the design context for which the class is resolved + * @return a component class object representing the provided class name + */ + protected Class<? extends Component> resolveComponentClass( + String qualifiedClassName, DesignContext context) { + try { + Class<?> componentClass = Class.forName(qualifiedClassName); + return componentClass.asSubclass(Component.class); + } catch (ClassNotFoundException e) { + throw new DesignException( + "Unable to load component for design", e); + } + } + + } + + private static volatile ComponentFactory componentFactory = new DefaultComponentFactory(); + + /** + * Sets the component factory that is used for creating component instances + * based on fully qualified class names derived from a design file. + * <p> + * Please note that this setting is global, so care should be taken to avoid + * conflicting changes. + * + * @param componentFactory + * the component factory to set; not <code>null</code> + * + * @since 7.4.1 + */ + public static void setComponentFactory(ComponentFactory componentFactory) { + if (componentFactory == null) { + throw new IllegalArgumentException( + "Cannot set null component factory"); + } + Design.componentFactory = componentFactory; + } + + /** + * Gets the currently used component factory. + * + * @see #setComponentFactory(ComponentFactory) + * + * @return the component factory + * + * @since 7.4.1 + */ + public static ComponentFactory getComponentFactory() { + return componentFactory; + } + /** * Parses the given input stream into a jsoup document * diff --git a/server/src/com/vaadin/ui/declarative/DesignContext.java b/server/src/com/vaadin/ui/declarative/DesignContext.java index 5f160d6f26..09fefd0a6b 100644 --- a/server/src/com/vaadin/ui/declarative/DesignContext.java +++ b/server/src/com/vaadin/ui/declarative/DesignContext.java @@ -31,6 +31,7 @@ import com.vaadin.annotations.DesignRoot; import com.vaadin.shared.util.SharedUtil; import com.vaadin.ui.Component; import com.vaadin.ui.HasComponents; +import com.vaadin.ui.declarative.Design.ComponentFactory; /** * This class contains contextual information that is collected when a component @@ -482,14 +483,17 @@ public class DesignContext implements Serializable { private Component instantiateComponent(Node node) { // Extract the package and class names. String qualifiedClassName = tagNameToClassName(node); - try { - Class<? extends Component> componentClass = resolveComponentClass(qualifiedClassName); - Component newComponent = componentClass.newInstance(); - return newComponent; - } catch (Exception e) { - throw new DesignException("No component class could be found for " - + node.nodeName() + ".", e); + + ComponentFactory factory = Design.getComponentFactory(); + Component component = factory.createComponent(qualifiedClassName, this); + + if (component == null) { + throw new DesignException("Got unexpected null component from " + + factory.getClass().getName() + " for class " + + qualifiedClassName); } + + return component; } /** @@ -530,39 +534,6 @@ public class DesignContext implements Serializable { return packageName + "." + className; } - @SuppressWarnings("unchecked") - private Class<? extends Component> resolveComponentClass( - String qualifiedClassName) throws ClassNotFoundException { - Class<?> componentClass = null; - componentClass = Class.forName(qualifiedClassName); - - // Check that we're dealing with a Component. - if (isComponent(componentClass)) { - return (Class<? extends Component>) componentClass; - } else { - throw new IllegalArgumentException(String.format( - "Resolved class %s is not a %s.", componentClass.getName(), - Component.class.getName())); - } - } - - /** - * Returns {@code true} if the given {@link Class} implements the - * {@link Component} interface of Vaadin Framework otherwise {@code false}. - * - * @param componentClass - * {@link Class} to check against {@link Component} interface. - * @return {@code true} if the given {@link Class} is a {@link Component}, - * {@code false} otherwise. - */ - private static boolean isComponent(Class<?> componentClass) { - if (componentClass != null) { - return Component.class.isAssignableFrom(componentClass); - } else { - return false; - } - } - /** * Returns the root component of a created component hierarchy. * |