Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse html import dependencies and add them as dependencies via UIInternals #3568

Merged
merged 10 commits into from
Feb 19, 2018
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.vaadin.flow.component.Component;
Expand All @@ -34,6 +35,7 @@
import com.vaadin.flow.component.dependency.Uses;
import com.vaadin.flow.internal.AnnotationReader;
import com.vaadin.flow.internal.ReflectTools;
import com.vaadin.flow.shared.ui.LoadMode;

/**
* Immutable meta data related to a component class.
Expand All @@ -48,11 +50,11 @@ public class ComponentMetaData {
* Framework internal class, thus package-private.
*/
public static class DependencyInfo {
private final List<HtmlImport> htmlImports = new ArrayList<>();
private final List<HtmlImportDependency> htmlImports = new ArrayList<>();
private final List<JavaScript> javaScripts = new ArrayList<>();
private final List<StyleSheet> styleSheets = new ArrayList<>();

List<HtmlImport> getHtmlImports() {
List<HtmlImportDependency> getHtmlImports() {
return Collections.unmodifiableList(htmlImports);
}

Expand All @@ -66,6 +68,27 @@ List<StyleSheet> getStyleSheets() {

}

public static class HtmlImportDependency {

private final Collection<String> uris;

private final LoadMode loadMode;

private HtmlImportDependency(Collection<String> uris,
LoadMode loadMode) {
this.uris = uris;
this.loadMode = loadMode;
}

public Collection<String> getUris() {
return Collections.unmodifiableCollection(uris);
}

public LoadMode getLoadMode() {
return loadMode;
}
}

/**
* Synchronized properties defined for a {@link Component} class.
* <p>
Expand Down Expand Up @@ -126,8 +149,8 @@ private static DependencyInfo findDependencies(

scannedClasses.add(componentClass);

dependencyInfo.htmlImports.addAll(
AnnotationReader.getHtmlImportAnnotations(componentClass));
dependencyInfo.htmlImports
.addAll(getHtmlImportDependencies(componentClass));
dependencyInfo.javaScripts.addAll(
AnnotationReader.getJavaScriptAnnotations(componentClass));
dependencyInfo.styleSheets.addAll(
Expand Down Expand Up @@ -169,6 +192,22 @@ public DependencyInfo getDependencyInfo() {
return dependencyInfo;
}

private static Collection<HtmlImportDependency> getHtmlImportDependencies(
Class<? extends Component> componentClass) {
return AnnotationReader.getHtmlImportAnnotations(componentClass)
.stream().map(ComponentMetaData::getHtmlImportDependencies)
.collect(Collectors.toList());
}

private static HtmlImportDependency getHtmlImportDependencies(
HtmlImport htmlImport) {
String value = htmlImport.value();
HtmlDependencyParser parser = new HtmlDependencyParser(value);

return new HtmlImportDependency(parser.parseDependencies(),
htmlImport.loadMode());
}

/**
* Scans the class for {@link Synchronize} annotations and gathers the data.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
* Copyright 2000-2017 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.flow.component.internal;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Stream;

import javax.servlet.ServletContext;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.vaadin.flow.server.VaadinRequest;
import com.vaadin.flow.server.VaadinService;
import com.vaadin.flow.server.VaadinSession;
import com.vaadin.flow.server.VaadinUriResolverFactory;
import com.vaadin.flow.server.WrappedHttpSession;

/**
* Html import dependencies parser.
* <p>
* It takes the an HTML import url as a root and parse the content recursively
* collecting html import dependencies.
*
* @author Vaadin Ltd
*
*/
public class HtmlDependencyParser {

private final String root;

/**
* Creates a new instance using the given {@code uri} as a root.
*
* @param uri
* HTML import uri
*/
public HtmlDependencyParser(String uri) {
this.root = uri;
}

Collection<String> parseDependencies() {
Set<String> dependencies = new HashSet<>();

parseDependencies(root, dependencies);

return dependencies;
}

private void parseDependencies(String path, Set<String> dependencies) {
if (dependencies.contains(path)) {
return;
}
dependencies.add(path);

VaadinRequest request = VaadinService.getCurrentRequest();
VaadinSession session = VaadinSession.getCurrent();
if (request == null || session == null
|| request.getWrappedSession() == null) {
/*
* Cannot happen in runtime.
*
* But not all unit tests set it. So let's just return the root uri
* and return.
*/
return;
}
VaadinUriResolverFactory factory = session
.getAttribute(VaadinUriResolverFactory.class);
assert factory != null;
ServletContext context = ((WrappedHttpSession) request
.getWrappedSession()).getHttpSession().getServletContext();

String resolvedUri = factory.toServletContextPath(request, path);

try (InputStream content = context.getResourceAsStream(resolvedUri)) {
if (content == null) {
getLogger().info(
"Can't find resource '%s' via the servlet context",
path);
} else {
parseHtmlImports(content, path)
.map(uri -> resolveUri(uri, path))
.forEach(uri -> parseDependencies(uri, dependencies));
}
} catch (IOException exception) {
// ignore exception on close()
getLogger().debug("Couldn't close template input stream",
exception);
}
}

private String resolveUri(String relative, String base) {
if (relative.startsWith("/")) {
return relative;
}
try {
URI uri = new URI(base);
return uri.resolve(relative).toString();
} catch (URISyntaxException exception) {
getLogger().debug(
"Couldn't make URI for {}. The path {} will be used as is.",
base, relative);
}
return relative;
}

private Stream<String> parseHtmlImports(InputStream content, String path) {
assert content != null;
try {
Document parsedDocument = Jsoup.parse(content,
StandardCharsets.UTF_8.name(), "");

return parsedDocument.getElementsByTag("link").stream()
.filter(link -> link.hasAttr("rel") && link.hasAttr("href"))
.filter(link -> link.attr("rel").equals("import"))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MINOR Move the "import" string literal on the left side of this string comparison. rule

.map(link -> link.attr("href"));
} catch (IOException exception) {
getLogger().info(
"Can't parse the template declared using '%s' path", path,
exception);
}
return Stream.empty();
}

private Logger getLogger() {
return LoggerFactory.getLogger(HtmlDependencyParser.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -779,16 +779,17 @@ public void addComponentDependencies(
Page page = ui.getPage();
DependencyInfo dependencies = ComponentUtil
.getDependencies(componentClass);
dependencies.getHtmlImports().forEach(html -> page
.addHtmlImport(getHtmlImportValue(html), html.loadMode()));
dependencies.getHtmlImports()
.forEach(html -> html.getUris().forEach(
uri -> page.addHtmlImport(getHtmlImportValue(uri),
html.getLoadMode())));
dependencies.getJavaScripts()
.forEach(js -> page.addJavaScript(js.value(), js.loadMode()));
dependencies.getStyleSheets().forEach(styleSheet -> page
.addStyleSheet(styleSheet.value(), styleSheet.loadMode()));
}

private String getHtmlImportValue(HtmlImport html) {
String importValue = html.value();
private String getHtmlImportValue(String importValue) {
if (theme != null) {
return VaadinServlet.getCurrent().getUrlTranslation(theme,
importValue);
Expand Down Expand Up @@ -864,7 +865,7 @@ public void setContinueNavigationAction(
/**
* Gets the application id tied with this UI. Different applications in the
* same page have different unique ids.
*
*
* @return the id of the application tied with this UI
*/
public String getAppId() {
Expand Down
Loading