diff options
author | Artur <artur@vaadin.com> | 2017-05-02 08:30:59 +0300 |
---|---|---|
committer | Ilia Motornyi <elmot@vaadin.com> | 2017-05-02 08:30:59 +0300 |
commit | f8921dc387a572b12ac7c9c6f4677e5a1d0e5b70 (patch) | |
tree | be13f735bb10458e54d80e722242c902c5f2b8e5 | |
parent | 9c443c9de42343a94df5f7040bfed731f8d8f84e (diff) | |
download | vaadin-framework-f8921dc387a572b12ac7c9c6f4677e5a1d0e5b70.tar.gz vaadin-framework-f8921dc387a572b12ac7c9c6f4677e5a1d0e5b70.zip |
Add support for pluggable filters for rewriting dependencies (#9182)
* Add support for pluggable filters for rewriting dependencies
Fixes #9151
18 files changed, 445 insertions, 5 deletions
diff --git a/server/src/main/java/com/vaadin/server/BootstrapHandler.java b/server/src/main/java/com/vaadin/server/BootstrapHandler.java index 30b2a5d372..e26518064d 100644 --- a/server/src/main/java/com/vaadin/server/BootstrapHandler.java +++ b/server/src/main/java/com/vaadin/server/BootstrapHandler.java @@ -44,6 +44,7 @@ import org.jsoup.parser.Tag; import com.vaadin.annotations.Viewport; import com.vaadin.annotations.ViewportGeneratorClass; +import com.vaadin.server.DependencyFilter.FilterContext; import com.vaadin.server.communication.AtmospherePushConnection; import com.vaadin.shared.ApplicationConstants; import com.vaadin.shared.VaadinUriResolver; @@ -572,9 +573,10 @@ public abstract class BootstrapHandler extends SynchronizedRequestHandler { .attr("href", themeUri + "/favicon.ico"); } - Collection<? extends Dependency> deps = Dependency.findDependencies( - Collections.singletonList(uiClass), - context.getSession().getCommunicationManager()); + Collection<? extends Dependency> deps = Dependency + .findAndFilterDependencies(Collections.singletonList(uiClass), + context.getSession().getCommunicationManager(), + new FilterContext(context.getSession())); for (Dependency dependency : deps) { Type type = dependency.getType(); String url = context.getUriResolver() diff --git a/server/src/main/java/com/vaadin/server/DependencyFilter.java b/server/src/main/java/com/vaadin/server/DependencyFilter.java new file mode 100644 index 0000000000..f5ab634ce1 --- /dev/null +++ b/server/src/main/java/com/vaadin/server/DependencyFilter.java @@ -0,0 +1,88 @@ +/* + * Copyright 2000-2016 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 java.util.List; + +import com.vaadin.annotations.HtmlImport; +import com.vaadin.annotations.JavaScript; +import com.vaadin.annotations.StyleSheet; +import com.vaadin.ui.Dependency; + +/** + * Filter for dependencies loaded using {@link StyleSheet @StyleSheet}, + * {@link JavaScript @JavaScript} and {@link HtmlImport @HtmlImport}. + * + * @since + */ +public interface DependencyFilter extends Serializable { + + /** + * Filters the list of dependencies and returns a (possibly) updated + * version. + * <p> + * Called whenever dependencies are about to be sent to the client side for + * loading. + * + * @param dependencies + * the collected dependencies, possibly already modified by other + * filters + * @param filterContext + * context information, e.g about the target UI + * @return a list of dependencies to load + */ + public List<Dependency> filter(List<Dependency> dependencies, + FilterContext filterContext); + + /** + * Provides context information for the dependency filter operation. + * + * @since + */ + public static class FilterContext implements Serializable { + + private VaadinSession session; + + /** + * Creates a new context for the given session. + * + * @param session + * the session which is loading dependencies + */ + public FilterContext(VaadinSession session) { + this.session = session; + } + + /** + * Gets the related Vaadin session. + * + * @return the Vaadin session + */ + public VaadinSession getSession() { + return session; + } + + /** + * Gets the related Vaadin service. + * + * @return the Vaadin service + */ + public VaadinService getService() { + return session.getService(); + } + } +} diff --git a/server/src/main/java/com/vaadin/server/VaadinService.java b/server/src/main/java/com/vaadin/server/VaadinService.java index 80ae39cb35..4bd2958eaf 100644 --- a/server/src/main/java/com/vaadin/server/VaadinService.java +++ b/server/src/main/java/com/vaadin/server/VaadinService.java @@ -135,6 +135,7 @@ public abstract class VaadinService implements Serializable { private ClassLoader classLoader; private Iterable<RequestHandler> requestHandlers; + private Iterable<DependencyFilter> dependencyFilters; private boolean atmosphereAvailable = checkAtmosphereSupport(); @@ -206,6 +207,8 @@ public abstract class VaadinService implements Serializable { requestHandlers = Collections.unmodifiableCollection(handlers); + dependencyFilters = Collections + .unmodifiableCollection(createDependencyFilters()); initialized = true; } @@ -1430,6 +1433,54 @@ public abstract class VaadinService implements Serializable { } /** + * Constructs the list of resource dependency filters to use for the + * application. + * <p> + * The filters can freely update the dependencies in any way they see fit + * (bundle, rewrite, merge). + * <p> + * By default all filters found using the service loader are added to the + * list. + * <p> + * The filters are called in the order the service loader returns the + * filters, which is undefined. If you need a specific order, you can + * override this method and alter the order. + * + * @since + * @return the list of dependency filters to use for filtering resources, + * not null + * @throws ServiceException + * if something went wrong while determining the filters + * + */ + protected List<DependencyFilter> createDependencyFilters() + throws ServiceException { + ArrayList<DependencyFilter> filters = new ArrayList<>(); + ServiceLoader<DependencyFilter> loader = ServiceLoader + .load(DependencyFilter.class, getClassLoader()); + loader.iterator().forEachRemaining(filters::add); + + return filters; + } + + /** + * Gets the filters which all resource dependencies are passed through + * before being sent to the client for loading. + * + * @see #createDependencyFilters() + * + * @since + * @return the dependency filters to pass resources dependencies through + * before loading + */ + public Iterable<DependencyFilter> getDependencyFilters() { + if (dependencyFilters == null) { + return Collections.emptyList(); + } + return dependencyFilters; + } + + /** * Handles the incoming request and writes the response into the response * object. Uses {@link #getRequestHandlers()} for handling the request. * <p> diff --git a/server/src/main/java/com/vaadin/server/communication/UidlWriter.java b/server/src/main/java/com/vaadin/server/communication/UidlWriter.java index 28f4cda09a..7b5f83000c 100644 --- a/server/src/main/java/com/vaadin/server/communication/UidlWriter.java +++ b/server/src/main/java/com/vaadin/server/communication/UidlWriter.java @@ -30,6 +30,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import com.vaadin.server.ClientConnector; +import com.vaadin.server.DependencyFilter.FilterContext; import com.vaadin.server.JsonPaintTarget; import com.vaadin.server.LegacyCommunicationManager; import com.vaadin.server.LegacyCommunicationManager.ClientCache; @@ -283,7 +284,8 @@ public class UidlWriter implements Serializable { }); List<Dependency> dependencies = Dependency - .findDependencies(newConnectorTypes, manager); + .findAndFilterDependencies(newConnectorTypes, manager, + new FilterContext(session)); // Include dependencies in output if there are any if (!dependencies.isEmpty()) { diff --git a/server/src/main/java/com/vaadin/ui/Dependency.java b/server/src/main/java/com/vaadin/ui/Dependency.java index 509b9d3039..6274ae23e6 100644 --- a/server/src/main/java/com/vaadin/ui/Dependency.java +++ b/server/src/main/java/com/vaadin/ui/Dependency.java @@ -24,7 +24,10 @@ import com.vaadin.annotations.HtmlImport; import com.vaadin.annotations.JavaScript; import com.vaadin.annotations.StyleSheet; import com.vaadin.server.ClientConnector; +import com.vaadin.server.DependencyFilter; +import com.vaadin.server.DependencyFilter.FilterContext; import com.vaadin.server.LegacyCommunicationManager; +import com.vaadin.server.VaadinService; /** * Represents a stylesheet or JavaScript to include on the page. @@ -152,7 +155,7 @@ public class Dependency implements Serializable { * @param manager * a reference to the communication manager which tracks * dependencies - * @return + * @return the list of found dependencies */ @SuppressWarnings("deprecation") public static List<Dependency> findDependencies( @@ -172,4 +175,32 @@ public class Dependency implements Serializable { return dependencies; } + /** + * Finds all the URLs defined for the given classes, registers the URLs to + * the communication manager, passes the registered dependencies through any + * defined filters and returns the filtered collection of dependencies to + * load. + * + * @since + * @param connectorTypes + * the collection of connector classes to scan + * @param manager + * a reference to the communication manager which tracks + * dependencies + * @param context + * the context information for the filtering operation + * @return the list of found and filtered dependencies + */ + public static List<Dependency> findAndFilterDependencies( + List<Class<? extends ClientConnector>> connectorTypes, + LegacyCommunicationManager manager, FilterContext context) { + List<Dependency> dependencies = findDependencies(connectorTypes, + manager); + VaadinService service = context.getService(); + for (DependencyFilter filter : service.getDependencyFilters()) { + dependencies = filter.filter(dependencies, context); + } + return dependencies; + } + } diff --git a/test/dependency-rewrite-addon/pom.xml b/test/dependency-rewrite-addon/pom.xml new file mode 100644 index 0000000000..f822e34b77 --- /dev/null +++ b/test/dependency-rewrite-addon/pom.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>com.vaadin</groupId> + <artifactId>vaadin-test</artifactId> + <version>8.1-SNAPSHOT</version> + </parent> + <artifactId>vaadin-test-dependency-rewrite-addon</artifactId> + <packaging>jar</packaging> + <dependencies> + <dependency> + <groupId>com.vaadin</groupId> + <artifactId>vaadin-test-widget-set-testutil</artifactId> + </dependency> + </dependencies> + +</project> diff --git a/test/dependency-rewrite-addon/src/main/java/com/vaadin/test/dependencyrewriteaddon/RewriteJQueryFilter.java b/test/dependency-rewrite-addon/src/main/java/com/vaadin/test/dependencyrewriteaddon/RewriteJQueryFilter.java new file mode 100644 index 0000000000..415bb9b093 --- /dev/null +++ b/test/dependency-rewrite-addon/src/main/java/com/vaadin/test/dependencyrewriteaddon/RewriteJQueryFilter.java @@ -0,0 +1,28 @@ +package com.vaadin.test.dependencyrewriteaddon; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import com.vaadin.server.DependencyFilter; +import com.vaadin.ui.Dependency; +import com.vaadin.ui.Dependency.Type; + +public class RewriteJQueryFilter implements DependencyFilter { + + @Override + public List<Dependency> filter(List<Dependency> dependencies, + FilterContext filterContext) { + List<Dependency> filtered = new ArrayList<>(); + for (Dependency dependency : dependencies) { + if (dependency.getType() == Type.JAVASCRIPT && dependency.getUrl() + .toLowerCase(Locale.ENGLISH).contains("jquery")) { + filtered.add( + new Dependency(Type.JAVASCRIPT, "vaadin://jquery.js")); + } else { + filtered.add(dependency); + } + } + return filtered; + } +} diff --git a/test/dependency-rewrite-addon/src/main/resources/META-INF/services/com.vaadin.server.DependencyFilter b/test/dependency-rewrite-addon/src/main/resources/META-INF/services/com.vaadin.server.DependencyFilter new file mode 100644 index 0000000000..f62efa26fd --- /dev/null +++ b/test/dependency-rewrite-addon/src/main/resources/META-INF/services/com.vaadin.server.DependencyFilter @@ -0,0 +1 @@ +com.vaadin.test.dependencyrewriteaddon.RewriteJQueryFilter diff --git a/test/dependency-rewrite/pom.xml b/test/dependency-rewrite/pom.xml new file mode 100644 index 0000000000..d776eba883 --- /dev/null +++ b/test/dependency-rewrite/pom.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>com.vaadin</groupId> + <artifactId>vaadin-test</artifactId> + <version>8.1-SNAPSHOT</version> + </parent> + <artifactId>vaadin-test-dependency-rewrite</artifactId> + <packaging>war</packaging> + + <dependencies> + <dependency> + <groupId>com.vaadin</groupId> + <artifactId>vaadin-test-widget-set-testutil</artifactId> + </dependency> + <dependency> + <groupId>com.vaadin</groupId> + <artifactId>vaadin-client-compiled</artifactId> + </dependency> + <dependency> + <groupId>com.vaadin</groupId> + <artifactId>vaadin-test-dependency-rewrite-addon</artifactId> + <version>${project.version}</version> + </dependency> + + </dependencies> + +</project> diff --git a/test/dependency-rewrite/src/main/java/com/vaadin/test/dependencyrewrite/ApplicationDependencyFilter.java b/test/dependency-rewrite/src/main/java/com/vaadin/test/dependencyrewrite/ApplicationDependencyFilter.java new file mode 100644 index 0000000000..63e1db0377 --- /dev/null +++ b/test/dependency-rewrite/src/main/java/com/vaadin/test/dependencyrewrite/ApplicationDependencyFilter.java @@ -0,0 +1,26 @@ +package com.vaadin.test.dependencyrewrite; + +import java.util.ArrayList; +import java.util.List; + +import com.vaadin.server.DependencyFilter; +import com.vaadin.ui.Dependency; + +public class ApplicationDependencyFilter implements DependencyFilter { + + @Override + public List<Dependency> filter(List<Dependency> dependencies, + FilterContext filterContext) { + List<Dependency> filtered = new ArrayList<>(); + for (Dependency dependency : dependencies) { + if (dependency.getUrl().startsWith("vaadin://")) { + filtered.add(new Dependency(dependency.getType(), dependency + .getUrl().replace("vaadin://", "vaadin://sub/"))); + } else { + filtered.add(dependency); + } + } + return filtered; + } + +} diff --git a/test/dependency-rewrite/src/main/java/com/vaadin/test/dependencyrewrite/DependencyDynamicUI.java b/test/dependency-rewrite/src/main/java/com/vaadin/test/dependencyrewrite/DependencyDynamicUI.java new file mode 100644 index 0000000000..ec696713f1 --- /dev/null +++ b/test/dependency-rewrite/src/main/java/com/vaadin/test/dependencyrewrite/DependencyDynamicUI.java @@ -0,0 +1,34 @@ +package com.vaadin.test.dependencyrewrite; + +import com.vaadin.annotations.JavaScript; +import com.vaadin.server.VaadinRequest; +import com.vaadin.ui.Label; +import com.vaadin.ui.UI; +import com.vaadin.ui.VerticalLayout; + +public class DependencyDynamicUI extends UI { + + @JavaScript("http://jquery.com/jquery-1.2.3.js") + public static class MyJqueryLabel extends Label { + public MyJqueryLabel() { + super("MyJqueryLabel"); + } + } + + @JavaScript("vaadin://jquery-33.3.3.js") + public static class MyJqueryLabel2 extends Label { + public MyJqueryLabel2() { + super("MyJqueryLabel2"); + } + } + + @Override + protected void init(VaadinRequest request) { + VerticalLayout layout = new VerticalLayout(); + layout.addComponent(new MyJqueryLabel()); + layout.addComponent(new MyJqueryLabel2()); + + setContent(layout); + } + +} diff --git a/test/dependency-rewrite/src/main/java/com/vaadin/test/dependencyrewrite/DependencyInitialUI.java b/test/dependency-rewrite/src/main/java/com/vaadin/test/dependencyrewrite/DependencyInitialUI.java new file mode 100644 index 0000000000..3e600c2aaa --- /dev/null +++ b/test/dependency-rewrite/src/main/java/com/vaadin/test/dependencyrewrite/DependencyInitialUI.java @@ -0,0 +1,21 @@ +package com.vaadin.test.dependencyrewrite; + +import com.vaadin.annotations.JavaScript; +import com.vaadin.server.VaadinRequest; +import com.vaadin.test.dependencyrewrite.DependencyDynamicUI.MyJqueryLabel; +import com.vaadin.ui.UI; +import com.vaadin.ui.VerticalLayout; + +@JavaScript("http://jquery.com/jquery-12.2.3.js") +@JavaScript("vaadin://jquery-323.3.3.js") +public class DependencyInitialUI extends UI { + + @Override + protected void init(VaadinRequest request) { + VerticalLayout layout = new VerticalLayout(); + layout.addComponent(new MyJqueryLabel()); + + setContent(layout); + } + +} diff --git a/test/dependency-rewrite/src/main/java/com/vaadin/test/dependencyrewrite/MyService.java b/test/dependency-rewrite/src/main/java/com/vaadin/test/dependencyrewrite/MyService.java new file mode 100644 index 0000000000..5723a93885 --- /dev/null +++ b/test/dependency-rewrite/src/main/java/com/vaadin/test/dependencyrewrite/MyService.java @@ -0,0 +1,27 @@ +package com.vaadin.test.dependencyrewrite; + +import java.util.List; + +import com.vaadin.server.DependencyFilter; +import com.vaadin.server.DeploymentConfiguration; +import com.vaadin.server.ServiceException; +import com.vaadin.server.VaadinServlet; +import com.vaadin.server.VaadinServletService; + +public class MyService extends VaadinServletService { + + public MyService(VaadinServlet servlet, + DeploymentConfiguration deploymentConfiguration) + throws ServiceException { + super(servlet, deploymentConfiguration); + } + + @Override + protected List<DependencyFilter> createDependencyFilters() + throws ServiceException { + List<DependencyFilter> list = super.createDependencyFilters(); + list.add(new ApplicationDependencyFilter()); + return list; + } + +} diff --git a/test/dependency-rewrite/src/main/java/com/vaadin/test/dependencyrewrite/MyUIProvider.java b/test/dependency-rewrite/src/main/java/com/vaadin/test/dependencyrewrite/MyUIProvider.java new file mode 100644 index 0000000000..dd4675888a --- /dev/null +++ b/test/dependency-rewrite/src/main/java/com/vaadin/test/dependencyrewrite/MyUIProvider.java @@ -0,0 +1,22 @@ +package com.vaadin.test.dependencyrewrite; + +import com.vaadin.server.UIClassSelectionEvent; +import com.vaadin.server.UIProvider; +import com.vaadin.ui.UI; + +public class MyUIProvider extends UIProvider { + + @Override + public Class<? extends UI> getUIClass(UIClassSelectionEvent event) { + String url = event.getRequest().getPathInfo(); + if (url.contains("/dynamic/")) { + return DependencyDynamicUI.class; + } else if (url.contains("/initial/")) { + return DependencyInitialUI.class; + } else { + return null; + } + + } + +} diff --git a/test/dependency-rewrite/src/main/java/com/vaadin/test/dependencyrewrite/MyUIServlet.java b/test/dependency-rewrite/src/main/java/com/vaadin/test/dependencyrewrite/MyUIServlet.java new file mode 100644 index 0000000000..dbddf3d970 --- /dev/null +++ b/test/dependency-rewrite/src/main/java/com/vaadin/test/dependencyrewrite/MyUIServlet.java @@ -0,0 +1,22 @@ +package com.vaadin.test.dependencyrewrite; + +import javax.servlet.annotation.WebInitParam; +import javax.servlet.annotation.WebServlet; + +import com.vaadin.server.DeploymentConfiguration; +import com.vaadin.server.ServiceException; +import com.vaadin.server.VaadinServlet; +import com.vaadin.server.VaadinServletService;; + +@WebServlet(urlPatterns = { + "/*" }, name = "MyUIServlet", asyncSupported = true, initParams = @WebInitParam(name = "UIProvider", value = "com.vaadin.test.dependencyrewrite.MyUIProvider")) +public class MyUIServlet extends VaadinServlet { + @Override + protected VaadinServletService createServletService( + DeploymentConfiguration deploymentConfiguration) + throws ServiceException { + MyService service = new MyService(this, deploymentConfiguration); + service.init(); + return service; + } +} diff --git a/test/dependency-rewrite/src/main/webapp/VAADIN/sub/jquery.js b/test/dependency-rewrite/src/main/webapp/VAADIN/sub/jquery.js new file mode 100644 index 0000000000..b6aa9b8a0f --- /dev/null +++ b/test/dependency-rewrite/src/main/webapp/VAADIN/sub/jquery.js @@ -0,0 +1,5 @@ +if (!window.jqueryLoaded) { + window.jqueryLoaded = 1; +} else { + window.jqueryLoaded++; +} diff --git a/test/dependency-rewrite/src/test/java/com/vaadin/test/dependencyrewrite/DependencyFilterIT.java b/test/dependency-rewrite/src/test/java/com/vaadin/test/dependencyrewrite/DependencyFilterIT.java new file mode 100644 index 0000000000..5256def3f5 --- /dev/null +++ b/test/dependency-rewrite/src/test/java/com/vaadin/test/dependencyrewrite/DependencyFilterIT.java @@ -0,0 +1,29 @@ +package com.vaadin.test.dependencyrewrite; + +import org.junit.Assert; +import org.junit.Test; +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.phantomjs.PhantomJSDriver; + +import com.vaadin.testbench.TestBenchTestCase; + +public class DependencyFilterIT extends TestBenchTestCase { + + @Test + public void dynamicallyAddedResources() { + setDriver(new PhantomJSDriver()); + getDriver().get("http://localhost:8080/dynamic/"); + Assert.assertEquals(1L, ((JavascriptExecutor) getDriver()) + .executeScript("return window.jqueryLoaded")); + } + + @Test + public void initiallyLoadedResources() { + setDriver(new PhantomJSDriver()); + getDriver().get("http://localhost:8080/initial/"); + // 2 because of https://github.com/vaadin/framework/issues/9181 + Assert.assertEquals(2L, ((JavascriptExecutor) getDriver()) + .executeScript("return window.jqueryLoaded")); + } + +} diff --git a/test/pom.xml b/test/pom.xml index 9987f99082..919ef28ac7 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -107,6 +107,8 @@ <module>servlet-containers/jsp-integration</module> <module>bean-api-validation</module> <module>bean-impl-validation</module> + <module>dependency-rewrite-addon</module> + <module>dependency-rewrite</module> </modules> <build> |