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 2, 2020
1 parent 6aeb767 commit aaa0983
Show file tree
Hide file tree
Showing 10 changed files with 216 additions and 27 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,10 +25,12 @@
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;

import com.vaadin.flow.component.PushConfiguration;
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.AppShellConfigurator;
import com.vaadin.flow.component.page.Viewport;
Expand Down Expand Up @@ -260,6 +262,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 Down Expand Up @@ -46,6 +45,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 +113,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 @@ -259,6 +271,10 @@ public String getContentType() {
private TestVaadinServlet servlet;
private TestVaadinServletService service;
private MockDeploymentConfiguration deploymentConfiguration = new MockDeploymentConfiguration();
@Mock
private VaadinServletContext context;
@Mock
private AppShellRegistry appShellRegistry;

public MockServletServiceSessionSetup() throws Exception {
this(true);
Expand Down Expand Up @@ -310,6 +326,13 @@ public MockServletServiceSessionSetup(boolean sessionAvailable)
}

Mockito.when(request.getServletPath()).thenReturn("");

AppShellRegistry.AppShellRegistryWrapper attribute = new AppShellRegistry.AppShellRegistryWrapper(
appShellRegistry);
Mockito.when(context
.getAttribute(AppShellRegistry.AppShellRegistryWrapper.class))
.thenReturn(attribute);
service.setContext(context);
}

public TestVaadinServletService getService() {
Expand Down Expand Up @@ -344,6 +367,10 @@ public MockDeploymentConfiguration getDeploymentConfiguration() {
return deploymentConfiguration;
}

public AppShellRegistry getAppShellRegistry() {
return appShellRegistry;
}

public WebBrowser getBrowser() {
return browser;
}
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,57 @@ 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 {
VaadinRequest request = mocks.createRequest(mocks,
"/foo/?v-r=init&foo");
jsInitHandler.handleRequest(session, request, response);

Mockito.verify(mocks.getAppShellRegistry())
.modifyPushConfiguration(Mockito.any(PushConfiguration.class));
}

@Test
public void should_respondPushScript_when_annotatedInAppShell()
throws Exception {
AppShellRegistry registry = mocks.getAppShellRegistry();
Mockito.doCallRealMethod().when(registry).setShell(PushAppShell.class);
registry.setShell(PushAppShell.class);
Mockito.doCallRealMethod().when(registry).getShell();
Mockito.doCallRealMethod().when(registry)
.modifyPushConfiguration(Mockito.any(PushConfiguration.class));

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 aaa0983

Please sign in to comment.