diff options
author | Markus Koivisto <markus@vaadin.com> | 2015-04-08 14:53:27 +0300 |
---|---|---|
committer | Markus Koivisto <markus@vaadin.com> | 2015-04-08 14:53:27 +0300 |
commit | 2478267d11fddfd798fee89227b5f258441e1331 (patch) | |
tree | ad41582f60af26d2b47959f00130f48a0a510919 | |
parent | a1aa700db37f727bb6a3690f1704d3ec512ee468 (diff) | |
parent | 6abcbdf1eeb8f23ebda97df3ffad7b0abdee37ea (diff) | |
download | vaadin-framework-2478267d11fddfd798fee89227b5f258441e1331.tar.gz vaadin-framework-2478267d11fddfd798fee89227b5f258441e1331.zip |
Merge branch 'master' into grid-7.5
Conflicts:
WebContent/release-notes.html
Change-Id: I3906caeb01d1991dc9cb927b3d9ce0fb21c77e04
35 files changed, 1945 insertions, 176 deletions
diff --git a/WebContent/WEB-INF/web.xml b/WebContent/WEB-INF/web.xml index ef60364202..51ac907843 100644 --- a/WebContent/WEB-INF/web.xml +++ b/WebContent/WEB-INF/web.xml @@ -87,6 +87,17 @@ </servlet> <servlet> + <servlet-name>VaadinApplicationRunnerWithJSR356</servlet-name> + <servlet-class>com.vaadin.launcher.ApplicationRunnerServlet</servlet-class> + <!-- Force web sockets to use JSR 356 standard --> + <init-param> + <param-name>org.atmosphere.cpr.asyncSupport</param-name> + <param-value>org.atmosphere.container.JSR356AsyncSupport</param-value> + </init-param> + <async-supported>true</async-supported> + </servlet> + + <servlet> <!-- This servlet is a separate instance for the sole purpose of testing #12446 (com.vaadin.tests.components.ui.TimeoutRedirectResetsOnActivity) because it modifies the VaadinService timeout parameters --> @@ -149,6 +160,11 @@ </servlet-mapping> <servlet-mapping> + <servlet-name>VaadinApplicationRunnerWithJSR356</servlet-name> + <url-pattern>/run-jsr356/*</url-pattern> + </servlet-mapping> + + <servlet-mapping> <servlet-name>IntegrationTest</servlet-name> <url-pattern>/integration/*</url-pattern> </servlet-mapping> diff --git a/WebContent/release-notes.html b/WebContent/release-notes.html index e810285d83..97730cc292 100644 --- a/WebContent/release-notes.html +++ b/WebContent/release-notes.html @@ -108,7 +108,7 @@ <h3 id="incompatible">Incompatible or Behavior-altering Changes in @version-minor@</h3> <ul> - + <li>Push path has been changed from /PUSH/ to /PUSH to be compatible with JSR 356.</li> </ul> <h3 id="knownissues">Known Issues and Limitations</h3> <ul> diff --git a/client/src/com/vaadin/client/communication/AtmospherePushConnection.java b/client/src/com/vaadin/client/communication/AtmospherePushConnection.java index e544c91d0f..cd989d7ea4 100644 --- a/client/src/com/vaadin/client/communication/AtmospherePushConnection.java +++ b/client/src/com/vaadin/client/communication/AtmospherePushConnection.java @@ -197,7 +197,7 @@ public class AtmospherePushConnection implements PushConnection { private void connect() { String baseUrl = connection .translateVaadinUri(ApplicationConstants.APP_PROTOCOL_PREFIX - + ApplicationConstants.PUSH_PATH + '/'); + + ApplicationConstants.PUSH_PATH); String extraParams = UIConstants.UI_ID_PARAMETER + "=" + connection.getConfiguration().getUIId(); diff --git a/push/ivy.xml b/push/ivy.xml index 605f5d1a05..8827d92bc0 100644 --- a/push/ivy.xml +++ b/push/ivy.xml @@ -29,9 +29,9 @@ <dependencies> <!-- API DEPENDENCIES --> - <!--Servlet API version 2.4 --> - <dependency org="javax.servlet" name="servlet-api" - rev="2.4" conf="build-provided,ide,test -> default" /> + <!-- @WebListener is used for JSR356 websockets so we need to compile with servlet 3 API --> + <dependency org="javax.servlet" name="javax.servlet-api" + rev="3.0.1" conf="build-provided,ide,test -> default" /> <!-- Atmosphere --> <dependency org="com.vaadin.external.atmosphere" diff --git a/server/ivy.xml b/server/ivy.xml index b30e6a72ef..e9bc8b818d 100644 --- a/server/ivy.xml +++ b/server/ivy.xml @@ -26,9 +26,9 @@ <dependency org="com.liferay.portal" name="portal-service" rev="6.0.2" conf="build-provided,ide -> default" /> - <!--Servlet API version 2.4 --> - <dependency org="javax.servlet" name="servlet-api" - rev="2.4" conf="build-provided,ide,test -> default" /> + <!--Servlet API version 3.0 --> + <dependency org="javax.servlet" name="javax.servlet-api" + rev="3.0.1" conf="build-provided,ide,test -> default" /> <!--Portlet API version 2.0 (JSR-286) --> <dependency org="javax.portlet" name="portlet-api" diff --git a/server/src/com/vaadin/server/ServletPortletHelper.java b/server/src/com/vaadin/server/ServletPortletHelper.java index 197d9fe416..33ecf16e17 100644 --- a/server/src/com/vaadin/server/ServletPortletHelper.java +++ b/server/src/com/vaadin/server/ServletPortletHelper.java @@ -102,6 +102,24 @@ public class ServletPortletHelper implements Serializable { return false; } + private static boolean isPathInfo(VaadinRequest request, String string) { + String pathInfo = request.getPathInfo(); + + if (pathInfo == null) { + return false; + } + + if (!string.startsWith("/")) { + string = '/' + string; + } + + if (pathInfo.equals(string)) { + return true; + } + + return false; + } + public static boolean isFileUploadRequest(VaadinRequest request) { return hasPathPrefix(request, UPLOAD_URL_PREFIX); } @@ -124,7 +142,7 @@ public class ServletPortletHelper implements Serializable { } public static boolean isPushRequest(VaadinRequest request) { - return hasPathPrefix(request, ApplicationConstants.PUSH_PATH + '/'); + return isPathInfo(request, ApplicationConstants.PUSH_PATH); } public static void initDefaultUIProvider(VaadinSession session, diff --git a/server/src/com/vaadin/server/communication/JSR356WebsocketInitializer.java b/server/src/com/vaadin/server/communication/JSR356WebsocketInitializer.java new file mode 100644 index 0000000000..fd61f6180f --- /dev/null +++ b/server/src/com/vaadin/server/communication/JSR356WebsocketInitializer.java @@ -0,0 +1,203 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.server.communication; + +import java.util.Collections; +import java.util.Enumeration; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.ServletRegistration; +import javax.servlet.annotation.WebListener; + +import org.atmosphere.cpr.AtmosphereFramework; + +import com.vaadin.server.VaadinServlet; + +/** + * Initializer class for JSR 356 websockets. + * <p> + * Websocket specification says that initialization of websocket end points + * should be done in the servlet context initialization phase. Some servers + * implement this strictly so that end points cannot be registered after the + * context initialization phase. + * <p> + * Note that {@link WebListener} is Servlet 3.0 API so this will not be run for + * older servers (unless added to web.xml), but these servers do not support JSR + * 356 websockets either. + * + * @since + * @author Vaadin Ltd + */ +@WebListener +public class JSR356WebsocketInitializer implements ServletContextListener { + + private static boolean atmosphereAvailable = false; + static { + try { + org.atmosphere.util.Version.getRawVersion(); + atmosphereAvailable = true; + } catch (NoClassDefFoundError e) { + } + } + + /** + * "ServletConfig" which only provides information from a + * {@link ServletRegistration} and its {@link ServletContext} + */ + public static class FakeServletConfig implements ServletConfig { + + private ServletRegistration servletRegistration; + private ServletContext servletContext; + + public FakeServletConfig(ServletRegistration servletRegistration, + ServletContext servletContext) { + this.servletContext = servletContext; + this.servletRegistration = servletRegistration; + } + + @Override + public String getServletName() { + return servletRegistration.getName(); + } + + @Override + public ServletContext getServletContext() { + return servletContext; + } + + @Override + public String getInitParameter(String name) { + return servletRegistration.getInitParameter(name); + } + + @Override + public Enumeration<String> getInitParameterNames() { + return Collections.enumeration(servletRegistration + .getInitParameters().keySet()); + } + + } + + @Override + public void contextInitialized(ServletContextEvent sce) { + ServletContext servletContext = sce.getServletContext(); + if (servletContext.getMajorVersion() < 3) { + return; + } + + if (!atmosphereAvailable) { + return; + } + + Map<String, ? extends ServletRegistration> regs = servletContext + .getServletRegistrations(); + for (String servletName : regs.keySet()) { + ServletRegistration servletRegistration = regs.get(servletName); + + if (isVaadinServlet(servletRegistration)) { + try { + initAtmosphereForVaadinServlet(servletRegistration, + servletContext); + } catch (Exception e) { + getLogger().log( + Level.WARNING, + "Failed to initialize Atmosphere for " + + servletName, e); + } + } + } + } + + /** + * Initializes Atmosphere for use with the given Vaadin servlet + * <p> + * For JSR 356 websockets to work properly, the initialization must be done + * in the servlet context initialization phase. + * + * @param servletRegistration + * The servlet registration info for the servlet + * @param servletContext + */ + public static void initAtmosphereForVaadinServlet( + ServletRegistration servletRegistration, + ServletContext servletContext) { + String servletName = servletRegistration.getName(); + String attributeName = getAttributeName(servletName); + + if (servletContext.getAttribute(attributeName) != null) { + // Already initialized + getLogger().warning("Atmosphere already initialized"); + return; + } + getLogger().finer("Creating AtmosphereFramework for " + servletName); + AtmosphereFramework framework = PushRequestHandler + .initAtmosphere(new FakeServletConfig(servletRegistration, + servletContext)); + servletContext.setAttribute(attributeName, framework); + getLogger().finer("Created AtmosphereFramework for " + servletName); + + } + + /** + * Returns the name of the attribute in the servlet context where the + * pre-initialized Atmosphere object is stored + * + * @param servletName + * The name of the servlet + * @return The attribute name which contains the initialized Atmosphere + * object + */ + public static String getAttributeName(String servletName) { + return JSR356WebsocketInitializer.class.getName() + "." + servletName; + } + + /** + * Tries to determine if the given servlet registration refers to a Vaadin + * servlet. + * + * @param servletRegistration + * The servlet registration info for the servlet + * @return false if the servlet is definitely not a Vaadin servlet, true + * otherwise + */ + protected boolean isVaadinServlet(ServletRegistration servletRegistration) { + try { + Class<?> servletClass = Class.forName(servletRegistration + .getClassName()); + return VaadinServlet.class.isAssignableFrom(servletClass); + } catch (Exception e) { + // This will fail in OSGi environments, assume everything is a + // VaadinServlet + return true; + } + } + + private static final Logger getLogger() { + return Logger.getLogger(JSR356WebsocketInitializer.class.getName()); + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + // Nothing to do here + } + +} diff --git a/server/src/com/vaadin/server/communication/PushAtmosphereHandler.java b/server/src/com/vaadin/server/communication/PushAtmosphereHandler.java new file mode 100644 index 0000000000..0e94eaa75f --- /dev/null +++ b/server/src/com/vaadin/server/communication/PushAtmosphereHandler.java @@ -0,0 +1,120 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.server.communication; + +import java.io.IOException; +import java.io.Serializable; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.atmosphere.cpr.AtmosphereRequest; +import org.atmosphere.cpr.AtmosphereResource; +import org.atmosphere.cpr.AtmosphereResourceEvent; +import org.atmosphere.cpr.AtmosphereResourceEventListenerAdapter; +import org.atmosphere.handler.AbstractReflectorAtmosphereHandler; + +/** + * Handles Atmosphere requests and forwards them to logical methods in + * {@link PushHandler} + * + * @since + * @author Vaadin Ltd + */ +public class PushAtmosphereHandler extends AbstractReflectorAtmosphereHandler + implements Serializable { + + private PushHandler pushHandler = null; + + public void setPushHandler(PushHandler pushHandler) { + this.pushHandler = pushHandler; + } + + private static final Logger getLogger() { + return Logger.getLogger(PushAtmosphereHandler.class.getName()); + } + + @Override + public void onStateChange(AtmosphereResourceEvent event) throws IOException { + super.onStateChange(event); + if (pushHandler == null) { + getLogger() + .warning( + "AtmosphereHandler.onStateChange called before PushHandler has been set. This should really not happen"); + return; + } + + if (event.isCancelled() || event.isResumedOnTimeout()) { + pushHandler.connectionLost(event); + } + } + + @Override + public void onRequest(AtmosphereResource resource) { + if (pushHandler == null) { + getLogger() + .warning( + "AtmosphereHandler.onRequest called before PushHandler has been set. This should really not happen"); + return; + } + + AtmosphereRequest req = resource.getRequest(); + + if (req.getMethod().equalsIgnoreCase("GET")) { + onConnect(resource); + } else if (req.getMethod().equalsIgnoreCase("POST")) { + onMessage(resource); + } + } + + /** + * Called when the client sends a message through the push channel + * + * @param resource + */ + private void onMessage(AtmosphereResource resource) { + pushHandler.onMessage(resource); + } + + /** + * Called when the client sends the first request (to establish a push + * connection) + * + * @param resource + */ + private void onConnect(AtmosphereResource resource) { + resource.addEventListener(new AtmosphereResourceListener()); + + pushHandler.onConnect(resource); + } + + private class AtmosphereResourceListener extends + AtmosphereResourceEventListenerAdapter implements Serializable { + + @Override + public void onDisconnect(AtmosphereResourceEvent event) { + // Log event on trace level + super.onDisconnect(event); + pushHandler.connectionLost(event); + } + + @Override + public void onThrowable(AtmosphereResourceEvent event) { + getLogger().log(Level.SEVERE, "Exception in push connection", + event.throwable()); + pushHandler.connectionLost(event); + } + } +} diff --git a/server/src/com/vaadin/server/communication/PushHandler.java b/server/src/com/vaadin/server/communication/PushHandler.java index ea937d279e..552f2e9e03 100644 --- a/server/src/com/vaadin/server/communication/PushHandler.java +++ b/server/src/com/vaadin/server/communication/PushHandler.java @@ -22,14 +22,11 @@ import java.util.Collection; import java.util.logging.Level; import java.util.logging.Logger; -import org.atmosphere.cpr.AtmosphereHandler; import org.atmosphere.cpr.AtmosphereRequest; import org.atmosphere.cpr.AtmosphereResource; import org.atmosphere.cpr.AtmosphereResource.TRANSPORT; import org.atmosphere.cpr.AtmosphereResourceEvent; -import org.atmosphere.cpr.AtmosphereResourceEventListenerAdapter; import org.atmosphere.cpr.AtmosphereResourceImpl; -import org.atmosphere.handler.AbstractReflectorAtmosphereHandler; import com.vaadin.server.ErrorEvent; import com.vaadin.server.ErrorHandler; @@ -50,37 +47,13 @@ import com.vaadin.ui.UI; import elemental.json.JsonException; /** - * Establishes bidirectional ("push") communication channels + * Handles incoming push connections and messages and dispatches them to the + * correct {@link UI}/ {@link AtmospherePushConnection} * * @author Vaadin Ltd * @since 7.1 */ -public class PushHandler extends AtmosphereResourceEventListenerAdapter { - - AtmosphereHandler handler = new AbstractReflectorAtmosphereHandler() { - - @Override - public void onStateChange(AtmosphereResourceEvent event) - throws IOException { - super.onStateChange(event); - if (event.isCancelled() || event.isResumedOnTimeout()) { - connectionLost(event); - } - } - - @Override - public void onRequest(AtmosphereResource resource) { - AtmosphereRequest req = resource.getRequest(); - - if (req.getMethod().equalsIgnoreCase("GET")) { - callWithUi(resource, establishCallback, false); - } else if (req.getMethod().equalsIgnoreCase("POST")) { - callWithUi(resource, receiveCallback, - resource.transport() == TRANSPORT.WEBSOCKET); - } - } - - }; +public class PushHandler { /** * Callback interface used internally to process an event with the @@ -103,8 +76,6 @@ public class PushHandler extends AtmosphereResourceEventListenerAdapter { "New push connection for resource {0} with transport {1}", new Object[] { resource.uuid(), resource.transport() }); - resource.addEventListener(PushHandler.this); - resource.getResponse().setContentType("text/plain; charset=UTF-8"); VaadinSession session = ui.getSession(); @@ -325,21 +296,7 @@ public class PushHandler extends AtmosphereResourceEventListenerAdapter { } } - @Override - public void onDisconnect(AtmosphereResourceEvent event) { - // Log event on trace level - super.onDisconnect(event); - connectionLost(event); - } - - @Override - public void onThrowable(AtmosphereResourceEvent event) { - getLogger().log(Level.SEVERE, "Exception in push connection", - event.throwable()); - connectionLost(event); - } - - private void connectionLost(AtmosphereResourceEvent event) { + 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. @@ -506,4 +463,28 @@ public class PushHandler extends AtmosphereResourceEventListenerAdapter { private static final Logger getLogger() { return Logger.getLogger(PushHandler.class.getName()); } + + /** + * Called when a new push connection is requested to be opened by the client + * + * @since + * @param resource + * The related atmosphere resources + */ + void onConnect(AtmosphereResource resource) { + callWithUi(resource, establishCallback, false); + } + + /** + * Called when a message is received through the push connection + * + * @since + * @param resource + * The related atmosphere resources + */ + void onMessage(AtmosphereResource resource) { + callWithUi(resource, receiveCallback, + resource.transport() == TRANSPORT.WEBSOCKET); + } + } diff --git a/server/src/com/vaadin/server/communication/PushRequestHandler.java b/server/src/com/vaadin/server/communication/PushRequestHandler.java index 74165a4988..0e3ec300b4 100644 --- a/server/src/com/vaadin/server/communication/PushRequestHandler.java +++ b/server/src/com/vaadin/server/communication/PushRequestHandler.java @@ -17,6 +17,8 @@ package com.vaadin.server.communication; import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.servlet.ServletConfig; import javax.servlet.ServletException; @@ -25,6 +27,8 @@ import org.atmosphere.cache.UUIDBroadcasterCache; import org.atmosphere.client.TrackMessageSizeInterceptor; import org.atmosphere.cpr.ApplicationConfig; import org.atmosphere.cpr.AtmosphereFramework; +import org.atmosphere.cpr.AtmosphereFramework.AtmosphereHandlerWrapper; +import org.atmosphere.cpr.AtmosphereHandler; import org.atmosphere.cpr.AtmosphereInterceptor; import org.atmosphere.cpr.AtmosphereRequest; import org.atmosphere.cpr.AtmosphereResponse; @@ -48,7 +52,8 @@ import com.vaadin.shared.communication.PushConstants; /** * Handles requests to open a push (bidirectional) communication channel between * the client and the server. After the initial request, communication through - * the push channel is managed by {@link PushHandler}. + * the push channel is managed by {@link PushAtmosphereHandler} and + * {@link PushHandler} * * @author Vaadin Ltd * @since 7.1 @@ -62,10 +67,85 @@ public class PushRequestHandler implements RequestHandler, public PushRequestHandler(VaadinServletService service) throws ServiceException { + service.addServiceDestroyListener(new ServiceDestroyListener() { + @Override + public void serviceDestroy(ServiceDestroyEvent event) { + destroy(); + } + }); + final ServletConfig vaadinServletConfig = service.getServlet() .getServletConfig(); - atmosphere = new AtmosphereFramework() { + pushHandler = new PushHandler(service); + + atmosphere = getPreInitializedAtmosphere(vaadinServletConfig); + if (atmosphere == null) { + // Not initialized by JSR356WebsocketInitializer + getLogger().fine( + "Initializing Atmosphere for servlet " + + vaadinServletConfig.getServletName()); + try { + atmosphere = initAtmosphere(vaadinServletConfig); + } catch (Exception e) { + getLogger().log( + Level.WARNING, + "Failed to initialize Atmosphere for " + + service.getServlet().getServletName() + + ". Push will not work.", e); + return; + } + } else { + getLogger().fine( + "Using pre-initialized Atmosphere for servlet " + + vaadinServletConfig.getServletName()); + } + + for (AtmosphereHandlerWrapper handlerWrapper : atmosphere + .getAtmosphereHandlers().values()) { + AtmosphereHandler handler = handlerWrapper.atmosphereHandler; + if (handler instanceof PushAtmosphereHandler) { + // Map the (possibly pre-initialized) handler to the actual push + // handler + ((PushAtmosphereHandler) handler).setPushHandler(pushHandler); + } + + } + } + + private static final Logger getLogger() { + return Logger.getLogger(PushRequestHandler.class.getName()); + } + + /** + * Returns an AtmosphereFramework instance which was initialized in the + * servlet context init phase by {@link JSR356WebsocketInitializer}, if such + * exists + */ + private AtmosphereFramework getPreInitializedAtmosphere( + ServletConfig vaadinServletConfig) { + String attributeName = JSR356WebsocketInitializer + .getAttributeName(vaadinServletConfig.getServletName()); + Object framework = vaadinServletConfig.getServletContext() + .getAttribute(attributeName); + if (framework != null && framework instanceof AtmosphereFramework) { + return (AtmosphereFramework) framework; + } + + return null; + } + + /** + * Initializes Atmosphere for the given ServletConfiguration + * + * @since + * @param vaadinServletConfig + * The servlet configuration for the servlet which should have + * Atmosphere support + */ + static AtmosphereFramework initAtmosphere( + final ServletConfig vaadinServletConfig) { + AtmosphereFramework atmosphere = new AtmosphereFramework() { @Override protected void analytics() { // Overridden to disable version number check @@ -81,15 +161,7 @@ public class PushRequestHandler implements RequestHandler, } }; - service.addServiceDestroyListener(new ServiceDestroyListener() { - @Override - public void serviceDestroy(ServiceDestroyEvent event) { - destroy(); - } - }); - - pushHandler = new PushHandler(service); - atmosphere.addAtmosphereHandler("/*", pushHandler.handler); + atmosphere.addAtmosphereHandler("/*", new PushAtmosphereHandler()); atmosphere.addInitParameter(ApplicationConfig.BROADCASTER_CACHE, UUIDBroadcasterCache.class.getName()); atmosphere.addInitParameter(ApplicationConfig.ANNOTATION_PROCESSOR, @@ -131,8 +203,9 @@ public class PushRequestHandler implements RequestHandler, trackMessageSize.configure(atmosphere.getAtmosphereConfig()); atmosphere.interceptor(trackMessageSize); } catch (ServletException e) { - throw new ServiceException("Atmosphere init failed", e); + throw new RuntimeException("Atmosphere init failed", e); } + return atmosphere; } @Override @@ -144,6 +217,11 @@ public class PushRequestHandler implements RequestHandler, } if (request instanceof VaadinServletRequest) { + if (atmosphere == null) { + response.sendError(500, + "Atmosphere initialization failed. No push available."); + return true; + } try { atmosphere.doCometSupport(AtmosphereRequest .wrap((VaadinServletRequest) request), diff --git a/server/src/com/vaadin/ui/AbstractColorPicker.java b/server/src/com/vaadin/ui/AbstractColorPicker.java index 608a42d33b..3212d1b23f 100644 --- a/server/src/com/vaadin/ui/AbstractColorPicker.java +++ b/server/src/com/vaadin/ui/AbstractColorPicker.java @@ -17,6 +17,10 @@ package com.vaadin.ui; import java.io.Serializable; import java.lang.reflect.Method; +import java.util.Collection; + +import org.jsoup.nodes.Attributes; +import org.jsoup.nodes.Element; import com.vaadin.shared.ui.colorpicker.Color; import com.vaadin.shared.ui.colorpicker.ColorPickerServerRpc; @@ -27,6 +31,8 @@ import com.vaadin.ui.components.colorpicker.ColorChangeEvent; import com.vaadin.ui.components.colorpicker.ColorChangeListener; import com.vaadin.ui.components.colorpicker.ColorPickerPopup; import com.vaadin.ui.components.colorpicker.ColorSelector; +import com.vaadin.ui.declarative.DesignAttributeHandler; +import com.vaadin.ui.declarative.DesignContext; /** * An abstract class that defines default implementation for a color picker @@ -276,6 +282,16 @@ public abstract class AbstractColorPicker extends AbstractComponent implements } /** + * Gets the style for the popup window + * + * @since + * @return popup window style + */ + public PopupStyle getPopupStyle() { + return popupStyle; + } + + /** * Set the visibility of the RGB Tab * * @param visible @@ -294,6 +310,16 @@ public abstract class AbstractColorPicker extends AbstractComponent implements } /** + * Gets the visibility of the RGB Tab + * + * @since + * @return visibility of the RGB tab + */ + public boolean getRGBVisibility() { + return rgbVisible; + } + + /** * Set the visibility of the HSV Tab * * @param visible @@ -311,6 +337,16 @@ public abstract class AbstractColorPicker extends AbstractComponent implements } /** + * Gets the visibility of the HSV Tab + * + * @since + * @return visibility of the HSV tab + */ + public boolean getHSVVisibility() { + return hsvVisible; + } + + /** * Set the visibility of the Swatches Tab * * @param visible @@ -328,6 +364,16 @@ public abstract class AbstractColorPicker extends AbstractComponent implements } /** + * Gets the visibility of the Swatches Tab + * + * @since + * @return visibility of the swatches tab + */ + public boolean getSwatchesVisibility() { + return swatchesVisible; + } + + /** * Sets the visibility of the Color History * * @param visible @@ -341,6 +387,16 @@ public abstract class AbstractColorPicker extends AbstractComponent implements } /** + * Gets the visibility of the Color History + * + * @since + * @return visibility of color history + */ + public boolean getHistoryVisibility() { + return historyVisible; + } + + /** * Sets the visibility of the CSS color code text field * * @param visible @@ -353,6 +409,16 @@ public abstract class AbstractColorPicker extends AbstractComponent implements } } + /** + * Gets the visibility of CSS color code text field + * + * @since + * @return visibility of css color code text field + */ + public boolean getTextfieldVisibility() { + return textfieldVisible; + } + @Override protected ColorPickerState getState() { return (ColorPickerState) super.getState(); @@ -473,4 +539,49 @@ public abstract class AbstractColorPicker extends AbstractComponent implements public boolean isHtmlContentAllowed() { return isCaptionAsHtml(); } + + @Override + public void readDesign(Element design, DesignContext designContext) { + super.readDesign(design, designContext); + + Attributes attributes = design.attributes(); + if (design.hasAttr("color")) { + // Ignore the # character + String hexColor = DesignAttributeHandler.readAttribute("color", + attributes, String.class).substring(1); + setColor(new Color(Integer.parseInt(hexColor, 16))); + } + if (design.hasAttr("popup-style")) { + setPopupStyle(PopupStyle.valueOf("POPUP_" + + attributes.get("popup-style").toUpperCase())); + } + if (design.hasAttr("position")) { + String[] position = attributes.get("position").split(","); + setPosition(Integer.parseInt(position[0]), + Integer.parseInt(position[1])); + } + } + + @Override + public void writeDesign(Element design, DesignContext designContext) { + super.writeDesign(design, designContext); + + Attributes attribute = design.attributes(); + DesignAttributeHandler.writeAttribute("color", attribute, + color.getCSS(), Color.WHITE.getCSS(), String.class); + DesignAttributeHandler.writeAttribute("popup-style", attribute, + (popupStyle == PopupStyle.POPUP_NORMAL ? "normal" : "simple"), + "normal", String.class); + DesignAttributeHandler.writeAttribute("position", attribute, positionX + + "," + positionY, "0,0", String.class); + } + + @Override + protected Collection<String> getCustomAttributes() { + Collection<String> result = super.getCustomAttributes(); + result.add("color"); + result.add("position"); + result.add("popup-style"); + return result; + } } diff --git a/server/src/com/vaadin/ui/AbstractComponent.java b/server/src/com/vaadin/ui/AbstractComponent.java index ebe438b908..dae073904b 100644 --- a/server/src/com/vaadin/ui/AbstractComponent.java +++ b/server/src/com/vaadin/ui/AbstractComponent.java @@ -982,11 +982,6 @@ public abstract class AbstractComponent extends AbstractClientConnector Integer.class)); } - // handle responsive - if (attr.hasKey("responsive")) { - setResponsive(DesignAttributeHandler.getFormatter().parse( - attr.get("responsive"), Boolean.class)); - } // check for unsupported attributes Set<String> supported = new HashSet<String>(); supported.addAll(getDefaultAttributes()); @@ -1032,11 +1027,11 @@ public abstract class AbstractComponent extends AbstractClientConnector /** * Toggles responsiveness of this component. * - * @since 7.4 + * @since * @param responsive * boolean enables responsiveness, false disables */ - private void setResponsive(boolean responsive) { + public void setResponsive(boolean responsive) { if (responsive) { // make responsive if necessary if (!isResponsive()) { @@ -1057,10 +1052,10 @@ public abstract class AbstractComponent extends AbstractClientConnector /** * Returns true if the component is responsive * - * @since 7.4 + * @since * @return true if the component is responsive */ - private boolean isResponsive() { + public boolean isResponsive() { for (Extension e : getExtensions()) { if (e instanceof Responsive) { return true; @@ -1233,8 +1228,8 @@ public abstract class AbstractComponent extends AbstractClientConnector private static final String[] customAttributes = new String[] { "width", "height", "debug-id", "error", "width-auto", "height-auto", - "width-full", "height-full", "size-auto", "size-full", - "responsive", "immediate", "locale", "read-only", "_id" }; + "width-full", "height-full", "size-auto", "size-full", "immediate", + "locale", "read-only", "_id" }; /* * (non-Javadoc) @@ -1280,10 +1275,6 @@ public abstract class AbstractComponent extends AbstractClientConnector ((Focusable) def).getTabIndex(), Integer.class); } - // handle responsive - if (isResponsive()) { - attr.put("responsive", ""); - } } /* diff --git a/server/src/com/vaadin/ui/AbstractSelect.java b/server/src/com/vaadin/ui/AbstractSelect.java index 06790ca78d..4c5e6b6ea3 100644 --- a/server/src/com/vaadin/ui/AbstractSelect.java +++ b/server/src/com/vaadin/ui/AbstractSelect.java @@ -2222,4 +2222,30 @@ public abstract class AbstractSelect extends AbstractField<Object> implements } } } + + @Override + public void writeDesign(Element design, DesignContext designContext) { + // Write default attributes + super.writeDesign(design, designContext); + + // Write options if warranted + if (designContext.shouldWriteData(this)) { + for (Object itemId : getItemIds()) { + Element optionElement = design.appendElement("option"); + + optionElement.html(getItemCaption(itemId)); + + Resource icon = getItemIcon(itemId); + if (icon != null) { + DesignAttributeHandler.writeAttribute("icon", + optionElement.attributes(), icon, null, + Resource.class); + } + + if (isSelected(itemId)) { + optionElement.attr("selected", ""); + } + } + } + } }
\ No newline at end of file diff --git a/server/src/com/vaadin/ui/AbstractSingleComponentContainer.java b/server/src/com/vaadin/ui/AbstractSingleComponentContainer.java index 244feb3bb9..767ae66515 100644 --- a/server/src/com/vaadin/ui/AbstractSingleComponentContainer.java +++ b/server/src/com/vaadin/ui/AbstractSingleComponentContainer.java @@ -19,6 +19,7 @@ import java.util.Collections; import java.util.Iterator; import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; import com.vaadin.server.ComponentSizeValidator; import com.vaadin.server.VaadinService; @@ -288,17 +289,33 @@ public abstract class AbstractSingleComponentContainer extends public void readDesign(Element design, DesignContext designContext) { // process default attributes super.readDesign(design, designContext); - // handle child element, checking that the design specifies at most one - // child - int childCount = design.children().size(); - if (childCount > 1) { + readDesignChildren(design.children(), designContext); + } + + /** + * Reads the content component from the list of child elements of a design. + * The list must be empty or contain a single element; if the design + * contains multiple child elements, a DesignException is thrown. This + * method should be overridden by subclasses whose design may contain + * non-content child elements. + * + * @param children + * the child elements of the design that is being read + * @param context + * the DesignContext instance used to parse the design + * + * @throws DesignException + * if there are multiple child elements + * @throws DesignException + * if a child element could not be parsed as a Component + */ + protected void readDesignChildren(Elements children, DesignContext context) { + if (children.size() > 1) { throw new DesignException("The container of type " + getClass().toString() + " can have only one child component."); - } else if (childCount == 1) { - Element childElement = design.children().get(0); - Component newChild = designContext.readDesign(childElement); - setContent(newChild); + } else if (children.size() == 1) { + setContent(context.readDesign(children.first())); } } diff --git a/server/src/com/vaadin/ui/Component.java b/server/src/com/vaadin/ui/Component.java index 9e0816a398..adef4b69c5 100644 --- a/server/src/com/vaadin/ui/Component.java +++ b/server/src/com/vaadin/ui/Component.java @@ -149,8 +149,8 @@ public interface Component extends ClientConnector, Sizeable, Serializable { * * <p> * Each style name will occur in two versions: one as specified and one that - * is prefixed wil the style name of the component. For example, if you have - * a {@code Button} component and give it "{@code mystyle}" style, the + * is prefixed with the style name of the component. For example, if you + * have a {@code Button} component and give it "{@code mystyle}" style, the * component will have both "{@code mystyle}" and "{@code v-button-mystyle}" * styles. You could then style the component either with: * </p> @@ -253,9 +253,10 @@ public interface Component extends ClientConnector, Sizeable, Serializable { public boolean isEnabled(); /** - * Enables or disables the component. The user can not interact disabled - * components, which are shown with a style that indicates the status, - * usually shaded in light gray color. Components are enabled by default. + * Enables or disables the component. The user can not interact with + * disabled components, which are shown with a style that indicates the + * status, usually shaded in light gray color. Components are enabled by + * default. * * <pre> * Button enabled = new Button("Enabled"); @@ -909,7 +910,7 @@ public interface Component extends ClientConnector, Sizeable, Serializable { * </pre> * * @param event - * the event that has occured. + * the event that has occurred. */ public void componentEvent(Component.Event event); } diff --git a/server/src/com/vaadin/ui/PopupView.java b/server/src/com/vaadin/ui/PopupView.java index 2a2da26b62..12034cb56c 100644 --- a/server/src/com/vaadin/ui/PopupView.java +++ b/server/src/com/vaadin/ui/PopupView.java @@ -20,8 +20,13 @@ import java.lang.reflect.Method; import java.util.Collections; import java.util.Iterator; +import org.jsoup.nodes.Element; +import org.jsoup.nodes.Node; +import org.jsoup.parser.Tag; + import com.vaadin.shared.ui.popupview.PopupViewServerRpc; import com.vaadin.shared.ui.popupview.PopupViewState; +import com.vaadin.ui.declarative.DesignContext; /** * @@ -61,9 +66,15 @@ public class PopupView extends AbstractComponent implements HasComponents { /* Constructors */ - private PopupView() { + /** + * This is an internal constructor. Use + * {@link PopupView#PopupView(String, Component)}Â instead. + */ + @Deprecated + public PopupView() { registerRpc(rpc); setHideOnMouseOut(true); + setContent(createContent("", new Label(""))); } /** @@ -77,18 +88,7 @@ public class PopupView extends AbstractComponent implements HasComponents { * the full, Component-type representation */ public PopupView(final java.lang.String small, final Component large) { - this(new PopupView.Content() { - @Override - public java.lang.String getMinimizedValueAsHTML() { - return small; - } - - @Override - public Component getPopupComponent() { - return large; - } - }); - + this(createContent(small, large)); } /** @@ -104,6 +104,30 @@ public class PopupView extends AbstractComponent implements HasComponents { } /** + * Creates a Content from given text representation and popup content. + * + * @param minimizedValue + * text representation when popup is hidden + * @param popupContent + * popup content + * @return content with given data + */ + protected static Content createContent(final String minimizedValue, + final Component popupContent) { + return new Content() { + @Override + public String getMinimizedValueAsHTML() { + return minimizedValue; + } + + @Override + public Component getPopupComponent() { + return popupContent; + } + }; + } + + /** * This method will replace the current content of the panel with a new one. * * @param newContent @@ -233,6 +257,44 @@ public class PopupView extends AbstractComponent implements HasComponents { } @Override + public void readDesign(Element design, DesignContext designContext) { + + // Read content first to avoid NPE when setting popup visible + Component popupContent = null; + String minimizedValue = ""; + for (Node childNode : design.childNodes()) { + if (childNode instanceof Element) { + Element child = (Element) childNode; + if (child.tagName().equals("popup-content")) { + popupContent = designContext.readDesign(child.child(0)); + } else { + minimizedValue += child.toString(); + } + } else { + minimizedValue += childNode.toString(); + } + } + setContent(createContent(minimizedValue.trim(), popupContent)); + + super.readDesign(design, designContext); + } + + @Override + public void writeDesign(Element design, DesignContext designContext) { + super.writeDesign(design, designContext); + + Element popupContent = new Element(Tag.valueOf("popup-content"), ""); + popupContent.appendChild(designContext.createElement(content + .getPopupComponent())); + + String minimizedHTML = content.getMinimizedValueAsHTML(); + if (minimizedHTML != null && !minimizedHTML.isEmpty()) { + design.append(minimizedHTML); + } + design.appendChild(popupContent); + } + + @Override protected PopupViewState getState() { return (PopupViewState) super.getState(); } diff --git a/server/src/com/vaadin/ui/Table.java b/server/src/com/vaadin/ui/Table.java index 316564c7e3..347bb8fbff 100644 --- a/server/src/com/vaadin/ui/Table.java +++ b/server/src/com/vaadin/ui/Table.java @@ -700,7 +700,7 @@ public class Table extends AbstractSelect implements Action.Container, * Gets the headers of the columns. * * <p> - * The headers match the property id:s given my the set visible column + * The headers match the property id:s given by the set visible column * headers. The table must be set in either * {@link #COLUMN_HEADER_MODE_EXPLICIT} or * {@link #COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID} mode to show the @@ -727,7 +727,7 @@ public class Table extends AbstractSelect implements Action.Container, * Sets the headers of the columns. * * <p> - * The headers match the property id:s given my the set visible column + * The headers match the property id:s given by the set visible column * headers. The table must be set in either * {@link #COLUMN_HEADER_MODE_EXPLICIT} or * {@link #COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID} mode to show the @@ -760,7 +760,7 @@ public class Table extends AbstractSelect implements Action.Container, * Gets the icons of the columns. * * <p> - * The icons in headers match the property id:s given my the set visible + * The icons in headers match the property id:s given by the set visible * column headers. The table must be set in either * {@link #COLUMN_HEADER_MODE_EXPLICIT} or * {@link #COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID} mode to show the headers @@ -787,7 +787,7 @@ public class Table extends AbstractSelect implements Action.Container, * Sets the icons of the columns. * * <p> - * The icons in headers match the property id:s given my the set visible + * The icons in headers match the property id:s given by the set visible * column headers. The table must be set in either * {@link #COLUMN_HEADER_MODE_EXPLICIT} or * {@link #COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID} mode to show the headers @@ -887,7 +887,7 @@ public class Table extends AbstractSelect implements Action.Container, } /** - * Sets columns width (in pixels). Theme may not necessary respect very + * Sets columns width (in pixels). Theme may not necessarily respect very * small or very big values. Setting width to -1 (default) means that theme * will make decision of width. * @@ -896,9 +896,9 @@ public class Table extends AbstractSelect implements Action.Container, * is used. See @link {@link #setColumnExpandRatio(Object, float)}. * * @param propertyId - * colunmns property id + * columns property id * @param width - * width to be reserved for colunmns content + * width to be reserved for columns content * @since 4.0.3 */ public void setColumnWidth(Object propertyId, int width) { @@ -975,7 +975,7 @@ public class Table extends AbstractSelect implements Action.Container, } /** - * Gets the column expand ratio for a columnd. See + * Gets the column expand ratio for a column. See * {@link #setColumnExpandRatio(Object, float)} * * @param propertyId @@ -1091,7 +1091,7 @@ public class Table extends AbstractSelect implements Action.Container, */ public Object getCurrentPageFirstItemId() { - // Priorise index over id if indexes are supported + // Prioritise index over id if indexes are supported if (items instanceof Container.Indexed) { final int index = getCurrentPageFirstItemIndex(); Object id = null; @@ -1193,7 +1193,7 @@ public class Table extends AbstractSelect implements Action.Container, * Gets the icon Resource for the specified column. * * @param propertyId - * the propertyId indentifying the column. + * the propertyId identifying the column. * @return the icon for the specified column; null if the column has no icon * set, or if the column is not visible. */ @@ -2598,7 +2598,7 @@ public class Table extends AbstractSelect implements Action.Container, * types. * @param itemId * the Id the new row. If null, a new id is automatically - * assigned. If given, the table cant already have a item with + * assigned. If given, the table cannot already have a item with * given id. * @return Returns item id for the new row. Returns null if operation fails. */ @@ -4326,7 +4326,7 @@ public class Table extends AbstractSelect implements Action.Container, * Adds a new property to the table and show it as a visible column. * * @param propertyId - * the Id of the proprty. + * the Id of the property. * @param type * the class of the property. * @param defaultValue @@ -4361,7 +4361,7 @@ public class Table extends AbstractSelect implements Action.Container, * Adds a new property to the table and show it as a visible column. * * @param propertyId - * the Id of the proprty + * the Id of the property * @param type * the class of the property * @param defaultValue @@ -4571,7 +4571,7 @@ public class Table extends AbstractSelect implements Action.Container, disableContentRefreshing(); super.containerPropertySetChange(event); - // sanitetize visibleColumns. note that we are not adding previously + // sanitize visibleColumns. note that we are not adding previously // non-existing properties as columns Collection<?> containerPropertyIds = getContainerDataSource() .getContainerPropertyIds(); @@ -4757,11 +4757,11 @@ public class Table extends AbstractSelect implements Action.Container, * If table is editable a editor of type Field is created for each table * cell. The assigned FieldFactory is used to create the instances. * - * To provide custom editors for table cells create a class implementins the + * To provide custom editors for table cells create a class implementing the * FieldFactory interface, and assign it to table, and set the editable * property to true. * - * @return true if table is editable, false oterwise. + * @return true if table is editable, false otherwise. * @see Field * @see FieldFactory * @@ -4776,7 +4776,7 @@ public class Table extends AbstractSelect implements Action.Container, * If table is editable a editor of type Field is created for each table * cell. The assigned FieldFactory is used to create the instances. * - * To provide custom editors for table cells create a class implementins the + * To provide custom editors for table cells create a class implementing the * FieldFactory interface, and assign it to table, and set the editable * property to true. * @@ -5346,7 +5346,7 @@ public class Table extends AbstractSelect implements Action.Container, /** * Gets the property id of the column which header was pressed * - * @return The column propety id + * @return The column property id */ public Object getPropertyId() { return columnPropertyId; @@ -5396,7 +5396,7 @@ public class Table extends AbstractSelect implements Action.Container, /** * Gets the property id of the column which header was pressed * - * @return The column propety id + * @return The column property id */ public Object getPropertyId() { return columnPropertyId; diff --git a/server/src/com/vaadin/ui/Window.java b/server/src/com/vaadin/ui/Window.java index 653b620746..e7764ffd8d 100644 --- a/server/src/com/vaadin/ui/Window.java +++ b/server/src/com/vaadin/ui/Window.java @@ -18,11 +18,18 @@ package com.vaadin.ui; import java.io.Serializable; import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; 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 org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + import com.vaadin.event.FieldEvents.BlurEvent; import com.vaadin.event.FieldEvents.BlurListener; import com.vaadin.event.FieldEvents.BlurNotifier; @@ -42,6 +49,9 @@ import com.vaadin.shared.ui.window.WindowMode; import com.vaadin.shared.ui.window.WindowRole; import com.vaadin.shared.ui.window.WindowServerRpc; import com.vaadin.shared.ui.window.WindowState; +import com.vaadin.ui.declarative.DesignAttributeHandler; +import com.vaadin.ui.declarative.DesignContext; +import com.vaadin.ui.declarative.DesignException; import com.vaadin.util.ReflectTools; /** @@ -1283,4 +1293,116 @@ public class Window extends Panel implements FocusNotifier, BlurNotifier, public String getTabStopBottomAssistiveText() { return getState(false).assistiveTabStopBottomText; } + + @Override + public void readDesign(Element design, DesignContext context) { + super.readDesign(design, context); + + if (design.hasAttr("center")) { + center(); + } + if (design.hasAttr("position")) { + String[] position = design.attr("position").split(","); + setPositionX(Integer.parseInt(position[0])); + setPositionY(Integer.parseInt(position[1])); + } + if (design.hasAttr("close-shortcut")) { + ShortcutAction shortcut = DesignAttributeHandler + .readAttribute("close-shortcut", design.attributes(), + ShortcutAction.class); + setCloseShortcut(shortcut.getKeyCode(), shortcut.getModifiers()); + } + } + + /** + * Reads the content and possible assistive descriptions from the list of + * child elements of a design. If an element has an + * {@code :assistive-description} attribute, adds the parsed component to + * the list of components used as the assistive description of this Window. + * Otherwise, sets the component as the content of this Window. If there are + * multiple non-description elements, throws a DesignException. + * + * @param children + * child elements in a design + * @param context + * the DesignContext instance used to parse the design + * + * @throws DesignException + * if there are multiple non-description child elements + * @throws DesignException + * if a child element could not be parsed as a Component + * + * @see #setContent(Component) + * @see #setAssistiveDescription(Component...) + */ + @Override + protected void readDesignChildren(Elements children, DesignContext context) { + List<Component> descriptions = new ArrayList<Component>(); + Elements content = new Elements(); + + for (Element child : children) { + if (child.hasAttr(":assistive-description")) { + descriptions.add(context.readDesign(child)); + } else { + content.add(child); + } + } + super.readDesignChildren(content, context); + setAssistiveDescription(descriptions.toArray(new Component[0])); + } + + @Override + public void writeDesign(Element design, DesignContext context) { + super.writeDesign(design, context); + + Window def = context.getDefaultInstance(this); + + if (getState().centered) { + design.attr("center", ""); + } + + DesignAttributeHandler.writeAttribute("position", design.attributes(), + getPosition(), def.getPosition(), String.class); + + CloseShortcut shortcut = getCloseShortcut(); + if (shortcut != null) { + // TODO What if several close shortcuts?? + + CloseShortcut defShortcut = def.getCloseShortcut(); + if (defShortcut == null + || shortcut.getKeyCode() != defShortcut.getKeyCode() + || !Arrays.equals(shortcut.getModifiers(), + defShortcut.getModifiers())) { + DesignAttributeHandler.writeAttribute("close-shortcut", + design.attributes(), shortcut, null, + CloseShortcut.class); + } + } + + for (Component c : getAssistiveDescription()) { + Element child = context.createElement(c).attr( + ":assistive-description", ""); + design.appendChild(child); + } + } + + private String getPosition() { + return getPositionX() + "," + getPositionY(); + } + + private CloseShortcut getCloseShortcut() { + Iterator<CloseShortcut> i = getCloseShortcuts().iterator(); + return i.hasNext() ? i.next() : null; + } + + @Override + protected Collection<String> getCustomAttributes() { + Collection<String> result = super.getCustomAttributes(); + result.add("center"); + result.add("position"); + result.add("position-y"); + result.add("position-x"); + result.add("close-shortcut"); + return result; + } } diff --git a/server/src/com/vaadin/ui/declarative/DesignContext.java b/server/src/com/vaadin/ui/declarative/DesignContext.java index f991b3013a..fe3abcfb77 100644 --- a/server/src/com/vaadin/ui/declarative/DesignContext.java +++ b/server/src/com/vaadin/ui/declarative/DesignContext.java @@ -75,6 +75,8 @@ public class DesignContext implements Serializable { // component creation listeners private List<ComponentCreationListener> listeners = new ArrayList<ComponentCreationListener>(); + private ShouldWriteDataDelegate shouldWriteDataDelegate = ShouldWriteDataDelegate.DEFAULT; + public DesignContext(Document doc) { this.doc = doc; // Initialize the mapping between prefixes and package names. @@ -168,16 +170,16 @@ public class DesignContext implements Serializable { * component, the mapping from c to the string is removed. Similarly, if * component was mapped to some string s different from localId, the mapping * from s to component is removed. - * - * @param localId - * The new local id of the component. * @param component * The component whose local id is to be set. + * @param localId + * The new local id of the component. + * * @return true, if there already was a local id mapping from the string to * some component or from the component to some string. Otherwise * returns false. */ - private boolean mapLocalId(String localId, Component component) { + public boolean setComponentLocalId(Component component, String localId) { return twoWayMap(localId, component, localIdToComponent, componentToLocalId); } @@ -464,7 +466,7 @@ public class DesignContext implements Serializable { // from the attributes of componentDesign if (attributes.hasKey(LOCAL_ID_ATTRIBUTE)) { String localId = attributes.get(LOCAL_ID_ATTRIBUTE); - boolean mappingExists = mapLocalId(localId, component); + boolean mappingExists = setComponentLocalId(component, localId); if (mappingExists) { throw new DesignException( "the following local id is not unique: " + localId); @@ -661,4 +663,56 @@ public class DesignContext implements Serializable { return true; } + + /** + * Determines whether the container data of a component should be written + * out by delegating to a {@link ShouldWriteDataDelegate}. The default + * delegate assumes that all component data is provided by a data source + * connected to a back end system and that the data should thus not be + * written. + * + * @since + * @see #setShouldWriteDataDelegate(ShouldWriteDataDelegate) + * @param component + * the component to check + * @return <code>true</code> if container data should be written out for the + * provided component; otherwise <code>false</code>. + */ + public boolean shouldWriteData(Component component) { + return getShouldWriteDataDelegate().shouldWriteData(component); + } + + /** + * Sets the delegate that determines whether the container data of a + * component should be written out. + * + * @since + * @see #shouldWriteChildren(Component, Component) + * @see #getShouldWriteDataDelegate() + * @param shouldWriteDataDelegate + * the delegate to set, not <code>null</code> + * @throws IllegalArgumentException + * if the provided delegate is <code>null</code> + */ + public void setShouldWriteDataDelegate( + ShouldWriteDataDelegate shouldWriteDataDelegate) { + if (shouldWriteDataDelegate == null) { + throw new IllegalArgumentException("Delegate cannot be null"); + } + this.shouldWriteDataDelegate = shouldWriteDataDelegate; + } + + /** + * Gets the delegate that determines whether the container data of a + * component should be written out. + * + * @since + * @see #setShouldWriteDataDelegate(ShouldWriteDataDelegate) + * @see #shouldWriteChildren(Component, Component) + * @return the shouldWriteDataDelegate the currently use delegate + */ + public ShouldWriteDataDelegate getShouldWriteDataDelegate() { + return shouldWriteDataDelegate; + } + } diff --git a/server/src/com/vaadin/ui/declarative/ShouldWriteDataDelegate.java b/server/src/com/vaadin/ui/declarative/ShouldWriteDataDelegate.java new file mode 100644 index 0000000000..29a78e1db6 --- /dev/null +++ b/server/src/com/vaadin/ui/declarative/ShouldWriteDataDelegate.java @@ -0,0 +1,55 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.ui.declarative; + +import java.io.Serializable; + +import com.vaadin.ui.Component; + +/** + * Delegate used by {@link DesignContext} to determine whether container data + * should be written out for a component. + * + * @see DesignContext#shouldWriteData(Component) + * + * @since + * @author Vaadin Ltd + */ +public interface ShouldWriteDataDelegate extends Serializable { + + /** + * The default delegate implementation that assumes that all component data + * is provided by a data source connected to a back end system and that the + * data should thus not be written. + */ + public static final ShouldWriteDataDelegate DEFAULT = new ShouldWriteDataDelegate() { + @Override + public boolean shouldWriteData(Component component) { + return false; + } + }; + + /** + * Determines whether the container data of a component should be written + * out. + * + * @param component + * the component to check + * @return <code>true</code> if container data should be written out for the + * provided component; otherwise <code>false</code>. + */ + boolean shouldWriteData(Component component); +} diff --git a/server/src/com/vaadin/ui/declarative/converters/DesignShortcutActionConverter.java b/server/src/com/vaadin/ui/declarative/converters/DesignShortcutActionConverter.java index e2b6ed8e14..d6f2f65938 100644 --- a/server/src/com/vaadin/ui/declarative/converters/DesignShortcutActionConverter.java +++ b/server/src/com/vaadin/ui/declarative/converters/DesignShortcutActionConverter.java @@ -126,32 +126,37 @@ public class DesignShortcutActionConverter implements if (value.length() == 0) { return null; } - String[] data = value.split(" ", 2); + String[] data = value.split(" ", 2); String[] parts = data[0].split("-"); - // handle keycode - String keyCodePart = parts[parts.length - 1]; - int keyCode = getKeycodeForString(keyCodePart); - if (keyCode < 0) { - throw new IllegalArgumentException("Invalid shortcut definition " - + value); - } - // handle modifiers - int[] modifiers = null; - if (parts.length > 1) { - modifiers = new int[parts.length - 1]; - } - for (int i = 0; i < parts.length - 1; i++) { - int modifier = getKeycodeForString(parts[i]); - if (modifier > 0) { - modifiers[i] = modifier; - } else { - throw new IllegalArgumentException( - "Invalid shortcut definition " + value); + + try { + // handle keycode + String keyCodePart = parts[parts.length - 1]; + int keyCode = getKeycodeForString(keyCodePart); + if (keyCode < 0) { + throw new IllegalArgumentException("Invalid key '" + + keyCodePart + "'"); + } + // handle modifiers + int[] modifiers = null; + if (parts.length > 1) { + modifiers = new int[parts.length - 1]; + } + for (int i = 0; i < parts.length - 1; i++) { + int modifier = getKeycodeForString(parts[i]); + if (modifier > 0) { + modifiers[i] = modifier; + } else { + throw new IllegalArgumentException("Invalid modifier '" + + parts[i] + "'"); + } } + return new ShortcutAction(data.length == 2 ? data[1] : null, + keyCode, modifiers); + } catch (Exception e) { + throw new ConversionException("Invalid shortcut '" + value + "'", e); } - return new ShortcutAction(data.length == 2 ? data[1] : null, keyCode, - modifiers); } @Override diff --git a/server/tests/src/com/vaadin/server/MockServletContext.java b/server/tests/src/com/vaadin/server/MockServletContext.java index 031c83ae90..45ec700c40 100644 --- a/server/tests/src/com/vaadin/server/MockServletContext.java +++ b/server/tests/src/com/vaadin/server/MockServletContext.java @@ -24,12 +24,21 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.Collections; import java.util.Enumeration; +import java.util.EventListener; +import java.util.Map; import java.util.Set; +import javax.servlet.Filter; +import javax.servlet.FilterRegistration; import javax.servlet.RequestDispatcher; import javax.servlet.Servlet; import javax.servlet.ServletContext; import javax.servlet.ServletException; +import javax.servlet.ServletRegistration; +import javax.servlet.ServletRegistration.Dynamic; +import javax.servlet.SessionCookieConfig; +import javax.servlet.SessionTrackingMode; +import javax.servlet.descriptor.JspConfigDescriptor; /** * @@ -56,7 +65,7 @@ public class MockServletContext implements ServletContext { */ @Override public int getMajorVersion() { - return 2; + return 3; } /* @@ -66,7 +75,7 @@ public class MockServletContext implements ServletContext { */ @Override public int getMinorVersion() { - return 4; + return 0; } /* @@ -153,8 +162,7 @@ public class MockServletContext implements ServletContext { */ @Override public Enumeration getServlets() { - // TODO Auto-generated method stub - return null; + return Collections.enumeration(Collections.EMPTY_SET); } /* @@ -301,4 +309,315 @@ public class MockServletContext implements ServletContext { return null; } + /* + * (non-Javadoc) + * + * @see javax.servlet.ServletContext#getContextPath() + */ + @Override + public String getContextPath() { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.ServletContext#getEffectiveMajorVersion() + */ + @Override + public int getEffectiveMajorVersion() { + return 3; + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.ServletContext#getEffectiveMinorVersion() + */ + @Override + public int getEffectiveMinorVersion() { + return 0; + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.ServletContext#setInitParameter(java.lang.String, + * java.lang.String) + */ + @Override + public boolean setInitParameter(String name, String value) { + // TODO Auto-generated method stub + return false; + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.ServletContext#addServlet(java.lang.String, + * java.lang.String) + */ + @Override + public Dynamic addServlet(String servletName, String className) { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.ServletContext#addServlet(java.lang.String, + * javax.servlet.Servlet) + */ + @Override + public Dynamic addServlet(String servletName, Servlet servlet) { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.ServletContext#addServlet(java.lang.String, + * java.lang.Class) + */ + @Override + public Dynamic addServlet(String servletName, + Class<? extends Servlet> servletClass) { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.ServletContext#createServlet(java.lang.Class) + */ + @Override + public <T extends Servlet> T createServlet(Class<T> clazz) + throws ServletException { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * + * @see + * javax.servlet.ServletContext#getServletRegistration(java.lang.String) + */ + @Override + public ServletRegistration getServletRegistration(String servletName) { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.ServletContext#getServletRegistrations() + */ + @Override + public Map<String, ? extends ServletRegistration> getServletRegistrations() { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.ServletContext#addFilter(java.lang.String, + * java.lang.String) + */ + @Override + public javax.servlet.FilterRegistration.Dynamic addFilter( + String filterName, String className) { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.ServletContext#addFilter(java.lang.String, + * javax.servlet.Filter) + */ + @Override + public javax.servlet.FilterRegistration.Dynamic addFilter( + String filterName, Filter filter) { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.ServletContext#addFilter(java.lang.String, + * java.lang.Class) + */ + @Override + public javax.servlet.FilterRegistration.Dynamic addFilter( + String filterName, Class<? extends Filter> filterClass) { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.ServletContext#createFilter(java.lang.Class) + */ + @Override + public <T extends Filter> T createFilter(Class<T> clazz) + throws ServletException { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.ServletContext#getFilterRegistration(java.lang.String) + */ + @Override + public FilterRegistration getFilterRegistration(String filterName) { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.ServletContext#getFilterRegistrations() + */ + @Override + public Map<String, ? extends FilterRegistration> getFilterRegistrations() { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.ServletContext#getSessionCookieConfig() + */ + @Override + public SessionCookieConfig getSessionCookieConfig() { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.ServletContext#setSessionTrackingModes(java.util.Set) + */ + @Override + public void setSessionTrackingModes( + Set<SessionTrackingMode> sessionTrackingModes) { + // TODO Auto-generated method stub + + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.ServletContext#getDefaultSessionTrackingModes() + */ + @Override + public Set<SessionTrackingMode> getDefaultSessionTrackingModes() { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.ServletContext#getEffectiveSessionTrackingModes() + */ + @Override + public Set<SessionTrackingMode> getEffectiveSessionTrackingModes() { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.ServletContext#addListener(java.lang.String) + */ + @Override + public void addListener(String className) { + // TODO Auto-generated method stub + + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.ServletContext#addListener(java.util.EventListener) + */ + @Override + public <T extends EventListener> void addListener(T t) { + // TODO Auto-generated method stub + + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.ServletContext#addListener(java.lang.Class) + */ + @Override + public void addListener(Class<? extends EventListener> listenerClass) { + // TODO Auto-generated method stub + + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.ServletContext#createListener(java.lang.Class) + */ + @Override + public <T extends EventListener> T createListener(Class<T> clazz) + throws ServletException { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.ServletContext#getJspConfigDescriptor() + */ + @Override + public JspConfigDescriptor getJspConfigDescriptor() { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.ServletContext#getClassLoader() + */ + @Override + public ClassLoader getClassLoader() { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.ServletContext#declareRoles(java.lang.String[]) + */ + @Override + public void declareRoles(String... roleNames) { + // TODO Auto-generated method stub + + } + } diff --git a/server/tests/src/com/vaadin/tests/design/AbstractComponentSetResponsiveTest.java b/server/tests/src/com/vaadin/tests/design/AbstractComponentSetResponsiveTest.java new file mode 100644 index 0000000000..f7dbd0c97e --- /dev/null +++ b/server/tests/src/com/vaadin/tests/design/AbstractComponentSetResponsiveTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.design; + +import org.junit.Test; + +import com.vaadin.tests.design.DeclarativeTestBase; +import com.vaadin.ui.GridLayout; + +public class AbstractComponentSetResponsiveTest extends + DeclarativeTestBase<GridLayout> { + + @Test + public void testResponsiveFlag() { + GridLayout gl = new GridLayout(); + gl.setResponsive(true); + + String design = "<v-grid-layout responsive='true' />"; + + testWrite(design, gl); + testRead(design, gl); + } + +} diff --git a/server/tests/src/com/vaadin/tests/design/DeclarativeTestBase.java b/server/tests/src/com/vaadin/tests/design/DeclarativeTestBase.java index cba981c947..10f1e5c711 100644 --- a/server/tests/src/com/vaadin/tests/design/DeclarativeTestBase.java +++ b/server/tests/src/com/vaadin/tests/design/DeclarativeTestBase.java @@ -24,6 +24,7 @@ import java.util.Map; import org.junit.Assert; +import com.vaadin.shared.Connector; import com.vaadin.ui.Component; import com.vaadin.ui.Flash; @@ -59,6 +60,11 @@ public abstract class DeclarativeTestBase<T extends Component> extends if (readMethod == null || writeMethod == null) { continue; } + if (Connector.class.isAssignableFrom(c) + && readMethod.getName().equals("getParent")) { + // Hack to break cycles in the connector hierarchy + continue; + } try { c.getDeclaredMethod(readMethod.getName()); } catch (Exception e) { @@ -99,7 +105,6 @@ public abstract class DeclarativeTestBase<T extends Component> extends } } }); - } @Override @@ -118,5 +123,4 @@ public abstract class DeclarativeTestBase<T extends Component> extends } return comp; } - } diff --git a/server/tests/src/com/vaadin/tests/design/DeclarativeTestBaseBase.java b/server/tests/src/com/vaadin/tests/design/DeclarativeTestBaseBase.java index 8dc32e00d6..4cb627d035 100644 --- a/server/tests/src/com/vaadin/tests/design/DeclarativeTestBaseBase.java +++ b/server/tests/src/com/vaadin/tests/design/DeclarativeTestBaseBase.java @@ -32,6 +32,8 @@ import org.junit.Assert; import com.vaadin.ui.Component; import com.vaadin.ui.declarative.Design; +import com.vaadin.ui.declarative.DesignContext; +import com.vaadin.ui.declarative.ShouldWriteDataDelegate; public abstract class DeclarativeTestBaseBase<T extends Component> { public interface EqualsAsserter<TT> { @@ -47,10 +49,21 @@ public abstract class DeclarativeTestBaseBase<T extends Component> { } } - protected String write(T object) { + protected String write(T object, boolean writeData) { try { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - Design.write(object, outputStream); + + DesignContext dc = new DesignContext(); + if (writeData) { + dc.setShouldWriteDataDelegate(new ShouldWriteDataDelegate() { + @Override + public boolean shouldWriteData(Component component) { + return true; + } + }); + } + dc.setRootComponent(object); + Design.write(dc, outputStream); return outputStream.toString("UTF-8"); } catch (Exception e) { throw new RuntimeException(e); @@ -71,12 +84,21 @@ public abstract class DeclarativeTestBaseBase<T extends Component> { return; } - if (o1 instanceof Collection && o2 instanceof Collection) { - - } else { + if (!(o1 instanceof Collection && o2 instanceof Collection)) { Assert.assertEquals(o1.getClass(), o2.getClass()); } + if (o1 instanceof Object[]) { + Object[] a1 = ((Object[]) o1); + Object[] a2 = ((Object[]) o2); + Assert.assertEquals(message + ": array length", a1.length, + a2.length); + for (int i = 0; i < a1.length; i++) { + assertEquals(message, a1[i], a2[i]); + } + return; + } + List<EqualsAsserter<Object>> comparators = getComparators(o1); if (!comparators.isEmpty()) { for (EqualsAsserter<Object> ec : comparators) { @@ -113,7 +135,9 @@ public abstract class DeclarativeTestBaseBase<T extends Component> { protected abstract <TT> EqualsAsserter<TT> getComparator(Class<TT> c); private boolean isVaadin(Class<?> c) { - return c.getPackage().getName().startsWith("com.vaadin"); + return c.getPackage() != null + && c.getPackage().getName().startsWith("com.vaadin"); + } public void testRead(String design, T expected) { @@ -121,7 +145,11 @@ public abstract class DeclarativeTestBaseBase<T extends Component> { } public void testWrite(String design, T expected) { - String written = write(expected); + testWrite(design, expected, false); + } + + public void testWrite(String design, T expected, boolean writeData) { + String written = write(expected, writeData); Element producedElem = Jsoup.parse(written).body().child(0); Element comparableElem = Jsoup.parse(design).body().child(0); @@ -132,6 +160,10 @@ public abstract class DeclarativeTestBaseBase<T extends Component> { Assert.assertEquals(comparable, produced); } + protected Element createElement(Component c) { + return new DesignContext().createElement(c); + } + private String elementToHtml(Element producedElem) { StringBuilder stringBuilder = new StringBuilder(); elementToHtml(producedElem, stringBuilder); diff --git a/server/tests/src/com/vaadin/tests/design/DesignContextLocalIdTest.java b/server/tests/src/com/vaadin/tests/design/DesignContextLocalIdTest.java new file mode 100644 index 0000000000..c3d7e6d8cf --- /dev/null +++ b/server/tests/src/com/vaadin/tests/design/DesignContextLocalIdTest.java @@ -0,0 +1,60 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.design; + +import static org.junit.Assert.assertEquals; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; + +import org.junit.Test; + +import com.vaadin.ui.Button; +import com.vaadin.ui.TextField; +import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.declarative.Design; +import com.vaadin.ui.declarative.DesignContext; + +/** + * Tests that setting local id via DesignContext works as intended. + * + * @since + * @author Vaadin Ltd + */ +public class DesignContextLocalIdTest { + + @Test + public void testSetLocalId() throws FileNotFoundException { + DesignContext ctx = Design.read(new FileInputStream( + "server/tests/src/com/vaadin/tests/design/local-ids.html"), + new VerticalLayout()); + TextField tf = (TextField) ctx.getComponentByLocalId("foo"); + Button b = (Button) ctx.getComponentByLocalId("bar"); + // A duplicate id should be handled by removing the id from the old + // component. + ctx.setComponentLocalId(b, "foo"); + assertEquals("Found the wrong component by local id.", ctx + .getComponentByLocalId("foo").getClass(), Button.class); + assertEquals("Found the wrong component by local id.", + ctx.getComponentByLocalId("bar"), null); + // Set an id also for the text field. + ctx.setComponentLocalId(tf, "bar"); + assertEquals("Found the wrong component by local id.", ctx + .getComponentByLocalId("foo").getClass(), Button.class); + assertEquals("Found the wrong component by local id.", ctx + .getComponentByLocalId("bar").getClass(), TextField.class); + } +} diff --git a/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java b/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java index 05b2484767..681b9d80a3 100644 --- a/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java +++ b/server/tests/src/com/vaadin/tests/design/DesignFormatterTest.java @@ -24,10 +24,14 @@ import java.util.Date; import java.util.HashSet; import java.util.TimeZone; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import com.vaadin.data.util.converter.Converter.ConversionException; import com.vaadin.event.ShortcutAction; +import com.vaadin.event.ShortcutAction.KeyCode; +import com.vaadin.event.ShortcutAction.ModifierKey; import com.vaadin.server.ExternalResource; import com.vaadin.server.FileResource; import com.vaadin.server.Resource; @@ -203,10 +207,8 @@ public class DesignFormatterTest { @Test public void testShortcutActionNoCaption() { - ShortcutAction action = new ShortcutAction(null, - ShortcutAction.KeyCode.D, new int[] { - ShortcutAction.ModifierKey.ALT, - ShortcutAction.ModifierKey.CTRL }); + ShortcutAction action = new ShortcutAction(null, KeyCode.D, new int[] { + ModifierKey.ALT, ModifierKey.CTRL }); String formatted = formatter.format(action); assertEquals("alt-ctrl-d", formatted); @@ -216,6 +218,23 @@ public class DesignFormatterTest { } @Test + public void testInvalidShortcutAction() { + assertInvalidShortcut("-"); + assertInvalidShortcut("foo"); + assertInvalidShortcut("atl-ctrl"); + assertInvalidShortcut("-a"); + } + + protected void assertInvalidShortcut(String shortcut) { + try { + formatter.parse(shortcut, ShortcutAction.class); + Assert.fail("Invalid shortcut '" + shortcut + "' should throw"); + } catch (ConversionException e) { + // expected + } + } + + @Test public void testTimeZone() { TimeZone zone = TimeZone.getTimeZone("GMT+2"); String formatted = formatter.format(zone); @@ -227,6 +246,25 @@ public class DesignFormatterTest { assertEquals(zone, result); } + @Test + public void testExternalResource() { + String url = "://example.com/my%20icon.png?a=b"; + + for (String scheme : new String[] { "http", "https", "ftp", "ftps" }) { + Resource resource = formatter.parse(scheme + url, Resource.class); + + assertTrue(scheme + " url should be parsed as ExternalResource", + resource instanceof ExternalResource); + assertEquals("parsed ExternalResource", scheme + url, + ((ExternalResource) resource).getURL()); + + String formatted = formatter.format(new ExternalResource(scheme + + url)); + + assertEquals("formatted ExternalResource", scheme + url, formatted); + } + } + /** * A static method to allow comparison two different actions. * diff --git a/server/tests/src/com/vaadin/tests/design/local-ids.html b/server/tests/src/com/vaadin/tests/design/local-ids.html new file mode 100644 index 0000000000..638d004124 --- /dev/null +++ b/server/tests/src/com/vaadin/tests/design/local-ids.html @@ -0,0 +1,4 @@ +<v-vertical-layout> + <v-text-field caption="Enter your name" _id="foo"/> + <v-button _id="bar">Say hello</v-button> +</v-vertical-layout> diff --git a/server/tests/src/com/vaadin/tests/server/ClassesSerializableTest.java b/server/tests/src/com/vaadin/tests/server/ClassesSerializableTest.java index 0de8fd6aab..9603032ce5 100644 --- a/server/tests/src/com/vaadin/tests/server/ClassesSerializableTest.java +++ b/server/tests/src/com/vaadin/tests/server/ClassesSerializableTest.java @@ -16,7 +16,6 @@ import java.util.jar.JarFile; import junit.framework.TestCase; -import org.junit.Ignore; import org.junit.Test; public class ClassesSerializableTest extends TestCase { @@ -80,6 +79,7 @@ public class ClassesSerializableTest extends TestCase { "com\\.vaadin\\.external\\..*", // "com\\.vaadin\\.util\\.WeakValueMap.*", // "com\\.vaadin\\.themes\\.valoutil\\.BodyStyleName", // + "com\\.vaadin\\.server\\.communication\\.JSR356WebsocketInitializer.*", // }; /** diff --git a/server/tests/src/com/vaadin/tests/server/component/abstractselect/AbstractSelectDeclarativeTest.java b/server/tests/src/com/vaadin/tests/server/component/abstractselect/AbstractSelectDeclarativeTest.java index 61128a1803..b3867a7a3a 100644 --- a/server/tests/src/com/vaadin/tests/server/component/abstractselect/AbstractSelectDeclarativeTest.java +++ b/server/tests/src/com/vaadin/tests/server/component/abstractselect/AbstractSelectDeclarativeTest.java @@ -137,12 +137,18 @@ public class AbstractSelectDeclarativeTest extends } @Test - public void testWriteInlineData() { + public void testWriteInlineDataIgnored() { // No data is written by default testWrite(stripOptionTags(getDesignForInlineData()), getExpectedComponentForInlineData()); } + @Test + public void testWriteInlineData() { + testWrite(getDesignForInlineData(), + getExpectedComponentForInlineData(), true); + } + private String getDesignForInlineData() { return "<v-list-select>\n" + " <option icon='http://some.url/icon.png'>Value 1</option>\n" // diff --git a/server/tests/src/com/vaadin/tests/server/component/colorpicker/AbstractColorPickerDeclarativeTest.java b/server/tests/src/com/vaadin/tests/server/component/colorpicker/AbstractColorPickerDeclarativeTest.java new file mode 100644 index 0000000000..59b2efdc42 --- /dev/null +++ b/server/tests/src/com/vaadin/tests/server/component/colorpicker/AbstractColorPickerDeclarativeTest.java @@ -0,0 +1,87 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.server.component.colorpicker; + +import org.junit.Test; + +import com.vaadin.shared.ui.colorpicker.Color; +import com.vaadin.tests.design.DeclarativeTestBase; +import com.vaadin.ui.AbstractColorPicker; +import com.vaadin.ui.AbstractColorPicker.PopupStyle; +import com.vaadin.ui.ColorPicker; +import com.vaadin.ui.ColorPickerArea; + +public class AbstractColorPickerDeclarativeTest extends + DeclarativeTestBase<AbstractColorPicker> { + + @Test + public void testAllAbstractColorPickerFeatures() { + String design = "<v-color-picker color='#fafafa' default-caption-enabled='true' position='100,100'" + + " popup-style='simple' rgb-visibility='false' hsv-visibility='false'" + + " history-visibility=false textfield-visibility=false />"; + ColorPicker colorPicker = new ColorPicker(); + int colorInt = Integer.parseInt("fafafa", 16); + colorPicker.setColor(new Color(colorInt)); + colorPicker.setDefaultCaptionEnabled(true); + colorPicker.setPosition(100, 100); + colorPicker.setPopupStyle(PopupStyle.POPUP_SIMPLE); + colorPicker.setRGBVisibility(false); + colorPicker.setHSVVisibility(false); + colorPicker.setSwatchesVisibility(true); + colorPicker.setHistoryVisibility(false); + colorPicker.setTextfieldVisibility(false); + + testWrite(design, colorPicker); + testRead(design, colorPicker); + } + + @Test + public void testEmptyColorPicker() { + String design = "<v-color-picker />"; + ColorPicker colorPicker = new ColorPicker(); + testRead(design, colorPicker); + testWrite(design, colorPicker); + } + + @Test + public void testAllAbstractColorPickerAreaFeatures() { + String design = "<v-color-picker-area color='#fafafa' default-caption-enabled='true' position='100,100'" + + " popup-style='simple' rgb-visibility='false' hsv-visibility='false'" + + " history-visibility=false textfield-visibility=false />"; + AbstractColorPicker colorPicker = new ColorPickerArea(); + int colorInt = Integer.parseInt("fafafa", 16); + colorPicker.setColor(new Color(colorInt)); + colorPicker.setDefaultCaptionEnabled(true); + colorPicker.setPosition(100, 100); + colorPicker.setPopupStyle(PopupStyle.POPUP_SIMPLE); + colorPicker.setRGBVisibility(false); + colorPicker.setHSVVisibility(false); + colorPicker.setSwatchesVisibility(true); + colorPicker.setHistoryVisibility(false); + colorPicker.setTextfieldVisibility(false); + + testWrite(design, colorPicker); + testRead(design, colorPicker); + } + + @Test + public void testEmptyColorPickerArea() { + String design = "<v-color-picker-area />"; + AbstractColorPicker colorPicker = new ColorPickerArea(); + testRead(design, colorPicker); + testWrite(design, colorPicker); + } +} diff --git a/server/tests/src/com/vaadin/tests/server/component/popupview/PopupViewDeclarativeTest.java b/server/tests/src/com/vaadin/tests/server/component/popupview/PopupViewDeclarativeTest.java new file mode 100644 index 0000000000..8bad68f5b9 --- /dev/null +++ b/server/tests/src/com/vaadin/tests/server/component/popupview/PopupViewDeclarativeTest.java @@ -0,0 +1,74 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.server.component.popupview; + +import org.junit.Test; + +import com.vaadin.tests.design.DeclarativeTestBase; +import com.vaadin.ui.Component; +import com.vaadin.ui.Label; +import com.vaadin.ui.PopupView; +import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.declarative.DesignContext; + +public class PopupViewDeclarativeTest extends DeclarativeTestBase<PopupView> { + + @Test + public void testEmptyPopupView() { + PopupView component = new PopupView(); + Component popup = component.getContent().getPopupComponent(); + String design = "<v-popup-view><popup-content>" + + new DesignContext().createElement(popup) + + "</popup-content></v-popup-view>"; + testWrite(design, component); + testRead(design, component); + } + + @Test + public void testVisiblePopupDesign() { + final VerticalLayout verticalLayout = new VerticalLayout(); + verticalLayout.setWidth("300px"); + verticalLayout.setHeight("400px"); + + PopupView component = new PopupView("Click <u>here</u> to open", + verticalLayout); + component.setHideOnMouseOut(true); + component.setPopupVisible(true); + // hide-on-mouse-out is true by default. not seen in design + String design = "<v-popup-view popup-visible='true'>" // + + "Click <u>here</u> to open" + + "<popup-content>" + + new DesignContext().createElement(verticalLayout) + + "</popup-content>" // + + "</v-popup-view>"; + testWrite(design, component); + testRead(design, component); + } + + @Test + public void testHideOnMouseOutDisabled() { + final Label label = new Label("Foo"); + PopupView component = new PopupView("Click Me!", label); + component.setHideOnMouseOut(false); + String design = "<v-popup-view hide-on-mouse-out='false'>" // + + "Click Me!" + + "<popup-content>" + + new DesignContext().createElement(label) + "</popup-content>" // + + "</v-popup-view>"; + testWrite(design, component); + testRead(design, component); + } +} diff --git a/server/tests/src/com/vaadin/tests/server/component/window/WindowDeclarativeTest.java b/server/tests/src/com/vaadin/tests/server/component/window/WindowDeclarativeTest.java new file mode 100644 index 0000000000..1ab0011442 --- /dev/null +++ b/server/tests/src/com/vaadin/tests/server/component/window/WindowDeclarativeTest.java @@ -0,0 +1,153 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.server.component.window; + +import org.junit.Assert; +import org.junit.Test; + +import com.vaadin.event.ShortcutAction.KeyCode; +import com.vaadin.event.ShortcutAction.ModifierKey; +import com.vaadin.shared.ui.window.WindowMode; +import com.vaadin.shared.ui.window.WindowRole; +import com.vaadin.tests.design.DeclarativeTestBase; +import com.vaadin.ui.Button; +import com.vaadin.ui.Label; +import com.vaadin.ui.Window; +import com.vaadin.ui.declarative.DesignException; + +/** + * Tests declarative support for implementations of {@link Window}. + * + * @since + * @author Vaadin Ltd + */ +public class WindowDeclarativeTest extends DeclarativeTestBase<Window> { + + @Test + public void testDefault() { + String design = "<v-window>"; + + Window expected = new Window(); + + testRead(design, expected); + testWrite(design, expected); + } + + @Test + public void testFeatures() { + + String design = "<v-window position='100,100' window-mode='maximized' " + + "center modal=true resizable=false resize-lazy=true closable=false draggable=false " + + "close-shortcut='ctrl-alt-escape' " + + "assistive-prefix='Hello' assistive-postfix='World' assistive-role='alertdialog' " + + "tab-stop-enabled=true " + + "tab-stop-top-assistive-text='Do not move above the window' " + + "tab-stop-bottom-assistive-text='End of window'>" + + "</v-window>"; + + Window expected = new Window(); + + expected.setPositionX(100); + expected.setPositionY(100); + expected.setWindowMode(WindowMode.MAXIMIZED); + + expected.center(); + expected.setModal(!expected.isModal()); + expected.setResizable(!expected.isResizable()); + expected.setResizeLazy(!expected.isResizeLazy()); + expected.setClosable(!expected.isClosable()); + expected.setDraggable(!expected.isDraggable()); + + expected.setCloseShortcut(KeyCode.ESCAPE, ModifierKey.CTRL, + ModifierKey.ALT); + + expected.setAssistivePrefix("Hello"); + expected.setAssistivePostfix("World"); + expected.setAssistiveRole(WindowRole.ALERTDIALOG); + expected.setTabStopEnabled(!expected.isTabStopEnabled()); + expected.setTabStopTopAssistiveText("Do not move above the window"); + expected.setTabStopBottomAssistiveText("End of window"); + + testRead(design, expected); + testWrite(design, expected); + } + + @Test + public void testInvalidPosition() { + assertInvalidPosition(""); + assertInvalidPosition("1"); + assertInvalidPosition("100,100.1"); + assertInvalidPosition("x"); + assertInvalidPosition("2,foo"); + // Should be invalid, not checked currently + // assertInvalidPosition("1,2,3"); + } + + protected void assertInvalidPosition(String position) { + try { + read("<v-window position='" + position + "'>"); + Assert.fail("Invalid position '" + position + "' should throw"); + } catch (Exception e) { + // expected + } + } + + @Test + public void testChildContent() { + + String design = "<v-window>" + createElement(new Button("OK")) + + "</v-window>"; + + Window expected = new Window(); + expected.setContent(new Button("OK")); + + testRead(design, expected); + testWrite(design, expected); + } + + @Test(expected = DesignException.class) + public void testMultipleContentChildren() { + + String design = "<v-window>" + createElement(new Label("Hello")) + + createElement(new Button("OK")) + "</v-window>"; + + read(design); + } + + @Test + public void testAssistiveDescription() { + + Label assistive1 = new Label("Assistive text"); + Label assistive2 = new Label("More assistive text"); + + String design = "<v-window>" + + createElement(assistive1).attr(":assistive-description", "") + + createElement(new Button("OK")) + + createElement(assistive2).attr(":assistive-description", ""); + + Window expected = new Window(); + expected.setContent(new Button("OK")); + expected.setAssistiveDescription(assistive1, assistive2); + + testRead(design, expected); + + String written = "<v-window>" + createElement(new Button("OK")) + + createElement(assistive1).attr(":assistive-description", "") + + createElement(assistive2).attr(":assistive-description", ""); + + testWrite(written, expected); + } +} diff --git a/uitest/src/com/vaadin/tests/integration/ServletIntegrationJSR356WebsocketUITest.java b/uitest/src/com/vaadin/tests/integration/ServletIntegrationJSR356WebsocketUITest.java new file mode 100644 index 0000000000..f118d38158 --- /dev/null +++ b/uitest/src/com/vaadin/tests/integration/ServletIntegrationJSR356WebsocketUITest.java @@ -0,0 +1,32 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.integration; + +public class ServletIntegrationJSR356WebsocketUITest extends + AbstractServletIntegrationTest { + // Uses the test method declared in the super class + + @Override + protected String getDeploymentPath(Class<?> uiClass) { + return super.getDeploymentPath(uiClass) + .replace("/run/", "/run-jsr356/"); + } + + @Override + protected Class<?> getUIClass() { + return ServletIntegrationWebsocketUI.class; + } +} diff --git a/uitest/src/com/vaadin/tests/tb3/ServletIntegrationTests.java b/uitest/src/com/vaadin/tests/tb3/ServletIntegrationTests.java index 77c5676215..2f97ce27c3 100644 --- a/uitest/src/com/vaadin/tests/tb3/ServletIntegrationTests.java +++ b/uitest/src/com/vaadin/tests/tb3/ServletIntegrationTests.java @@ -17,21 +17,84 @@ package com.vaadin.tests.tb3; import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import org.junit.runner.RunWith; import org.junit.runners.model.InitializationError; import com.vaadin.tests.integration.AbstractServletIntegrationTest; +import com.vaadin.tests.integration.ServletIntegrationJSR356WebsocketUITest; +import com.vaadin.tests.integration.ServletIntegrationWebsocketUITest; import com.vaadin.tests.tb3.ServletIntegrationTests.ServletIntegrationTestSuite; @RunWith(ServletIntegrationTestSuite.class) public class ServletIntegrationTests { + public static Set<String> notJSR356Compatible = new HashSet<String>(); + public static Set<String> notWebsocketCompatible = new HashSet<String>(); + static { + notJSR356Compatible.add("jboss4"); + notJSR356Compatible.add("jboss5"); + notJSR356Compatible.add("jboss6"); + notJSR356Compatible.add("jbosseap6"); + notJSR356Compatible.add("jboss7"); + + notJSR356Compatible.add("jetty7"); + notJSR356Compatible.add("jetty8"); + + notJSR356Compatible.add("glassfish3"); + + notJSR356Compatible.add("tomcat6"); + notJSR356Compatible.add("tomcat7"); + notJSR356Compatible.add("tomcat7apacheproxy"); + notJSR356Compatible.add("weblogic10"); + notJSR356Compatible.add("osgi"); // Karaf 3, Jetty 8 + + notWebsocketCompatible.add("glassfish2"); + // In theory GF3 could work but in reality broken + notWebsocketCompatible.add("glassfish3"); + notWebsocketCompatible.add("jboss4"); + notWebsocketCompatible.add("jboss5"); + notWebsocketCompatible.add("jboss6"); + notWebsocketCompatible.add("jboss7"); + notWebsocketCompatible.add("jbosseap6"); + notWebsocketCompatible.add("jetty5"); + notWebsocketCompatible.add("jetty6"); + notWebsocketCompatible.add("tomcat5"); + notWebsocketCompatible.add("tomcat6"); + notWebsocketCompatible.add("tomcat7apacheproxy"); + notWebsocketCompatible.add("weblogic10"); + } + public static class ServletIntegrationTestSuite extends TB3TestSuite { public ServletIntegrationTestSuite(Class<?> klass) throws InitializationError, IOException { super(klass, AbstractServletIntegrationTest.class, - "com.vaadin.tests.integration", new String[] {}); + "com.vaadin.tests.integration", new String[] {}, + new ServletTestLocator()); + } + } + + public static class ServletTestLocator extends TB3TestLocator { + @Override + protected <T> List<Class<? extends T>> findClasses(Class<T> baseClass, + String basePackage, String[] ignoredPackages) + throws IOException { + List<Class<? extends T>> allClasses = super.findClasses(baseClass, + basePackage, ignoredPackages); + String serverName = System.getProperty("server-name"); + + if (notJSR356Compatible.contains(serverName)) { + allClasses + .remove(ServletIntegrationJSR356WebsocketUITest.class); + } + + if (notWebsocketCompatible.contains(serverName)) { + allClasses.remove(ServletIntegrationWebsocketUITest.class); + } + return allClasses; } } } |