diff options
author | Leif Åstrand <leif@vaadin.com> | 2012-10-08 08:11:51 +0300 |
---|---|---|
committer | Vaadin Code Review <review@vaadin.com> | 2012-10-08 10:35:02 +0000 |
commit | 13d5b3e98954c2ade382305f8d044b2b49fdbd0b (patch) | |
tree | 37559368471cbed3a9ea159261869bcf5c82e52a | |
parent | 6337f8f8ccb88014f7a3d269332661ec8183b758 (diff) | |
download | vaadin-framework-13d5b3e98954c2ade382305f8d044b2b49fdbd0b.tar.gz vaadin-framework-13d5b3e98954c2ade382305f8d044b2b49fdbd0b.zip |
Bootstrap UI using relative URLs with servlets (#6771)
* 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, 495 insertions, 274 deletions
diff --git a/WebContent/VAADIN/vaadinBootstrap.js b/WebContent/VAADIN/vaadinBootstrap.js index 3fff0bd829..8c6e80a14f 100644 --- a/WebContent/VAADIN/vaadinBootstrap.js +++ b/WebContent/VAADIN/vaadinBootstrap.js @@ -81,12 +81,8 @@ 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"); @@ -94,11 +90,11 @@ 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); @@ -145,12 +141,13 @@ } 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) { diff --git a/WebContent/WEB-INF/web.xml b/WebContent/WEB-INF/web.xml index ea15e2d2a1..e8ac6b7797 100644 --- a/WebContent/WEB-INF/web.xml +++ b/WebContent/WEB-INF/web.xml @@ -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> diff --git a/client/src/com/vaadin/client/ApplicationConfiguration.java b/client/src/com/vaadin/client/ApplicationConfiguration.java index f620a39a70..9d668121a7 100644 --- a/client/src/com/vaadin/client/ApplicationConfiguration.java +++ b/client/src/com/vaadin/client/ApplicationConfiguration.java @@ -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(); diff --git a/client/src/com/vaadin/client/ApplicationConnection.java b/client/src/com/vaadin/client/ApplicationConnection.java index ab28ad291b..1e10762c28 100644 --- a/client/src/com/vaadin/client/ApplicationConnection.java +++ b/client/src/com/vaadin/client/ApplicationConnection.java @@ -528,16 +528,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; diff --git a/client/src/com/vaadin/client/ResourceLoader.java b/client/src/com/vaadin/client/ResourceLoader.java index f9925f89a4..eb718e24d0 100644 --- a/client/src/com/vaadin/client/ResourceLoader.java +++ b/client/src/com/vaadin/client/ResourceLoader.java @@ -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) { diff --git a/client/src/com/vaadin/client/Util.java b/client/src/com/vaadin/client/Util.java index 5ec5939c7b..7aea69a61d 100644 --- a/client/src/com/vaadin/client/Util.java +++ b/client/src/com/vaadin/client/Util.java @@ -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(); + } } diff --git a/server/src/com/vaadin/server/BootstrapHandler.java b/server/src/com/vaadin/server/BootstrapHandler.java index c08d3bdbb1..9a0e4c2071 100644 --- a/server/src/com/vaadin/server/BootstrapHandler.java +++ b/server/src/com/vaadin/server/BootstrapHandler.java @@ -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. diff --git a/server/src/com/vaadin/server/CombinedRequest.java b/server/src/com/vaadin/server/CombinedRequest.java index 34ac0b6a6f..5cb5b674b6 100644 --- a/server/src/com/vaadin/server/CombinedRequest.java +++ b/server/src/com/vaadin/server/CombinedRequest.java @@ -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 diff --git a/server/src/com/vaadin/server/CommunicationManager.java b/server/src/com/vaadin/server/CommunicationManager.java index 5f61079261..6cfaf37092 100644 --- a/server/src/com/vaadin/server/CommunicationManager.java +++ b/server/src/com/vaadin/server/CommunicationManager.java @@ -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 diff --git a/server/src/com/vaadin/server/GAEVaadinServlet.java b/server/src/com/vaadin/server/GAEVaadinServlet.java index c68f25a282..d2c53c6fcb 100644 --- a/server/src/com/vaadin/server/GAEVaadinServlet.java +++ b/server/src/com/vaadin/server/GAEVaadinServlet.java @@ -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; } diff --git a/server/src/com/vaadin/server/PortletCommunicationManager.java b/server/src/com/vaadin/server/PortletCommunicationManager.java index 8d39eee9ac..635202a074 100644 --- a/server/src/com/vaadin/server/PortletCommunicationManager.java +++ b/server/src/com/vaadin/server/PortletCommunicationManager.java @@ -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; } diff --git a/server/src/com/vaadin/server/VaadinPortletService.java b/server/src/com/vaadin/server/VaadinPortletService.java index ac48900945..12bf45f43a 100644 --- a/server/src/com/vaadin/server/VaadinPortletService.java +++ b/server/src/com/vaadin/server/VaadinPortletService.java @@ -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(); + } +} diff --git a/server/src/com/vaadin/server/VaadinService.java b/server/src/com/vaadin/server/VaadinService.java index 28684ec8dc..d9c8b83ea4 100644 --- a/server/src/com/vaadin/server/VaadinService.java +++ b/server/src/com/vaadin/server/VaadinService.java @@ -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); } diff --git a/server/src/com/vaadin/server/VaadinServlet.java b/server/src/com/vaadin/server/VaadinServlet.java index bd724aad63..68fca7b463 100644 --- a/server/src/com/vaadin/server/VaadinServlet.java +++ b/server/src/com/vaadin/server/VaadinServlet.java @@ -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; diff --git a/server/src/com/vaadin/server/VaadinServletService.java b/server/src/com/vaadin/server/VaadinServletService.java index b8af3b13fb..9992b3698d 100644 --- a/server/src/com/vaadin/server/VaadinServletService.java +++ b/server/src/com/vaadin/server/VaadinServletService.java @@ -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; + } +} diff --git a/shared/src/com/vaadin/shared/ApplicationConstants.java b/shared/src/com/vaadin/shared/ApplicationConstants.java index 2c0c0c4af0..079aa492cc 100644 --- a/shared/src/com/vaadin/shared/ApplicationConstants.java +++ b/shared/src/com/vaadin/shared/ApplicationConstants.java @@ -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"; } diff --git a/uitest/ivy.xml b/uitest/ivy.xml index a261c741a1..ce80832689 100644 --- a/uitest/ivy.xml +++ b/uitest/ivy.xml @@ -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" diff --git a/uitest/src/com/vaadin/launcher/ApplicationRunnerServlet.java b/uitest/src/com/vaadin/launcher/ApplicationRunnerServlet.java index 12d1cb2c38..0c2a1f965a 100644 --- a/uitest/src/com/vaadin/launcher/ApplicationRunnerServlet.java +++ b/uitest/src/com/vaadin/launcher/ApplicationRunnerServlet.java @@ -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 index 0000000000..f52f35ed55 --- /dev/null +++ b/uitest/src/com/vaadin/tests/integration/ProxyTest.html @@ -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 index 0000000000..97a4efe90e --- /dev/null +++ b/uitest/src/com/vaadin/tests/integration/ProxyTest.java @@ -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); + } + +} |