Skip to content

Commit

Permalink
CCDM: allow page configuration in client mode
Browse files Browse the repository at this point in the history
- Added a `VaadinAppShell` class for configurin clientSide page
- Added a `ServletContinerInitializer` for checking annotations on startup
- Implement `Meta` annotations in clientSide mode
  • Loading branch information
manolo committed Nov 27, 2019
1 parent 297cb08 commit eaa922a
Show file tree
Hide file tree
Showing 10 changed files with 615 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2000-2018 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.page;

import java.io.Serializable;

/**
* A marker class to configure the `index.hml` page when in clientSide mode.
*
* There should be only one class in the application extending this.
*
* It is mandatory to remove all annotations from other class before adding
* an <code>AppShell</code> to the project.
*
* <p>
*
* <code>
*
* &#64;Meta(name = "Author", content = "Donald Duck")
* public class AppShell extends VaadinAppShell {
* }
* </code>
*
* @since 3.0
*/
@Meta(name = "charset", content = "UTF-8")
public class VaadinAppShell implements Serializable {
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package com.vaadin.flow.server.communication;

import javax.servlet.ServletContext;

import java.io.IOException;
import java.util.regex.Pattern;

Expand All @@ -29,8 +31,11 @@
import com.vaadin.flow.component.UI;
import com.vaadin.flow.server.VaadinRequest;
import com.vaadin.flow.server.VaadinResponse;
import com.vaadin.flow.server.VaadinService;
import com.vaadin.flow.server.VaadinServletService;
import com.vaadin.flow.server.VaadinSession;
import com.vaadin.flow.server.frontend.FrontendUtils;
import com.vaadin.flow.server.startup.VaadinAppShellRegistry;

import elemental.json.JsonObject;
import elemental.json.impl.JsonUtil;
Expand All @@ -55,7 +60,11 @@ public boolean synchronizedHandleRequest(VaadinSession session,
VaadinRequest request, VaadinResponse response) throws IOException {
Document indexDocument = getIndexHtmlDocument(request);

ServletContext context = ((VaadinServletService) VaadinService
.getCurrent()).getServlet().getServletContext();

prependBaseHref(request, indexDocument);

if (request.getService().getBootstrapInitialPredicate()
.includeInitialUidl(request)) {
includeInitialUidl(session, request, response, indexDocument);
Expand All @@ -65,7 +74,10 @@ public boolean synchronizedHandleRequest(VaadinSession session,
session.setAttribute(SERVER_ROUTING, Boolean.TRUE);
}

includeAppShellElements(indexDocument, context);

configureErrorDialogStyles(indexDocument);

showWebpackErrors(indexDocument);

response.setContentType(CONTENT_TYPE_TEXT_HTML_UTF_8);
Expand All @@ -88,13 +100,19 @@ private void includeInitialUidl(VaadinSession session,
Document indexDocument) {
JsonObject initial = getInitialJson(request, response, session);


Element elm = new Element("script");
elm.attr("initial", "");
elm.appendChild(new DataNode("window.Vaadin = {Flow : {initial: "
+ JsonUtil.stringify(initial) + "}}"));
indexDocument.head().insertChildren(0, elm);
}

private void includeAppShellElements(Document document, ServletContext context) {
VaadinAppShellRegistry.getInstance(context).getElements()
.forEach(elem -> document.head().appendChild(elem));
}

@Override
protected boolean canHandleRequest(VaadinRequest request) {
String pathInfo = request.getPathInfo();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ default Set<Class<?>> getAnnotatedClasses(String className)

/**
* Get class loader which is used to find classes.
*
*
* @return the class loader which is used to find classes..
*/
ClassLoader getClassLoader();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.util.stream.Collectors;

import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.page.VaadinAppShell;
import com.vaadin.flow.router.ParentLayout;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.router.RouteAlias;
Expand Down Expand Up @@ -107,7 +108,12 @@ protected String getErrorHint() {
* {@code clazz}
*/
protected String getClassAnnotations(Class<?> clazz) {
return getAnnotations().stream()
return getClassAnnotations(clazz, getAnnotations());
}

@SuppressWarnings("unchecked")
protected static String getClassAnnotations(Class<?> clazz, List<Class<?>> annotations) {
return annotations.stream()
.filter(ann -> clazz
.isAnnotationPresent((Class<? extends Annotation>) ann))
.map(Class::getSimpleName).collect(Collectors.joining(", "));
Expand All @@ -130,6 +136,8 @@ private List<String> validateAnnotatedClasses(
offendingAnnotations.add(String.format(NON_PARENT_ALIAS,
clazz.getName(), getClassAnnotations(clazz)));
}
} else if (VaadinAppShell.class.isAssignableFrom(clazz)) {
// This is handled in separated validation
} else if (!RouterLayout.class.isAssignableFrom(clazz)) {
if (!Modifier.isAbstract(clazz.getModifiers())) {
handleNonRouterLayout(clazz)
Expand All @@ -141,7 +149,6 @@ private List<String> validateAnnotatedClasses(
clazz.getName(), getClassAnnotations(clazz)));
}
}

return offendingAnnotations;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/*
* Copyright 2000-2018 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.server.startup;

import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import javax.servlet.annotation.HandlesTypes;
import javax.servlet.annotation.WebListener;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import com.vaadin.flow.component.page.Meta;
import com.vaadin.flow.component.page.VaadinAppShell;
import com.vaadin.flow.function.DeploymentConfiguration;
import com.vaadin.flow.server.InvalidApplicationConfigurationException;
import com.vaadin.flow.server.VaadinServlet;
import com.vaadin.flow.server.startup.ServletDeployer.StubServletConfig;

/**
* Servlet initializer visiting {@link VaadinAppShell} configuration.
*
* @since 3.0
*/
@HandlesTypes({ VaadinAppShell.class, Meta.class })
@WebListener
public class VaadinAppShellInitializer implements ServletContainerInitializer,
Serializable, ServletContextListener {

@Override
public void onStartup(Set<Class<?>> classes, ServletContext context)
throws ServletException {

Collection<? extends ServletRegistration> registrations = context
.getServletRegistrations().values();
if (registrations.isEmpty()) {
return;
}

DeploymentConfiguration config = StubServletConfig
.createDeploymentConfiguration(context,
registrations.iterator().next(), VaadinServlet.class);

init(classes, context, config);
}

@Override
public void contextInitialized(ServletContextEvent sce) {
}

@Override
public void contextDestroyed(ServletContextEvent sce) {
}

/**
* Initializes the {@link VaadinAppShellRegistry} for the application.
*
* @param classes
* a set of classes that matches the {@link HandlesTypes} set in
* this class.
* @param context
* the servlet context.
* @param config
* the vaadin configuration for the application.
*/
@SuppressWarnings("unchecked")
public static void init(Set<Class<?>> classes, ServletContext context,
DeploymentConfiguration config) {

if (!config.isClientSideMode()) {
return;
}

VaadinAppShellRegistry registry = VaadinAppShellRegistry
.getInstance(context);

// Reset the registry
registry.setShell(null, context);

if (classes.isEmpty()) {
return;
}

List<String> offendingAnnotations = new ArrayList<>();

classes.stream()
// sort classes by putting VaadinAppShell in first position
.sorted((a, b) -> registry.isShell(a) ? -1
: registry.isShell(b) ? 1 : 0)
.forEach(clz -> {
if (registry.isShell(clz)) {
registry.setShell(
(Class<? extends VaadinAppShell>) clz, context);
} else {
String error = registry.validateClass(clz);
if (error != null) {
offendingAnnotations.add(error);
}
}
});

if (!offendingAnnotations.isEmpty()) {
String message = String.format(VaadinAppShellRegistry.ERROR_HEADER,
registry.getShell().getName(),
String.join("\n", offendingAnnotations));
throw new InvalidApplicationConfigurationException(message);
}
}

/**
* Return the set of valid annotations in a {@link VaadinAppShell} class.
* This method is thought to be called from external plugins to decouple
* them.
*
* @return
*/
@SuppressWarnings("unchecked")
public static List<Class<? extends Annotation>> getValidAnnotations() {
HandlesTypes annotation = VaadinAppShellInitializer.class
.getAnnotation(HandlesTypes.class);
assert annotation != null;
List<Class<? extends Annotation>> ret = new ArrayList<>();
for (Class<?> clazz : annotation.value()) {
if (clazz.isAnnotation()) {
ret.add((Class<? extends Annotation>) clazz);
}
}
return ret;
}

/**
* Return the {@link VaadinAppShell} class. This method is thought to be
* called from external plugins to decouple them.
*
* @return
*/
public static List<Class<?>> getValidSupers() {
return Collections.singletonList(VaadinAppShell.class);
}
}

0 comments on commit eaa922a

Please sign in to comment.