diff options
author | Leif Åstrand <leif@vaadin.com> | 2012-08-30 11:30:39 +0300 |
---|---|---|
committer | Leif Åstrand <leif@vaadin.com> | 2012-08-30 12:47:35 +0300 |
commit | fd3826abf42f69db546eff18d269e462843feb49 (patch) | |
tree | 2852b77e657e90061842bc2d99dad09b36cd4150 /server/src | |
parent | e71794ee177e309a4cba15ebd6b65d5950721dd7 (diff) | |
download | vaadin-framework-fd3826abf42f69db546eff18d269e462843feb49.tar.gz vaadin-framework-fd3826abf42f69db546eff18d269e462843feb49.zip |
Replace ApplicationResource with ConnectorResource (#9419)
Diffstat (limited to 'server/src')
25 files changed, 876 insertions, 555 deletions
diff --git a/server/src/com/vaadin/Application.java b/server/src/com/vaadin/Application.java index ef3c805bb1..7c80ce654a 100644 --- a/server/src/com/vaadin/Application.java +++ b/server/src/com/vaadin/Application.java @@ -30,7 +30,6 @@ import java.util.EventListener; import java.util.EventObject; import java.util.HashMap; import java.util.HashSet; -import java.util.Hashtable; import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -53,7 +52,6 @@ import com.vaadin.data.util.converter.DefaultConverterFactory; import com.vaadin.event.EventRouter; import com.vaadin.server.AbstractApplicationServlet; import com.vaadin.server.AbstractErrorMessage; -import com.vaadin.server.ApplicationResource; import com.vaadin.server.BootstrapFragmentResponse; import com.vaadin.server.BootstrapListener; import com.vaadin.server.BootstrapPageResponse; @@ -62,14 +60,15 @@ import com.vaadin.server.ChangeVariablesErrorEvent; import com.vaadin.server.ClientConnector; import com.vaadin.server.CombinedRequest; import com.vaadin.server.DeploymentConfiguration; +import com.vaadin.server.GlobalResourceHandler; import com.vaadin.server.RequestHandler; import com.vaadin.server.Terminal; import com.vaadin.server.UIProvider; import com.vaadin.server.VariableOwner; import com.vaadin.server.WebApplicationContext; import com.vaadin.server.WrappedRequest; -import com.vaadin.server.WrappedResponse; import com.vaadin.server.WrappedRequest.BrowserDetails; +import com.vaadin.server.WrappedResponse; import com.vaadin.service.ApplicationContext; import com.vaadin.shared.ui.ui.UIConstants; import com.vaadin.tools.ReflectTools; @@ -123,8 +122,8 @@ import com.vaadin.ui.Window; * found out, the window itself is queried for a preferred theme. If the window * does not prefer a specific theme, the application containing the window is * queried. If neither the application prefers a theme, the default theme for - * the {@link com.vaadin.server.Terminal terminal} is used. The terminal - * always defines a default theme. + * the {@link com.vaadin.server.Terminal terminal} is used. The terminal always + * defines a default theme. * </p> * * @author Vaadin Ltd. @@ -458,10 +457,6 @@ public class Application implements Terminal.ErrorListener, Serializable { /** * Application resource mapping: key <-> resource. */ - private final Hashtable<ApplicationResource, String> resourceKeyMap = new Hashtable<ApplicationResource, String>(); - - private final Hashtable<String, ApplicationResource> keyResourceMap = new Hashtable<String, ApplicationResource>(); - private long lastResourceKeyNumber = 0; /** @@ -508,6 +503,8 @@ public class Application implements Terminal.ErrorListener, Serializable { private List<UIProvider> uiProviders = new LinkedList<UIProvider>(); + private GlobalResourceHandler globalResourceHandler; + /** * Gets the user of the application. * @@ -694,71 +691,6 @@ public class Application implements Terminal.ErrorListener, Serializable { } /** - * Adds new resource to the application. The resource can be accessed by the - * user of the application. - * - * @param resource - * the resource to add. - */ - public void addResource(ApplicationResource resource) { - - // Check if the resource is already mapped - if (resourceKeyMap.containsKey(resource)) { - return; - } - - // Generate key - final String key = String.valueOf(++lastResourceKeyNumber); - - // Add the resource to mappings - resourceKeyMap.put(resource, key); - keyResourceMap.put(key, resource); - } - - /** - * Removes the resource from the application. - * - * @param resource - * the resource to remove. - */ - public void removeResource(ApplicationResource resource) { - final Object key = resourceKeyMap.get(resource); - if (key != null) { - resourceKeyMap.remove(resource); - keyResourceMap.remove(key); - } - } - - /** - * Gets the relative uri of the resource. This method is intended to be - * called only be the terminal implementation. - * - * This method can only be called from within the processing of a UIDL - * request, not from a background thread. - * - * @param resource - * the resource to get relative location. - * @return the relative uri of the resource or null if called in a - * background thread - * - * @deprecated this method is intended to be used by the terminal only. It - * may be removed or moved in the future. - */ - @Deprecated - public String getRelativeLocation(ApplicationResource resource) { - - // Gets the key - final String key = resourceKeyMap.get(resource); - - // If the resource is not registered, return null - if (key == null) { - return null; - } - - return context.generateApplicationResourceURL(resource, key); - } - - /** * Gets the default locale for this application. * * By default this is the preferred locale of the user using the @@ -2066,20 +1998,6 @@ public class Application implements Terminal.ErrorListener, Serializable { } /** - * Find an application resource with a given key. - * - * @param key - * The key of the resource - * @return The application resource corresponding to the provided key, or - * <code>null</code> if no resource is registered for the key - * - * @since 7.0 - */ - public ApplicationResource getResource(String key) { - return keyResourceMap.get(key); - } - - /** * Thread local for keeping track of currently used application instance * * @since 7.0 @@ -2510,4 +2428,30 @@ public class Application implements Terminal.ErrorListener, Serializable { } return true; } + + /** + * Gets this application's global resource handler that takes care of + * serving connector resources that are not served by any single connector + * because e.g. because they are served with strong caching or because of + * legacy reasons. + * + * @param createOnDemand + * <code>true</code> if a resource handler should be initialized + * if there is no handler associated with this application. + * </code>false</code> if </code>null</code> should be returned + * if there is no registered handler. + * @return this application's global resource handler, or <code>null</code> + * if there is no handler and the createOnDemand parameter is + * <code>false</code>. + * + * @since 7.0.0 + */ + public GlobalResourceHandler getGlobalResourceHandler(boolean createOnDemand) { + if (globalResourceHandler == null && createOnDemand) { + globalResourceHandler = new GlobalResourceHandler(); + addRequestHandler(globalResourceHandler); + } + + return globalResourceHandler; + } } diff --git a/server/src/com/vaadin/server/AbstractClientConnector.java b/server/src/com/vaadin/server/AbstractClientConnector.java index 745ccf679c..fa127fa2aa 100644 --- a/server/src/com/vaadin/server/AbstractClientConnector.java +++ b/server/src/com/vaadin/server/AbstractClientConnector.java @@ -15,6 +15,7 @@ */ package com.vaadin.server; +import java.io.IOException; import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; @@ -367,8 +368,8 @@ public abstract class AbstractClientConnector implements ClientConnector { /** * Finds a UI ancestor of this connector. <code>null</code> is returned if - * no UI ancestor is found (typically because the connector is not - * attached to a proper hierarchy). + * no UI ancestor is found (typically because the connector is not attached + * to a proper hierarchy). * * @return the UI ancestor of this connector, or <code>null</code> if none * is found. @@ -561,4 +562,59 @@ public abstract class AbstractClientConnector implements ClientConnector { public void beforeClientResponse(boolean initial) { // Do nothing by default } + + @Override + public boolean handleConnectorRequest(WrappedRequest request, + WrappedResponse response, String path) throws IOException { + String[] parts = path.split("/", 2); + String key = parts[0]; + + ConnectorResource resource = (ConnectorResource) getResource(key); + if (resource != null) { + DownloadStream stream = resource.getStream(); + stream.writeTo(response); + return true; + } else { + return false; + } + } + + /** + * Gets a resource defined using {@link #setResource(String, Resource)} with + * the corresponding key. + * + * @param key + * the string identifier of the resource + * @return a resource, or <code>null</code> if there's no resource + * associated with the given key + * + * @see #setResource(String, Resource) + */ + protected Resource getResource(String key) { + return ResourceReference.getResource(getState().resources.get(key)); + } + + /** + * Registers a resource with this connector using the given key. This will + * make the URL for retrieving the resource available to the client-side + * connector using + * {@link com.vaadin.terminal.gwt.client.ui.AbstractConnector#getResourceUrl(String)} + * with the same key. + * + * @param key + * the string key to associate the resource with + * @param resource + * the resource to set, or <code>null</code> to clear a previous + * association. + */ + protected void setResource(String key, Resource resource) { + ResourceReference resourceReference = ResourceReference.create( + resource, this, key); + + if (resourceReference == null) { + getState().resources.remove(key); + } else { + getState().resources.put(key, resourceReference); + } + } } diff --git a/server/src/com/vaadin/server/AbstractCommunicationManager.java b/server/src/com/vaadin/server/AbstractCommunicationManager.java index 2d60c6e587..2655ee9a00 100644 --- a/server/src/com/vaadin/server/AbstractCommunicationManager.java +++ b/server/src/com/vaadin/server/AbstractCommunicationManager.java @@ -103,10 +103,10 @@ public abstract class AbstractCommunicationManager implements Serializable { private static final String DASHDASH = "--"; - private static final RequestHandler APP_RESOURCE_HANDLER = new ApplicationResourceHandler(); - private static final RequestHandler UNSUPPORTED_BROWSER_HANDLER = new UnsupportedBrowserHandler(); + private static final RequestHandler CONNECTOR_RESOURCE_HANDLER = new ConnectorResourceHandler(); + /** * TODO Document me! * @@ -178,8 +178,8 @@ public abstract class AbstractCommunicationManager implements Serializable { public AbstractCommunicationManager(Application application) { this.application = application; application.addRequestHandler(getBootstrapHandler()); - application.addRequestHandler(APP_RESOURCE_HANDLER); application.addRequestHandler(UNSUPPORTED_BROWSER_HANDLER); + application.addRequestHandler(CONNECTOR_RESOURCE_HANDLER); requireLocale(application.getLocale().toString()); } diff --git a/server/src/com/vaadin/server/AbstractWebApplicationContext.java b/server/src/com/vaadin/server/AbstractWebApplicationContext.java index 78cf8fdab8..cf983f4c80 100644 --- a/server/src/com/vaadin/server/AbstractWebApplicationContext.java +++ b/server/src/com/vaadin/server/AbstractWebApplicationContext.java @@ -18,9 +18,6 @@ package com.vaadin.server; import java.io.PrintWriter; import java.io.Serializable; import java.io.StringWriter; -import java.io.UnsupportedEncodingException; -import java.net.URL; -import java.net.URLEncoder; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -35,7 +32,6 @@ import javax.servlet.http.HttpSessionBindingListener; import com.vaadin.Application; import com.vaadin.service.ApplicationContext; -import com.vaadin.shared.ApplicationConstants; /** * Base class for web application contexts (including portlet contexts) that @@ -186,66 +182,6 @@ public abstract class AbstractWebApplicationContext implements applicationToAjaxAppMgrMap.remove(application); } - @Override - public String generateApplicationResourceURL(ApplicationResource resource, - String mapKey) { - - final String filename = resource.getFilename(); - if (filename == null) { - return ApplicationConstants.APP_PROTOCOL_PREFIX - + ApplicationConstants.APP_REQUEST_PATH + mapKey + "/"; - } else { - // #7738 At least Tomcat and JBoss refuses requests containing - // encoded slashes or backslashes in URLs. Application resource URLs - // should really be passed in another way than as part of the path - // in the future. - String encodedFileName = urlEncode(filename).replace("%2F", "/") - .replace("%5C", "\\"); - return ApplicationConstants.APP_PROTOCOL_PREFIX - + ApplicationConstants.APP_REQUEST_PATH + mapKey + "/" - + encodedFileName; - } - - } - - static String urlEncode(String filename) { - try { - return URLEncoder.encode(filename, "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException( - "UTF-8 charset not available (\"this should never happen\")", - e); - } - } - - @Override - public boolean isApplicationResourceURL(URL context, String relativeUri) { - // If the relative uri is null, we are ready - if (relativeUri == null) { - return false; - } - - // Resolves the prefix - String prefix = relativeUri; - final int index = relativeUri.indexOf('/'); - if (index >= 0) { - prefix = relativeUri.substring(0, index); - } - - // Handles the resource requests - return (prefix.equals("APP")); - } - - @Override - public String getURLKey(URL context, String relativeUri) { - final int index = relativeUri.indexOf('/'); - final int next = relativeUri.indexOf('/', index + 1); - if (next < 0) { - return null; - } - return relativeUri.substring(index + 1, next); - } - /** * @return The total time spent servicing requests in this session. */ diff --git a/server/src/com/vaadin/server/ApplicationResource.java b/server/src/com/vaadin/server/ApplicationResource.java deleted file mode 100644 index b18886ed55..0000000000 --- a/server/src/com/vaadin/server/ApplicationResource.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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.server; - -import java.io.Serializable; - -import com.vaadin.Application; - -/** - * This interface must be implemented by classes wishing to provide Application - * resources. - * <p> - * <code>ApplicationResource</code> are a set of named resources (pictures, - * sounds, etc) associated with some specific application. Having named - * application resources provides a convenient method for having inter-theme - * common resources for an application. - * </p> - * - * @author Vaadin Ltd. - * @since 3.0 - */ -public interface ApplicationResource extends Resource, Serializable { - - /** - * Default cache time. - */ - public static final long DEFAULT_CACHETIME = 1000 * 60 * 60 * 24; - - /** - * Gets resource as stream. - */ - public DownloadStream getStream(); - - /** - * Gets the application of the resource. - */ - public Application getApplication(); - - /** - * Gets the virtual filename for this resource. - * - * @return the file name associated to this resource. - */ - public String getFilename(); - - /** - * Gets the length of cache expiration time. - * - * <p> - * This gives the adapter the possibility cache streams sent to the client. - * The caching may be made in adapter or at the client if the client - * supports caching. Default is <code>DEFAULT_CACHETIME</code>. - * </p> - * - * @return Cache time in milliseconds - */ - public long getCacheTime(); - - /** - * Gets the size of the download buffer used for this resource. - * - * <p> - * If the buffer size is 0, the buffer size is decided by the terminal - * adapter. The default value is 0. - * </p> - * - * @return int the size of the buffer in bytes. - */ - public int getBufferSize(); - -} diff --git a/server/src/com/vaadin/server/ApplicationResourceHandler.java b/server/src/com/vaadin/server/ApplicationResourceHandler.java deleted file mode 100644 index 1cad5efcfd..0000000000 --- a/server/src/com/vaadin/server/ApplicationResourceHandler.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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.server; - -import java.io.IOException; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.servlet.http.HttpServletResponse; - -import com.vaadin.Application; - -public class ApplicationResourceHandler implements RequestHandler { - private static final Pattern APP_RESOURCE_PATTERN = Pattern - .compile("^/?APP/(\\d+)/.*"); - - @Override - public boolean handleRequest(Application application, - WrappedRequest request, WrappedResponse response) - throws IOException { - // Check for application resources - String requestPath = request.getRequestPathInfo(); - if (requestPath == null) { - return false; - } - Matcher resourceMatcher = APP_RESOURCE_PATTERN.matcher(requestPath); - - if (resourceMatcher.matches()) { - ApplicationResource resource = application - .getResource(resourceMatcher.group(1)); - if (resource != null) { - DownloadStream stream = resource.getStream(); - if (stream != null) { - stream.setCacheTime(resource.getCacheTime()); - stream.writeTo(response); - return true; - } - } - // We get here if the url looks like an application resource but no - // resource can be served - response.sendError(HttpServletResponse.SC_NOT_FOUND, - request.getRequestPathInfo() + " can not be found"); - return true; - } - - return false; - } -} diff --git a/server/src/com/vaadin/server/ClassResource.java b/server/src/com/vaadin/server/ClassResource.java index 4b4837faa3..2f05115fd4 100644 --- a/server/src/com/vaadin/server/ClassResource.java +++ b/server/src/com/vaadin/server/ClassResource.java @@ -20,6 +20,8 @@ import java.io.Serializable; import com.vaadin.Application; import com.vaadin.service.FileTypeResolver; +import com.vaadin.ui.UI; +import com.vaadin.ui.UI.LegacyWindow; /** * <code>ClassResource</code> is a named resource accessed with the class @@ -33,7 +35,7 @@ import com.vaadin.service.FileTypeResolver; * @since 3.0 */ @SuppressWarnings("serial") -public class ClassResource implements ApplicationResource, Serializable { +public class ClassResource implements ConnectorResource, Serializable { /** * Default buffer size for this stream resource. @@ -43,10 +45,10 @@ public class ClassResource implements ApplicationResource, Serializable { /** * Default cache time for this stream resource. */ - private long cacheTime = DEFAULT_CACHETIME; + private long cacheTime = DownloadStream.DEFAULT_CACHETIME; /** - * Associated class used for indetifying the source of the resource. + * Associated class used for identifying the source of the resource. */ private final Class<?> associatedClass; @@ -56,21 +58,15 @@ public class ClassResource implements ApplicationResource, Serializable { private final String resourceName; /** - * Application used for serving the class. - */ - private final Application application; - - /** * Creates a new application resource instance. The resource id is relative - * to the location of the application class. + * to the location of the UI of the component using this resource (or the + * Application if using LegacyWindow). * * @param resourceName * the Unique identifier of the resource within the application. - * @param application - * the application this resource will be added to. */ - public ClassResource(String resourceName, Application application) { - this(application.getClass(), resourceName, application); + public ClassResource(String resourceName) { + this(null, resourceName); } /** @@ -80,18 +76,13 @@ public class ClassResource implements ApplicationResource, Serializable { * the class of the which the resource is associated. * @param resourceName * the Unique identifier of the resource within the application. - * @param application - * the application this resource will be added to. */ - public ClassResource(Class<?> associatedClass, String resourceName, - Application application) { + public ClassResource(Class<?> associatedClass, String resourceName) { this.associatedClass = associatedClass; this.resourceName = resourceName; - this.application = application; - if (resourceName == null || associatedClass == null) { + if (resourceName == null) { throw new NullPointerException(); } - application.addResource(this); } /** @@ -104,50 +95,43 @@ public class ClassResource implements ApplicationResource, Serializable { return FileTypeResolver.getMIMEType(resourceName); } - /** - * Gets the application of this resource. - * - * @see com.vaadin.server.ApplicationResource#getApplication() - */ - @Override - public Application getApplication() { - return application; - } - - /** - * Gets the virtual filename for this resource. - * - * @return the file name associated to this resource. - * @see com.vaadin.server.ApplicationResource#getFilename() - */ @Override public String getFilename() { - int index = 0; - int next = 0; - while ((next = resourceName.indexOf('/', index)) > 0 - && next + 1 < resourceName.length()) { - index = next + 1; - } - return resourceName.substring(index); + String[] parts = resourceName.split("/"); + return parts[parts.length - 1]; } - /** - * Gets resource as stream. - * - * @see com.vaadin.server.ApplicationResource#getStream() - */ @Override public DownloadStream getStream() { - final DownloadStream ds = new DownloadStream( - associatedClass.getResourceAsStream(resourceName), - getMIMEType(), getFilename()); + final DownloadStream ds = new DownloadStream(getAssociatedClass() + .getResourceAsStream(resourceName), getMIMEType(), + getFilename()); ds.setBufferSize(getBufferSize()); - ds.setCacheTime(cacheTime); + ds.setCacheTime(getCacheTime()); return ds; } - /* documented in superclass */ - @Override + protected Class<?> getAssociatedClass() { + if (associatedClass == null) { + Class<? extends UI> associatedClass = UI.getCurrent().getClass(); + if (associatedClass == LegacyWindow.class) { + return Application.getCurrent().getClass(); + } + return associatedClass; + } + return associatedClass; + } + + /** + * Gets the size of the download buffer used for this resource. + * + * <p> + * If the buffer size is 0, the buffer size is decided by the terminal + * adapter. The default value is 0. + * </p> + * + * @return the size of the buffer in bytes. + */ public int getBufferSize() { return bufferSize; } @@ -157,13 +141,24 @@ public class ClassResource implements ApplicationResource, Serializable { * * @param bufferSize * the size of the buffer in bytes. + * + * @see #getBufferSize() */ public void setBufferSize(int bufferSize) { this.bufferSize = bufferSize; } - /* documented in superclass */ - @Override + /** + * Gets the length of cache expiration time. + * + * <p> + * This gives the adapter the possibility cache streams sent to the client. + * The caching may be made in adapter or at the client if the client + * supports caching. Default is {@link DownloadStream#DEFAULT_CACHETIME}. + * </p> + * + * @return Cache time in milliseconds + */ public long getCacheTime() { return cacheTime; } @@ -174,7 +169,7 @@ public class ClassResource implements ApplicationResource, Serializable { * <p> * This gives the adapter the possibility cache streams sent to the client. * The caching may be made in adapter or at the client if the client - * supports caching. Zero or negavive value disbales the caching of this + * supports caching. Zero or negative value disables the caching of this * stream. * </p> * diff --git a/server/src/com/vaadin/server/ClientConnector.java b/server/src/com/vaadin/server/ClientConnector.java index ce392ce226..3a340c2d7d 100644 --- a/server/src/com/vaadin/server/ClientConnector.java +++ b/server/src/com/vaadin/server/ClientConnector.java @@ -15,6 +15,7 @@ */ package com.vaadin.server; +import java.io.IOException; import java.util.Collection; import java.util.List; @@ -211,4 +212,30 @@ public interface ClientConnector extends Connector, RpcTarget { * if the state can not be encoded */ public JSONObject encodeState() throws JSONException; + + /** + * Handle a request directed to this connector. This can be used by + * connectors to dynamically generate a response and it is also used + * internally when serving {@link ConnectorResource}s. + * <p> + * Requests to <code>/APP/connector/[ui id]/[connector id]/</code> are + * routed to this method with the remaining part of the requested path + * available in the path parameter. + * <p> + * {@link DynamicConnectorResource} can be used to easily make an + * appropriate URL available to the client-side code. + * + * @param request + * the request that should be handled + * @param response + * the response object to which the response should be written + * @param path + * the requested relative path + * @return <code>true</code> if the request has been handled, + * <code>false</code> if no response has been written. + * @throws IOException + * if there is a problem generating a response. + */ + public boolean handleConnectorRequest(WrappedRequest request, + WrappedResponse response, String path) throws IOException; } diff --git a/server/src/com/vaadin/server/ConnectorResource.java b/server/src/com/vaadin/server/ConnectorResource.java new file mode 100644 index 0000000000..7f30de4bbe --- /dev/null +++ b/server/src/com/vaadin/server/ConnectorResource.java @@ -0,0 +1,43 @@ +/* + * 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.server; + + +/** + * A resource that is served through the Connector that is using the resource. + * + * @see AbstractClientConnector#setResource(String, Resource) + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + */ +public interface ConnectorResource extends Resource { + public static final String CONNECTOR_REQUEST_PATH = "connector/"; + + /** + * Gets resource as stream. + */ + public DownloadStream getStream(); + + /** + * Gets the virtual filename for this resource. + * + * @return the file name associated to this resource. + */ + public String getFilename(); +} diff --git a/server/src/com/vaadin/server/ConnectorResourceHandler.java b/server/src/com/vaadin/server/ConnectorResourceHandler.java new file mode 100644 index 0000000000..b988510b8e --- /dev/null +++ b/server/src/com/vaadin/server/ConnectorResourceHandler.java @@ -0,0 +1,81 @@ +package com.vaadin.server; + +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.servlet.http.HttpServletResponse; + +import com.vaadin.Application; +import com.vaadin.shared.ApplicationConstants; +import com.vaadin.ui.UI; + +public class ConnectorResourceHandler implements RequestHandler { + // APP/connector/[uiid]/[cid]/[filename.xyz] + private static final Pattern CONNECTOR_RESOURCE_PATTERN = Pattern + .compile("^/?" + ApplicationConstants.APP_REQUEST_PATH + + ConnectorResource.CONNECTOR_REQUEST_PATH + + "(\\d+)/(\\d+)/(.*)"); + + private static Logger getLogger() { + return Logger.getLogger(ConnectorResourceHandler.class.getName()); + + } + + @Override + public boolean handleRequest(Application application, + WrappedRequest request, WrappedResponse response) + throws IOException { + String requestPath = request.getRequestPathInfo(); + if (requestPath == null) { + return false; + } + Matcher matcher = CONNECTOR_RESOURCE_PATTERN.matcher(requestPath); + if (matcher.matches()) { + String uiId = matcher.group(1); + String cid = matcher.group(2); + String key = matcher.group(3); + UI ui = application.getUIById(Integer.parseInt(uiId)); + if (ui == null) { + return error(request, response, + "Ignoring connector request for no-existent root " + + uiId); + } + + UI.setCurrent(ui); + Application.setCurrent(ui.getApplication()); + + ClientConnector connector = ui.getConnectorTracker().getConnector( + cid); + if (connector == null) { + return error(request, response, + "Ignoring connector request for no-existent connector " + + cid + " in root " + uiId); + } + + if (!connector.handleConnectorRequest(request, response, key)) { + return error(request, response, connector.getClass() + .getSimpleName() + + " (" + + connector.getConnectorId() + + ") did not handle connector request for " + key); + } + + return true; + } else { + return false; + } + } + + private static boolean error(WrappedRequest request, + WrappedResponse response, String logMessage) throws IOException { + getLogger().log(Level.WARNING, logMessage); + response.sendError(HttpServletResponse.SC_NOT_FOUND, + request.getRequestPathInfo() + " can not be found"); + + // Request handled (though not in a nice way) + return true; + } +} diff --git a/server/src/com/vaadin/server/DragAndDropService.java b/server/src/com/vaadin/server/DragAndDropService.java index 29825496b5..3e7de5c9a2 100644 --- a/server/src/com/vaadin/server/DragAndDropService.java +++ b/server/src/com/vaadin/server/DragAndDropService.java @@ -15,6 +15,7 @@ */ package com.vaadin.server; +import java.io.IOException; import java.io.PrintWriter; import java.util.Collection; import java.util.Collections; @@ -338,4 +339,10 @@ public class DragAndDropService implements VariableOwner, ClientConnector { // TODO Auto-generated method stub return null; } + + @Override + public boolean handleConnectorRequest(WrappedRequest request, + WrappedResponse response, String path) throws IOException { + return false; + } } diff --git a/server/src/com/vaadin/server/DynamicConnectorResource.java b/server/src/com/vaadin/server/DynamicConnectorResource.java new file mode 100644 index 0000000000..8269f261f7 --- /dev/null +++ b/server/src/com/vaadin/server/DynamicConnectorResource.java @@ -0,0 +1,95 @@ +/* + * 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.server; + +import java.util.Collections; +import java.util.Map; + +import com.vaadin.service.FileTypeResolver; + +/** + * A resource that is served by calling + * {@link ClientConnector#handleConnectorRequest(WrappedRequest, WrappedResponse, String)} + * with appropriate parameters. + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + */ +public class DynamicConnectorResource implements Resource { + + private final ClientConnector connector; + private final String path; + private final Map<String, String> parameters; + + /** + * Creates a DynamicConnectorResoruce for the given connector that will be + * served by calling + * {@link ClientConnector#handleConnectorRequest(WrappedRequest, WrappedResponse, String)} + * with the given path. + * + * @param connector + * the connector that should serve the resource + * @param path + * the relative path of the request + */ + public DynamicConnectorResource(ClientConnector connector, String path) { + this(connector, path, null); + } + + /** + * Creates a DynamicConnectorResoruce for the given connector that will be + * served by calling + * {@link ClientConnector#handleConnectorRequest(WrappedRequest, WrappedResponse, String)} + * with the given path and the given request parameters. + * + * @param connector + * the connector that should serve the resource + * @param path + * the relative path of the request + * @param parameters + * the parameters that should be present in the request + */ + public DynamicConnectorResource(ClientConnector connector, String path, + Map<String, String> parameters) { + this.connector = connector; + this.path = path; + this.parameters = parameters; + } + + @Override + public String getMIMEType() { + return FileTypeResolver.getMIMEType(path); + } + + public String getPath() { + return path; + } + + public ClientConnector getConnector() { + return connector; + } + + public Map<String, String> getParameters() { + if (parameters == null) { + return Collections.emptyMap(); + } else { + return Collections.unmodifiableMap(parameters); + } + } + +} diff --git a/server/src/com/vaadin/server/FileResource.java b/server/src/com/vaadin/server/FileResource.java index 7b3f338b4f..fbf353362e 100644 --- a/server/src/com/vaadin/server/FileResource.java +++ b/server/src/com/vaadin/server/FileResource.java @@ -34,7 +34,7 @@ import com.vaadin.service.FileTypeResolver; * @since 3.0 */ @SuppressWarnings("serial") -public class FileResource implements ApplicationResource { +public class FileResource implements ConnectorResource { /** * Default buffer size for this stream resource. @@ -47,11 +47,6 @@ public class FileResource implements ApplicationResource { private File sourceFile; /** - * Application. - */ - private final Application application; - - /** * Default cache time for this stream resource. */ private long cacheTime = DownloadStream.DEFAULT_CACHETIME; @@ -59,18 +54,14 @@ public class FileResource implements ApplicationResource { /** * Creates a new file resource for providing given file for client * terminals. + * + * @param sourceFile + * the file that should be served. */ - public FileResource(File sourceFile, Application application) { - this.application = application; + public FileResource(File sourceFile) { setSourceFile(sourceFile); - application.addResource(this); } - /** - * Gets the resource as stream. - * - * @see com.vaadin.server.ApplicationResource#getStream() - */ @Override public DownloadStream getStream() { try { @@ -83,14 +74,15 @@ public class FileResource implements ApplicationResource { return ds; } catch (final FileNotFoundException e) { // Log the exception using the application error handler - getApplication().getErrorHandler().terminalError(new ErrorEvent() { + Application.getCurrent().getErrorHandler() + .terminalError(new ErrorEvent() { - @Override - public Throwable getThrowable() { - return e; - } + @Override + public Throwable getThrowable() { + return e; + } - }); + }); return null; } @@ -111,29 +103,15 @@ public class FileResource implements ApplicationResource { * @param sourceFile * the source file to set. */ - public void setSourceFile(File sourceFile) { + private void setSourceFile(File sourceFile) { this.sourceFile = sourceFile; } - /** - * @see com.vaadin.server.ApplicationResource#getApplication() - */ - @Override - public Application getApplication() { - return application; - } - - /** - * @see com.vaadin.server.ApplicationResource#getFilename() - */ @Override public String getFilename() { return sourceFile.getName(); } - /** - * @see com.vaadin.server.Resource#getMIMEType() - */ @Override public String getMIMEType() { return FileTypeResolver.getMIMEType(sourceFile); @@ -147,7 +125,6 @@ public class FileResource implements ApplicationResource { * * @return Cache time in milliseconds. */ - @Override public long getCacheTime() { return cacheTime; } @@ -165,8 +142,16 @@ public class FileResource implements ApplicationResource { this.cacheTime = cacheTime; } - /* documented in superclass */ - @Override + /** + * Gets the size of the download buffer used for this resource. + * + * <p> + * If the buffer size is 0, the buffer size is decided by the terminal + * adapter. The default value is 0. + * </p> + * + * @return the size of the buffer in bytes. + */ public int getBufferSize() { return bufferSize; } diff --git a/server/src/com/vaadin/server/GlobalResourceHandler.java b/server/src/com/vaadin/server/GlobalResourceHandler.java new file mode 100644 index 0000000000..7038d51251 --- /dev/null +++ b/server/src/com/vaadin/server/GlobalResourceHandler.java @@ -0,0 +1,239 @@ +/* + * 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.server; + +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.servlet.http.HttpServletResponse; + +import com.vaadin.Application; +import com.vaadin.shared.ApplicationConstants; +import com.vaadin.ui.UI; + +/** + * A {@link RequestHandler} that takes care of {@link ConnectorResource}s that + * should not be served by the connector. + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + */ +public class GlobalResourceHandler implements RequestHandler { + private static final String LEGACY_TYPE = "legacy"; + + private static final String RESOURCE_REQUEST_PATH = "global/"; + + /** + * Used to detect when a resource is no longer used by any connector. + */ + private final Map<Resource, Set<ClientConnector>> resourceUsers = new HashMap<Resource, Set<ClientConnector>>(); + /** + * Used to find the resources that might not be needed any more when a + * connector is unregistered. + */ + private final Map<ClientConnector, Set<Resource>> usedResources = new HashMap<ClientConnector, Set<Resource>>(); + + private final Map<ConnectorResource, String> legacyResourceKeys = new HashMap<ConnectorResource, String>(); + private final Map<String, ConnectorResource> legacyResources = new HashMap<String, ConnectorResource>(); + private int nextLegacyId = 0; + + // APP/global/[uiid]/[type]/[id] + private final Matcher matcher = Pattern.compile( + "^/?" + ApplicationConstants.APP_REQUEST_PATH + + RESOURCE_REQUEST_PATH + "(\\d+)/(([^/]+)(/.*))").matcher( + ""); + + @Override + public boolean handleRequest(Application application, + WrappedRequest request, WrappedResponse response) + throws IOException { + String pathInfo = request.getRequestPathInfo(); + if (pathInfo == null) { + return false; + } + + matcher.reset(pathInfo); + if (!matcher.matches()) { + return false; + } + + String uiid = matcher.group(1); + String type = matcher.group(3); + String key = matcher.group(2); + + // Allow GCing pathInfo string + matcher.reset(); + + if (key == null) { + return error(request, response, pathInfo + + " is not a valid global resource path"); + } + + UI ui = application.getUIById(Integer.parseInt(uiid)); + if (ui == null) { + return error(request, response, "No UI found for id " + uiid); + } + UI.setCurrent(ui); + + ConnectorResource resource; + if (LEGACY_TYPE.equals(type)) { + resource = legacyResources.get(key); + } else { + return error(request, response, "Unknown global resource type " + + type + " in requested path " + pathInfo); + } + + if (resource == null) { + return error(request, response, "Global resource " + key + + " not found"); + } + + DownloadStream stream = resource.getStream(); + if (stream == null) { + return error(request, response, "Resource " + resource + + " didn't produce any stream."); + } + + stream.writeTo(response); + return true; + } + + /** + * Registers a resource to be served with a global URL. + * <p> + * A {@link ConnectorResource} registered for a {@link Vaadin6Component} + * will be set to be served with a global URL. Other resource types will be + * ignored and thus not served by this handler. + * + * @param resource + * the resource to register + * @param ownerConnector + * the connector to which the resource belongs + */ + public void register(Resource resource, ClientConnector ownerConnector) { + if (resource instanceof ConnectorResource) { + if (!(ownerConnector instanceof LegacyComponent)) { + throw new IllegalArgumentException( + "A normal ConnectorResource can only be registered for legacy components."); + } + ConnectorResource connectorResource = (ConnectorResource) resource; + if (!legacyResourceKeys.containsKey(resource)) { + String uri = LEGACY_TYPE + '/' + + Integer.toString(nextLegacyId++); + String filename = connectorResource.getFilename(); + if (filename != null && !filename.isEmpty()) { + uri += '/' + filename; + } + legacyResourceKeys.put(connectorResource, uri); + legacyResources.put(uri, connectorResource); + registerResourceUsage(connectorResource, ownerConnector); + } + } + } + + private void unregisterResource(Resource resource) { + String oldUri = legacyResourceKeys.remove(resource); + if (oldUri != null) { + legacyResources.remove(oldUri); + } + } + + private void registerResourceUsage(Resource resource, + ClientConnector connector) { + ensureInSet(resourceUsers, resource, connector); + ensureInSet(usedResources, connector, resource); + } + + private <K, V> void ensureInSet(Map<K, Set<V>> map, K key, V value) { + Set<V> set = map.get(key); + if (set == null) { + set = new HashSet<V>(); + map.put(key, set); + } + set.add(value); + } + + /** + * Gets a global URI for a resource if it's registered with this handler. + * + * @param connector + * the connector for which the uri should be generated. + * @param resource + * the resource for which the uri should be generated. + * @return an URI string, or <code>null</code> if the resource is not + * registered. + */ + public String getUri(ClientConnector connector, ConnectorResource resource) { + // app://APP/global/[ui]/[type]/[id] + String uri = legacyResourceKeys.get(resource); + if (uri != null && !uri.isEmpty()) { + return ApplicationConstants.APP_PROTOCOL_PREFIX + + ApplicationConstants.APP_REQUEST_PATH + + RESOURCE_REQUEST_PATH + connector.getUI().getUIId() + '/' + + uri; + } else { + return null; + } + } + + /** + * Notifies this handler that resources registered for the given connector + * can be released. + * + * @param connector + * the connector for which any registered resources can be + * released. + */ + public void unregisterConnector(ClientConnector connector) { + Set<Resource> set = usedResources.remove(connector); + if (set == null) { + return; + } + + for (Resource resource : set) { + Set<ClientConnector> users = resourceUsers.get(resource); + users.remove(connector); + if (users.isEmpty()) { + resourceUsers.remove(resource); + unregisterResource(resource); + } + } + } + + private static Logger getLogger() { + return Logger.getLogger(GlobalResourceHandler.class.getName()); + } + + private static boolean error(WrappedRequest request, + WrappedResponse response, String logMessage) throws IOException { + getLogger().log(Level.WARNING, logMessage); + response.sendError(HttpServletResponse.SC_NOT_FOUND, + request.getRequestPathInfo() + " can not be found"); + + // Request handled (though not in a nice way) + return true; + } + +} diff --git a/server/src/com/vaadin/server/JsonPaintTarget.java b/server/src/com/vaadin/server/JsonPaintTarget.java index 96d7cec82e..f776e38a1f 100644 --- a/server/src/com/vaadin/server/JsonPaintTarget.java +++ b/server/src/com/vaadin/server/JsonPaintTarget.java @@ -344,7 +344,12 @@ public class JsonPaintTarget implements PaintTarget { if (value == null) { throw new NullPointerException(); } - ResourceReference reference = ResourceReference.create(value); + ClientConnector ownerConnector = openPaintables.peek(); + ownerConnector.getUI().getApplication().getGlobalResourceHandler(true) + .register(value, ownerConnector); + + ResourceReference reference = ResourceReference.create(value, + ownerConnector, name); addAttribute(name, reference.getURL()); } diff --git a/server/src/com/vaadin/server/ResourceReference.java b/server/src/com/vaadin/server/ResourceReference.java index f2af92aa73..098fb6c3e4 100644 --- a/server/src/com/vaadin/server/ResourceReference.java +++ b/server/src/com/vaadin/server/ResourceReference.java @@ -15,15 +15,25 @@ */ package com.vaadin.server; -import com.vaadin.Application; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Map.Entry; +import java.util.Set; + +import com.vaadin.shared.ApplicationConstants; import com.vaadin.shared.communication.URLReference; public class ResourceReference extends URLReference { - private Resource resource; + private final Resource resource; + private final ClientConnector connector; + private final String key; - public ResourceReference(Resource resource) { + public ResourceReference(Resource resource, ClientConnector connector, + String key) { this.resource = resource; + this.connector = connector; + this.key = key; } public Resource getResource() { @@ -34,16 +44,49 @@ public class ResourceReference extends URLReference { public String getURL() { if (resource instanceof ExternalResource) { return ((ExternalResource) resource).getURL(); - } else if (resource instanceof ApplicationResource) { - final ApplicationResource r = (ApplicationResource) resource; - final Application a = r.getApplication(); - if (a == null) { - throw new RuntimeException( - "An ApplicationResource (" - + r.getClass().getName() - + " must be attached to an application when it is sent to the client."); + } else if (resource instanceof DynamicConnectorResource) { + DynamicConnectorResource dcr = (DynamicConnectorResource) resource; + + String filename = dcr.getPath(); + StringBuilder builder = new StringBuilder(getConnectorResourceBase( + filename, dcr.getConnector())); + + Set<Entry<String, String>> entrySet = dcr.getParameters() + .entrySet(); + boolean first = true; + for (Entry<String, String> entry : entrySet) { + String key = entry.getKey(); + String value = entry.getValue(); + if (first) { + builder.append('?'); + first = false; + } else { + builder.append('&'); + } + // TODO URL encode!!! + builder.append(key).append('=').append(value); + } + return builder.toString(); + } else if (resource instanceof ConnectorResource) { + ConnectorResource connectorResource = (ConnectorResource) resource; + + GlobalResourceHandler globalResourceHandler = connector.getUI() + .getApplication().getGlobalResourceHandler(false); + if (globalResourceHandler != null) { + String uri = globalResourceHandler.getUri(connector, + connectorResource); + if (uri != null && !uri.isEmpty()) { + return uri; + } + } + + // app://APP/connector/[uiid]/[cid]/[key]/[filename] + String prefix = key; + String filename = connectorResource.getFilename(); + if (filename != null && !filename.isEmpty()) { + prefix += '/' + filename; } - final String uri = a.getRelativeLocation(r); + String uri = getConnectorResourceBase(prefix, connector); return uri; } else if (resource instanceof ThemeResource) { final String uri = "theme://" @@ -57,11 +100,40 @@ public class ResourceReference extends URLReference { } - public static ResourceReference create(Resource resource) { + private static String getConnectorResourceBase(String filename, + ClientConnector connector) { + String uri = ApplicationConstants.APP_PROTOCOL_PREFIX + + ApplicationConstants.APP_REQUEST_PATH + + ConnectorResource.CONNECTOR_REQUEST_PATH + + connector.getUI().getUIId() + '/' + + connector.getConnectorId() + '/' + encodeFileName(filename); + return uri; + } + + public static String encodeFileName(String filename) { + // #7738 At least Tomcat and JBoss refuses requests containing + // encoded slashes or backslashes in URLs. Application resource URLs + // should really be passed in another way than as part of the path + // in the future. + return urlEncode(filename).replace("%2F", "/").replace("%5C", "\\"); + } + + static String urlEncode(String filename) { + try { + return URLEncoder.encode(filename, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException( + "UTF-8 charset not available (\"this should never happen\")", + e); + } + } + + public static ResourceReference create(Resource resource, + ClientConnector connector, String key) { if (resource == null) { return null; } else { - return new ResourceReference(resource); + return new ResourceReference(resource, connector, key); } } diff --git a/server/src/com/vaadin/server/StreamResource.java b/server/src/com/vaadin/server/StreamResource.java index 443831129f..26514c9353 100644 --- a/server/src/com/vaadin/server/StreamResource.java +++ b/server/src/com/vaadin/server/StreamResource.java @@ -19,7 +19,6 @@ package com.vaadin.server; import java.io.InputStream; import java.io.Serializable; -import com.vaadin.Application; import com.vaadin.service.FileTypeResolver; /** @@ -32,7 +31,7 @@ import com.vaadin.service.FileTypeResolver; * @since 3.0 */ @SuppressWarnings("serial") -public class StreamResource implements ApplicationResource { +public class StreamResource implements ConnectorResource { /** * Source stream the downloaded content is fetched from. @@ -50,11 +49,6 @@ public class StreamResource implements ApplicationResource { private String filename; /** - * Application. - */ - private final Application application; - - /** * Default buffer size for this stream resource. */ private int bufferSize = 0; @@ -62,7 +56,7 @@ public class StreamResource implements ApplicationResource { /** * Default cache time for this stream resource. */ - private long cacheTime = DEFAULT_CACHETIME; + private long cacheTime = DownloadStream.DEFAULT_CACHETIME; /** * Creates a new stream resource for downloading from stream. @@ -74,16 +68,9 @@ public class StreamResource implements ApplicationResource { * @param application * the Application object. */ - public StreamResource(StreamSource streamSource, String filename, - Application application) { - - this.application = application; + public StreamResource(StreamSource streamSource, String filename) { setFilename(filename); setStreamSource(streamSource); - - // Register to application - application.addResource(this); - } /** @@ -149,17 +136,6 @@ public class StreamResource implements ApplicationResource { this.filename = filename; } - /** - * @see com.vaadin.server.ApplicationResource#getApplication() - */ - @Override - public Application getApplication() { - return application; - } - - /** - * @see com.vaadin.server.ApplicationResource#getStream() - */ @Override public DownloadStream getStream() { final StreamSource ss = getStreamSource(); @@ -187,8 +163,16 @@ public class StreamResource implements ApplicationResource { public InputStream getStream(); } - /* documented in superclass */ - @Override + /** + * Gets the size of the download buffer used for this resource. + * + * <p> + * If the buffer size is 0, the buffer size is decided by the terminal + * adapter. The default value is 0. + * </p> + * + * @return the size of the buffer in bytes. + */ public int getBufferSize() { return bufferSize; } @@ -203,8 +187,14 @@ public class StreamResource implements ApplicationResource { this.bufferSize = bufferSize; } - /* documented in superclass */ - @Override + /** + * Gets the length of cache expiration time. This gives the adapter the + * possibility cache streams sent to the client. The caching may be made in + * adapter or at the client if the client supports caching. Default is + * <code>DownloadStream.DEFAULT_CACHETIME</code>. + * + * @return Cache time in milliseconds. + */ public long getCacheTime() { return cacheTime; } @@ -227,4 +217,29 @@ public class StreamResource implements ApplicationResource { this.cacheTime = cacheTime; } + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (obj instanceof StreamResource) { + StreamResource that = (StreamResource) obj; + return getStreamSource().equals(that.getStreamSource()) + && getMIMEType().equals(that.getMIMEType()) + && String.valueOf(getFilename()).equals( + String.valueOf(that.getFilename())) + && getBufferSize() == that.getBufferSize() + && getCacheTime() == that.getCacheTime(); + } else { + return false; + } + } + + @Override + public int hashCode() { + return (int) (getStreamSource().hashCode() + 37 + * getMIMEType().hashCode() + 37 ^ 2 + * String.valueOf(getFilename()).hashCode() + 37 ^ 3 + * getBufferSize() + 37 ^ 4 * getCacheTime()); + } + } diff --git a/server/src/com/vaadin/service/ApplicationContext.java b/server/src/com/vaadin/service/ApplicationContext.java index 08e553d0e5..591704764f 100644 --- a/server/src/com/vaadin/service/ApplicationContext.java +++ b/server/src/com/vaadin/service/ApplicationContext.java @@ -18,12 +18,9 @@ package com.vaadin.service; import java.io.File; import java.io.Serializable; -import java.net.URL; import java.util.Collection; import com.vaadin.Application; -import com.vaadin.server.AbstractCommunicationManager; -import com.vaadin.server.ApplicationResource; /** * <code>ApplicationContext</code> provides information about the running @@ -86,59 +83,6 @@ public interface ApplicationContext extends Serializable { public int getMaxInactiveInterval(); /** - * Generate a URL that can be used as the relative location of e.g. an - * {@link ApplicationResource}. - * - * This method should only be called from the processing of a UIDL request, - * not from a background thread. The return value is null if used outside a - * suitable request. - * - * @deprecated this method is intended for terminal implementation only and - * is subject to change/removal from the interface (to - * {@link AbstractCommunicationManager}) - * - * @param resource - * @param urlKey - * a key for the resource that can later be extracted from a URL - * with {@link #getURLKey(URL, String)} - */ - @Deprecated - public String generateApplicationResourceURL(ApplicationResource resource, - String urlKey); - - /** - * Tests if a URL is for an application resource (APP/...). - * - * @deprecated this method is intended for terminal implementation only and - * is subject to change/removal from the interface (to - * {@link AbstractCommunicationManager}) - * - * @param context - * @param relativeUri - * @return - */ - @Deprecated - public boolean isApplicationResourceURL(URL context, String relativeUri); - - /** - * Gets the identifier (key) from an application resource URL. This key is - * the one that was given to - * {@link #generateApplicationResourceURL(ApplicationResource, String)} when - * creating the URL. - * - * @deprecated this method is intended for terminal implementation only and - * is subject to change/removal from the interface (to - * {@link AbstractCommunicationManager}) - * - * - * @param context - * @param relativeUri - * @return - */ - @Deprecated - public String getURLKey(URL context, String relativeUri); - - /** * Interface for listening to transaction events. Implement this interface * to listen to all transactions between the client and the application. * diff --git a/server/src/com/vaadin/ui/AbstractComponent.java b/server/src/com/vaadin/ui/AbstractComponent.java index 47d893cd27..045173036e 100644 --- a/server/src/com/vaadin/ui/AbstractComponent.java +++ b/server/src/com/vaadin/ui/AbstractComponent.java @@ -37,8 +37,8 @@ import com.vaadin.server.ClientConnector; import com.vaadin.server.ComponentSizeValidator; import com.vaadin.server.ErrorMessage; import com.vaadin.server.Resource; -import com.vaadin.server.ResourceReference; import com.vaadin.server.Terminal; +import com.vaadin.shared.ComponentConstants; import com.vaadin.shared.ComponentState; import com.vaadin.tools.ReflectTools; @@ -297,7 +297,7 @@ public abstract class AbstractComponent extends AbstractClientConnector */ @Override public Resource getIcon() { - return ResourceReference.getResource(getState().getIcon()); + return getResource(ComponentConstants.ICON_RESOURCE); } /** @@ -309,7 +309,7 @@ public abstract class AbstractComponent extends AbstractClientConnector */ @Override public void setIcon(Resource icon) { - getState().setIcon(ResourceReference.create(icon)); + setResource(ComponentConstants.ICON_RESOURCE, icon); } /* diff --git a/server/src/com/vaadin/ui/AbstractEmbedded.java b/server/src/com/vaadin/ui/AbstractEmbedded.java index f3256dcde6..d94f62120f 100644 --- a/server/src/com/vaadin/ui/AbstractEmbedded.java +++ b/server/src/com/vaadin/ui/AbstractEmbedded.java @@ -5,7 +5,6 @@ package com.vaadin.ui; import com.vaadin.server.Resource; -import com.vaadin.server.ResourceReference; import com.vaadin.shared.ui.AbstractEmbeddedState; /** @@ -32,12 +31,7 @@ public abstract class AbstractEmbedded extends AbstractComponent { * the source to set. */ public void setSource(Resource source) { - if (source == null) { - getState().setSource(null); - } else { - getState().setSource(new ResourceReference(source)); - } - requestRepaint(); + setResource(AbstractEmbeddedState.SOURCE_RESOURCE, source); } /** @@ -46,12 +40,7 @@ public abstract class AbstractEmbedded extends AbstractComponent { * @return the source */ public Resource getSource() { - ResourceReference ref = ((ResourceReference) getState().getSource()); - if (ref == null) { - return null; - } else { - return ref.getResource(); - } + return getResource(AbstractEmbeddedState.SOURCE_RESOURCE); } /** diff --git a/server/src/com/vaadin/ui/AbstractMedia.java b/server/src/com/vaadin/ui/AbstractMedia.java index f9eb67f666..940d85a8b9 100644 --- a/server/src/com/vaadin/ui/AbstractMedia.java +++ b/server/src/com/vaadin/ui/AbstractMedia.java @@ -16,11 +16,18 @@ package com.vaadin.ui; +import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import com.vaadin.server.ConnectorResource; import com.vaadin.server.Resource; import com.vaadin.server.ResourceReference; +import com.vaadin.server.WrappedRequest; +import com.vaadin.server.WrappedResponse; import com.vaadin.shared.communication.URLReference; import com.vaadin.shared.ui.AbstractMediaState; import com.vaadin.shared.ui.MediaControl; @@ -64,11 +71,43 @@ public abstract class AbstractMedia extends AbstractComponent { */ public void addSource(Resource source) { if (source != null) { - getState().getSources().add(new ResourceReference(source)); + List<URLReference> sources = getState().getSources(); + sources.add(new ResourceReference(source, this, Integer + .toString(sources.size()))); getState().getSourceTypes().add(source.getMIMEType()); } } + @Override + public boolean handleConnectorRequest(WrappedRequest request, + WrappedResponse response, String path) throws IOException { + Matcher matcher = Pattern.compile("(\\d+)(/.*)?").matcher(path); + if (matcher.matches()) { + List<URLReference> sources = getState().getSources(); + + int sourceIndex = Integer.parseInt(matcher.group(1)); + + if (sourceIndex < 0 || sourceIndex >= sources.size()) { + getLogger().warning( + "Requested source index " + sourceIndex + + " is out of bounds"); + return false; + } + + URLReference reference = sources.get(sourceIndex); + ConnectorResource resource = (ConnectorResource) ResourceReference + .getResource(reference); + resource.getStream().writeTo(response); + return true; + } else { + return super.handleConnectorRequest(request, response, path); + } + } + + private Logger getLogger() { + return Logger.getLogger(AbstractMedia.class.getName()); + } + /** * Set multiple sources at once. Which of the sources is used is selected by * the browser depending on which file formats it supports. See <a diff --git a/server/src/com/vaadin/ui/ConnectorTracker.java b/server/src/com/vaadin/ui/ConnectorTracker.java index 265099f372..3140c26525 100644 --- a/server/src/com/vaadin/ui/ConnectorTracker.java +++ b/server/src/com/vaadin/ui/ConnectorTracker.java @@ -28,6 +28,7 @@ import java.util.logging.Logger; import com.vaadin.server.AbstractClientConnector; import com.vaadin.server.AbstractCommunicationManager; import com.vaadin.server.ClientConnector; +import com.vaadin.server.GlobalResourceHandler; /** * A class which takes care of book keeping of {@link ClientConnector}s for a @@ -141,11 +142,22 @@ public class ConnectorTracker implements Serializable { getLogger().fine( "Unregistered " + connector.getClass().getSimpleName() + " (" + connectorId + ")"); + + removeFromGlobalResourceHandler(connector); connectorIdToConnector.remove(connectorId); uninitializedConnectors.remove(connector); diffStates.remove(connector); } + private void removeFromGlobalResourceHandler(ClientConnector connector) { + GlobalResourceHandler globalResourceHandler = uI.getApplication() + .getGlobalResourceHandler(false); + // Nothing to do if there is no handler + if (globalResourceHandler != null) { + globalResourceHandler.unregisterConnector(connector); + } + } + /** * Checks whether the given connector has already been initialized in the * browser. The given connector should be registered with this connector @@ -224,6 +236,8 @@ public class ConnectorTracker implements Serializable { "cleanConnectorMap unregistered connector " + getConnectorAndParentInfo(connector) + "). This should have been done when the connector was detached."); + + removeFromGlobalResourceHandler(connector); uninitializedConnectors.remove(connector); diffStates.remove(connector); iterator.remove(); diff --git a/server/src/com/vaadin/ui/Link.java b/server/src/com/vaadin/ui/Link.java index e98b558dbe..a2737e4483 100644 --- a/server/src/com/vaadin/ui/Link.java +++ b/server/src/com/vaadin/ui/Link.java @@ -18,11 +18,12 @@ package com.vaadin.ui; import java.util.Map; +import com.vaadin.server.LegacyComponent; import com.vaadin.server.PaintException; import com.vaadin.server.PaintTarget; import com.vaadin.server.Resource; -import com.vaadin.server.LegacyComponent; import com.vaadin.shared.ui.BorderStyle; +import com.vaadin.shared.ui.link.LinkConstants; /** * Link is used to create external or internal URL links. @@ -45,8 +46,6 @@ public class Link extends AbstractComponent implements LegacyComponent { @Deprecated public static final BorderStyle TARGET_BORDER_DEFAULT = BorderStyle.DEFAULT; - private Resource resource = null; - private String targetName; private BorderStyle targetBorder = BorderStyle.DEFAULT; @@ -70,7 +69,7 @@ public class Link extends AbstractComponent implements LegacyComponent { */ public Link(String caption, Resource resource) { setCaption(caption); - this.resource = resource; + setResource(resource); } /** @@ -94,7 +93,7 @@ public class Link extends AbstractComponent implements LegacyComponent { public Link(String caption, Resource resource, String targetName, int width, int height, BorderStyle border) { setCaption(caption); - this.resource = resource; + setResource(resource); setTargetName(targetName); setTargetWidth(width); setTargetHeight(height); @@ -111,10 +110,7 @@ public class Link extends AbstractComponent implements LegacyComponent { */ @Override public void paintContent(PaintTarget target) throws PaintException { - - if (resource != null) { - target.addAttribute("src", resource); - } else { + if (getResource() == null) { return; } @@ -230,7 +226,7 @@ public class Link extends AbstractComponent implements LegacyComponent { * @return the Resource. */ public Resource getResource() { - return resource; + return getResource(LinkConstants.HREF_RESOURCE); } /** @@ -240,8 +236,7 @@ public class Link extends AbstractComponent implements LegacyComponent { * the resource to set. */ public void setResource(Resource resource) { - this.resource = resource; - markAsDirty(); + setResource(LinkConstants.HREF_RESOURCE, resource); } @Override diff --git a/server/src/com/vaadin/ui/LoginForm.java b/server/src/com/vaadin/ui/LoginForm.java index 7715f08a6e..0d89fd19a3 100644 --- a/server/src/com/vaadin/ui/LoginForm.java +++ b/server/src/com/vaadin/ui/LoginForm.java @@ -25,7 +25,7 @@ import java.util.Iterator; import java.util.Map; import com.vaadin.Application; -import com.vaadin.server.ApplicationResource; +import com.vaadin.server.ConnectorResource; import com.vaadin.server.DownloadStream; import com.vaadin.server.RequestHandler; import com.vaadin.server.WrappedRequest; @@ -58,23 +58,7 @@ public class LoginForm extends CustomComponent { private Embedded iframe = new Embedded(); - private ApplicationResource loginPage = new ApplicationResource() { - - @Override - public Application getApplication() { - return LoginForm.this.getApplication(); - } - - @Override - public int getBufferSize() { - return getLoginHTML().length; - } - - @Override - public long getCacheTime() { - return -1; - } - + private ConnectorResource loginPage = new ConnectorResource() { @Override public String getFilename() { return "login"; @@ -82,8 +66,13 @@ public class LoginForm extends CustomComponent { @Override public DownloadStream getStream() { - return new DownloadStream(new ByteArrayInputStream(getLoginHTML()), - getMIMEType(), getFilename()); + byte[] loginHTML = getLoginHTML(); + DownloadStream downloadStream = new DownloadStream( + new ByteArrayInputStream(loginHTML), getMIMEType(), + getFilename()); + downloadStream.setBufferSize(loginHTML.length); + downloadStream.setCacheTime(-1); + return downloadStream; } @Override @@ -197,14 +186,12 @@ public class LoginForm extends CustomComponent { @Override public void attach() { super.attach(); - getApplication().addResource(loginPage); getApplication().addRequestHandler(requestHandler); iframe.setSource(loginPage); } @Override public void detach() { - getApplication().removeResource(loginPage); getApplication().removeRequestHandler(requestHandler); super.detach(); diff --git a/server/src/com/vaadin/ui/Video.java b/server/src/com/vaadin/ui/Video.java index d14ed71270..2d83538d57 100644 --- a/server/src/com/vaadin/ui/Video.java +++ b/server/src/com/vaadin/ui/Video.java @@ -17,7 +17,7 @@ package com.vaadin.ui; import com.vaadin.server.Resource; -import com.vaadin.server.ResourceReference; +import com.vaadin.shared.ui.video.VideoConstants; import com.vaadin.shared.ui.video.VideoState; /** @@ -79,14 +79,14 @@ public class Video extends AbstractMedia { * @param poster */ public void setPoster(Resource poster) { - getState().setPoster(ResourceReference.create(poster)); + setResource(VideoConstants.POSTER_RESOURCE, poster); } /** * @return The poster image. */ public Resource getPoster() { - return ResourceReference.getResource(getState().getPoster()); + return getResource(VideoConstants.POSTER_RESOURCE); } } |