]> source.dussan.org Git - vaadin-framework.git/commitdiff
Bootstrap UI using relative URLs with servlets (#6771) 59/59/5
authorLeif Åstrand <leif@vaadin.com>
Mon, 8 Oct 2012 05:11:51 +0000 (08:11 +0300)
committerVaadin Code Review <review@vaadin.com>
Mon, 8 Oct 2012 10:35:02 +0000 (10:35 +0000)
* Configure widgetset using URLs relative to the requested page
* Provide a Util method for getting an absolute URL from a relative URL
* Test by using an embedded Jetty acting as a transparent proxy
* Make /embed1 use the Buttons test to enable testing UIDL requests

Change-Id: I4ef9b40e3954ae16b682d743a339f4360db40d4d

20 files changed:
WebContent/VAADIN/vaadinBootstrap.js
WebContent/WEB-INF/web.xml
client/src/com/vaadin/client/ApplicationConfiguration.java
client/src/com/vaadin/client/ApplicationConnection.java
client/src/com/vaadin/client/ResourceLoader.java
client/src/com/vaadin/client/Util.java
server/src/com/vaadin/server/BootstrapHandler.java
server/src/com/vaadin/server/CombinedRequest.java
server/src/com/vaadin/server/CommunicationManager.java
server/src/com/vaadin/server/GAEVaadinServlet.java
server/src/com/vaadin/server/PortletCommunicationManager.java
server/src/com/vaadin/server/VaadinPortletService.java
server/src/com/vaadin/server/VaadinService.java
server/src/com/vaadin/server/VaadinServlet.java
server/src/com/vaadin/server/VaadinServletService.java
shared/src/com/vaadin/shared/ApplicationConstants.java
uitest/ivy.xml
uitest/src/com/vaadin/launcher/ApplicationRunnerServlet.java
uitest/src/com/vaadin/tests/integration/ProxyTest.html [new file with mode: 0644]
uitest/src/com/vaadin/tests/integration/ProxyTest.java [new file with mode: 0644]

index 3fff0bd8292f69a657de6abe5389554d129cc6a8..8c6e80a14f4beafd63f2edec399782ce97109154 100644 (file)
                                log('Fetching root config');
                                var url = getConfig('browserDetailsUrl');
                                if (!url) {
-                                       // No special url defined, use the default URL
-                                       url = getConfig('appUri');
-                                       // Add a slash to the end, because ApplicationConiguration.loadFromDOM does so...
-                                       if (url.length == 0 || url.substr(url.length-1) !== "/") {
-                                               url += '/';
-                                       }
+                                       // No special url defined, use the same URL that loaded this page (without the fragment)
+                                       url = window.location.href.replace(/#.*/,'');
                                }
                                url += ((/\?/).test(url) ? "&" : "?") + "browserDetails=1";
                                var rootId = getConfig("rootId");
                                        url += "&rootId=" + rootId;
                                }
 
-                               var initialPath = getConfig("initialPath");
-                               if (initialPath !== undefined) {
-                                       url += '&initialPath=' + encodeURIComponent(initialPath);
+                               // Tell the UI what theme it is configured to use
+                               var theme = getConfig('theme');
+                               if (theme !== undefined) {
+                                       url += '&theme=' + encodeURIComponent(theme);
                                }
-                               url += '&initialParams=' + encodeURIComponent(JSON.stringify(getConfig("initialParams")));
                                
                                url += '&' + vaadin.getBrowserDetailsParameters(appId); 
                                
                        }
                        
                        var bootstrapApp = function(mayDefer) {
-                               var themeUri = getConfig('themeUri');
+                               var vaadinDir = getConfig('vaadinDir');
+                               
+                               var themeUri = vaadinDir + 'themes/' + getConfig('theme')
                                loadTheme(themeUri);
                                
-                               var widgetsetBase = getConfig('widgetsetBase');
                                var widgetset = getConfig('widgetset');
-                               loadWidgetset(widgetsetBase, widgetset);
+                               loadWidgetset(vaadinDir + 'widgetsets/', widgetset);
                                
                                if (getConfig('uidl') === undefined) {
                                        if (mayDefer) {
index ea15e2d2a16a65fc6fc34f87f0a909f968360cc2..e8ac6b779704fd8e1c6300334bfa0476140f6b97 100644 (file)
@@ -24,7 +24,7 @@
                <servlet-class>com.vaadin.server.LegacyVaadinServlet</servlet-class>
                <init-param>
                        <param-name>application</param-name>
-                       <param-value>com.vaadin.tests.components.absolutelayout.AbsoluteLayoutClipping</param-value>
+                       <param-value>com.vaadin.tests.components.button.Buttons</param-value>
                </init-param>
        </servlet>
        <servlet>
index f620a39a70d7ea1b76a110f5be493963ceb0960a..9d668121a731745bf3cabc69897e6a274634db3b 100644 (file)
@@ -189,8 +189,12 @@ public class ApplicationConfiguration implements EntryPoint {
     private static WidgetSet widgetSet = GWT.create(WidgetSet.class);
 
     private String id;
-    private String themeUri;
-    private String appUri;
+    /**
+     * The URL to the VAADIN directory containing themes and widgetsets. Should
+     * always end with a slash (/).
+     */
+    private String vaadinDirUrl;
+    private String serviceUrl;
     private int uiId;
     private boolean standalone;
     private ErrorMessage communicationError;
@@ -214,13 +218,20 @@ public class ApplicationConfiguration implements EntryPoint {
     private Map<Integer, Integer> componentInheritanceMap = new HashMap<Integer, Integer>();
     private Map<Integer, String> tagToServerSideClassName = new HashMap<Integer, String>();
 
-    public boolean usePortletURLs() {
-        return getPortletResourceUrl() != null;
-    }
-
-    public String getPortletResourceUrl() {
-        return getJsoConfiguration(id).getConfigString(
-                ApplicationConstants.PORTLET_RESOUCE_URL_BASE);
+    /**
+     * Checks whether path info in requests to the server-side service should be
+     * in a request parameter (named
+     * {@value ApplicationConstants#V_RESOURCE_PATH}) or appended to the end of
+     * the service URL.
+     * 
+     * @see #getServiceUrl()
+     * 
+     * @return <code>true</code> if path info should be a request parameter;
+     *         <code>false</code> if the path info goes after the service URL
+     */
+    public boolean useServiceUrlPathParam() {
+        return getJsoConfiguration(id).getConfigBoolean(
+                ApplicationConstants.SERVICE_URL_PATH_AS_PARAMETER) == Boolean.TRUE;
     }
 
     public String getRootPanelId() {
@@ -228,24 +239,28 @@ public class ApplicationConfiguration implements EntryPoint {
     }
 
     /**
-     * Gets the application base URI. Using this other than as the download
-     * action URI can cause problems in Portlet 2.0 deployments.
+     * Gets the URL to the server-side VaadinService. If
+     * {@link #useServiceUrlPathParam()} return <code>true</code>, the requested
+     * path info should be in the {@value ApplicationConstants#V_RESOURCE_PATH}
+     * query parameter; else the path info should be appended to the end of the
+     * URL.
+     * 
+     * @see #useServiceUrlPathParam()
      * 
-     * @return application base URI
+     * @return the URL to the server-side service as a string
      */
-    public String getApplicationUri() {
-        return appUri;
+    public String getServiceUrl() {
+        return serviceUrl;
     }
 
     public String getThemeName() {
-        String uri = getThemeUri();
-        String themeName = uri.substring(uri.lastIndexOf('/'));
+        String themeName = getJsoConfiguration(id).getConfigString("theme");
         themeName = themeName.replaceAll("[^a-zA-Z0-9]", "");
         return themeName;
     }
 
     public String getThemeUri() {
-        return themeUri;
+        return vaadinDirUrl + "themes/" + getThemeName();
     }
 
     public void setAppId(String appId) {
@@ -306,11 +321,29 @@ public class ApplicationConfiguration implements EntryPoint {
      */
     private void loadFromDOM() {
         JsoConfiguration jsoConfiguration = getJsoConfiguration(id);
-        appUri = jsoConfiguration.getConfigString("appUri");
-        if (appUri != null && !appUri.endsWith("/")) {
-            appUri += '/';
+        serviceUrl = jsoConfiguration
+                .getConfigString(ApplicationConstants.SERVICE_URL);
+        if (serviceUrl == null || "".equals(serviceUrl)) {
+            /*
+             * Use the current url without query parameters and fragment as the
+             * default value.
+             */
+            serviceUrl = Window.Location.getHref().replaceFirst("[?#].*", "");
+        } else {
+            /*
+             * Resolve potentially relative URLs to ensure they point to the
+             * desired locations even if the base URL of the page changes later
+             * (e.g. with pushState)
+             */
+            serviceUrl = Util.getAbsoluteUrl(serviceUrl);
+        }
+        // Ensure there's an ending slash (to make appending e.g. UIDL work)
+        if (!useServiceUrlPathParam() && !serviceUrl.endsWith("/")) {
+            serviceUrl += '/';
         }
-        themeUri = jsoConfiguration.getConfigString("themeUri");
+
+        vaadinDirUrl = Util.getAbsoluteUrl(jsoConfiguration
+                .getConfigString(ApplicationConstants.VAADIN_DIR_URL));
         uiId = jsoConfiguration.getConfigInteger(UIConstants.UI_ID_PARAMETER)
                 .intValue();
 
index ab28ad291bac1cbec19c5413f7beb25f29b52e9a..1e10762c288cb38123547a65ab3dbdea66051d4f 100644 (file)
@@ -527,16 +527,6 @@ public class ApplicationConnection {
     }
     }-*/;
 
-    /**
-     * Gets the application base URI. Using this other than as the download
-     * action URI can cause problems in Portlet 2.0 deployments.
-     * 
-     * @return application base URI
-     */
-    public String getAppUri() {
-        return configuration.getApplicationUri();
-    };
-
     /**
      * Indicates whether or not there are currently active UIDL requests. Used
      * internally to sequence requests properly, seldom needed in Widgets.
@@ -2604,29 +2594,29 @@ public class ApplicationConnection {
             String relativeUrl = uidlUri
                     .substring(ApplicationConstants.APP_PROTOCOL_PREFIX
                             .length());
-            if (getConfiguration().usePortletURLs()) {
+            ApplicationConfiguration conf = getConfiguration();
+            String serviceUrl = conf.getServiceUrl();
+            if (conf.useServiceUrlPathParam()) {
                 // Should put path in v-resourcePath parameter and append query
                 // params to base portlet url
                 String[] parts = relativeUrl.split("\\?", 2);
                 String path = parts[0];
 
-                String url = getConfiguration().getPortletResourceUrl();
-
                 // If there's a "?" followed by something, append it as a query
                 // string to the base URL
                 if (parts.length > 1) {
                     String appUrlParams = parts[1];
-                    url = addGetParameters(url, appUrlParams);
+                    serviceUrl = addGetParameters(serviceUrl, appUrlParams);
                 }
                 if (!path.startsWith("/")) {
                     path = '/' + path;
                 }
                 String pathParam = ApplicationConstants.V_RESOURCE_PATH + "="
                         + URL.encodeQueryString(path);
-                url = addGetParameters(url, pathParam);
-                uidlUri = url;
+                serviceUrl = addGetParameters(serviceUrl, pathParam);
+                uidlUri = serviceUrl;
             } else {
-                uidlUri = getAppUri() + relativeUrl;
+                uidlUri = serviceUrl + relativeUrl;
             }
         }
         return uidlUri;
index f9925f89a4b29b58bb6aff538c421daf50568bcf..eb718e24d040e12fd9808b15e4ed1076d211047a 100644 (file)
@@ -26,7 +26,6 @@ import com.google.gwt.core.client.Duration;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.Scheduler;
 import com.google.gwt.core.client.Scheduler.RepeatingCommand;
-import com.google.gwt.dom.client.AnchorElement;
 import com.google.gwt.dom.client.Document;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.LinkElement;
@@ -205,7 +204,7 @@ public class ResourceLoader {
      */
     public void loadScript(final String scriptUrl,
             final ResourceLoadListener resourceLoadListener) {
-        final String url = getAbsoluteUrl(scriptUrl);
+        final String url = Util.getAbsoluteUrl(scriptUrl);
         ResourceLoadEvent event = new ResourceLoadEvent(this, url, false);
         if (loadedResources.contains(url)) {
             if (resourceLoadListener != null) {
@@ -252,12 +251,6 @@ public class ResourceLoader {
         }
     }
 
-    private static String getAbsoluteUrl(String url) {
-        AnchorElement a = Document.get().createAnchorElement();
-        a.setHref(url);
-        return a.getHref();
-    }
-
     /**
      * Download a resource and notify a listener when the resource is loaded
      * without attempting to interpret the resource. When a resource has been
@@ -278,7 +271,7 @@ public class ResourceLoader {
      */
     public void preloadResource(String url,
             ResourceLoadListener resourceLoadListener) {
-        url = getAbsoluteUrl(url);
+        url = Util.getAbsoluteUrl(url);
         ResourceLoadEvent event = new ResourceLoadEvent(this, url, true);
         if (loadedResources.contains(url) || preloadedResources.contains(url)) {
             // Already loaded or preloaded -> just fire listener
@@ -363,7 +356,7 @@ public class ResourceLoader {
      */
     public void loadStylesheet(final String stylesheetUrl,
             final ResourceLoadListener resourceLoadListener) {
-        final String url = getAbsoluteUrl(stylesheetUrl);
+        final String url = Util.getAbsoluteUrl(stylesheetUrl);
         final ResourceLoadEvent event = new ResourceLoadEvent(this, url, false);
         if (loadedResources.contains(url)) {
             if (resourceLoadListener != null) {
index 5ec5939c7b69b926b7b09d874012db72bb36b8f9..7aea69a61d31639e6c78ae5464a4eb971057caf2 100644 (file)
@@ -24,6 +24,7 @@ import java.util.List;
 
 import com.google.gwt.core.client.Scheduler;
 import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.AnchorElement;
 import com.google.gwt.dom.client.Document;
 import com.google.gwt.dom.client.NativeEvent;
 import com.google.gwt.dom.client.Node;
@@ -1196,4 +1197,18 @@ public class Util {
         }
         return getSimpleName(p) + " (" + p.getConnectorId() + ")";
     }
+
+    /**
+     * Resolve a relative URL to an absolute URL based on the current document's
+     * location.
+     * 
+     * @param url
+     *            a string with the relative URL to resolve
+     * @return the corresponding absolute URL as a string
+     */
+    public static String getAbsoluteUrl(String url) {
+        AnchorElement a = Document.get().createAnchorElement();
+        a.setHref(url);
+        return a.getHref();
+    }
 }
index c08d3bdbb1510cff4e3bacf3d54f22b95e69fe3a..9a0e4c2071bde347c42689a48a1558ec3a8653a3 100644 (file)
@@ -21,7 +21,6 @@ import java.io.IOException;
 import java.io.OutputStreamWriter;
 import java.io.Serializable;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
@@ -101,7 +100,8 @@ public abstract class BootstrapHandler implements RequestHandler {
 
         public String getAppId() {
             if (appId == null) {
-                appId = getApplicationId(this);
+                appId = getRequest().getService().getMainDivId(getSession(),
+                        getRequest(), getUIClass());
             }
             return appId;
         }
@@ -274,16 +274,6 @@ public abstract class BootstrapHandler implements RequestHandler {
         return null;
     }
 
-    /**
-     * Creates and returns a unique ID for the DIV where the application is to
-     * be rendered.
-     * 
-     * @param context
-     * 
-     * @return the id to use in the DOM
-     */
-    protected abstract String getApplicationId(BootstrapContext context);
-
     public String getWidgetsetForUI(BootstrapContext context) {
         VaadinRequest request = context.getRequest();
 
@@ -411,7 +401,7 @@ public abstract class BootstrapHandler implements RequestHandler {
 
         String themeName = context.getThemeName();
         if (themeName != null) {
-            appConfig.put("themeUri", getThemeUri(context, themeName));
+            appConfig.put("theme", themeName);
         }
 
         JSONObject versionInfo = new JSONObject();
@@ -420,17 +410,6 @@ public abstract class BootstrapHandler implements RequestHandler {
 
         appConfig.put("widgetset", context.getWidgetsetName());
 
-        appConfig.put("initialPath", request.getRequestPathInfo());
-
-        Map<String, String[]> parameterMap = new HashMap<String, String[]>(
-                request.getParameterMap());
-
-        // Include theme as a fake initial param so that UI can know its theme
-        // for serving CustomLayout templates
-        parameterMap.put("theme", new String[] { themeName });
-
-        appConfig.put("initialParams", parameterMap);
-
         // Use locale from session if set, else from the request
         Locale locale = ServletPortletHelper.findLocale(null,
                 context.getSession(), context.getRequest());
@@ -457,11 +436,11 @@ public abstract class BootstrapHandler implements RequestHandler {
             appConfig.put("authErrMsg", authErrMsg);
         }
 
-        String staticFileLocation = vaadinService
-                .getStaticFileLocation(request);
-        String widgetsetBase = staticFileLocation + "/"
-                + VaadinServlet.WIDGETSET_DIRECTORY_PATH;
-        appConfig.put("widgetsetBase", widgetsetBase);
+        // getStaticFileLocation documented to never end with a slash
+        // vaadinDir should always end with a slash
+        String vaadinDir = vaadinService.getStaticFileLocation(request)
+                + "/VAADIN/";
+        appConfig.put(ApplicationConstants.VAADIN_DIR_URL, vaadinDir);
 
         if (!session.getConfiguration().isProductionMode()) {
             appConfig.put("debug", true);
@@ -474,12 +453,15 @@ public abstract class BootstrapHandler implements RequestHandler {
         appConfig.put("heartbeatInterval", vaadinService
                 .getDeploymentConfiguration().getHeartbeatInterval());
 
-        appConfig.put("appUri", getAppUri(context));
+        String serviceUrl = getServiceUrl(context);
+        if (serviceUrl != null) {
+            appConfig.put(ApplicationConstants.SERVICE_URL, serviceUrl);
+        }
 
         return appConfig;
     }
 
-    protected abstract String getAppUri(BootstrapContext context);
+    protected abstract String getServiceUrl(BootstrapContext context);
 
     /**
      * Get the URI for the application theme.
index 34ac0b6a6f0c6445eb3ae6e992d80a42ca81070c..5cb5b674b6c62703f032bc76f2fe2c9c7a225bd4 100644 (file)
@@ -20,15 +20,10 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
 import java.util.Locale;
 import java.util.Map;
 
-import org.json.JSONArray;
 import org.json.JSONException;
-import org.json.JSONObject;
 
 /**
  * A {@link VaadinRequest} with path and parameters from one request and
@@ -42,7 +37,6 @@ import org.json.JSONObject;
 public class CombinedRequest implements VaadinRequest {
 
     private final VaadinRequest secondRequest;
-    private Map<String, String[]> parameterMap;
 
     /**
      * Creates a new combined request based on the second request and some
@@ -56,37 +50,16 @@ public class CombinedRequest implements VaadinRequest {
      */
     public CombinedRequest(VaadinRequest secondRequest) throws JSONException {
         this.secondRequest = secondRequest;
-
-        HashMap<String, String[]> map = new HashMap<String, String[]>();
-        JSONObject initialParams = new JSONObject(
-                secondRequest.getParameter("initialParams"));
-        for (Iterator<?> keys = initialParams.keys(); keys.hasNext();) {
-            String name = (String) keys.next();
-            JSONArray jsonValues = initialParams.getJSONArray(name);
-            String[] values = new String[jsonValues.length()];
-            for (int i = 0; i < values.length; i++) {
-                values[i] = jsonValues.getString(i);
-            }
-            map.put(name, values);
-        }
-
-        parameterMap = Collections.unmodifiableMap(map);
-
     }
 
     @Override
     public String getParameter(String parameter) {
-        String[] strings = getParameterMap().get(parameter);
-        if (strings == null || strings.length == 0) {
-            return null;
-        } else {
-            return strings[0];
-        }
+        return secondRequest.getParameter(parameter);
     }
 
     @Override
     public Map<String, String[]> getParameterMap() {
-        return parameterMap;
+        return secondRequest.getParameterMap();
     }
 
     @Override
@@ -111,7 +84,7 @@ public class CombinedRequest implements VaadinRequest {
 
     @Override
     public String getRequestPathInfo() {
-        return secondRequest.getParameter("initialPath");
+        return secondRequest.getRequestPathInfo();
     }
 
     @Override
index 5f6107926155786bf7050642bffd4fa17363c68a..6cfaf37092c0a1ccfa2efbb48759d21b85a04576 100644 (file)
@@ -17,8 +17,6 @@
 package com.vaadin.server;
 
 import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
 
 import javax.servlet.ServletContext;
 
@@ -54,45 +52,19 @@ public class CommunicationManager extends AbstractCommunicationManager {
     protected BootstrapHandler createBootstrapHandler() {
         return new BootstrapHandler() {
             @Override
-            protected String getApplicationId(BootstrapContext context) {
-                String appUrl = getAppUri(context);
-
-                String appId = appUrl;
-                if ("".equals(appUrl)) {
-                    appId = "ROOT";
-                }
-                appId = appId.replaceAll("[^a-zA-Z0-9]", "");
-                // Add hashCode to the end, so that it is still (sort of)
-                // predictable, but indicates that it should not be used in CSS
-                // and
-                // such:
-                int hashCode = appId.hashCode();
-                if (hashCode < 0) {
-                    hashCode = -hashCode;
-                }
-                appId = appId + "-" + hashCode;
-                return appId;
-            }
-
-            @Override
-            protected String getAppUri(BootstrapContext context) {
-                /* Fetch relative url to application */
-                // don't use server and port in uri. It may cause problems with
-                // some
-                // virtual server configurations which lose the server name
-                URL url;
-
-                try {
-                    url = context.getRequest().getService()
-                            .getApplicationUrl(context.getRequest());
-                } catch (MalformedURLException e) {
-                    throw new RuntimeException(e);
-                }
-                String appUrl = url.getPath();
-                if (appUrl.endsWith("/")) {
-                    appUrl = appUrl.substring(0, appUrl.length() - 1);
+            protected String getServiceUrl(BootstrapContext context) {
+                String pathInfo = context.getRequest().getRequestPathInfo();
+                if (pathInfo == null) {
+                    return null;
+                } else {
+                    /*
+                     * Make a relative URL to the servlet by adding one ../ for
+                     * each path segment in pathInfo (i.e. the part of the
+                     * requested path that comes after the servlet mapping)
+                     */
+                    return VaadinServletService
+                            .getCancelingRelativePath(pathInfo);
                 }
-                return appUrl;
             }
 
             @Override
index c68f25a282108b2e51446565ef87761d5229047f..d2c53c6fcbf6391fc7a6850a4743fff1d461ab5f 100644 (file)
@@ -348,7 +348,7 @@ public class GAEVaadinServlet extends VaadinServlet {
     }
 
     private boolean isCleanupRequest(HttpServletRequest request) {
-        String path = getRequestPathInfo(request);
+        String path = request.getPathInfo();
         if (path != null && path.equals(CLEANUP_PATH)) {
             return true;
         }
index 8d39eee9acd6033b6649d8410b312db4fad401fc..635202a0747052d235fdfc753ade97bef15c4d4d 100644 (file)
@@ -65,19 +65,11 @@ public class PortletCommunicationManager extends AbstractCommunicationManager {
             }
 
             @Override
-            protected String getApplicationId(BootstrapContext context) {
-                PortletRequest portletRequest = VaadinPortletRequest.cast(
-                        context.getRequest()).getPortletRequest();
-                /*
-                 * We need to generate a unique ID because some portals already
-                 * create a DIV with the portlet's Window ID as the DOM ID.
-                 */
-                return "v-" + portletRequest.getWindowID();
-            }
-
-            @Override
-            protected String getAppUri(BootstrapContext context) {
-                return getRenderResponse(context).createActionURL().toString();
+            protected String getServiceUrl(BootstrapContext context) {
+                ResourceURL portletResourceUrl = getRenderResponse(context)
+                        .createResourceURL();
+                portletResourceUrl.setResourceID(VaadinPortlet.RESOURCE_URL_ID);
+                return portletResourceUrl.toString();
             }
 
             private RenderResponse getRenderResponse(BootstrapContext context) {
@@ -129,11 +121,10 @@ public class PortletCommunicationManager extends AbstractCommunicationManager {
                 resourceURL.setResourceID("browserDetails");
                 parameters.put("browserDetailsUrl", resourceURL.toString());
 
-                ResourceURL portletResourceUrl = getRenderResponse(context)
-                        .createResourceURL();
-                portletResourceUrl.setResourceID(VaadinPortlet.RESOURCE_URL_ID);
-                parameters.put(ApplicationConstants.PORTLET_RESOUCE_URL_BASE,
-                        portletResourceUrl.toString());
+                // Always send path info as a query parameter
+                parameters.put(
+                        ApplicationConstants.SERVICE_URL_PATH_AS_PARAMETER,
+                        true);
 
                 return parameters;
             }
index ac48900945bcefa29f8e69732c63de15ca46ee4b..12bf45f43ab3c07f80a735cbd9e65953c92d9973 100644 (file)
@@ -25,6 +25,7 @@ import javax.portlet.PortletContext;
 import javax.portlet.PortletRequest;
 
 import com.vaadin.server.VaadinPortlet.RequestType;
+import com.vaadin.ui.UI;
 
 public class VaadinPortletService extends VaadinService {
     private final VaadinPortlet portlet;
@@ -218,4 +219,16 @@ public class VaadinPortletService extends VaadinService {
     public boolean preserveUIOnRefresh(UIProvider provider, UICreateEvent event) {
         return true;
     }
-}
\ No newline at end of file
+
+    @Override
+    public String getMainDivId(VaadinServiceSession session,
+            VaadinRequest request, Class<? extends UI> uiClass) {
+        PortletRequest portletRequest = VaadinPortletRequest.cast(request)
+                .getPortletRequest();
+        /*
+         * We need to generate a unique ID because some portals already create a
+         * DIV with the portlet's Window ID as the DOM ID.
+         */
+        return "v-" + portletRequest.getWindowID();
+    }
+}
index 28684ec8dc94d81810fe82b59eefebb09b308083..d9c8b83ea4db94b3176a66cb67fdd7cebf83b5f4 100644 (file)
@@ -730,4 +730,20 @@ public abstract class VaadinService implements Serializable {
         }
 
     }
+
+    /**
+     * Creates and returns a unique ID for the DIV where the UI is to be
+     * rendered.
+     * 
+     * @param session
+     *            The service session to which the bootstrapped UI will belong.
+     * @param request
+     *            The request for which a div id is needed
+     * @param uiClass
+     *            The class of the UI that will be bootstrapped
+     * 
+     * @return the id to use in the DOM
+     */
+    public abstract String getMainDivId(VaadinServiceSession session,
+            VaadinRequest request, Class<? extends UI> uiClass);
 }
index bd724aad63f554b4d2e6e00701928f72cac86d22..68fca7b46353eb22b29f823b7b793c51ef6c8e90 100644 (file)
@@ -1165,26 +1165,6 @@ public class VaadinServlet extends HttpServlet implements Constants {
         return u;
     }
 
-    /**
-     * Returns the path info; note that this _can_ be different than
-     * request.getPathInfo(). Examples where this might be useful:
-     * <ul>
-     * <li>An application runner servlet that runs different Vaadin applications
-     * based on an identifier.</li>
-     * <li>Providing a REST interface in the context root, while serving a
-     * Vaadin UI on a sub-URI using only one servlet (e.g. REST on
-     * http://example.com/foo, UI on http://example.com/foo/vaadin)</li>
-     * 
-     * @param request
-     * @return
-     * 
-     * @deprecated might be refactored or removed before 7.0.0
-     */
-    @Deprecated
-    protected String getRequestPathInfo(HttpServletRequest request) {
-        return request.getPathInfo();
-    }
-
     public class RequestError implements Terminal.ErrorEvent, Serializable {
 
         private final Throwable throwable;
index b8af3b13fb4b501c3c5ae5e47fb36fc37816a1d1..9992b3698db0283b351bc673e771ec5b2d6aa1ac 100644 (file)
@@ -23,6 +23,7 @@ import java.net.URL;
 import javax.servlet.http.HttpServletRequest;
 
 import com.vaadin.server.VaadinServlet.RequestType;
+import com.vaadin.ui.UI;
 
 public class VaadinServletService extends VaadinService {
     private final VaadinServlet servlet;
@@ -50,31 +51,35 @@ public class VaadinServletService extends VaadinService {
         }
 
         // the last (but most common) option is to generate default location
-        // from request
+        // from request by finding how many "../" should be added to the
+        // requested path before we get to the context root
 
-        // if context is specified add it to widgetsetUrl
-        String ctxPath = servletRequest.getContextPath();
-
-        // FIXME: ctxPath.length() == 0 condition is probably unnecessary
-        // and
-        // might even be wrong.
-
-        if (ctxPath.length() == 0
-                && request.getAttribute("javax.servlet.include.context_path") != null) {
-            // include request (e.g portlet), get context path from
-            // attribute
-            ctxPath = (String) request
-                    .getAttribute("javax.servlet.include.context_path");
+        String requestedPath = servletRequest.getServletPath();
+        String pathInfo = servletRequest.getPathInfo();
+        if (pathInfo != null) {
+            requestedPath += pathInfo;
         }
 
-        // Remove heading and trailing slashes from the context path
-        ctxPath = VaadinServlet.removeHeadingOrTrailing(ctxPath, "/");
+        return getCancelingRelativePath(requestedPath);
+    }
 
-        if (ctxPath.equals("")) {
-            return "";
-        } else {
-            return "/" + ctxPath;
+    /**
+     * Gets a relative path that cancels the provided path. This essentially
+     * adds one .. for each part of the path to cancel.
+     * 
+     * @param pathToCancel
+     *            the path that should be canceled
+     * @return a relative path that cancels out the provided path segment
+     */
+    public static String getCancelingRelativePath(String pathToCancel) {
+        StringBuilder sb = new StringBuilder(".");
+        // Start from i = 1 to ignore first slash
+        for (int i = 1; i < pathToCancel.length(); i++) {
+            if (pathToCancel.charAt(i) == '/') {
+                sb.append("/..");
+            }
         }
+        return sb.toString();
     }
 
     @Override
@@ -184,4 +189,32 @@ public class VaadinServletService extends VaadinService {
     public String getServiceName() {
         return getServlet().getServletName();
     }
-}
\ No newline at end of file
+
+    @Override
+    public String getMainDivId(VaadinServiceSession session,
+            VaadinRequest request, Class<? extends UI> uiClass) {
+        String appId = null;
+        try {
+            URL appUrl = getServlet().getApplicationUrl(
+                    VaadinServletRequest.cast(request));
+            appId = appUrl.getPath();
+        } catch (MalformedURLException e) {
+            // Just ignore problem here
+        }
+
+        if (appId == null || "".equals(appId)) {
+            appId = "ROOT";
+        }
+        appId = appId.replaceAll("[^a-zA-Z0-9]", "");
+        // Add hashCode to the end, so that it is still (sort of)
+        // predictable, but indicates that it should not be used in CSS
+        // and
+        // such:
+        int hashCode = appId.hashCode();
+        if (hashCode < 0) {
+            hashCode = -hashCode;
+        }
+        appId = appId + "-" + hashCode;
+        return appId;
+    }
+}
index 2c0c0c4af0b8c91ff62d3cc15b6cec5ddc426862..079aa492ccd14ba17f63e099bd15b2f5da255233 100644 (file)
@@ -39,7 +39,9 @@ public class ApplicationConstants {
     @Deprecated
     public static final String UPDATE_VARIABLE_METHOD = "v";
 
-    public static final String PORTLET_RESOUCE_URL_BASE = "portletAppURLBase";
+    public static final String SERVICE_URL = "serviceUrl";
+
+    public static final String SERVICE_URL_PATH_AS_PARAMETER = "usePathParameter";
 
     public static final String V_RESOURCE_PATH = "v-resourcePath";
 
@@ -52,4 +54,14 @@ public class ApplicationConstants {
      * changes.
      */
     public static final String URL_PARAMETER_REPAINT_ALL = "repaintAll";
+
+    /**
+     * Configuration parameter giving the (in some cases relative) URL to the
+     * VAADIN folder from where themes and widgetsets are loaded.
+     * <p>
+     * <b>Refactor warning:</b> This value is also hardcoded in
+     * vaadinBootstrap.js.
+     * </p>
+     */
+    public static final String VAADIN_DIR_URL = "vaadinDir";
 }
index a261c741a1e48969dfd9af14fb6c872a63c24a8d..ce808326891be3e39ff296af4cee9bb74f791e91 100644 (file)
@@ -49,6 +49,8 @@
             mapping problem) -->
         <dependency org="org.eclipse.jetty" name="jetty-server"
             rev="7.4.5.v20110725" conf="build-provided, ide, jetty-run->default" />
+        <dependency org="org.eclipse.jetty" name="jetty-servlets"
+            rev="7.4.5.v20110725" conf="build-provided, ide, jetty-run->default" />
         <!-- <dependency org="org.mortbay.jetty" name="jetty-util" -->
         <!-- rev="8.1.5.v20120716" conf="build,ide,jetty-run->default" /> -->
         <dependency org="org.eclipse.jetty" name="jetty-webapp"
index 12d1cb2c383aea1909b5bf490023a0a537a4c618..0c2a1f965a0b3eb5c9aef38d7d153a99e0c8e327 100644 (file)
@@ -30,7 +30,6 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import com.vaadin.LegacyApplication;
-import com.vaadin.server.DeploymentConfiguration;
 import com.vaadin.server.LegacyVaadinServlet;
 import com.vaadin.server.ServiceException;
 import com.vaadin.server.SessionInitEvent;
@@ -40,7 +39,6 @@ import com.vaadin.server.UIProvider;
 import com.vaadin.server.VaadinRequest;
 import com.vaadin.server.VaadinServiceSession;
 import com.vaadin.server.VaadinServletRequest;
-import com.vaadin.server.VaadinServletService;
 import com.vaadin.tests.components.TestBase;
 import com.vaadin.ui.UI;
 
@@ -174,8 +172,9 @@ public class ApplicationRunnerServlet extends LegacyVaadinServlet {
         return getApplicationRunnerURIs(request).applicationClassname;
     }
 
+    // TODO Don't need to use a data object now that there's only one field
     private static class URIS {
-        String staticFilesPath;
+        // String staticFilesPath;
         // String applicationURI;
         // String context;
         // String runner;
@@ -201,21 +200,18 @@ public class ApplicationRunnerServlet extends LegacyVaadinServlet {
     private static URIS getApplicationRunnerURIs(HttpServletRequest request) {
         final String[] urlParts = request.getRequestURI().toString()
                 .split("\\/");
-        String context = null;
         // String runner = null;
         URIS uris = new URIS();
         String applicationClassname = null;
         String contextPath = request.getContextPath();
         if (urlParts[1].equals(contextPath.replaceAll("\\/", ""))) {
             // class name comes after web context and runner application
-            context = urlParts[1];
             // runner = urlParts[2];
             if (urlParts.length == 3) {
                 throw new IllegalArgumentException("No application specified");
             }
             applicationClassname = urlParts[3];
 
-            uris.staticFilesPath = "/" + context;
             // uris.applicationURI = "/" + context + "/" + runner + "/"
             // + applicationClassname;
             // uris.context = context;
@@ -223,14 +219,12 @@ public class ApplicationRunnerServlet extends LegacyVaadinServlet {
             uris.applicationClassname = applicationClassname;
         } else {
             // no context
-            context = "";
             // runner = urlParts[1];
             if (urlParts.length == 2) {
                 throw new IllegalArgumentException("No application specified");
             }
             applicationClassname = urlParts[2];
 
-            uris.staticFilesPath = "/";
             // uris.applicationURI = "/" + runner + "/" + applicationClassname;
             // uris.context = context;
             // uris.runner = runner;
@@ -274,47 +268,6 @@ public class ApplicationRunnerServlet extends LegacyVaadinServlet {
         throw new ClassNotFoundException();
     }
 
-    @Override
-    protected String getRequestPathInfo(HttpServletRequest request) {
-        String path = request.getPathInfo();
-        if (path == null) {
-            return null;
-        }
-
-        path = path.substring(1 + getApplicationRunnerApplicationClassName(
-                request).length());
-        return path;
-    }
-
-    @Override
-    protected VaadinServletService createServletService(
-            DeploymentConfiguration deploymentConfiguration) {
-        return new VaadinServletService(this, deploymentConfiguration) {
-            @Override
-            public String getStaticFileLocation(VaadinRequest request) {
-                URIS uris = getApplicationRunnerURIs(VaadinServletRequest
-                        .cast(request));
-                String staticFilesPath = uris.staticFilesPath;
-                if (staticFilesPath.equals("/")) {
-                    staticFilesPath = "";
-                }
-
-                return staticFilesPath;
-            }
-        };
-    }
-
-    @Override
-    protected VaadinServletRequest createVaadinRequest(
-            HttpServletRequest request) {
-        return new VaadinServletRequest(request, getService()) {
-            @Override
-            public String getRequestPathInfo() {
-                return ApplicationRunnerServlet.this.getRequestPathInfo(this);
-            }
-        };
-    }
-
     private Logger getLogger() {
         return Logger.getLogger(ApplicationRunnerServlet.class.getName());
     }
diff --git a/uitest/src/com/vaadin/tests/integration/ProxyTest.html b/uitest/src/com/vaadin/tests/integration/ProxyTest.html
new file mode 100644 (file)
index 0000000..f52f35e
--- /dev/null
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="" />
+<title>AddAndRemoveTabs</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr><td rowspan="1" colspan="3">AddAndRemoveTabs</td></tr>
+</thead><tbody>
+<tr>
+       <td>open</td>
+       <td>/run/com.vaadin.tests.integration.ProxyTest?restartApplication</td>
+       <td></td>
+</tr>
+<tr>
+       <td>click</td>
+       <td>vaadin=runcomvaadintestsintegrationProxyTest::/VVerticalLayout[0]/VOrderedLayout$Slot[1]/VVerticalLayout[0]/VOrderedLayout$Slot[0]/VButton[0]/domChild[0]/domChild[0]</td>
+       <td></td>
+</tr>
+<tr>
+       <td>click</td>
+       <td>vaadin=runcomvaadintestsintegrationProxyTest::/VVerticalLayout[0]/VOrderedLayout$Slot[1]/VVerticalLayout[0]/VOrderedLayout$Slot[2]/VVerticalLayout[0]/VOrderedLayout$Slot[0]/VLink[0]/domChild[0]/domChild[0]</td>
+       <td></td>
+</tr>
+<tr>
+       <td>pause</td>
+       <td></td>
+       <td>2000</td>
+</tr>
+<tr>
+       <td>assertText</td>
+       <td>vaadin=embed1::/VVerticalLayout[0]/VOrderedLayout$Slot[0]/VLabel[0]</td>
+       <td>A generic test for Buttons in different configurations</td>
+</tr>
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=embed1::/VVerticalLayout[0]/VOrderedLayout$Slot[1]/VVerticalLayout[0]/VOrderedLayout$Slot[0]/VHorizontalLayout[0]/VOrderedLayout$Slot[0]/VCheckBox[0]/domChild[0]</td>
+       <td>5,5</td>
+</tr>
+<tr>
+       <td>assertCSSClass</td>
+       <td>vaadin=embed1::/VVerticalLayout[0]/VOrderedLayout$Slot[1]/VVerticalLayout[0]/VOrderedLayout$Slot[1]/VButton[0]</td>
+       <td>v-disabled</td>
+</tr>
+<tr>
+       <td>open</td>
+       <td>/run/com.vaadin.tests.integration.ProxyTest</td>
+       <td></td>
+</tr>
+<tr>
+       <td>click</td>
+       <td>vaadin=runcomvaadintestsintegrationProxyTest::/VVerticalLayout[0]/VOrderedLayout$Slot[1]/VVerticalLayout[0]/VOrderedLayout$Slot[2]/VVerticalLayout[0]/VOrderedLayout$Slot[1]/VLink[0]/domChild[0]/domChild[0]</td>
+       <td></td>
+</tr>
+<tr>
+       <td>pause</td>
+       <td></td>
+       <td>2000</td>
+</tr>
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=embed1::/VVerticalLayout[0]/VOrderedLayout$Slot[1]/VVerticalLayout[0]/VOrderedLayout$Slot[0]/VHorizontalLayout[0]/VOrderedLayout$Slot[0]/VCheckBox[0]/domChild[0]</td>
+       <td>7,5</td>
+</tr>
+<tr>
+       <td>assertNotCSSClass</td>
+       <td>vaadin=embed1::/VVerticalLayout[0]/VOrderedLayout$Slot[1]/VVerticalLayout[0]/VOrderedLayout$Slot[1]/VButton[0]</td>
+       <td>v-disabled</td>
+</tr>
+<tr>
+       <td>open</td>
+       <td>/run/com.vaadin.tests.integration.ProxyTest</td>
+       <td></td>
+</tr>
+<tr>
+       <td>click</td>
+       <td>vaadin=runcomvaadintestsintegrationProxyTest::/VVerticalLayout[0]/VOrderedLayout$Slot[1]/VVerticalLayout[0]/VOrderedLayout$Slot[2]/VVerticalLayout[0]/VOrderedLayout$Slot[2]/VLink[0]/domChild[0]/domChild[0]</td>
+       <td></td>
+</tr>
+<tr>
+       <td>pause</td>
+       <td></td>
+       <td>2000</td>
+</tr>
+<tr>
+       <td>mouseClick</td>
+       <td>vaadin=runcomvaadintestscomponentsbuttonButtons::/VVerticalLayout[0]/VOrderedLayout$Slot[1]/VVerticalLayout[0]/VOrderedLayout$Slot[0]/VHorizontalLayout[0]/VOrderedLayout$Slot[0]/VCheckBox[0]/domChild[0]</td>
+       <td>35,7</td>
+</tr>
+<tr>
+       <td>assertCSSClass</td>
+       <td>vaadin=runcomvaadintestscomponentsbuttonButtons::/VVerticalLayout[0]/VOrderedLayout$Slot[1]/VVerticalLayout[0]/VOrderedLayout$Slot[1]/VButton[0]</td>
+       <td>v-disabled</td>
+</tr>
+<tr>
+       <td>open</td>
+       <td>/run/com.vaadin.tests.integration.ProxyTest</td>
+       <td></td>
+</tr>
+<tr>
+       <td>click</td>
+       <td>vaadin=runcomvaadintestsintegrationProxyTest::/VVerticalLayout[0]/VOrderedLayout$Slot[1]/VVerticalLayout[0]/VOrderedLayout$Slot[1]/VButton[0]/domChild[0]/domChild[0]</td>
+       <td></td>
+</tr>
+</tbody></table>
+</body>
+</html>
diff --git a/uitest/src/com/vaadin/tests/integration/ProxyTest.java b/uitest/src/com/vaadin/tests/integration/ProxyTest.java
new file mode 100644 (file)
index 0000000..97a4efe
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2011 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;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.nio.SelectChannelConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.servlets.ProxyServlet;
+
+import com.vaadin.annotations.PreserveOnRefresh;
+import com.vaadin.server.ExternalResource;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.server.VaadinServletService;
+import com.vaadin.tests.components.AbstractTestUI;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Button.ClickEvent;
+import com.vaadin.ui.Link;
+import com.vaadin.ui.VerticalLayout;
+
+@PreserveOnRefresh
+public class ProxyTest extends AbstractTestUI {
+
+    private Server server;
+
+    private final Button startButton = new Button("Start proxy",
+            new Button.ClickListener() {
+                @Override
+                public void buttonClick(ClickEvent event) {
+                    startProxy();
+                    stopButton.setEnabled(true);
+                }
+            });
+    private final Button stopButton = new Button("Stop proxy",
+            new Button.ClickListener() {
+                @Override
+                public void buttonClick(ClickEvent event) {
+                    stopProxy();
+                    startButton.setEnabled(true);
+                }
+            });
+    private VerticalLayout linkHolder = new VerticalLayout();
+
+    @Override
+    protected void setup(VaadinRequest request) {
+        stopButton.setDisableOnClick(true);
+        stopButton.setEnabled(false);
+        startButton.setDisableOnClick(true);
+
+        addCleanupListener(new CleanupListener() {
+            @Override
+            public void cleanup(CleanupEvent event) {
+                if (server != null && server.isRunning()) {
+                    try {
+                        server.stop();
+                    } catch (Exception e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+        });
+
+        addComponent(startButton);
+        addComponent(stopButton);
+        addComponent(linkHolder);
+    }
+
+    private void startProxy() {
+        HttpServletRequest request = VaadinServletService
+                .getCurrentServletRequest();
+
+        // Set up a server
+        server = new Server();
+        SelectChannelConnector connector = new SelectChannelConnector();
+        // Uses random available port by default, uncomment this to make local
+        // testing easier (you can just reload old tab after restarting proxy)
+        // connector.setPort(8889);
+        server.setConnectors(new Connector[] { connector });
+
+        // Create root context and add the ProxyServlet.Transparent to it
+        ServletContextHandler contextHandler = new ServletContextHandler();
+        server.setHandler(contextHandler);
+        contextHandler.setContextPath("/");
+        ServletHolder servletHolder = contextHandler.addServlet(
+                ProxyServlet.Transparent.class, "/*");
+
+        // Configure servlet to forward to the root of the original server
+        servletHolder.setInitParameter(
+                "ProxyTo",
+                "http://" + request.getLocalAddr() + ":"
+                        + request.getLocalPort() + "/");
+        // Configure servlet to strip beginning of paths
+        servletHolder.setInitParameter("Prefix", "/proxypath/");
+
+        // Start the server
+        try {
+            server.start();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+
+        // Add links to some proxied urls to tests
+        String linkBase = "http://" + request.getLocalName() + ":"
+                + connector.getLocalPort() + "/proxypath/";
+
+        linkHolder.removeAllComponents();
+        linkHolder.addComponent(new Link("Open embed1 in proxy",
+                new ExternalResource(linkBase + "embed1")));
+        linkHolder.addComponent(new Link("Open embed1/ in proxy",
+                new ExternalResource(linkBase + "embed1/")));
+        linkHolder.addComponent(new Link("Open Buttons in proxy",
+                new ExternalResource(linkBase
+                        + "run/com.vaadin.tests.components.button.Buttons")));
+
+    }
+
+    private void stopProxy() {
+        linkHolder.removeAllComponents();
+        try {
+            server.stop();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        server.destroy();
+        server = null;
+    }
+
+    @Override
+    protected String getTestDescription() {
+        return "Test UI for starting an embedded Jetty on a different port that proxies requests back to the original server using a different path.";
+    }
+
+    @Override
+    protected Integer getTicketNumber() {
+        return Integer.valueOf(6771);
+    }
+
+}