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 42e4b7f7d0a..482625b0f71 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 @@ -23,6 +23,7 @@ import java.util.function.Function; import java.util.stream.Collectors; +import com.vaadin.flow.server.AbstractConfiguration; import com.vaadin.flow.server.Constants; import com.vaadin.flow.server.InitParameters; import com.vaadin.flow.server.WrappedSession; @@ -30,7 +31,6 @@ import static com.vaadin.flow.server.Constants.POLYFILLS_DEFAULT_VALUE; import static com.vaadin.flow.server.InitParameters.SERVLET_PARAMETER_POLYFILLS; -import static com.vaadin.flow.server.InitParameters.SERVLET_PARAMETER_USE_V14_BOOTSTRAP; /** * A collection of properties configured at deploy time as well as a way of @@ -39,23 +39,8 @@ * @author Vaadin Ltd * @since 1.0 */ -public interface DeploymentConfiguration extends Serializable { - - /** - * Returns whether Vaadin is in production mode. - * - * @return true if in production mode, false otherwise. - */ - boolean isProductionMode(); - - /** - * Returns whether Vaadin is running in useDeprecatedV14Bootstrapping. - * - * @return true if in useDeprecatedV14Bootstrapping, false otherwise. - */ - default boolean useV14Bootstrap() { - return getBooleanProperty(SERVLET_PARAMETER_USE_V14_BOOTSTRAP, false); - } +public interface DeploymentConfiguration + extends AbstractConfiguration, Serializable { /** * Returns whether the server provides timing info to the client. @@ -195,6 +180,7 @@ T getApplicationOrSystemProperty(String propertyName, T defaultValue, * @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()); @@ -224,6 +210,7 @@ default String getStringProperty(String propertyName, String defaultValue) { * @throws IllegalArgumentException * if property value string is not a boolean value */ + @Override default boolean getBooleanProperty(String propertyName, boolean defaultValue) throws IllegalArgumentException { String booleanString = getStringProperty(propertyName, null); @@ -283,7 +270,8 @@ default boolean disableAutomaticServletRegistration() { * false to not serve Brotli files. */ default boolean isBrotli() { - return getBooleanProperty(InitParameters.SERVLET_PARAMETER_BROTLI, false); + return getBooleanProperty(InitParameters.SERVLET_PARAMETER_BROTLI, + false); } default String getCompiledWebComponentsPath() { @@ -295,8 +283,8 @@ default String getCompiledWebComponentsPath() { * Returns an array with polyfills to be loaded when the app is loaded. * * The default value is empty, but it can be changed by setting the - * {@link InitParameters#SERVLET_PARAMETER_POLYFILLS} as a comma separated list - * of JS files to load. + * {@link InitParameters#SERVLET_PARAMETER_POLYFILLS} as a comma separated + * list of JS files to load. * * @return polyfills to load */ @@ -313,9 +301,10 @@ default List getPolyfills() { * * @return true if dev server should be used */ + @Override default boolean enableDevServer() { - return getBooleanProperty(InitParameters.SERVLET_PARAMETER_ENABLE_DEV_SERVER, - true); + return getBooleanProperty( + InitParameters.SERVLET_PARAMETER_ENABLE_DEV_SERVER, true); } /** @@ -326,8 +315,8 @@ default boolean enableDevServer() { * @return true if dev server should be reused */ default boolean reuseDevServer() { - return getBooleanProperty(InitParameters.SERVLET_PARAMETER_REUSE_DEV_SERVER, - true); + return getBooleanProperty( + InitParameters.SERVLET_PARAMETER_REUSE_DEV_SERVER, true); } /** @@ -377,7 +366,8 @@ default boolean isEagerServerLoad() { /** * Checks if dev mode live reload is enabled or not. * - * @return {@code true} if dev mode live reload is enabled, {@code false} otherwise + * @return {@code true} if dev mode live reload is enabled, {@code false} + * otherwise */ boolean isDevModeLiveReloadEnabled(); @@ -387,6 +377,7 @@ default boolean isEagerServerLoad() { * @return {@code true} if enabled, {@code false} if not * @since 2.2 */ + @Override default boolean isPnpmEnabled() { return getBooleanProperty(InitParameters.SERVLET_PARAMETER_ENABLE_PNPM, Boolean.valueOf(Constants.ENABLE_PNPM_DEFAULT_STRING)); 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 new file mode 100644 index 00000000000..33f862b4e10 --- /dev/null +++ b/flow-server/src/main/java/com/vaadin/flow/server/AbstractConfiguration.java @@ -0,0 +1,95 @@ +/* + * 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 static com.vaadin.flow.server.InitParameters.SERVLET_PARAMETER_USE_V14_BOOTSTRAP; + +/** + * Defines a base contract for configuration (e.g. on an application level, + * servlet level,...). + * + * @author Vaadin Ltd + * @since + * + */ +public interface AbstractConfiguration { + /** + * Returns whether Vaadin is in production mode. + * + * @return true if in production mode, false otherwise. + */ + boolean isProductionMode(); + + /** + * Get if the dev server should be enabled. True by default + * + * @return true if dev server should be used + */ + default boolean enableDevServer() { + return getBooleanProperty( + InitParameters.SERVLET_PARAMETER_ENABLE_DEV_SERVER, true); + } + + /** + * Returns whether Vaadin is running in useDeprecatedV14Bootstrapping. + * + * @return true if in useDeprecatedV14Bootstrapping, false otherwise. + */ + default boolean useV14Bootstrap() { + return getBooleanProperty(SERVLET_PARAMETER_USE_V14_BOOTSTRAP, false); + } + + /** + * Gets a configured property as a string. + * + * @param name + * 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 + */ + String getStringProperty(String name, String defaultValue); + + /** + * Gets a configured property as a boolean. + * + * + * @param name + * 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 + * + */ + boolean getBooleanProperty(String name, boolean defaultValue); + + /** + * Returns whether pnpm is enabled or not. + * + * @return {@code true} if enabled, {@code false} if not + */ + default boolean isPnpmEnabled() { + return getBooleanProperty(InitParameters.SERVLET_PARAMETER_ENABLE_PNPM, + Boolean.valueOf(Constants.ENABLE_PNPM_DEFAULT_STRING)); + } + +} diff --git a/flow-server/src/main/java/com/vaadin/flow/server/AbstractDeploymentConfiguration.java b/flow-server/src/main/java/com/vaadin/flow/server/AbstractDeploymentConfiguration.java index be31517b155..59bf64c06ae 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/AbstractDeploymentConfiguration.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/AbstractDeploymentConfiguration.java @@ -15,6 +15,8 @@ */ package com.vaadin.flow.server; +import java.util.Map; + import com.vaadin.flow.component.UI; import com.vaadin.flow.function.DeploymentConfiguration; @@ -26,8 +28,18 @@ * @author Vaadin Ltd * @since 1.0 */ -public abstract class AbstractDeploymentConfiguration - implements DeploymentConfiguration { +public abstract class AbstractDeploymentConfiguration extends + AbstractPropertyConfiguration implements DeploymentConfiguration { + + /** + * Creates a new configuration based on {@code properties}. + * + * @param properties + * configuration properties + */ + protected AbstractDeploymentConfiguration(Map properties) { + super(properties); + } @Override public String getUIClassName() { 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 new file mode 100644 index 00000000000..e63215c33e8 --- /dev/null +++ b/flow-server/src/main/java/com/vaadin/flow/server/AbstractPropertyConfiguration.java @@ -0,0 +1,145 @@ +/* + * 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.util.Collections; +import java.util.Map; +import java.util.function.Function; + +import static com.vaadin.flow.server.Constants.VAADIN_PREFIX; + +/** + * @author Vaadin Ltd + * @since + * + */ +public abstract class AbstractPropertyConfiguration + implements AbstractConfiguration { + + private final Map properties; + + public AbstractPropertyConfiguration(Map properties) { + this.properties = properties; + } + + @Override + public String getStringProperty(String name, String defaultValue) { + return getApplicationOrSystemProperty(name, defaultValue, + Function.identity()); + } + + @Override + public boolean getBooleanProperty(String name, boolean defaultValue) { + /* + * Considers {@code ""} to be equal {@code true} in order to treat + * params like {@code -Dtest.param} as enabled ({@code test.param == + * true}). + */ + String booleanString = getStringProperty(name, null); + if (booleanString == null) { + return defaultValue; + } else if (booleanString.isEmpty()) { + return true; + } else { + boolean parsedBoolean = Boolean.parseBoolean(booleanString); + if (Boolean.toString(parsedBoolean) + .equalsIgnoreCase(booleanString)) { + return parsedBoolean; + } else { + throw new IllegalArgumentException(String.format( + "Property named '%s' is boolean, but contains incorrect value '%s' that is not boolean '%s'", + name, booleanString, parsedBoolean)); + } + } + } + + /** + * Gets an application property value. + * + * @param parameterName + * the Name or the parameter. + * @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; + } + + /** + * Gets unmodifiable underlying properties. + * + * @return the properties map + */ + protected Map getProperties() { + return Collections.unmodifiableMap(properties); + } + + /** + * Gets a configured property. The properties are typically read from e.g. + * web.xml or from system properties of the JVM. + * + * @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 + * @param converter + * the way string should be converted into the required property + * @param + * type of a property + * @return the property value, or the passed default value if no property + * value is found + */ + public T getApplicationOrSystemProperty(String propertyName, + T defaultValue, Function converter) { + // Try system properties + String val = getSystemProperty(propertyName); + if (val != null) { + return converter.apply(val); + } + + // Try application properties + val = getApplicationProperty(propertyName); + if (val != null) { + return converter.apply(val); + } + + return defaultValue; + } + + /** + * Gets an system property value. + * + * @param parameterName + * the Name or the parameter. + * @return String value or null if not found + */ + protected String getSystemProperty(String parameterName) { + // version prefixed with just "vaadin." + return System.getProperty(VAADIN_PREFIX + parameterName); + } + +} 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 c6d12bea685..97d2be75927 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,11 +15,14 @@ */ package com.vaadin.flow.server; +import java.util.HashMap; import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; import java.util.Properties; -import java.util.function.Function; 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; @@ -29,7 +32,6 @@ import static com.vaadin.flow.server.InitParameters.SERVLET_PARAMETER_REQUEST_TIMING; import static com.vaadin.flow.server.InitParameters.SERVLET_PARAMETER_SEND_URLS_AS_PARAMETERS; import static com.vaadin.flow.server.InitParameters.SERVLET_PARAMETER_SYNC_ID_CHECK; -import static com.vaadin.flow.server.Constants.VAADIN_PREFIX; /** * The property handling implementation of {@link DeploymentConfiguration} based @@ -40,9 +42,10 @@ public class PropertyDeploymentConfiguration extends AbstractDeploymentConfiguration { - private final Properties initParameters; private final Class systemPropertyBaseClass; + private final Properties initialParameters; + /** * Create a new property deployment configuration instance. * @@ -53,30 +56,14 @@ public class PropertyDeploymentConfiguration * the init parameters that should make up the foundation for * this configuration */ - public PropertyDeploymentConfiguration(Class systemPropertyBaseClass, - Properties initParameters) { - this.initParameters = initParameters; + public PropertyDeploymentConfiguration( + ApplicationConfiguration parentConfig, + Class systemPropertyBaseClass, Properties initParameters) { + super(filterStringProperties(initParameters)); + initialParameters = initParameters; this.systemPropertyBaseClass = systemPropertyBaseClass; } - @Override - public T getApplicationOrSystemProperty(String propertyName, - T defaultValue, Function converter) { - // Try system properties - String val = getSystemProperty(propertyName); - if (val != null) { - return converter.apply(val); - } - - // Try application properties - val = getApplicationProperty(propertyName); - if (val != null) { - return converter.apply(val); - } - - return defaultValue; - } - /** * Gets an system property value. * @@ -84,6 +71,7 @@ public T getApplicationOrSystemProperty(String propertyName, * the Name or the parameter. * @return String value or null if not found */ + @Override protected String getSystemProperty(String parameterName) { String pkgName; final Package pkg = systemPropertyBaseClass.getPackage(); @@ -116,10 +104,7 @@ protected String getSystemProperty(String parameterName) { return val; } - // version prefixed with just "vaadin." - val = System.getProperty(VAADIN_PREFIX + parameterName); - - return val; + return super.getSystemProperty(parameterName); } /** @@ -129,16 +114,17 @@ protected String getSystemProperty(String parameterName) { * the Name or the parameter. * @return String value or null if not found */ + @Override public String getApplicationProperty(String parameterName) { - String val = initParameters.getProperty(parameterName); + String val = getProperties().get(parameterName); if (val != null) { return val; } // Try lower case application properties for backward compatibility with // 3.0.2 and earlier - val = initParameters.getProperty(parameterName.toLowerCase()); + val = getProperties().get(parameterName.toLowerCase()); return val; } @@ -203,7 +189,7 @@ public String getPushURL() { @Override public Properties getInitParameters() { - return initParameters; + return initialParameters; } /** @@ -215,8 +201,23 @@ public Properties getInitParameters() { */ @Override public boolean isDevModeLiveReloadEnabled() { - return !isProductionMode() && getBooleanProperty( - SERVLET_PARAMETER_DEVMODE_ENABLE_LIVE_RELOAD, true) + return !isProductionMode() + && getBooleanProperty( + SERVLET_PARAMETER_DEVMODE_ENABLE_LIVE_RELOAD, true) && enableDevServer(); // gizmo excluded from prod bundle } + + private static Map filterStringProperties( + Properties properties) { + Map result = new HashMap<>(); + for (Entry entry : properties.entrySet()) { + Object key = entry.getKey(); + Object value = entry.getValue(); + // Hashtable doesn't allow null for key and value + if (key instanceof String && value instanceof String) { + result.put(key.toString(), value.toString()); + } + } + return result; + } } 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 new file mode 100644 index 00000000000..ea1339d3863 --- /dev/null +++ b/flow-server/src/main/java/com/vaadin/flow/server/startup/AbstractConfigurationFactory.java @@ -0,0 +1,185 @@ +/* + * 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.util.HashMap; +import java.util.Map; + +import com.vaadin.flow.server.InitParameters; +import com.vaadin.flow.server.frontend.FrontendUtils; + +import elemental.json.JsonObject; + +import static com.vaadin.flow.server.Constants.CONNECT_APPLICATION_PROPERTIES_TOKEN; +import static com.vaadin.flow.server.Constants.CONNECT_GENERATED_TS_DIR_TOKEN; +import static com.vaadin.flow.server.Constants.CONNECT_JAVA_SOURCE_FOLDER_TOKEN; +import static com.vaadin.flow.server.Constants.CONNECT_OPEN_API_FILE_TOKEN; +import static com.vaadin.flow.server.Constants.EXTERNAL_STATS_FILE; +import static com.vaadin.flow.server.Constants.EXTERNAL_STATS_FILE_TOKEN; +import static com.vaadin.flow.server.Constants.EXTERNAL_STATS_URL; +import static com.vaadin.flow.server.Constants.EXTERNAL_STATS_URL_TOKEN; +import static com.vaadin.flow.server.Constants.FRONTEND_TOKEN; +import static com.vaadin.flow.server.Constants.NPM_TOKEN; +import static com.vaadin.flow.server.Constants.VAADIN_PREFIX; +import static com.vaadin.flow.server.InitParameters.SERVLET_PARAMETER_ENABLE_DEV_SERVER; +import static com.vaadin.flow.server.InitParameters.SERVLET_PARAMETER_INITIAL_UIDL; +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.InitParameters.SERVLET_PARAMETER_USE_V14_BOOTSTRAP; +import static com.vaadin.flow.server.frontend.FrontendUtils.PROJECT_BASEDIR; + +/** + * @author Vaadin Ltd + * @since + * + */ +public class AbstractConfigurationFactory { + + 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"; + + protected Map getInitParametersUsingTokenData( + JsonObject buildInfo) { + Map params = new HashMap<>(); + if (buildInfo.hasKey(SERVLET_PARAMETER_PRODUCTION_MODE)) { + params.put(SERVLET_PARAMETER_PRODUCTION_MODE, String.valueOf( + buildInfo.getBoolean(SERVLET_PARAMETER_PRODUCTION_MODE))); + } + if (buildInfo.hasKey(EXTERNAL_STATS_FILE_TOKEN) + || buildInfo.hasKey(EXTERNAL_STATS_URL_TOKEN)) { + // If external stats file is flagged then + // dev server should be false - only variable that can + // be configured, in addition to stats variables, is + // production mode + params.put(SERVLET_PARAMETER_ENABLE_DEV_SERVER, + Boolean.toString(false)); + params.put(EXTERNAL_STATS_FILE, Boolean.toString(true)); + if (buildInfo.hasKey(EXTERNAL_STATS_URL_TOKEN)) { + params.put(EXTERNAL_STATS_URL, + buildInfo.getString(EXTERNAL_STATS_URL_TOKEN)); + } + // NO OTHER CONFIGURATION: + return params; + } + if (buildInfo.hasKey(SERVLET_PARAMETER_USE_V14_BOOTSTRAP)) { + params.put(SERVLET_PARAMETER_USE_V14_BOOTSTRAP, String.valueOf( + buildInfo.getBoolean(SERVLET_PARAMETER_USE_V14_BOOTSTRAP))); + // Need to be sure that we remove the system property, + // because it has priority in the configuration getter + System.clearProperty( + VAADIN_PREFIX + SERVLET_PARAMETER_USE_V14_BOOTSTRAP); + } + if (buildInfo.hasKey(SERVLET_PARAMETER_INITIAL_UIDL)) { + params.put(SERVLET_PARAMETER_INITIAL_UIDL, String.valueOf( + buildInfo.getBoolean(SERVLET_PARAMETER_INITIAL_UIDL))); + // Need to be sure that we remove the system property, + // because it has priority in the configuration getter + System.clearProperty( + VAADIN_PREFIX + SERVLET_PARAMETER_INITIAL_UIDL); + } + + if (buildInfo.hasKey(NPM_TOKEN)) { + params.put(PROJECT_BASEDIR, buildInfo.getString(NPM_TOKEN)); + verifyFolderExists(params, buildInfo.getString(NPM_TOKEN)); + } + + if (buildInfo.hasKey(FRONTEND_TOKEN)) { + params.put(FrontendUtils.PARAM_FRONTEND_DIR, + buildInfo.getString(FRONTEND_TOKEN)); + // Only verify frontend folder if it's not a subfolder of the + // npm folder. + if (!buildInfo.hasKey(NPM_TOKEN) + || !buildInfo.getString(FRONTEND_TOKEN) + .startsWith(buildInfo.getString(NPM_TOKEN))) { + verifyFolderExists(params, buildInfo.getString(FRONTEND_TOKEN)); + } + } + + // These should be internal only so if there is a System + // property override then the user probably knows what + // they are doing. + if (buildInfo.hasKey(SERVLET_PARAMETER_ENABLE_DEV_SERVER)) { + params.put(SERVLET_PARAMETER_ENABLE_DEV_SERVER, String.valueOf( + buildInfo.getBoolean(SERVLET_PARAMETER_ENABLE_DEV_SERVER))); + } + if (buildInfo.hasKey(SERVLET_PARAMETER_REUSE_DEV_SERVER)) { + params.put(SERVLET_PARAMETER_REUSE_DEV_SERVER, String.valueOf( + buildInfo.getBoolean(SERVLET_PARAMETER_REUSE_DEV_SERVER))); + } + if (buildInfo.hasKey(CONNECT_JAVA_SOURCE_FOLDER_TOKEN)) { + params.put(CONNECT_JAVA_SOURCE_FOLDER_TOKEN, + buildInfo.getString(CONNECT_JAVA_SOURCE_FOLDER_TOKEN)); + } + if (buildInfo.hasKey(CONNECT_OPEN_API_FILE_TOKEN)) { + params.put(CONNECT_OPEN_API_FILE_TOKEN, + buildInfo.getString(CONNECT_OPEN_API_FILE_TOKEN)); + } + if (buildInfo.hasKey(CONNECT_APPLICATION_PROPERTIES_TOKEN)) { + params.put(CONNECT_APPLICATION_PROPERTIES_TOKEN, + buildInfo.getString(CONNECT_APPLICATION_PROPERTIES_TOKEN)); + } + if (buildInfo.hasKey(CONNECT_GENERATED_TS_DIR_TOKEN)) { + params.put(CONNECT_GENERATED_TS_DIR_TOKEN, + buildInfo.getString(CONNECT_GENERATED_TS_DIR_TOKEN)); + } + + setDevModePropertiesUsingTokenData(params, buildInfo); + return params; + } + + protected void setDevModePropertiesUsingTokenData( + Map params, JsonObject buildInfo) { + // read dev mode properties from the token and set init parameter only + // if it's not yet set + if (params.get(InitParameters.SERVLET_PARAMETER_ENABLE_PNPM) == null + && buildInfo + .hasKey(InitParameters.SERVLET_PARAMETER_ENABLE_PNPM)) { + params.put(InitParameters.SERVLET_PARAMETER_ENABLE_PNPM, + String.valueOf(buildInfo.getBoolean( + InitParameters.SERVLET_PARAMETER_ENABLE_PNPM))); + } + if (params.get(InitParameters.REQUIRE_HOME_NODE_EXECUTABLE) == null + && buildInfo + .hasKey(InitParameters.REQUIRE_HOME_NODE_EXECUTABLE)) { + params.put(InitParameters.REQUIRE_HOME_NODE_EXECUTABLE, + String.valueOf(buildInfo.getBoolean( + InitParameters.REQUIRE_HOME_NODE_EXECUTABLE))); + } + } + + /** + * 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 params + * parameters map + * @param folder + * folder to check exists + */ + protected void verifyFolderExists(Map params, + String folder) { + Boolean productionMode = Boolean.parseBoolean(params + .getOrDefault(SERVLET_PARAMETER_PRODUCTION_MODE, "false")); + if (!productionMode && !new File(folder).exists()) { + String message = String.format(DEV_FOLDER_MISSING_MESSAGE, folder); + throw new IllegalStateException(message); + } + } +} 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 new file mode 100644 index 00000000000..5d972874d30 --- /dev/null +++ b/flow-server/src/main/java/com/vaadin/flow/server/startup/ApplicationConfiguration.java @@ -0,0 +1,72 @@ +/* + * 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 javax.servlet.Servlet; + +import java.util.Enumeration; + +import com.vaadin.flow.di.Lookup; +import com.vaadin.flow.function.DeploymentConfiguration; +import com.vaadin.flow.server.AbstractConfiguration; +import com.vaadin.flow.server.VaadinContext; + +/** + * Configuration on the application level. + *

+ * Configuration is based on {@link VaadinContext} which provides application + * level data in contrast to {@link DeploymentConfiguration} which provides a + * {@link Servlet} level configuration. + * + * @author Vaadin Ltd + * @since + * + */ +public interface ApplicationConfiguration extends AbstractConfiguration { + + /** + * Gets a configuration instance for the given {code context}. + * + * @param context + * the context to get the configuration for + * @return the application level configuration for the given {@code context} + */ + static ApplicationConfiguration get(VaadinContext context) { + return context.getAttribute(ApplicationConfiguration.class, () -> { + Lookup lookup = context.getAttribute(Lookup.class); + ApplicationConfigurationFactory factory = lookup + .lookup(ApplicationConfigurationFactory.class); + return factory.create(context); + }); + } + + /** + * Returns the names of the configuration properties as an + * Enumeration, or an empty Enumeration if there + * are o initialization parameters. + * + * @return configuration properties as a Enumeration + */ + Enumeration getPropertyNames(); + + /** + * The context which the configuration is based on. + * + * @return the vaadin context + */ + VaadinContext getContext(); + +} diff --git a/flow-server/src/main/java/com/vaadin/flow/server/startup/ApplicationConfigurationFactory.java b/flow-server/src/main/java/com/vaadin/flow/server/startup/ApplicationConfigurationFactory.java new file mode 100644 index 00000000000..7dabd70f0e6 --- /dev/null +++ b/flow-server/src/main/java/com/vaadin/flow/server/startup/ApplicationConfigurationFactory.java @@ -0,0 +1,38 @@ +/* + * 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 com.vaadin.flow.server.VaadinContext; + +/** + * A factory for {@link ApplicationConfiguration}. + * + * @author Vaadin Ltd + * @since + * + */ +public interface ApplicationConfigurationFactory { + + /** + * Creates a new instance of {@link ApplicationConfiguration} for the given + * {@code context}. + * + * @param context + * the context to create a configuration for + * @return the configuration created based on the {@code context} + */ + ApplicationConfiguration create(VaadinContext context); +} 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 new file mode 100644 index 00000000000..888b145bba5 --- /dev/null +++ b/flow-server/src/main/java/com/vaadin/flow/server/startup/DefaultApplicationConfigurationFactory.java @@ -0,0 +1,210 @@ +/* + * 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.IOException; +import java.io.UncheckedIOException; +import java.net.URL; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.vaadin.flow.di.Lookup; +import com.vaadin.flow.di.ResourceProvider; +import com.vaadin.flow.server.AbstractPropertyConfiguration; +import com.vaadin.flow.server.VaadinContext; +import com.vaadin.flow.server.frontend.FrontendUtils; + +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.TOKEN_FILE; + +/** + * Default implementation of {@link ApplicationConfigurationFactory}. + * + * @author Vaadin Ltd + * @since + * + */ +public class DefaultApplicationConfigurationFactory + extends AbstractConfigurationFactory + implements ApplicationConfigurationFactory { + + protected static class ApplicationConfigurationImpl extends + AbstractPropertyConfiguration implements ApplicationConfiguration { + + private final VaadinContext context; + + protected ApplicationConfigurationImpl(VaadinContext context, + Map properties) { + super(properties); + this.context = context; + } + + @Override + public boolean isProductionMode() { + return getBooleanProperty(SERVLET_PARAMETER_PRODUCTION_MODE, false); + } + + @Override + public Enumeration getPropertyNames() { + return Collections.enumeration(getProperties().keySet()); + } + + @Override + public VaadinContext getContext() { + return context; + } + + } + + @Override + public ApplicationConfiguration create(VaadinContext context) { + Map props = new HashMap<>(); + for (final Enumeration e = context.getContextParameterNames(); e + .hasMoreElements();) { + final String name = e.nextElement(); + props.put(name, context.getContextParameter(name)); + } + try { + JsonObject buildInfo = JsonUtil + .parse(getTokenFileFromClassloader(context)); + + props.putAll(getInitParametersUsingTokenData(buildInfo)); + } catch (IOException exception) { + throw new UncheckedIOException(exception); + } + return new ApplicationConfigurationImpl(context, props); + } + + /** + * 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 + */ + protected 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 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); + + // 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()); + } + } + } + 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()); + getLogger().warn(warningMessage); + } else { + String debugMessage = String.format( + "Unable to fully determine correct flow-build-info.%n" + + "Accepting file '%s'", + firstResource.getPath()); + getLogger().debug(debugMessage); + } + return FrontendUtils.streamToString(firstResource.openStream()); + } + + private int countInstances(String input, String value) { + return input.split(value, -1).length - 1; + } + + private Logger getLogger() { + return LoggerFactory + .getLogger(DefaultApplicationConfigurationFactory.class); + } +}