aboutsummaryrefslogtreecommitdiffstats
path: root/server/src/main/java
diff options
context:
space:
mode:
authorArtur <artur@vaadin.com>2017-01-25 11:27:49 +0200
committerGitHub <noreply@github.com>2017-01-25 11:27:49 +0200
commitbe694984fb35262b32c89be075e6d4a059931b62 (patch)
treed66a85f91404b5bb7f82d845a25b96ec9cebf350 /server/src/main/java
parente397ea01e5ddb0c977561c008b84c6ed7c0ef706 (diff)
downloadvaadin-framework-be694984fb35262b32c89be075e6d4a059931b62.tar.gz
vaadin-framework-be694984fb35262b32c89be075e6d4a059931b62.zip
Support loading of HTML imports using @HtmlImport (#8301)
Note that not all browsers yet support HTML imports. If a polyfill is needed to load HTML imports, it must be loaded before HTML Imports can be loaded. There is no automatic loading of any polyfills.
Diffstat (limited to 'server/src/main/java')
-rw-r--r--server/src/main/java/com/vaadin/annotations/HtmlImport.java73
-rw-r--r--server/src/main/java/com/vaadin/annotations/InternalContainerAnnotationForHtml.java45
-rw-r--r--server/src/main/java/com/vaadin/annotations/JavaScript.java1
-rw-r--r--server/src/main/java/com/vaadin/server/BootstrapHandler.java59
-rw-r--r--server/src/main/java/com/vaadin/server/communication/UidlWriter.java54
-rw-r--r--server/src/main/java/com/vaadin/ui/Dependency.java174
6 files changed, 333 insertions, 73 deletions
diff --git a/server/src/main/java/com/vaadin/annotations/HtmlImport.java b/server/src/main/java/com/vaadin/annotations/HtmlImport.java
new file mode 100644
index 0000000000..f0c52ff171
--- /dev/null
+++ b/server/src/main/java/com/vaadin/annotations/HtmlImport.java
@@ -0,0 +1,73 @@
+/*
+ * 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.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import com.vaadin.server.ClientConnector;
+
+/**
+ * If this annotation is present on a {@link ClientConnector} class, the
+ * framework ensures the referenced HTML imports are loaded before the init
+ * method for the corresponding client-side connector is invoked.
+ * <p>
+ * Note that not all browsers yet support HTML imports. If a polyfill is needed
+ * to load HTML imports, it must be loaded before HTML Imports can be loaded.
+ * There is no automatic loading of any polyfill.
+ * <p>
+ * <ul>
+ * <li>Relative URLs are interpreted as relative to the {@code VAADIN} folder,
+ * i.e. {@literal bower_components/paper-slider/paper-slider.html} is equal to
+ * {@literal vaadin://bower_components/paper-slider/paper-slider.html}.
+ * <li>Absolute URLs including protocol and host are used as is on the
+ * client-side.
+ * </ul>
+ * Note that it is a good idea to use relative URLs and place all HTML imports
+ * in the same folder. Polymer elements rely on importing dependencies using
+ * relative paths {@literal ../../other-element/other-element.html}, which will
+ * not work if they are installed in different locations.
+ * <p>
+ * HTML imports are added to the page after any {@code @JavaScript} dependencies
+ * added at the same time.
+ * <p>
+ * Example:
+ * <code>@HtmlImport("bower_components/paper-slider/paper-slider.html")</code>
+ * on the class com.example.MyConnector would load the file
+ * http://host.com/VAADIN/bower_components/paper-slider/paper-slider.html before
+ * the {@code init()} method of the client side connector is invoked.
+ *
+ * @author Vaadin Ltd
+ * @since 8.0.0
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Documented
+@Repeatable(InternalContainerAnnotationForHtml.class)
+public @interface HtmlImport {
+
+ /**
+ * HTML file URL(s) to load before using the annotated
+ * {@link ClientConnector} in the browser.
+ *
+ * @return html file URL(s) to load
+ */
+ String[] value();
+}
diff --git a/server/src/main/java/com/vaadin/annotations/InternalContainerAnnotationForHtml.java b/server/src/main/java/com/vaadin/annotations/InternalContainerAnnotationForHtml.java
new file mode 100644
index 0000000000..be009bc4ce
--- /dev/null
+++ b/server/src/main/java/com/vaadin/annotations/InternalContainerAnnotationForHtml.java
@@ -0,0 +1,45 @@
+/*
+ * 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.annotations;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation enabling using multiple {@link HtmlImport @HtmlImport}
+ * annotations.
+ * <p>
+ * <b>NOT meant to be used</b>, for multiple HTML dependencies,
+ * {@link HtmlImport @HtmlImport} should be used instead.
+ *
+ * @author Vaadin Ltd
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Documented
+public @interface InternalContainerAnnotationForHtml {
+
+ /**
+ * Not to be used, instead multiple {@link HtmlImport @HtmlImport}
+ * annotations should be used.
+ *
+ * @return an array of the HtmlImport annotations
+ */
+ HtmlImport[] value();
+}
diff --git a/server/src/main/java/com/vaadin/annotations/JavaScript.java b/server/src/main/java/com/vaadin/annotations/JavaScript.java
index 6ea8904c79..d6c35bdd4c 100644
--- a/server/src/main/java/com/vaadin/annotations/JavaScript.java
+++ b/server/src/main/java/com/vaadin/annotations/JavaScript.java
@@ -69,4 +69,5 @@ public @interface JavaScript {
* @return an array of JavaScript file URLs
*/
public String[] value();
+
}
diff --git a/server/src/main/java/com/vaadin/server/BootstrapHandler.java b/server/src/main/java/com/vaadin/server/BootstrapHandler.java
index be0e9e4593..d94101152e 100644
--- a/server/src/main/java/com/vaadin/server/BootstrapHandler.java
+++ b/server/src/main/java/com/vaadin/server/BootstrapHandler.java
@@ -23,12 +23,15 @@ import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.logging.Logger;
import javax.servlet.http.HttpServletResponse;
@@ -39,8 +42,6 @@ import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.parser.Tag;
-import com.vaadin.annotations.JavaScript;
-import com.vaadin.annotations.StyleSheet;
import com.vaadin.annotations.Viewport;
import com.vaadin.annotations.ViewportGeneratorClass;
import com.vaadin.server.communication.AtmospherePushConnection;
@@ -48,6 +49,8 @@ import com.vaadin.shared.ApplicationConstants;
import com.vaadin.shared.VaadinUriResolver;
import com.vaadin.shared.Version;
import com.vaadin.shared.communication.PushMode;
+import com.vaadin.ui.Dependency;
+import com.vaadin.ui.Dependency.Type;
import com.vaadin.ui.UI;
import elemental.json.Json;
@@ -437,29 +440,25 @@ public abstract class BootstrapHandler extends SynchronizedRequestHandler {
.attr("href", themeUri + "/favicon.ico");
}
- JavaScript[] javaScripts = uiClass
- .getAnnotationsByType(JavaScript.class);
- if (javaScripts != null) {
- for (JavaScript javaScript : javaScripts) {
- String[] resources = javaScript.value();
- for (String resource : resources) {
- String url = registerDependency(context, uiClass, resource);
- head.appendElement("script").attr("type", "text/javascript")
- .attr("src", url);
- }
- }
- }
-
- StyleSheet[] styleSheets = uiClass
- .getAnnotationsByType(StyleSheet.class);
- if (styleSheets != null) {
- for (StyleSheet styleSheet : styleSheets) {
- String[] resources = styleSheet.value();
- for (String resource : resources) {
- String url = registerDependency(context, uiClass, resource);
- head.appendElement("link").attr("rel", "stylesheet")
- .attr("type", "text/css").attr("href", url);
- }
+ Collection<? extends Dependency> deps = Dependency.findDependencies(
+ Collections.singletonList(uiClass),
+ context.getSession().getCommunicationManager());
+ for (Dependency dependency : deps) {
+ Type type = dependency.getType();
+ String url = context.getUriResolver()
+ .resolveVaadinUri(dependency.getUrl());
+ if (type == Type.HTMLIMPORT) {
+ head.appendElement("link").attr("rel", "import").attr("href",
+ url);
+ } else if (type == Type.JAVASCRIPT) {
+ head.appendElement("script").attr("type", "text/javascript")
+ .attr("src", url);
+ } else if (type == Type.STYLESHEET) {
+ head.appendElement("link").attr("rel", "stylesheet")
+ .attr("type", "text/css").attr("href", url);
+ } else {
+ getLogger().severe("Ignoring unknown dependency type "
+ + dependency.getType());
}
}
@@ -468,14 +467,8 @@ public abstract class BootstrapHandler extends SynchronizedRequestHandler {
body.addClass(ApplicationConstants.GENERATED_BODY_CLASSNAME);
}
- private String registerDependency(BootstrapContext context,
- Class<? extends UI> uiClass, String resource) {
- String url = context.getSession().getCommunicationManager()
- .registerDependency(resource, uiClass);
-
- url = context.getUriResolver().resolveVaadinUri(url);
-
- return url;
+ private static Logger getLogger() {
+ return Logger.getLogger(BootstrapHandler.class.getName());
}
protected String getMainDivStyle(BootstrapContext context) {
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 f8f6c2fc5b..4f94f4780b 100644
--- a/server/src/main/java/com/vaadin/server/communication/UidlWriter.java
+++ b/server/src/main/java/com/vaadin/server/communication/UidlWriter.java
@@ -29,8 +29,6 @@ import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
-import com.vaadin.annotations.JavaScript;
-import com.vaadin.annotations.StyleSheet;
import com.vaadin.server.ClientConnector;
import com.vaadin.server.JsonPaintTarget;
import com.vaadin.server.LegacyCommunicationManager;
@@ -40,10 +38,12 @@ import com.vaadin.server.VaadinService;
import com.vaadin.server.VaadinSession;
import com.vaadin.shared.ApplicationConstants;
import com.vaadin.ui.ConnectorTracker;
+import com.vaadin.ui.Dependency;
import com.vaadin.ui.UI;
import elemental.json.Json;
import elemental.json.JsonArray;
+import elemental.json.JsonObject;
import elemental.json.impl.JsonUtil;
/**
@@ -284,43 +284,13 @@ public class UidlWriter implements Serializable {
}
});
- List<String> scriptDependencies = new ArrayList<>();
- List<String> styleDependencies = new ArrayList<>();
-
- for (Class<? extends ClientConnector> class1 : newConnectorTypes) {
- JavaScript[] jsAnnotations = class1
- .getAnnotationsByType(JavaScript.class);
- if (jsAnnotations != null) {
- for (JavaScript jsAnnotation : jsAnnotations) {
- for (String uri : jsAnnotation.value()) {
- scriptDependencies.add(
- manager.registerDependency(uri, class1));
- }
- }
- }
-
- StyleSheet[] styleAnnotations = class1
- .getAnnotationsByType(StyleSheet.class);
- if (styleAnnotations != null) {
- for (StyleSheet styleAnnotation : styleAnnotations) {
- for (String uri : styleAnnotation.value()) {
- styleDependencies.add(
- manager.registerDependency(uri, class1));
- }
- }
- }
- }
-
- // Include script dependencies in output if there are any
- if (!scriptDependencies.isEmpty()) {
- writer.write(", \"scriptDependencies\": "
- + JsonUtil.stringify(toJsonArray(scriptDependencies)));
- }
+ List<Dependency> dependencies = Dependency
+ .findDependencies(newConnectorTypes, manager);
- // Include style dependencies in output if there are any
- if (!styleDependencies.isEmpty()) {
- writer.write(", \"styleDependencies\": "
- + JsonUtil.stringify(toJsonArray(styleDependencies)));
+ // Include dependencies in output if there are any
+ if (!dependencies.isEmpty()) {
+ writer.write(", \"dependencies\": "
+ + JsonUtil.stringify(toJsonArray(dependencies)));
}
session.getDragAndDropService().printJSONResponse(writer);
@@ -339,10 +309,14 @@ public class UidlWriter implements Serializable {
}
}
- private JsonArray toJsonArray(List<String> list) {
+ private JsonArray toJsonArray(List<Dependency> list) {
JsonArray result = Json.createArray();
for (int i = 0; i < list.size(); i++) {
- result.set(i, list.get(i));
+ JsonObject dep = Json.createObject();
+ Dependency dependency = list.get(i);
+ dep.put("type", dependency.getType().name());
+ dep.put("url", dependency.getUrl());
+ result.set(i, dep);
}
return result;
diff --git a/server/src/main/java/com/vaadin/ui/Dependency.java b/server/src/main/java/com/vaadin/ui/Dependency.java
new file mode 100644
index 0000000000..8c82dc0275
--- /dev/null
+++ b/server/src/main/java/com/vaadin/ui/Dependency.java
@@ -0,0 +1,174 @@
+/*
+ * 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.ui;
+
+import java.io.Serializable;
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.vaadin.annotations.HtmlImport;
+import com.vaadin.annotations.JavaScript;
+import com.vaadin.annotations.StyleSheet;
+import com.vaadin.server.ClientConnector;
+import com.vaadin.server.LegacyCommunicationManager;
+
+/**
+ * Represents a stylesheet or JavaScript to include on the page.
+ *
+ * @author Vaadin Ltd
+ */
+public class Dependency implements Serializable {
+ /**
+ * The type of dependency.
+ */
+ public enum Type {
+ STYLESHEET(StyleSheet.class), //
+ JAVASCRIPT(JavaScript.class), //
+ HTMLIMPORT(HtmlImport.class);
+
+ private Class<? extends Annotation> annotationType;
+
+ private Type(Class<? extends Annotation> annotationType) {
+ this.annotationType = annotationType;
+ }
+ }
+
+ private final Type type;
+ private final String url;
+
+ /**
+ * Creates a new dependency of the given type, to be loaded from the given
+ * URL.
+ * <p>
+ * The URL is passed through the translation mechanism before loading, so
+ * custom protocols such as "vaadin://" can be used.
+ *
+ * @param type
+ * the type of dependency, not <code>null</code>
+ * @param url
+ * the URL to load the dependency from, not <code>null</code>
+ */
+ public Dependency(Type type, String url) {
+ if (url == null) {
+ throw new IllegalArgumentException("url cannot be null");
+ }
+ assert type != null;
+ this.type = type;
+ this.url = url;
+ }
+
+ /**
+ * Gets the untranslated URL for the dependency.
+ *
+ * @return the URL for the dependency
+ */
+ public String getUrl() {
+ return url;
+ }
+
+ /**
+ * Gets the type of the dependency.
+ *
+ * @return the type of the dependency
+ */
+ public Type getType() {
+ return type;
+ }
+
+ /**
+ * Finds all the URLs defined for the given class using annotations for the
+ * given type, registers the URLs to the communication manager and adds the
+ * registered dependencies to the given list.
+ *
+ * @param type
+ * the type of dependencies to look for
+ * @param cls
+ * the class to scan
+ * @param manager
+ * a reference to the communication manager which tracks
+ * dependencies
+ * @param dependencies
+ * the list to add registered dependencies to
+ *
+ * @return a stream of resource URLs in the order defined by the annotations
+ */
+ @SuppressWarnings("deprecation")
+ private static void findAndRegisterResources(Type type,
+ Class<? extends ClientConnector> cls,
+ LegacyCommunicationManager manager, List<Dependency> dependencies) {
+ Annotation[] annotations = cls
+ .getAnnotationsByType(type.annotationType);
+ if (annotations != null) {
+ for (Annotation annotation : annotations) {
+ String[] resources;
+ if (annotation instanceof StyleSheet) {
+ resources = ((StyleSheet) annotation).value();
+ } else if (annotation instanceof JavaScript) {
+ resources = ((JavaScript) annotation).value();
+ } else if (annotation instanceof HtmlImport) {
+ resources = ((HtmlImport) annotation).value();
+ } else {
+ throw new IllegalArgumentException(
+ "Unknown annotation type: "
+ + annotation.annotationType().getName());
+ }
+
+ for (String resource : resources) {
+ String url = manager.registerDependency(resource, cls);
+ dependencies.add(new Dependency(type, url));
+ }
+ }
+ }
+ }
+
+ /**
+ * Finds all the URLs defined for the given classes, registers the URLs to
+ * the communication manager and returns the registered dependencies.
+ * <p>
+ * The returned collection contains all types of dependencies for each class
+ * in the given list in the order the classes are in the list, i.e. all
+ * dependencies for the first class before all dependencies for the next
+ * class.
+ * <p>
+ * JavaScript dependencies are returned before HTML imports.
+ *
+ * @param connectorTypes
+ * the collection of connector classes to scan
+ * @param manager
+ * a reference to the communication manager which tracks
+ * dependencies
+ * @return
+ */
+ @SuppressWarnings("deprecation")
+ public static List<Dependency> findDependencies(
+ List<Class<? extends ClientConnector>> connectorTypes,
+ LegacyCommunicationManager manager) {
+ List<Dependency> dependencies = new ArrayList<>();
+
+ for (Class<? extends ClientConnector> connectorType : connectorTypes) {
+ findAndRegisterResources(Type.JAVASCRIPT, connectorType, manager,
+ dependencies);
+ findAndRegisterResources(Type.HTMLIMPORT, connectorType, manager,
+ dependencies);
+ findAndRegisterResources(Type.STYLESHEET, connectorType, manager,
+ dependencies);
+ }
+
+ return dependencies;
+ }
+
+}