* Add support for pluggable filters for rewriting dependencies Fixes #9151tags/8.1.0.alpha7
@@ -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() |
@@ -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(); | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
@@ -1429,6 +1432,54 @@ public abstract class VaadinService implements Serializable { | |||
return requestHandlers; | |||
} | |||
/** | |||
* 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. |
@@ -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()) { |
@@ -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; | |||
} | |||
} |
@@ -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> |
@@ -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; | |||
} | |||
} |
@@ -0,0 +1 @@ | |||
com.vaadin.test.dependencyrewriteaddon.RewriteJQueryFilter |
@@ -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> |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -0,0 +1,5 @@ | |||
if (!window.jqueryLoaded) { | |||
window.jqueryLoaded = 1; | |||
} else { | |||
window.jqueryLoaded++; | |||
} |
@@ -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")); | |||
} | |||
} |
@@ -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> |