Skip to content

Commit

Permalink
CCDM: support @Push annotation
Browse files Browse the repository at this point in the history
Fixes #6972
  • Loading branch information
platosha committed Jan 3, 2020
1 parent edcf0f7 commit c761d30
Show file tree
Hide file tree
Showing 11 changed files with 208 additions and 40 deletions.
38 changes: 27 additions & 11 deletions flow-client/src/main/resources/META-INF/resources/frontend/Flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ export interface FlowConfig {
}

interface AppConfig {
productionMode: boolean,
appId: string,
uidl: object,
webComponentMode: boolean
productionMode: boolean;
appId: string;
uidl: object;
webComponentMode: boolean;
}

interface AppInitResponse {
appConfig: AppConfig;
pushScript?: string;
}

interface HTMLRouterContainer extends HTMLElement {
Expand Down Expand Up @@ -178,13 +179,20 @@ export class Flow {
// Enable or disable server side routing
this.response.appConfig.webComponentMode = !serverSideRouting;

const {pushScript, appConfig} = this.response;

if (typeof pushScript === 'string') {
await this.loadScript(pushScript);
}
const {appId} = appConfig;

// Load bootstrap script with server side parameters
const bootstrapMod = await import('./FlowBootstrap');
await bootstrapMod.init(this.response);

// Load custom modules defined by user
if (typeof this.config.imports === 'function') {
this.injectAppIdScript(this.response);
this.injectAppIdScript(appId);
await this.config.imports();
}

Expand All @@ -194,11 +202,10 @@ export class Flow {

// When client-side router, create a container for server views
if (!serverSideRouting) {
const id = this.response.appConfig.appId;
// we use a custom tag for the flow app container
const tag = `flow-container-${id.toLowerCase()}`;
this.container = this.flowRoot.$[id] = document.createElement(tag);
this.container.id = id;
const tag = `flow-container-${appId.toLowerCase()}`;
this.container = this.flowRoot.$[appId] = document.createElement(tag);
this.container.id = appId;

// It might be that components created from server expect that their content has been rendered.
// Appending eagerly the container we avoid these kind of errors.
Expand All @@ -210,8 +217,17 @@ export class Flow {
return this.response;
}

private injectAppIdScript(response: AppInitResponse) {
const appId = response.appConfig.appId;
private async loadScript(url: string): Promise<void> {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.onload = () => resolve();
script.onerror = reject;
script.src = url;
document.body.appendChild(script);
});
}

private injectAppIdScript(appId: string) {
const appIdWithoutHashCode = appId.substring(0, appId.lastIndexOf('-'));
const scriptAppId = document.createElement('script');
scriptAppId.type = 'module';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@
* <li>{@link Viewport}: defines the viewport tag of the page</li>
* <li>{@link BodySize}: configures the size of the body</li>
* <li>{@link PageTitle}: establishes the page title</li>
* <li>{@link PWA}: Defines application PWA properties</li>
* <li>{@link Push}: configures automatic server push</li>
* <li>{@link PWA}: defines application PWA properties</li>
* </ul>
*
* <p>
* There is a single application shell for the entire Vaadin application, and
* therefore there can be at max one class implementing {@link AppShellConfigurator}.
Expand All @@ -55,6 +57,7 @@
* &#64;Viewport("width=device-width, initial-scale=1")
* &#64;BodySize(height = "100vh", width = "100vw")
* &#64;PageTitle("my-title")
* &#67;Push(value = PushMode.AUTOMATIC, transport = Transport.WEBSOCKET_XHR)
* public class AppShell implements VaadinAppShell {
* }
* </code>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;

import com.vaadin.flow.component.PushConfiguration;
import com.vaadin.flow.component.page.AppShellConfigurator;
import com.vaadin.flow.component.page.BodySize;
import com.vaadin.flow.component.page.Inline;
import com.vaadin.flow.component.page.Inline.Position;
import com.vaadin.flow.component.page.Meta;
import com.vaadin.flow.component.page.Push;
import com.vaadin.flow.component.page.TargetElement;
import com.vaadin.flow.component.page.Viewport;
import com.vaadin.flow.internal.ReflectTools;
Expand Down Expand Up @@ -263,6 +265,26 @@ public void modifyIndexHtml(Document document, VaadinRequest request) {
document.body()::appendChild));
}

/**
* Modifies PushConfiguration instance based on the {@link Push}
* annotation on {@link AppShellConfigurator}
*
* @param pushConfiguration the PushConfigration instance to modify
*/
public void modifyPushConfiguration(PushConfiguration pushConfiguration) {
List<Push> pushAnnotations = getAnnotations(Push.class);
if (pushAnnotations.size() > 1) {
throw new InvalidApplicationConfigurationException(String.format(
AppShellRegistry.ERROR_MULTIPLE_ANNOTATION,
Push.class.getSimpleName()));
} else if (!pushAnnotations.isEmpty()) {
Push push = pushAnnotations.get(0);
pushConfiguration.setPushMode(push.value());
pushConfiguration.setTransport(push.transport());
}
}


private void insertElement(Element elm, Consumer<Element> action) {
action.accept(elm);
for (String cssQuery : UNIQUE_ELEMENTS) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import com.vaadin.flow.server.VaadinResponse;
import com.vaadin.flow.server.VaadinServletRequest;
import com.vaadin.flow.server.VaadinSession;
import com.vaadin.flow.server.AppShellRegistry;
import com.vaadin.flow.shared.ApplicationConstants;
import com.vaadin.flow.theme.ThemeDefinition;

Expand Down Expand Up @@ -122,12 +123,11 @@ protected BootstrapContext createAndInitUI(
PushConfiguration pushConfiguration = context.getUI()
.getPushConfiguration();
pushConfiguration.setPushUrl(pushURL);

AppShellRegistry registry = AppShellRegistry
.getInstance(session.getService().getContext());
registry.modifyPushConfiguration(pushConfiguration);

// TODO(manolo) this comment is left intentionally because we
// need to revise whether the info passed to client is valid
// when initialising push. Right now ccdm is not doing
// anything with push.
config.put("pushScript", getPushScript(context));
config.put("requestURL", requestURL);

return context;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import com.vaadin.flow.component.page.Inline;
import com.vaadin.flow.component.page.Meta;
import com.vaadin.flow.component.page.AppShellConfigurator;
import com.vaadin.flow.component.page.Push;
import com.vaadin.flow.component.page.Viewport;
import com.vaadin.flow.function.DeploymentConfiguration;
import com.vaadin.flow.router.PageTitle;
Expand All @@ -61,7 +62,7 @@
*/
@HandlesTypes({ AppShellConfigurator.class, Meta.class, Meta.Container.class,
PWA.class, Inline.class, Inline.Container.class, Viewport.class,
BodySize.class, PageTitle.class, PageConfigurator.class })
BodySize.class, PageTitle.class, PageConfigurator.class, Push.class })
@WebListener
public class VaadinAppShellInitializer implements ServletContainerInitializer,
Serializable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
Expand All @@ -33,6 +32,7 @@
import com.vaadin.flow.internal.ResponseWriterTest.CapturingServletOutputStream;
import com.vaadin.flow.router.Router;
import com.vaadin.flow.router.TestRouteRegistry;
import com.vaadin.flow.server.AppShellRegistry.AppShellRegistryWrapper;
import com.vaadin.flow.server.communication.IndexHtmlRequestListener;
import com.vaadin.flow.server.communication.IndexHtmlResponse;
import com.vaadin.tests.util.MockDeploymentConfiguration;
Expand All @@ -46,6 +46,7 @@ public class TestVaadinServletService extends VaadinServletService {
private Router router;
private List<BootstrapListener> bootstrapListeners = new ArrayList<>();
private List<IndexHtmlRequestListener> indexHtmlRequestListeners = new ArrayList<>();
private VaadinContext context;

public TestVaadinServletService(TestVaadinServlet testVaadinServlet,
DeploymentConfiguration deploymentConfiguration) {
Expand Down Expand Up @@ -113,6 +114,18 @@ public void modifyIndexHtmlResponse(IndexHtmlResponse response) {

super.modifyIndexHtmlResponse(response);
}

@Override
public VaadinContext getContext() {
if (context != null) {
return context;
}
return super.getContext();
}

public void setContext(VaadinContext context) {
this.context = context;
}
}

public class TestVaadinServlet extends VaadinServlet {
Expand Down Expand Up @@ -356,6 +369,12 @@ public void setProductionMode(boolean productionMode) {
deploymentConfiguration.setProductionMode(productionMode);
}

public void setAppShellRegistry(AppShellRegistry appShellRegistry) {
Mockito.when(servletContext
.getAttribute(AppShellRegistryWrapper.class.getName()))
.thenReturn(new AppShellRegistryWrapper(appShellRegistry));
}

public TestVaadinServletResponse createResponse() throws IOException {
HttpServletResponse httpServletResponse = Mockito
.mock(HttpServletResponse.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
package com.vaadin.flow.server.communication;

import javax.servlet.http.HttpServletRequest;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
Expand All @@ -38,13 +37,13 @@

import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.internal.JavaScriptBootstrapUI;
import com.vaadin.flow.server.AppShellRegistry;
import com.vaadin.flow.server.AppShellRegistry.AppShellRegistryWrapper;
import com.vaadin.flow.server.DevModeHandler;
import com.vaadin.flow.server.MockServletServiceSessionSetup;
import com.vaadin.flow.server.AppShellRegistry;
import com.vaadin.flow.server.VaadinResponse;
import com.vaadin.flow.server.VaadinServletRequest;
import com.vaadin.flow.server.VaadinSession;
import com.vaadin.flow.server.AppShellRegistry.AppShellRegistryWrapper;
import com.vaadin.flow.server.frontend.FrontendUtils;
import com.vaadin.flow.server.startup.VaadinAppShellInitializerTest.MyAppShellWithConfigurator;
import com.vaadin.flow.server.startup.VaadinAppShellInitializerTest.MyAppShellWithMultipleAnnotations;
Expand Down Expand Up @@ -373,9 +372,7 @@ public void should_add_metaAndPwa_Inline_Elements_when_appShellPresent() throws
// Set class in context and do not call initializer
AppShellRegistry registry = new AppShellRegistry();
registry.setShell(MyAppShellWithMultipleAnnotations.class);
Mockito.when(mocks.getServletContext()
.getAttribute(AppShellRegistryWrapper.class.getName()))
.thenReturn(new AppShellRegistryWrapper(registry));
mocks.setAppShellRegistry(registry);

indexHtmlRequestHandler.synchronizedHandleRequest(session,
createVaadinRequest("/"), response);
Expand Down Expand Up @@ -414,9 +411,7 @@ public void should_add_elements_when_appShellWithConfigurator() throws Exception
// Set class in context and do not call initializer
AppShellRegistry registry = new AppShellRegistry();
registry.setShell(MyAppShellWithConfigurator.class);
Mockito.when(mocks.getServletContext()
.getAttribute(AppShellRegistryWrapper.class.getName()))
.thenReturn(new AppShellRegistryWrapper(registry));
mocks.setAppShellRegistry(registry);

indexHtmlRequestHandler.synchronizedHandleRequest(session,
createVaadinRequest("/"), response);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,20 @@
import org.junit.Test;
import org.mockito.Mockito;

import com.vaadin.flow.component.PushConfiguration;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.internal.JavaScriptBootstrapUI;
import com.vaadin.flow.component.page.AppShellConfigurator;
import com.vaadin.flow.component.page.Push;
import com.vaadin.flow.dom.NodeVisitor.ElementType;
import com.vaadin.flow.dom.TestNodeVisitor;
import com.vaadin.flow.dom.impl.BasicElementStateProvider;
import com.vaadin.flow.server.AppShellRegistry;
import com.vaadin.flow.server.MockServletServiceSessionSetup;
import com.vaadin.flow.server.MockServletServiceSessionSetup.TestVaadinServletResponse;
import com.vaadin.flow.server.VaadinRequest;
import com.vaadin.flow.server.VaadinSession;
import com.vaadin.flow.shared.communication.PushMode;

import elemental.json.Json;
import elemental.json.JsonObject;
Expand All @@ -48,6 +53,10 @@ public class JavaScriptBootstrapHandlerTest {
private VaadinSession session;
private JavaScriptBootstrapHandler jsInitHandler;

@Push
static public class PushAppShell implements AppShellConfigurator {
}

@Before
public void setup() throws Exception {
mocks = new MockServletServiceSessionSetup();
Expand Down Expand Up @@ -98,9 +107,7 @@ public void should_produceValidJsonResponse() throws Exception {
json.getObject("appConfig").get("serviceUrl"));
Assert.assertEquals("http://localhost:8888/foo/", json.getObject("appConfig").getString("requestURL"));

// Using regex, because version depends on the build
Assert.assertTrue(json.getObject("appConfig").getString("pushScript")
.matches("^\\./VAADIN/static/push/vaadinPush\\.js\\?v=[\\w\\.\\-]+$"));
Assert.assertFalse(json.hasKey("pushScript"));
}

@Test
Expand Down Expand Up @@ -156,6 +163,56 @@ public void should_attachViewTo_Body_when_location() throws Exception {
Mockito.verify(session, Mockito.times(1)).setAttribute(SERVER_ROUTING, Boolean.TRUE);
}

@Test
public void should_respondPushScript_when_enabledInDeploymentConfiguration()
throws Exception {
mocks.getDeploymentConfiguration().setPushMode(PushMode.AUTOMATIC);

VaadinRequest request = mocks.createRequest(mocks,
"/foo/?v-r=init&foo");
jsInitHandler.handleRequest(session, request, response);

Assert.assertEquals(200, response.getErrorCode());
Assert.assertEquals("application/json", response.getContentType());
JsonObject json = Json.parse(response.getPayload());

// Using regex, because version depends on the build
Assert.assertTrue(json.getString("pushScript").matches(
"^\\./VAADIN/static/push/vaadinPush\\.js\\?v=[\\w\\.\\-]+$"));
}

@Test
public void should_invoke_modifyPushConfiguration() throws Exception {
AppShellRegistry registry = Mockito.mock(AppShellRegistry.class);
mocks.setAppShellRegistry(registry);

VaadinRequest request = mocks.createRequest(mocks,
"/foo/?v-r=init&foo");
jsInitHandler.handleRequest(session, request, response);

Mockito.verify(registry)
.modifyPushConfiguration(Mockito.any(PushConfiguration.class));
}

@Test
public void should_respondPushScript_when_annotatedInAppShell()
throws Exception {
AppShellRegistry registry = new AppShellRegistry();
registry.setShell(PushAppShell.class);
mocks.setAppShellRegistry(registry);

VaadinRequest request = mocks.createRequest(mocks, "/foo/?v-r=init&foo");
jsInitHandler.handleRequest(session, request, response);

Assert.assertEquals(200, response.getErrorCode());
Assert.assertEquals("application/json", response.getContentType());
JsonObject json = Json.parse(response.getPayload());

// Using regex, because version depends on the build
Assert.assertTrue(json.getString("pushScript").matches(
"^\\./VAADIN/static/push/vaadinPush\\.js\\?v=[\\w\\.\\-]+$"));
}

private boolean hasNodeTag(TestNodeVisitor visitor, String htmContent, ElementType type) {
Pattern regex = Pattern.compile(htmContent, Pattern.DOTALL);
return visitor
Expand Down

0 comments on commit c761d30

Please sign in to comment.