Converted DevModeInitializer.java to kotlin

This commit is contained in:
Robinson 2021-08-31 19:21:31 -06:00
parent c9351c9647
commit 51b17ac65e
No known key found for this signature in database
GPG Key ID: 8E7DB78588BD6F5C
1 changed files with 416 additions and 484 deletions

View File

@ -13,89 +13,51 @@
* License for the specific language governing permissions and limitations under * License for the specific language governing permissions and limitations under
* the License. * the License.
*/ */
package dorkbox.vaadin; package dorkbox.vaadin
import static com.vaadin.flow.server.Constants.PACKAGE_JSON; import com.vaadin.flow.component.WebComponentExporter
import static com.vaadin.flow.server.InitParameters.SERVLET_PARAMETER_DEVMODE_OPTIMIZE_BUNDLE; import com.vaadin.flow.component.WebComponentExporterFactory
import static com.vaadin.flow.server.frontend.FrontendUtils.DEFAULT_FRONTEND_DIR; import com.vaadin.flow.component.dependency.CssImport
import static com.vaadin.flow.server.frontend.FrontendUtils.DEFAULT_GENERATED_DIR; import com.vaadin.flow.component.dependency.JavaScript
import static com.vaadin.flow.server.frontend.FrontendUtils.PARAM_FRONTEND_DIR; import com.vaadin.flow.component.dependency.JsModule
import static com.vaadin.flow.server.frontend.FrontendUtils.PARAM_GENERATED_DIR; import com.vaadin.flow.component.dependency.NpmPackage
import static com.vaadin.flow.server.frontend.FrontendUtils.WEBPACK_GENERATED; import com.vaadin.flow.function.DeploymentConfiguration
import com.vaadin.flow.router.HasErrorParameter
import java.io.File; import com.vaadin.flow.router.Route
import java.io.IOException; import com.vaadin.flow.server.*
import java.io.InputStream; import com.vaadin.flow.server.frontend.FrontendUtils
import java.io.Serializable; import com.vaadin.flow.server.frontend.NodeTasks
import java.io.UncheckedIOException; import com.vaadin.flow.server.frontend.scanner.ClassFinder.DefaultClassFinder
import java.lang.annotation.Annotation; import com.vaadin.flow.server.startup.ClassLoaderAwareServletContainerInitializer
import java.lang.reflect.InvocationTargetException; import com.vaadin.flow.server.startup.ServletDeployer.StubServletConfig
import java.lang.reflect.Method; import com.vaadin.flow.theme.NoTheme
import java.net.URL; import com.vaadin.flow.theme.Theme
import java.net.URLDecoder; import elemental.json.Json
import java.nio.charset.StandardCharsets; import elemental.json.JsonObject
import java.nio.file.Files; import org.apache.commons.io.FileUtils
import java.nio.file.Path; import org.apache.commons.io.IOUtils
import java.nio.file.Paths; import org.slf4j.Logger
import java.util.Collection; import org.slf4j.LoggerFactory
import java.util.Collections; import java.io.*
import java.util.Enumeration; import java.lang.reflect.InvocationTargetException
import java.util.HashSet; import java.net.URL
import java.util.List; import java.net.URLDecoder
import java.util.Set; import java.nio.charset.StandardCharsets
import java.util.concurrent.CompletableFuture; import java.nio.file.Files
import java.util.concurrent.CompletionException; import java.nio.file.Path
import java.util.concurrent.Executor; import java.nio.file.Paths
import java.util.regex.Matcher; import java.util.*
import java.util.regex.Pattern; import java.util.concurrent.CompletableFuture
import java.util.stream.Collectors; import java.util.concurrent.CompletionException
import java.util.stream.Stream; import java.util.concurrent.Executor
import java.util.zip.ZipEntry; import java.util.regex.Pattern
import java.util.zip.ZipOutputStream; import java.util.stream.Collectors
import java.util.stream.Stream
import javax.servlet.ServletContext; import java.util.zip.ZipEntry
import javax.servlet.ServletContextEvent; import java.util.zip.ZipOutputStream
import javax.servlet.ServletContextListener; import javax.servlet.*
import javax.servlet.ServletException; import javax.servlet.annotation.HandlesTypes
import javax.servlet.ServletRegistration; import javax.servlet.annotation.WebListener
import javax.servlet.annotation.HandlesTypes;
import javax.servlet.annotation.WebListener;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.vaadin.flow.component.WebComponentExporter;
import com.vaadin.flow.component.WebComponentExporterFactory;
import com.vaadin.flow.component.dependency.CssImport;
import com.vaadin.flow.component.dependency.JavaScript;
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.dependency.NpmPackage;
import com.vaadin.flow.function.DeploymentConfiguration;
import com.vaadin.flow.router.HasErrorParameter;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.Constants;
import com.vaadin.flow.server.DevModeHandler;
import com.vaadin.flow.server.ExecutionFailedException;
import com.vaadin.flow.server.InitParameters;
import com.vaadin.flow.server.UIInitListener;
import com.vaadin.flow.server.VaadinContext;
import com.vaadin.flow.server.VaadinServiceInitListener;
import com.vaadin.flow.server.VaadinServlet;
import com.vaadin.flow.server.VaadinServletContext;
import com.vaadin.flow.server.frontend.FallbackChunk;
import com.vaadin.flow.server.frontend.FrontendUtils;
import com.vaadin.flow.server.frontend.NodeTasks;
import com.vaadin.flow.server.frontend.NodeTasks.Builder;
import com.vaadin.flow.server.frontend.scanner.ClassFinder.DefaultClassFinder;
import com.vaadin.flow.server.startup.ClassLoaderAwareServletContainerInitializer;
import com.vaadin.flow.server.startup.ServletDeployer.StubServletConfig;
import com.vaadin.flow.theme.NoTheme;
import com.vaadin.flow.theme.Theme;
import elemental.json.Json;
import elemental.json.JsonObject;
/** /**
* THIS IS COPIED DIRECTLY FROM VAADIN 14.6.8 (flow 2.4.6) * THIS IS COPIED DIRECTLY FROM VAADIN 14.6.8 (flow 2.4.6)
@ -112,483 +74,453 @@ import elemental.json.JsonObject;
* *
* @since 2.0 * @since 2.0
*/ */
@HandlesTypes({Route.class, UIInitListener.class, @HandlesTypes(
VaadinServiceInitListener.class, WebComponentExporter.class, Route::class,
WebComponentExporterFactory.class, NpmPackage.class, UIInitListener::class,
NpmPackage.Container.class, JsModule.class, JsModule.Container.class, VaadinServiceInitListener::class,
CssImport.class, CssImport.Container.class, JavaScript.class, WebComponentExporter::class,
JavaScript.Container.class, Theme.class, NoTheme.class, WebComponentExporterFactory::class,
HasErrorParameter.class }) NpmPackage::class,
NpmPackage.Container::class,
JsModule::class,
JsModule.Container::class,
CssImport::class,
CssImport.Container::class,
JavaScript::class,
JavaScript.Container::class,
Theme::class,
NoTheme::class,
HasErrorParameter::class
)
@WebListener @WebListener
public class DevModeInitializer class DevModeInitializer : ClassLoaderAwareServletContainerInitializer, Serializable, ServletContextListener {
implements ClassLoaderAwareServletContainerInitializer, Serializable, internal class DevModeClassFinder(classes: Set<Class<*>?>?) : DefaultClassFinder(classes) {
ServletContextListener { companion object {
private val APPLICABLE_CLASS_NAMES = Collections.unmodifiableSet(calculateApplicableClassNames())
static class DevModeClassFinder extends DefaultClassFinder { private fun calculateApplicableClassNames(): Set<String> {
val handlesTypes: HandlesTypes = DevModeInitializer::class.java.getAnnotation(HandlesTypes::class.java)
private static final Set<String> APPLICABLE_CLASS_NAMES = Collections val values: Array<Class<Any>> = handlesTypes.value.map { (it as Any).javaClass }.toTypedArray()
.unmodifiableSet(calculateApplicableClassNames()); return Stream.of<Class<*>>(*values).map { obj: Class<*> -> obj.name }
.collect(Collectors.toSet())
public DevModeClassFinder(Set<Class<?>> classes) { }
super(classes);
} }
@Override override fun getAnnotatedClasses(annotation: Class<out Annotation?>): Set<Class<*>> {
public Set<Class<?>> getAnnotatedClasses( ensureImplementation(annotation)
Class<? extends Annotation> annotation) { return super.getAnnotatedClasses(annotation)
ensureImplementation(annotation);
return super.getAnnotatedClasses(annotation);
} }
@Override override fun <T> getSubTypesOf(type: Class<T>): Set<Class<out T>> {
public <T> Set<Class<? extends T>> getSubTypesOf(Class<T> type) { ensureImplementation(type)
ensureImplementation(type); return super.getSubTypesOf(type)
return super.getSubTypesOf(type);
} }
private void ensureImplementation(Class<?> clazz) { private fun ensureImplementation(clazz: Class<*>) {
if (!APPLICABLE_CLASS_NAMES.contains(clazz.getName())) { require(APPLICABLE_CLASS_NAMES.contains(clazz.name)) {
throw new IllegalArgumentException("Unexpected class name " ("Unexpected class name "
+ clazz + ". Implementation error: the class finder " + clazz + ". Implementation error: the class finder "
+ "instance is not aware of this class. " + "instance is not aware of this class. "
+ "Fix @HandlesTypes annotation value for " + "Fix @HandlesTypes annotation value for "
+ DevModeInitializer.class.getName()); + DevModeInitializer::class.java.name)
} }
} }
private static Set<String> calculateApplicableClassNames() {
HandlesTypes handlesTypes = DevModeInitializer.class
.getAnnotation(HandlesTypes.class);
return Stream.of(handlesTypes.value()).map(Class::getName)
.collect(Collectors.toSet());
}
} }
private static final Pattern JAR_FILE_REGEX = Pattern @Throws(ServletException::class)
.compile(".*file:(.+\\.jar).*"); override fun process(classes: Set<Class<*>?>?, context: ServletContext) {
val registrations: Collection<ServletRegistration> = context.servletRegistrations.values
var vaadinServletRegistration: ServletRegistration? = null
// Path of jar files in a URL with zip protocol doesn't start with "zip:" for (registration in registrations) {
// nor "file:". It contains only the path of the file.
// Weblogic uses zip protocol.
private static final Pattern ZIP_PROTOCOL_JAR_FILE_REGEX = Pattern
.compile("(.+\\.jar).*");
private static final Pattern VFS_FILE_REGEX = Pattern
.compile("(vfs:/.+\\.jar).*");
private static final Pattern VFS_DIRECTORY_REGEX = Pattern
.compile("vfs:/.+");
// allow trailing slash
private static final Pattern DIR_REGEX_FRONTEND_DEFAULT = Pattern.compile(
"^(?:file:0)?(.+)" + Constants.RESOURCES_FRONTEND_DEFAULT + "/?$");
// allow trailing slash
private static final Pattern DIR_REGEX_COMPATIBILITY_FRONTEND_DEFAULT = Pattern
.compile("^(?:file:)?(.+)"
+ Constants.COMPATIBILITY_RESOURCES_FRONTEND_DEFAULT
+ "/?$");
@Override
public void process(Set<Class<?>> classes, ServletContext context)
throws ServletException {
Collection<? extends ServletRegistration> registrations = context
.getServletRegistrations().values();
ServletRegistration vaadinServletRegistration = null;
for (ServletRegistration registration : registrations) {
try { try {
if (registration.getClassName() != null if (registration.className != null && isVaadinServletSubClass(registration.className)) {
&& isVaadinServletSubClass( vaadinServletRegistration = registration
registration.getClassName())) { break
vaadinServletRegistration = registration;
break;
} }
} catch (ClassNotFoundException e) { } catch (e: ClassNotFoundException) {
throw new ServletException( throw ServletException(
String.format("Servlet class name (%s) can't be found!", String.format(
registration.getClassName()), "Servlet class name (%s) can't be found!",
e); registration.className
),
e
)
} }
} }
DeploymentConfiguration config; val config = if (vaadinServletRegistration != null) {
if (vaadinServletRegistration != null) { StubServletConfig.createDeploymentConfiguration(context, vaadinServletRegistration, VaadinServlet::class.java)
config = StubServletConfig.createDeploymentConfiguration(context,
vaadinServletRegistration, VaadinServlet.class);
} else { } else {
config = StubServletConfig.createDeploymentConfiguration(context, StubServletConfig.createDeploymentConfiguration(context, VaadinServlet::class.java)
VaadinServlet.class);
} }
initDevModeHandler(classes, context, config)
initDevModeHandler(classes, context, config);
} }
private boolean isVaadinServletSubClass(String className) @Throws(ClassNotFoundException::class)
throws ClassNotFoundException { private fun isVaadinServletSubClass(className: String): Boolean {
return VaadinServlet.class.isAssignableFrom(Class.forName(className)); return VaadinServlet::class.java.isAssignableFrom(Class.forName(className))
} }
/** override fun contextInitialized(ctx: ServletContextEvent) {
* Initialize the devmode server if not in production mode or compatibility // No need to do anything on init
* mode. }
*
* @param classes override fun contextDestroyed(ctx: ServletContextEvent) {
* classes to check for npm- and js modules val handler = DevModeHandler.getDevModeHandler()
* @param context if (handler != null && !handler.reuseDevServer()) {
* servlet context we are running in handler.stop()
* @param config
* deployment configuration
*
* @throws ServletException
* if dev mode can't be initialized
*/
public static void initDevModeHandler(Set<Class<?>> classes,
ServletContext context, DeploymentConfiguration config)
throws ServletException {
if (config.isProductionMode()) {
log().debug("Skipping DEV MODE because PRODUCTION MODE is set.");
return;
}
if (config.isCompatibilityMode()) {
log().debug("Skipping DEV MODE because BOWER MODE is set.");
return;
}
if (!config.enableDevServer()) {
log().debug(
"Skipping DEV MODE because dev server shouldn't be enabled.");
return;
} }
}
String baseDir = config.getStringProperty(FrontendUtils.PROJECT_BASEDIR, companion object {
null); private val JAR_FILE_REGEX = Pattern.compile(".*file:(.+\\.jar).*")
if (baseDir == null) {
baseDir = getBaseDirectoryFallback();
}
String generatedDir = System.getProperty(PARAM_GENERATED_DIR, // Path of jar files in a URL with zip protocol doesn't start with "zip:"
DEFAULT_GENERATED_DIR); // nor "file:". It contains only the path of the file.
String frontendFolder = config.getStringProperty(PARAM_FRONTEND_DIR, // Weblogic uses zip protocol.
System.getProperty(PARAM_FRONTEND_DIR, DEFAULT_FRONTEND_DIR)); private val ZIP_PROTOCOL_JAR_FILE_REGEX = Pattern.compile("(.+\\.jar).*")
private val VFS_FILE_REGEX = Pattern.compile("(vfs:/.+\\.jar).*")
private val VFS_DIRECTORY_REGEX = Pattern.compile("vfs:/.+")
Builder builder = new Builder(new DevModeClassFinder(classes), // allow trailing slash
new File(baseDir), new File(generatedDir), private val DIR_REGEX_FRONTEND_DEFAULT = Pattern.compile("^(?:file:0)?(.+)" + Constants.RESOURCES_FRONTEND_DEFAULT + "/?$")
new File(frontendFolder));
log().info("Starting dev-mode updaters in {} folder.", // allow trailing slash
builder.npmFolder); private val DIR_REGEX_COMPATIBILITY_FRONTEND_DEFAULT = Pattern.compile(
"^(?:file:)?(.+)"
+ Constants.COMPATIBILITY_RESOURCES_FRONTEND_DEFAULT
+ "/?$"
)
if (!builder.generatedFolder.exists()) { /**
try { * Initialize the devmode server if not in production mode or compatibility
FileUtils.forceMkdir(builder.generatedFolder); * mode.
} catch (IOException e) { *
throw new UncheckedIOException( * @param classes
String.format("Failed to create directory '%s'", * classes to check for npm- and js modules
builder.generatedFolder), * @param context
e); * servlet context we are running in
* @param config
* deployment configuration
*
* @throws ServletException
* if dev mode can't be initialized
*/
@Throws(ServletException::class)
fun initDevModeHandler(classes: Set<Class<*>?>?, context: ServletContext?, config: DeploymentConfiguration) {
System.err.println("CUSTOM INIT!")
if (config.isProductionMode) {
log().debug("Skipping DEV MODE because PRODUCTION MODE is set.")
return
}
if (config.isCompatibilityMode) {
log().debug("Skipping DEV MODE because BOWER MODE is set.")
return
}
if (!config.enableDevServer()) {
log().debug("Skipping DEV MODE because dev server shouldn't be enabled.")
return
} }
}
File generatedPackages = new File(builder.generatedFolder,
PACKAGE_JSON);
// If we are missing the generated webpack configuration then generate val baseDir = config.getStringProperty(FrontendUtils.PROJECT_BASEDIR, null) ?: baseDirectoryFallback
// webpack configurations val generatedDir = System.getProperty(FrontendUtils.PARAM_GENERATED_DIR, FrontendUtils.DEFAULT_GENERATED_DIR)
if (!new File(builder.npmFolder, WEBPACK_GENERATED).exists()) {
builder.withWebpack(builder.npmFolder, FrontendUtils.WEBPACK_CONFIG,
FrontendUtils.WEBPACK_GENERATED);
}
// If we are missing either the base or generated package json files val frontendFolder = config.getStringProperty(FrontendUtils.PARAM_FRONTEND_DIR,
// generate those System.getProperty(FrontendUtils.PARAM_FRONTEND_DIR, FrontendUtils.DEFAULT_FRONTEND_DIR))
if (!new File(builder.npmFolder, PACKAGE_JSON).exists()
|| !generatedPackages.exists()) {
builder.createMissingPackageJson(true);
}
Set<File> frontendLocations = getFrontendLocationsFromClassloader( val builder = NodeTasks.Builder(DevModeClassFinder(classes), File(baseDir), File(generatedDir), File(frontendFolder))
DevModeInitializer.class.getClassLoader());
boolean useByteCodeScanner = config.getBooleanProperty( log().info("Starting dev-mode updaters in {} folder.", builder.npmFolder)
SERVLET_PARAMETER_DEVMODE_OPTIMIZE_BUNDLE, if (!builder.generatedFolder.exists()) {
Boolean.parseBoolean(System.getProperty( try {
SERVLET_PARAMETER_DEVMODE_OPTIMIZE_BUNDLE, FileUtils.forceMkdir(builder.generatedFolder)
Boolean.FALSE.toString()))); } catch (e: IOException) {
throw UncheckedIOException(
String.format(
"Failed to create directory '%s'",
builder.generatedFolder
),
e
)
}
}
val generatedPackages = File(builder.generatedFolder, Constants.PACKAGE_JSON)
boolean enablePnpm = config.isPnpmEnabled(); // If we are missing the generated webpack configuration then generate
// webpack configurations
if (!File(builder.npmFolder, FrontendUtils.WEBPACK_GENERATED).exists()) {
builder.withWebpack(builder.npmFolder, FrontendUtils.WEBPACK_CONFIG, FrontendUtils.WEBPACK_GENERATED)
}
boolean useHomeNodeExec = config.getBooleanProperty( // If we are missing either the base or generated package json files
InitParameters.REQUIRE_HOME_NODE_EXECUTABLE, false); // generate those
if (!File(builder.npmFolder, Constants.PACKAGE_JSON).exists() || !generatedPackages.exists()) {
builder.createMissingPackageJson(true)
}
VaadinContext vaadinContext = new VaadinServletContext(context); val frontendLocations = getFrontendLocationsFromClassloader(DevModeInitializer::class.java.classLoader)
JsonObject tokenFileData = Json.createObject(); val useByteCodeScanner = config.getBooleanProperty(
try { InitParameters.SERVLET_PARAMETER_DEVMODE_OPTIMIZE_BUNDLE,
builder.enablePackagesUpdate(true) java.lang.Boolean.parseBoolean(
System.getProperty(
InitParameters.SERVLET_PARAMETER_DEVMODE_OPTIMIZE_BUNDLE,
java.lang.Boolean.FALSE.toString()
)
)
)
val enablePnpm = config.isPnpmEnabled
val useHomeNodeExec = config.getBooleanProperty(InitParameters.REQUIRE_HOME_NODE_EXECUTABLE, false)
val vaadinContext: VaadinContext = VaadinServletContext(context)
val tokenFileData = Json.createObject()
try {
builder.enablePackagesUpdate(true)
.useByteCodeScanner(useByteCodeScanner) .useByteCodeScanner(useByteCodeScanner)
.copyResources(frontendLocations) .copyResources(frontendLocations)
.copyLocalResources(new File(baseDir, .copyLocalResources(File(baseDir, Constants.LOCAL_FRONTEND_RESOURCES_PATH))
Constants.LOCAL_FRONTEND_RESOURCES_PATH))
.enableImportsUpdate(true).runNpmInstall(true) .enableImportsUpdate(true).runNpmInstall(true)
.populateTokenFileData(tokenFileData) .populateTokenFileData(tokenFileData)
.withEmbeddableWebComponents(true).enablePnpm(enablePnpm) .withEmbeddableWebComponents(true).enablePnpm(enablePnpm)
.withHomeNodeExecRequired(useHomeNodeExec).build() .withHomeNodeExecRequired(useHomeNodeExec).build()
.execute(); .execute()
FallbackChunk chunk = FrontendUtils val chunk = FrontendUtils.readFallbackChunk(tokenFileData)
.readFallbackChunk(tokenFileData); if (chunk != null) {
if (chunk != null) { vaadinContext.setAttribute(chunk)
vaadinContext.setAttribute(chunk); }
} catch (exception: ExecutionFailedException) {
log().debug("Could not initialize dev mode handler. One of the node tasks failed", exception)
throw ServletException(exception)
} }
} catch (ExecutionFailedException exception) { val tasks = builder.enablePackagesUpdate(true)
log().debug(
"Could not initialize dev mode handler. One of the node tasks failed",
exception);
throw new ServletException(exception);
}
NodeTasks tasks = builder.enablePackagesUpdate(true)
.useByteCodeScanner(useByteCodeScanner) .useByteCodeScanner(useByteCodeScanner)
.copyResources(frontendLocations) .copyResources(frontendLocations)
.copyLocalResources(new File(baseDir, .copyLocalResources(File(baseDir, Constants.LOCAL_FRONTEND_RESOURCES_PATH))
Constants.LOCAL_FRONTEND_RESOURCES_PATH))
.enableImportsUpdate(true).runNpmInstall(true) .enableImportsUpdate(true).runNpmInstall(true)
.populateTokenFileData(tokenFileData) .populateTokenFileData(tokenFileData)
.withEmbeddableWebComponents(true).enablePnpm(enablePnpm) .withEmbeddableWebComponents(true).enablePnpm(enablePnpm)
.withHomeNodeExecRequired(useHomeNodeExec).build(); .withHomeNodeExecRequired(useHomeNodeExec).build()
// Check whether executor is provided by the caller (framework) // Check whether executor is provided by the caller (framework)
Object service = config.getInitParameters().get(Executor.class); val service = config.initParameters[Executor::class.java]
val runnable = Runnable {
Runnable runnable = () -> runNodeTasks(vaadinContext, tokenFileData, runNodeTasks(
tasks); vaadinContext, tokenFileData,
tasks
CompletableFuture<Void> nodeTasksFuture; )
if (service instanceof Executor) {
// if there is an executor use it to run the task
nodeTasksFuture = CompletableFuture.runAsync(runnable,
(Executor) service);
} else {
nodeTasksFuture = CompletableFuture.runAsync(runnable);
}
DevModeHandler.start(config, builder.npmFolder, nodeTasksFuture);
}
private static Logger log() {
return LoggerFactory.getLogger(DevModeInitializer.class);
}
@Override
public void contextInitialized(ServletContextEvent ctx) {
// No need to do anything on init
}
@Override
public void contextDestroyed(ServletContextEvent ctx) {
DevModeHandler handler = DevModeHandler.getDevModeHandler();
if (handler != null && !handler.reuseDevServer()) {
handler.stop();
}
}
/*
* Accept user.dir or cwd as a fallback only if the directory seems to be a
* Maven or Gradle project. Check to avoid cluttering server directories
* (see tickets #8249, #8403).
*/
private static String getBaseDirectoryFallback() {
String baseDirCandidate = System.getProperty("user.dir", ".");
Path path = Paths.get(baseDirCandidate);
if (path.toFile().isDirectory()
&& (path.resolve("pom.xml").toFile().exists()
|| path.resolve("build.gradle").toFile().exists())) {
return path.toString();
} else {
throw new IllegalStateException(String.format(
"Failed to determine project directory for dev mode. "
+ "Directory '%s' does not look like a Maven or "
+ "Gradle project. Ensure that you have run the "
+ "prepare-frontend Maven goal, which generates "
+ "'flow-build-info.json', prior to deploying your "
+ "application",
path.toString()));
}
}
/*
* This method returns all folders of jar files having files in the
* META-INF/resources/frontend folder. We don't use URLClassLoader because
* will fail in Java 9+
*/
static Set<File> getFrontendLocationsFromClassloader(
ClassLoader classLoader) throws ServletException {
Set<File> frontendFiles = new HashSet<>();
frontendFiles.addAll(getFrontendLocationsFromClassloader(classLoader,
Constants.RESOURCES_FRONTEND_DEFAULT));
frontendFiles.addAll(getFrontendLocationsFromClassloader(classLoader,
Constants.COMPATIBILITY_RESOURCES_FRONTEND_DEFAULT));
return frontendFiles;
}
private static void runNodeTasks(VaadinContext vaadinContext,
JsonObject tokenFileData, NodeTasks tasks) {
try {
tasks.execute();
FallbackChunk chunk = FrontendUtils
.readFallbackChunk(tokenFileData);
if (chunk != null) {
vaadinContext.setAttribute(chunk);
} }
} catch (ExecutionFailedException exception) {
log().debug(
"Could not initialize dev mode handler. One of the node tasks failed",
exception);
throw new CompletionException(exception);
}
}
private static Set<File> getFrontendLocationsFromClassloader( val nodeTasksFuture =
ClassLoader classLoader, String resourcesFolder) if (service is Executor) {
throws ServletException { // if there is an executor use it to run the task
Set<File> frontendFiles = new HashSet<>(); CompletableFuture.runAsync(
try { runnable,
Enumeration<URL> en = classLoader.getResources(resourcesFolder); service as Executor?
if (en == null) { )
return frontendFiles; } else {
CompletableFuture.runAsync(runnable)
} }
Set<String> vfsJars = new HashSet<>();
while (en.hasMoreElements()) {
URL url = en.nextElement();
String urlString = url.toString();
String path = URLDecoder.decode(url.getPath(), DevModeHandler.start(config, builder.npmFolder, nodeTasksFuture)
StandardCharsets.UTF_8.name()); }
Matcher jarMatcher = JAR_FILE_REGEX.matcher(path);
Matcher zipProtocolJarMatcher = ZIP_PROTOCOL_JAR_FILE_REGEX private fun log(): Logger {
.matcher(path); return LoggerFactory.getLogger(DevModeInitializer::class.java)
Matcher dirMatcher = DIR_REGEX_FRONTEND_DEFAULT.matcher(path); }
Matcher dirCompatibilityMatcher = DIR_REGEX_COMPATIBILITY_FRONTEND_DEFAULT
.matcher(path); /*
Matcher jarVfsMatcher = VFS_FILE_REGEX.matcher(urlString); * Accept user.dir or cwd as a fallback only if the directory seems to be a
Matcher dirVfsMatcher = VFS_DIRECTORY_REGEX.matcher(urlString); * Maven or Gradle project. Check to avoid cluttering server directories
if (jarVfsMatcher.find()) { * (see tickets #8249, #8403).
String vfsJar = jarVfsMatcher.group(1); */
if (vfsJars.add(vfsJar)) private val baseDirectoryFallback: String
frontendFiles.add( private get() {
getPhysicalFileOfJBossVfsJar(new URL(vfsJar))); val baseDirCandidate = System.getProperty("user.dir", ".")
} else if (dirVfsMatcher.find()) { val path = Paths.get(baseDirCandidate)
URL vfsDirUrl = new URL(urlString.substring(0, return if (path.toFile().isDirectory
urlString.lastIndexOf(resourcesFolder))); && (path.resolve("pom.xml").toFile().exists()
frontendFiles || path.resolve("build.gradle").toFile().exists())
.add(getPhysicalFileOfJBossVfsDirectory(vfsDirUrl)); ) {
} else if (jarMatcher.find()) { path.toString()
frontendFiles.add(new File(jarMatcher.group(1)));
} else if ("zip".equalsIgnoreCase(url.getProtocol())
&& zipProtocolJarMatcher.find()) {
frontendFiles.add(new File(zipProtocolJarMatcher.group(1)));
} else if (dirMatcher.find()) {
frontendFiles.add(new File(dirMatcher.group(1)));
} else if (dirCompatibilityMatcher.find()) {
frontendFiles
.add(new File(dirCompatibilityMatcher.group(1)));
} else { } else {
log().warn( throw IllegalStateException(
"Resource {} not visited because does not meet supported formats.", String.format(
url.getPath()); "Failed to determine project directory for dev mode. "
+ "Directory '%s' does not look like a Maven or "
+ "Gradle project. Ensure that you have run the "
+ "prepare-frontend Maven goal, which generates "
+ "'flow-build-info.json', prior to deploying your "
+ "application",
path.toString()
)
)
} }
} }
} catch (IOException e) {
throw new UncheckedIOException(e); /*
* This method returns all folders of jar files having files in the
* META-INF/resources/frontend folder. We don't use URLClassLoader because
* will fail in Java 9+
*/
@Throws(ServletException::class)
fun getFrontendLocationsFromClassloader(classLoader: ClassLoader): Set<File> {
val frontendFiles: MutableSet<File> = HashSet()
frontendFiles.addAll(
getFrontendLocationsFromClassloader(
classLoader,
Constants.RESOURCES_FRONTEND_DEFAULT
)
)
frontendFiles.addAll(
getFrontendLocationsFromClassloader(
classLoader,
Constants.COMPATIBILITY_RESOURCES_FRONTEND_DEFAULT
)
)
return frontendFiles
} }
return frontendFiles;
}
private static File getPhysicalFileOfJBossVfsDirectory(URL url) private fun runNodeTasks(vaadinContext: VaadinContext, tokenFileData: JsonObject, tasks: NodeTasks) {
throws IOException, ServletException { try {
try { tasks.execute()
Object virtualFile = url.openConnection().getContent(); val chunk = FrontendUtils.readFallbackChunk(tokenFileData)
Class virtualFileClass = virtualFile.getClass(); if (chunk != null) {
vaadinContext.setAttribute(chunk)
// Reflection as we cannot afford a dependency to WildFly or JBoss }
Method getChildrenRecursivelyMethod = virtualFileClass } catch (exception: ExecutionFailedException) {
.getMethod("getChildrenRecursively"); log().debug("Could not initialize dev mode handler. One of the node tasks failed", exception)
Method getPhysicalFileMethod = virtualFileClass throw CompletionException(exception)
.getMethod("getPhysicalFile");
// By calling getPhysicalFile, we make sure that the corresponding
// physical files/directories of the root directory and its children
// are created. Later, these physical files are scanned to collect
// their resources.
List virtualFiles = (List) getChildrenRecursivelyMethod
.invoke(virtualFile);
File rootDirectory = (File) getPhysicalFileMethod
.invoke(virtualFile);
for (Object child : virtualFiles) {
// side effect: create real-world files
getPhysicalFileMethod.invoke(child);
} }
return rootDirectory;
} catch (NoSuchMethodException | IllegalAccessException
| InvocationTargetException exc) {
throw new ServletException("Failed to invoke JBoss VFS API.", exc);
} }
}
private static File getPhysicalFileOfJBossVfsJar(URL url) @Throws(ServletException::class)
throws IOException, ServletException { private fun getFrontendLocationsFromClassloader(classLoader: ClassLoader, resourcesFolder: String): Set<File> {
try { val frontendFiles: MutableSet<File> = HashSet()
Object jarVirtualFile = url.openConnection().getContent(); try {
val en = classLoader.getResources(resourcesFolder) ?: return frontendFiles
val vfsJars: MutableSet<String> = HashSet()
while (en.hasMoreElements()) {
val url = en.nextElement()
val urlString = url.toString()
val path = URLDecoder.decode(url.path, StandardCharsets.UTF_8.name())
// Creating a temporary jar file out of the vfs files val jarMatcher = JAR_FILE_REGEX.matcher(path)
String vfsJarPath = url.toString(); val zipProtocolJarMatcher = ZIP_PROTOCOL_JAR_FILE_REGEX.matcher(path)
String fileNamePrefix = vfsJarPath.substring( val dirMatcher = DIR_REGEX_FRONTEND_DEFAULT.matcher(path)
vfsJarPath.lastIndexOf('/') + 1, val dirCompatibilityMatcher = DIR_REGEX_COMPATIBILITY_FRONTEND_DEFAULT.matcher(path)
vfsJarPath.lastIndexOf(".jar")); val jarVfsMatcher = VFS_FILE_REGEX.matcher(urlString)
Path tempJar = Files.createTempFile(fileNamePrefix, ".jar"); val dirVfsMatcher = VFS_DIRECTORY_REGEX.matcher(urlString)
generateJarFromJBossVfsFolder(jarVirtualFile, tempJar); if (jarVfsMatcher.find()) {
val vfsJar = jarVfsMatcher.group(1)
File tempJarFile = tempJar.toFile(); if (vfsJars.add(vfsJar)) frontendFiles.add(
tempJarFile.deleteOnExit(); getPhysicalFileOfJBossVfsJar(URL(vfsJar))
return tempJarFile; )
} catch (NoSuchMethodException | IllegalAccessException } else if (dirVfsMatcher.find()) {
| InvocationTargetException exc) { val vfsDirUrl = URL(
throw new ServletException("Failed to invoke JBoss VFS API.", exc); urlString.substring(0,urlString.lastIndexOf(resourcesFolder)
)
)
frontendFiles.add(getPhysicalFileOfJBossVfsDirectory(vfsDirUrl))
} else if (jarMatcher.find()) {
frontendFiles.add(File(jarMatcher.group(1)))
} else if ("zip".equals(url.protocol, ignoreCase = true) && zipProtocolJarMatcher.find()
) {
frontendFiles.add(File(zipProtocolJarMatcher.group(1)))
} else if (dirMatcher.find()) {
frontendFiles.add(File(dirMatcher.group(1)))
} else if (dirCompatibilityMatcher.find()) {
frontendFiles.add(File(dirCompatibilityMatcher.group(1)))
} else {
log().warn("Resource {} not visited because does not meet supported formats.", url.path)
}
}
} catch (e: IOException) {
throw UncheckedIOException(e)
}
return frontendFiles
} }
}
private static void generateJarFromJBossVfsFolder(Object jarVirtualFile, @Throws(IOException::class, ServletException::class)
Path tempJar) throws IOException, IllegalAccessException, private fun getPhysicalFileOfJBossVfsDirectory(url: URL): File {
InvocationTargetException, NoSuchMethodException { return try {
// We should use reflection to use JBoss VFS API as we cannot afford a val virtualFile = url.openConnection().content
// dependency to WildFly or JBoss val virtualFileClass: Class<*> = virtualFile.javaClass
Class virtualFileClass = jarVirtualFile.getClass();
Method getChildrenRecursivelyMethod = virtualFileClass
.getMethod("getChildrenRecursively");
Method openStreamMethod = virtualFileClass.getMethod("openStream");
Method isFileMethod = virtualFileClass.getMethod("isFile");
Method getPathNameRelativeToMethod = virtualFileClass
.getMethod("getPathNameRelativeTo", virtualFileClass);
List jarVirtualChildren = (List) getChildrenRecursivelyMethod // Reflection as we cannot afford a dependency to WildFly or JBoss
.invoke(jarVirtualFile); val getChildrenRecursivelyMethod = virtualFileClass.getMethod("getChildrenRecursively")
try (ZipOutputStream zipOutputStream = new ZipOutputStream( val getPhysicalFileMethod = virtualFileClass.getMethod("getPhysicalFile")
Files.newOutputStream(tempJar))) {
for (Object child : jarVirtualChildren) {
if (!(Boolean) isFileMethod.invoke(child))
continue;
String relativePath = (String) getPathNameRelativeToMethod // By calling getPhysicalFile, we make sure that the corresponding
.invoke(child, jarVirtualFile); // physical files/directories of the root directory and its children
InputStream inputStream = (InputStream) openStreamMethod // are created. Later, these physical files are scanned to collect
.invoke(child); // their resources.
ZipEntry zipEntry = new ZipEntry(relativePath); val virtualFiles = getChildrenRecursivelyMethod.invoke(virtualFile) as List<*>
zipOutputStream.putNextEntry(zipEntry); val rootDirectory = getPhysicalFileMethod.invoke(virtualFile) as File
IOUtils.copy(inputStream, zipOutputStream); for (child in virtualFiles) {
zipOutputStream.closeEntry(); // side effect: create real-world files
getPhysicalFileMethod.invoke(child)
}
rootDirectory
} catch (exc: NoSuchMethodException) {
throw ServletException("Failed to invoke JBoss VFS API.", exc)
} catch (exc: IllegalAccessException) {
throw ServletException("Failed to invoke JBoss VFS API.", exc)
} catch (exc: InvocationTargetException) {
throw ServletException("Failed to invoke JBoss VFS API.", exc)
}
}
@Throws(IOException::class, ServletException::class)
private fun getPhysicalFileOfJBossVfsJar(url: URL): File {
return try {
val jarVirtualFile = url.openConnection().content
// Creating a temporary jar file out of the vfs files
val vfsJarPath = url.toString()
val fileNamePrefix = vfsJarPath.substring(vfsJarPath.lastIndexOf('/') + 1, vfsJarPath.lastIndexOf(".jar"))
val tempJar = Files.createTempFile(fileNamePrefix, ".jar")
generateJarFromJBossVfsFolder(jarVirtualFile, tempJar)
val tempJarFile = tempJar.toFile()
tempJarFile.deleteOnExit()
tempJarFile
} catch (exc: NoSuchMethodException) {
throw ServletException("Failed to invoke JBoss VFS API.", exc)
} catch (exc: IllegalAccessException) {
throw ServletException("Failed to invoke JBoss VFS API.", exc)
} catch (exc: InvocationTargetException) {
throw ServletException("Failed to invoke JBoss VFS API.", exc)
}
}
@Throws(IOException::class, IllegalAccessException::class, InvocationTargetException::class, NoSuchMethodException::class)
private fun generateJarFromJBossVfsFolder(jarVirtualFile: Any, tempJar: Path) {
// We should use reflection to use JBoss VFS API as we cannot afford a
// dependency to WildFly or JBoss
val virtualFileClass: Class<*> = jarVirtualFile.javaClass
val getChildrenRecursivelyMethod = virtualFileClass.getMethod("getChildrenRecursively")
val openStreamMethod = virtualFileClass.getMethod("openStream")
val isFileMethod = virtualFileClass.getMethod("isFile")
val getPathNameRelativeToMethod = virtualFileClass.getMethod("getPathNameRelativeTo", virtualFileClass)
val jarVirtualChildren = getChildrenRecursivelyMethod.invoke(jarVirtualFile) as List<*>
ZipOutputStream(Files.newOutputStream(tempJar)).use { zipOutputStream ->
for (child in jarVirtualChildren) {
if (!(isFileMethod.invoke(child) as Boolean)) continue
val relativePath = getPathNameRelativeToMethod.invoke(child, jarVirtualFile) as String
val inputStream = openStreamMethod.invoke(child) as InputStream
val zipEntry = ZipEntry(relativePath)
zipOutputStream.putNextEntry(zipEntry)
IOUtils.copy(inputStream, zipOutputStream)
zipOutputStream.closeEntry()
}
} }
} }
} }