From e3821fd5036744f240634383d1046971d4b2fe24 Mon Sep 17 00:00:00 2001 From: Denis Date: Tue, 22 Dec 2020 11:41:37 +0300 Subject: [PATCH] refactor!: Extract app config from deployment config (#9642) Removes deprecated `VaadinServletConfiguration` and not needed `VaadinConfigurationException`. Part of #9417 --- .../component/dnd/AbstractDnDUnitTest.java | 17 +- flow-server/pom.xml | 9 + .../function/DeploymentConfiguration.java | 39 -- .../flow/server/AbstractConfiguration.java | 23 + .../server/AbstractPropertyConfiguration.java | 36 +- .../DefaultDeploymentConfiguration.java | 55 ++- .../DeploymentConfigurationFactory.java | 309 ++----------- .../vaadin/flow/server/DevModeHandler.java | 29 +- .../PropertyDeploymentConfiguration.java | 120 ++++- .../server/VaadinConfigurationException.java | 35 -- .../com/vaadin/flow/server/VaadinServlet.java | 19 +- .../server/VaadinServletConfiguration.java | 124 ----- .../startup/AbstractConfigurationFactory.java | 35 +- .../startup/ApplicationConfiguration.java | 9 + ...efaultApplicationConfigurationFactory.java | 57 ++- .../server/startup/DevModeInitializer.java | 60 +-- .../server/startup/LookupInitializer.java | 32 +- .../flow/server/startup/ServletDeployer.java | 46 +- .../startup/VaadinAppShellInitializer.java | 30 +- .../flow/server/CustomUIClassLoaderTest.java | 11 +- .../DefaultDeploymentConfigurationTest.java | 156 ++++++- .../DeploymentConfigurationFactoryTest.java | 362 +++++++-------- .../flow/server/DevModeHandlerTest.java | 55 ++- .../PropertyDeploymentConfigurationTest.java | 435 ++++++++++++++++++ .../VaadinServletConfigurationTest.java | 131 ------ .../IndexHtmlRequestHandlerTest.java | 27 +- .../PushAtmosphereHandlerTest.java | 16 +- .../communication/UidlRequestHandlerTest.java | 58 ++- .../flow/server/frontend/TestUtils.java | 6 +- ...ltApplicationConfigurationFactoryTest.java | 273 +++++++++++ .../startup/DevModeInitializerTest.java | 96 ++-- .../startup/DevModeInitializerTestBase.java | 45 +- .../server/startup/LookupInitializerTest.java | 46 ++ .../server/startup/ServletDeployerTest.java | 18 + .../VaadinAppShellInitializerTest.java | 40 +- ...oductionModeTimingDataViewTestServlet.java | 7 +- .../ProductionModeViewTestServlet.java | 7 +- .../flow/uitest/servlet/ViewTestServlet.java | 6 +- .../flow/memoryleaks/ui/MemoryLeakUI.java | 10 +- .../ItApplicationConfigurationFactory.java | 39 ++ .../java/com/vaadin/flow/osgi/Activator.java | 26 +- .../servlet/ApplicationRunnerServlet.java | 6 + .../flow/test/scalability/HelloWorldUI.java | 9 +- .../connect/VaadinConnectController.java | 95 ++-- .../connect/VaadinConnectControllerTest.java | 291 +++++++----- .../rest/EndpointWithRestControllerTest.java | 72 +-- .../BaseTypeConversionTest.java | 28 +- .../DevModeInitializerEndpointTest.java | 205 ++++----- 48 files changed, 2189 insertions(+), 1471 deletions(-) delete mode 100644 flow-server/src/main/java/com/vaadin/flow/server/VaadinConfigurationException.java delete mode 100644 flow-server/src/main/java/com/vaadin/flow/server/VaadinServletConfiguration.java create mode 100644 flow-server/src/test/java/com/vaadin/flow/server/PropertyDeploymentConfigurationTest.java delete mode 100644 flow-server/src/test/java/com/vaadin/flow/server/VaadinServletConfigurationTest.java create mode 100644 flow-server/src/test/java/com/vaadin/flow/server/startup/DefaultApplicationConfigurationFactoryTest.java create mode 100644 flow-tests/test-root-context/src/main/java/com/vaadin/flow/ItApplicationConfigurationFactory.java diff --git a/flow-dnd/src/test/java/com/vaadin/flow/component/dnd/AbstractDnDUnitTest.java b/flow-dnd/src/test/java/com/vaadin/flow/component/dnd/AbstractDnDUnitTest.java index 96ecb2402da..3cfa76a152f 100644 --- a/flow-dnd/src/test/java/com/vaadin/flow/component/dnd/AbstractDnDUnitTest.java +++ b/flow-dnd/src/test/java/com/vaadin/flow/component/dnd/AbstractDnDUnitTest.java @@ -17,9 +17,15 @@ package com.vaadin.flow.component.dnd; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Properties; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + import com.vaadin.flow.component.Component; import com.vaadin.flow.component.dnd.internal.DnDUtilHelper; import com.vaadin.flow.component.internal.DependencyList; @@ -30,11 +36,8 @@ import com.vaadin.flow.server.VaadinService; import com.vaadin.flow.server.VaadinServlet; import com.vaadin.flow.server.VaadinSession; +import com.vaadin.flow.server.startup.ApplicationConfiguration; import com.vaadin.flow.shared.ui.Dependency; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; public abstract class AbstractDnDUnitTest { @@ -43,8 +46,12 @@ public abstract class AbstractDnDUnitTest { @Before public void setup() { + ApplicationConfiguration appConfig = Mockito + .mock(ApplicationConfiguration.class); + Mockito.when(appConfig.getPropertyNames()) + .thenReturn(Collections.emptyEnumeration()); DefaultDeploymentConfiguration configuration = new DefaultDeploymentConfiguration( - VaadinServlet.class, new Properties()); + appConfig, VaadinServlet.class, new Properties()); VaadinService service = Mockito.mock(VaadinService.class); Mockito.when(service.resolveResource(Mockito.anyString())) diff --git a/flow-server/pom.xml b/flow-server/pom.xml index 1b2b7d73819..18465a06ba8 100644 --- a/flow-server/pom.xml +++ b/flow-server/pom.xml @@ -39,6 +39,15 @@ ${project.version} test + + + + + org.osgi + osgi.cmpn + ${osgi.compendium.version} + provided + diff --git a/flow-server/src/main/java/com/vaadin/flow/function/DeploymentConfiguration.java b/flow-server/src/main/java/com/vaadin/flow/function/DeploymentConfiguration.java index 3f69e2b6ce9..2a6455ac7ff 100644 --- a/flow-server/src/main/java/com/vaadin/flow/function/DeploymentConfiguration.java +++ b/flow-server/src/main/java/com/vaadin/flow/function/DeploymentConfiguration.java @@ -49,13 +49,6 @@ public interface DeploymentConfiguration */ boolean isRequestTiming(); - /** - * Returns whether cross-site request forgery protection is enabled. - * - * @return true if XSRF protection is enabled, false otherwise. - */ - boolean isXsrfProtectionEnabled(); - /** * Returns whether sync id checking is enabled. The sync id is used to * gracefully handle situations when the client sends a message to a @@ -166,26 +159,6 @@ public interface DeploymentConfiguration T getApplicationOrSystemProperty(String propertyName, T defaultValue, Function converter); - /** - * A shorthand of - * {@link DeploymentConfiguration#getApplicationOrSystemProperty(String, Object, Function)} - * for {@link String} type. - * - * @param propertyName - * The simple of the property, in some contexts, lookup might be - * performed using variations of the provided name. - * @param defaultValue - * the default value that should be used if no value has been - * defined - * @return the property value, or the passed default value if no property - * value is found - */ - @Override - default String getStringProperty(String propertyName, String defaultValue) { - return getApplicationOrSystemProperty(propertyName, defaultValue, - Function.identity()); - } - /** * A shorthand of * {@link DeploymentConfiguration#getApplicationOrSystemProperty(String, Object, Function)} @@ -296,18 +269,6 @@ default List getPolyfills() { .collect(Collectors.toList()); } - /** - * Get if the dev server should be reused on each reload. True by default, - * set it to false in tests so as dev server is not kept as a daemon after - * the test. - * - * @return true if dev server should be reused - */ - default boolean reuseDevServer() { - return getBooleanProperty( - InitParameters.SERVLET_PARAMETER_REUSE_DEV_SERVER, true); - } - /** * Get if the stats.json file should be retrieved from an external service * or through the classpath. diff --git a/flow-server/src/main/java/com/vaadin/flow/server/AbstractConfiguration.java b/flow-server/src/main/java/com/vaadin/flow/server/AbstractConfiguration.java index 1e18943f691..3caa71d376f 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/AbstractConfiguration.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/AbstractConfiguration.java @@ -17,6 +17,7 @@ import java.io.Serializable; +import static com.vaadin.flow.server.InitParameters.SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION; import static com.vaadin.flow.server.InitParameters.SERVLET_PARAMETER_USE_V14_BOOTSTRAP; /** @@ -45,6 +46,18 @@ default boolean enableDevServer() { InitParameters.SERVLET_PARAMETER_ENABLE_DEV_SERVER, true); } + /** + * Get if the dev server should be reused on each reload. True by default, + * set it to false in tests so as dev server is not kept as a daemon after + * the test. + * + * @return true if dev server should be reused + */ + default boolean reuseDevServer() { + return getBooleanProperty( + InitParameters.SERVLET_PARAMETER_REUSE_DEV_SERVER, true); + } + /** * Returns whether Vaadin is running in useDeprecatedV14Bootstrapping. * @@ -94,4 +107,14 @@ default boolean isPnpmEnabled() { Boolean.valueOf(Constants.ENABLE_PNPM_DEFAULT_STRING)); } + /** + * Returns whether cross-site request forgery protection is enabled. + * + * @return true if XSRF protection is enabled, false otherwise. + */ + default boolean isXsrfProtectionEnabled() { + return !getBooleanProperty(SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION, + false); + } + } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/AbstractPropertyConfiguration.java b/flow-server/src/main/java/com/vaadin/flow/server/AbstractPropertyConfiguration.java index 0914f4e30c9..f5f9265349c 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/AbstractPropertyConfiguration.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/AbstractPropertyConfiguration.java @@ -16,6 +16,7 @@ package com.vaadin.flow.server; import java.util.Collections; +import java.util.Locale; import java.util.Map; import java.util.function.Function; @@ -82,17 +83,7 @@ public boolean getBooleanProperty(String name, boolean defaultValue) { * @return String value or null if not found */ public String getApplicationProperty(String parameterName) { - - String val = properties.get(parameterName); - if (val != null) { - return val; - } - - // Try lower case application properties for backward compatibility - // with 3.0.2 and earlier - val = properties.get(parameterName.toLowerCase()); - - return val; + return getApplicationProperty(getProperties()::get, parameterName); } /** @@ -150,4 +141,27 @@ protected String getSystemProperty(String parameterName) { return System.getProperty(VAADIN_PREFIX + parameterName); } + /** + * Gets application property value using the {@code valueProvider}. + * + * @param valueProvider + * a value provider for the property + * @param propertyName + * the name or the parameter. + * @return String value or null if not found + */ + protected String getApplicationProperty( + Function valueProvider, String propertyName) { + String val = valueProvider.apply(propertyName); + if (val != null) { + return val; + } + + // Try lower case application properties for backward compatibility with + // 3.0.2 and earlier + val = valueProvider.apply(propertyName.toLowerCase(Locale.ENGLISH)); + + return val; + } + } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/DefaultDeploymentConfiguration.java b/flow-server/src/main/java/com/vaadin/flow/server/DefaultDeploymentConfiguration.java index e0227c0a10b..29123eb77d0 100755 --- a/flow-server/src/main/java/com/vaadin/flow/server/DefaultDeploymentConfiguration.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/DefaultDeploymentConfiguration.java @@ -26,6 +26,7 @@ import org.slf4j.LoggerFactory; import com.vaadin.flow.function.DeploymentConfiguration; +import com.vaadin.flow.server.startup.ApplicationConfiguration; import com.vaadin.flow.shared.communication.PushMode; import static com.vaadin.flow.server.frontend.FrontendUtils.DEFAULT_FRONTEND_DIR; @@ -56,7 +57,8 @@ public class DefaultDeploymentConfiguration + "Client-side views written in TypeScript are not supported. Vaadin 15+ enables client-side and server-side views.\n" + "See https://vaadin.com/docs/v15/flow/typescript/starting-the-app.html for more information."; - // not a warning anymore, but keeping variable name to avoid breaking anything + // not a warning anymore, but keeping variable name to avoid breaking + // anything public static final String WARNING_V15_BOOTSTRAP = "Using Vaadin 15+ bootstrap mode.%n %s%n %s"; private static final String DEPLOYMENT_WARNINGS = "Following issues were discovered with deployment configuration:"; @@ -120,7 +122,9 @@ public class DefaultDeploymentConfiguration /** * Create a new deployment configuration instance. - * + * + * @param parentConfig + * a parent application configuration * @param systemPropertyBaseClass * the class that should be used as a basis when reading system * properties @@ -128,9 +132,9 @@ public class DefaultDeploymentConfiguration * the init parameters that should make up the foundation for * this configuration */ - public DefaultDeploymentConfiguration(Class systemPropertyBaseClass, - Properties initParameters) { - super(systemPropertyBaseClass, initParameters); + public DefaultDeploymentConfiguration(ApplicationConfiguration parentConfig, + Class systemPropertyBaseClass, Properties initParameters) { + super(parentConfig, systemPropertyBaseClass, initParameters); boolean log = logging.getAndSet(false); @@ -287,8 +291,12 @@ public String getPushURL() { * Log a warning if Vaadin is not running in production mode. */ private void checkProductionMode(boolean log) { - productionMode = getBooleanProperty( - InitParameters.SERVLET_PARAMETER_PRODUCTION_MODE, false); + if (isOwnProperty(InitParameters.SERVLET_PARAMETER_PRODUCTION_MODE)) { + productionMode = getBooleanProperty( + InitParameters.SERVLET_PARAMETER_PRODUCTION_MODE, false); + } else { + productionMode = getParentConfiguration().isProductionMode(); + } if (log) { if (productionMode) { info.add("Vaadin is running in production mode."); @@ -306,8 +314,13 @@ private void checkProductionMode(boolean log) { * Log a message about the bootstrapping being used. */ private void checkV14Bootsrapping(boolean log) { - useDeprecatedV14Bootstrapping = getBooleanProperty( - InitParameters.SERVLET_PARAMETER_USE_V14_BOOTSTRAP, false); + if (isOwnProperty(InitParameters.SERVLET_PARAMETER_USE_V14_BOOTSTRAP)) { + useDeprecatedV14Bootstrapping = getBooleanProperty( + InitParameters.SERVLET_PARAMETER_USE_V14_BOOTSTRAP, false); + } else { + useDeprecatedV14Bootstrapping = getParentConfiguration() + .useV14Bootstrap(); + } if (log) { if (useDeprecatedV14Bootstrapping) { warnings.add(WARNING_V14_BOOTSTRAP); @@ -362,15 +375,23 @@ private String getIndexHTMLMessage(String frontendDir) { */ private void checkRequestTiming() { requestTiming = getBooleanProperty( - InitParameters.SERVLET_PARAMETER_REQUEST_TIMING, !productionMode); + InitParameters.SERVLET_PARAMETER_REQUEST_TIMING, + !productionMode); } /** * Log a warning if cross-site request forgery protection is disabled. */ private void checkXsrfProtection(boolean loggWarning) { - xsrfProtectionEnabled = !getBooleanProperty( - InitParameters.SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION, false); + if (isOwnProperty( + InitParameters.SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION)) { + xsrfProtectionEnabled = !getBooleanProperty( + InitParameters.SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION, + false); + } else { + xsrfProtectionEnabled = getParentConfiguration() + .isXsrfProtectionEnabled(); + } if (!xsrfProtectionEnabled && loggWarning) { warnings.add(WARNING_XSRF_PROTECTION_DISABLED); } @@ -422,9 +443,9 @@ private void checkCloseIdleSessions() { private void checkPushMode() { try { pushMode = getApplicationOrSystemProperty( - InitParameters.SERVLET_PARAMETER_PUSH_MODE, PushMode.DISABLED, - stringMode -> Enum.valueOf(PushMode.class, - stringMode.toUpperCase())); + InitParameters.SERVLET_PARAMETER_PUSH_MODE, + PushMode.DISABLED, stringMode -> Enum + .valueOf(PushMode.class, stringMode.toUpperCase())); } catch (IllegalArgumentException e) { warnings.add(WARNING_PUSH_MODE_NOT_RECOGNIZED); pushMode = PushMode.DISABLED; @@ -432,7 +453,8 @@ private void checkPushMode() { } private void checkPushURL() { - pushURL = getStringProperty(InitParameters.SERVLET_PARAMETER_PUSH_URL, ""); + pushURL = getStringProperty(InitParameters.SERVLET_PARAMETER_PUSH_URL, + ""); } private void checkSyncIdCheck() { @@ -446,4 +468,5 @@ private void checkSendUrlsAsParameters() { InitParameters.SERVLET_PARAMETER_SEND_URLS_AS_PARAMETERS, DEFAULT_SEND_URLS_AS_PARAMETERS); } + } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/DeploymentConfigurationFactory.java b/flow-server/src/main/java/com/vaadin/flow/server/DeploymentConfigurationFactory.java index 57f9beff739..1b9395961a2 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/DeploymentConfigurationFactory.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/DeploymentConfigurationFactory.java @@ -16,49 +16,29 @@ package com.vaadin.flow.server; -import java.io.File; -import java.io.IOException; import java.io.Serializable; -import java.io.UncheckedIOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.URL; -import java.nio.charset.StandardCharsets; import java.util.Enumeration; -import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.Optional; import java.util.Properties; -import org.apache.commons.io.FileUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.vaadin.flow.component.UI; -import com.vaadin.flow.di.Lookup; -import com.vaadin.flow.di.ResourceProvider; import com.vaadin.flow.function.DeploymentConfiguration; -import com.vaadin.flow.internal.AnnotationReader; import com.vaadin.flow.server.frontend.FallbackChunk; import com.vaadin.flow.server.frontend.FrontendUtils; import com.vaadin.flow.server.startup.AbstractConfigurationFactory; +import com.vaadin.flow.server.startup.ApplicationConfiguration; import elemental.json.JsonObject; import elemental.json.impl.JsonUtil; -import static com.vaadin.flow.server.Constants.VAADIN_SERVLET_RESOURCES; -import static com.vaadin.flow.server.InitParameters.SERVLET_PARAMETER_PRODUCTION_MODE; -import static com.vaadin.flow.server.frontend.FrontendUtils.PARAM_TOKEN_FILE; -import static com.vaadin.flow.server.frontend.FrontendUtils.TOKEN_FILE; - /** * Creates {@link DeploymentConfiguration} filled with all parameters specified * by the framework users. * * @since 1.2 */ -public final class DeploymentConfigurationFactory implements Serializable { +public class DeploymentConfigurationFactory extends AbstractConfigurationFactory + implements Serializable { public static final Object FALLBACK_CHUNK = new Serializable() { }; @@ -67,14 +47,6 @@ public final class DeploymentConfigurationFactory implements Serializable { + "the project/working directory. Ensure 'webpack.config.js' is present or trigger creation of " + "'flow-build-info.json' via running 'prepare-frontend' Maven goal."; - public static final String DEV_FOLDER_MISSING_MESSAGE = "Running project in development mode with no access to folder '%s'.%n" - + "Build project in production mode instead, see https://vaadin.com/docs/v15/flow/production/tutorial-production-mode-basic.html"; - private static final Logger logger = LoggerFactory - .getLogger(DeploymentConfigurationFactory.class); - - private DeploymentConfigurationFactory() { - } - /** * Creates a {@link DeploymentConfiguration} instance that is filled with * all parameters, specified for the current app. @@ -84,13 +56,12 @@ private DeploymentConfigurationFactory() { * @param vaadinConfig * the config to get the rest of the properties from * @return {@link DeploymentConfiguration} instance - * @throws VaadinConfigurationException - * thrown if property construction fails */ - public static DeploymentConfiguration createDeploymentConfiguration( - Class systemPropertyBaseClass, VaadinConfig vaadinConfig) - throws VaadinConfigurationException { - return new DefaultDeploymentConfiguration(systemPropertyBaseClass, + public DeploymentConfiguration createDeploymentConfiguration( + Class systemPropertyBaseClass, VaadinConfig vaadinConfig) { + return new DefaultDeploymentConfiguration( + ApplicationConfiguration.get(vaadinConfig.getVaadinContext()), + systemPropertyBaseClass, createInitParameters(systemPropertyBaseClass, vaadinConfig)); } @@ -104,13 +75,12 @@ public static DeploymentConfiguration createDeploymentConfiguration( * @param vaadinConfig * the config to get the rest of the properties from * @return {@link DeploymentConfiguration} instance - * @throws VaadinConfigurationException - * thrown if property construction fails */ - public static DeploymentConfiguration createPropertyDeploymentConfiguration( - Class systemPropertyBaseClass, VaadinConfig vaadinConfig) - throws VaadinConfigurationException { - return new PropertyDeploymentConfiguration(systemPropertyBaseClass, + public DeploymentConfiguration createPropertyDeploymentConfiguration( + Class systemPropertyBaseClass, VaadinConfig vaadinConfig) { + return new PropertyDeploymentConfiguration( + ApplicationConfiguration.get(vaadinConfig.getVaadinContext()), + systemPropertyBaseClass, createInitParameters(systemPropertyBaseClass, vaadinConfig)); } @@ -123,24 +93,11 @@ public static DeploymentConfiguration createPropertyDeploymentConfiguration( * @param vaadinConfig * the config to get the rest of the properties from * @return {@link Properties} instance - * @throws VaadinConfigurationException - * thrown if property construction fails */ - protected static Properties createInitParameters( - Class systemPropertyBaseClass, VaadinConfig vaadinConfig) - throws VaadinConfigurationException { + protected Properties createInitParameters(Class systemPropertyBaseClass, + VaadinConfig vaadinConfig) { Properties initParameters = new Properties(); readUiFromEnclosingClass(systemPropertyBaseClass, initParameters); - readConfigurationAnnotation(systemPropertyBaseClass, initParameters); - - // TODO : will be removed in futher commits - // Read default parameters from server.xml - final VaadinContext context = vaadinConfig.getVaadinContext(); - for (final Enumeration e = context.getContextParameterNames(); e - .hasMoreElements();) { - final String name = e.nextElement(); - initParameters.setProperty(name, context.getContextParameter(name)); - } // Override with application config from web.xml for (final Enumeration e = vaadinConfig @@ -150,185 +107,32 @@ protected static Properties createInitParameters( vaadinConfig.getConfigParameter(name)); } - readBuildInfo(systemPropertyBaseClass, initParameters, - vaadinConfig.getVaadinContext()); + readBuildInfo(initParameters, vaadinConfig.getVaadinContext()); return initParameters; } - private static void readBuildInfo(Class systemPropertyBaseClass, - Properties initParameters, VaadinContext context) { - String json = getTokenFileContents(systemPropertyBaseClass, - initParameters, context); + private void readBuildInfo(Properties initParameters, + VaadinContext context) { + String json = getTokenFileContent(initParameters::getProperty); + + FallbackChunk fallbackChunk = null; // Read the json and set the appropriate system properties if not // already set. if (json != null) { JsonObject buildInfo = JsonUtil.parse(json); - // TODO : will be rewritten properly without extra instantiation - Map params = new AbstractConfigurationFactory() - .getConfigParametersUsingTokenData(buildInfo); - initParameters.putAll(params); - - FallbackChunk fallbackChunk = FrontendUtils - .readFallbackChunk(buildInfo); - if (fallbackChunk != null) { - initParameters.put(FALLBACK_CHUNK, fallbackChunk); - } - } - } - - private static String getTokenFileContents(Class systemPropertyBaseClass, - Properties initParameters, VaadinContext context) { - String json = null; - try { - json = getResourceFromFile(initParameters); - if (json == null) { - json = getTokenFileFromClassloader(context); - } - } catch (IOException e) { - throw new UncheckedIOException(e); - } - return json; - } - - private static String getResourceFromFile(Properties initParameters) - throws IOException { - String json = null; - // token file location passed via init parameter property - String tokenLocation = initParameters.getProperty(PARAM_TOKEN_FILE); - if (tokenLocation != null) { - File tokenFile = new File(tokenLocation); - if (tokenFile != null && tokenFile.canRead()) { - json = FileUtils.readFileToString(tokenFile, - StandardCharsets.UTF_8); - } - } - return json; - } - - /** - * Gets token file from the classpath using the provided {@code context}. - *

- * The {@code contextClass} may be a class which is defined in the Web - * Application module/bundle and in this case it may be used to get Web - * Application resources. Also a {@link VaadinContext} {@code context} - * instance may be used to get a context of the Web Application (since the - * {@code contextClass} may be a class not from Web Application module). In - * WAR case it doesn't matter which class is used to get the resources (Web - * Application classes or e.g. "flow-server" classes) since they are loaded - * by the same {@link ClassLoader}. But in OSGi "flow-server" module classes - * can't be used to get Web Application resources since they are in - * different bundles. - * - * @param context - * a VaadinContext which may provide information how to get token - * file for the web application - * @return the token file content - * @throws IOException - * if I/O fails during access to the token file - */ - private static String getTokenFileFromClassloader(VaadinContext context) - throws IOException { - String tokenResource = VAADIN_SERVLET_RESOURCES + TOKEN_FILE; - - Lookup lookup = context.getAttribute(Lookup.class); - ResourceProvider resourceProvider = lookup - .lookup(ResourceProvider.class); - - List resources = resourceProvider - .getApplicationResources(tokenResource); - - // Accept resource that doesn't contain - // 'jar!/META-INF/Vaadin/config/flow-build-info.json' - URL resource = resources.stream() - .filter(url -> !url.getPath().endsWith("jar!/" + tokenResource)) - .findFirst().orElse(null); - if (resource == null && !resources.isEmpty()) { - // For no non jar build info, in production mode check for - // webpack.generated.json if it's in a jar then accept - // single jar flow-build-info. - return getPossibleJarResource(context, resources); - } - return resource == null ? null - : FrontendUtils.streamToString(resource.openStream()); - - } - - /** - * Check if the webpack.generated.js resources is inside 2 jars - * (flow-server.jar and application.jar) if this is the case then we can - * accept a build info file from inside jar with a single jar in the path. - *

- * Else we will accept any flow-build-info and log a warning that it may not - * be the correct file, but it's the best we could find. - */ - private static String getPossibleJarResource(VaadinContext context, - List resources) throws IOException { - Objects.requireNonNull(resources); - - Lookup lookup = context.getAttribute(Lookup.class); - ResourceProvider resourceProvider = lookup - .lookup(ResourceProvider.class); - - assert !resources - .isEmpty() : "Possible jar resource requires resources to be available."; - - URL webpackGenerated = resourceProvider - .getApplicationResource(FrontendUtils.WEBPACK_GENERATED); + Map properties = getConfigParametersUsingTokenData( + buildInfo); + initParameters.putAll(properties); - // If jar!/ exists 2 times for webpack.generated.json then we are - // running from a jar - if (webpackGenerated != null - && countInstances(webpackGenerated.getPath(), "jar!/") >= 2) { - for (URL resource : resources) { - // As we now know that we are running from a jar we can accept a - // build info with a single jar in the path - if (countInstances(resource.getPath(), "jar!/") == 1) { - return FrontendUtils.streamToString(resource.openStream()); - } - } + fallbackChunk = FrontendUtils.readFallbackChunk(buildInfo); } - URL firstResource = resources.get(0); - if (resources.size() > 1) { - String warningMessage = String.format( - "Unable to fully determine correct flow-build-info.%n" - + "Accepting file '%s' first match of '%s' possible.%n" - + "Please verify flow-build-info file content.", - firstResource.getPath(), resources.size()); - logger.warn(warningMessage); - } else { - String debugMessage = String.format( - "Unable to fully determine correct flow-build-info.%n" - + "Accepting file '%s'", - firstResource.getPath()); - logger.debug(debugMessage); + if (fallbackChunk == null) { + fallbackChunk = ApplicationConfiguration.get(context) + .getFallbackChunk(); } - return FrontendUtils.streamToString(firstResource.openStream()); - } - - private static int countInstances(String input, String value) { - return input.split(value, -1).length - 1; - } - - /** - * Verify that given folder actually exists on the system if we are not in - * production mode. - *

- * If folder doesn't exist throw IllegalStateException saying that this - * should probably be a production mode build. - * - * @param initParameters - * deployment init parameters - * @param folder - * folder to check exists - */ - private static void verifyFolderExists(Properties initParameters, - String folder) { - Boolean productionMode = Boolean.parseBoolean(initParameters - .getProperty(SERVLET_PARAMETER_PRODUCTION_MODE, "false")); - if (!productionMode && !new File(folder).exists()) { - String message = String.format(DEV_FOLDER_MISSING_MESSAGE, folder); - throw new IllegalStateException(message); + if (fallbackChunk != null) { + initParameters.put(FALLBACK_CHUNK, fallbackChunk); } } @@ -343,57 +147,4 @@ private static void readUiFromEnclosingClass( } } - /** - * Read the VaadinServletConfiguration annotation for initialization name - * value pairs and add them to the initial properties object. - * - * @param systemPropertyBaseClass - * base class for constructing the configuration - * @param initParameters - * current initParameters object - * @throws VaadinConfigurationException - * exception thrown for failure in invoking method on - * configuration annotation - */ - private static void readConfigurationAnnotation( - Class systemPropertyBaseClass, Properties initParameters) - throws VaadinConfigurationException { - Optional optionalConfigAnnotation = AnnotationReader - .getAnnotationFor(systemPropertyBaseClass, - VaadinServletConfiguration.class); - - if (!optionalConfigAnnotation.isPresent()) { - return; - } - - VaadinServletConfiguration configuration = optionalConfigAnnotation - .get(); - Method[] methods = VaadinServletConfiguration.class - .getDeclaredMethods(); - for (Method method : methods) { - VaadinServletConfiguration.InitParameterName name = method - .getAnnotation( - VaadinServletConfiguration.InitParameterName.class); - assert name != null : "All methods declared in VaadinServletConfiguration should have a @InitParameterName annotation"; - - try { - Object value = method.invoke(configuration); - - String stringValue; - if (value instanceof Class) { - stringValue = ((Class) value).getName(); - } else { - stringValue = value.toString(); - } - - initParameters.setProperty(name.value(), stringValue); - } catch (IllegalAccessException | InvocationTargetException e) { - // This should never happen - throw new VaadinConfigurationException( - "Could not read @VaadinServletConfiguration value " - + method.getName(), - e); - } - } - } } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/DevModeHandler.java b/flow-server/src/main/java/com/vaadin/flow/server/DevModeHandler.java index a871114bbea..e0b96f2305c 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/DevModeHandler.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/DevModeHandler.java @@ -18,6 +18,7 @@ import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; + import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -46,12 +47,12 @@ import org.slf4j.LoggerFactory; import com.vaadin.flow.di.Lookup; -import com.vaadin.flow.function.DeploymentConfiguration; import com.vaadin.flow.internal.BrowserLiveReload; import com.vaadin.flow.internal.Pair; import com.vaadin.flow.server.communication.StreamRequestHandler; import com.vaadin.flow.server.frontend.FrontendTools; import com.vaadin.flow.server.frontend.FrontendUtils; +import com.vaadin.flow.server.startup.ApplicationConfiguration; import static com.vaadin.flow.server.Constants.VAADIN_MAPPING; import static com.vaadin.flow.server.InitParameters.SERVLET_PARAMETER_DEVMODE_WEBPACK_ERROR_PATTERN; @@ -142,8 +143,8 @@ private DevModeHandler(Lookup lookup, int runningPort, File npmFolder, this.npmFolder = npmFolder; port = runningPort; - DeploymentConfiguration config = lookup - .lookup(DeploymentConfiguration.class); + ApplicationConfiguration config = lookup + .lookup(ApplicationConfiguration.class); reuseDevServer = config.reuseDevServer(); devServerPortFile = getDevServerPortFile(npmFolder); @@ -200,8 +201,8 @@ public static DevModeHandler start(Lookup lookup, File npmFolder, */ public static DevModeHandler start(int runningPort, Lookup lookup, File npmFolder, CompletableFuture waitFor) { - DeploymentConfiguration configuration = lookup - .lookup(DeploymentConfiguration.class); + ApplicationConfiguration configuration = lookup + .lookup(ApplicationConfiguration.class); if (configuration.isProductionMode() || !configuration.enableDevServer()) { return null; @@ -285,9 +286,11 @@ private static DevModeHandler createInstance(int runningPort, Lookup lookup, */ public boolean isDevModeRequest(HttpServletRequest request) { String pathInfo = request.getPathInfo(); - return pathInfo != null && (pathInfo.startsWith("/" + VAADIN_MAPPING) - || APP_THEME_PATTERN.matcher(pathInfo).find()) && !pathInfo - .startsWith("/" + StreamRequestHandler.DYN_RES_PREFIX); + return pathInfo != null + && (pathInfo.startsWith("/" + VAADIN_MAPPING) + || APP_THEME_PATTERN.matcher(pathInfo).find()) + && !pathInfo + .startsWith("/" + StreamRequestHandler.DYN_RES_PREFIX); } /** @@ -326,7 +329,7 @@ public boolean serveDevModeRequest(HttpServletRequest request, } // Redirect theme source request - if(APP_THEME_PATTERN.matcher(requestFilename).find()) { + if (APP_THEME_PATTERN.matcher(requestFilename).find()) { requestFilename = "/VAADIN/static" + requestFilename; } @@ -530,7 +533,7 @@ public void removeRunningDevServerPort() { FileUtils.deleteQuietly(devServerPortFile); } - private void runOnFutureComplete(DeploymentConfiguration config) { + private void runOnFutureComplete(ApplicationConfiguration config) { try { doStartDevModeServer(config); } catch (ExecutionFailedException exception) { @@ -564,7 +567,7 @@ private boolean checkPort() { } - private void doStartDevModeServer(DeploymentConfiguration config) + private void doStartDevModeServer(ApplicationConfiguration config) throws ExecutionFailedException { // If port is defined, means that webpack is already running if (port > 0) { @@ -611,7 +614,7 @@ private void doStartDevModeServer(DeploymentConfiguration config) } } - private boolean doStartWebpack(DeploymentConfiguration config, + private boolean doStartWebpack(ApplicationConfiguration config, Pair webPackFiles, long start) { ProcessBuilder processBuilder = new ProcessBuilder() .directory(npmFolder); @@ -700,7 +703,7 @@ private void reuseExistingPort(int port) { watchDog.set(null); } - private List makeCommands(DeploymentConfiguration config, + private List makeCommands(ApplicationConfiguration config, File webpack, File webpackConfig, String nodeExec) { List command = new ArrayList<>(); command.add(nodeExec); diff --git a/flow-server/src/main/java/com/vaadin/flow/server/PropertyDeploymentConfiguration.java b/flow-server/src/main/java/com/vaadin/flow/server/PropertyDeploymentConfiguration.java index e2d95978044..6df74f64a07 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/PropertyDeploymentConfiguration.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/PropertyDeploymentConfiguration.java @@ -15,6 +15,7 @@ */ package com.vaadin.flow.server; +import java.util.Enumeration; import java.util.HashMap; import java.util.Locale; import java.util.Map; @@ -22,6 +23,7 @@ import java.util.Properties; import com.vaadin.flow.function.DeploymentConfiguration; +import com.vaadin.flow.server.startup.ApplicationConfiguration; import com.vaadin.flow.shared.communication.PushMode; import static com.vaadin.flow.server.InitParameters.SERVLET_PARAMETER_CLOSE_IDLE_SESSIONS; @@ -43,11 +45,18 @@ public class PropertyDeploymentConfiguration private final Class systemPropertyBaseClass; - private final Properties initialParameters; + /** + * Contains properties from both: parent config and provided properties. + */ + private final Properties allProperties; + + private final ApplicationConfiguration parentConfig; /** * Create a new property deployment configuration instance. - * + * + * @param parentConfig + * a parent application configuration * @param systemPropertyBaseClass * the class that should be used as a basis when reading system * properties @@ -55,10 +64,12 @@ public class PropertyDeploymentConfiguration * the init parameters that should make up the foundation for * this configuration */ - public PropertyDeploymentConfiguration(Class systemPropertyBaseClass, - Properties initParameters) { + public PropertyDeploymentConfiguration( + ApplicationConfiguration parentConfig, + Class systemPropertyBaseClass, Properties initParameters) { super(filterStringProperties(initParameters)); - initialParameters = initParameters; + this.parentConfig = parentConfig; + allProperties = mergeProperties(parentConfig, initParameters); this.systemPropertyBaseClass = systemPropertyBaseClass; } @@ -114,22 +125,54 @@ protected String getSystemProperty(String parameterName) { */ @Override public String getApplicationProperty(String parameterName) { + String val = getApplicationProperty(getProperties()::get, + parameterName); + if (val == null) { + val = getApplicationProperty( + prop -> parentConfig.getStringProperty(prop, null), + parameterName); + } + return val; + } - String val = getProperties().get(parameterName); - if (val != null) { - return val; + @Override + public boolean isProductionMode() { + if (isOwnProperty(SERVLET_PARAMETER_PRODUCTION_MODE)) { + return getBooleanProperty(SERVLET_PARAMETER_PRODUCTION_MODE, false); } + return parentConfig.isProductionMode(); + } - // Try lower case application properties for backward compatibility with - // 3.0.2 and earlier - val = getProperties().get(parameterName.toLowerCase()); + @Override + public boolean enableDevServer() { + if (isOwnProperty(InitParameters.SERVLET_PARAMETER_ENABLE_DEV_SERVER)) { + return super.enableDevServer(); + } + return parentConfig.enableDevServer(); + } - return val; + @Override + public boolean useV14Bootstrap() { + if (isOwnProperty(InitParameters.SERVLET_PARAMETER_USE_V14_BOOTSTRAP)) { + return super.useV14Bootstrap(); + } + return parentConfig.useV14Bootstrap(); } @Override - public boolean isProductionMode() { - return getBooleanProperty(SERVLET_PARAMETER_PRODUCTION_MODE, false); + public boolean isPnpmEnabled() { + if (isOwnProperty(InitParameters.SERVLET_PARAMETER_ENABLE_PNPM)) { + return super.isPnpmEnabled(); + } + return parentConfig.isPnpmEnabled(); + } + + @Override + public boolean reuseDevServer() { + if (isOwnProperty(InitParameters.SERVLET_PARAMETER_REUSE_DEV_SERVER)) { + return super.reuseDevServer(); + } + return parentConfig.reuseDevServer(); } @Override @@ -140,8 +183,10 @@ public boolean isRequestTiming() { @Override public boolean isXsrfProtectionEnabled() { - return !getBooleanProperty(SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION, - false); + if (isOwnProperty(SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION)) { + return super.isXsrfProtectionEnabled(); + } + return parentConfig.isXsrfProtectionEnabled(); } @Override @@ -187,7 +232,7 @@ public String getPushURL() { @Override public Properties getInitParameters() { - return initialParameters; + return allProperties; } /** @@ -205,6 +250,46 @@ && getBooleanProperty( && enableDevServer(); // gizmo excluded from prod bundle } + /** + * Checks whether the given {@code property} is the property explicitly set + * in this deployment configuration (not in it's parent config). + *

+ * The deployment configuration consists of properties defined in the + * configuration itself and properties which are coming from the application + * configuration. The properties which are defined in the deployment + * configuration itself (own properties) should take precedence: their + * values should override the parent config properties values. + * + * @param property + * a property name + * @return whether the {@code property} is explicitly set in the + * configuration + */ + protected boolean isOwnProperty(String property) { + return getApplicationProperty(getProperties()::get, property) != null; + } + + /** + * Returns parent application configuration; + * + * @return the parent config + */ + protected ApplicationConfiguration getParentConfiguration() { + return parentConfig; + } + + private Properties mergeProperties(ApplicationConfiguration config, + Properties properties) { + Properties result = new Properties(); + Enumeration propertyNames = config.getPropertyNames(); + while (propertyNames.hasMoreElements()) { + String property = propertyNames.nextElement(); + result.put(property, config.getStringProperty(property, null)); + } + result.putAll(properties); + return result; + } + private static Map filterStringProperties( Properties properties) { Map result = new HashMap<>(); @@ -218,4 +303,5 @@ private static Map filterStringProperties( } return result; } + } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/VaadinConfigurationException.java b/flow-server/src/main/java/com/vaadin/flow/server/VaadinConfigurationException.java deleted file mode 100644 index 8e07e0df614..00000000000 --- a/flow-server/src/main/java/com/vaadin/flow/server/VaadinConfigurationException.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2000-2020 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; - -/** - * Exception thrown for failures in the generation of a deployment configuration - * object. - */ -public class VaadinConfigurationException extends Exception { - - /** - * Exception constructor. - * - * @param message - * exception message - * @param exception - * exception cause - */ - public VaadinConfigurationException(String message, Exception exception) { - super(message, exception); - } -} diff --git a/flow-server/src/main/java/com/vaadin/flow/server/VaadinServlet.java b/flow-server/src/main/java/com/vaadin/flow/server/VaadinServlet.java index eb03ee27b7d..c481473cf59 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/VaadinServlet.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/VaadinServlet.java @@ -34,6 +34,7 @@ import com.vaadin.flow.internal.CurrentInstance; import com.vaadin.flow.internal.VaadinContextInitializer; import com.vaadin.flow.server.HandlerHelper.RequestType; +import com.vaadin.flow.server.startup.ApplicationConfiguration; import com.vaadin.flow.shared.JsonConstants; /** @@ -180,14 +181,10 @@ public static VaadinServlet getCurrent() { */ protected DeploymentConfiguration createDeploymentConfiguration() throws ServletException { - try { - return createDeploymentConfiguration(DeploymentConfigurationFactory - .createInitParameters(getClass(), - new VaadinServletConfig(getServletConfig()))); - } catch (VaadinConfigurationException e) { - throw new ServletException( - "Failed to construct DeploymentConfiguration.", e); - } + return createDeploymentConfiguration( + new DeploymentConfigurationFactory().createInitParameters( + getClass(), + new VaadinServletConfig(getServletConfig()))); } /** @@ -201,7 +198,11 @@ protected DeploymentConfiguration createDeploymentConfiguration() */ protected DeploymentConfiguration createDeploymentConfiguration( Properties initParameters) { - return new DefaultDeploymentConfiguration(getClass(), initParameters); + VaadinServletContext context = new VaadinServletContext( + getServletContext()); + return new DefaultDeploymentConfiguration( + ApplicationConfiguration.get(context), getClass(), + initParameters); } /** diff --git a/flow-server/src/main/java/com/vaadin/flow/server/VaadinServletConfiguration.java b/flow-server/src/main/java/com/vaadin/flow/server/VaadinServletConfiguration.java deleted file mode 100644 index 846e729bf2d..00000000000 --- a/flow-server/src/main/java/com/vaadin/flow/server/VaadinServletConfiguration.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2000-2020 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; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.util.function.Function; - -import com.vaadin.flow.component.UI; -import com.vaadin.flow.function.DeploymentConfiguration; - -/** - * Configures subclasses of {@link VaadinServlet}. For a {@link VaadinServlet} - * class that has this annotation, the defined values are read during - * initialization and will be available using - * {@link DeploymentConfiguration#getApplicationOrSystemProperty(String, Object, Function)} - * as well as from specific methods in {@link DeploymentConfiguration}. Init - * params defined in web.xml or the @WebServlet - * annotation take precedence over values defined in this annotation. - * - * @deprecated Leftover from Vaadin Framework 8 where the developer typically - * defines their own servlet class. Starting from Flow 1.0, a - * servlet is automatically registered and this annotation serves - * very little purpose. - * - * @author Vaadin Ltd - * @since 1.0 - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@Deprecated -public @interface VaadinServletConfiguration { - /** - * Defines the init parameter name for methods in - * {@link VaadinServletConfiguration}. - * - * @author Vaadin Ltd - * @since 1.0 - */ - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.METHOD) - @Documented - @interface InitParameterName { - /** - * The name of the init parameter that the annotated method controls. - * - * @return the parameter name - */ - String value(); - } - - /** - * Whether Vaadin is in production mode. - * - * @return true if in production mode, false otherwise. - * - * @see DeploymentConfiguration#isProductionMode() - */ - @InitParameterName(InitParameters.SERVLET_PARAMETER_PRODUCTION_MODE) - boolean productionMode(); - - /** - * Gets the UI class to use for the servlet. - * - * @return the UI class - */ - @InitParameterName(InitParameters.UI_PARAMETER) - Class ui() default UI.class; - - /** - * The number of seconds between heartbeat requests of a UI, or a - * non-positive number if heartbeat is disabled. The default value is 300 - * seconds, i.e. 5 minutes. - * - * @return the time between heartbeats - * - * @see DeploymentConfiguration#getHeartbeatInterval() - */ - @InitParameterName(InitParameters.SERVLET_PARAMETER_HEARTBEAT_INTERVAL) - int heartbeatInterval() default DefaultDeploymentConfiguration.DEFAULT_HEARTBEAT_INTERVAL; - - /** - * Whether a session should be closed when all its open UIs have been idle - * for longer than its configured maximum inactivity time. The default value - * is false. - * - * @return true if UIs and sessions receiving only heartbeat requests are - * eventually closed; false if heartbeat requests extend UI and - * session lifetime indefinitely - * - * @see DeploymentConfiguration#isCloseIdleSessions() - */ - @InitParameterName(InitParameters.SERVLET_PARAMETER_CLOSE_IDLE_SESSIONS) - boolean closeIdleSessions() default DefaultDeploymentConfiguration.DEFAULT_CLOSE_IDLE_SESSIONS; - - /** - * Whether the framework should register automatic servlets for the - * application or not. The default value is false. - * - * @return true if no automatic servlet registration should be done - * - * @see DeploymentConfiguration#disableAutomaticServletRegistration() - */ - @InitParameterName(InitParameters.DISABLE_AUTOMATIC_SERVLET_REGISTRATION) - boolean disableAutomaticServletRegistration() default false; - -} diff --git a/flow-server/src/main/java/com/vaadin/flow/server/startup/AbstractConfigurationFactory.java b/flow-server/src/main/java/com/vaadin/flow/server/startup/AbstractConfigurationFactory.java index 91c01ca7c41..19a63cc3ecc 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/startup/AbstractConfigurationFactory.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/startup/AbstractConfigurationFactory.java @@ -16,9 +16,15 @@ package com.vaadin.flow.server.startup; import java.io.File; +import java.io.IOException; import java.io.Serializable; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; +import java.util.function.Function; + +import org.apache.commons.io.FileUtils; import com.vaadin.flow.server.InitParameters; import com.vaadin.flow.server.frontend.FrontendUtils; @@ -62,7 +68,7 @@ public class AbstractConfigurationFactory implements Serializable { * the token file data * @return the config parameters */ - public Map getConfigParametersUsingTokenData( + protected Map getConfigParametersUsingTokenData( JsonObject buildInfo) { Map params = new HashMap<>(); if (buildInfo.hasKey(SERVLET_PARAMETER_PRODUCTION_MODE)) { @@ -181,6 +187,33 @@ protected void setDevModePropertiesUsingTokenData( } } + /** + * Gets the content of the token file with given {@code locationProvider}. + * + * @param locationProvider + * the token file location provider + * @return the token file location, may be {@code null} + */ + protected String getTokenFileContent( + Function locationProvider) { + String location = locationProvider + .apply(FrontendUtils.PARAM_TOKEN_FILE); + String json = null; + // token file location passed via init parameter property + try { + if (location != null) { + File tokenFile = new File(location); + if (tokenFile != null && tokenFile.canRead()) { + json = FileUtils.readFileToString(tokenFile, + StandardCharsets.UTF_8); + } + } + return json; + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + /** * Verify that given folder actually exists on the system if we are not in * production mode. diff --git a/flow-server/src/main/java/com/vaadin/flow/server/startup/ApplicationConfiguration.java b/flow-server/src/main/java/com/vaadin/flow/server/startup/ApplicationConfiguration.java index 5d972874d30..c22077b81f9 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/startup/ApplicationConfiguration.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/startup/ApplicationConfiguration.java @@ -23,6 +23,7 @@ import com.vaadin.flow.function.DeploymentConfiguration; import com.vaadin.flow.server.AbstractConfiguration; import com.vaadin.flow.server.VaadinContext; +import com.vaadin.flow.server.frontend.FallbackChunk; /** * Configuration on the application level. @@ -69,4 +70,12 @@ static ApplicationConfiguration get(VaadinContext context) { */ VaadinContext getContext(); + /** + * Gets a fallback chunk for the application or {@code null} if it's not + * available. + * + * @return the application fallback chunk, may be {@code null}. + */ + FallbackChunk getFallbackChunk(); + } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/startup/DefaultApplicationConfigurationFactory.java b/flow-server/src/main/java/com/vaadin/flow/server/startup/DefaultApplicationConfigurationFactory.java index 9bf5a9f707f..d97b67b07a7 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/startup/DefaultApplicationConfigurationFactory.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/startup/DefaultApplicationConfigurationFactory.java @@ -25,6 +25,8 @@ import java.util.Map; import java.util.Objects; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.propertytypes.ServiceRanking; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,6 +34,7 @@ import com.vaadin.flow.di.ResourceProvider; import com.vaadin.flow.server.AbstractPropertyConfiguration; import com.vaadin.flow.server.VaadinContext; +import com.vaadin.flow.server.frontend.FallbackChunk; import com.vaadin.flow.server.frontend.FrontendUtils; import elemental.json.JsonObject; @@ -48,6 +51,8 @@ * @since * */ +@Component(service = ApplicationConfigurationFactory.class) +@ServiceRanking(Integer.MIN_VALUE) public class DefaultApplicationConfigurationFactory extends AbstractConfigurationFactory implements ApplicationConfigurationFactory { @@ -57,10 +62,13 @@ protected static class ApplicationConfigurationImpl extends private final VaadinContext context; + private final FallbackChunk fallbackChunk; + protected ApplicationConfigurationImpl(VaadinContext context, - Map properties) { + FallbackChunk fallbackChunk, Map properties) { super(properties); this.context = context; + this.fallbackChunk = fallbackChunk; } @Override @@ -78,25 +86,55 @@ public VaadinContext getContext() { return context; } + @Override + public FallbackChunk getFallbackChunk() { + return fallbackChunk; + } + } @Override public ApplicationConfiguration create(VaadinContext context) { + Objects.requireNonNull(context); Map props = new HashMap<>(); - for (final Enumeration e = context.getContextParameterNames(); e - .hasMoreElements();) { - final String name = e.nextElement(); + for (final Enumeration paramNames = context + .getContextParameterNames(); paramNames.hasMoreElements();) { + final String name = paramNames.nextElement(); props.put(name, context.getContextParameter(name)); } + JsonObject buildInfo = null; try { - JsonObject buildInfo = JsonUtil - .parse(getTokenFileFromClassloader(context)); - - props.putAll(getConfigParametersUsingTokenData(buildInfo)); + String content = getTokenFileContent(props::get); + if (content == null) { + content = getTokenFileFromClassloader(context); + } + buildInfo = content == null ? null : JsonUtil.parse(content); + if (buildInfo != null) { + props.putAll(getConfigParametersUsingTokenData(buildInfo)); + } } catch (IOException exception) { throw new UncheckedIOException(exception); } - return new ApplicationConfigurationImpl(context, props); + return doCreate(context, buildInfo == null ? null + : FrontendUtils.readFallbackChunk(buildInfo), props); + } + + /** + * Creates application configuration instance based on provided data. + * + * @param context + * the Vaadin context, not {@code null} + * @param chunk + * the fallback chunk, may be {@code null} + * @param properties + * the context parameters, not {@code null} + * @return a new application configuration instance + */ + protected ApplicationConfigurationImpl doCreate(VaadinContext context, + FallbackChunk chunk, Map properties) { + Objects.requireNonNull(context); + Objects.requireNonNull(properties); + return new ApplicationConfigurationImpl(context, chunk, properties); } /** @@ -204,4 +242,5 @@ private Logger getLogger() { return LoggerFactory .getLogger(DefaultApplicationConfigurationFactory.class); } + } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/startup/DevModeInitializer.java b/flow-server/src/main/java/com/vaadin/flow/server/startup/DevModeInitializer.java index e7fa4ff1680..2e3a15e3b55 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/startup/DevModeInitializer.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/startup/DevModeInitializer.java @@ -19,7 +19,6 @@ 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; @@ -37,7 +36,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; @@ -66,7 +64,6 @@ import com.vaadin.flow.component.dependency.NpmPackage; import com.vaadin.flow.component.page.AppShellConfigurator; import com.vaadin.flow.di.Lookup; -import com.vaadin.flow.function.DeploymentConfiguration; import com.vaadin.flow.router.HasErrorParameter; import com.vaadin.flow.router.Route; import com.vaadin.flow.server.Constants; @@ -85,7 +82,6 @@ import com.vaadin.flow.server.frontend.NodeTasks.Builder; import com.vaadin.flow.server.frontend.scanner.ClassFinder; import com.vaadin.flow.server.frontend.scanner.ClassFinder.DefaultClassFinder; -import com.vaadin.flow.server.startup.ServletDeployer.StubServletConfig; import com.vaadin.flow.theme.NoTheme; import com.vaadin.flow.theme.Theme; @@ -203,28 +199,7 @@ private static Set calculateApplicableClassNames() { @Override public void process(Set> classes, ServletContext context) throws ServletException { - Collection registrations = context - .getServletRegistrations().values(); - - ServletRegistration vaadinServletRegistration = null; - for (ServletRegistration registration : registrations) { - if (registration.getClassName() != null - && isVaadinServletSubClass(registration.getClassName())) { - vaadinServletRegistration = registration; - break; - } - } - - DeploymentConfiguration config; - if (vaadinServletRegistration != null) { - config = StubServletConfig.createDeploymentConfiguration(context, - vaadinServletRegistration, VaadinServlet.class); - } else { - config = StubServletConfig.createDeploymentConfiguration(context, - VaadinServlet.class); - } - - initDevModeHandler(classes, context, config); + initDevModeHandler(classes, context); setDevModeStarted(context); } @@ -253,15 +228,16 @@ private void setDevModeStarted(ServletContext context) { * classes to check for npm- and js modules * @param context * servlet context we are running in - * @param config - * deployment configuration * * @throws ServletException * if dev mode can't be initialized */ public static void initDevModeHandler(Set> classes, - ServletContext context, DeploymentConfiguration config) - throws ServletException { + ServletContext context) throws ServletException { + VaadinContext vaadinContext = new VaadinServletContext(context); + + ApplicationConfiguration config = ApplicationConfiguration + .get(vaadinContext); if (config.isProductionMode()) { log().debug("Skipping DEV MODE because PRODUCTION MODE is set."); return; @@ -286,13 +262,14 @@ public static void initDevModeHandler(Set> classes, File flowResourcesFolder = new File(baseDir, DEFAULT_FLOW_RESOURCES_FOLDER); - VaadinContext vaadinContext = new VaadinServletContext(context); - Lookup lookupFromServletConetext = new VaadinServletContext(context).getAttribute(Lookup.class); - Lookup lookupForClassFinder = Lookup.of(new DevModeClassFinder(classes), ClassFinder.class); - Lookup lookup = Lookup.compose(lookupForClassFinder, lookupFromServletConetext); - Builder builder = new NodeTasks.Builder(lookup, - new File(baseDir), new File(generatedDir), - new File(frontendFolder)); + Lookup lookupFromServletConetext = new VaadinServletContext(context) + .getAttribute(Lookup.class); + Lookup lookupForClassFinder = Lookup.of(new DevModeClassFinder(classes), + ClassFinder.class); + Lookup lookup = Lookup.compose(lookupForClassFinder, + lookupFromServletConetext); + Builder builder = new NodeTasks.Builder(lookup, new File(baseDir), + new File(generatedDir), new File(frontendFolder)); log().info("Starting dev-mode updaters in {} folder.", builder.npmFolder); @@ -397,7 +374,7 @@ public static void initDevModeHandler(Set> classes, DevModeHandler.start( Lookup.compose(lookup, - Lookup.of(config, DeploymentConfiguration.class)), + Lookup.of(config, ApplicationConfiguration.class)), builder.npmFolder, nodeTasksFuture); } @@ -411,9 +388,10 @@ private static boolean isEndpointServiceAvailable(Lookup lookup) { /** * Shows whether {@link DevModeHandler} has been already started or not. * - * @param servletContext The servlet context, not null - * @return true if {@link DevModeHandler} has already been started, - * false - otherwise + * @param servletContext + * The servlet context, not null + * @return true if {@link DevModeHandler} has already been + * started, false - otherwise */ public static boolean isDevModeAlreadyStarted( ServletContext servletContext) { diff --git a/flow-server/src/main/java/com/vaadin/flow/server/startup/LookupInitializer.java b/flow-server/src/main/java/com/vaadin/flow/server/startup/LookupInitializer.java index 867c25acf9d..25a521f371d 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/startup/LookupInitializer.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/startup/LookupInitializer.java @@ -24,7 +24,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.lang.reflect.Modifier; import java.net.URL; import java.util.ArrayList; import java.util.Collection; @@ -58,7 +57,8 @@ */ @HandlesTypes({ ResourceProvider.class, InstantiatorFactory.class, DeprecatedPolymerPublishedEventHandler.class, - EndpointGeneratorTaskFactory.class }) + EndpointGeneratorTaskFactory.class, + ApplicationConfigurationFactory.class }) public class LookupInitializer implements ClassLoaderAwareServletContainerInitializer { @@ -277,6 +277,9 @@ private void initStandardLookup(Set> classSet, for (Class serviceType : getServiceTypes()) { if (ResourceProvider.class.equals(serviceType)) { collectResourceProviders(classSet, services); + } else if (ApplicationConfigurationFactory.class + .equals(serviceType)) { + collectApplicationConfigurationFactories(classSet, services); } else { collectSubclasses(serviceType, classSet, services); } @@ -310,6 +313,28 @@ private void collectResourceProviders(Set> classSet, } } + private void collectApplicationConfigurationFactories( + Set> classSet, + Map, Collection> services) { + Set> factories = filterSubClasses( + ApplicationConfigurationFactory.class, classSet); + factories.remove(DefaultApplicationConfigurationFactory.class); + if (factories.isEmpty()) { + services.put(ApplicationConfigurationFactory.class, + Collections.singletonList( + new DefaultApplicationConfigurationFactory())); + } else if (factories.size() > 1) { + throw new IllegalStateException(SEVERAL_IMPLS + + DefaultApplicationConfigurationFactory.class + .getSimpleName() + + SPI + classSet + ONE_IMPL_REQUIRED); + } else { + Class clazz = factories.iterator().next(); + services.put(ApplicationConfigurationFactory.class, Collections + .singletonList(ReflectTools.createInstance(clazz))); + } + } + private Set> filterResourceProviders(Set> classes) { Set> resourceProviders = filterSubClasses( ResourceProvider.class, classes); @@ -321,8 +346,7 @@ private Set> filterSubClasses(Class clazz, Set> classes) { return classes == null ? Collections.emptySet() : classes.stream().filter(clazz::isAssignableFrom) - .filter(cls -> !cls.isInterface() && !cls.isSynthetic() - && !Modifier.isAbstract(cls.getModifiers())) + .filter(ReflectTools::isInstantiableService) .filter(cls -> !clazz.equals(cls)) .collect(Collectors.toSet()); } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/startup/ServletDeployer.java b/flow-server/src/main/java/com/vaadin/flow/server/startup/ServletDeployer.java index 2b3e2526172..d8d2e87b7fd 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/startup/ServletDeployer.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/startup/ServletDeployer.java @@ -21,6 +21,7 @@ import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.ServletRegistration; + import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -28,23 +29,22 @@ import java.util.Map; import java.util.Optional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.vaadin.flow.function.DeploymentConfiguration; import com.vaadin.flow.router.Route; import com.vaadin.flow.server.DeploymentConfigurationFactory; import com.vaadin.flow.server.InitParameters; import com.vaadin.flow.server.VaadinConfig; -import com.vaadin.flow.server.VaadinConfigurationException; import com.vaadin.flow.server.VaadinContext; import com.vaadin.flow.server.VaadinService; import com.vaadin.flow.server.VaadinServlet; import com.vaadin.flow.server.VaadinServletConfig; -import com.vaadin.flow.server.VaadinServletConfiguration; import com.vaadin.flow.server.VaadinServletContext; import com.vaadin.flow.server.VaadinServletService; import com.vaadin.flow.server.frontend.FrontendUtils; import com.vaadin.flow.server.webcomponent.WebComponentConfigurationRegistry; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Context listener that automatically registers Vaadin servlets. @@ -65,11 +65,10 @@ *

* In addition to the rules above, a servlet won't be registered, if any servlet * had been mapped to the same path already or if - * {@link InitParameters#DISABLE_AUTOMATIC_SERVLET_REGISTRATION} - * system property is set to {@code true}. + * {@link InitParameters#DISABLE_AUTOMATIC_SERVLET_REGISTRATION} system property + * is set to {@code true}. * * @author Vaadin Ltd - * @see VaadinServletConfiguration#disableAutomaticServletRegistration() * @since 1.0 */ public class ServletDeployer implements ServletContextListener { @@ -126,7 +125,7 @@ public String getConfigParameter(String name) { /** * Default ServletConfig implementation. */ - public static class StubServletConfig implements ServletConfig { + private static class StubServletConfig implements ServletConfig { private final ServletContext context; private final ServletRegistration registration; @@ -179,17 +178,11 @@ public Enumeration getInitParameterNames() { public static DeploymentConfiguration createDeploymentConfiguration( ServletContext context, ServletRegistration registration, Class servletClass) { - try { - ServletConfig servletConfig = new StubServletConfig(context, - registration); - return DeploymentConfigurationFactory - .createPropertyDeploymentConfiguration(servletClass, - new VaadinServletConfig(servletConfig)); - } catch (VaadinConfigurationException e) { - throw new IllegalStateException(String.format( - "Failed to get deployment configuration data for servlet with name '%s' and class '%s'", - registration.getName(), servletClass), e); - } + ServletConfig servletConfig = new StubServletConfig(context, + registration); + return new DeploymentConfigurationFactory() + .createPropertyDeploymentConfiguration(servletClass, + new VaadinServletConfig(servletConfig)); } /** @@ -203,15 +196,9 @@ public static DeploymentConfiguration createDeploymentConfiguration( */ public static DeploymentConfiguration createDeploymentConfiguration( ServletContext context, Class servletClass) { - try { - return DeploymentConfigurationFactory - .createPropertyDeploymentConfiguration(servletClass, - new VaadinServletContextConfig(context)); - } catch (VaadinConfigurationException e) { - throw new IllegalStateException(String.format( - "Failed to get deployment configuration data for servlet class '%s'", - servletClass), e); - } + return new DeploymentConfigurationFactory() + .createPropertyDeploymentConfiguration(servletClass, + new VaadinServletContextConfig(context)); } } @@ -261,7 +248,8 @@ private void logServletCreation(VaadinServletCreation servletCreation, } /** - * Prints to sysout a notification to the user that the application has been deployed. + * Prints to sysout a notification to the user that the application has been + * deployed. *

* This method is public so that it can be called in add-ons that map * servlet automatically but don't use this class for that. diff --git a/flow-server/src/main/java/com/vaadin/flow/server/startup/VaadinAppShellInitializer.java b/flow-server/src/main/java/com/vaadin/flow/server/startup/VaadinAppShellInitializer.java index 971c48d8516..d344feac6eb 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/startup/VaadinAppShellInitializer.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/startup/VaadinAppShellInitializer.java @@ -19,14 +19,13 @@ 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.Arrays; -import java.util.Collection; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -40,16 +39,13 @@ import com.vaadin.flow.component.page.Meta; 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; import com.vaadin.flow.server.AppShellRegistry; import com.vaadin.flow.server.Constants; import com.vaadin.flow.server.InvalidApplicationConfigurationException; import com.vaadin.flow.server.PWA; import com.vaadin.flow.server.PageConfigurator; -import com.vaadin.flow.server.VaadinServlet; import com.vaadin.flow.server.VaadinServletContext; -import com.vaadin.flow.server.startup.ServletDeployer.StubServletConfig; import com.vaadin.flow.theme.NoTheme; import com.vaadin.flow.theme.Theme; @@ -79,18 +75,7 @@ public class VaadinAppShellInitializer @Override public void process(Set> classes, ServletContext context) throws ServletException { - - Collection registrations = context - .getServletRegistrations().values(); - if (registrations.isEmpty()) { - return; - } - - DeploymentConfiguration config = StubServletConfig - .createDeploymentConfiguration(context, - registrations.iterator().next(), VaadinServlet.class); - - init(classes, context, config); + init(classes, context); } /** @@ -101,12 +86,11 @@ public void process(Set> classes, ServletContext context) * this class. * @param context * the servlet context. - * @param config - * the vaadin configuration for the application. */ @SuppressWarnings("unchecked") - public static void init(Set> classes, ServletContext context, - DeploymentConfiguration config) { + public static void init(Set> classes, ServletContext context) { + ApplicationConfiguration config = ApplicationConfiguration + .get(new VaadinServletContext(context)); if (config.useV14Bootstrap()) { return; @@ -186,7 +170,7 @@ public static void init(Set> classes, ServletContext context, * scanning. * * @return list of annotations handled by - * {@link VaadinAppShellInitializer#init(Set, ServletContext, DeploymentConfiguration)} + * {@link VaadinAppShellInitializer#init(Set, ServletContext)} */ @SuppressWarnings("unchecked") public static List> getValidAnnotations() { @@ -202,7 +186,7 @@ public static List> getValidAnnotations() { * scanning. * * @return list of super classes handled by - * {@link VaadinAppShellInitializer#init(Set, ServletContext, DeploymentConfiguration)} + * {@link VaadinAppShellInitializer#init(Set, ServletContext)} */ public static List> getValidSupers() { return Arrays.stream(getHandledTypes()) diff --git a/flow-server/src/test/java/com/vaadin/flow/server/CustomUIClassLoaderTest.java b/flow-server/src/test/java/com/vaadin/flow/server/CustomUIClassLoaderTest.java index 01a32e59791..5c594df9206 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/CustomUIClassLoaderTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/CustomUIClassLoaderTest.java @@ -1,14 +1,17 @@ package com.vaadin.flow.server; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Properties; import junit.framework.TestCase; import org.easymock.EasyMock; +import org.mockito.Mockito; import com.vaadin.flow.component.UI; import com.vaadin.flow.function.DeploymentConfiguration; +import com.vaadin.flow.server.startup.ApplicationConfiguration; import com.vaadin.tests.util.AlwaysLockedVaadinSession; public class CustomUIClassLoaderTest extends TestCase { @@ -59,8 +62,12 @@ public void testWithDefaultClassLoader() throws Exception { private static DeploymentConfiguration createConfigurationMock() { Properties properties = new Properties(); properties.put(InitParameters.UI_PARAMETER, MyUI.class.getName()); - return new DefaultDeploymentConfiguration(CustomUIClassLoaderTest.class, - properties); + ApplicationConfiguration config = Mockito + .mock(ApplicationConfiguration.class); + Mockito.when(config.getPropertyNames()) + .thenReturn(Collections.emptyEnumeration()); + return new DefaultDeploymentConfiguration(config, + CustomUIClassLoaderTest.class, properties); } private static VaadinRequest createRequestMock(ClassLoader classloader) { diff --git a/flow-server/src/test/java/com/vaadin/flow/server/DefaultDeploymentConfigurationTest.java b/flow-server/src/test/java/com/vaadin/flow/server/DefaultDeploymentConfigurationTest.java index 9872205cc1e..9fd98827710 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/DefaultDeploymentConfigurationTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/DefaultDeploymentConfigurationTest.java @@ -15,9 +15,14 @@ */ package com.vaadin.flow.server; +import java.util.Collections; import java.util.Properties; +import org.junit.Assert; import org.junit.Test; +import org.mockito.Mockito; + +import com.vaadin.flow.server.startup.ApplicationConfiguration; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; @@ -41,8 +46,12 @@ public void testGetSystemPropertyForDefaultPackage() String prop = "prop"; System.setProperty(prop, value); Properties initParameters = new Properties(); + ApplicationConfiguration appConfig = Mockito + .mock(ApplicationConfiguration.class); + Mockito.when(appConfig.getPropertyNames()) + .thenReturn(Collections.emptyEnumeration()); DefaultDeploymentConfiguration config = new DefaultDeploymentConfiguration( - clazz, initParameters); + appConfig, clazz, initParameters); assertEquals(value, config.getSystemProperty(prop)); } @@ -55,8 +64,13 @@ public void testGetSystemProperty() throws ClassNotFoundException { + '.' + prop, value); Properties initParameters = new Properties(); + ApplicationConfiguration appConfig = Mockito + .mock(ApplicationConfiguration.class); + Mockito.when(appConfig.getPropertyNames()) + .thenReturn(Collections.emptyEnumeration()); DefaultDeploymentConfiguration config = new DefaultDeploymentConfiguration( - DefaultDeploymentConfigurationTest.class, initParameters); + appConfig, DefaultDeploymentConfigurationTest.class, + initParameters); assertEquals(value, config.getSystemProperty(prop)); } @@ -64,7 +78,8 @@ public void testGetSystemProperty() throws ClassNotFoundException { public void booleanValueReadIgnoreTheCase_true() { Properties initParameters = new Properties(); initParameters.setProperty( - InitParameters.SERVLET_PARAMETER_SEND_URLS_AS_PARAMETERS, "tRUe"); + InitParameters.SERVLET_PARAMETER_SEND_URLS_AS_PARAMETERS, + "tRUe"); DefaultDeploymentConfiguration config = createDeploymentConfig( initParameters); @@ -77,7 +92,8 @@ public void booleanValueReadIgnoreTheCase_true() { public void booleanValueReadIgnoreTheCase_false() { Properties initParameters = new Properties(); initParameters.setProperty( - InitParameters.SERVLET_PARAMETER_SEND_URLS_AS_PARAMETERS, "FaLsE"); + InitParameters.SERVLET_PARAMETER_SEND_URLS_AS_PARAMETERS, + "FaLsE"); DefaultDeploymentConfiguration config = createDeploymentConfig( initParameters); @@ -110,12 +126,6 @@ public void booleanValueRead_exceptionOnNonBooleanValue() { createDeploymentConfig(initParameters); } - private DefaultDeploymentConfiguration createDeploymentConfig( - Properties initParameters) { - return new DefaultDeploymentConfiguration( - DefaultDeploymentConfigurationTest.class, initParameters); - } - @Test public void defaultPushUrl() { Properties initParameters = new Properties(); @@ -127,7 +137,8 @@ public void defaultPushUrl() { @Test public void pushUrl() { Properties initParameters = new Properties(); - initParameters.setProperty(InitParameters.SERVLET_PARAMETER_PUSH_URL, "foo"); + initParameters.setProperty(InitParameters.SERVLET_PARAMETER_PUSH_URL, + "foo"); DefaultDeploymentConfiguration config = createDeploymentConfig( initParameters); @@ -149,9 +160,130 @@ public void maxMessageSuspendTimeout_validValue_accepted() { public void maxMessageSuspendTimeout_invalidValue_defaultValue() { Properties initParameters = new Properties(); initParameters.setProperty( - InitParameters.SERVLET_PARAMETER_MAX_MESSAGE_SUSPEND_TIMEOUT, "kk"); + InitParameters.SERVLET_PARAMETER_MAX_MESSAGE_SUSPEND_TIMEOUT, + "kk"); DefaultDeploymentConfiguration config = createDeploymentConfig( initParameters); assertEquals(5000, config.getMaxMessageSuspendTimeout()); } + + @Test + public void isProductionMode_productionModeIsSetViaParentOnly_productionModeIsTakenFromParent() { + ApplicationConfiguration appConfig = Mockito + .mock(ApplicationConfiguration.class); + Mockito.when(appConfig.isProductionMode()).thenReturn(true); + + // Note: application configuration doesn't contain production mode + // parameter ! + Assert.assertNull(appConfig.getStringProperty( + InitParameters.SERVLET_PARAMETER_PRODUCTION_MODE, null)); + + DefaultDeploymentConfiguration config = createDeploymentConfig( + appConfig, new Properties()); + Assert.assertTrue(config.isProductionMode()); + Assert.assertTrue(config.getProperties().isEmpty()); + } + + @Test + public void isProductionMode_productionModeIsSetViaPropertiesAndViaParent_productionModeIsTakenFromProperties() { + ApplicationConfiguration appConfig = Mockito + .mock(ApplicationConfiguration.class); + Mockito.when(appConfig.isProductionMode()).thenReturn(false); + + Properties initParameters = new Properties(); + initParameters.setProperty( + InitParameters.SERVLET_PARAMETER_PRODUCTION_MODE, + Boolean.TRUE.toString()); + DefaultDeploymentConfiguration config = createDeploymentConfig( + appConfig, initParameters); + // the deployment configuration parameter takes precedence over parent + // config + Assert.assertTrue(config.isProductionMode()); + } + + @Test + public void useV14Bootstrap_v14ModeIsSetViaParentOnly_v14ModeIsTakenFromParent() { + ApplicationConfiguration appConfig = Mockito + .mock(ApplicationConfiguration.class); + Mockito.when(appConfig.useV14Bootstrap()).thenReturn(true); + + // Note: application configuration doesn't contain production mode + // parameter ! + Assert.assertNull(appConfig.getStringProperty( + InitParameters.SERVLET_PARAMETER_USE_V14_BOOTSTRAP, null)); + + DefaultDeploymentConfiguration config = createDeploymentConfig( + appConfig, new Properties()); + Assert.assertTrue(config.useV14Bootstrap()); + Assert.assertTrue(config.getProperties().isEmpty()); + } + + @Test + public void useV14Bootstrap_v14ModeIsSetViaParentOnlyAndViaParent_v14ModeIsTakenFromParent() { + ApplicationConfiguration appConfig = Mockito + .mock(ApplicationConfiguration.class); + Mockito.when(appConfig.useV14Bootstrap()).thenReturn(true); + + Properties initParameters = new Properties(); + initParameters.setProperty( + InitParameters.SERVLET_PARAMETER_USE_V14_BOOTSTRAP, + Boolean.TRUE.toString()); + + DefaultDeploymentConfiguration config = createDeploymentConfig( + appConfig, initParameters); + // the deployment configuration parameter takes precedence over parent + // config + Assert.assertTrue(config.useV14Bootstrap()); + } + + @Test + public void isXsrfProtectionEnabled_valueIsSetViaParentOnly_valueIsTakenFromParent() { + ApplicationConfiguration appConfig = Mockito + .mock(ApplicationConfiguration.class); + Mockito.when(appConfig.isXsrfProtectionEnabled()).thenReturn(true); + + // Note: application configuration doesn't contain production mode + // parameter ! + Assert.assertNull(appConfig.getStringProperty( + InitParameters.SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION, + null)); + + DefaultDeploymentConfiguration config = createDeploymentConfig( + appConfig, new Properties()); + Assert.assertTrue(config.isXsrfProtectionEnabled()); + Assert.assertTrue(config.getProperties().isEmpty()); + } + + @Test + public void isXsrfProtectionEnabled_valueIsSetViaParentOnlyAndViaParent_valueIsTakenFromParent() { + ApplicationConfiguration appConfig = Mockito + .mock(ApplicationConfiguration.class); + Mockito.when(appConfig.isXsrfProtectionEnabled()).thenReturn(false); + + Properties initParameters = new Properties(); + initParameters.setProperty( + InitParameters.SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION, + Boolean.FALSE.toString()); + + DefaultDeploymentConfiguration config = createDeploymentConfig( + appConfig, initParameters); + // the deployment configuration parameter takes precedence over parent + // config + Assert.assertTrue(config.isXsrfProtectionEnabled()); + } + + private DefaultDeploymentConfiguration createDeploymentConfig( + Properties initParameters) { + ApplicationConfiguration appConfig = Mockito + .mock(ApplicationConfiguration.class); + return createDeploymentConfig(appConfig, initParameters); + } + + private DefaultDeploymentConfiguration createDeploymentConfig( + ApplicationConfiguration appConfig, Properties initParameters) { + Mockito.when(appConfig.getPropertyNames()) + .thenReturn(Collections.emptyEnumeration()); + return new DefaultDeploymentConfiguration(appConfig, + DefaultDeploymentConfigurationTest.class, initParameters); + } } diff --git a/flow-server/src/test/java/com/vaadin/flow/server/DeploymentConfigurationFactoryTest.java b/flow-server/src/test/java/com/vaadin/flow/server/DeploymentConfigurationFactoryTest.java index 86a509a57f8..f4f15d733d7 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/DeploymentConfigurationFactoryTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/DeploymentConfigurationFactoryTest.java @@ -7,15 +7,12 @@ import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; -import java.net.URLConnection; -import java.net.URLStreamHandler; import java.nio.file.Files; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; -import java.util.Hashtable; import java.util.Map; import java.util.Properties; import java.util.Set; @@ -38,12 +35,13 @@ import com.vaadin.flow.server.frontend.FallbackChunk; import com.vaadin.flow.server.frontend.FallbackChunk.CssImportData; import com.vaadin.flow.server.frontend.FrontendUtils; +import com.vaadin.flow.server.startup.ApplicationConfiguration; import static com.vaadin.flow.server.Constants.VAADIN_SERVLET_RESOURCES; -import static com.vaadin.flow.server.DeploymentConfigurationFactory.DEV_FOLDER_MISSING_MESSAGE; import static com.vaadin.flow.server.InitParameters.SERVLET_PARAMETER_PRODUCTION_MODE; import static com.vaadin.flow.server.frontend.FrontendUtils.PARAM_TOKEN_FILE; import static com.vaadin.flow.server.frontend.FrontendUtils.TOKEN_FILE; +import static com.vaadin.flow.server.startup.AbstractConfigurationFactory.DEV_FOLDER_MISSING_MESSAGE; import static java.util.Collections.emptyMap; import static org.easymock.EasyMock.capture; import static org.easymock.EasyMock.expect; @@ -60,6 +58,10 @@ public class DeploymentConfigurationFactoryTest { private File tokenFile; private ServletContext contextMock; + private ApplicationConfiguration appConfiguration; + + private FallbackChunk fallbackChunk; + @Rule public ExpectedException exception = ExpectedException.none(); @@ -73,10 +75,6 @@ private static class ServletWithEnclosingUi extends VaadinServlet { } } - @VaadinServletConfiguration(productionMode = true, heartbeatInterval = 222) - private static class VaadinSettings extends VaadinServlet { - } - @Before public void setup() throws IOException { System.setProperty("user.dir", @@ -84,6 +82,7 @@ public void setup() throws IOException { tokenFile = new File(temporaryFolder.getRoot(), VAADIN_SERVLET_RESOURCES + TOKEN_FILE); FileUtils.writeLines(tokenFile, Arrays.asList("{", "}")); + appConfiguration = mockApplicationConfiguration(); contextMock = mock(ServletContext.class); defaultServletParams.put(PARAM_TOKEN_FILE, tokenFile.getPath()); @@ -100,7 +99,7 @@ public void servletWithEnclosingUI_hasItsNameInConfig() throws Exception { Map servletConfigParams = new HashMap<>( new HashMap<>(defaultServletParams)); - DeploymentConfiguration config = DeploymentConfigurationFactory + DeploymentConfiguration config = new DeploymentConfigurationFactory() .createDeploymentConfiguration(servlet, createVaadinConfigMock(servletConfigParams, Collections.singletonMap(PARAM_TOKEN_FILE, @@ -124,7 +123,7 @@ public void servletWithNoEnclosingUI_hasDefaultUiInConfig() Map servletConfigParams = new HashMap<>( defaultServletParams); - DeploymentConfiguration config = DeploymentConfigurationFactory + DeploymentConfiguration config = new DeploymentConfigurationFactory() .createDeploymentConfiguration(servlet, createVaadinConfigMock( servletConfigParams, emptyMap())); @@ -137,83 +136,6 @@ public void servletWithNoEnclosingUI_hasDefaultUiInConfig() servlet), UI.class.getName(), config.getUIClassName()); } - @Test - public void vaadinServletConfigurationRead() throws Exception { - Class servlet = VaadinSettings.class; - - Map servletConfigParams = new HashMap<>( - defaultServletParams); - - DeploymentConfiguration config = DeploymentConfigurationFactory - .createDeploymentConfiguration(servlet, createVaadinConfigMock( - servletConfigParams, emptyMap())); - - assertTrue(String.format( - "Unexpected value for production mode, check '%s' class annotation", - servlet), config.isProductionMode()); - assertEquals(String.format( - "Unexpected value for heartbeat interval, check '%s' class annotation", - servlet), 222, config.getHeartbeatInterval()); - } - - @Test - public void servletConfigParametersOverrideVaadinParameters() - throws Exception { - Class servlet = VaadinSettings.class; - - boolean overridingProductionModeValue = false; - int overridingHeartbeatIntervalValue = 444; - - Map servletConfigParams = new HashMap<>( - defaultServletParams); - servletConfigParams.put(SERVLET_PARAMETER_PRODUCTION_MODE, - Boolean.toString(overridingProductionModeValue)); - servletConfigParams.put( - InitParameters.SERVLET_PARAMETER_HEARTBEAT_INTERVAL, - Integer.toString(overridingHeartbeatIntervalValue)); - - DeploymentConfiguration config = DeploymentConfigurationFactory - .createDeploymentConfiguration(servlet, createVaadinConfigMock( - servletConfigParams, emptyMap())); - - assertEquals( - "Unexpected value for production mode, should be the same as in servlet config parameters", - overridingProductionModeValue, config.isProductionMode()); - assertEquals( - "Unexpected value for heartbeat interval, should be the same as in servlet config parameters", - overridingHeartbeatIntervalValue, - config.getHeartbeatInterval()); - } - - @Test - public void servletContextParametersOverrideVaadinParameters() - throws Exception { - Class servlet = VaadinSettings.class; - - boolean overridingProductionModeValue = false; - int overridingHeartbeatIntervalValue = 444; - - Map servletContextParams = new HashMap<>( - defaultServletParams); - servletContextParams.put(SERVLET_PARAMETER_PRODUCTION_MODE, - Boolean.toString(overridingProductionModeValue)); - servletContextParams.put( - InitParameters.SERVLET_PARAMETER_HEARTBEAT_INTERVAL, - Integer.toString(overridingHeartbeatIntervalValue)); - - DeploymentConfiguration config = DeploymentConfigurationFactory - .createDeploymentConfiguration(servlet, createVaadinConfigMock( - emptyMap(), servletContextParams)); - - assertEquals( - "Unexpected value for production mode, should be the same as in servlet context parameters", - overridingProductionModeValue, config.isProductionMode()); - assertEquals( - "Unexpected value for heartbeat interval, should be the same as in servlet context parameters", - overridingHeartbeatIntervalValue, - config.getHeartbeatInterval()); - } - @Test public void servletConfigParametersOverrideServletContextParameters() throws Exception { @@ -240,7 +162,7 @@ public void servletConfigParametersOverrideServletContextParameters() InitParameters.SERVLET_PARAMETER_HEARTBEAT_INTERVAL, Integer.toString(servletContextHeartbeatIntervalValue)); - DeploymentConfiguration config = DeploymentConfigurationFactory + DeploymentConfiguration config = new DeploymentConfigurationFactory() .createDeploymentConfiguration(servlet, createVaadinConfigMock( servletConfigParams, servletContextParams)); @@ -264,7 +186,7 @@ public void shouldNotThrow_noTokenFile_correctWebPackConfigExists() FrontendUtils.WEBPACK_CONFIG); FileUtils.writeLines(webPack, Arrays.asList("./webpack.generated.js")); - DeploymentConfigurationFactory.createDeploymentConfiguration( + new DeploymentConfigurationFactory().createDeploymentConfiguration( VaadinServlet.class, createVaadinConfigMock(map, emptyMap())); } @@ -359,34 +281,119 @@ public void shouldNotThrow_tokenFileFoldersExist() throws Exception { } @Test - public void externalStatsFileTrue_predefinedContext() throws Exception { - FileUtils.writeLines(tokenFile, - Arrays.asList("{", "\"externalStatsFile\": true", "}")); + public void createInitParameters_valuesFromContextAreIgnored_valuesAreTakenFromservletConfig() + throws Exception { + DeploymentConfigurationFactory factory = new DeploymentConfigurationFactory(); - DeploymentConfiguration config = createConfig(Collections - .singletonMap(PARAM_TOKEN_FILE, tokenFile.getPath())); + VaadinContext context = Mockito.mock(VaadinContext.class); + VaadinConfig config = Mockito.mock(VaadinConfig.class); - assertEquals(false, config.isProductionMode()); - assertEquals(false, config.enableDevServer()); - assertEquals(true, config.isStatsExternal()); - assertEquals(Constants.DEFAULT_EXTERNAL_STATS_URL, - config.getExternalStatsUrl()); + Mockito.when(config.getVaadinContext()).thenReturn(context); + + ApplicationConfiguration appConfig = Mockito + .mock(ApplicationConfiguration.class); + + Mockito.when(config.getConfigParameterNames()).thenReturn( + Collections.enumeration(Collections.singleton("foo"))); + Mockito.when(context.getContextParameterNames()).thenReturn( + Collections.enumeration(Collections.singleton("bar"))); + + Mockito.when(config.getConfigParameter("foo")).thenReturn("baz"); + Mockito.when(context.getContextParameter("bar")).thenReturn("foobar"); + + Mockito.when(context.getAttribute( + Mockito.eq(ApplicationConfiguration.class), Mockito.any())) + .thenReturn(appConfig); + + Properties parameters = factory.createInitParameters(Object.class, + config); + + Assert.assertEquals("baz", parameters.get("foo")); + Assert.assertFalse(parameters.contains("bar")); } @Test - public void externalStatsUrlGiven_predefinedContext() throws Exception { - FileUtils.writeLines(tokenFile, Arrays.asList("{", - "\"externalStatsUrl\": \"http://my.server/static/stats.json\"", - "}")); + public void createInitParameters_tokenFileIsSetViaContext_externalStatsUrlIsReadFromTokenFile_predefinedProperties() + throws Exception { + DeploymentConfigurationFactory factory = new DeploymentConfigurationFactory(); - DeploymentConfiguration config = createConfig(Collections - .singletonMap(PARAM_TOKEN_FILE, tokenFile.getPath())); + VaadinConfig config = mockTokenFileViaContextParam( + "{ 'externalStatsUrl': 'http://my.server/static/stats.json'}"); - assertEquals(false, config.isProductionMode()); - assertEquals(false, config.enableDevServer()); - assertEquals(true, config.isStatsExternal()); - assertEquals("http://my.server/static/stats.json", - config.getExternalStatsUrl()); + Properties parameters = factory.createInitParameters(Object.class, + config); + + Assert.assertEquals("http://my.server/static/stats.json", + parameters.get(Constants.EXTERNAL_STATS_URL)); + Assert.assertEquals(Boolean.TRUE.toString(), + parameters.get(Constants.EXTERNAL_STATS_FILE)); + Assert.assertEquals(Boolean.FALSE.toString(), parameters + .get(InitParameters.SERVLET_PARAMETER_ENABLE_DEV_SERVER)); + } + + @Test + public void createInitParameters_tokenFileIsSetViaContext_externalStatsFileIsReadFromTokenFile_predefinedProperties() + throws Exception { + DeploymentConfigurationFactory factory = new DeploymentConfigurationFactory(); + + VaadinConfig config = mockTokenFileViaContextParam( + "{ 'externalStatsFile': true}"); + + Properties parameters = factory.createInitParameters(Object.class, + config); + + Assert.assertEquals(Boolean.TRUE.toString(), + parameters.get(Constants.EXTERNAL_STATS_FILE)); + Assert.assertEquals(Boolean.FALSE.toString(), parameters + .get(InitParameters.SERVLET_PARAMETER_ENABLE_DEV_SERVER)); + } + + @Test + public void createInitParameters_tokenFileIsSetViaContext_setPropertyFromTokenFile() + throws Exception { + DeploymentConfigurationFactory factory = new DeploymentConfigurationFactory(); + + VaadinConfig config = mockTokenFileViaContextParam( + "{ '" + SERVLET_PARAMETER_PRODUCTION_MODE + "': true}"); + + Properties parameters = factory.createInitParameters(Object.class, + config); + + Assert.assertEquals(Boolean.TRUE.toString(), + parameters.get(SERVLET_PARAMETER_PRODUCTION_MODE)); + } + + private VaadinConfig mockTokenFileViaContextParam(String content) + throws IOException { + VaadinContext context = Mockito.mock(VaadinContext.class); + VaadinConfig config = Mockito.mock(VaadinConfig.class); + + ApplicationConfiguration appConfig = Mockito + .mock(ApplicationConfiguration.class); + + Mockito.when(config.getConfigParameterNames()) + .thenReturn(Collections.enumeration( + Collections.singleton(FrontendUtils.PARAM_TOKEN_FILE))); + Mockito.when(context.getContextParameterNames()) + .thenReturn(Collections.emptyEnumeration()); + + Mockito.when(config.getVaadinContext()).thenReturn(context); + + File tmpFile = temporaryFolder.newFile(); + Files.write(tmpFile.toPath(), Collections.singletonList(content)); + + Mockito.when( + context.getContextParameter(FrontendUtils.PARAM_TOKEN_FILE)) + .thenReturn(tmpFile.getPath()); + + Mockito.when(config.getConfigParameter(FrontendUtils.PARAM_TOKEN_FILE)) + .thenReturn(tmpFile.toString()); + + Mockito.when(context.getAttribute( + Mockito.eq(ApplicationConfiguration.class), Mockito.any())) + .thenReturn(appConfig); + + return config; } @Test @@ -413,21 +420,47 @@ public void externalStatsFileTrue_predefinedValuesAreNotOverridden() } @Test - public void createInitParameters_fallbackChunkObjectIsInInitParams() - throws VaadinConfigurationException, IOException { + public void createInitParameters_fallbackChunkIsCreatedViaAppConfig_fallbackChunkObjectIsInInitParams() + throws IOException { ServletContext context = Mockito.mock(ServletContext.class); ServletConfig config = Mockito.mock(ServletConfig.class); + ApplicationConfiguration appConfig = Mockito + .mock(ApplicationConfiguration.class); Mockito.when(config.getServletContext()).thenReturn(context); - Hashtable table = new Hashtable<>( - Collections.singletonMap(FrontendUtils.PARAM_TOKEN_FILE, "")); - Mockito.when(context.getInitParameterNames()).thenReturn(table.keys()); + Mockito.when(config.getInitParameterNames()) + .thenReturn(Collections.emptyEnumeration()); + + Mockito.when( + context.getAttribute(ApplicationConfiguration.class.getName())) + .thenReturn(appConfig); + + Mockito.when(appConfig.getFallbackChunk()).thenReturn(fallbackChunk); + Properties properties = new DeploymentConfigurationFactory() + .createInitParameters(Object.class, + new VaadinServletConfig(config)); + Object object = properties + .get(DeploymentConfigurationFactory.FALLBACK_CHUNK); + + Assert.assertSame(fallbackChunk, object); + } + + @Test + public void createInitParameters_servletConfigDefinesTokenFile_fallbackChunkObjectIsInInitParams() + throws IOException { + ServletContext context = Mockito.mock(ServletContext.class); + ServletConfig config = Mockito.mock(ServletConfig.class); + Mockito.when(config.getServletContext()).thenReturn(context); Mockito.when(config.getInitParameterNames()) - .thenReturn(new Hashtable().keys()); + .thenReturn(Collections.enumeration( + Collections.singleton(FrontendUtils.PARAM_TOKEN_FILE))); File tokenFile = temporaryFolder.newFile(); + Mockito.when(config.getInitParameter(FrontendUtils.PARAM_TOKEN_FILE)) + .thenReturn(tokenFile.getPath()); + Files.write(tokenFile.toPath(), Collections.singletonList("{ 'chunks': { " + "'fallback': {" + " 'jsModules': ['foo', 'bar']," @@ -437,7 +470,7 @@ public void createInitParameters_fallbackChunkObjectIsInInitParams() Mockito.when(context.getInitParameter(FrontendUtils.PARAM_TOKEN_FILE)) .thenReturn(tokenFile.getPath()); - Properties properties = DeploymentConfigurationFactory + Properties properties = new DeploymentConfigurationFactory() .createInitParameters(Object.class, new VaadinServletConfig(config)); @@ -473,60 +506,6 @@ public void createInitParameters_readDevModeProperties() throws Exception { .getProperty(InitParameters.REQUIRE_HOME_NODE_EXECUTABLE)); } - @Test - public void createInitParameters_readTokenFileFromContext() - throws VaadinConfigurationException, IOException { - VaadinContext context = Mockito.mock(VaadinContext.class); - VaadinConfig config = Mockito.mock(VaadinConfig.class); - - ResourceProvider resourceProvider = mockResourceProvider(config, - context); - - DeploymentConfigurationFactory.createInitParameters( - DeploymentConfigurationFactoryTest.class, config); - - Mockito.verify(resourceProvider) - .getApplicationResources(VAADIN_SERVLET_RESOURCES + TOKEN_FILE); - } - - @Test - public void createInitParameters_checkWebpackGeneratedFromContext() - throws VaadinConfigurationException, IOException { - VaadinContext context = Mockito.mock(VaadinContext.class); - VaadinConfig config = Mockito.mock(VaadinConfig.class); - - ResourceProvider resourceProvider = mockResourceProvider(config, - context); - - String path = VAADIN_SERVLET_RESOURCES + TOKEN_FILE; - - File tmpFile = temporaryFolder.newFile(); - Files.write(tmpFile.toPath(), Collections.singletonList("{}")); - - URLStreamHandler handler = new URLStreamHandler() { - - @Override - protected URLConnection openConnection(URL u) throws IOException { - return tmpFile.toURI().toURL().openConnection(); - } - }; - URL url = new URL("file", "", -1, "foo.jar!/" + path, handler); - - Mockito.when(resourceProvider.getApplicationResources(path)) - .thenReturn(Collections.singletonList(url)); - - Mockito.when(resourceProvider - .getApplicationResource(FrontendUtils.WEBPACK_GENERATED)) - .thenReturn(tmpFile.toURI().toURL()); - - DeploymentConfigurationFactory.createInitParameters( - DeploymentConfigurationFactoryTest.class, config); - - Mockito.verify(resourceProvider) - .getApplicationResource(FrontendUtils.WEBPACK_GENERATED); - - } - @Test public void createInitParameters_initParamtersAreSet_tokenDevModePropertiesAreNotSet() throws Exception { @@ -557,8 +536,9 @@ public void createInitParameters_initParamtersAreSet_tokenDevModePropertiesAreNo private DeploymentConfiguration createConfig(Map map) throws Exception { - return DeploymentConfigurationFactory.createDeploymentConfiguration( - VaadinServlet.class, createVaadinConfigMock(map, emptyMap())); + return new DeploymentConfigurationFactory() + .createDeploymentConfiguration(VaadinServlet.class, + createVaadinConfigMock(map, emptyMap())); } private VaadinConfig createVaadinConfigMock( @@ -568,23 +548,27 @@ private VaadinConfig createVaadinConfigMock( servletConfigParameters, servletContextParameters)); } - private ResourceProvider mockResourceProvider(VaadinConfig config, - VaadinContext context) throws VaadinConfigurationException { - Mockito.when(config.getVaadinContext()).thenReturn(context); + private ApplicationConfiguration mockApplicationConfiguration() { + ApplicationConfiguration configuration = mock( + ApplicationConfiguration.class); + expect(configuration.enableDevServer()).andReturn(true).anyTimes(); + expect(configuration.isProductionMode()).andReturn(true).anyTimes(); + expect(configuration.useV14Bootstrap()).andReturn(false).anyTimes(); + expect(configuration.getStringProperty(EasyMock.anyString(), + EasyMock.anyString())).andReturn(null).anyTimes(); + expect(configuration.isXsrfProtectionEnabled()).andReturn(false) + .anyTimes(); - Mockito.when(context.getContextParameterNames()) - .thenReturn(Collections.emptyEnumeration()); - Mockito.when(config.getConfigParameterNames()) - .thenReturn(Collections.emptyEnumeration()); + expect(configuration.getPropertyNames()) + .andReturn(Collections.emptyEnumeration()).anyTimes(); - Lookup lookup = Mockito.mock(Lookup.class); - ResourceProvider resourceProvider = Mockito - .mock(ResourceProvider.class); - Mockito.when(lookup.lookup(ResourceProvider.class)) - .thenReturn(resourceProvider); - Mockito.when(context.getAttribute(Lookup.class)).thenReturn(lookup); + fallbackChunk = mock(FallbackChunk.class); + + expect(configuration.getFallbackChunk()).andReturn(fallbackChunk) + .anyTimes(); + replay(configuration); + return configuration; - return resourceProvider; } private ServletConfig createServletConfigMock( @@ -594,6 +578,10 @@ private ServletConfig createServletConfigMock( URLClassLoader classLoader = new URLClassLoader( new URL[] { temporaryFolder.getRoot().toURI().toURL() }); + expect(contextMock + .getAttribute(ApplicationConfiguration.class.getName())) + .andReturn(appConfiguration).anyTimes(); + expect(contextMock.getInitParameterNames()) .andAnswer(() -> Collections .enumeration(servletContextParameters.keySet())) diff --git a/flow-server/src/test/java/com/vaadin/flow/server/DevModeHandlerTest.java b/flow-server/src/test/java/com/vaadin/flow/server/DevModeHandlerTest.java index 421b69342ee..5c4ebb8e991 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/DevModeHandlerTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/DevModeHandlerTest.java @@ -59,6 +59,7 @@ import com.vaadin.flow.function.DeploymentConfiguration; import com.vaadin.flow.server.communication.StreamRequestHandler; import com.vaadin.flow.server.frontend.FrontendUtils; +import com.vaadin.flow.server.startup.ApplicationConfiguration; import com.vaadin.tests.util.MockDeploymentConfiguration; import static com.vaadin.flow.server.DevModeHandler.WEBPACK_SERVER; @@ -81,7 +82,7 @@ @SuppressWarnings("restriction") public class DevModeHandlerTest { - private MockDeploymentConfiguration configuration; + private ApplicationConfiguration configuration; private HttpServer httpServer; private int responseStatus; @@ -98,6 +99,11 @@ private static class CustomRuntimeException extends RuntimeException { } + public abstract static class TestAppConfig + implements ApplicationConfiguration { + + } + private String baseDir; @Before @@ -105,8 +111,8 @@ public void setup() throws Exception { baseDir = temporaryFolder.getRoot().getAbsolutePath(); npmFolder = temporaryFolder.getRoot(); - configuration = new MockDeploymentConfiguration(); - configuration.setProductionMode(false); + configuration = Mockito.mock(ApplicationConfiguration.class); + mockApplicationConfiguration(configuration); new File(baseDir, FrontendUtils.WEBPACK_CONFIG).createNewFile(); createStubWebpackServer("Compiled", 100, baseDir); @@ -216,8 +222,9 @@ public void should_Fail_When_WebpackPrematurelyExit() throws Exception { @Test public void should_CaptureWebpackOutput_When_Failed() throws Exception { - configuration.setApplicationOrSystemProperty( - SERVLET_PARAMETER_DEVMODE_WEBPACK_TIMEOUT, "100"); + Mockito.when(configuration.getStringProperty( + SERVLET_PARAMETER_DEVMODE_WEBPACK_TIMEOUT, null)) + .thenReturn("100"); createStubWebpackServer("Failed to compile", 300, baseDir); DevModeHandler handler = DevModeHandler.start(createDevModeLookup(), npmFolder, CompletableFuture.completedFuture(null)); @@ -236,7 +243,7 @@ public void should_CaptureWebpackOutput_When_Failed() throws Exception { @Test public void shouldNot_CreateInstance_When_ProductionMode() throws Exception { - configuration.setProductionMode(true); + Mockito.when(configuration.isProductionMode()).thenReturn(true); DevModeHandler handler = DevModeHandler.start(createDevModeLookup(), npmFolder, CompletableFuture.completedFuture(null)); assertNull(handler); @@ -245,7 +252,7 @@ public void shouldNot_CreateInstance_When_ProductionMode() @Test public void enableDevServerFalse_shouldNotCreateInstance() throws Exception { - configuration.setEnableDevServer(false); + Mockito.when(configuration.enableDevServer()).thenReturn(false); DevModeHandler handler = DevModeHandler.start(createDevModeLookup(), npmFolder, CompletableFuture.completedFuture(null)); assertNull(handler); @@ -397,9 +404,14 @@ public void vaadinServlet_forDifferentRequests_shouldHaveCorrectResponse() @Test public void should_GetStatsJson_From_Webpack() throws Exception { VaadinService vaadinService = mock(VaadinService.class); + DeploymentConfiguration configuration = Mockito + .mock(DeploymentConfiguration.class); Mockito.when(vaadinService.getDeploymentConfiguration()) .thenReturn(configuration); + Mockito.when(configuration.isProductionMode()).thenReturn(false); + Mockito.when(configuration.enableDevServer()).thenReturn(true); + String statsContent = "{}"; int port = prepareHttpServer(0, HTTP_OK, statsContent); DevModeHandler.start(port, createDevModeLookup(), npmFolder, @@ -441,9 +453,9 @@ public void startDevModeHandler_vaadinHomeNodeIsAFolder_throws() FrontendUtils.isWindows() ? "node/node.exe" : "node/node"); FileUtils.forceMkdir(node); - configuration.setApplicationOrSystemProperty( - InitParameters.REQUIRE_HOME_NODE_EXECUTABLE, - Boolean.TRUE.toString()); + Mockito.when(configuration.getBooleanProperty( + InitParameters.REQUIRE_HOME_NODE_EXECUTABLE, false)) + .thenReturn(true); DevModeHandler.start(createDevModeLookup(), npmFolder, CompletableFuture.completedFuture(null)).join(); } finally { @@ -494,8 +506,8 @@ public void serveDevModeRequest_prepareTasksThrows_serveDevModeReturnsFalseAndDo @Test public void start_twoTimes_onlyOneHandlerInstanceIsCreated() { - MockDeploymentConfiguration configuration = Mockito - .spy(MockDeploymentConfiguration.class); + TestAppConfig conf = Mockito.spy(TestAppConfig.class); + mockApplicationConfiguration(conf); DevModeHandler handler = DevModeHandler.start(0, createDevModeLookup(configuration), npmFolder, CompletableFuture.completedFuture(null)); @@ -724,8 +736,23 @@ private Lookup createDevModeLookup() { return createDevModeLookup(configuration); } - private Lookup createDevModeLookup(DeploymentConfiguration config) { - return Lookup.of(config, DeploymentConfiguration.class); + private Lookup createDevModeLookup(ApplicationConfiguration config) { + return Lookup.of(config, ApplicationConfiguration.class); + } + + private void mockApplicationConfiguration( + ApplicationConfiguration appConfig) { + Mockito.when(appConfig.isProductionMode()).thenReturn(false); + Mockito.when(appConfig.enableDevServer()).thenReturn(true); + + Mockito.when(appConfig.getStringProperty(Mockito.anyString(), + Mockito.anyString())) + .thenAnswer(invocation -> invocation.getArgumentAt(1, + String.class)); + Mockito.when(appConfig.getBooleanProperty(Mockito.anyString(), + Mockito.anyBoolean())) + .thenAnswer(invocation -> invocation.getArgumentAt(1, + Boolean.class)); } public static HttpServer createStubWebpackTcpListener(int port, int status, diff --git a/flow-server/src/test/java/com/vaadin/flow/server/PropertyDeploymentConfigurationTest.java b/flow-server/src/test/java/com/vaadin/flow/server/PropertyDeploymentConfigurationTest.java new file mode 100644 index 00000000000..c303828adb5 --- /dev/null +++ b/flow-server/src/test/java/com/vaadin/flow/server/PropertyDeploymentConfigurationTest.java @@ -0,0 +1,435 @@ +/* + * Copyright 2000-2020 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; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.Properties; + +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +import com.vaadin.flow.server.startup.ApplicationConfiguration; + +public class PropertyDeploymentConfigurationTest { + + @Test + public void isProductionMode_modeIsProvidedViaParentOnly_valueFromParentIsReturned() { + ApplicationConfiguration appConfig = mockAppConfig(); + Mockito.when(appConfig.isProductionMode()).thenReturn(true); + PropertyDeploymentConfiguration config = createConfiguration(appConfig, + new Properties()); + Assert.assertTrue(config.isProductionMode()); + // there is no any property + Assert.assertTrue(config.getInitParameters().isEmpty()); + } + + @Test + public void isProductionMode_modeIsProvidedViaPropertiesAndParent_valueFromPropertiesIsReturned() { + ApplicationConfiguration appConfig = mockAppConfig(); + Mockito.when(appConfig.isProductionMode()).thenReturn(false); + + Properties properties = new Properties(); + properties.put(InitParameters.SERVLET_PARAMETER_PRODUCTION_MODE, + Boolean.TRUE.toString()); + PropertyDeploymentConfiguration config = createConfiguration(appConfig, + properties); + Assert.assertTrue(config.isProductionMode()); + Assert.assertEquals(properties, config.getInitParameters()); + } + + @Test + public void enableDevServer_valueIsProvidedViaParentOnly_valueFromParentIsReturned() { + ApplicationConfiguration appConfig = mockAppConfig(); + Mockito.when(appConfig.enableDevServer()).thenReturn(true); + PropertyDeploymentConfiguration config = createConfiguration(appConfig, + new Properties()); + Assert.assertTrue(config.enableDevServer()); + // there is no any property + Assert.assertTrue(config.getInitParameters().isEmpty()); + } + + @Test + public void reuseDevServer_valueIsProvidedViaPropertiesAndParent_valueFromPropertiesIsReturned() { + ApplicationConfiguration appConfig = mockAppConfig(); + Mockito.when(appConfig.reuseDevServer()).thenReturn(false); + + Properties properties = new Properties(); + properties.put(InitParameters.SERVLET_PARAMETER_REUSE_DEV_SERVER, + Boolean.TRUE.toString()); + PropertyDeploymentConfiguration config = createConfiguration(appConfig, + properties); + Assert.assertTrue(config.reuseDevServer()); + Assert.assertEquals(properties, config.getInitParameters()); + } + + @Test + public void reuseDevServer_valueIsProvidedViaParentOnly_valueFromParentIsReturned() { + ApplicationConfiguration appConfig = mockAppConfig(); + Mockito.when(appConfig.reuseDevServer()).thenReturn(true); + PropertyDeploymentConfiguration config = createConfiguration(appConfig, + new Properties()); + Assert.assertTrue(config.reuseDevServer()); + // there is no any property + Assert.assertTrue(config.getInitParameters().isEmpty()); + } + + @Test + public void enableDevServer_valueIsProvidedViaPropertiesAndParent_valueFromPropertiesIsReturned() { + ApplicationConfiguration appConfig = mockAppConfig(); + Mockito.when(appConfig.enableDevServer()).thenReturn(false); + + Properties properties = new Properties(); + properties.put(InitParameters.SERVLET_PARAMETER_ENABLE_DEV_SERVER, + Boolean.TRUE.toString()); + PropertyDeploymentConfiguration config = createConfiguration(appConfig, + properties); + Assert.assertTrue(config.enableDevServer()); + Assert.assertEquals(properties, config.getInitParameters()); + } + + @Test + public void useV14Bootstrap_valueIsProvidedViaParentOnly_valueFromParentIsReturned() { + ApplicationConfiguration appConfig = mockAppConfig(); + Mockito.when(appConfig.useV14Bootstrap()).thenReturn(true); + PropertyDeploymentConfiguration config = createConfiguration(appConfig, + new Properties()); + Assert.assertTrue(config.useV14Bootstrap()); + // there is no any property + Assert.assertTrue(config.getInitParameters().isEmpty()); + } + + @Test + public void useV14Bootstrap_valueIsProvidedViaPropertiesAndParent_valueFromPropertiesIsReturned() { + ApplicationConfiguration appConfig = mockAppConfig(); + Mockito.when(appConfig.useV14Bootstrap()).thenReturn(false); + + Properties properties = new Properties(); + properties.put(InitParameters.SERVLET_PARAMETER_USE_V14_BOOTSTRAP, + Boolean.TRUE.toString()); + PropertyDeploymentConfiguration config = createConfiguration(appConfig, + properties); + Assert.assertTrue(config.useV14Bootstrap()); + Assert.assertEquals(properties, config.getInitParameters()); + } + + @Test + public void isPnpmEnabled_valueIsProvidedViaParentOnly_valueFromParentIsReturned() { + ApplicationConfiguration appConfig = mockAppConfig(); + Mockito.when(appConfig.isPnpmEnabled()).thenReturn(true); + PropertyDeploymentConfiguration config = createConfiguration(appConfig, + new Properties()); + Assert.assertTrue(config.isPnpmEnabled()); + // there is no any property + Assert.assertTrue(config.getInitParameters().isEmpty()); + } + + @Test + public void isPnpmEnabled_valueIsProvidedViaPropertiesAndParent_valueFromPropertiesIsReturned() { + ApplicationConfiguration appConfig = mockAppConfig(); + Mockito.when(appConfig.isPnpmEnabled()).thenReturn(false); + + Properties properties = new Properties(); + properties.put(InitParameters.SERVLET_PARAMETER_ENABLE_PNPM, + Boolean.TRUE.toString()); + PropertyDeploymentConfiguration config = createConfiguration(appConfig, + properties); + Assert.assertTrue(config.isPnpmEnabled()); + Assert.assertEquals(properties, config.getInitParameters()); + } + + @Test + public void isXsrfProtectionEnabled_valueIsProvidedViaParentOnly_valueFromParentIsReturned() { + ApplicationConfiguration appConfig = mockAppConfig(); + Mockito.when(appConfig.isXsrfProtectionEnabled()).thenReturn(true); + PropertyDeploymentConfiguration config = createConfiguration(appConfig, + new Properties()); + Assert.assertTrue(config.isXsrfProtectionEnabled()); + // there is no any property + Assert.assertTrue(config.getInitParameters().isEmpty()); + } + + @Test + public void isXsrfProtectionEnabled_valueIsProvidedViaPropertiesAndParent_valueFromPropertiesIsReturned() { + ApplicationConfiguration appConfig = mockAppConfig(); + Mockito.when(appConfig.isXsrfProtectionEnabled()).thenReturn(false); + + Properties properties = new Properties(); + properties.put(InitParameters.SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION, + Boolean.FALSE.toString()); + PropertyDeploymentConfiguration config = createConfiguration(appConfig, + properties); + Assert.assertTrue(config.isXsrfProtectionEnabled()); + Assert.assertEquals(properties, config.getInitParameters()); + } + + @Test + public void getApplicationProperty_propertyIsDefinedInParentOnly_valueFromParentIsReturned() { + ApplicationConfiguration appConfig = mockAppConfig(); + + Mockito.when(appConfig.getStringProperty("foo", null)) + .thenReturn("bar"); + + PropertyDeploymentConfiguration configuration = createConfiguration( + appConfig, new Properties()); + + Assert.assertEquals("bar", configuration.getApplicationProperty("foo")); + // there is no any property + Assert.assertTrue(configuration.getInitParameters().isEmpty()); + } + + @Test + public void getApplicationProperty_propertyIsDefinedInPropertiesAndParent_valueFromPropertiesIsReturned() { + ApplicationConfiguration appConfig = mockAppConfig(); + + Mockito.when(appConfig.getStringProperty("foo", null)) + .thenReturn("bar"); + + Properties properties = new Properties(); + properties.put("foo", "baz"); + + PropertyDeploymentConfiguration configuration = createConfiguration( + appConfig, properties); + + Assert.assertEquals("baz", configuration.getApplicationProperty("foo")); + Assert.assertEquals(properties, configuration.getInitParameters()); + } + + @Test + public void isProductionMode_modeIsProvidedViaParentOnly_propertyIsSetToAnotherValue_valueFromParentIsReturnedViaAPI() { + ApplicationConfiguration appConfig = mockAppConfig(); + + // The property value is provided via API + Mockito.when(appConfig.isProductionMode()).thenReturn(true); + + // The property whose value is overridden above via API is different + Mockito.when(appConfig.getPropertyNames()) + .thenReturn(Collections.enumeration(Collections.singleton( + InitParameters.SERVLET_PARAMETER_PRODUCTION_MODE))); + + Mockito.when(appConfig.getStringProperty( + InitParameters.SERVLET_PARAMETER_PRODUCTION_MODE, null)) + .thenReturn(Boolean.FALSE.toString()); + + PropertyDeploymentConfiguration config = createConfiguration(appConfig, + new Properties()); + // Several things are checked: the value from parent is used via API and + // deployment configuration doesn't read the property directly even + // though its "getInitParameters" method returns the property. Also + // "getApplicationProperty" method checks the parent properties which + // should not be taken into account here + Assert.assertTrue(config.isProductionMode()); + Assert.assertTrue(config.getInitParameters() + .containsKey(InitParameters.SERVLET_PARAMETER_PRODUCTION_MODE)); + } + + @Test + public void enableDevServer_valueIsProvidedViaParentOnly_propertyIsSetToAnotherValue_valueFromParentIsReturnedViaAPI() { + ApplicationConfiguration appConfig = mockAppConfig(); + + // The property value is provided via API + Mockito.when(appConfig.enableDevServer()).thenReturn(true); + + // The property whose value is overridden above via API is different + Mockito.when(appConfig.getPropertyNames()) + .thenReturn(Collections.enumeration(Collections.singleton( + InitParameters.SERVLET_PARAMETER_ENABLE_DEV_SERVER))); + + Mockito.when(appConfig.getStringProperty( + InitParameters.SERVLET_PARAMETER_ENABLE_DEV_SERVER, null)) + .thenReturn(Boolean.FALSE.toString()); + + PropertyDeploymentConfiguration config = createConfiguration(appConfig, + new Properties()); + // Several things are checked: the value from parent is used via API and + // deployment configuration doesn't read the property directly even + // though its "getInitParameters" method returns the property. Also + // "getApplicationProperty" method checks the parent properties which + // should not be taken into account here + Assert.assertTrue(config.enableDevServer()); + Assert.assertTrue(config.getInitParameters().containsKey( + InitParameters.SERVLET_PARAMETER_ENABLE_DEV_SERVER)); + } + + @Test + public void useV14Bootstrap_valueIsProvidedViaParentOnly_propertyIsSetToAnotherValue_valueFromParentIsReturnedViaAPI() { + ApplicationConfiguration appConfig = mockAppConfig(); + + // The property value is provided via API + Mockito.when(appConfig.useV14Bootstrap()).thenReturn(true); + + // The property whose value is overridden above via API is different + Mockito.when(appConfig.getPropertyNames()) + .thenReturn(Collections.enumeration(Collections.singleton( + InitParameters.SERVLET_PARAMETER_USE_V14_BOOTSTRAP))); + + Mockito.when(appConfig.getStringProperty( + InitParameters.SERVLET_PARAMETER_USE_V14_BOOTSTRAP, null)) + .thenReturn(Boolean.FALSE.toString()); + + PropertyDeploymentConfiguration config = createConfiguration(appConfig, + new Properties()); + // Several things are checked: the value from parent is used via API and + // deployment configuration doesn't read the property directly even + // though its "getInitParameters" method returns the property. Also + // "getApplicationProperty" method checks the parent properties which + // should not be taken into account here + Assert.assertTrue(config.useV14Bootstrap()); + Assert.assertTrue(config.getInitParameters().containsKey( + InitParameters.SERVLET_PARAMETER_USE_V14_BOOTSTRAP)); + } + + @Test + public void isPnpmEnabled_valueIsProvidedViaParentOnly_propertyIsSetToAnotherValue_valueFromParentIsReturnedViaAPI() { + ApplicationConfiguration appConfig = mockAppConfig(); + + // The property value is provided via API + Mockito.when(appConfig.isPnpmEnabled()).thenReturn(true); + + // The property whose value is overridden above via API is different + Mockito.when(appConfig.getPropertyNames()) + .thenReturn(Collections.enumeration(Collections.singleton( + InitParameters.SERVLET_PARAMETER_ENABLE_PNPM))); + + Mockito.when(appConfig.getStringProperty( + InitParameters.SERVLET_PARAMETER_ENABLE_PNPM, null)) + .thenReturn(Boolean.FALSE.toString()); + + PropertyDeploymentConfiguration config = createConfiguration(appConfig, + new Properties()); + // Several things are checked: the value from parent is used via API and + // deployment configuration doesn't read the property directly even + // though its "getInitParameters" method returns the property. Also + // "getApplicationProperty" method checks the parent properties which + // should not be taken into account here + Assert.assertTrue(config.isPnpmEnabled()); + Assert.assertTrue(config.getInitParameters() + .containsKey(InitParameters.SERVLET_PARAMETER_ENABLE_PNPM)); + } + + @Test + public void reuseDevServer_valueIsProvidedViaParentOnly_propertyIsSetToAnotherValue_valueFromParentIsReturnedViaAPI() { + ApplicationConfiguration appConfig = mockAppConfig(); + + // The property value is provided via API + Mockito.when(appConfig.reuseDevServer()).thenReturn(true); + + // The property whose value is overridden above via API is different + Mockito.when(appConfig.getPropertyNames()) + .thenReturn(Collections.enumeration(Collections.singleton( + InitParameters.SERVLET_PARAMETER_REUSE_DEV_SERVER))); + + Mockito.when(appConfig.getStringProperty( + InitParameters.SERVLET_PARAMETER_REUSE_DEV_SERVER, null)) + .thenReturn(Boolean.FALSE.toString()); + + PropertyDeploymentConfiguration config = createConfiguration(appConfig, + new Properties()); + // Several things are checked: the value from parent is used via API and + // deployment configuration doesn't read the property directly even + // though its "getInitParameters" method returns the property. Also + // "getApplicationProperty" method checks the parent properties which + // should not be taken into account here + Assert.assertTrue(config.reuseDevServer()); + Assert.assertTrue(config.getInitParameters().containsKey( + InitParameters.SERVLET_PARAMETER_REUSE_DEV_SERVER)); + } + + @Test + public void isXsrfProtectionEnabled_valueIsProvidedViaParentOnly_propertyIsSetToAnotherValue_valueFromParentIsReturnedViaAPI() { + ApplicationConfiguration appConfig = mockAppConfig(); + + // The property value is provided via API + Mockito.when(appConfig.isXsrfProtectionEnabled()).thenReturn(true); + + // The property whose value is overridden above via API is different + Mockito.when(appConfig.getPropertyNames()) + .thenReturn(Collections.enumeration(Collections.singleton( + InitParameters.SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION))); + + Mockito.when(appConfig.getStringProperty( + InitParameters.SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION, null)) + .thenReturn(Boolean.TRUE.toString()); + + PropertyDeploymentConfiguration config = createConfiguration(appConfig, + new Properties()); + // Several things are checked: the value from parent is used via API and + // deployment configuration doesn't read the property directly even + // though its "getInitParameters" method returns the property. Also + // "getApplicationProperty" method checks the parent properties which + // should not be taken into account here + Assert.assertTrue(config.isXsrfProtectionEnabled()); + Assert.assertTrue(config.getInitParameters().containsKey( + InitParameters.SERVLET_PARAMETER_DISABLE_XSRF_PROTECTION)); + } + + @Test + public void getInitParameters_prorprtiesAreMergedFromParentAndDeploymentConfig() { + ApplicationConfiguration appConfig = Mockito + .mock(ApplicationConfiguration.class); + Mockito.when(appConfig.getPropertyNames()).thenReturn( + Collections.enumeration(Collections.singleton("foo"))); + + Mockito.when(appConfig.getStringProperty("foo", null)) + .thenReturn("foobar"); + + Properties properties = new Properties(); + properties.put("bar", "baz"); + PropertyDeploymentConfiguration configuration = createConfiguration( + appConfig, properties); + Properties initParameters = configuration.getInitParameters(); + + Assert.assertEquals("foobar", initParameters.get("foo")); + Assert.assertEquals("baz", initParameters.get("bar")); + } + + @Test + public void allDefaultAbstractConfigurationMethodsAreOverridden() { + Method[] methods = PropertyDeploymentConfiguration.class.getMethods(); + for (Method method : methods) { + Assert.assertNotEquals("There is a method '" + method.getName() + + "' which is declared in " + AbstractConfiguration.class + + " interface but it's not overriden in the " + + PropertyDeploymentConfiguration.class + + ". That's most likely a mistake because every method implementation in " + + PropertyDeploymentConfiguration.class + + " must take into account parent " + + ApplicationConfiguration.class + + " API which shares the same interface " + + AbstractConfiguration.class + " with " + + PropertyDeploymentConfiguration.class + + ", so every API method should call parent config and may not use just default implementation of " + + AbstractConfiguration.class, AbstractConfiguration.class, + method.getDeclaringClass()); + } + } + + private ApplicationConfiguration mockAppConfig() { + ApplicationConfiguration appConfig = Mockito + .mock(ApplicationConfiguration.class); + Mockito.when(appConfig.getPropertyNames()) + .thenReturn(Collections.emptyEnumeration()); + + return appConfig; + } + + private PropertyDeploymentConfiguration createConfiguration( + ApplicationConfiguration appConfig, Properties properties) { + return new PropertyDeploymentConfiguration(appConfig, Object.class, + properties); + } +} diff --git a/flow-server/src/test/java/com/vaadin/flow/server/VaadinServletConfigurationTest.java b/flow-server/src/test/java/com/vaadin/flow/server/VaadinServletConfigurationTest.java deleted file mode 100644 index ae8ae6e2216..00000000000 --- a/flow-server/src/test/java/com/vaadin/flow/server/VaadinServletConfigurationTest.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2000-2020 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; - -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; - -import java.util.Properties; - -import org.easymock.EasyMock; -import org.junit.Assert; -import org.junit.Test; -import org.mockito.Mockito; - -import com.vaadin.flow.component.UI; -import com.vaadin.flow.di.Lookup; -import com.vaadin.flow.di.ResourceProvider; -import com.vaadin.flow.function.DeploymentConfiguration; -import com.vaadin.flow.server.MockUIContainingServlet.ServletInUI; - -public class VaadinServletConfigurationTest { - - @Test - public void testEnclosingUIClass() throws Exception { - Properties servletInitParams = new Properties(); - ServletInUI servlet = new MockUIContainingServlet.ServletInUI(); - servlet.init(createServletConfig(servletInitParams)); - - Class uiClass = BootstrapHandler - .getUIClass(new VaadinServletRequest( - EasyMock.createMock(HttpServletRequest.class), - servlet.getService())); - Assert.assertEquals(MockUIContainingServlet.class, uiClass); - } - - @Test - public void testValuesFromAnnotation() throws ServletException { - Properties servletInitParams = new Properties(); - TestServlet servlet = new TestServlet(); - servlet.init(createServletConfig(servletInitParams)); - - DeploymentConfiguration configuration = servlet.getService() - .getDeploymentConfiguration(); - - Assert.assertEquals(true, configuration.isProductionMode()); - Assert.assertEquals(true, configuration.isCloseIdleSessions()); - Assert.assertEquals(1234, configuration.getHeartbeatInterval()); - - Class uiClass = BootstrapHandler - .getUIClass(new VaadinServletRequest( - EasyMock.createMock(HttpServletRequest.class), - servlet.getService())); - Assert.assertEquals(MockUIContainingServlet.class, uiClass); - } - - @Test - public void testValuesOverriddenForServlet() throws ServletException { - final boolean expectedBoolean = false; - final int expectedInt = 1111; - - Properties servletInitParams = new Properties(); - servletInitParams.setProperty( - InitParameters.SERVLET_PARAMETER_SEND_URLS_AS_PARAMETERS, - Boolean.toString(expectedBoolean)); - servletInitParams.setProperty( - InitParameters.SERVLET_PARAMETER_HEARTBEAT_INTERVAL, - Integer.toString(expectedInt)); - - TestServlet servlet = new TestServlet(); - servlet.init(createServletConfig(servletInitParams)); - - DeploymentConfiguration configuration = servlet.getService() - .getDeploymentConfiguration(); - - // Values from servlet init params take precedence - Assert.assertEquals(expectedBoolean, - configuration.isSendUrlsAsParameters()); - Assert.assertEquals(expectedInt, configuration.getHeartbeatInterval()); - - // Other params are as defined in the annotation - Assert.assertEquals(true, configuration.isCloseIdleSessions()); - - Class uiClass = BootstrapHandler - .getUIClass(new VaadinServletRequest( - EasyMock.createMock(HttpServletRequest.class), - servlet.getService())); - Assert.assertEquals(MockUIContainingServlet.class, uiClass); - } - - private MockServletConfig createServletConfig(Properties properties) { - MockServletConfig config = new MockServletConfig(properties); - ServletContext servletContext = config.getServletContext(); - Lookup lookup = Mockito.mock(Lookup.class); - servletContext.setAttribute(Lookup.class.getName(), lookup); - - ResourceProvider provider = Mockito.mock(ResourceProvider.class); - Mockito.when(lookup.lookup(ResourceProvider.class)) - .thenReturn(provider); - return config; - } - -} - -@VaadinServletConfiguration(productionMode = true, ui = MockUIContainingServlet.class, closeIdleSessions = true, heartbeatInterval = 1234) -class TestServlet extends VaadinServlet { - -} - -@VaadinServletConfiguration(productionMode = true, ui = MockUIContainingServlet.class) -class LegacyPropertyWarningTestServlet extends VaadinServlet { - -} - -@VaadinServletConfiguration(productionMode = true, ui = MockUIContainingServlet.class) -class LegacyPropertyEnabledTestServlet extends VaadinServlet { - -} diff --git a/flow-server/src/test/java/com/vaadin/flow/server/communication/IndexHtmlRequestHandlerTest.java b/flow-server/src/test/java/com/vaadin/flow/server/communication/IndexHtmlRequestHandlerTest.java index f91f5810394..eddd3c51e92 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/communication/IndexHtmlRequestHandlerTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/communication/IndexHtmlRequestHandlerTest.java @@ -44,7 +44,6 @@ import com.vaadin.flow.component.UI; import com.vaadin.flow.component.internal.JavaScriptBootstrapUI; import com.vaadin.flow.di.Lookup; -import com.vaadin.flow.function.DeploymentConfiguration; import com.vaadin.flow.internal.UsageStatistics; import com.vaadin.flow.server.AppShellRegistry; import com.vaadin.flow.server.DevModeHandler; @@ -55,6 +54,7 @@ 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.ApplicationConfiguration; import com.vaadin.flow.server.startup.VaadinAppShellInitializerTest.AppShellWithPWA; import com.vaadin.flow.server.startup.VaadinAppShellInitializerTest.MyAppShellWithConfigurator; import com.vaadin.tests.util.MockDeploymentConfiguration; @@ -448,10 +448,14 @@ public void should_attachWebpackErrors() throws Exception { // Create a DevModeHandler deploymentConfiguration.setEnableDevServer(true); deploymentConfiguration.setProductionMode(false); + + ApplicationConfiguration appConfig = Mockito + .mock(ApplicationConfiguration.class); + mockApplicationConfiguration(appConfig); + DevModeHandler handler = DevModeHandler.start(0, - Lookup.of(deploymentConfiguration, - DeploymentConfiguration.class), - npmFolder, CompletableFuture.completedFuture(null)); + Lookup.of(appConfig, ApplicationConfiguration.class), npmFolder, + CompletableFuture.completedFuture(null)); Method join = DevModeHandler.class.getDeclaredMethod("join"); join.setAccessible(true); join.invoke(handler); @@ -665,4 +669,19 @@ private HttpServletRequest createRequest(String pathInfo) { .getRequestURL(); return request; } + + private void mockApplicationConfiguration( + ApplicationConfiguration appConfig) { + Mockito.when(appConfig.isProductionMode()).thenReturn(false); + Mockito.when(appConfig.enableDevServer()).thenReturn(true); + + Mockito.when(appConfig.getStringProperty(Mockito.anyString(), + Mockito.anyString())) + .thenAnswer(invocation -> invocation.getArgumentAt(1, + String.class)); + Mockito.when(appConfig.getBooleanProperty(Mockito.anyString(), + Mockito.anyBoolean())) + .thenAnswer(invocation -> invocation.getArgumentAt(1, + Boolean.class)); + } } diff --git a/flow-server/src/test/java/com/vaadin/flow/server/communication/PushAtmosphereHandlerTest.java b/flow-server/src/test/java/com/vaadin/flow/server/communication/PushAtmosphereHandlerTest.java index df5c4fbf412..9cc2dae8fb8 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/communication/PushAtmosphereHandlerTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/communication/PushAtmosphereHandlerTest.java @@ -19,10 +19,9 @@ import java.io.IOException; import java.io.PrintWriter; +import java.util.Collections; import java.util.Properties; -import com.vaadin.flow.server.DefaultDeploymentConfiguration; -import com.vaadin.flow.server.VaadinServletService; import org.atmosphere.cpr.AtmosphereRequest; import org.atmosphere.cpr.AtmosphereResource; import org.atmosphere.cpr.AtmosphereResponse; @@ -31,6 +30,10 @@ import org.junit.Test; import org.mockito.Mockito; +import com.vaadin.flow.server.DefaultDeploymentConfiguration; +import com.vaadin.flow.server.VaadinServletService; +import com.vaadin.flow.server.startup.ApplicationConfiguration; + public class PushAtmosphereHandlerTest { private AtmosphereResource resource; @@ -52,8 +55,12 @@ public void setup() throws IOException { Mockito.when(resource.getRequest()).thenReturn(request); Mockito.when(resource.getResponse()).thenReturn(response); + ApplicationConfiguration config = Mockito + .mock(ApplicationConfiguration.class); + Mockito.when(config.getPropertyNames()) + .thenReturn(Collections.emptyEnumeration()); VaadinServletService service = new VaadinServletService(null, - new DefaultDeploymentConfiguration(getClass(), + new DefaultDeploymentConfiguration(config, getClass(), new Properties())); PushHandler handler = new PushHandler(service); @@ -72,7 +79,8 @@ public void writeSessionExpiredAsyncPost() throws Exception { writeSessionExpiredAsync("POST"); } - private void writeSessionExpiredAsync(String httpMethod) throws IOException { + private void writeSessionExpiredAsync(String httpMethod) + throws IOException { Mockito.when(request.getMethod()).thenReturn(httpMethod); atmosphereHandler.onRequest(resource); diff --git a/flow-server/src/test/java/com/vaadin/flow/server/communication/UidlRequestHandlerTest.java b/flow-server/src/test/java/com/vaadin/flow/server/communication/UidlRequestHandlerTest.java index 20cc53636c3..84a5af76ade 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/communication/UidlRequestHandlerTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/communication/UidlRequestHandlerTest.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.OutputStream; import java.io.StringWriter; +import java.util.Collections; import java.util.Properties; import org.junit.Assert; @@ -35,6 +36,7 @@ import com.vaadin.flow.server.VaadinService; import com.vaadin.flow.server.VaadinServletService; import com.vaadin.flow.server.VaadinSession; +import com.vaadin.flow.server.startup.ApplicationConfiguration; import com.vaadin.flow.shared.ApplicationConstants; import elemental.json.JsonObject; @@ -68,14 +70,16 @@ public void setup() throws IOException { @Test public void writeSessionExpired() throws Exception { - + ApplicationConfiguration config = Mockito + .mock(ApplicationConfiguration.class); + Mockito.when(config.getPropertyNames()) + .thenReturn(Collections.emptyEnumeration()); VaadinService service = new VaadinServletService(null, - new DefaultDeploymentConfiguration(getClass(), + new DefaultDeploymentConfiguration(config, getClass(), new Properties())); when(request.getService()).thenReturn(service); - when(request - .getParameter(ApplicationConstants.REQUEST_TYPE_PARAMETER)) + when(request.getParameter(ApplicationConstants.REQUEST_TYPE_PARAMETER)) .thenReturn(RequestType.UIDL.getIdentifier()); boolean result = handler.handleSessionExpired(request, response); @@ -113,7 +117,8 @@ public void writeSessionExpired_whenUINotFound() throws IOException { } @Test - public void should_not_modifyUidl_when_MPR_nonJavaScriptBootstrapUI() throws Exception { + public void should_not_modifyUidl_when_MPR_nonJavaScriptBootstrapUI() + throws Exception { JavaScriptBootstrapUI ui = null; UidlRequestHandler handler = spy(new UidlRequestHandler()); @@ -135,13 +140,13 @@ public void should_not_modifyUidl_when_MPR_nonJavaScriptBootstrapUI() throws Exc assertTrue(v7Uidl.contains("http://localhost:9998/#!away")); assertTrue(v7Uidl.contains("window.location.hash = '!away';")); - assertEquals( - "setTimeout(() => window.history.pushState(null, '', $0))", + assertEquals("setTimeout(() => window.history.pushState(null, '', $0))", uidl.getArray("execute").getArray(1).getString(1)); } @Test - public void should_modifyUidl_when_MPR_JavaScriptBootstrapUI() throws Exception { + public void should_modifyUidl_when_MPR_JavaScriptBootstrapUI() + throws Exception { JavaScriptBootstrapUI ui = mock(JavaScriptBootstrapUI.class); UidlRequestHandler handler = spy(new UidlRequestHandler()); @@ -182,7 +187,8 @@ public void should_changeURL_when_v7LocationProvided() throws Exception { } @Test - public void should_updateHash_when_v7LocationNotProvided() throws Exception { + public void should_updateHash_when_v7LocationNotProvided() + throws Exception { JavaScriptBootstrapUI ui = mock(JavaScriptBootstrapUI.class); UidlRequestHandler handler = spy(new UidlRequestHandler()); @@ -215,7 +221,6 @@ public void should_not_modify_non_MPR_Uidl() throws Exception { handler.writeUidl(ui, writer, false); - String expected = uidl.toJson(); String out = writer.toString(); @@ -227,19 +232,21 @@ public void should_not_modify_non_MPR_Uidl() throws Exception { } private JsonObject generateUidl(boolean withLocation, boolean withHash) { + + // @formatter:off JsonObject uidl = JsonUtil.parse( - "{" + - " \"syncId\": 3," + - " \"clientId\": 3," + - " \"changes\": []," + - " \"execute\": [" + - " [\"\", \"document.title = $0\"]," + - " [\"\", \"setTimeout(() => window.history.pushState(null, '', $0))\"]," + - " [[0, 16], \"___PLACE_FOR_V7_UIDL___\", \"$0.firstElementChild.setResponse($1)\"]," + - " [1,null,[0, 16], \"return (function() { this.$server['}p']($0, true, $1)}).apply($2)\"]" + - " ]," + - " \"timings\": []" + - "}"); + "{" + + " \"syncId\": 3," + + " \"clientId\": 3," + + " \"changes\": []," + + " \"execute\": [" + + " [\"\", \"document.title = $0\"]," + + " [\"\", \"setTimeout(() => window.history.pushState(null, '', $0))\"]," + + " [[0, 16], \"___PLACE_FOR_V7_UIDL___\", \"$0.firstElementChild.setResponse($1)\"]," + + " [1,null,[0, 16], \"return (function() { this.$server['}p']($0, true, $1)}).apply($2)\"]" + + " ]," + + " \"timings\": []" + + "}"); String v7String = "\"syncId\": 2," + @@ -272,8 +279,11 @@ private JsonObject generateUidl(boolean withLocation, boolean withHash) { String hashRpc = "window.location.hash = '!away';"; + // @formatter:on + if (withLocation) { - v7String = v7String.replace("\"___PLACE_FOR_LOCATION_CHANGE___\"", locationChange); + v7String = v7String.replace("\"___PLACE_FOR_LOCATION_CHANGE___\"", + locationChange); } if (withHash) { v7String = v7String.replace("___PLACE_FOR_HASH_RPC___", hashRpc); @@ -283,6 +293,4 @@ private JsonObject generateUidl(boolean withLocation, boolean withHash) { return uidl; } - - } diff --git a/flow-server/src/test/java/com/vaadin/flow/server/frontend/TestUtils.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/TestUtils.java index 4b063696ed1..23b53fe00f3 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/frontend/TestUtils.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/frontend/TestUtils.java @@ -16,8 +16,6 @@ package com.vaadin.flow.server.frontend; -import static org.junit.Assert.assertNotNull; - import java.io.File; import java.io.IOException; import java.net.URL; @@ -26,6 +24,8 @@ import java.util.List; import java.util.stream.Collectors; +import static org.junit.Assert.assertNotNull; + /** * Shared code to use in the unit tests. * @@ -68,7 +68,7 @@ public static File getTestJar(String jarName) { */ public static File getTestFolder(String name) { File folder = new File(getTestResource(name).getFile()); - assert(folder.isDirectory()); + assert (folder.isDirectory()); return folder; } diff --git a/flow-server/src/test/java/com/vaadin/flow/server/startup/DefaultApplicationConfigurationFactoryTest.java b/flow-server/src/test/java/com/vaadin/flow/server/startup/DefaultApplicationConfigurationFactoryTest.java new file mode 100644 index 00000000000..72b385882d3 --- /dev/null +++ b/flow-server/src/test/java/com/vaadin/flow/server/startup/DefaultApplicationConfigurationFactoryTest.java @@ -0,0 +1,273 @@ +/* + * Copyright 2000-2020 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 java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; +import java.nio.file.Files; +import java.util.Collections; +import java.util.List; + +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.mockito.Mockito; + +import com.vaadin.flow.di.Lookup; +import com.vaadin.flow.di.ResourceProvider; +import com.vaadin.flow.server.Constants; +import com.vaadin.flow.server.VaadinConfig; +import com.vaadin.flow.server.VaadinContext; +import com.vaadin.flow.server.frontend.FrontendUtils; + +import static com.vaadin.flow.server.Constants.VAADIN_SERVLET_RESOURCES; +import static com.vaadin.flow.server.InitParameters.SERVLET_PARAMETER_USE_V14_BOOTSTRAP; +import static com.vaadin.flow.server.frontend.FrontendUtils.TOKEN_FILE; + +public class DefaultApplicationConfigurationFactoryTest { + + @Rule + public final TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Test + public void getTokenFileFromClassloader_tokenFileIsRead_checkWebpackGeneratedFromContext() + throws IOException { + VaadinContext context = Mockito.mock(VaadinContext.class); + VaadinConfig config = Mockito.mock(VaadinConfig.class); + + ResourceProvider resourceProvider = mockResourceProvider(config, + context); + + String path = VAADIN_SERVLET_RESOURCES + TOKEN_FILE; + + String content = "{ 'foo':'bar' }"; + mockClassPathTokenFile(resourceProvider, content); + + DefaultApplicationConfigurationFactory factory = new DefaultApplicationConfigurationFactory(); + + String tokenFileContent = factory.getTokenFileFromClassloader(context); + + Mockito.verify(resourceProvider) + .getApplicationResource(FrontendUtils.WEBPACK_GENERATED); + + Mockito.verify(resourceProvider).getApplicationResources(path); + + Assert.assertEquals(content, tokenFileContent.trim()); + } + + @Test + public void create_tokenFileIsReadFromClassloader_externalStatsFileIsReadFromTokenFile_predefinedContext() + throws MalformedURLException, IOException { + VaadinContext context = Mockito.mock(VaadinContext.class); + VaadinConfig config = Mockito.mock(VaadinConfig.class); + + ResourceProvider resourceProvider = mockResourceProvider(config, + context); + + String content = "{ 'externalStatsFile':true }"; + mockClassPathTokenFile(resourceProvider, content); + + DefaultApplicationConfigurationFactory factory = new DefaultApplicationConfigurationFactory(); + ApplicationConfiguration configuration = factory.create(context); + + List propertyNames = Collections + .list(configuration.getPropertyNames()); + Assert.assertTrue( + propertyNames.contains(Constants.EXTERNAL_STATS_FILE)); + Assert.assertTrue(configuration + .getBooleanProperty(Constants.EXTERNAL_STATS_FILE, false)); + Assert.assertFalse(configuration.isProductionMode()); + Assert.assertFalse(configuration.enableDevServer()); + } + + @Test + public void create_tokenFileIsSetViaContext_externalStatsFileIsReadFromTokenFile_predefinedContext() + throws MalformedURLException, IOException { + String content = "{ 'externalStatsFile':true }"; + VaadinContext context = mockTokenFileViaContextParam(content); + + DefaultApplicationConfigurationFactory factory = new DefaultApplicationConfigurationFactory(); + ApplicationConfiguration configuration = factory.create(context); + + List propertyNames = Collections + .list(configuration.getPropertyNames()); + Assert.assertTrue( + propertyNames.contains(Constants.EXTERNAL_STATS_FILE)); + Assert.assertTrue(configuration + .getBooleanProperty(Constants.EXTERNAL_STATS_FILE, false)); + Assert.assertFalse(configuration.isProductionMode()); + Assert.assertFalse(configuration.enableDevServer()); + } + + @Test + public void create_tokenFileIsSetViaContext_tokenFileIsReadViaContextProperty_propertiesAreReadFromContext() + throws IOException { + VaadinContext context = mockTokenFileViaContextParam( + "{ '" + SERVLET_PARAMETER_USE_V14_BOOTSTRAP + "':true }"); + + DefaultApplicationConfigurationFactory factory = new DefaultApplicationConfigurationFactory(); + ApplicationConfiguration configuration = factory.create(context); + + List propertyNames = Collections + .list(configuration.getPropertyNames()); + Assert.assertEquals(2, propertyNames.size()); + Assert.assertEquals(FrontendUtils.PARAM_TOKEN_FILE, + propertyNames.get(0)); + Assert.assertEquals(SERVLET_PARAMETER_USE_V14_BOOTSTRAP, + propertyNames.get(1)); + + Assert.assertTrue(configuration.useV14Bootstrap()); + } + + @Test + public void create_tokenFileIsSetViaContext_externalStatsUrlIsReadFromTokenFile_predefinedContext() + throws MalformedURLException, IOException { + String content = "{ 'externalStatsUrl': 'http://my.server/static/stats.json'}"; + VaadinContext context = mockTokenFileViaContextParam(content); + + DefaultApplicationConfigurationFactory factory = new DefaultApplicationConfigurationFactory(); + ApplicationConfiguration configuration = factory.create(context); + + List propertyNames = Collections + .list(configuration.getPropertyNames()); + Assert.assertTrue(propertyNames.contains(Constants.EXTERNAL_STATS_URL)); + Assert.assertTrue(configuration + .getBooleanProperty(Constants.EXTERNAL_STATS_FILE, false)); + Assert.assertEquals("http://my.server/static/stats.json", configuration + .getStringProperty(Constants.EXTERNAL_STATS_URL, null)); + Assert.assertFalse(configuration.isProductionMode()); + Assert.assertFalse(configuration.enableDevServer()); + } + + @Test + public void create_tokenFileIsReadFromClassloader_externalStatsUrlIsReadFromTokenFile_predefinedContext() + throws IOException { + VaadinContext context = Mockito.mock(VaadinContext.class); + VaadinConfig config = Mockito.mock(VaadinConfig.class); + + ResourceProvider resourceProvider = mockResourceProvider(config, + context); + + mockClassPathTokenFile(resourceProvider, + "{ 'externalStatsUrl': 'http://my.server/static/stats.json'}"); + + DefaultApplicationConfigurationFactory factory = new DefaultApplicationConfigurationFactory(); + ApplicationConfiguration configuration = factory.create(context); + + List propertyNames = Collections + .list(configuration.getPropertyNames()); + Assert.assertTrue(propertyNames.contains(Constants.EXTERNAL_STATS_URL)); + Assert.assertTrue(configuration + .getBooleanProperty(Constants.EXTERNAL_STATS_FILE, false)); + Assert.assertEquals("http://my.server/static/stats.json", configuration + .getStringProperty(Constants.EXTERNAL_STATS_URL, null)); + Assert.assertFalse(configuration.isProductionMode()); + Assert.assertFalse(configuration.enableDevServer()); + } + + @Test + public void create_propertiesAreReadFromContext() throws IOException { + VaadinContext context = Mockito.mock(VaadinContext.class); + VaadinConfig config = Mockito.mock(VaadinConfig.class); + ResourceProvider resourceProvider = mockResourceProvider(config, + context); + + Mockito.when(context.getContextParameterNames()).thenReturn( + Collections.enumeration(Collections.singleton("foo"))); + Mockito.when(context.getContextParameter("foo")).thenReturn("bar"); + + mockClassPathTokenFile(resourceProvider, "{}"); + + DefaultApplicationConfigurationFactory factory = new DefaultApplicationConfigurationFactory(); + ApplicationConfiguration configuration = factory.create(context); + + List propertyNames = Collections + .list(configuration.getPropertyNames()); + Assert.assertEquals(1, propertyNames.size()); + Assert.assertEquals("foo", propertyNames.get(0)); + Assert.assertEquals("bar", + configuration.getStringProperty("foo", null)); + } + + private void mockClassPathTokenFile(ResourceProvider resourceProvider, + String content) throws IOException, MalformedURLException { + String path = VAADIN_SERVLET_RESOURCES + TOKEN_FILE; + + File tmpFile = temporaryFolder.newFile(); + Files.write(tmpFile.toPath(), Collections.singletonList(content)); + + URLStreamHandler handler = new URLStreamHandler() { + + @Override + protected URLConnection openConnection(URL u) throws IOException { + return tmpFile.toURI().toURL().openConnection(); + } + }; + URL url = new URL("file", "", -1, "foo.jar!/" + path, handler); + + Mockito.when(resourceProvider.getApplicationResources(path)) + .thenReturn(Collections.singletonList(url)); + } + + private ResourceProvider mockResourceProvider(VaadinConfig config, + VaadinContext context) { + Mockito.when(config.getVaadinContext()).thenReturn(context); + + Mockito.when(context.getContextParameterNames()) + .thenReturn(Collections.emptyEnumeration()); + Mockito.when(config.getConfigParameterNames()) + .thenReturn(Collections.emptyEnumeration()); + + ApplicationConfiguration appConfig = Mockito + .mock(ApplicationConfiguration.class); + + Mockito.when(context.getAttribute(ApplicationConfiguration.class)) + .thenReturn(appConfig); + Mockito.when(context.getAttribute( + Mockito.eq(ApplicationConfiguration.class), Mockito.any())) + .thenReturn(appConfig); + + Lookup lookup = Mockito.mock(Lookup.class); + ResourceProvider resourceProvider = Mockito + .mock(ResourceProvider.class); + Mockito.when(lookup.lookup(ResourceProvider.class)) + .thenReturn(resourceProvider); + Mockito.when(context.getAttribute(Lookup.class)).thenReturn(lookup); + + return resourceProvider; + } + + private VaadinContext mockTokenFileViaContextParam(String content) + throws IOException { + VaadinContext context = Mockito.mock(VaadinContext.class); + Mockito.when(context.getContextParameterNames()) + .thenReturn(Collections.enumeration( + Collections.singleton(FrontendUtils.PARAM_TOKEN_FILE))); + + File tmpFile = temporaryFolder.newFile(); + Files.write(tmpFile.toPath(), Collections.singletonList(content)); + + Mockito.when( + context.getContextParameter(FrontendUtils.PARAM_TOKEN_FILE)) + .thenReturn(tmpFile.getPath()); + return context; + } +} diff --git a/flow-server/src/test/java/com/vaadin/flow/server/startup/DevModeInitializerTest.java b/flow-server/src/test/java/com/vaadin/flow/server/startup/DevModeInitializerTest.java index 8707635b5c9..836767e0cde 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/startup/DevModeInitializerTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/startup/DevModeInitializerTest.java @@ -13,7 +13,6 @@ import java.net.URLStreamHandler; import java.nio.file.Paths; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.EventListener; @@ -43,18 +42,16 @@ import com.vaadin.flow.server.frontend.FallbackChunk; import com.vaadin.flow.server.frontend.FrontendUtils; -import static com.vaadin.flow.server.Constants.CONNECT_JAVA_SOURCE_FOLDER_TOKEN; import static com.vaadin.flow.server.Constants.COMPATIBILITY_RESOURCES_FRONTEND_DEFAULT; +import static com.vaadin.flow.server.Constants.CONNECT_JAVA_SOURCE_FOLDER_TOKEN; import static com.vaadin.flow.server.Constants.RESOURCES_FRONTEND_DEFAULT; -import static com.vaadin.flow.server.InitParameters.SERVLET_PARAMETER_PRODUCTION_MODE; -import static com.vaadin.flow.server.InitParameters.SERVLET_PARAMETER_REUSE_DEV_SERVER; import static com.vaadin.flow.server.frontend.FrontendUtils.DEFAULT_CONNECT_OPENAPI_JSON_FILE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.never;; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times;; @NotThreadSafe public class DevModeInitializerTest extends DevModeInitializerTestBase { @@ -235,8 +232,7 @@ public void should_Run_Updaters_when_NoWebpackFile() throws Exception { @Test public void should_Not_Run_Updaters_inProductionMode() throws Exception { - System.setProperty("vaadin." + SERVLET_PARAMETER_PRODUCTION_MODE, - "true"); + Mockito.when(appConfig.isProductionMode()).thenReturn(true); DevModeInitializer devModeInitializer = new DevModeInitializer(); devModeInitializer.onStartup(classes, servletContext); assertNull(DevModeHandler.getDevModeHandler()); @@ -254,7 +250,7 @@ public void should_Not_AddContextListener() throws Exception { @Test public void listener_should_stopDevModeHandler_onDestroy() throws Exception { - initParams.put(SERVLET_PARAMETER_REUSE_DEV_SERVER, "false"); + Mockito.when(appConfig.reuseDevServer()).thenReturn(false); process(); @@ -267,8 +263,9 @@ public void listener_should_stopDevModeHandler_onDestroy() @Test public void shouldUseByteCodeScannerIfPropertySet() throws Exception { - initParams.put(InitParameters.SERVLET_PARAMETER_DEVMODE_OPTIMIZE_BUNDLE, - "true"); + Mockito.when(appConfig.getBooleanProperty( + InitParameters.SERVLET_PARAMETER_DEVMODE_OPTIMIZE_BUNDLE, + false)).thenReturn(true); DevModeInitializer devModeInitializer = new DevModeInitializer(); final Set> classes = new HashSet<>(); classes.add(NotVisitedWithDeps.class); @@ -304,16 +301,16 @@ public void should_generateOpenApi_when_EndpointPresents() throws Exception { String originalJavaSourceFolder = null; File generatedOpenApiJson = Paths - .get(baseDir, DEFAULT_CONNECT_OPENAPI_JSON_FILE).toFile(); + .get(baseDir, DEFAULT_CONNECT_OPENAPI_JSON_FILE).toFile(); try { - originalJavaSourceFolder = System.getProperty("vaadin." - + CONNECT_JAVA_SOURCE_FOLDER_TOKEN); + originalJavaSourceFolder = System + .getProperty("vaadin." + CONNECT_JAVA_SOURCE_FOLDER_TOKEN); // Configure a folder to check the endpoints, doesn't matter // which folder, since the actual task won't be run, just // to verify the mocked task is executed. System.setProperty("vaadin." + CONNECT_JAVA_SOURCE_FOLDER_TOKEN, - javaSourceFolder.getRoot().getAbsolutePath()); + javaSourceFolder.getRoot().getAbsolutePath()); Assert.assertFalse(generatedOpenApiJson.exists()); DevModeInitializer devModeInitializer = new DevModeInitializer(); @@ -323,11 +320,11 @@ public void should_generateOpenApi_when_EndpointPresents() Mockito.verify(taskGenerateConnect, times(1)).execute(); } finally { if (originalJavaSourceFolder != null) { - System.setProperty("vaadin." - + CONNECT_JAVA_SOURCE_FOLDER_TOKEN, originalJavaSourceFolder); + System.setProperty("vaadin." + CONNECT_JAVA_SOURCE_FOLDER_TOKEN, + originalJavaSourceFolder); } else { - System.clearProperty("vaadin." - + CONNECT_JAVA_SOURCE_FOLDER_TOKEN); + System.clearProperty( + "vaadin." + CONNECT_JAVA_SOURCE_FOLDER_TOKEN); } generatedOpenApiJson.delete(); } @@ -339,13 +336,13 @@ public void should_notGenerateOpenApi_when_EndpointIsNotUsed() throws Exception { String originalJavaSourceFolder = null; File generatedOpenApiJson = Paths - .get(baseDir, DEFAULT_CONNECT_OPENAPI_JSON_FILE).toFile(); + .get(baseDir, DEFAULT_CONNECT_OPENAPI_JSON_FILE).toFile(); try { - originalJavaSourceFolder = System.getProperty("vaadin." - + CONNECT_JAVA_SOURCE_FOLDER_TOKEN); - System.clearProperty("vaadin." - + CONNECT_JAVA_SOURCE_FOLDER_TOKEN); - Mockito.doReturn(null).when(lookup).lookup(EndpointGeneratorTaskFactory.class); + originalJavaSourceFolder = System + .getProperty("vaadin." + CONNECT_JAVA_SOURCE_FOLDER_TOKEN); + System.clearProperty("vaadin." + CONNECT_JAVA_SOURCE_FOLDER_TOKEN); + Mockito.doReturn(null).when(lookup) + .lookup(EndpointGeneratorTaskFactory.class); Assert.assertFalse(generatedOpenApiJson.exists()); devModeInitializer.onStartup(classes, servletContext); @@ -354,11 +351,11 @@ public void should_notGenerateOpenApi_when_EndpointIsNotUsed() Mockito.verify(taskGenerateConnect, never()).execute(); } finally { if (originalJavaSourceFolder != null) { - System.setProperty("vaadin." - + CONNECT_JAVA_SOURCE_FOLDER_TOKEN, originalJavaSourceFolder); + System.setProperty("vaadin." + CONNECT_JAVA_SOURCE_FOLDER_TOKEN, + originalJavaSourceFolder); } else { - System.clearProperty("vaadin." - + CONNECT_JAVA_SOURCE_FOLDER_TOKEN); + System.clearProperty( + "vaadin." + CONNECT_JAVA_SOURCE_FOLDER_TOKEN); } generatedOpenApiJson.delete(); } @@ -368,14 +365,14 @@ public void should_notGenerateOpenApi_when_EndpointIsNotUsed() public void should_generateTs_files() throws Exception { String originalJavaSourceFolder = null; try { - originalJavaSourceFolder = System.getProperty("vaadin." - + CONNECT_JAVA_SOURCE_FOLDER_TOKEN); + originalJavaSourceFolder = System + .getProperty("vaadin." + CONNECT_JAVA_SOURCE_FOLDER_TOKEN); // Configure a folder to check the endpoints, doesn't matter // which folder, since the actual task won't be run, just // to verify the mocked task is executed. System.setProperty("vaadin." + CONNECT_JAVA_SOURCE_FOLDER_TOKEN, - javaSourceFolder.getRoot().getAbsolutePath()); + javaSourceFolder.getRoot().getAbsolutePath()); DevModeInitializer devModeInitializer = new DevModeInitializer(); @@ -385,11 +382,11 @@ public void should_generateTs_files() throws Exception { Mockito.verify(taskGenerateConnect, times(1)).execute(); } finally { if (originalJavaSourceFolder != null) { - System.setProperty("vaadin." - + CONNECT_JAVA_SOURCE_FOLDER_TOKEN, originalJavaSourceFolder); + System.setProperty("vaadin." + CONNECT_JAVA_SOURCE_FOLDER_TOKEN, + originalJavaSourceFolder); } else { - System.clearProperty("vaadin." - + CONNECT_JAVA_SOURCE_FOLDER_TOKEN); + System.clearProperty( + "vaadin." + CONNECT_JAVA_SOURCE_FOLDER_TOKEN); } } } @@ -398,19 +395,6 @@ public void should_generateTs_files() throws Exception { public void onStartup_emptyServletRegistrations_shouldCreateDevModeHandler() throws Exception { DevModeInitializer devModeInitializer = new DevModeInitializer(); - Mockito.when(servletContext.getServletRegistrations()) - .thenReturn(Collections.emptyMap()); - Mockito.when(servletContext.getInitParameterNames()) - .thenReturn(Collections.enumeration(new HashSet<>(Arrays.asList( - InitParameters.SERVLET_PARAMETER_ENABLE_PNPM, - FrontendUtils.PROJECT_BASEDIR)))); - Mockito.when( - servletContext.getInitParameter(FrontendUtils.PROJECT_BASEDIR)) - .thenReturn(initParams.get(FrontendUtils.PROJECT_BASEDIR)); - Mockito.when(servletContext - .getInitParameter(InitParameters.SERVLET_PARAMETER_ENABLE_PNPM)) - .thenReturn(initParams - .get(InitParameters.SERVLET_PARAMETER_ENABLE_PNPM)); devModeInitializer.onStartup(classes, servletContext); assertNotNull(DevModeHandler.getDevModeHandler()); } @@ -430,6 +414,10 @@ public void onStartup_devModeAlreadyStarted_shouldBeTrueWhenStarted() .thenAnswer(answer -> servletContextAttributes .get(answer.getArgumentAt(0, String.class))); + Mockito.when(servletContext + .getAttribute(ApplicationConfiguration.class.getName())) + .thenReturn(appConfig); + Lookup lookup = Mockito.mock(Lookup.class); ResourceProvider resourceProvider = Mockito .mock(ResourceProvider.class); @@ -444,7 +432,8 @@ public void onStartup_devModeAlreadyStarted_shouldBeTrueWhenStarted() @Test(expected = IllegalStateException.class) public void onStartup_fallbackBaseDirIsNotProjectDirectory_throws() throws Exception { - initParams.remove(FrontendUtils.PROJECT_BASEDIR); + Mockito.when(appConfig.getStringProperty(FrontendUtils.PROJECT_BASEDIR, + null)).thenReturn(null); TemporaryFolder tmp = new TemporaryFolder(); tmp.create(); baseDir = tmp.getRoot().getPath(); @@ -464,7 +453,8 @@ public void onStartup_fallbackBaseDirIsNotProjectDirectory_throws() @Test public void onStartup_fallbackBaseDirIsMavenProjectDirectory_isAccepted() throws Exception { - initParams.remove(FrontendUtils.PROJECT_BASEDIR); + Mockito.when(appConfig.getStringProperty(FrontendUtils.PROJECT_BASEDIR, + null)).thenReturn(null); TemporaryFolder tmp = new TemporaryFolder(); tmp.create(); tmp.newFile("pom.xml"); @@ -485,7 +475,8 @@ public void onStartup_fallbackBaseDirIsMavenProjectDirectory_isAccepted() @Test public void onStartup_fallbackBaseDirIsGradleProjectDirectory_isAccepted() throws Exception { - initParams.remove(FrontendUtils.PROJECT_BASEDIR); + Mockito.when(appConfig.getStringProperty(FrontendUtils.PROJECT_BASEDIR, + null)).thenReturn(null); TemporaryFolder tmp = new TemporaryFolder(); tmp.create(); tmp.newFile("build.gradle"); @@ -589,4 +580,5 @@ private void loadingFsResources_allFilesExist(Collection urls, assertTrue("Resource doesn't load from given path", locations.get(0).exists()); } + } diff --git a/flow-server/src/test/java/com/vaadin/flow/server/startup/DevModeInitializerTestBase.java b/flow-server/src/test/java/com/vaadin/flow/server/startup/DevModeInitializerTestBase.java index cb557cf3c5e..1ceeb799118 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/startup/DevModeInitializerTestBase.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/startup/DevModeInitializerTestBase.java @@ -28,7 +28,6 @@ import com.vaadin.flow.di.Lookup; import com.vaadin.flow.di.ResourceProvider; import com.vaadin.flow.server.DevModeHandler; -import com.vaadin.flow.server.InitParameters; import com.vaadin.flow.server.VaadinServlet; import com.vaadin.flow.server.frontend.EndpointGeneratorTaskFactory; import com.vaadin.flow.server.frontend.FrontendUtils; @@ -48,7 +47,7 @@ import static com.vaadin.flow.server.frontend.NodeUpdateTestUtil.createStubNode; import static com.vaadin.flow.server.frontend.NodeUpdateTestUtil.createStubWebpackServer; import static org.junit.Assert.assertNull; -import static org.mockito.Mockito.any; +import static org.mockito.Matchers.any; /** * Base class for DevModeInitializer tests. It is an independent class so as it @@ -61,7 +60,6 @@ public class DevModeInitializerTestBase { // These fields are intentionally scoped default so // as they can be used in package tests ServletContext servletContext; - Map initParams; Set> classes; File mainPackageFile; File webpackFile; @@ -71,6 +69,8 @@ public class DevModeInitializerTestBase { TaskGenerateConnect taskGenerateConnect; TaskGenerateOpenApi taskGenerateOpenApi; + ApplicationConfiguration appConfig; + public final TemporaryFolder temporaryFolder = new TemporaryFolder(); @Rule @@ -89,6 +89,9 @@ public void setup() throws Exception { baseDir = temporaryFolder.getRoot().getPath(); Boolean enablePnpm = Boolean.TRUE; + appConfig = Mockito.mock(ApplicationConfiguration.class); + mockApplicationConfiguration(appConfig, enablePnpm); + createStubNode(false, true, enablePnpm, baseDir); createStubWebpackServer("Compiled", 500, baseDir); @@ -96,10 +99,15 @@ public void setup() throws Exception { ServletRegistration vaadinServletRegistration = Mockito .mock(ServletRegistration.class); - lookup = Mockito.mock(Lookup.class);; + Mockito.when(servletContext + .getAttribute(ApplicationConfiguration.class.getName())) + .thenReturn(appConfig); + + lookup = Mockito.mock(Lookup.class); Mockito.when(servletContext.getAttribute(Lookup.class.getName())) .thenReturn(lookup); - endpointGeneratorTaskFactory = Mockito.mock(EndpointGeneratorTaskFactory.class); + endpointGeneratorTaskFactory = Mockito + .mock(EndpointGeneratorTaskFactory.class); taskGenerateConnect = Mockito.mock(TaskGenerateConnect.class); taskGenerateOpenApi = Mockito.mock(TaskGenerateOpenApi.class); Mockito.doReturn(endpointGeneratorTaskFactory).when(lookup) @@ -117,14 +125,6 @@ public void setup() throws Exception { Mockito.when(vaadinServletRegistration.getClassName()) .thenReturn(VaadinServletSubClass.class.getName()); - initParams = new HashMap<>(); - initParams.put(FrontendUtils.PROJECT_BASEDIR, baseDir); - initParams.put(InitParameters.SERVLET_PARAMETER_ENABLE_PNPM, - enablePnpm.toString()); - - Mockito.when(vaadinServletRegistration.getInitParameters()) - .thenReturn(initParams); - classes = new HashSet<>(); classes.add(this.getClass()); @@ -216,6 +216,25 @@ public void runDestroy() throws Exception { devModeInitializer.contextDestroyed(null); } + private void mockApplicationConfiguration( + ApplicationConfiguration appConfig, boolean enablePnpm) { + Mockito.when(appConfig.isProductionMode()).thenReturn(false); + Mockito.when(appConfig.enableDevServer()).thenReturn(true); + Mockito.when(appConfig.isPnpmEnabled()).thenReturn(enablePnpm); + + Mockito.when(appConfig.getStringProperty(Mockito.anyString(), + Mockito.anyString())) + .thenAnswer(invocation -> invocation.getArgumentAt(1, + String.class)); + Mockito.when(appConfig.getBooleanProperty(Mockito.anyString(), + Mockito.anyBoolean())) + .thenAnswer(invocation -> invocation.getArgumentAt(1, + Boolean.class)); + + Mockito.when(appConfig.getStringProperty(FrontendUtils.PROJECT_BASEDIR, + null)).thenReturn(baseDir); + } + static List getClasspathURLs() { return Arrays.stream( System.getProperty("java.class.path").split(File.pathSeparator)) diff --git a/flow-server/src/test/java/com/vaadin/flow/server/startup/LookupInitializerTest.java b/flow-server/src/test/java/com/vaadin/flow/server/startup/LookupInitializerTest.java index 7007a8039ca..bc4e42e2306 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/startup/LookupInitializerTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/startup/LookupInitializerTest.java @@ -45,6 +45,7 @@ import com.vaadin.flow.di.InstantiatorFactory; import com.vaadin.flow.di.Lookup; import com.vaadin.flow.di.ResourceProvider; +import com.vaadin.flow.server.VaadinContext; import com.vaadin.flow.server.startup.testdata.AnotherTestInstantiatorFactory; import com.vaadin.flow.server.startup.testdata.OneMoreTestInstantiatorFactory; import com.vaadin.flow.server.startup.testdata.TestInstantiatorFactory; @@ -75,6 +76,14 @@ public Object getTemplateItem(Component template, JsonObject argValue, } + public static class TestApplicationConfigurationFactory + implements ApplicationConfigurationFactory { + @Override + public ApplicationConfiguration create(VaadinContext context) { + return null; + } + } + @Test public void processLookupInitializer_resourceProviderIsProvidedAsScannedClass_lookupReturnsTheProviderInstance() throws ServletException { @@ -276,6 +285,43 @@ protected Collection> getServiceTypes() { Assert.assertTrue(list instanceof ArrayList); } + @Test + public void processApplicationConfigurationFactory_noAvailableFactory_defaultFactoryIsReturned() + throws ServletException { + assertProcessApplicationConfigurationFactory(); + } + + @Test + public void processApplicationConfigurationFactory_defaultFactoryOnly_defaultFactoryIsReturned() + throws ServletException { + assertProcessApplicationConfigurationFactory( + DefaultApplicationConfigurationFactory.class); + } + + @Test + public void processApplicationConfigurationFactory_factoryIsProvided_providedFactoryIsCreated() + throws ServletException { + Lookup lookup = mockLookup(DefaultApplicationConfigurationFactory.class, + TestApplicationConfigurationFactory.class); + + ApplicationConfigurationFactory config = lookup + .lookup(ApplicationConfigurationFactory.class); + Assert.assertNotNull(config); + Assert.assertTrue( + config instanceof TestApplicationConfigurationFactory); + } + + private void assertProcessApplicationConfigurationFactory( + Class... factoryTypes) throws ServletException { + Lookup lookup = mockLookup(factoryTypes); + + ApplicationConfigurationFactory config = lookup + .lookup(ApplicationConfigurationFactory.class); + Assert.assertNotNull(config); + Assert.assertTrue( + config instanceof DefaultApplicationConfigurationFactory); + } + private Lookup mockLookup(ServletContext context, Class... classes) throws ServletException { ArgumentCaptor lookupCapture = ArgumentCaptor diff --git a/flow-server/src/test/java/com/vaadin/flow/server/startup/ServletDeployerTest.java b/flow-server/src/test/java/com/vaadin/flow/server/startup/ServletDeployerTest.java index bdd6aa64de0..89e721950ea 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/startup/ServletDeployerTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/startup/ServletDeployerTest.java @@ -37,6 +37,7 @@ import com.vaadin.flow.router.RouteConfiguration; import com.vaadin.flow.server.InitParameters; import com.vaadin.flow.server.VaadinServlet; +import com.vaadin.flow.server.frontend.FallbackChunk; import com.vaadin.flow.server.frontend.FrontendUtils; import com.vaadin.flow.server.webcomponent.WebComponentConfigurationRegistry; @@ -268,6 +269,23 @@ private ServletContextEvent getContextEvent(boolean addRoutes, replay(lookup); + ApplicationConfiguration appConfig = mock( + ApplicationConfiguration.class); + + expect(appConfig.getPropertyNames()) + .andReturn(Collections.emptyEnumeration()).anyTimes(); + expect(appConfig.getStringProperty(EasyMock.anyString(), + EasyMock.anyString())).andReturn(null).anyTimes(); + expect(appConfig.isProductionMode()).andReturn(false); + FallbackChunk chunk = mock(FallbackChunk.class); + expect(appConfig.getFallbackChunk()).andReturn(chunk).anyTimes(); + + replay(appConfig); + + expect(contextMock + .getAttribute(ApplicationConfiguration.class.getName())) + .andReturn(appConfig).anyTimes(); + expect(contextMock.getContextPath()).andReturn("").once(); expect(contextMock.getClassLoader()) .andReturn(this.getClass().getClassLoader()).anyTimes(); diff --git a/flow-server/src/test/java/com/vaadin/flow/server/startup/VaadinAppShellInitializerTest.java b/flow-server/src/test/java/com/vaadin/flow/server/startup/VaadinAppShellInitializerTest.java index 7e097e7c542..fb1aff37af8 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/startup/VaadinAppShellInitializerTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/startup/VaadinAppShellInitializerTest.java @@ -200,7 +200,6 @@ public static class AppShellWithPWA implements AppShellConfigurator { private ServletContext servletContext; private VaadinServletContext context; - private Map initParams; private Set> classes; private Document document; private Map attributeMap = new HashMap<>(); @@ -208,6 +207,7 @@ public static class AppShellWithPWA implements AppShellConfigurator { private MockServletServiceSessionSetup.TestVaadinServletService service; private PushConfiguration pushConfiguration; private Logger logger; + private ApplicationConfiguration appConfig; @Before public void setup() throws Exception { @@ -218,8 +218,11 @@ public void setup() throws Exception { servletContext = mocks.getServletContext(); + appConfig = mockApplicationConfiguration(); + attributeMap.put(Lookup.class.getName(), servletContext.getAttribute(Lookup.class.getName())); + attributeMap.put(ApplicationConfiguration.class.getName(), appConfig); service = mocks.getService(); Mockito.when(servletContext.getAttribute(Mockito.anyString())) @@ -234,9 +237,6 @@ public void setup() throws Exception { .mock(ServletRegistration.class); context = new VaadinServletContext(servletContext); - initParams = new HashMap<>(); - Mockito.when(registration.getInitParameters()).thenReturn(initParams); - classes = new HashSet<>(); Map registry = new HashMap<>(); @@ -398,7 +398,8 @@ public void should_throw_when_offendingClass() throws Exception { exception.expectMessage(containsString( "Found app shell configuration annotations in non")); exception.expectMessage(containsString( - "- @Meta, @Inline, @Viewport, @BodySize, @Push, @Theme" + " from")); + "- @Meta, @Inline, @Viewport, @BodySize, @Push, @Theme" + + " from")); classes.add(MyAppShellWithoutAnnotations.class); classes.add(OffendingClass.class); initializer.process(classes, servletContext); @@ -446,8 +447,8 @@ public void should_not_throw_when_classWithPageConfigurator() @Test public void should_not_throw_when_appShellAnnotationsAreAllowed_and_offendingClass() throws Exception { - initParams.put(Constants.ALLOW_APPSHELL_ANNOTATIONS, - Boolean.TRUE.toString()); + Mockito.when(appConfig.getBooleanProperty( + Constants.ALLOW_APPSHELL_ANNOTATIONS, false)).thenReturn(true); classes.add(OffendingClass.class); initializer.process(classes, servletContext); @@ -460,8 +461,8 @@ public void should_not_throw_when_appShellAnnotationsAreAllowed_and_offendingCla @Test public void should_link_to_PWA_article() throws Exception { - initParams.put(Constants.ALLOW_APPSHELL_ANNOTATIONS, - Boolean.TRUE.toString()); + Mockito.when(appConfig.getBooleanProperty( + Constants.ALLOW_APPSHELL_ANNOTATIONS, false)).thenReturn(true); ArgumentCaptor arg = ArgumentCaptor.forClass(String.class); classes.add(OffendingPwaClass.class); initializer.process(classes, servletContext); @@ -472,8 +473,8 @@ public void should_link_to_PWA_article() throws Exception { @Test public void should_not_link_to_PWA_article() throws Exception { - initParams.put(Constants.ALLOW_APPSHELL_ANNOTATIONS, - Boolean.TRUE.toString()); + Mockito.when(appConfig.getBooleanProperty( + Constants.ALLOW_APPSHELL_ANNOTATIONS, false)).thenReturn(true); ArgumentCaptor arg = ArgumentCaptor.forClass(String.class); classes.add(OffendingNonPwaClass.class); initializer.process(classes, servletContext); @@ -515,4 +516,21 @@ private ConcurrentMap clearIlogger() throws Exception { map.clear(); return map; } + + private ApplicationConfiguration mockApplicationConfiguration() { + ApplicationConfiguration config = Mockito + .mock(ApplicationConfiguration.class); + Mockito.when(config.isProductionMode()).thenReturn(false); + Mockito.when(config.enableDevServer()).thenReturn(true); + + Mockito.when(config.getStringProperty(Mockito.anyString(), + Mockito.anyString())) + .thenAnswer(invocation -> invocation.getArgumentAt(1, + String.class)); + Mockito.when(config.getBooleanProperty(Mockito.anyString(), + Mockito.anyBoolean())) + .thenAnswer(invocation -> invocation.getArgumentAt(1, + Boolean.class)); + return config; + } } diff --git a/flow-tests/test-common/src/main/java/com/vaadin/flow/uitest/servlet/ProductionModeTimingDataViewTestServlet.java b/flow-tests/test-common/src/main/java/com/vaadin/flow/uitest/servlet/ProductionModeTimingDataViewTestServlet.java index b6b8bc550ca..110e940c387 100644 --- a/flow-tests/test-common/src/main/java/com/vaadin/flow/uitest/servlet/ProductionModeTimingDataViewTestServlet.java +++ b/flow-tests/test-common/src/main/java/com/vaadin/flow/uitest/servlet/ProductionModeTimingDataViewTestServlet.java @@ -18,11 +18,10 @@ import javax.servlet.annotation.WebInitParam; import javax.servlet.annotation.WebServlet; -import com.vaadin.flow.server.VaadinServletConfiguration; - @WebServlet(asyncSupported = true, urlPatterns = { - "/view-production-timing/*" }, initParams = @WebInitParam(name = "requestTiming", value = "true")) -@VaadinServletConfiguration(productionMode = true) + "/view-production-timing/*" }, initParams = { + @WebInitParam(name = "requestTiming", value = "true"), + @WebInitParam(name = "productionMode", value = "true") }) public class ProductionModeTimingDataViewTestServlet extends ViewTestServlet { } diff --git a/flow-tests/test-common/src/main/java/com/vaadin/flow/uitest/servlet/ProductionModeViewTestServlet.java b/flow-tests/test-common/src/main/java/com/vaadin/flow/uitest/servlet/ProductionModeViewTestServlet.java index 79d701f1dba..2094a78c101 100644 --- a/flow-tests/test-common/src/main/java/com/vaadin/flow/uitest/servlet/ProductionModeViewTestServlet.java +++ b/flow-tests/test-common/src/main/java/com/vaadin/flow/uitest/servlet/ProductionModeViewTestServlet.java @@ -15,12 +15,11 @@ */ package com.vaadin.flow.uitest.servlet; +import javax.servlet.annotation.WebInitParam; import javax.servlet.annotation.WebServlet; -import com.vaadin.flow.server.VaadinServletConfiguration; - -@WebServlet(asyncSupported = true, urlPatterns = { "/view-production/*" }) -@VaadinServletConfiguration(productionMode = true) +@WebServlet(asyncSupported = true, urlPatterns = { + "/view-production/*" }, initParams = @WebInitParam(name = "productionMode", value = "true")) public class ProductionModeViewTestServlet extends ViewTestServlet { } diff --git a/flow-tests/test-common/src/main/java/com/vaadin/flow/uitest/servlet/ViewTestServlet.java b/flow-tests/test-common/src/main/java/com/vaadin/flow/uitest/servlet/ViewTestServlet.java index 6bbcfbbe102..e575629a437 100644 --- a/flow-tests/test-common/src/main/java/com/vaadin/flow/uitest/servlet/ViewTestServlet.java +++ b/flow-tests/test-common/src/main/java/com/vaadin/flow/uitest/servlet/ViewTestServlet.java @@ -17,13 +17,13 @@ import javax.servlet.ServletConfig; import javax.servlet.ServletException; +import javax.servlet.annotation.WebInitParam; import javax.servlet.annotation.WebServlet; import com.vaadin.flow.server.VaadinServlet; -import com.vaadin.flow.server.VaadinServletConfiguration; -@WebServlet(asyncSupported = true, urlPatterns = { "/view/*" }) -@VaadinServletConfiguration(productionMode = false) +@WebServlet(asyncSupported = true, urlPatterns = { + "/view/*" }, initParams = @WebInitParam(name = "productionMode", value = "false")) public class ViewTestServlet extends VaadinServlet { private static ViewClassLocator viewLocator; diff --git a/flow-tests/test-memory-leaks/src/main/java/com/vaadin/flow/memoryleaks/ui/MemoryLeakUI.java b/flow-tests/test-memory-leaks/src/main/java/com/vaadin/flow/memoryleaks/ui/MemoryLeakUI.java index e89fb55105a..ae850a5d958 100644 --- a/flow-tests/test-memory-leaks/src/main/java/com/vaadin/flow/memoryleaks/ui/MemoryLeakUI.java +++ b/flow-tests/test-memory-leaks/src/main/java/com/vaadin/flow/memoryleaks/ui/MemoryLeakUI.java @@ -15,6 +15,7 @@ */ package com.vaadin.flow.memoryleaks.ui; +import javax.servlet.annotation.WebInitParam; import javax.servlet.annotation.WebServlet; import com.vaadin.flow.component.Text; @@ -22,19 +23,20 @@ import com.vaadin.flow.component.html.NativeButton; import com.vaadin.flow.server.VaadinRequest; import com.vaadin.flow.server.VaadinServlet; -import com.vaadin.flow.server.VaadinServletConfiguration; public class MemoryLeakUI extends UI { - @WebServlet(asyncSupported = true, urlPatterns = { "/*" }) - @VaadinServletConfiguration(productionMode = false, ui = MemoryLeakUI.class) + @WebServlet(asyncSupported = true, urlPatterns = "/*", initParams = { + @WebInitParam(name = "ui", value = "com.vaadin.flow.memoryleaks.ui.MemoryLeakUI"), + @WebInitParam(name = "productionMode", value = "false") }) public static class MemoryLeakServlet extends VaadinServlet { } @Override protected void init(VaadinRequest request) { - NativeButton button = new NativeButton("Hello", e -> add(new Text("Hello"))); + NativeButton button = new NativeButton("Hello", + e -> add(new Text("Hello"))); button.setId("hello"); add(button); } diff --git a/flow-tests/test-root-context/src/main/java/com/vaadin/flow/ItApplicationConfigurationFactory.java b/flow-tests/test-root-context/src/main/java/com/vaadin/flow/ItApplicationConfigurationFactory.java new file mode 100644 index 00000000000..e80f627ab10 --- /dev/null +++ b/flow-tests/test-root-context/src/main/java/com/vaadin/flow/ItApplicationConfigurationFactory.java @@ -0,0 +1,39 @@ +/* + * Copyright 2000-2020 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; + +import java.util.Map; + +import org.osgi.service.component.annotations.Component; + +import com.vaadin.flow.server.Constants; +import com.vaadin.flow.server.VaadinContext; +import com.vaadin.flow.server.frontend.FallbackChunk; +import com.vaadin.flow.server.startup.ApplicationConfigurationFactory; +import com.vaadin.flow.server.startup.DefaultApplicationConfigurationFactory; + +@Component(service = ApplicationConfigurationFactory.class) +public class ItApplicationConfigurationFactory + extends DefaultApplicationConfigurationFactory { + + @Override + protected ApplicationConfigurationImpl doCreate(VaadinContext context, + FallbackChunk chunk, Map properties) { + properties.put(Constants.ALLOW_APPSHELL_ANNOTATIONS, + Boolean.TRUE.toString()); + return super.doCreate(context, chunk, properties); + } +} diff --git a/flow-tests/test-root-context/src/main/java/com/vaadin/flow/osgi/Activator.java b/flow-tests/test-root-context/src/main/java/com/vaadin/flow/osgi/Activator.java index d0accef12b2..3d4657e7c4c 100644 --- a/flow-tests/test-root-context/src/main/java/com/vaadin/flow/osgi/Activator.java +++ b/flow-tests/test-root-context/src/main/java/com/vaadin/flow/osgi/Activator.java @@ -33,7 +33,7 @@ import org.osgi.service.http.context.ServletContextHelper; import org.osgi.service.http.whiteboard.HttpWhiteboardConstants; -import com.vaadin.flow.server.VaadinServletConfiguration; +import com.vaadin.flow.server.InitParameters; import com.vaadin.flow.uitest.servlet.ProductionModeTimingDataViewTestServlet; import com.vaadin.flow.uitest.servlet.ProductionModeViewTestServlet; import com.vaadin.flow.uitest.servlet.RouterTestServlet; @@ -72,7 +72,6 @@ public void ungetService(Bundle bundle, } - @VaadinServletConfiguration(productionMode = false) private static class FixedViewServlet extends ViewTestServlet { @Override public void init(ServletConfig servletConfig) throws ServletException { @@ -84,7 +83,6 @@ public void init(ServletConfig servletConfig) throws ServletException { } } - @VaadinServletConfiguration(productionMode = false) private static class FixedRouterServlet extends RouterTestServlet { @Override public void init(ServletConfig servletConfig) throws ServletException { @@ -96,7 +94,6 @@ public void init(ServletConfig servletConfig) throws ServletException { } } - @VaadinServletConfiguration(productionMode = true) private static class FixedProductionModeViewServlet extends ProductionModeViewTestServlet { @@ -110,7 +107,6 @@ public void init(ServletConfig servletConfig) throws ServletException { } } - @VaadinServletConfiguration(productionMode = true) private static class FixedProductionModeTimingDataViewServlet extends ProductionModeTimingDataViewTestServlet { @Override @@ -129,21 +125,21 @@ void activate() throws NamespaceException { .getBundleContext(); context.registerService(Servlet.class, new FixedViewServlet(), - createProperties("/view/*")); + createProperties("/view/*", false)); context.registerService(Servlet.class, new FixedViewServlet(), - createProperties("/context/*")); + createProperties("/context/*", false)); context.registerService(Servlet.class, new FixedRouterServlet(), - createProperties("/new-router-session/*")); + createProperties("/new-router-session/*", false)); context.registerService(Servlet.class, new FixedProductionModeViewServlet(), - createProperties("/view-production/*")); + createProperties("/view-production/*", true)); context.registerService(Servlet.class, new FixedProductionModeTimingDataViewServlet(), - createProperties("/view-production-timing/*")); + createProperties("/view-production-timing/*", true)); registerPlainJsResource(context); @@ -175,7 +171,8 @@ private String registerCustomContext(BundleContext context) { private void registerCustomContextServlet(BundleContext context, String contextName) { - Hashtable properties = createProperties("/view/*"); + Hashtable properties = createProperties("/view/*", + false); properties.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT, "(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME + "=" + contextName + ")"); @@ -183,8 +180,13 @@ private void registerCustomContextServlet(BundleContext context, properties); } - private Hashtable createProperties(String mapping) { + private Hashtable createProperties(String mapping, + Boolean isProductionMode) { Hashtable properties = new Hashtable<>(); + properties.put( + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_INIT_PARAM_PREFIX + + InitParameters.SERVLET_PARAMETER_PRODUCTION_MODE, + isProductionMode.toString()); properties.put( HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_ASYNC_SUPPORTED, true); diff --git a/flow-tests/test-root-ui-context/src/main/java/com/vaadin/flow/uitest/servlet/ApplicationRunnerServlet.java b/flow-tests/test-root-ui-context/src/main/java/com/vaadin/flow/uitest/servlet/ApplicationRunnerServlet.java index 577aaf16c96..c3effc31786 100644 --- a/flow-tests/test-root-ui-context/src/main/java/com/vaadin/flow/uitest/servlet/ApplicationRunnerServlet.java +++ b/flow-tests/test-root-ui-context/src/main/java/com/vaadin/flow/uitest/servlet/ApplicationRunnerServlet.java @@ -58,9 +58,11 @@ import com.vaadin.flow.server.SystemMessagesProvider; import com.vaadin.flow.server.VaadinService; import com.vaadin.flow.server.VaadinServlet; +import com.vaadin.flow.server.VaadinServletContext; import com.vaadin.flow.server.VaadinServletRequest; import com.vaadin.flow.server.VaadinServletService; import com.vaadin.flow.server.VaadinSession; +import com.vaadin.flow.server.startup.ApplicationConfiguration; import com.vaadin.flow.uitest.servlet.CustomDeploymentConfiguration.Conf; import elemental.json.JsonValue; @@ -245,6 +247,8 @@ protected DeploymentConfiguration createDeploymentConfiguration( Properties initParameters) { // Get the original configuration from the super class final DeploymentConfiguration originalConfiguration = new DefaultDeploymentConfiguration( + ApplicationConfiguration + .get(new VaadinServletContext(getServletContext())), getClass(), initParameters) { @Override public String getUIClassName() { @@ -399,6 +403,8 @@ private DeploymentConfiguration findDeploymentConfiguration( getApplicationRunnerApplicationClassName( request.get())); configuration = new DefaultDeploymentConfiguration( + ApplicationConfiguration + .get(getService().getContext()), servlet.getClass(), initParameters); } else { configuration = originalConfiguration; diff --git a/flow-tests/test-scalability/src/main/java/com/vaadin/flow/test/scalability/HelloWorldUI.java b/flow-tests/test-scalability/src/main/java/com/vaadin/flow/test/scalability/HelloWorldUI.java index c86a579eb4c..214b4506155 100644 --- a/flow-tests/test-scalability/src/main/java/com/vaadin/flow/test/scalability/HelloWorldUI.java +++ b/flow-tests/test-scalability/src/main/java/com/vaadin/flow/test/scalability/HelloWorldUI.java @@ -1,5 +1,6 @@ package com.vaadin.flow.test.scalability; +import javax.servlet.annotation.WebInitParam; import javax.servlet.annotation.WebServlet; import com.vaadin.flow.component.Text; @@ -7,7 +8,6 @@ import com.vaadin.flow.component.html.NativeButton; import com.vaadin.flow.server.VaadinRequest; import com.vaadin.flow.server.VaadinServlet; -import com.vaadin.flow.server.VaadinServletConfiguration; /* * Copyright 2000-2020 Vaadin Ltd. @@ -29,12 +29,15 @@ public class HelloWorldUI extends UI { public static final String PATH = "/helloworld/"; + public static final String HELLO_WORLD_UI = HelloWorldUI.class.getName(); + /** * The main servlet for the application. */ @WebServlet(urlPatterns = PATH - + "*", name = "UIServlet", asyncSupported = true) - @VaadinServletConfiguration(ui = HelloWorldUI.class, productionMode = false) + + "*", name = "UIServlet", asyncSupported = true, initParams = { + @WebInitParam(name = "ui", value = "com.vaadin.flow.test.scalability.HelloWorldUI"), + @WebInitParam(name = "productionMode", value = "false") }) public static class Servlet extends VaadinServlet { } diff --git a/fusion-endpoint/src/main/java/com/vaadin/flow/server/connect/VaadinConnectController.java b/fusion-endpoint/src/main/java/com/vaadin/flow/server/connect/VaadinConnectController.java index 8cf73b77731..7ef2eb37bd2 100644 --- a/fusion-endpoint/src/main/java/com/vaadin/flow/server/connect/VaadinConnectController.java +++ b/fusion-endpoint/src/main/java/com/vaadin/flow/server/connect/VaadinConnectController.java @@ -16,7 +16,6 @@ package com.vaadin.flow.server.connect; import javax.servlet.ServletContext; -import javax.servlet.ServletRegistration; import javax.servlet.http.HttpServletRequest; import javax.validation.ConstraintViolation; import javax.validation.Validation; @@ -41,14 +40,13 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.googlecode.gentyref.GenericTypeReflector; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -67,17 +65,17 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; -import com.vaadin.flow.function.DeploymentConfiguration; import com.vaadin.flow.internal.CurrentInstance; import com.vaadin.flow.server.VaadinRequest; import com.vaadin.flow.server.VaadinService; +import com.vaadin.flow.server.VaadinServletContext; import com.vaadin.flow.server.VaadinServletRequest; import com.vaadin.flow.server.VaadinServletService; import com.vaadin.flow.server.connect.auth.VaadinConnectAccessChecker; import com.vaadin.flow.server.connect.exception.EndpointException; import com.vaadin.flow.server.connect.exception.EndpointValidationException; import com.vaadin.flow.server.connect.exception.EndpointValidationException.ValidationErrorData; -import com.vaadin.flow.server.startup.ServletDeployer.StubServletConfig; +import com.vaadin.flow.server.startup.ApplicationConfiguration; /** * The controller that is responsible for processing Vaadin endpoint requests. @@ -107,8 +105,7 @@ public class VaadinConnectController { * EndpointNameChecker, ExplicitNullableTypeChecker, * ApplicationContext, ServletContext) */ - public static final String VAADIN_ENDPOINT_MAPPER_BEAN_QUALIFIER = - "vaadinEndpointMapper"; + public static final String VAADIN_ENDPOINT_MAPPER_BEAN_QUALIFIER = "vaadinEndpointMapper"; final Map vaadinEndpoints = new HashMap<>(); @@ -131,44 +128,44 @@ public class VaadinConnectController { * the ACL checker to verify the endpoint method access * permissions * @param endpointNameChecker - * the endpoint name checker to verify custom Vaadin - * endpoint names + * the endpoint name checker to verify custom Vaadin endpoint + * names * @param explicitNullableTypeChecker * the method parameter and return value type checker to verify * that null values are explicit * @param context - * Spring context to extract beans annotated with {@link Endpoint} - * from + * Spring context to extract beans annotated with + * {@link Endpoint} from * @param servletContext * The servlet context for the controller. */ public VaadinConnectController( - @Autowired(required = false) @Qualifier(VAADIN_ENDPOINT_MAPPER_BEAN_QUALIFIER) - ObjectMapper vaadinEndpointMapper, + @Autowired(required = false) @Qualifier(VAADIN_ENDPOINT_MAPPER_BEAN_QUALIFIER) ObjectMapper vaadinEndpointMapper, VaadinConnectAccessChecker accessChecker, EndpointNameChecker endpointNameChecker, ExplicitNullableTypeChecker explicitNullableTypeChecker, - ApplicationContext context, - ServletContext servletContext) { + ApplicationContext context, ServletContext servletContext) { this.vaadinEndpointMapper = vaadinEndpointMapper != null ? vaadinEndpointMapper : createVaadinConnectObjectMapper(context); this.accessChecker = accessChecker; this.explicitNullableTypeChecker = explicitNullableTypeChecker; - context.getBeansWithAnnotation(Endpoint.class).forEach( - (name, endpointBean) -> validateEndpointBean(endpointNameChecker, - name, endpointBean)); - + context.getBeansWithAnnotation(Endpoint.class) + .forEach((name, endpointBean) -> validateEndpointBean( + endpointNameChecker, name, endpointBean)); - DeploymentConfiguration cfg = createDeploymentConfiguration(servletContext); + ApplicationConfiguration cfg = ApplicationConfiguration + .get(new VaadinServletContext(servletContext)); if (cfg != null) { accessChecker.enableCsrf(cfg.isXsrfProtectionEnabled()); } } - private ObjectMapper createVaadinConnectObjectMapper(ApplicationContext context) { - Jackson2ObjectMapperBuilder builder = context.getBean(Jackson2ObjectMapperBuilder.class); + private ObjectMapper createVaadinConnectObjectMapper( + ApplicationContext context) { + Jackson2ObjectMapperBuilder builder = context + .getBean(Jackson2ObjectMapperBuilder.class); ObjectMapper objectMapper = builder.createXmlMapper(false).build(); JacksonProperties jacksonProperties = context .getBean(JacksonProperties.class); @@ -177,17 +174,6 @@ private ObjectMapper createVaadinConnectObjectMapper(ApplicationContext context) JsonAutoDetect.Visibility.ANY); } return objectMapper; - } - - private DeploymentConfiguration createDeploymentConfiguration( - ServletContext ctx) { - if (ctx.getServletRegistrations().isEmpty()) { - return null; - } - ServletRegistration reg = ctx.getServletRegistrations().entrySet() - .iterator().next().getValue(); - return StubServletConfig.createDeploymentConfiguration(ctx, reg, - VaadinConnectController.class); } private static Logger getLogger() { @@ -211,8 +197,8 @@ void validateEndpointBean(EndpointNameChecker endpointNameChecker, name, beanType, Endpoint.class) + String.format( "Either modify the bean declaration so that it is not an " - + "anonymous class or specify an endpoint " + - "name in the '%s' annotation", + + "anonymous class or specify an endpoint " + + "name in the '%s' annotation", Endpoint.class)); } String validationError = endpointNameChecker.check(endpointName); @@ -253,8 +239,7 @@ void validateEndpointBean(EndpointNameChecker endpointNameChecker, * the current request which triggers the endpoint call * @return execution result as a JSON string or an error message string */ - @PostMapping(path = "/{endpoint}/{method}", produces = - MediaType.APPLICATION_JSON_UTF8_VALUE) + @PostMapping(path = "/{endpoint}/{method}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public ResponseEntity serveEndpoint( @PathVariable("endpoint") String endpointName, @PathVariable("method") String methodName, @@ -282,8 +267,10 @@ public ResponseEntity serveEndpoint( // Put a VaadinRequest in the instances object so as the request is // available in the end-point method - VaadinServletService service = (VaadinServletService)VaadinService.getCurrent(); - CurrentInstance.set(VaadinRequest.class, new VaadinServletRequest(request, service)); + VaadinServletService service = (VaadinServletService) VaadinService + .getCurrent(); + CurrentInstance.set(VaadinRequest.class, + new VaadinServletRequest(request, service)); return invokeVaadinEndpointMethod(endpointName, methodName, methodToInvoke, body, vaadinEndpointData, request); @@ -304,14 +291,14 @@ public ResponseEntity serveEndpoint( errorMessage), unexpected); } } finally { - CurrentInstance.set(VaadinRequest.class, null); + CurrentInstance.set(VaadinRequest.class, null); } } - private ResponseEntity invokeVaadinEndpointMethod(String endpointName, - String methodName, Method methodToInvoke, ObjectNode body, - VaadinEndpointData vaadinEndpointData, HttpServletRequest request) - throws JsonProcessingException { + private ResponseEntity invokeVaadinEndpointMethod( + String endpointName, String methodName, Method methodToInvoke, + ObjectNode body, VaadinEndpointData vaadinEndpointData, + HttpServletRequest request) throws JsonProcessingException { String checkError = accessChecker.check(methodToInvoke, request); if (checkError != null) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED) @@ -321,7 +308,8 @@ private ResponseEntity invokeVaadinEndpointMethod(String endpointName, } Map requestParameters = getRequestParameters(body); - Type[] javaParameters = getJavaParameters(methodToInvoke, ClassUtils.getUserClass(vaadinEndpointData.vaadinEndpointObject)); + Type[] javaParameters = getJavaParameters(methodToInvoke, ClassUtils + .getUserClass(vaadinEndpointData.vaadinEndpointObject)); if (javaParameters.length != requestParameters.size()) { return ResponseEntity.badRequest() .body(createResponseErrorObject(String.format( @@ -334,7 +322,8 @@ private ResponseEntity invokeVaadinEndpointMethod(String endpointName, Object[] vaadinEndpointParameters; try { vaadinEndpointParameters = getVaadinEndpointParameters( - requestParameters, javaParameters, methodName, endpointName); + requestParameters, javaParameters, methodName, + endpointName); } catch (EndpointValidationException e) { getLogger().debug( "Endpoint '{}' method '{}' received invalid response", @@ -411,14 +400,15 @@ private ResponseEntity invokeVaadinEndpointMethod(String endpointName, } private Type[] getJavaParameters(Method methodToInvoke, Type classType) { - return Stream.of(GenericTypeReflector.getExactParameterTypes(methodToInvoke, classType)).toArray(Type[]::new); + return Stream.of(GenericTypeReflector + .getExactParameterTypes(methodToInvoke, classType)) + .toArray(Type[]::new); } private ResponseEntity handleMethodExecutionError( String endpointName, String methodName, InvocationTargetException e) throws JsonProcessingException { - if (EndpointException.class - .isAssignableFrom(e.getCause().getClass())) { + if (EndpointException.class.isAssignableFrom(e.getCause().getClass())) { EndpointException endpointException = ((EndpointException) e .getCause()); getLogger().debug("Endpoint '{}' method '{}' aborted the execution", @@ -443,8 +433,8 @@ private String createResponseErrorObject(String errorMessage) } private String listMethodParameterTypes(Type[] javaParameters) { - return Stream.of(javaParameters) - .map(Type::getTypeName).collect(Collectors.joining(", ")); + return Stream.of(javaParameters).map(Type::getTypeName) + .collect(Collectors.joining(", ")); } private Object[] getVaadinEndpointParameters( @@ -509,8 +499,7 @@ private EndpointValidationException getInvalidEndpointParametersException( String message = String.format( "Validation error in endpoint '%s' method '%s'", endpointName, methodName); - return new EndpointValidationException(message, - validationErrorData); + return new EndpointValidationException(message, validationErrorData); } private List createBeanValidationErrors( diff --git a/fusion-endpoint/src/test/java/com/vaadin/flow/server/connect/VaadinConnectControllerTest.java b/fusion-endpoint/src/test/java/com/vaadin/flow/server/connect/VaadinConnectControllerTest.java index 402fe88c007..4648e47666a 100644 --- a/fusion-endpoint/src/test/java/com/vaadin/flow/server/connect/VaadinConnectControllerTest.java +++ b/fusion-endpoint/src/test/java/com/vaadin/flow/server/connect/VaadinConnectControllerTest.java @@ -28,7 +28,6 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.type.SimpleType; import com.fasterxml.jackson.databind.type.TypeFactory; - import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; @@ -51,8 +50,8 @@ import com.vaadin.flow.server.connect.exception.EndpointException; import com.vaadin.flow.server.connect.exception.EndpointValidationException; import com.vaadin.flow.server.connect.generator.endpoints.superclassmethods.PersonEndpoint; -import com.vaadin.flow.server.connect.generator.endpoints.superclassmethods.PersonEndpoint.Person; import com.vaadin.flow.server.connect.testendpoint.BridgeMethodTestEndpoint; +import com.vaadin.flow.server.startup.ApplicationConfiguration; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; @@ -60,12 +59,11 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.CALLS_REAL_METHODS; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.only; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -78,6 +76,7 @@ public class VaadinConnectControllerTest { private static final Method TEST_VALIDATION_METHOD; private HttpServletRequest requestMock; private Principal principal; + private ApplicationConfiguration appConfig; static { TEST_METHOD = Stream.of(TEST_ENDPOINT.getClass().getDeclaredMethods()) @@ -123,7 +122,7 @@ public String testAnonymousMethod() { } @PermitAll - @RolesAllowed({"FOO_ROLE", "BAR_ROLE"}) + @RolesAllowed({ "FOO_ROLE", "BAR_ROLE" }) public String testRoleAllowed() { return "Hello, user in role!"; } @@ -140,7 +139,8 @@ public String anonymousOverrides() { } public String getUserName() { - return VaadinService.getCurrentRequest().getUserPrincipal().getName(); + return VaadinService.getCurrentRequest().getUserPrincipal() + .getName(); } } @@ -179,12 +179,14 @@ public void setUp() { requestMock = mock(HttpServletRequest.class); principal = mock(Principal.class); + appConfig = Mockito.mock(ApplicationConfiguration.class); + when(requestMock.getUserPrincipal()).thenReturn(principal); when(requestMock.getHeader("X-CSRF-Token")).thenReturn("Vaadin CCDM"); HttpSession sessionMock = mock(HttpSession.class); - when(sessionMock.getAttribute(com.vaadin.flow.server.VaadinService.getCsrfTokenAttributeName())) - .thenReturn("Vaadin CCDM"); + when(sessionMock.getAttribute(com.vaadin.flow.server.VaadinService + .getCsrfTokenAttributeName())).thenReturn("Vaadin CCDM"); when(requestMock.getSession(false)).thenReturn(sessionMock); } @@ -203,8 +205,7 @@ public void should_ThrowException_When_NoEndpointNameCanBeReceived() { @Test public void should_ThrowException_When_IncorrectEndpointNameProvided() { - TestClassWithIllegalEndpointName endpointWithIllegalName = - new TestClassWithIllegalEndpointName(); + TestClassWithIllegalEndpointName endpointWithIllegalName = new TestClassWithIllegalEndpointName(); String incorrectName = endpointWithIllegalName.getClass() .getAnnotation(Endpoint.class).value(); EndpointNameChecker nameChecker = new EndpointNameChecker(); @@ -215,8 +216,8 @@ public void should_ThrowException_When_IncorrectEndpointNameProvided() { exception.expectMessage(incorrectName); exception.expectMessage(expectedCheckerMessage); - createVaadinController(endpointWithIllegalName, mock(ObjectMapper.class), - null, nameChecker, null); + createVaadinController(endpointWithIllegalName, + mock(ObjectMapper.class), null, nameChecker, null); } @Test @@ -236,8 +237,8 @@ public void should_Return404_When_MethodNotFound() { assertNotEquals(TEST_METHOD.getName(), missingEndpointMethod); ResponseEntity response = createVaadinController(TEST_ENDPOINT) - .serveEndpoint(TEST_ENDPOINT_NAME, missingEndpointMethod, - null, requestMock); + .serveEndpoint(TEST_ENDPOINT_NAME, missingEndpointMethod, null, + requestMock); assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); assertNull(response.getBody()); @@ -252,8 +253,7 @@ public void should_Return404_When_IllegalAccessToMethodIsPerformed() { when(restrictingCheckerMock.check(TEST_METHOD, requestMock)) .thenReturn(accessErrorMessage); - EndpointNameChecker nameCheckerMock = mock( - EndpointNameChecker.class); + EndpointNameChecker nameCheckerMock = mock(EndpointNameChecker.class); when(nameCheckerMock.check(TEST_ENDPOINT_NAME)).thenReturn(null); ExplicitNullableTypeChecker explicitNullableTypeCheckerMock = mock( @@ -261,9 +261,9 @@ public void should_Return404_When_IllegalAccessToMethodIsPerformed() { ResponseEntity response = createVaadinController(TEST_ENDPOINT, new ObjectMapper(), restrictingCheckerMock, nameCheckerMock, - explicitNullableTypeCheckerMock) - .serveEndpoint(TEST_ENDPOINT_NAME, - TEST_METHOD.getName(), null, requestMock); + explicitNullableTypeCheckerMock).serveEndpoint( + TEST_ENDPOINT_NAME, TEST_METHOD.getName(), null, + requestMock); assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); String responseBody = response.getBody(); @@ -271,15 +271,18 @@ public void should_Return404_When_IllegalAccessToMethodIsPerformed() { assertTrue(String.format("Invalid response body: '%s'", responseBody), responseBody.contains(accessErrorMessage)); - verify(restrictingCheckerMock, only()).check(TEST_METHOD, requestMock); - verify(restrictingCheckerMock, times(1)).check(TEST_METHOD, requestMock); + verify(restrictingCheckerMock).enableCsrf(Mockito.anyBoolean()); + verify(restrictingCheckerMock).check(TEST_METHOD, requestMock); + Mockito.verifyNoMoreInteractions(restrictingCheckerMock); + verify(restrictingCheckerMock, times(1)).check(TEST_METHOD, + requestMock); } @Test public void should_Return400_When_LessParametersSpecified1() { ResponseEntity response = createVaadinController(TEST_ENDPOINT) - .serveEndpoint(TEST_ENDPOINT_NAME, TEST_METHOD.getName(), - null, requestMock); + .serveEndpoint(TEST_ENDPOINT_NAME, TEST_METHOD.getName(), null, + requestMock); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); String responseBody = response.getBody(); @@ -296,7 +299,8 @@ public void should_Return400_When_MoreParametersSpecified() { ResponseEntity response = createVaadinController(TEST_ENDPOINT) .serveEndpoint(TEST_ENDPOINT_NAME, TEST_METHOD.getName(), createRequestParameters( - "{\"value1\": 222, \"value2\": 333}"), requestMock); + "{\"value1\": 222, \"value2\": 333}"), + requestMock); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); String responseBody = response.getBody(); @@ -312,7 +316,8 @@ public void should_Return400_When_MoreParametersSpecified() { public void should_Return400_When_IncorrectParameterTypesAreProvided() { ResponseEntity response = createVaadinController(TEST_ENDPOINT) .serveEndpoint(TEST_ENDPOINT_NAME, TEST_METHOD.getName(), - createRequestParameters("{\"value\": [222]}"), requestMock); + createRequestParameters("{\"value\": [222]}"), + requestMock); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); String responseBody = response.getBody(); @@ -351,6 +356,7 @@ public void should_CallMethodAnonymously_When_UserPrincipalIsNullAndAnonymousAll @Test public void should_NotCallMethod_When_a_CSRF_request() { + when(appConfig.isXsrfProtectionEnabled()).thenReturn(true); when(requestMock.getHeader("X-CSRF-Token")).thenReturn(null); VaadinConnectController vaadinController = createVaadinControllerWithoutPrincipal(); @@ -375,7 +381,8 @@ public void should_NotCallMethodAnonymously_When_UserPrincipalIsNotInRole() { createRequestParameters("{}"), requestMock); assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); - assertTrue(response.getBody().contains("Unauthorized access to Vaadin endpoint")); + assertTrue(response.getBody() + .contains("Unauthorized access to Vaadin endpoint")); } @Test @@ -411,11 +418,12 @@ public void should_CallMethodAnonymously_When_AnonymousOverridesRoles() { public void should_NotCallMethod_When_DenyAll() { VaadinConnectController vaadinController = createVaadinControllerWithoutPrincipal(); ResponseEntity response = vaadinController.serveEndpoint( - TEST_ENDPOINT_NAME, "denyAll", - createRequestParameters("{}"), requestMock); + TEST_ENDPOINT_NAME, "denyAll", createRequestParameters("{}"), + requestMock); assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); - assertTrue(response.getBody().contains("Anonymous access is not allowed")); + assertTrue( + response.getBody().contains("Anonymous access is not allowed")); } @Test @@ -437,10 +445,9 @@ public void should_clearVaadinRequestInsntace_after_EndpointCall() { VaadinConnectController vaadinController = createVaadinController( TEST_ENDPOINT, new VaadinConnectAccessChecker()); - vaadinController.serveEndpoint( - TEST_ENDPOINT_NAME, "getUserName", + vaadinController.serveEndpoint(TEST_ENDPOINT_NAME, "getUserName", createRequestParameters("{}"), requestMock); - + Assert.assertNull(CurrentInstance.get(VaadinRequest.class)); Assert.assertNull(VaadinRequest.getCurrent()); } @@ -451,8 +458,8 @@ public void should_Return400_When_EndpointMethodThrowsIllegalArgumentException() throws Exception { int inputValue = 222; - Method endpointMethodMock = createEndpointMethodMockThatThrows(inputValue, - new IllegalArgumentException("OOPS")); + Method endpointMethodMock = createEndpointMethodMockThatThrows( + inputValue, new IllegalArgumentException("OOPS")); VaadinConnectController controller = createVaadinController( TEST_ENDPOINT); @@ -462,7 +469,8 @@ public void should_Return400_When_EndpointMethodThrowsIllegalArgumentException() ResponseEntity response = controller.serveEndpoint( TEST_ENDPOINT_NAME, TEST_METHOD.getName(), createRequestParameters( - String.format("{\"value\": %s}", inputValue)), requestMock); + String.format("{\"value\": %s}", inputValue)), + requestMock); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); String responseBody = response.getBody(); @@ -481,8 +489,8 @@ public void should_Return500_When_EndpointMethodThrowsIllegalAccessException() throws Exception { int inputValue = 222; - Method endpointMethodMock = createEndpointMethodMockThatThrows(inputValue, - new IllegalAccessException("OOPS")); + Method endpointMethodMock = createEndpointMethodMockThatThrows( + inputValue, new IllegalAccessException("OOPS")); VaadinConnectController controller = createVaadinController( TEST_ENDPOINT); @@ -492,7 +500,8 @@ public void should_Return500_When_EndpointMethodThrowsIllegalAccessException() ResponseEntity response = controller.serveEndpoint( TEST_ENDPOINT_NAME, TEST_METHOD.getName(), createRequestParameters( - String.format("{\"value\": %s}", inputValue)), requestMock); + String.format("{\"value\": %s}", inputValue)), + requestMock); assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); @@ -511,8 +520,8 @@ public void should_Return500_When_EndpointMethodThrowsInvocationTargetException( throws Exception { int inputValue = 222; - Method endpointMethodMock = createEndpointMethodMockThatThrows(inputValue, - new InvocationTargetException( + Method endpointMethodMock = createEndpointMethodMockThatThrows( + inputValue, new InvocationTargetException( new IllegalStateException("OOPS"))); VaadinConnectController controller = createVaadinController( @@ -523,7 +532,8 @@ public void should_Return500_When_EndpointMethodThrowsInvocationTargetException( ResponseEntity response = controller.serveEndpoint( TEST_ENDPOINT_NAME, TEST_METHOD.getName(), createRequestParameters( - String.format("{\"value\": %s}", inputValue)), requestMock); + String.format("{\"value\": %s}", inputValue)), + requestMock); assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); @@ -543,8 +553,8 @@ public void should_Return400_When_EndpointMethodThrowsVaadinConnectException() int inputValue = 222; String expectedMessage = "OOPS"; - Method endpointMethodMock = createEndpointMethodMockThatThrows(inputValue, - new InvocationTargetException( + Method endpointMethodMock = createEndpointMethodMockThatThrows( + inputValue, new InvocationTargetException( new EndpointException(expectedMessage))); VaadinConnectController controller = createVaadinController( @@ -555,7 +565,8 @@ public void should_Return400_When_EndpointMethodThrowsVaadinConnectException() ResponseEntity response = controller.serveEndpoint( TEST_ENDPOINT_NAME, TEST_METHOD.getName(), createRequestParameters( - String.format("{\"value\": %s}", inputValue)), requestMock); + String.format("{\"value\": %s}", inputValue)), + requestMock); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); String responseBody = response.getBody(); @@ -581,7 +592,8 @@ public MyCustomException() { } } - Method endpointMethodMock = createEndpointMethodMockThatThrows(inputValue, + Method endpointMethodMock = createEndpointMethodMockThatThrows( + inputValue, new InvocationTargetException(new MyCustomException())); VaadinConnectController controller = createVaadinController( @@ -592,7 +604,8 @@ public MyCustomException() { ResponseEntity response = controller.serveEndpoint( TEST_ENDPOINT_NAME, TEST_METHOD.getName(), createRequestParameters( - String.format("{\"value\": %s}", inputValue)), requestMock); + String.format("{\"value\": %s}", inputValue)), + requestMock); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); String responseBody = response.getBody(); @@ -627,7 +640,8 @@ public void should_Return500_When_MapperFailsToSerializeResponse() ResponseEntity response = createVaadinController(TEST_ENDPOINT, mapperMock).serveEndpoint(TEST_ENDPOINT_NAME, TEST_METHOD.getName(), - createRequestParameters("{\"value\": 222}"), requestMock); + createRequestParameters("{\"value\": 222}"), + requestMock); assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); @@ -676,7 +690,8 @@ public void should_ReturnCorrectResponse_When_EverythingIsCorrect() { ResponseEntity response = createVaadinController(TEST_ENDPOINT) .serveEndpoint(TEST_ENDPOINT_NAME, TEST_METHOD.getName(), createRequestParameters( - String.format("{\"value\": %s}", inputValue)), requestMock); + String.format("{\"value\": %s}", inputValue)), + requestMock); assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(String.format("\"%s\"", expectedOutput), @@ -689,25 +704,24 @@ public void should_ReturnCorrectResponse_When_EndpointClassIsProxied() { ApplicationContext contextMock = mock(ApplicationContext.class); TestClass endpoint = new TestClass(); TestClass proxy = mock(TestClass.class, CALLS_REAL_METHODS); - when(contextMock.getBeansWithAnnotation(Endpoint.class)) - .thenReturn(Collections.singletonMap( - endpoint.getClass().getSimpleName(), proxy)); + when(contextMock.getBeansWithAnnotation(Endpoint.class)).thenReturn( + Collections.singletonMap(endpoint.getClass().getSimpleName(), + proxy)); VaadinConnectController vaadinConnectController = new VaadinConnectController( new ObjectMapper(), mock(VaadinConnectAccessChecker.class), mock(EndpointNameChecker.class), - mock(ExplicitNullableTypeChecker.class), - contextMock, - mock(ServletContext.class)); + mock(ExplicitNullableTypeChecker.class), contextMock, + mockServletContext()); int inputValue = 222; String expectedOutput = endpoint.testMethod(inputValue); - ResponseEntity response = vaadinConnectController - .serveEndpoint("TestClass", "testMethod", - createRequestParameters( - String.format("{\"value\": %s}", inputValue)), - requestMock); + ResponseEntity response = vaadinConnectController.serveEndpoint( + "TestClass", "testMethod", + createRequestParameters( + String.format("{\"value\": %s}", inputValue)), + requestMock); assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(String.format("\"%s\"", expectedOutput), @@ -722,8 +736,10 @@ public void should_NotUseBridgeMethod_When_EndpointHasBridgeMethodFromInterface( String testMethodName = "testMethodFromInterface"; ResponseEntity response = createVaadinController(testEndpoint) .serveEndpoint(testEndpoint.getClass().getSimpleName(), - testMethodName, createRequestParameters(String.format( - "{\"value\": {\"id\": \"%s\"}}", inputId)), requestMock); + testMethodName, + createRequestParameters(String.format( + "{\"value\": {\"id\": \"%s\"}}", inputId)), + requestMock); assertEquals(expectedResult, response.getBody()); } @@ -735,8 +751,10 @@ public void should_NotUseBridgeMethod_When_EndpointHasBridgeMethodFromParentClas ResponseEntity response = createVaadinController(testEndpoint) .serveEndpoint(testEndpoint.getClass().getSimpleName(), - testMethodName, createRequestParameters( - String.format("{\"value\": %s}", inputId)), requestMock); + testMethodName, + createRequestParameters( + String.format("{\"value\": %s}", inputId)), + requestMock); assertEquals(inputId, response.getBody()); } @@ -748,8 +766,10 @@ public void should_ReturnCorrectResponse_When_CallingNormalOverriddenMethod() { ResponseEntity response = createVaadinController(testEndpoint) .serveEndpoint(testEndpoint.getClass().getSimpleName(), - testMethodName, createRequestParameters( - String.format("{\"value\": %s}", inputId)), requestMock); + testMethodName, + createRequestParameters( + String.format("{\"value\": %s}", inputId)), + requestMock); assertEquals(inputId, response.getBody()); } @@ -768,13 +788,13 @@ public void should_UseCustomEndpointName_When_ItIsDefined() { VaadinConnectController vaadinConnectController = new VaadinConnectController( new ObjectMapper(), mock(VaadinConnectAccessChecker.class), mock(EndpointNameChecker.class), - mock(ExplicitNullableTypeChecker.class), - contextMock, - mock(ServletContext.class)); + mock(ExplicitNullableTypeChecker.class), contextMock, + mockServletContext()); ResponseEntity response = vaadinConnectController .serveEndpoint("CustomEndpoint", "testMethod", createRequestParameters( - String.format("{\"value\": %s}", input)), requestMock); + String.format("{\"value\": %s}", input)), + requestMock); assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals(String.format("\"%s\"", expectedOutput), response.getBody()); @@ -787,16 +807,15 @@ public void should_UseCustomEndpointName_When_EndpointClassIsProxied() { TestClassWithCustomEndpointName endpoint = new TestClassWithCustomEndpointName(); TestClassWithCustomEndpointName proxy = mock( TestClassWithCustomEndpointName.class, CALLS_REAL_METHODS); - when(contextMock.getBeansWithAnnotation(Endpoint.class)) - .thenReturn(Collections.singletonMap( - endpoint.getClass().getSimpleName(), proxy)); + when(contextMock.getBeansWithAnnotation(Endpoint.class)).thenReturn( + Collections.singletonMap(endpoint.getClass().getSimpleName(), + proxy)); VaadinConnectController vaadinConnectController = new VaadinConnectController( new ObjectMapper(), mock(VaadinConnectAccessChecker.class), mock(EndpointNameChecker.class), - mock(ExplicitNullableTypeChecker.class), - contextMock, - mock(ServletContext.class)); + mock(ExplicitNullableTypeChecker.class), contextMock, + mockServletContext()); int input = 111; String expectedOutput = endpoint.testMethod(input); @@ -816,7 +835,8 @@ public void should_Never_UseSpringObjectMapper() { ApplicationContext contextMock = mock(ApplicationContext.class); ObjectMapper mockSpringObjectMapper = mock(ObjectMapper.class); ObjectMapper mockOwnObjectMapper = mock(ObjectMapper.class); - Jackson2ObjectMapperBuilder mockObjectMapperBuilder = mock(Jackson2ObjectMapperBuilder.class); + Jackson2ObjectMapperBuilder mockObjectMapperBuilder = mock( + Jackson2ObjectMapperBuilder.class); JacksonProperties mockJacksonProperties = mock(JacksonProperties.class); when(contextMock.getBean(ObjectMapper.class)) .thenReturn(mockSpringObjectMapper); @@ -826,19 +846,18 @@ public void should_Never_UseSpringObjectMapper() { .thenReturn(mockObjectMapperBuilder); when(mockObjectMapperBuilder.createXmlMapper(false)) .thenReturn(mockObjectMapperBuilder); - when(mockObjectMapperBuilder.build()) - .thenReturn(mockOwnObjectMapper); + when(mockObjectMapperBuilder.build()).thenReturn(mockOwnObjectMapper); when(mockJacksonProperties.getVisibility()) .thenReturn(Collections.emptyMap()); new VaadinConnectController(null, mock(VaadinConnectAccessChecker.class), mock(EndpointNameChecker.class), - mock(ExplicitNullableTypeChecker.class), - contextMock, - mock(ServletContext.class)); + mock(ExplicitNullableTypeChecker.class), contextMock, + mockServletContext()); verify(contextMock, never()).getBean(ObjectMapper.class); - verify(contextMock, times(1)).getBean(Jackson2ObjectMapperBuilder.class); + verify(contextMock, times(1)) + .getBean(Jackson2ObjectMapperBuilder.class); verify(contextMock, times(1)).getBean(JacksonProperties.class); verify(mockOwnObjectMapper, times(1)).setVisibility( PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); @@ -849,7 +868,8 @@ public void should_NotOverrideVisibility_When_JacksonPropertiesProvideVisibility ApplicationContext contextMock = mock(ApplicationContext.class); ObjectMapper mockDefaultObjectMapper = mock(ObjectMapper.class); ObjectMapper mockOwnObjectMapper = mock(ObjectMapper.class); - Jackson2ObjectMapperBuilder mockObjectMapperBuilder = mock(Jackson2ObjectMapperBuilder.class); + Jackson2ObjectMapperBuilder mockObjectMapperBuilder = mock( + Jackson2ObjectMapperBuilder.class); JacksonProperties mockJacksonProperties = mock(JacksonProperties.class); when(contextMock.getBean(ObjectMapper.class)) .thenReturn(mockDefaultObjectMapper); @@ -859,24 +879,23 @@ public void should_NotOverrideVisibility_When_JacksonPropertiesProvideVisibility .thenReturn(mockObjectMapperBuilder); when(mockObjectMapperBuilder.createXmlMapper(false)) .thenReturn(mockObjectMapperBuilder); - when(mockObjectMapperBuilder.build()) - .thenReturn(mockOwnObjectMapper); + when(mockObjectMapperBuilder.build()).thenReturn(mockOwnObjectMapper); when(mockJacksonProperties.getVisibility()) .thenReturn(Collections.singletonMap(PropertyAccessor.ALL, JsonAutoDetect.Visibility.PUBLIC_ONLY)); new VaadinConnectController(null, mock(VaadinConnectAccessChecker.class), mock(EndpointNameChecker.class), - mock(ExplicitNullableTypeChecker.class), - contextMock, - mock(ServletContext.class)); + mock(ExplicitNullableTypeChecker.class), contextMock, + mockServletContext()); verify(contextMock, never()).getBean(ObjectMapper.class); - verify(contextMock, times(1)).getBean(Jackson2ObjectMapperBuilder.class); + verify(contextMock, times(1)) + .getBean(Jackson2ObjectMapperBuilder.class); verify(mockDefaultObjectMapper, never()).setVisibility( PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); - verify(mockOwnObjectMapper, never()).setVisibility( - PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + verify(mockOwnObjectMapper, never()).setVisibility(PropertyAccessor.ALL, + JsonAutoDetect.Visibility.ANY); verify(contextMock, times(1)).getBean(JacksonProperties.class); } @@ -890,7 +909,8 @@ public void should_ReturnValidationError_When_DeserializationFails() ResponseEntity response = createVaadinController(TEST_ENDPOINT) .serveEndpoint(TEST_ENDPOINT_NAME, TEST_METHOD.getName(), createRequestParameters( - String.format("{\"value\": %s}", inputValue)), requestMock); + String.format("{\"value\": %s}", inputValue)), + requestMock); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); ObjectNode jsonNodes = new ObjectMapper().readValue(response.getBody(), @@ -950,7 +970,8 @@ public void should_ReturnValidationError_When_EndpointMethodParameterIsInvalid() ResponseEntity response = createVaadinController(TEST_ENDPOINT) .serveEndpoint(TEST_ENDPOINT_NAME, TEST_VALIDATION_METHOD.getName(), - createRequestParameters("{\"parameter\": null}"), requestMock); + createRequestParameters("{\"parameter\": null}"), + requestMock); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); ObjectNode jsonNodes = new ObjectMapper().readValue(response.getBody(), @@ -988,7 +1009,8 @@ public void should_ReturnValidationError_When_EndpointMethodBeanIsInvalid() TEST_VALIDATION_METHOD.getName(), createRequestParameters(String.format( "{\"parameter\": {\"count\": %d}}", - invalidPropertyValue)), requestMock); + invalidPropertyValue)), + requestMock); assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); ObjectNode jsonNodes = new ObjectMapper().readValue(response.getBody(), @@ -1019,8 +1041,7 @@ public void should_Invoke_ExplicitNullableTypeChecker() ExplicitNullableTypeChecker.class); when(explicitNullableTypeChecker.checkValueForType( - eq(NullCheckerTestClass.OK_RESPONSE), - eq(String.class))) + eq(NullCheckerTestClass.OK_RESPONSE), eq(String.class))) .thenReturn(null); String testOkMethod = "testOkMethod"; @@ -1051,20 +1072,20 @@ public void should_ReturnException_When_ExplicitNullableTypeChecker_ReturnsError Method testNullMethod = NullCheckerTestClass.class .getMethod(testNullMethodName); when(explicitNullableTypeChecker.checkValueForAnnotatedElement(null, - testNullMethod)) - .thenReturn(errorMessage); + testNullMethod)).thenReturn(errorMessage); ResponseEntity response = createVaadinController( new NullCheckerTestClass(), null, null, null, explicitNullableTypeChecker).serveEndpoint( NullCheckerTestClass.class.getSimpleName(), - testNullMethodName, createRequestParameters("{}"), + testNullMethodName, createRequestParameters("{}"), requestMock); verify(explicitNullableTypeChecker).checkValueForAnnotatedElement(null, testNullMethod); - assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, + response.getStatusCode()); ObjectNode jsonNodes = new ObjectMapper().readValue(response.getBody(), ObjectNode.class); @@ -1072,31 +1093,41 @@ testNullMethodName, createRequestParameters("{}"), jsonNodes.get("type").asText()); final String message = jsonNodes.get("message").asText(); assertTrue(message.contains("Unexpected return value")); - assertTrue(message.contains(NullCheckerTestClass.class.getSimpleName())); + assertTrue( + message.contains(NullCheckerTestClass.class.getSimpleName())); assertTrue(message.contains(testNullMethodName)); assertTrue(message.contains(errorMessage)); } @Test public void should_ReturnResult_When_CallingSuperClassMethodWithGenericTypedParameter() { - ResponseEntity response = createVaadinController(new PersonEndpoint()) - .serveEndpoint(PersonEndpoint.class.getSimpleName(), "update", - createRequestParameters( - "{\"entity\":{\"name\":\"aa\"}}"), requestMock); + ResponseEntity response = createVaadinController( + new PersonEndpoint()) + .serveEndpoint(PersonEndpoint.class.getSimpleName(), + "update", + createRequestParameters( + "{\"entity\":{\"name\":\"aa\"}}"), + requestMock); assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals("{\"name\":\"aa\"}", response.getBody()); } @Test - public void should_AllowAccessToPackagePrivateEndpoint_PublicMethods() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { - Class packagePrivateEndpoint = Class.forName("com.vaadin.flow.server.connect.generator.endpoints.packageprivate.PackagePrivateEndpoint"); - Constructor packagePrivateEndpointConstructor = packagePrivateEndpoint.getConstructor(); + public void should_AllowAccessToPackagePrivateEndpoint_PublicMethods() + throws ClassNotFoundException, NoSuchMethodException, + IllegalAccessException, InvocationTargetException, + InstantiationException { + Class packagePrivateEndpoint = Class.forName( + "com.vaadin.flow.server.connect.generator.endpoints.packageprivate.PackagePrivateEndpoint"); + Constructor packagePrivateEndpointConstructor = packagePrivateEndpoint + .getConstructor(); packagePrivateEndpointConstructor.setAccessible(true); - ResponseEntity response = createVaadinController(packagePrivateEndpointConstructor.newInstance()) - .serveEndpoint("PackagePrivateEndpoint", "getRequest", - createRequestParameters("{}"), requestMock); + ResponseEntity response = createVaadinController( + packagePrivateEndpointConstructor.newInstance()).serveEndpoint( + "PackagePrivateEndpoint", "getRequest", + createRequestParameters("{}"), requestMock); assertEquals(HttpStatus.OK, response.getStatusCode()); assertEquals("\"Hello\"", response.getBody()); @@ -1126,12 +1157,14 @@ private VaadinConnectController createVaadinController(T endpoint) { private VaadinConnectController createVaadinController(T endpoint, ObjectMapper vaadinEndpointMapper) { - return createVaadinController(endpoint, vaadinEndpointMapper, null, null, null); + return createVaadinController(endpoint, vaadinEndpointMapper, null, + null, null); } private VaadinConnectController createVaadinController(T endpoint, VaadinConnectAccessChecker accessChecker) { - return createVaadinController(endpoint, null, accessChecker, null, null); + return createVaadinController(endpoint, null, accessChecker, null, + null); } private VaadinConnectController createVaadinController(T endpoint, @@ -1142,23 +1175,23 @@ private VaadinConnectController createVaadinController(T endpoint, Class endpointClass = endpoint.getClass(); ApplicationContext contextMock = mock(ApplicationContext.class); - when(contextMock.getBeansWithAnnotation(Endpoint.class)) - .thenReturn(Collections.singletonMap(endpointClass.getName(), - endpoint)); + when(contextMock.getBeansWithAnnotation(Endpoint.class)).thenReturn( + Collections.singletonMap(endpointClass.getName(), endpoint)); if (vaadinEndpointMapper == null) { vaadinEndpointMapper = new ObjectMapper(); } if (accessChecker == null) { - accessChecker = mock( - VaadinConnectAccessChecker.class); - when(accessChecker.check(TEST_METHOD, requestMock)).thenReturn(null); + accessChecker = mock(VaadinConnectAccessChecker.class); + when(accessChecker.check(TEST_METHOD, requestMock)) + .thenReturn(null); } if (endpointNameChecker == null) { endpointNameChecker = mock(EndpointNameChecker.class); - when(endpointNameChecker.check(TEST_ENDPOINT_NAME)).thenReturn(null); + when(endpointNameChecker.check(TEST_ENDPOINT_NAME)) + .thenReturn(null); } if (explicitNullableTypeChecker == null) { @@ -1168,15 +1201,17 @@ private VaadinConnectController createVaadinController(T endpoint, .thenReturn(null); } + ServletContext servletContext = mockServletContext(); + return new VaadinConnectController(vaadinEndpointMapper, accessChecker, - endpointNameChecker, explicitNullableTypeChecker, - contextMock, - mock(ServletContext.class)); + endpointNameChecker, explicitNullableTypeChecker, contextMock, + servletContext); } private VaadinConnectController createVaadinControllerWithoutPrincipal() { when(requestMock.getUserPrincipal()).thenReturn(null); - return createVaadinController(TEST_ENDPOINT, new VaadinConnectAccessChecker()); + return createVaadinController(TEST_ENDPOINT, + new VaadinConnectAccessChecker()); } private Method createEndpointMethodMockThatThrows(Object argument, @@ -1193,4 +1228,12 @@ private Method createEndpointMethodMockThatThrows(Object argument, when(endpointMethodMock.getName()).thenReturn(TEST_METHOD.getName()); return endpointMethodMock; } + + private ServletContext mockServletContext() { + ServletContext context = Mockito.mock(ServletContext.class); + Mockito.when( + context.getAttribute(ApplicationConfiguration.class.getName())) + .thenReturn(appConfig); + return context; + } } diff --git a/fusion-endpoint/src/test/java/com/vaadin/flow/server/connect/rest/EndpointWithRestControllerTest.java b/fusion-endpoint/src/test/java/com/vaadin/flow/server/connect/rest/EndpointWithRestControllerTest.java index 976f6ad6984..e6cb05270be 100644 --- a/fusion-endpoint/src/test/java/com/vaadin/flow/server/connect/rest/EndpointWithRestControllerTest.java +++ b/fusion-endpoint/src/test/java/com/vaadin/flow/server/connect/rest/EndpointWithRestControllerTest.java @@ -22,6 +22,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.context.ApplicationContext; @@ -37,6 +38,7 @@ import com.vaadin.flow.server.connect.ExplicitNullableTypeChecker; import com.vaadin.flow.server.connect.VaadinConnectController; import com.vaadin.flow.server.connect.auth.VaadinConnectAccessChecker; +import com.vaadin.flow.server.startup.ApplicationConfiguration; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; @@ -48,7 +50,7 @@ @RunWith(SpringRunner.class) @WebMvcTest -@Import({VaadinConnectEndpoints.class, MyRestController.class}) +@Import({ VaadinConnectEndpoints.class, MyRestController.class }) public class EndpointWithRestControllerTest { private MockMvc mockMvcForEndpoint; @@ -56,71 +58,81 @@ public class EndpointWithRestControllerTest { @Autowired private MockMvc mockMvcForRest; - @Autowired private ApplicationContext applicationContext; + private ApplicationConfiguration appConfig; + @Before public void setUp() { - mockMvcForEndpoint = MockMvcBuilders.standaloneSetup(new VaadinConnectController( - null, mock(VaadinConnectAccessChecker.class), - mock(EndpointNameChecker.class), - mock(ExplicitNullableTypeChecker.class), - applicationContext, - mock(ServletContext.class))) + appConfig = Mockito.mock(ApplicationConfiguration.class); + + mockMvcForEndpoint = MockMvcBuilders + .standaloneSetup(new VaadinConnectController(null, + mock(VaadinConnectAccessChecker.class), + mock(EndpointNameChecker.class), + mock(ExplicitNullableTypeChecker.class), + applicationContext, mockServletContext())) .build(); Assert.assertNotEquals(null, applicationContext); } @Test - //https://github.com/vaadin/flow/issues/8010 + // https://github.com/vaadin/flow/issues/8010 public void shouldNotExposePrivateAndProtectedFields_when_CallingFromRestAPIs() - throws Exception { - String result = mockMvcForRest.perform(get("/api/get") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andReturn().getResponse().getContentAsString(); + throws Exception { + String result = mockMvcForRest + .perform( + get("/api/get").contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andReturn().getResponse() + .getContentAsString(); assertEquals("{\"name\":\"Bond\"}", result); } @Test - //https://github.com/vaadin/flow/issues/8034 + // https://github.com/vaadin/flow/issues/8034 public void should_BeAbleToSerializePrivateFieldsOfABean_when_CallingFromConnectEndPoint() { try { String result = callEndpointMethod("getBeanWithPrivateFields"); - assertEquals("{\"codeNumber\":\"007\",\"name\":\"Bond\",\"firstName\":\"James\"}", result); + assertEquals( + "{\"codeNumber\":\"007\",\"name\":\"Bond\",\"firstName\":\"James\"}", + result); } catch (Exception e) { fail("failed to serialize a bean with private fields"); } } @Test - //https://github.com/vaadin/flow/issues/8034 + // https://github.com/vaadin/flow/issues/8034 public void should_BeAbleToSerializeABeanWithZonedDateTimeField() { try { String result = callEndpointMethod("getBeanWithZonedDateTimeField"); assertNotNull(result); assertNotEquals("", result); - assertNotEquals("{\"message\":\"Failed to serialize endpoint 'VaadinConnectTypeConversionEndpoints' method 'getBeanWithZonedDateTimeField' response. Double check method's return type or specify a custom mapper bean with qualifier 'vaadinEndpointMapper'\"}", result); + assertNotEquals( + "{\"message\":\"Failed to serialize endpoint 'VaadinConnectTypeConversionEndpoints' method 'getBeanWithZonedDateTimeField' response. Double check method's return type or specify a custom mapper bean with qualifier 'vaadinEndpointMapper'\"}", + result); } catch (Exception e) { fail("failed to serialize a bean with ZonedDateTime field"); } } @Test - //https://github.com/vaadin/flow/issues/8067 - public void should_RepsectJacksonAnnotation_when_serializeBean() throws Exception { + // https://github.com/vaadin/flow/issues/8067 + public void should_RepsectJacksonAnnotation_when_serializeBean() + throws Exception { String result = callEndpointMethod("getBeanWithJacksonAnnotation"); assertEquals("{\"name\":null,\"rating\":2,\"bookId\":null}", result); } @Test /** - * this requires jackson-datatype-jsr310, which is added as a test scope dependency. - * jackson-datatype-jsr310 is provided in spring-boot-starter-web, which is part of - * vaadin-spring-boot-starter + * this requires jackson-datatype-jsr310, which is added as a test scope + * dependency. jackson-datatype-jsr310 is provided in + * spring-boot-starter-web, which is part of vaadin-spring-boot-starter */ - public void should_serializeLocalTimeInExpectedFormat_when_UsingSpringBoot() throws Exception{ + public void should_serializeLocalTimeInExpectedFormat_when_UsingSpringBoot() + throws Exception { String result = callEndpointMethod("getLocalTime"); assertEquals("\"08:00:00\"", result); } @@ -132,7 +144,15 @@ private String callEndpointMethod(String methodName) throws Exception { .accept(MediaType.APPLICATION_JSON_UTF8_VALUE) .contentType(MediaType.APPLICATION_JSON_UTF8_VALUE); - return mockMvcForEndpoint.perform(requestBuilder).andReturn().getResponse().getContentAsString(); + return mockMvcForEndpoint.perform(requestBuilder).andReturn() + .getResponse().getContentAsString(); } -} + private ServletContext mockServletContext() { + ServletContext context = Mockito.mock(ServletContext.class); + Mockito.when( + context.getAttribute(ApplicationConfiguration.class.getName())) + .thenReturn(appConfig); + return context; + } +} diff --git a/fusion-endpoint/src/test/java/com/vaadin/flow/server/connect/typeconversion/BaseTypeConversionTest.java b/fusion-endpoint/src/test/java/com/vaadin/flow/server/connect/typeconversion/BaseTypeConversionTest.java index 388d56426cc..309a08e86be 100644 --- a/fusion-endpoint/src/test/java/com/vaadin/flow/server/connect/typeconversion/BaseTypeConversionTest.java +++ b/fusion-endpoint/src/test/java/com/vaadin/flow/server/connect/typeconversion/BaseTypeConversionTest.java @@ -20,6 +20,7 @@ import org.junit.Assert; import org.junit.Before; import org.junit.runner.RunWith; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.context.ApplicationContext; @@ -32,10 +33,11 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import com.vaadin.flow.server.connect.ExplicitNullableTypeChecker; import com.vaadin.flow.server.connect.EndpointNameChecker; +import com.vaadin.flow.server.connect.ExplicitNullableTypeChecker; import com.vaadin.flow.server.connect.VaadinConnectController; import com.vaadin.flow.server.connect.auth.VaadinConnectAccessChecker; +import com.vaadin.flow.server.startup.ApplicationConfiguration; import static org.mockito.Mockito.mock; @@ -49,14 +51,18 @@ public abstract class BaseTypeConversionTest { @Autowired private ApplicationContext applicationContext; + private ApplicationConfiguration appConfig; + @Before public void setUp() { - mockMvc = MockMvcBuilders.standaloneSetup(new VaadinConnectController( - null, mock(VaadinConnectAccessChecker.class), - mock(EndpointNameChecker.class), - mock(ExplicitNullableTypeChecker.class), - applicationContext, - mock(ServletContext.class))) + appConfig = Mockito.mock(ApplicationConfiguration.class); + + mockMvc = MockMvcBuilders + .standaloneSetup(new VaadinConnectController(null, + mock(VaadinConnectAccessChecker.class), + mock(EndpointNameChecker.class), + mock(ExplicitNullableTypeChecker.class), + applicationContext, mockServletContext())) .build(); Assert.assertNotEquals(null, applicationContext); } @@ -95,4 +101,12 @@ protected MockHttpServletResponse callMethod(String methodName, .contentType(MediaType.APPLICATION_JSON_UTF8_VALUE); return mockMvc.perform(requestBuilder).andReturn().getResponse(); } + + private ServletContext mockServletContext() { + ServletContext context = Mockito.mock(ServletContext.class); + Mockito.when( + context.getAttribute(ApplicationConfiguration.class.getName())) + .thenReturn(appConfig); + return context; + } } diff --git a/fusion-endpoint/src/test/java/com/vaadin/flow/server/startup/fusion/DevModeInitializerEndpointTest.java b/fusion-endpoint/src/test/java/com/vaadin/flow/server/startup/fusion/DevModeInitializerEndpointTest.java index fad55352d93..1f8a6abcfcb 100644 --- a/fusion-endpoint/src/test/java/com/vaadin/flow/server/startup/fusion/DevModeInitializerEndpointTest.java +++ b/fusion-endpoint/src/test/java/com/vaadin/flow/server/startup/fusion/DevModeInitializerEndpointTest.java @@ -1,14 +1,7 @@ package com.vaadin.flow.server.startup.fusion; -import static com.vaadin.flow.server.Constants.CONNECT_JAVA_SOURCE_FOLDER_TOKEN; -import static com.vaadin.flow.server.InitParameters.SERVLET_PARAMETER_PRODUCTION_MODE; -import static com.vaadin.flow.server.InitParameters.SERVLET_PARAMETER_REUSE_DEV_SERVER; -import static com.vaadin.flow.server.frontend.FrontendUtils.DEFAULT_CONNECT_GENERATED_TS_DIR; -import static com.vaadin.flow.server.frontend.FrontendUtils.DEFAULT_CONNECT_JAVA_SOURCE_FOLDER; -import static com.vaadin.flow.server.frontend.FrontendUtils.DEFAULT_CONNECT_OPENAPI_JSON_FILE; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import javax.servlet.ServletContext; +import javax.servlet.ServletRegistration; import java.io.File; import java.lang.reflect.InvocationTargetException; @@ -21,8 +14,14 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicReference; -import javax.servlet.ServletContext; -import javax.servlet.ServletRegistration; +import net.jcip.annotations.NotThreadSafe; +import org.apache.commons.io.FileUtils; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.mockito.Mockito; import com.vaadin.flow.di.Lookup; import com.vaadin.flow.di.ResourceProvider; @@ -31,27 +30,28 @@ import com.vaadin.flow.server.frontend.EndpointGeneratorTaskFactory; import com.vaadin.flow.server.frontend.FrontendUtils; import com.vaadin.flow.server.frontend.fusion.EndpointGeneratorTaskFactoryImpl; +import com.vaadin.flow.server.startup.ApplicationConfiguration; import com.vaadin.flow.server.startup.DevModeInitializer; -import org.apache.commons.io.FileUtils; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.mockito.Mockito; - -import net.jcip.annotations.NotThreadSafe; +import static com.vaadin.flow.server.Constants.CONNECT_JAVA_SOURCE_FOLDER_TOKEN; +import static com.vaadin.flow.server.InitParameters.SERVLET_PARAMETER_PRODUCTION_MODE; +import static com.vaadin.flow.server.InitParameters.SERVLET_PARAMETER_REUSE_DEV_SERVER; +import static com.vaadin.flow.server.frontend.FrontendUtils.DEFAULT_CONNECT_GENERATED_TS_DIR; +import static com.vaadin.flow.server.frontend.FrontendUtils.DEFAULT_CONNECT_JAVA_SOURCE_FOLDER; +import static com.vaadin.flow.server.frontend.FrontendUtils.DEFAULT_CONNECT_OPENAPI_JSON_FILE; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; @NotThreadSafe public class DevModeInitializerEndpointTest { private final AtomicReference atomicHandler = new AtomicReference<>(); - + String baseDir; ServletContext servletContext; - Map initParams; Set> classes; DevModeInitializer devModeInitializer; + private ApplicationConfiguration appConfig; private final TemporaryFolder temporaryFolder = new TemporaryFolder(); @@ -67,14 +67,24 @@ public void setup() throws Exception { temporaryFolder.create(); baseDir = temporaryFolder.getRoot().getPath(); - servletContext = Mockito.mock(ServletContext.class); + appConfig = Mockito.mock(ApplicationConfiguration.class); + Mockito.when(appConfig.getStringProperty(Mockito.anyString(), + Mockito.anyString())) + .thenAnswer(invocation -> invocation.getArgumentAt(1, + String.class)); + Mockito.when(appConfig.getStringProperty(FrontendUtils.PROJECT_BASEDIR, + null)).thenReturn(baseDir); + Mockito.when(appConfig.enableDevServer()).thenReturn(true); + + servletContext = mockServletContext(); ServletRegistration vaadinServletRegistration = Mockito .mock(ServletRegistration.class); Lookup lookup = Mockito.mock(Lookup.class); Mockito.when(servletContext.getAttribute(Lookup.class.getName())) .thenReturn(lookup); - Mockito.doReturn(new EndpointGeneratorTaskFactoryImpl()).when(lookup).lookup(EndpointGeneratorTaskFactory.class); + Mockito.doReturn(new EndpointGeneratorTaskFactoryImpl()).when(lookup) + .lookup(EndpointGeneratorTaskFactory.class); ResourceProvider resourceProvider = Mockito .mock(ResourceProvider.class); @@ -84,12 +94,6 @@ public void setup() throws Exception { Mockito.when(vaadinServletRegistration.getClassName()) .thenReturn(VaadinServletSubClass.class.getName()); - initParams = new HashMap<>(); - initParams.put(FrontendUtils.PROJECT_BASEDIR, baseDir); - - Mockito.when(vaadinServletRegistration.getInitParameters()) - .thenReturn(initParams); - classes = new HashSet<>(); classes.add(this.getClass()); @@ -128,105 +132,61 @@ public void teardown() throws Exception, SecurityException { @Test public void should_generateOpenApi_when_EndpointPresents() throws Exception { - String originalJavaSourceFolder = null; File generatedOpenApiJson = Paths - .get(baseDir, DEFAULT_CONNECT_OPENAPI_JSON_FILE).toFile(); - try { - originalJavaSourceFolder = System.getProperty("vaadin." - + CONNECT_JAVA_SOURCE_FOLDER_TOKEN); - // Configure a folder that has .java classes with valid endpoints - // Not using `src/test/java` because there are invalid endpoint names - // in some tests - File src = new File( - getClass().getClassLoader().getResource("java").getFile()); - System.setProperty("vaadin." + CONNECT_JAVA_SOURCE_FOLDER_TOKEN, - src.getAbsolutePath()); - - Assert.assertFalse(generatedOpenApiJson.exists()); - DevModeInitializer devModeInitializer = new DevModeInitializer(); - devModeInitializer.onStartup(classes, servletContext); - waitForDevModeServer(); - Thread.sleep(200); - Assert.assertTrue("Should generate OpenAPI spec if Endpoint is used.", - generatedOpenApiJson.exists()); - } finally { - if (originalJavaSourceFolder != null) { - System.setProperty("vaadin." - + CONNECT_JAVA_SOURCE_FOLDER_TOKEN, originalJavaSourceFolder); - } else { - System.clearProperty("vaadin." - + CONNECT_JAVA_SOURCE_FOLDER_TOKEN); - } - generatedOpenApiJson.delete(); - } + .get(baseDir, DEFAULT_CONNECT_OPENAPI_JSON_FILE).toFile(); + File src = new File( + getClass().getClassLoader().getResource("java").getFile()); + Mockito.when(appConfig.getStringProperty( + Mockito.eq(CONNECT_JAVA_SOURCE_FOLDER_TOKEN), + Mockito.anyString())).thenReturn(src.getAbsolutePath()); + + Assert.assertFalse(generatedOpenApiJson.exists()); + DevModeInitializer devModeInitializer = new DevModeInitializer(); + devModeInitializer.process(classes, servletContext); + waitForDevModeServer(); + Thread.sleep(200); + Assert.assertTrue("Should generate OpenAPI spec if Endpoint is used.", + generatedOpenApiJson.exists()); } @Test public void should_notGenerateOpenApi_when_EndpointIsNotUsed() throws Exception { - String originalJavaSourceFolder = null; File generatedOpenApiJson = Paths .get(baseDir, DEFAULT_CONNECT_OPENAPI_JSON_FILE).toFile(); - try { - originalJavaSourceFolder = System.getProperty("vaadin." - + CONNECT_JAVA_SOURCE_FOLDER_TOKEN); - - System.clearProperty("vaadin." - + CONNECT_JAVA_SOURCE_FOLDER_TOKEN); - - Assert.assertFalse(generatedOpenApiJson.exists()); - devModeInitializer.onStartup(classes, servletContext); - Assert.assertFalse( + + Assert.assertFalse(generatedOpenApiJson.exists()); + devModeInitializer.process(classes, servletContext); + Assert.assertFalse( "Should not generate OpenAPI spec if Endpoint is not used.", generatedOpenApiJson.exists()); - } finally { - if (originalJavaSourceFolder != null) { - System.setProperty("vaadin." - + CONNECT_JAVA_SOURCE_FOLDER_TOKEN, originalJavaSourceFolder); - } else { - System.clearProperty("vaadin." - + CONNECT_JAVA_SOURCE_FOLDER_TOKEN); - } - generatedOpenApiJson.delete(); - } } @Test public void should_generateTs_files() throws Exception { - String originalJavaSourceFolder = null; - try { - originalJavaSourceFolder = System.getProperty("vaadin." - + CONNECT_JAVA_SOURCE_FOLDER_TOKEN); - // Configure a folder that has .java classes with valid endpoints - // Not using `src/test/java` because there are invalid endpoint names - // in some tests - File src = new File( - getClass().getClassLoader().getResource("java").getFile()); - System.setProperty("vaadin." + CONNECT_JAVA_SOURCE_FOLDER_TOKEN, - src.getAbsolutePath()); - - DevModeInitializer devModeInitializer = new DevModeInitializer(); - - File ts1 = new File(baseDir, - DEFAULT_CONNECT_GENERATED_TS_DIR + "MyEndpoint.ts"); - File ts2 = new File(baseDir, DEFAULT_CONNECT_GENERATED_TS_DIR - + "connect-client.default.ts"); - - assertFalse(ts1.exists()); - assertFalse(ts2.exists()); - devModeInitializer.onStartup(classes, servletContext); - waitForDevModeServer(); - assertTrue(ts1.exists()); - assertTrue(ts2.exists()); - } finally { - if (originalJavaSourceFolder != null) { - System.setProperty("vaadin." - + CONNECT_JAVA_SOURCE_FOLDER_TOKEN, originalJavaSourceFolder); - } else { - System.clearProperty("vaadin." - + CONNECT_JAVA_SOURCE_FOLDER_TOKEN); - } - } + // Configure a folder that has .java classes with valid endpoints + // Not using `src/test/java` because there are invalid endpoint + // names + // in some tests + File src = new File( + getClass().getClassLoader().getResource("java").getFile()); + Mockito.when(appConfig.getStringProperty( + Mockito.eq(CONNECT_JAVA_SOURCE_FOLDER_TOKEN), + Mockito.anyString())).thenReturn(src.getAbsolutePath()); + + DevModeInitializer devModeInitializer = new DevModeInitializer(); + + File ts1 = new File(baseDir, + DEFAULT_CONNECT_GENERATED_TS_DIR + "MyEndpoint.ts"); + File ts2 = new File(baseDir, + DEFAULT_CONNECT_GENERATED_TS_DIR + "connect-client.default.ts"); + + assertFalse(ts1.exists()); + assertFalse(ts2.exists()); + devModeInitializer.process(classes, servletContext); + waitForDevModeServer(); + assertTrue(ts1.exists()); + assertTrue(ts2.exists()); } /** @@ -235,11 +195,12 @@ public void should_generateTs_files() throws Exception { * @return devModeHandler or {@code null} if not started */ private DevModeHandler getDevModeHandler() { - return atomicHandler.get(); + return atomicHandler.get(); } - private void waitForDevModeServer() throws NoSuchMethodException, - IllegalAccessException, InvocationTargetException, InterruptedException { + private void waitForDevModeServer() + throws NoSuchMethodException, IllegalAccessException, + InvocationTargetException, InterruptedException { DevModeHandler handler = DevModeHandler.getDevModeHandler(); Assert.assertNotNull(handler); Method join = DevModeHandler.class.getDeclaredMethod("join"); @@ -247,4 +208,12 @@ private void waitForDevModeServer() throws NoSuchMethodException, join.invoke(handler); } + private ServletContext mockServletContext() { + ServletContext context = Mockito.mock(ServletContext.class); + Mockito.when( + context.getAttribute(ApplicationConfiguration.class.getName())) + .thenReturn(appConfig); + return context; + } + }