Browse Source

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
tags/7.0.0.beta6
Leif Åstrand 11 years ago
parent
commit
13d5b3e989

+ 10
- 13
WebContent/VAADIN/vaadinBootstrap.js View File

@@ -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) {

+ 1
- 1
WebContent/WEB-INF/web.xml View 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>

+ 54
- 21
client/src/com/vaadin/client/ApplicationConfiguration.java View 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();


+ 7
- 17
client/src/com/vaadin/client/ApplicationConnection.java View 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;

+ 3
- 10
client/src/com/vaadin/client/ResourceLoader.java View 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) {

+ 15
- 0
client/src/com/vaadin/client/Util.java View 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();
}
}

+ 13
- 31
server/src/com/vaadin/server/BootstrapHandler.java View 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.

+ 3
- 30
server/src/com/vaadin/server/CombinedRequest.java View 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

+ 12
- 40
server/src/com/vaadin/server/CommunicationManager.java View 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

+ 1
- 1
server/src/com/vaadin/server/GAEVaadinServlet.java View 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;
}

+ 9
- 18
server/src/com/vaadin/server/PortletCommunicationManager.java View 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;
}

+ 14
- 1
server/src/com/vaadin/server/VaadinPortletService.java View 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;
}
}

@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();
}
}

+ 16
- 0
server/src/com/vaadin/server/VaadinService.java View 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);
}

+ 0
- 20
server/src/com/vaadin/server/VaadinServlet.java View 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;

+ 54
- 21
server/src/com/vaadin/server/VaadinServletService.java View 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();
}
}

@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;
}
}

+ 13
- 1
shared/src/com/vaadin/shared/ApplicationConstants.java View 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";
}

+ 2
- 0
uitest/ivy.xml View 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"

+ 2
- 49
uitest/src/com/vaadin/launcher/ApplicationRunnerServlet.java View 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());
}

+ 111
- 0
uitest/src/com/vaadin/tests/integration/ProxyTest.html View File

@@ -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>

+ 155
- 0
uitest/src/com/vaadin/tests/integration/ProxyTest.java View File

@@ -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);
}

}

Loading…
Cancel
Save