From f8921dc387a572b12ac7c9c6f4677e5a1d0e5b70 Mon Sep 17 00:00:00 2001 From: Artur Date: Tue, 2 May 2017 08:30:59 +0300 Subject: Add support for pluggable filters for rewriting dependencies (#9182) * Add support for pluggable filters for rewriting dependencies Fixes #9151 --- .../java/com/vaadin/server/BootstrapHandler.java | 8 +- .../java/com/vaadin/server/DependencyFilter.java | 88 ++++++++++++++++++++++ .../main/java/com/vaadin/server/VaadinService.java | 51 +++++++++++++ .../vaadin/server/communication/UidlWriter.java | 4 +- server/src/main/java/com/vaadin/ui/Dependency.java | 33 +++++++- test/dependency-rewrite-addon/pom.xml | 19 +++++ .../RewriteJQueryFilter.java | 28 +++++++ .../services/com.vaadin.server.DependencyFilter | 1 + test/dependency-rewrite/pom.xml | 30 ++++++++ .../ApplicationDependencyFilter.java | 26 +++++++ .../dependencyrewrite/DependencyDynamicUI.java | 34 +++++++++ .../dependencyrewrite/DependencyInitialUI.java | 21 ++++++ .../vaadin/test/dependencyrewrite/MyService.java | 27 +++++++ .../test/dependencyrewrite/MyUIProvider.java | 22 ++++++ .../vaadin/test/dependencyrewrite/MyUIServlet.java | 22 ++++++ .../src/main/webapp/VAADIN/sub/jquery.js | 5 ++ .../test/dependencyrewrite/DependencyFilterIT.java | 29 +++++++ test/pom.xml | 2 + 18 files changed, 445 insertions(+), 5 deletions(-) create mode 100644 server/src/main/java/com/vaadin/server/DependencyFilter.java create mode 100644 test/dependency-rewrite-addon/pom.xml create mode 100644 test/dependency-rewrite-addon/src/main/java/com/vaadin/test/dependencyrewriteaddon/RewriteJQueryFilter.java create mode 100644 test/dependency-rewrite-addon/src/main/resources/META-INF/services/com.vaadin.server.DependencyFilter create mode 100644 test/dependency-rewrite/pom.xml create mode 100644 test/dependency-rewrite/src/main/java/com/vaadin/test/dependencyrewrite/ApplicationDependencyFilter.java create mode 100644 test/dependency-rewrite/src/main/java/com/vaadin/test/dependencyrewrite/DependencyDynamicUI.java create mode 100644 test/dependency-rewrite/src/main/java/com/vaadin/test/dependencyrewrite/DependencyInitialUI.java create mode 100644 test/dependency-rewrite/src/main/java/com/vaadin/test/dependencyrewrite/MyService.java create mode 100644 test/dependency-rewrite/src/main/java/com/vaadin/test/dependencyrewrite/MyUIProvider.java create mode 100644 test/dependency-rewrite/src/main/java/com/vaadin/test/dependencyrewrite/MyUIServlet.java create mode 100644 test/dependency-rewrite/src/main/webapp/VAADIN/sub/jquery.js create mode 100644 test/dependency-rewrite/src/test/java/com/vaadin/test/dependencyrewrite/DependencyFilterIT.java 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 deps = Dependency.findDependencies( - Collections.singletonList(uiClass), - context.getSession().getCommunicationManager()); + Collection 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. + *

+ * 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 filter(List 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 requestHandlers; + private Iterable 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; } @@ -1429,6 +1432,54 @@ public abstract class VaadinService implements Serializable { return requestHandlers; } + /** + * Constructs the list of resource dependency filters to use for the + * application. + *

+ * The filters can freely update the dependencies in any way they see fit + * (bundle, rewrite, merge). + *

+ * By default all filters found using the service loader are added to the + * list. + *

+ * 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 createDependencyFilters() + throws ServiceException { + ArrayList filters = new ArrayList<>(); + ServiceLoader 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 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. 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 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 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 findAndFilterDependencies( + List> connectorTypes, + LegacyCommunicationManager manager, FilterContext context) { + List 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 @@ + + + 4.0.0 + + com.vaadin + vaadin-test + 8.1-SNAPSHOT + + vaadin-test-dependency-rewrite-addon + jar + + + com.vaadin + vaadin-test-widget-set-testutil + + + + 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 filter(List dependencies, + FilterContext filterContext) { + List 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 @@ + + + 4.0.0 + + com.vaadin + vaadin-test + 8.1-SNAPSHOT + + vaadin-test-dependency-rewrite + war + + + + com.vaadin + vaadin-test-widget-set-testutil + + + com.vaadin + vaadin-client-compiled + + + com.vaadin + vaadin-test-dependency-rewrite-addon + ${project.version} + + + + + 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 filter(List dependencies, + FilterContext filterContext) { + List 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 createDependencyFilters() + throws ServiceException { + List 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 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 @@ servlet-containers/jsp-integration bean-api-validation bean-impl-validation + dependency-rewrite-addon + dependency-rewrite -- cgit v1.2.3