Skip to content

Commit 37babdf

Browse files
authored
feat: Launch a browser automatically when starting an app in development mode (#873)
Integrates the launch util from https://github.com/Artur-/a-vaadin-helper/blob/master/src/main/java/org/vaadin/artur/helpers/LaunchUtil.java Fixes #786
1 parent 8d1ef9e commit 37babdf

File tree

8 files changed

+310
-0
lines changed

8 files changed

+310
-0
lines changed

vaadin-spring/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@
3333
<artifactId>fusion-endpoint</artifactId>
3434
<scope>provided</scope>
3535
</dependency>
36+
<dependency>
37+
<groupId>com.vaadin</groupId>
38+
<artifactId>vaadin-dev-server</artifactId>
39+
<scope>provided</scope>
40+
</dependency>
3641
<dependency>
3742
<groupId>javax.servlet</groupId>
3843
<artifactId>javax.servlet-api</artifactId>
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
* Copyright 2000-2021 Vaadin Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.vaadin.flow.spring;
17+
18+
import java.io.Serializable;
19+
20+
import javax.servlet.ServletContext;
21+
22+
import com.vaadin.base.devserver.util.BrowserLauncher;
23+
import com.vaadin.flow.server.VaadinContext;
24+
import com.vaadin.flow.server.VaadinServletContext;
25+
import com.vaadin.flow.server.startup.ApplicationConfiguration;
26+
27+
import org.slf4j.Logger;
28+
import org.slf4j.LoggerFactory;
29+
import org.springframework.boot.SpringApplication;
30+
import org.springframework.boot.SpringApplicationRunListener;
31+
import org.springframework.context.ApplicationContext;
32+
import org.springframework.context.ConfigurableApplicationContext;
33+
import org.springframework.web.context.support.GenericWebApplicationContext;
34+
35+
/**
36+
* Utilities for launching a browser when running in development mode.
37+
*/
38+
public class DevModeBrowserLauncher
39+
implements SpringApplicationRunListener, Serializable {
40+
41+
private static final String LAUNCH_TRACKER = "LaunchUtil.hasLaunched";
42+
private static final String LAUNCHED_VALUE = "yes";
43+
44+
public DevModeBrowserLauncher(SpringApplication application,
45+
String[] arguments) {
46+
}
47+
48+
@Override
49+
public void running(ConfigurableApplicationContext context) {
50+
try {
51+
VaadinConfigurationProperties properties = context
52+
.getBean(VaadinConfigurationProperties.class);
53+
54+
if (properties.isLaunchBrowser()) {
55+
launchBrowserInDevelopmentMode(context);
56+
}
57+
} catch (Exception e) {
58+
getLogger().debug("Failed to launch browser", e);
59+
}
60+
}
61+
62+
/**
63+
* Launch the default browser and open the given application base URL if
64+
* running in development mode.
65+
*
66+
* Does nothing if the application is running in production mode.
67+
*
68+
* @param applicationContext
69+
* the application context
70+
*/
71+
private void launchBrowserInDevelopmentMode(ApplicationContext appContext) {
72+
if (isLaunched()) {
73+
// Only launch browser on startup, not on reload
74+
return;
75+
}
76+
if (!(appContext instanceof GenericWebApplicationContext)) {
77+
getLogger().warn(
78+
"Unable to determine production mode for an Spring Boot application context of type "
79+
+ appContext.getClass().getName());
80+
return;
81+
}
82+
GenericWebApplicationContext webAppContext = (GenericWebApplicationContext) appContext;
83+
if (!DevModeBrowserLauncher.isProductionMode(webAppContext)) {
84+
String location = getUrl(webAppContext);
85+
String outputOnFailure = "Application started at " + location;
86+
try {
87+
BrowserLauncher.launch(location, outputOnFailure);
88+
setLaunched();
89+
} catch (Exception | NoClassDefFoundError e) { // NOSONAR
90+
// NoClassDefFoundError happens if vaadin-dev-server is not on
91+
// the classpath
92+
getLogger().info(outputOnFailure);
93+
}
94+
}
95+
}
96+
97+
static String getUrl(GenericWebApplicationContext app) {
98+
String port = app.getEnvironment().getProperty("server.port");
99+
String host = "http://localhost:" + port;
100+
101+
String path = "/";
102+
String contextPath = app.getServletContext().getContextPath();
103+
String vaadinServletMapping = app.getEnvironment()
104+
.getProperty("vaadin.urlMapping");
105+
106+
if (contextPath != null && !contextPath.isEmpty()) {
107+
path = contextPath + "/";
108+
}
109+
110+
if (vaadinServletMapping != null && !vaadinServletMapping.isEmpty()) {
111+
if (vaadinServletMapping.startsWith("/")) {
112+
vaadinServletMapping = vaadinServletMapping.substring(1);
113+
}
114+
if (vaadinServletMapping.endsWith("*")) {
115+
vaadinServletMapping = vaadinServletMapping.substring(0,
116+
vaadinServletMapping.length() - 1);
117+
118+
}
119+
path += vaadinServletMapping;
120+
}
121+
122+
return host + path;
123+
}
124+
125+
private static Logger getLogger() {
126+
return LoggerFactory.getLogger(DevModeBrowserLauncher.class);
127+
}
128+
129+
private static boolean isProductionMode(GenericWebApplicationContext app) {
130+
ServletContext servletContext = app.getServletContext();
131+
VaadinContext context = new VaadinServletContext(servletContext);
132+
ApplicationConfiguration applicationConfiguration = ApplicationConfiguration
133+
.get(context);
134+
return applicationConfiguration.isProductionMode();
135+
}
136+
137+
private static boolean isLaunched() {
138+
return LAUNCHED_VALUE.equals(System.getProperty(LAUNCH_TRACKER));
139+
}
140+
141+
private static void setLaunched() {
142+
System.setProperty(LAUNCH_TRACKER, LAUNCHED_VALUE);
143+
}
144+
145+
}

vaadin-spring/src/main/java/com/vaadin/flow/spring/VaadinConfigurationProperties.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ public class VaadinConfigurationProperties {
6161
*/
6262
private List<String> whitelistedPackages = new ArrayList<>();
6363

64+
/**
65+
* Whether a browser should be launched on startup when in development mode.
66+
*/
67+
private boolean launchBrowser = true;
68+
6469
public static class Pnpm {
6570
private boolean enable;
6671

@@ -155,6 +160,30 @@ public void setLoadOnStartup(boolean loadOnStartup) {
155160
this.loadOnStartup = loadOnStartup;
156161
}
157162

163+
/**
164+
* Returns if a browser should be launched on startup when in development
165+
* mode.
166+
* <p>
167+
*
168+
* @return if a browser should be launched on startup when in development
169+
* mode
170+
*/
171+
public boolean isLaunchBrowser() {
172+
return launchBrowser;
173+
}
174+
175+
/**
176+
* Sets whether a browser should be launched on startup when in development
177+
* mode.
178+
*
179+
* @param launchBrowser
180+
* {@code true} to launch a browser on startup when in
181+
* development mode, {@code false} otherwise
182+
*/
183+
public void setLaunchBrowser(boolean launchBrowser) {
184+
this.launchBrowser = launchBrowser;
185+
}
186+
158187
/**
159188
* Returns if pnpm support is enabled.
160189
*
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.vaadin.flow.spring.SpringBootAutoConfiguration,com.vaadin.flow.spring.SpringSecurityAutoConfiguration,com.vaadin.flow.spring.VaadinScopesConfig
2+
org.springframework.boot.SpringApplicationRunListener=com.vaadin.flow.spring.DevModeBrowserLauncher
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2000-2021 Vaadin Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.vaadin.flow.spring;
17+
18+
import com.vaadin.fusion.FusionControllerConfiguration;
19+
import com.vaadin.fusion.FusionEndpointProperties;
20+
21+
import org.junit.Assert;
22+
import org.junit.jupiter.api.Test;
23+
import org.springframework.beans.factory.annotation.Autowired;
24+
import org.springframework.boot.test.context.SpringBootTest;
25+
import org.springframework.test.context.ContextConfiguration;
26+
import org.springframework.test.context.TestPropertySource;
27+
import org.springframework.web.context.support.GenericWebApplicationContext;
28+
29+
@SpringBootTest(classes = { FusionEndpointProperties.class })
30+
@ContextConfiguration(classes = { FusionControllerConfiguration.class,
31+
SpringBootAutoConfiguration.class,
32+
SpringSecurityAutoConfiguration.class })
33+
@TestPropertySource(properties = { "server.port = 1244" })
34+
public class DevModeBrowserLauncherNoPropertiesTest {
35+
36+
@Autowired
37+
protected GenericWebApplicationContext app;
38+
39+
@Test
40+
public void getUrl_noProperties_givesUrlWithNoContextPathAndUrlMapping() {
41+
String url = DevModeBrowserLauncher.getUrl(app);
42+
Assert.assertEquals("http://localhost:1244/", url);
43+
}
44+
45+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.vaadin.flow.spring;
2+
3+
import org.junit.Assert;
4+
import org.junit.jupiter.api.Test;
5+
import org.springframework.mock.web.MockServletContext;
6+
7+
public class DevModeBrowserLauncherServletMappingTest
8+
extends DevModeBrowserLauncherNoPropertiesTest {
9+
10+
@Test
11+
public void getUrl_withContextPath_givesUrlWithContextPathAndNoUrlMapping() {
12+
MockServletContext ctx = (MockServletContext) app.getServletContext();
13+
ctx.setContextPath("/contextpath");
14+
String url = DevModeBrowserLauncher.getUrl(app);
15+
Assert.assertEquals("http://localhost:1244/contextpath/", url);
16+
}
17+
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2000-2021 Vaadin Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.vaadin.flow.spring;
17+
18+
import org.junit.Assert;
19+
import org.junit.jupiter.api.Test;
20+
import org.springframework.mock.web.MockServletContext;
21+
import org.springframework.test.context.TestPropertySource;
22+
23+
@TestPropertySource(properties = { "vaadin.urlMapping=/ui/*" })
24+
public class DevModeBrowserLauncherVaadinAndServletMappingTest
25+
extends DevModeBrowserLauncherNoPropertiesTest {
26+
27+
@Test
28+
public void getUrl_withContextPathAndUrlMapping_givesUrlWithContextPathAndUrlMapping() {
29+
MockServletContext ctx = (MockServletContext) app.getServletContext();
30+
ctx.setContextPath("/contextpath");
31+
String url = DevModeBrowserLauncher.getUrl(app);
32+
Assert.assertEquals("http://localhost:1244/contextpath/ui/", url);
33+
}
34+
35+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2000-2021 Vaadin Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.vaadin.flow.spring;
17+
18+
import org.junit.Assert;
19+
import org.junit.jupiter.api.Test;
20+
import org.springframework.test.context.TestPropertySource;
21+
22+
@TestPropertySource(properties = { "vaadin.urlMapping=/ui/*" })
23+
public class DevModeBrowserLauncherVaadinMappingTest
24+
extends DevModeBrowserLauncherNoPropertiesTest {
25+
26+
@Test
27+
public void getUrl_withUrlMapping_givesUrlWithUrlMappingAndNoContextPath() {
28+
String url = DevModeBrowserLauncher.getUrl(app);
29+
Assert.assertEquals("http://localhost:1244/ui/", url);
30+
}
31+
32+
}

0 commit comments

Comments
 (0)