diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yml index 82962a3fa52..408bd4d91b5 100644 --- a/.github/workflows/validation.yml +++ b/.github/workflows/validation.yml @@ -1,7 +1,7 @@ name: Flow Validation on: push: - branches: [main, '24.5', '24.4', '24.3', '23.5', '9.1'] + branches: [main, '24.6', '24.5', '24.4', '23.5'] workflow_dispatch: pull_request_target: types: [opened, synchronize, reopened, edited] diff --git a/README.md b/README.md index 6a111427861..0e80d833d64 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,12 @@ Ask questions about Vaadin Flow in our [forum](https://vaadin.com/forum/c/flow/8 Since [Vaadin platform 23.0](https://github.com/vaadin/platform), Flow major and minor versions are aligned with platform versions: -| Branch | [Platform Version](https://github.com/vaadin/platform/releases) | [Flow Version](https://github.com/vaadin/flow/releases) | -|--------|-----------------------------------------------------------------|---------------------------------------------------------| -| 1.0 | 10 (LTS) | 1.0 | -| 2.10 | 14.11 (LATEST free with Java 8+ support and Servlet) | 2.10 | -| 2.11 | 14.12 (LATEST commercial with Java 8+ support and Servlet 3) | 2.11 | -| 23.4 | 23.4 (LATEST free with Java 11+ support and Servlet 3) | 23.4 | -| 23.5 | 23.5 (LATEST commercial with Java 11+ support and Servlet 3) | 23.5 | -| 24.5 | 24.5 (LATEST release, Java 17+, Jakarta EE 10, Spring-boot 3) | 24.5 | -| main | 24.6 (Vaadin 24.6 preparations) | 24.6 | +| Branch | [Platform Version](https://github.com/vaadin/platform/releases) | [Flow Version](https://github.com/vaadin/flow/releases) | +|--------|-------------------------------------------------------------------------|---------------------------------------------------------| +| 1.0 | 10 (LTS) | 1.0 | +| 2.11 | 14.12 (LATEST commercial with Java 8+ support and Servlet 3) | 2.11 | +| 23.5 | 23.5 (LATEST commercial with Java 11+ support and Servlet 3) | 23.5 | +| 24.4 | 24.4 (maintained minor release, Java 17+, Jakarta EE 10, Spring-boot 3) | 24.4 | +| 24.5 | 24.5 (LATEST release, Java 17+, Jakarta EE 10, Spring-boot 3) | 24.5 | +| 24.6 | 24.6 (Vaadin 24.6 pre-release) | 24.6 | +| main | 24.7 (Vaadin 24.7 preparations) | 24.7 | diff --git a/flow-bom/pom.xml b/flow-bom/pom.xml index d172d70542b..ec65a4025a2 100644 --- a/flow-bom/pom.xml +++ b/flow-bom/pom.xml @@ -4,7 +4,7 @@ com.vaadin flow-project - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-bom pom diff --git a/flow-client/pom.xml b/flow-client/pom.xml index 92c79dea5a1..858f9ec64e9 100644 --- a/flow-client/pom.xml +++ b/flow-client/pom.xml @@ -6,7 +6,7 @@ com.vaadin flow-project - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-client Flow Client diff --git a/flow-client/src/main/frontend/Flow.ts b/flow-client/src/main/frontend/Flow.ts index d9f7515657d..79d82c9dd2a 100644 --- a/flow-client/src/main/frontend/Flow.ts +++ b/flow-client/src/main/frontend/Flow.ts @@ -351,7 +351,7 @@ export class Flow { script.onload = () => resolve(); script.onerror = reject; script.src = url; - const nonce = $wnd.Vaadin.Flow.nonce; + const { nonce } = $wnd.Vaadin.Flow; if (nonce !== undefined) { script.setAttribute('nonce', nonce); } @@ -376,7 +376,7 @@ export class Flow { const scriptAppId = document.createElement('script'); scriptAppId.type = 'module'; scriptAppId.setAttribute('data-app-id', appIdWithoutHashCode); - const nonce = $wnd.Vaadin.Flow.nonce; + const { nonce } = $wnd.Vaadin.Flow; if (nonce !== undefined) { scriptAppId.setAttribute('nonce', nonce); } diff --git a/flow-client/src/main/java/com/vaadin/client/communication/MessageSender.java b/flow-client/src/main/java/com/vaadin/client/communication/MessageSender.java index 9a563ff153b..6182fde8123 100644 --- a/flow-client/src/main/java/com/vaadin/client/communication/MessageSender.java +++ b/flow-client/src/main/java/com/vaadin/client/communication/MessageSender.java @@ -209,7 +209,7 @@ public void send(final JsonObject payload) { pushPendingMessage = payload; push.push(payload); } else { - Console.log("send XHR"); + Console.debug("send XHR"); registry.getXhrConnection().send(payload); } } diff --git a/flow-data/pom.xml b/flow-data/pom.xml index 9e2cb8ffe4a..d540c992ac0 100644 --- a/flow-data/pom.xml +++ b/flow-data/pom.xml @@ -6,7 +6,7 @@ com.vaadin flow-project - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-data Flow Data diff --git a/flow-dnd/pom.xml b/flow-dnd/pom.xml index 4fbb4629fc5..f88d53d8cc6 100644 --- a/flow-dnd/pom.xml +++ b/flow-dnd/pom.xml @@ -6,7 +6,7 @@ flow-project com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-dnd diff --git a/flow-dnd/src/main/java/com/vaadin/flow/component/dnd/DragSource.java b/flow-dnd/src/main/java/com/vaadin/flow/component/dnd/DragSource.java index 7e799cef943..b3fa8e28227 100644 --- a/flow-dnd/src/main/java/com/vaadin/flow/component/dnd/DragSource.java +++ b/flow-dnd/src/main/java/com/vaadin/flow/component/dnd/DragSource.java @@ -17,6 +17,8 @@ import java.util.Locale; +import org.slf4j.LoggerFactory; + import com.vaadin.flow.component.Component; import com.vaadin.flow.component.ComponentEventListener; import com.vaadin.flow.component.ComponentUtil; @@ -25,6 +27,7 @@ import com.vaadin.flow.component.dependency.JsModule; import com.vaadin.flow.component.dnd.internal.DndUtil; import com.vaadin.flow.dom.Element; +import com.vaadin.flow.dom.Style; import com.vaadin.flow.internal.nodefeature.VirtualChildrenList; import com.vaadin.flow.shared.Registration; @@ -344,6 +347,10 @@ default void setDragImage(Component dragImage) { * the y-offset of the drag image */ default void setDragImage(Component dragImage, int offsetX, int offsetY) { + if (dragImage != null && !dragImage.isVisible()) { + throw new IllegalStateException( + "Drag image element is not visible and will not show.\nMake element visible to use as drag image!"); + } if (getDragImage() != null && getDragImage() != dragImage) { // Remove drag image from the virtual children list if it's there. if (getDraggableElement().getNode() @@ -362,14 +369,12 @@ default void setDragImage(Component dragImage, int offsetX, int offsetY) { getDragSourceComponent().addAttachListener(event -> { if (!dragImage.isAttached() && dragImage.getParent().isEmpty()) { - getDraggableElement() - .appendVirtualChild(dragImage.getElement()); + appendDragElement(dragImage.getElement()); } event.unregisterListener(); }); } else { - getDraggableElement() - .appendVirtualChild(dragImage.getElement()); + appendDragElement(dragImage.getElement()); } } ComponentUtil.setData(getDragSourceComponent(), @@ -380,6 +385,21 @@ default void setDragImage(Component dragImage, int offsetX, int offsetY) { (dragImage == null ? 0 : offsetY), getDraggableElement()); } + private void appendDragElement(Element dragElement) { + if (dragElement.getTag().equals("img")) { + getDraggableElement().appendVirtualChild(dragElement); + } else { + LoggerFactory.getLogger(DragSource.class).debug( + "Attaching child to dom in position -100,-100. Consider adding the component manually to not get overlapping components on drag for element."); + getDraggableElement().appendChild(dragElement); + Style style = dragElement.getStyle(); + style.set("position", "absolute"); + style.set("top", "-100px"); + style.set("left", "-100px"); + style.set("display", "none"); + } + } + /** * Get server side drag image. This image is applied automatically in the * next drag start event in the browser. diff --git a/flow-dnd/src/main/resources/META-INF/resources/frontend/dndConnector.js b/flow-dnd/src/main/resources/META-INF/resources/frontend/dndConnector.js index 853dec45483..5450db3083b 100644 --- a/flow-dnd/src/main/resources/META-INF/resources/frontend/dndConnector.js +++ b/flow-dnd/src/main/resources/META-INF/resources/frontend/dndConnector.js @@ -93,6 +93,10 @@ window.Vaadin.Flow.dndConnector = { event.currentTarget.classList.add('v-dragged'); } if(event.currentTarget.__dragImage) { + if(event.currentTarget.__dragImage.style.display === "none") { + event.currentTarget.__dragImage.style.display = "block"; + event.currentTarget.classList.add('shown'); + } event.dataTransfer.setDragImage( event.currentTarget.__dragImage, event.currentTarget.__dragImageOffsetX, @@ -102,6 +106,10 @@ window.Vaadin.Flow.dndConnector = { __dragendListener: function (event) { event.currentTarget.classList.remove('v-dragged'); + if(event.currentTarget.classList.contains('shown')) { + event.currentTarget.classList.remove('shown'); + event.currentTarget.__dragImage.style.display = "none"; + } }, updateDragSource: function (element) { diff --git a/flow-html-components-testbench/pom.xml b/flow-html-components-testbench/pom.xml index 3ee8d24d11a..346e63a4b0b 100644 --- a/flow-html-components-testbench/pom.xml +++ b/flow-html-components-testbench/pom.xml @@ -5,7 +5,7 @@ com.vaadin flow-project - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-html-components-testbench TestBench elements for Flow HTML Components diff --git a/flow-html-components/pom.xml b/flow-html-components/pom.xml index cadcfdba62f..5b42c27b5bd 100644 --- a/flow-html-components/pom.xml +++ b/flow-html-components/pom.xml @@ -6,7 +6,7 @@ com.vaadin flow-project - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-html-components Flow HTML Components diff --git a/flow-jandex/pom.xml b/flow-jandex/pom.xml index 9c7169266b8..ccb4bc2282b 100644 --- a/flow-jandex/pom.xml +++ b/flow-jandex/pom.xml @@ -22,7 +22,7 @@ com.vaadin flow-project - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-jandex diff --git a/flow-lit-template/pom.xml b/flow-lit-template/pom.xml index b376029ca87..dcae134c8fa 100644 --- a/flow-lit-template/pom.xml +++ b/flow-lit-template/pom.xml @@ -6,7 +6,7 @@ com.vaadin flow-project - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-lit-template Flow Lit Templates Support diff --git a/flow-plugins/flow-dev-bundle-plugin/pom.xml b/flow-plugins/flow-dev-bundle-plugin/pom.xml index d2af60aeda4..3d14d33c3df 100644 --- a/flow-plugins/flow-dev-bundle-plugin/pom.xml +++ b/flow-plugins/flow-dev-bundle-plugin/pom.xml @@ -5,7 +5,7 @@ com.vaadin flow-plugins - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-dev-bundle-plugin maven-plugin diff --git a/flow-plugins/flow-dev-bundle-plugin/src/main/java/com/vaadin/flow/plugin/maven/BuildDevBundleMojo.java b/flow-plugins/flow-dev-bundle-plugin/src/main/java/com/vaadin/flow/plugin/maven/BuildDevBundleMojo.java index ede91310d0d..643bef5c7bd 100644 --- a/flow-plugins/flow-dev-bundle-plugin/src/main/java/com/vaadin/flow/plugin/maven/BuildDevBundleMojo.java +++ b/flow-plugins/flow-dev-bundle-plugin/src/main/java/com/vaadin/flow/plugin/maven/BuildDevBundleMojo.java @@ -17,27 +17,40 @@ import java.io.File; import java.io.IOException; +import java.lang.reflect.Method; import java.net.URI; import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.DependencyResolutionRequiredException; import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecution; +import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugin.descriptor.PluginDescriptor; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.classworlds.realm.ClassRealm; +import org.codehaus.plexus.classworlds.realm.NoSuchRealmException; import com.vaadin.flow.component.dependency.JavaScript; import com.vaadin.flow.component.dependency.JsModule; @@ -53,7 +66,9 @@ import com.vaadin.flow.server.frontend.installer.NodeInstaller; import com.vaadin.flow.server.frontend.installer.Platform; import com.vaadin.flow.server.frontend.scanner.ClassFinder; +import com.vaadin.flow.server.scanner.ReflectionsClassFinder; import com.vaadin.flow.theme.Theme; +import com.vaadin.flow.utils.FlowFileUtils; import static com.vaadin.flow.server.Constants.VAADIN_SERVLET_RESOURCES; import static com.vaadin.flow.server.Constants.VAADIN_WEBAPP_RESOURCES; @@ -131,6 +146,9 @@ public class BuildDevBundleMojo extends AbstractMojo @Parameter(defaultValue = "${project}", readonly = true, required = true) MavenProject project; + @Parameter(defaultValue = "${mojoExecution}") + MojoExecution mojoExecution; + /** * The folder where `package.json` file is located. Default is project root * dir. @@ -175,8 +193,33 @@ public class BuildDevBundleMojo extends AbstractMojo @Parameter(property = InitParameters.NPM_EXCLUDE_WEB_COMPONENTS, defaultValue = "false") private boolean npmExcludeWebComponents; + static final String CLASSFINDER_FIELD_NAME = "classFinder"; + + private ClassFinder classFinder; + @Override - public void execute() throws MojoFailureException { + public void execute() throws MojoExecutionException, MojoFailureException { + PluginDescriptor pluginDescriptor = mojoExecution.getMojoDescriptor() + .getPluginDescriptor(); + checkFlowCompatibility(pluginDescriptor); + + Reflector reflector = getOrCreateReflector(); + ClassLoader tccl = Thread.currentThread().getContextClassLoader(); + Thread.currentThread() + .setContextClassLoader(reflector.getIsolatedClassLoader()); + try { + org.apache.maven.plugin.Mojo task = reflector.createMojo(this); + findExecuteMethod(task.getClass()).invoke(task); + } catch (MojoExecutionException | MojoFailureException e) { + throw e; + } catch (Exception e) { + throw new MojoFailureException(e.getMessage(), e); + } finally { + Thread.currentThread().setContextClassLoader(tccl); + } + } + + public void executeInternal() throws MojoFailureException { long start = System.nanoTime(); try { @@ -243,7 +286,9 @@ public boolean compressBundle() { * @param project * a given MavenProject * @return List of ClasspathElements + * @deprecated will be removed without replacement. */ + @Deprecated(forRemoval = true) public static List getClasspathElements(MavenProject project) { try { @@ -286,11 +331,13 @@ public File generatedTsFolder() { @Override public ClassFinder getClassFinder() { - - List classpathElements = getClasspathElements(project); - - return BuildFrontendUtil.getClassFinder(classpathElements); - + if (classFinder == null) { + URLClassLoader classLoader = getOrCreateReflector() + .getIsolatedClassLoader(); + classFinder = new ReflectionsClassFinder(classLoader, + classLoader.getURLs()); + } + return classFinder; } @Override @@ -483,4 +530,126 @@ public boolean checkRuntimeDependency(String groupId, String artifactId, public boolean isNpmExcludeWebComponents() { return npmExcludeWebComponents; } + + private static URLClassLoader createIsolatedClassLoader( + MavenProject project, MojoExecution mojoExecution) { + List urls = new ArrayList<>(); + String outputDirectory = project.getBuild().getOutputDirectory(); + if (outputDirectory != null) { + urls.add(FlowFileUtils.convertToUrl(new File(outputDirectory))); + } + + Function keyMapper = artifact -> artifact.getGroupId() + + ":" + artifact.getArtifactId(); + + Map projectDependencies = new HashMap<>(project + .getArtifacts().stream() + .filter(artifact -> artifact.getFile() != null + && artifact.getArtifactHandler().isAddedToClasspath() + && (Artifact.SCOPE_COMPILE.equals(artifact.getScope()) + || Artifact.SCOPE_RUNTIME + .equals(artifact.getScope()) + || Artifact.SCOPE_SYSTEM + .equals(artifact.getScope()) + || (Artifact.SCOPE_PROVIDED + .equals(artifact.getScope()) + && artifact.getFile().getPath().matches( + INCLUDE_FROM_COMPILE_DEPS_REGEX)))) + .collect(Collectors.toMap(keyMapper, Function.identity()))); + if (mojoExecution != null) { + mojoExecution.getMojoDescriptor().getPluginDescriptor() + .getArtifacts().stream() + .filter(artifact -> !projectDependencies + .containsKey(keyMapper.apply(artifact))) + .forEach(artifact -> projectDependencies + .put(keyMapper.apply(artifact), artifact)); + } + + projectDependencies.values().stream() + .map(artifact -> FlowFileUtils.convertToUrl(artifact.getFile())) + .forEach(urls::add); + ClassLoader mavenApiClassLoader; + if (mojoExecution != null) { + ClassRealm pluginClassRealm = mojoExecution.getMojoDescriptor() + .getPluginDescriptor().getClassRealm(); + try { + mavenApiClassLoader = pluginClassRealm.getWorld() + .getRealm("maven.api"); + } catch (NoSuchRealmException e) { + throw new RuntimeException(e); + } + } else { + mavenApiClassLoader = org.apache.maven.plugin.Mojo.class + .getClassLoader(); + if (mavenApiClassLoader instanceof ClassRealm classRealm) { + try { + mavenApiClassLoader = classRealm.getWorld() + .getRealm("maven.api"); + } catch (NoSuchRealmException e) { + // Should never happen. In case, ignore the error and use + // class loader from the Maven class + } + } + } + return new URLClassLoader(urls.toArray(URL[]::new), + mavenApiClassLoader); + } + + private void checkFlowCompatibility(PluginDescriptor pluginDescriptor) { + Predicate isFlowServer = artifact -> "com.vaadin" + .equals(artifact.getGroupId()) + && "flow-server".equals(artifact.getArtifactId()); + String projectFlowVersion = project.getArtifacts().stream() + .filter(isFlowServer).map(Artifact::getVersion).findFirst() + .orElse(null); + String pluginFlowVersion = pluginDescriptor.getArtifacts().stream() + .filter(isFlowServer).map(Artifact::getVersion).findFirst() + .orElse(null); + if (!Objects.equals(projectFlowVersion, pluginFlowVersion)) { + getLog().warn( + "Vaadin Flow used in project does not match the version expected by the Vaadin plugin. " + + "Flow version for project is " + + projectFlowVersion + + ", Vaadin plugin is built for Flow version " + + pluginFlowVersion + "."); + } + } + + private Reflector getOrCreateReflector() { + Map pluginContext = getPluginContext(); + String pluginKey = mojoExecution.getPlugin().getKey(); + String reflectorKey = Reflector.class.getName() + "-" + pluginKey + "-" + + mojoExecution.getLifecyclePhase(); + if (pluginContext != null && pluginContext.containsKey(reflectorKey)) { + getLog().debug("Using cached Reflector for plugin " + pluginKey + + " and phase " + mojoExecution.getLifecyclePhase()); + return Reflector.adapt(pluginContext.get(reflectorKey)); + } + Reflector reflector = Reflector.of(project, mojoExecution); + if (pluginContext != null) { + pluginContext.put(reflectorKey, reflector); + getLog().debug("Cached Reflector for plugin " + pluginKey + + " and phase " + mojoExecution.getLifecyclePhase()); + } + return reflector; + } + + private Method findExecuteMethod(Class taskClass) + throws NoSuchMethodException { + + while (taskClass != null && taskClass != Object.class) { + try { + Method executeInternal = taskClass + .getDeclaredMethod("executeInternal"); + executeInternal.setAccessible(true); + return executeInternal; + } catch (NoSuchMethodException e) { + // ignore + } + taskClass = taskClass.getSuperclass(); + } + throw new NoSuchMethodException( + "Method executeInternal not found in " + getClass().getName()); + } + } diff --git a/flow-plugins/flow-dev-bundle-plugin/src/main/java/com/vaadin/flow/plugin/maven/Reflector.java b/flow-plugins/flow-dev-bundle-plugin/src/main/java/com/vaadin/flow/plugin/maven/Reflector.java new file mode 100644 index 00000000000..b130a235255 --- /dev/null +++ b/flow-plugins/flow-dev-bundle-plugin/src/main/java/com/vaadin/flow/plugin/maven/Reflector.java @@ -0,0 +1,409 @@ +/* + * Copyright 2000-2024 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.plugin.maven; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.plugin.Mojo; +import org.apache.maven.plugin.MojoExecution; +import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.classworlds.realm.ClassRealm; +import org.codehaus.plexus.classworlds.realm.NoSuchRealmException; + +import com.vaadin.flow.internal.ReflectTools; +import com.vaadin.flow.server.frontend.scanner.ClassFinder; +import com.vaadin.flow.server.scanner.ReflectionsClassFinder; +import com.vaadin.flow.utils.FlowFileUtils; + +/** + * Helper class to deal with classloading of Flow plugin mojos. + */ +public final class Reflector { + + public static final String INCLUDE_FROM_COMPILE_DEPS_REGEX = ".*(/|\\\\)(portlet-api|javax\\.servlet-api)-.+jar$"; + private static final Set DEPENDENCIES_GROUP_EXCLUSIONS = Set.of( + "org.apache.maven", "org.codehaus.plexus", "org.slf4j", + "org.eclipse.sisu"); + + private final URLClassLoader isolatedClassLoader; + private Object classFinder; + + /** + * Creates a new reflector instance for the given classloader. + * + * @param isolatedClassLoader + * class loader to be used to create mojo instances. + */ + public Reflector(URLClassLoader isolatedClassLoader) { + this.isolatedClassLoader = isolatedClassLoader; + } + + private Reflector(URLClassLoader isolatedClassLoader, Object classFinder) { + this.isolatedClassLoader = isolatedClassLoader; + this.classFinder = classFinder; + } + + /** + * Gets a {@link Reflector} instance usable with the caller class loader. + *

+ *

+ * Reflector instances are cached in Maven plugin context, but instances + * might be associated to the plugin class loader, thus not working with + * classes loaded by the isolated class loader. This method returns the + * input object if it is compatible with the class loader, otherwise it + * creates a copy referencing the same isolated class loader and + * {@link ClassFinder}. + * + * @param reflector + * the {@link Reflector} instance. + * @return a {@link Reflector} instance compatible with the current class + * loader. + * @throws IllegalArgumentException + * if the input object is not a {@link Reflector} instance or if + * it is not possible to make a copy for it due to class + * definition incompatibilities. + */ + static Reflector adapt(Object reflector) { + if (reflector instanceof Reflector sameClassLoader) { + return sameClassLoader; + } else if (Reflector.class.getName() + .equals(reflector.getClass().getName())) { + Class reflectorClass = reflector.getClass(); + try { + URLClassLoader classLoader = (URLClassLoader) ReflectTools + .getJavaFieldValue(reflector, + findField(reflectorClass, + "isolatedClassLoader"), + URLClassLoader.class); + Object classFinder = ReflectTools.getJavaFieldValue(reflector, + findField(reflectorClass, "classFinder")); + return new Reflector(classLoader, classFinder); + } catch (Exception e) { + throw new IllegalArgumentException( + "Object of type " + reflector.getClass().getName() + + " is not a compatible Reflector", + e); + } + } + throw new IllegalArgumentException( + "Object of type " + reflector.getClass().getName() + + " is not a compatible Reflector"); + } + + /** + * Gets the isolated class loader. + * + * @return the isolated class loader. + */ + public URLClassLoader getIsolatedClassLoader() { + return isolatedClassLoader; + } + + /** + * Loads the class with the given name from the isolated classloader. + * + * @param className + * the name of the class to load. + * @return the class object. + * @throws ClassNotFoundException + * if the class was not found. + */ + public Class loadClass(String className) throws ClassNotFoundException { + return isolatedClassLoader.loadClass(className); + } + + /** + * Get a resource from the classpath of the isolated class loader. + * + * @param name + * class literal + * @return the resource + */ + public URL getResource(String name) { + return isolatedClassLoader.getResource(name); + } + + /** + * Creates a copy of the given Flow mojo, loading classes the isolated + * classloader. + *

+ *

+ * Loads the given mojo class from the isolated class loader and then + * creates a new instance for it and fills all field copying values from the + * original mojo. The input mojo must have a public no-args constructor. + * Mojo fields must reference types that can be safely loaded be the + * isolated class loader, such as JDK or Maven core API. It also creates and + * injects a {@link ClassFinder}, based on the isolated class loader. + * + * @param sourceMojo + * The mojo for which to create the instance from the isolated + * class loader. + * @return an instance of the mojo loaded from the isolated class loader. + * @throws Exception + * if the mojo instance cannot be created. + */ + public Mojo createMojo(BuildDevBundleMojo sourceMojo) throws Exception { + Class targetMojoClass = loadClass(sourceMojo.getClass().getName()); + Object targetMojo = targetMojoClass.getConstructor().newInstance(); + copyFields(sourceMojo, targetMojo); + Field classFinderField = findField(targetMojoClass, + BuildDevBundleMojo.CLASSFINDER_FIELD_NAME); + ReflectTools.setJavaFieldValue(targetMojo, classFinderField, + getOrCreateClassFinder()); + return (Mojo) targetMojo; + } + + /** + * Gets a new {@link Reflector} instance for the current Mojo execution. + *

+ *

+ * An isolated class loader is created based on project and plugin + * dependencies, with the first ones having precedence over the seconds. The + * maven.api class realm is used as parent classloader, allowing usage of + * Maven core classes in the mojo. + * + * @param project + * the maven project. + * @param mojoExecution + * the current mojo execution. + * @return a Reflector instance for the current maven execution. + */ + public static Reflector of(MavenProject project, + MojoExecution mojoExecution) { + URLClassLoader classLoader = createIsolatedClassLoader(project, + mojoExecution); + return new Reflector(classLoader); + } + + private synchronized Object getOrCreateClassFinder() throws Exception { + if (classFinder == null) { + Class classFinderImplClass = loadClass( + ReflectionsClassFinder.class.getName()); + classFinder = classFinderImplClass + .getConstructor(ClassLoader.class, URL[].class).newInstance( + isolatedClassLoader, isolatedClassLoader.getURLs()); + } + return classFinder; + } + + private static URLClassLoader createIsolatedClassLoader( + MavenProject project, MojoExecution mojoExecution) { + List urls = new ArrayList<>(); + String outputDirectory = project.getBuild().getOutputDirectory(); + if (outputDirectory != null) { + urls.add(FlowFileUtils.convertToUrl(new File(outputDirectory))); + } + + Function keyMapper = artifact -> artifact.getGroupId() + + ":" + artifact.getArtifactId(); + + Map projectDependencies = new HashMap<>(project + .getArtifacts().stream() + // Exclude all maven artifacts to prevent class loading clash + // with maven.api class realm + .filter(artifact -> !DEPENDENCIES_GROUP_EXCLUSIONS + .contains(artifact.getGroupId())) + .filter(artifact -> artifact.getFile() != null + && artifact.getArtifactHandler().isAddedToClasspath() + && (Artifact.SCOPE_COMPILE.equals(artifact.getScope()) + || Artifact.SCOPE_RUNTIME + .equals(artifact.getScope()) + || Artifact.SCOPE_SYSTEM + .equals(artifact.getScope()) + || (Artifact.SCOPE_PROVIDED + .equals(artifact.getScope()) + && artifact.getFile().getPath().matches( + INCLUDE_FROM_COMPILE_DEPS_REGEX)))) + .collect(Collectors.toMap(keyMapper, Function.identity()))); + if (mojoExecution != null) { + mojoExecution.getMojoDescriptor().getPluginDescriptor() + .getArtifacts().stream() + // Exclude all maven artifacts to prevent class loading + // clash with maven.api class realm + .filter(artifact -> !DEPENDENCIES_GROUP_EXCLUSIONS + .contains(artifact.getGroupId())) + .filter(artifact -> !projectDependencies + .containsKey(keyMapper.apply(artifact))) + .forEach(artifact -> projectDependencies + .put(keyMapper.apply(artifact), artifact)); + } + + projectDependencies.values().stream() + .map(artifact -> FlowFileUtils.convertToUrl(artifact.getFile())) + .forEach(urls::add); + ClassLoader mavenApiClassLoader; + if (mojoExecution != null) { + ClassRealm pluginClassRealm = mojoExecution.getMojoDescriptor() + .getPluginDescriptor().getClassRealm(); + try { + mavenApiClassLoader = pluginClassRealm.getWorld() + .getRealm("maven.api"); + } catch (NoSuchRealmException e) { + throw new RuntimeException(e); + } + } else { + mavenApiClassLoader = Mojo.class.getClassLoader(); + if (mavenApiClassLoader instanceof ClassRealm classRealm) { + try { + mavenApiClassLoader = classRealm.getWorld() + .getRealm("maven.api"); + } catch (NoSuchRealmException e) { + // Should never happen. In case, ignore the error and use + // class loader from the Maven class + } + } + } + return new CombinedClassLoader(urls.toArray(new URL[0]), + mavenApiClassLoader); + } + + // Tries to load class from the give class loader and fallbacks + // to Platform class loader in case of failure. + private static class CombinedClassLoader extends URLClassLoader { + private final ClassLoader delegate; + + private CombinedClassLoader(URL[] urls, ClassLoader delegate) { + super(urls, null); + this.delegate = delegate; + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + try { + return super.loadClass(name); + } catch (ClassNotFoundException e) { + // ignore and continue with delegate class loader + } + if (delegate != null) { + try { + return delegate.loadClass(name); + } catch (ClassNotFoundException e) { + // ignore and continue with platform class loader + } + } + return ClassLoader.getPlatformClassLoader().loadClass(name); + } + + @Override + public URL getResource(String name) { + URL url = super.getResource(name); + if (url == null && delegate != null) { + url = delegate.getResource(name); + } + if (url == null) { + url = ClassLoader.getPlatformClassLoader().getResource(name); + } + return url; + } + + @Override + public Enumeration getResources(String name) throws IOException { + Enumeration resources = super.getResources(name); + if (!resources.hasMoreElements() && delegate != null) { + resources = delegate.getResources(name); + } + if (!resources.hasMoreElements()) { + resources = ClassLoader.getPlatformClassLoader() + .getResources(name); + } + return resources; + } + } + + private void copyFields(BuildDevBundleMojo sourceMojo, Object targetMojo) + throws IllegalAccessException, NoSuchFieldException { + Class sourceClass = sourceMojo.getClass(); + Class targetClass = targetMojo.getClass(); + while (sourceClass != null && sourceClass != Object.class) { + for (Field sourceField : sourceClass.getDeclaredFields()) { + copyField(sourceMojo, targetMojo, sourceField, targetClass); + } + targetClass = targetClass.getSuperclass(); + sourceClass = sourceClass.getSuperclass(); + } + } + + private static void copyField(BuildDevBundleMojo sourceMojo, + Object targetMojo, Field sourceField, Class targetClass) + throws IllegalAccessException, NoSuchFieldException { + if (Modifier.isStatic(sourceField.getModifiers())) { + return; + } + sourceField.setAccessible(true); + Object value = sourceField.get(sourceMojo); + if (value == null) { + return; + } + Field targetField; + try { + targetField = targetClass.getDeclaredField(sourceField.getName()); + } catch (NoSuchFieldException ex) { + // Should never happen, since the class definition should be + // the same + String message = "Field " + sourceField.getName() + " defined in " + + sourceField.getDeclaringClass().getName() + + " is missing in " + targetClass.getName(); + sourceMojo.logError(message, ex); + throw ex; + } + + Class targetFieldType = targetField.getType(); + if (!targetFieldType.isAssignableFrom(sourceField.getType())) { + String message = "Field " + targetFieldType.getName() + " in class " + + targetClass.getName() + " of type " + + targetFieldType.getName() + + " is loaded from different class loaders." + + " Source class loader: " + + sourceField.getType().getClassLoader() + + ", Target class loader: " + + targetFieldType.getClassLoader() + + ". This is likely a bug in the Vaadin Maven plugin." + + " Please, report the error on the issue tracker."; + sourceMojo.logError(message); + throw new NoSuchFieldException(message); + } + targetField.setAccessible(true); + targetField.set(targetMojo, value); + } + + private static Field findField(Class clazz, String fieldName) + throws NoSuchFieldException { + while (clazz != null && !clazz.equals(Object.class)) { + try { + return clazz.getDeclaredField(fieldName); + } catch (NoSuchFieldException e) { + clazz = clazz.getSuperclass(); + } + } + throw new NoSuchFieldException(fieldName); + } + +} \ No newline at end of file diff --git a/flow-plugins/flow-gradle-plugin/README.md b/flow-plugins/flow-gradle-plugin/README.md index d33612aca34..960e50704c4 100644 --- a/flow-plugins/flow-gradle-plugin/README.md +++ b/flow-plugins/flow-gradle-plugin/README.md @@ -209,7 +209,7 @@ Alternatively, you can build and publish the Flow Gradle plugin into the local M 1. Clone the Base Starter Gradle project. 2. Add `mavenLocal()` to `buildscript.repositories` as the first place to look up. -3. Add `dependencies { classpath 'com.vaadin:flow-gradle-plugin:24.6-SNAPSHOT' }` to `buildscript.repositories`. +3. Add `dependencies { classpath 'com.vaadin:flow-gradle-plugin:24.7-SNAPSHOT' }` to `buildscript.repositories`. 4. Run `./gradlew clean build publishToMavenLocal` in the `flow-plugins/flow-gradle-plugin` repo folder. 5. Run the previous command with `-x functionalTest` to skip functional tests. 6. If you now run `./gradlew vaadinPrepareFrontend` in the Starter project folder, Gradle will use the local version of the Flow plugin. You can verify that by adding `println()` statements into the `VaadinPrepareFrontendTask` class. diff --git a/flow-plugins/flow-gradle-plugin/pom.xml b/flow-plugins/flow-gradle-plugin/pom.xml index 9a808ca4104..9c6a9841a5c 100644 --- a/flow-plugins/flow-gradle-plugin/pom.xml +++ b/flow-plugins/flow-gradle-plugin/pom.xml @@ -3,7 +3,7 @@ com.vaadin flow-plugins - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-gradle-plugin diff --git a/flow-plugins/flow-gradle-plugin/src/functionalTest/kotlin/com/vaadin/gradle/AbstractGradleTest.kt b/flow-plugins/flow-gradle-plugin/src/functionalTest/kotlin/com/vaadin/gradle/AbstractGradleTest.kt index 6262138a14c..df0f8a16d3e 100644 --- a/flow-plugins/flow-gradle-plugin/src/functionalTest/kotlin/com/vaadin/gradle/AbstractGradleTest.kt +++ b/flow-plugins/flow-gradle-plugin/src/functionalTest/kotlin/com/vaadin/gradle/AbstractGradleTest.kt @@ -27,7 +27,7 @@ import java.io.File */ abstract class AbstractGradleTest { - val flowVersion = System.getenv("vaadin.version").takeUnless { it.isNullOrEmpty() } ?: "24.6-SNAPSHOT" + val flowVersion = System.getenv("vaadin.version").takeUnless { it.isNullOrEmpty() } ?: "24.7-SNAPSHOT" val slf4jVersion = "2.0.3" /** diff --git a/flow-plugins/flow-gradle-plugin/src/main/kotlin/com/vaadin/gradle/VaadinFlowPluginExtension.kt b/flow-plugins/flow-gradle-plugin/src/main/kotlin/com/vaadin/gradle/VaadinFlowPluginExtension.kt index 93380385efa..a5cc38b75e1 100644 --- a/flow-plugins/flow-gradle-plugin/src/main/kotlin/com/vaadin/gradle/VaadinFlowPluginExtension.kt +++ b/flow-plugins/flow-gradle-plugin/src/main/kotlin/com/vaadin/gradle/VaadinFlowPluginExtension.kt @@ -290,7 +290,7 @@ public abstract class VaadinFlowPluginExtension @Inject constructor(private val public abstract val frontendExtraFileExtensions: ListProperty /** - * Whether to include web component npm packages in packages.json + * Whether to exclude Vaadin web component npm packages in packages.json */ public abstract val npmExcludeWebComponents: Property diff --git a/flow-plugins/flow-maven-plugin/pom.xml b/flow-plugins/flow-maven-plugin/pom.xml index ed1557f1ddd..dacfaefd4a5 100644 --- a/flow-plugins/flow-maven-plugin/pom.xml +++ b/flow-plugins/flow-maven-plugin/pom.xml @@ -1,11 +1,11 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 com.vaadin flow-plugins - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-maven-plugin maven-plugin @@ -106,6 +106,35 @@ + + + org.apache.maven.plugins + maven-invoker-plugin + 3.8.1 + + ${skipTests} + ${skipTests} + target/local-repo + + com.vaadin:flow-client:${project.version} + + true + src/it/settings.xml + verify + true + + + + integration-test + + install + integration-test + verify + + + + + diff --git a/flow-plugins/flow-maven-plugin/src/it/.gitignore b/flow-plugins/flow-maven-plugin/src/it/.gitignore new file mode 100644 index 00000000000..bd1e4eb08d1 --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/it/.gitignore @@ -0,0 +1,7 @@ +**/src/main/bundles +**/src/main/frontend/generated +**/src/main/frontend/index.html +**/package*.json +**/tsconfig.json +**/types.d.ts +**/vite.*.ts \ No newline at end of file diff --git a/flow-plugins/flow-maven-plugin/src/it/appshellconfiguration-external-annotations/invoker.properties b/flow-plugins/flow-maven-plugin/src/it/appshellconfiguration-external-annotations/invoker.properties new file mode 100644 index 00000000000..a6700ec7e57 --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/it/appshellconfiguration-external-annotations/invoker.properties @@ -0,0 +1,18 @@ +# +# Copyright 2000-2024 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. +# +invoker.goals=clean package +invoker.profiles.1=prepare-frontend-after-compile +invoker.profiles.2=build-frontend-full-dep-scan \ No newline at end of file diff --git a/flow-plugins/flow-maven-plugin/src/it/appshellconfiguration-external-annotations/pom.xml b/flow-plugins/flow-maven-plugin/src/it/appshellconfiguration-external-annotations/pom.xml new file mode 100644 index 00000000000..3009df71bbf --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/it/appshellconfiguration-external-annotations/pom.xml @@ -0,0 +1,118 @@ + + + 4.0.0 + + com.vaadin.test.maven + classfinder-lookup + 1.0 + jar + + + + + UTF-8 + 17 + ${maven.compiler.release} + ${maven.compiler.release} + true + + @project.version@ + + + + + com.vaadin + flow-server + ${flow.version} + + + com.vaadin + flow-client + ${flow.version} + + + org.springframework.data + spring-data-jpa + 3.3.4 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + + com.vaadin + flow-maven-plugin + ${flow.version} + + + org.springframework.data + spring-data-commons + 3.3.4 + + + + + + + + + prepare-frontend-after-compile + + + + com.vaadin + flow-maven-plugin + + + compile + + prepare-frontend + + + + + + + + + build-frontend-full-dep-scan + + + + com.vaadin + flow-maven-plugin + + false + + + + + prepare-frontend + build-frontend + + + + + + + + + + diff --git a/flow-plugins/flow-maven-plugin/src/it/appshellconfiguration-external-annotations/src/main/java/com/vaadin/test/AppConfig.java b/flow-plugins/flow-maven-plugin/src/it/appshellconfiguration-external-annotations/src/main/java/com/vaadin/test/AppConfig.java new file mode 100644 index 00000000000..2c4d7d02ee0 --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/it/appshellconfiguration-external-annotations/src/main/java/com/vaadin/test/AppConfig.java @@ -0,0 +1,28 @@ +/* + * Copyright 2000-2024 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.test; + +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +import com.vaadin.flow.component.dependency.NpmPackage; +import com.vaadin.flow.component.page.AppShellConfigurator; + +@NpmPackage(value = "react-error-boundary", version = "4.0.13") +@EnableJpaRepositories +public class AppConfig implements AppShellConfigurator { + +} diff --git a/flow-plugins/flow-maven-plugin/src/it/classfinder-lookup/invoker.properties b/flow-plugins/flow-maven-plugin/src/it/classfinder-lookup/invoker.properties new file mode 100644 index 00000000000..44594528d31 --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/it/classfinder-lookup/invoker.properties @@ -0,0 +1,17 @@ +# +# Copyright 2000-2024 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. +# + +invoker.goals=clean package \ No newline at end of file diff --git a/flow-plugins/flow-maven-plugin/src/it/classfinder-lookup/pom.xml b/flow-plugins/flow-maven-plugin/src/it/classfinder-lookup/pom.xml new file mode 100644 index 00000000000..d147b262e7b --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/it/classfinder-lookup/pom.xml @@ -0,0 +1,68 @@ + + + 4.0.0 + + com.vaadin.test.maven + classfinder-lookup + 1.0 + jar + + + Tests that there are no class loading issues for components loaded by Lookup backed by ClassFinder + + + + UTF-8 + 17 + ${maven.compiler.release} + ${maven.compiler.release} + true + + @project.version@ + + + + + com.vaadin + flow-server + ${flow.version} + + + com.vaadin + flow-client + ${flow.version} + + + com.vaadin.test + flow-addon + 1.0.0 + system + ${project.basedir}/../flow-addon/target/flow-addon-1.0.0.jar + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + + com.vaadin + flow-maven-plugin + ${flow.version} + + + + prepare-frontend + build-frontend + + + + + + + + diff --git a/flow-plugins/flow-maven-plugin/src/it/classfinder-lookup/src/main/java/com/vaadin/test/ProjectFlowExtension.java b/flow-plugins/flow-maven-plugin/src/it/classfinder-lookup/src/main/java/com/vaadin/test/ProjectFlowExtension.java new file mode 100644 index 00000000000..fde6fd519aa --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/it/classfinder-lookup/src/main/java/com/vaadin/test/ProjectFlowExtension.java @@ -0,0 +1,21 @@ +package com.vaadin.test; + +import java.util.List; + +import com.vaadin.flow.server.frontend.Options; +import com.vaadin.flow.server.frontend.TypeScriptBootstrapModifier; +import com.vaadin.flow.server.frontend.scanner.FrontendDependenciesScanner; + +/** + * Hello world! + */ +public class ProjectFlowExtension implements TypeScriptBootstrapModifier { + + @Override + public void modify(List bootstrapTypeScript, Options options, + FrontendDependenciesScanner frontendDependenciesScanner) { + bootstrapTypeScript.add(""" + (window as any).testProject=1; + """); + } +} diff --git a/flow-plugins/flow-maven-plugin/src/it/classfinder-lookup/verify.bsh b/flow-plugins/flow-maven-plugin/src/it/classfinder-lookup/verify.bsh new file mode 100644 index 00000000000..530ee4be4b9 --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/it/classfinder-lookup/verify.bsh @@ -0,0 +1,14 @@ +import java.nio.file.*; + +vaadinTs = basedir.toPath().resolve("src/main/frontend/generated/vaadin.ts"); +if ( !Files.exists(vaadinTs, new LinkOption[0]) ) +{ + throw new RuntimeException("vaadin.ts file not generated"); +} +lines = Files.readAllLines(vaadinTs); +if (!lines.contains("(window as any).testProject=1;")) { + throw new RuntimeException("vaadin.ts does note contain lines added by project TypeScriptBootstrapModifier"); +} +if (!lines.contains("(window as any).testAddOn=1;")) { + throw new RuntimeException("vaadin.ts does note contain lines added by project dependency TypeScriptBootstrapModifier"); +} diff --git a/flow-plugins/flow-maven-plugin/src/it/flow-addon/invoker.properties b/flow-plugins/flow-maven-plugin/src/it/flow-addon/invoker.properties new file mode 100644 index 00000000000..d7243b3a7fc --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/it/flow-addon/invoker.properties @@ -0,0 +1,23 @@ +# +# Copyright 2000-2024 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. +# + +# High ordinal number to be executed first +invoker.ordinal = 100 +# Not invoking clean to make sure JAR from both executions are preserved +invoker.goals=package +invoker.profiles.1= +invoker.profiles.2=fake-flow-resources +invoker.profiles.3=fake-flow-plugin-resources diff --git a/flow-plugins/flow-maven-plugin/src/it/flow-addon/pom.xml b/flow-plugins/flow-maven-plugin/src/it/flow-addon/pom.xml new file mode 100644 index 00000000000..163310e8b76 --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/it/flow-addon/pom.xml @@ -0,0 +1,64 @@ + + + 4.0.0 + + com.vaadin.test + flow-addon + 1.0.0 + + flow-addon + + Test project to build the JAR file for other tests. + Run 'mvn package' on this module and then copy the JAR + where needed. + + + + @project.version@ + UTF-8 + 17 + ${maven.compiler.release} + ${maven.compiler.release} + true + + + + + com.vaadin + flow-server + ${vaadin.version} + + + org.apache.commons + commons-lang3 + 3.11 + + + + + + fake-flow-resources + + fake-resources-${project.version} + + + ${project.basedir}/src/main/fake-resources + + + + + + fake-flow-plugin-resources + + fake-resources-plugin-${project.version} + + + ${project.basedir}/src/main/fake-plugin-resources + + + + + + + diff --git a/flow-plugins/flow-maven-plugin/src/it/flow-addon/src/main/fake-plugin-resources/com/vaadin/flow/server/frontend/Flow.tsx b/flow-plugins/flow-maven-plugin/src/it/flow-addon/src/main/fake-plugin-resources/com/vaadin/flow/server/frontend/Flow.tsx new file mode 100644 index 00000000000..a01c615012c --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/it/flow-addon/src/main/fake-plugin-resources/com/vaadin/flow/server/frontend/Flow.tsx @@ -0,0 +1,18 @@ +/* + * Copyright 2000-2024 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. + */ + +// Resource loaded from plugin dependency +export const serverSideRoutes = [] diff --git a/flow-plugins/flow-maven-plugin/src/it/flow-addon/src/main/fake-resources/com/vaadin/flow/server/frontend/Flow.tsx b/flow-plugins/flow-maven-plugin/src/it/flow-addon/src/main/fake-resources/com/vaadin/flow/server/frontend/Flow.tsx new file mode 100644 index 00000000000..a843a54097a --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/it/flow-addon/src/main/fake-resources/com/vaadin/flow/server/frontend/Flow.tsx @@ -0,0 +1,18 @@ +/* + * Copyright 2000-2024 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. + */ + +// Resource loaded from project dependency +export const serverSideRoutes = [] diff --git a/flow-plugins/flow-maven-plugin/src/it/flow-addon/src/main/java/com/vaadin/test/Addon.java b/flow-plugins/flow-maven-plugin/src/it/flow-addon/src/main/java/com/vaadin/test/Addon.java new file mode 100644 index 00000000000..9b6ea6fe637 --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/it/flow-addon/src/main/java/com/vaadin/test/Addon.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2024 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.test; + +import java.util.List; + +import com.vaadin.flow.server.frontend.Options; +import com.vaadin.flow.server.frontend.TypeScriptBootstrapModifier; +import com.vaadin.flow.server.frontend.scanner.FrontendDependenciesScanner; + +public class Addon implements TypeScriptBootstrapModifier { + + @Override + public void modify(List bootstrapTypeScript, Options options, + FrontendDependenciesScanner frontendDependenciesScanner) { + bootstrapTypeScript.add(""" + (window as any).testAddOn=1; + """); + } +} diff --git a/flow-plugins/flow-maven-plugin/src/it/ignore-maven-deps-from-project/invoker.properties b/flow-plugins/flow-maven-plugin/src/it/ignore-maven-deps-from-project/invoker.properties new file mode 100644 index 00000000000..5aa13827263 --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/it/ignore-maven-deps-from-project/invoker.properties @@ -0,0 +1,17 @@ +# +# Copyright 2000-2024 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. +# + +invoker.goals=clean package diff --git a/flow-plugins/flow-maven-plugin/src/it/ignore-maven-deps-from-project/pom.xml b/flow-plugins/flow-maven-plugin/src/it/ignore-maven-deps-from-project/pom.xml new file mode 100644 index 00000000000..56131acf0c8 --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/it/ignore-maven-deps-from-project/pom.xml @@ -0,0 +1,88 @@ + + + 4.0.0 + + com.vaadin.test.maven + resources-from-project + 1.0 + jar + + + Tests that plugin dependencies do not override resources from project artifacts. + + + + UTF-8 + 17 + ${maven.compiler.release} + ${maven.compiler.release} + true + + @project.version@ + 3.9.9 + + + + + com.vaadin + flow-server + ${flow.version} + + + com.vaadin + flow-client + ${flow.version} + + + org.apache.maven + maven-artifact + ${maven.version} + + + org.apache.maven + maven-core + ${maven.version} + + + org.apache.maven + maven-plugin-api + ${maven.version} + + + org.slf4j + slf4j-simple + 2.0.16 + + + org.codehaus.plexus + plexus-build-api + 1.2.0 + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + + com.vaadin + flow-maven-plugin + ${flow.version} + + + + prepare-frontend + build-frontend + + + + + + + + diff --git a/flow-plugins/flow-maven-plugin/src/it/ignore-maven-deps-from-project/src/main/java/com/vaadin/test/ProjectFlowExtension.java b/flow-plugins/flow-maven-plugin/src/it/ignore-maven-deps-from-project/src/main/java/com/vaadin/test/ProjectFlowExtension.java new file mode 100644 index 00000000000..fd5304d4b88 --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/it/ignore-maven-deps-from-project/src/main/java/com/vaadin/test/ProjectFlowExtension.java @@ -0,0 +1,20 @@ +package com.vaadin.test; + +import java.util.List; + +import com.vaadin.flow.server.frontend.Options; +import com.vaadin.flow.server.frontend.TypeScriptBootstrapModifier; +import com.vaadin.flow.server.frontend.scanner.FrontendDependenciesScanner; + +/** + * Hello world! + */ +public class ProjectFlowExtension implements TypeScriptBootstrapModifier { + + @Override + public void modify(List bootstrapTypeScript, Options options, + FrontendDependenciesScanner frontendDependenciesScanner) { + System.out.println("ProjectFlowExtension"); + bootstrapTypeScript.add("(window as any).testProject=1;"); + } +} diff --git a/flow-plugins/flow-maven-plugin/src/it/resources-from-project/invoker.properties b/flow-plugins/flow-maven-plugin/src/it/resources-from-project/invoker.properties new file mode 100644 index 00000000000..44594528d31 --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/it/resources-from-project/invoker.properties @@ -0,0 +1,17 @@ +# +# Copyright 2000-2024 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. +# + +invoker.goals=clean package \ No newline at end of file diff --git a/flow-plugins/flow-maven-plugin/src/it/resources-from-project/pom.xml b/flow-plugins/flow-maven-plugin/src/it/resources-from-project/pom.xml new file mode 100644 index 00000000000..489fc2c332b --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/it/resources-from-project/pom.xml @@ -0,0 +1,77 @@ + + + 4.0.0 + + com.vaadin.test.maven + resources-from-project + 1.0 + jar + + + Tests that plugin dependencies do not override resources from project artifacts. + + + + UTF-8 + 17 + ${maven.compiler.release} + ${maven.compiler.release} + true + + @project.version@ + + + + + com.vaadin + flow-server + ${flow.version} + + + com.vaadin + flow-client + ${flow.version} + + + com.vaadin.test + fake-flow-resources + 1.0.0 + system + ${project.basedir}/../flow-addon/target/fake-resources-1.0.0.jar + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + + com.vaadin + flow-maven-plugin + ${flow.version} + + + + prepare-frontend + build-frontend + + + + + + com.vaadin.test + fake-flow-resources + 1.0.0 + system + ${project.basedir}/../flow-addon/target/fake-resources-plugin-1.0.0.jar + + + + + + + diff --git a/flow-plugins/flow-maven-plugin/src/it/resources-from-project/src/main/java/com/vaadin/test/ProjectFlowExtension.java b/flow-plugins/flow-maven-plugin/src/it/resources-from-project/src/main/java/com/vaadin/test/ProjectFlowExtension.java new file mode 100644 index 00000000000..fd5304d4b88 --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/it/resources-from-project/src/main/java/com/vaadin/test/ProjectFlowExtension.java @@ -0,0 +1,20 @@ +package com.vaadin.test; + +import java.util.List; + +import com.vaadin.flow.server.frontend.Options; +import com.vaadin.flow.server.frontend.TypeScriptBootstrapModifier; +import com.vaadin.flow.server.frontend.scanner.FrontendDependenciesScanner; + +/** + * Hello world! + */ +public class ProjectFlowExtension implements TypeScriptBootstrapModifier { + + @Override + public void modify(List bootstrapTypeScript, Options options, + FrontendDependenciesScanner frontendDependenciesScanner) { + System.out.println("ProjectFlowExtension"); + bootstrapTypeScript.add("(window as any).testProject=1;"); + } +} diff --git a/flow-plugins/flow-maven-plugin/src/it/resources-from-project/verify.bsh b/flow-plugins/flow-maven-plugin/src/it/resources-from-project/verify.bsh new file mode 100644 index 00000000000..ab033bdbc16 --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/it/resources-from-project/verify.bsh @@ -0,0 +1,12 @@ +import java.nio.file.*; + +flowTsx = basedir.toPath().resolve("src/main/frontend/generated/flow/Flow.tsx"); +if ( !Files.exists(flowTsx, new LinkOption[0]) ) +{ + throw new RuntimeException("Flow.tsx file not generated"); +} + +lines = Files.readAllLines(flowTsx); +if (lines.contains("// Resource loaded from plugin dependency")) { + throw new RuntimeException("Flow.tsx has been extracted from plugin classloader"); +} diff --git a/flow-plugins/flow-maven-plugin/src/it/settings.xml b/flow-plugins/flow-maven-plugin/src/it/settings.xml new file mode 100644 index 00000000000..21d21ecab70 --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/it/settings.xml @@ -0,0 +1,51 @@ + + + + + + + it-repo + + + local.central + @localRepositoryUrl@ + + true + + + true + + + + + + local.central + @localRepositoryUrl@ + + true + + + true + + + + + + + it-repo + + \ No newline at end of file diff --git a/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/BuildFrontendMojo.java b/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/BuildFrontendMojo.java index 6a3343c5f5c..53ef9e4162a 100644 --- a/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/BuildFrontendMojo.java +++ b/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/BuildFrontendMojo.java @@ -133,7 +133,8 @@ public class BuildFrontendMojo extends FlowModeAbstractMojo private boolean cleanFrontendFiles; @Override - public void execute() throws MojoExecutionException, MojoFailureException { + protected void executeInternal() + throws MojoExecutionException, MojoFailureException { long start = System.nanoTime(); TaskCleanFrontendFiles cleanTask = new TaskCleanFrontendFiles( diff --git a/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/CleanFrontendMojo.java b/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/CleanFrontendMojo.java index 80afe857b06..b8d7f09a307 100644 --- a/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/CleanFrontendMojo.java +++ b/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/CleanFrontendMojo.java @@ -40,7 +40,7 @@ public class CleanFrontendMojo extends FlowModeAbstractMojo { @Override - public void execute() throws MojoFailureException { + protected void executeInternal() throws MojoFailureException { try { CleanFrontendUtil.runCleaning(this, new CleanOptions()); } catch (CleanFrontendException e) { diff --git a/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/ConvertPolymerMojo.java b/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/ConvertPolymerMojo.java index 0ceaa86e078..1e45d7acff4 100644 --- a/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/ConvertPolymerMojo.java +++ b/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/ConvertPolymerMojo.java @@ -50,7 +50,7 @@ public class ConvertPolymerMojo extends FlowModeAbstractMojo { private boolean disableOptionalChaining; @Override - public void execute() throws MojoFailureException { + protected void executeInternal() throws MojoFailureException { if (isHillaUsed(frontendDirectory())) { getLog().warn( """ diff --git a/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/FlowModeAbstractMojo.java b/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/FlowModeAbstractMojo.java index c4dc8291a3e..f1367cf19d8 100644 --- a/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/FlowModeAbstractMojo.java +++ b/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/FlowModeAbstractMojo.java @@ -15,24 +15,38 @@ */ package com.vaadin.flow.plugin.maven; +import javax.inject.Inject; + import java.io.File; +import java.lang.reflect.Method; import java.net.URI; import java.net.URISyntaxException; +import java.net.URLClassLoader; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.Set; +import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.DependencyResolutionRequiredException; import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.Mojo; +import org.apache.maven.plugin.MojoExecution; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugin.descriptor.PluginDescriptor; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.build.BuildContext; import com.vaadin.flow.internal.StringUtil; import com.vaadin.flow.plugin.base.BuildFrontendUtil; @@ -44,6 +58,7 @@ import com.vaadin.flow.server.frontend.installer.NodeInstaller; import com.vaadin.flow.server.frontend.installer.Platform; import com.vaadin.flow.server.frontend.scanner.ClassFinder; +import com.vaadin.flow.server.scanner.ReflectionsClassFinder; import static com.vaadin.flow.server.Constants.VAADIN_SERVLET_RESOURCES; import static com.vaadin.flow.server.Constants.VAADIN_WEBAPP_RESOURCES; @@ -172,6 +187,9 @@ public abstract class FlowModeAbstractMojo extends AbstractMojo @Parameter(defaultValue = "${project}", readonly = true, required = true) MavenProject project; + @Parameter(defaultValue = "${mojoExecution}") + MojoExecution mojoExecution; + /** * The folder where `package.json` file is located. Default is project root * dir. @@ -239,7 +257,9 @@ public abstract class FlowModeAbstractMojo extends AbstractMojo private boolean npmExcludeWebComponents; /** - * Parameter for adding file extensions to handle during frontend tasks. + * Parameter for adding file extensions to handle when generating bundles. + * Hashes are calculated for these files as part of detecting if a new + * bundle should be generated. *

* From the commandline use comma separated list * {@code -Ddevmode.frontendExtraFileExtensions="svg,ico"} @@ -262,8 +282,69 @@ public abstract class FlowModeAbstractMojo extends AbstractMojo @Parameter(property = InitParameters.APPLICATION_IDENTIFIER) private String applicationIdentifier; + static final String CLASSFINDER_FIELD_NAME = "classFinder"; private ClassFinder classFinder; + private Consumer buildContextRefresher; + + @Inject + void setBuildContext(BuildContext buildContext) { + buildContextRefresher = buildContext::refresh; + } + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + PluginDescriptor pluginDescriptor = mojoExecution.getMojoDescriptor() + .getPluginDescriptor(); + checkFlowCompatibility(pluginDescriptor); + + Reflector reflector = getOrCreateReflector(); + ClassLoader tccl = Thread.currentThread().getContextClassLoader(); + Thread.currentThread() + .setContextClassLoader(reflector.getIsolatedClassLoader()); + try { + Mojo task = reflector.createMojo(this); + findExecuteMethod(task.getClass()).invoke(task); + } catch (MojoExecutionException | MojoFailureException e) { + throw e; + } catch (Exception e) { + throw new MojoFailureException(e.getMessage(), e); + } finally { + Thread.currentThread().setContextClassLoader(tccl); + } + } + + /** + * Perform whatever build-process behavior this Mojo + * implements.
+ * This is the main trigger for the Mojo inside the + * Maven system, and allows the Mojo to + * communicate errors. + * + * @throws MojoExecutionException + * if an unexpected problem occurs. Throwing this exception + * causes a "BUILD ERROR" message to be displayed. + * @throws MojoFailureException + * if an expected problem (such as a compilation failure) + * occurs. Throwing this exception causes a "BUILD FAILURE" + * message to be displayed. + */ + protected abstract void executeInternal() + throws MojoExecutionException, MojoFailureException; + + /** + * Indicates that the file or folder content has been modified during the + * build. + * + * @param file + * a {@link java.io.File} object. + */ + protected void triggerRefresh(File file) { + if (buildContextRefresher != null) { + buildContextRefresher.accept(file); + } + } + /** * Generates a List of ClasspathElements (Run and CompileTime) from a * MavenProject. @@ -271,7 +352,9 @@ public abstract class FlowModeAbstractMojo extends AbstractMojo * @param project * a given MavenProject * @return List of ClasspathElements + * @deprecated will be removed without replacement. */ + @Deprecated(forRemoval = true) public static List getClasspathElements(MavenProject project) { try { @@ -296,7 +379,7 @@ public static List getClasspathElements(MavenProject project) { * @return true if Hilla is available, false otherwise */ public boolean isHillaAvailable() { - return getClassFinder().getResource( + return getOrCreateReflector().getResource( "com/vaadin/hilla/EndpointController.class") != null; } @@ -308,7 +391,7 @@ public boolean isHillaAvailable() { * @return true if Hilla is available, false otherwise */ public static boolean isHillaAvailable(MavenProject mavenProject) { - return createClassFinder(mavenProject).getResource( + return Reflector.of(mavenProject, null).getResource( "com/vaadin/hilla/EndpointController.class") != null; } @@ -371,16 +454,14 @@ public File generatedTsFolder() { @Override public ClassFinder getClassFinder() { if (classFinder == null) { - classFinder = createClassFinder(project); + URLClassLoader classLoader = getOrCreateReflector() + .getIsolatedClassLoader(); + classFinder = new ReflectionsClassFinder(classLoader, + classLoader.getURLs()); } return classFinder; } - private static ClassFinder createClassFinder(MavenProject project) { - List classpathElements = getClasspathElements(project); - return BuildFrontendUtil.getClassFinder(classpathElements); - } - @Override public Set getJarFiles() { @@ -604,4 +685,61 @@ public List frontendExtraFileExtensions() { public boolean isNpmExcludeWebComponents() { return npmExcludeWebComponents; } + + private void checkFlowCompatibility(PluginDescriptor pluginDescriptor) { + Predicate isFlowServer = artifact -> "com.vaadin" + .equals(artifact.getGroupId()) + && "flow-server".equals(artifact.getArtifactId()); + String projectFlowVersion = project.getArtifacts().stream() + .filter(isFlowServer).map(Artifact::getVersion).findFirst() + .orElse(null); + String pluginFlowVersion = pluginDescriptor.getArtifacts().stream() + .filter(isFlowServer).map(Artifact::getVersion).findFirst() + .orElse(null); + if (!Objects.equals(projectFlowVersion, pluginFlowVersion)) { + getLog().warn( + "Vaadin Flow used in project does not match the version expected by the Vaadin plugin. " + + "Flow version for project is " + + projectFlowVersion + + ", Vaadin plugin is built for Flow version " + + pluginFlowVersion + "."); + } + } + + private Method findExecuteMethod(Class taskClass) + throws NoSuchMethodException { + + while (taskClass != null && taskClass != Object.class) { + try { + Method executeInternal = taskClass + .getDeclaredMethod("executeInternal"); + executeInternal.setAccessible(true); + return executeInternal; + } catch (NoSuchMethodException e) { + // ignore + } + taskClass = taskClass.getSuperclass(); + } + throw new NoSuchMethodException( + "Method executeInternal not found in " + getClass().getName()); + } + + private Reflector getOrCreateReflector() { + Map pluginContext = getPluginContext(); + String pluginKey = mojoExecution.getPlugin().getKey(); + String reflectorKey = Reflector.class.getName() + "-" + pluginKey + "-" + + mojoExecution.getLifecyclePhase(); + if (pluginContext != null && pluginContext.containsKey(reflectorKey)) { + getLog().debug("Using cached Reflector for plugin " + pluginKey + + " and phase " + mojoExecution.getLifecyclePhase()); + return Reflector.adapt(pluginContext.get(reflectorKey)); + } + Reflector reflector = Reflector.of(project, mojoExecution); + if (pluginContext != null) { + pluginContext.put(reflectorKey, reflector); + getLog().debug("Cached Reflector for plugin " + pluginKey + + " and phase " + mojoExecution.getLifecyclePhase()); + } + return reflector; + } } diff --git a/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/GenerateNpmBOMMojo.java b/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/GenerateNpmBOMMojo.java index 5926cc68f68..d5e6dc05904 100644 --- a/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/GenerateNpmBOMMojo.java +++ b/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/GenerateNpmBOMMojo.java @@ -138,7 +138,8 @@ public class GenerateNpmBOMMojo extends FlowModeAbstractMojo { private String specVersion; @Override - public void execute() throws MojoExecutionException, MojoFailureException { + protected void executeInternal() + throws MojoExecutionException, MojoFailureException { InvocationRequestBuilder requestBuilder = new InvocationRequestBuilder(); InvocationRequest request = requestBuilder.groupId(GROUP) .artifactId(ARTIFACT).version(VERSION).goal(GOAL) diff --git a/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/PrepareFrontendMojo.java b/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/PrepareFrontendMojo.java index 457f39a6b14..8e65eb821a7 100644 --- a/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/PrepareFrontendMojo.java +++ b/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/PrepareFrontendMojo.java @@ -16,16 +16,12 @@ package com.vaadin.flow.plugin.maven; import java.io.File; -import java.io.IOException; -import org.apache.commons.io.FileUtils; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.ResolutionScope; -import org.codehaus.plexus.build.BuildContext; import com.vaadin.flow.plugin.base.BuildFrontendUtil; @@ -41,11 +37,9 @@ @Mojo(name = "prepare-frontend", requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, defaultPhase = LifecyclePhase.PROCESS_RESOURCES) public class PrepareFrontendMojo extends FlowModeAbstractMojo { - @Component - private BuildContext buildContext; // m2eclipse integration - @Override - public void execute() throws MojoExecutionException, MojoFailureException { + protected void executeInternal() + throws MojoExecutionException, MojoFailureException { if (productionMode != null) { logWarn("The " + productionMode + " Maven parameter no longer has any effect and can be removed. Production mode is automatically enabled when you run the build-frontend target."); @@ -56,9 +50,7 @@ public void execute() throws MojoExecutionException, MojoFailureException { // Inform m2eclipse that the directory containing the token file has // been updated in order to trigger server re-deployment (#6103) - if (buildContext != null) { - buildContext.refresh(tokenFile.getParentFile()); - } + triggerRefresh(tokenFile.getParentFile()); try { BuildFrontendUtil.prepareFrontend(this); diff --git a/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/Reflector.java b/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/Reflector.java new file mode 100644 index 00000000000..b5ee45c4408 --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/main/java/com/vaadin/flow/plugin/maven/Reflector.java @@ -0,0 +1,409 @@ +/* + * Copyright 2000-2024 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.plugin.maven; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.plugin.Mojo; +import org.apache.maven.plugin.MojoExecution; +import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.classworlds.realm.ClassRealm; +import org.codehaus.plexus.classworlds.realm.NoSuchRealmException; + +import com.vaadin.flow.internal.ReflectTools; +import com.vaadin.flow.server.frontend.scanner.ClassFinder; +import com.vaadin.flow.server.scanner.ReflectionsClassFinder; +import com.vaadin.flow.utils.FlowFileUtils; + +/** + * Helper class to deal with classloading of Flow plugin mojos. + */ +public final class Reflector { + + public static final String INCLUDE_FROM_COMPILE_DEPS_REGEX = ".*(/|\\\\)(portlet-api|javax\\.servlet-api)-.+jar$"; + private static final Set DEPENDENCIES_GROUP_EXCLUSIONS = Set.of( + "org.apache.maven", "org.codehaus.plexus", "org.slf4j", + "org.eclipse.sisu"); + + private final URLClassLoader isolatedClassLoader; + private Object classFinder; + + /** + * Creates a new reflector instance for the given classloader. + * + * @param isolatedClassLoader + * class loader to be used to create mojo instances. + */ + public Reflector(URLClassLoader isolatedClassLoader) { + this.isolatedClassLoader = isolatedClassLoader; + } + + private Reflector(URLClassLoader isolatedClassLoader, Object classFinder) { + this.isolatedClassLoader = isolatedClassLoader; + this.classFinder = classFinder; + } + + /** + * Gets a {@link Reflector} instance usable with the caller class loader. + *

+ *

+ * Reflector instances are cached in Maven plugin context, but instances + * might be associated to the plugin class loader, thus not working with + * classes loaded by the isolated class loader. This method returns the + * input object if it is compatible with the class loader, otherwise it + * creates a copy referencing the same isolated class loader and + * {@link ClassFinder}. + * + * @param reflector + * the {@link Reflector} instance. + * @return a {@link Reflector} instance compatible with the current class + * loader. + * @throws IllegalArgumentException + * if the input object is not a {@link Reflector} instance or if + * it is not possible to make a copy for it due to class + * definition incompatibilities. + */ + static Reflector adapt(Object reflector) { + if (reflector instanceof Reflector sameClassLoader) { + return sameClassLoader; + } else if (Reflector.class.getName() + .equals(reflector.getClass().getName())) { + Class reflectorClass = reflector.getClass(); + try { + URLClassLoader classLoader = (URLClassLoader) ReflectTools + .getJavaFieldValue(reflector, + findField(reflectorClass, + "isolatedClassLoader"), + URLClassLoader.class); + Object classFinder = ReflectTools.getJavaFieldValue(reflector, + findField(reflectorClass, "classFinder")); + return new Reflector(classLoader, classFinder); + } catch (Exception e) { + throw new IllegalArgumentException( + "Object of type " + reflector.getClass().getName() + + " is not a compatible Reflector", + e); + } + } + throw new IllegalArgumentException( + "Object of type " + reflector.getClass().getName() + + " is not a compatible Reflector"); + } + + /** + * Gets the isolated class loader. + * + * @return the isolated class loader. + */ + public URLClassLoader getIsolatedClassLoader() { + return isolatedClassLoader; + } + + /** + * Loads the class with the given name from the isolated classloader. + * + * @param className + * the name of the class to load. + * @return the class object. + * @throws ClassNotFoundException + * if the class was not found. + */ + public Class loadClass(String className) throws ClassNotFoundException { + return isolatedClassLoader.loadClass(className); + } + + /** + * Get a resource from the classpath of the isolated class loader. + * + * @param name + * class literal + * @return the resource + */ + public URL getResource(String name) { + return isolatedClassLoader.getResource(name); + } + + /** + * Creates a copy of the given Flow mojo, loading classes the isolated + * classloader. + *

+ *

+ * Loads the given mojo class from the isolated class loader and then + * creates a new instance for it and fills all field copying values from the + * original mojo. The input mojo must have a public no-args constructor. + * Mojo fields must reference types that can be safely loaded be the + * isolated class loader, such as JDK or Maven core API. It also creates and + * injects a {@link ClassFinder}, based on the isolated class loader. + * + * @param sourceMojo + * The mojo for which to create the instance from the isolated + * class loader. + * @return an instance of the mojo loaded from the isolated class loader. + * @throws Exception + * if the mojo instance cannot be created. + */ + public Mojo createMojo(FlowModeAbstractMojo sourceMojo) throws Exception { + Class targetMojoClass = loadClass(sourceMojo.getClass().getName()); + Object targetMojo = targetMojoClass.getConstructor().newInstance(); + copyFields(sourceMojo, targetMojo); + Field classFinderField = findField(targetMojoClass, + FlowModeAbstractMojo.CLASSFINDER_FIELD_NAME); + ReflectTools.setJavaFieldValue(targetMojo, classFinderField, + getOrCreateClassFinder()); + return (Mojo) targetMojo; + } + + /** + * Gets a new {@link Reflector} instance for the current Mojo execution. + *

+ *

+ * An isolated class loader is created based on project and plugin + * dependencies, with the first ones having precedence over the seconds. The + * maven.api class realm is used as parent classloader, allowing usage of + * Maven core classes in the mojo. + * + * @param project + * the maven project. + * @param mojoExecution + * the current mojo execution. + * @return a Reflector instance for the current maven execution. + */ + public static Reflector of(MavenProject project, + MojoExecution mojoExecution) { + URLClassLoader classLoader = createIsolatedClassLoader(project, + mojoExecution); + return new Reflector(classLoader); + } + + private synchronized Object getOrCreateClassFinder() throws Exception { + if (classFinder == null) { + Class classFinderImplClass = loadClass( + ReflectionsClassFinder.class.getName()); + classFinder = classFinderImplClass + .getConstructor(ClassLoader.class, URL[].class).newInstance( + isolatedClassLoader, isolatedClassLoader.getURLs()); + } + return classFinder; + } + + private static URLClassLoader createIsolatedClassLoader( + MavenProject project, MojoExecution mojoExecution) { + List urls = new ArrayList<>(); + String outputDirectory = project.getBuild().getOutputDirectory(); + if (outputDirectory != null) { + urls.add(FlowFileUtils.convertToUrl(new File(outputDirectory))); + } + + Function keyMapper = artifact -> artifact.getGroupId() + + ":" + artifact.getArtifactId(); + + Map projectDependencies = new HashMap<>(project + .getArtifacts().stream() + // Exclude all maven artifacts to prevent class loading clash + // with maven.api class realm + .filter(artifact -> !DEPENDENCIES_GROUP_EXCLUSIONS + .contains(artifact.getGroupId())) + .filter(artifact -> artifact.getFile() != null + && artifact.getArtifactHandler().isAddedToClasspath() + && (Artifact.SCOPE_COMPILE.equals(artifact.getScope()) + || Artifact.SCOPE_RUNTIME + .equals(artifact.getScope()) + || Artifact.SCOPE_SYSTEM + .equals(artifact.getScope()) + || (Artifact.SCOPE_PROVIDED + .equals(artifact.getScope()) + && artifact.getFile().getPath().matches( + INCLUDE_FROM_COMPILE_DEPS_REGEX)))) + .collect(Collectors.toMap(keyMapper, Function.identity()))); + if (mojoExecution != null) { + mojoExecution.getMojoDescriptor().getPluginDescriptor() + .getArtifacts().stream() + // Exclude all maven artifacts to prevent class loading + // clash with maven.api class realm + .filter(artifact -> !DEPENDENCIES_GROUP_EXCLUSIONS + .contains(artifact.getGroupId())) + .filter(artifact -> !projectDependencies + .containsKey(keyMapper.apply(artifact))) + .forEach(artifact -> projectDependencies + .put(keyMapper.apply(artifact), artifact)); + } + + projectDependencies.values().stream() + .map(artifact -> FlowFileUtils.convertToUrl(artifact.getFile())) + .forEach(urls::add); + ClassLoader mavenApiClassLoader; + if (mojoExecution != null) { + ClassRealm pluginClassRealm = mojoExecution.getMojoDescriptor() + .getPluginDescriptor().getClassRealm(); + try { + mavenApiClassLoader = pluginClassRealm.getWorld() + .getRealm("maven.api"); + } catch (NoSuchRealmException e) { + throw new RuntimeException(e); + } + } else { + mavenApiClassLoader = Mojo.class.getClassLoader(); + if (mavenApiClassLoader instanceof ClassRealm classRealm) { + try { + mavenApiClassLoader = classRealm.getWorld() + .getRealm("maven.api"); + } catch (NoSuchRealmException e) { + // Should never happen. In case, ignore the error and use + // class loader from the Maven class + } + } + } + return new CombinedClassLoader(urls.toArray(new URL[0]), + mavenApiClassLoader); + } + + // Tries to load class from the give class loader and fallbacks + // to Platform class loader in case of failure. + private static class CombinedClassLoader extends URLClassLoader { + private final ClassLoader delegate; + + private CombinedClassLoader(URL[] urls, ClassLoader delegate) { + super(urls, null); + this.delegate = delegate; + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + try { + return super.loadClass(name); + } catch (ClassNotFoundException e) { + // ignore and continue with delegate class loader + } + if (delegate != null) { + try { + return delegate.loadClass(name); + } catch (ClassNotFoundException e) { + // ignore and continue with platform class loader + } + } + return ClassLoader.getPlatformClassLoader().loadClass(name); + } + + @Override + public URL getResource(String name) { + URL url = super.getResource(name); + if (url == null && delegate != null) { + url = delegate.getResource(name); + } + if (url == null) { + url = ClassLoader.getPlatformClassLoader().getResource(name); + } + return url; + } + + @Override + public Enumeration getResources(String name) throws IOException { + Enumeration resources = super.getResources(name); + if (!resources.hasMoreElements() && delegate != null) { + resources = delegate.getResources(name); + } + if (!resources.hasMoreElements()) { + resources = ClassLoader.getPlatformClassLoader() + .getResources(name); + } + return resources; + } + } + + private void copyFields(FlowModeAbstractMojo sourceMojo, Object targetMojo) + throws IllegalAccessException, NoSuchFieldException { + Class sourceClass = sourceMojo.getClass(); + Class targetClass = targetMojo.getClass(); + while (sourceClass != null && sourceClass != Object.class) { + for (Field sourceField : sourceClass.getDeclaredFields()) { + copyField(sourceMojo, targetMojo, sourceField, targetClass); + } + targetClass = targetClass.getSuperclass(); + sourceClass = sourceClass.getSuperclass(); + } + } + + private static void copyField(FlowModeAbstractMojo sourceMojo, + Object targetMojo, Field sourceField, Class targetClass) + throws IllegalAccessException, NoSuchFieldException { + if (Modifier.isStatic(sourceField.getModifiers())) { + return; + } + sourceField.setAccessible(true); + Object value = sourceField.get(sourceMojo); + if (value == null) { + return; + } + Field targetField; + try { + targetField = targetClass.getDeclaredField(sourceField.getName()); + } catch (NoSuchFieldException ex) { + // Should never happen, since the class definition should be + // the same + String message = "Field " + sourceField.getName() + " defined in " + + sourceField.getDeclaringClass().getName() + + " is missing in " + targetClass.getName(); + sourceMojo.logError(message, ex); + throw ex; + } + + Class targetFieldType = targetField.getType(); + if (!targetFieldType.isAssignableFrom(sourceField.getType())) { + String message = "Field " + targetFieldType.getName() + " in class " + + targetClass.getName() + " of type " + + targetFieldType.getName() + + " is loaded from different class loaders." + + " Source class loader: " + + sourceField.getType().getClassLoader() + + ", Target class loader: " + + targetFieldType.getClassLoader() + + ". This is likely a bug in the Vaadin Maven plugin." + + " Please, report the error on the issue tracker."; + sourceMojo.logError(message); + throw new NoSuchFieldException(message); + } + targetField.setAccessible(true); + targetField.set(targetMojo, value); + } + + private static Field findField(Class clazz, String fieldName) + throws NoSuchFieldException { + while (clazz != null && !clazz.equals(Object.class)) { + try { + return clazz.getDeclaredField(fieldName); + } catch (NoSuchFieldException e) { + clazz = clazz.getSuperclass(); + } + } + throw new NoSuchFieldException(fieldName); + } + +} \ No newline at end of file diff --git a/flow-plugins/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/BuildFrontendMojoTest.java b/flow-plugins/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/BuildFrontendMojoTest.java index e3b3ac56455..d5f32136ea5 100644 --- a/flow-plugins/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/BuildFrontendMojoTest.java +++ b/flow-plugins/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/BuildFrontendMojoTest.java @@ -29,29 +29,26 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import java.util.stream.Stream; -import com.vaadin.flow.di.Lookup; -import com.vaadin.flow.internal.StringUtil; -import com.vaadin.flow.plugin.TestUtils; -import com.vaadin.flow.server.Constants; -import com.vaadin.flow.server.InitParameters; -import com.vaadin.flow.server.frontend.EndpointGeneratorTaskFactory; -import com.vaadin.flow.server.frontend.FrontendTools; -import com.vaadin.flow.server.frontend.FrontendUtils; -import com.vaadin.flow.server.frontend.installer.NodeInstaller; -import com.vaadin.flow.server.frontend.scanner.ClassFinder; -import elemental.json.Json; -import elemental.json.JsonObject; -import elemental.json.impl.JsonUtil; +import org.apache.maven.artifact.DefaultArtifact; +import org.apache.maven.artifact.handler.DefaultArtifactHandler; import org.apache.maven.model.Build; +import org.apache.maven.model.Plugin; import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecution; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugin.descriptor.MojoDescriptor; +import org.apache.maven.plugin.descriptor.PluginDescriptor; import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.classworlds.ClassWorld; +import org.codehaus.plexus.classworlds.realm.ClassRealm; import org.codehaus.plexus.util.FileUtils; import org.codehaus.plexus.util.ReflectionUtils; import org.junit.After; @@ -62,7 +59,20 @@ import org.junit.rules.TemporaryFolder; import org.mockito.Mockito; -import static java.io.File.pathSeparator; +import com.vaadin.flow.di.Lookup; +import com.vaadin.flow.internal.StringUtil; +import com.vaadin.flow.plugin.TestUtils; +import com.vaadin.flow.server.Constants; +import com.vaadin.flow.server.InitParameters; +import com.vaadin.flow.server.frontend.EndpointGeneratorTaskFactory; +import com.vaadin.flow.server.frontend.FrontendTools; +import com.vaadin.flow.server.frontend.FrontendUtils; +import com.vaadin.flow.server.frontend.installer.NodeInstaller; +import com.vaadin.flow.server.frontend.scanner.ClassFinder; + +import elemental.json.Json; +import elemental.json.JsonObject; +import elemental.json.impl.JsonUtil; import static com.vaadin.flow.server.Constants.PACKAGE_JSON; import static com.vaadin.flow.server.Constants.TARGET; @@ -79,8 +89,7 @@ import static com.vaadin.flow.server.frontend.FrontendUtils.TOKEN_FILE; import static com.vaadin.flow.server.frontend.FrontendUtils.VITE_CONFIG; import static com.vaadin.flow.server.frontend.FrontendUtils.VITE_GENERATED_CONFIG; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static java.io.File.pathSeparator; public class BuildFrontendMojoTest { public static final String TEST_PROJECT_RESOURCE_JS = "test_project_resource.js"; @@ -221,16 +230,48 @@ public void teardown() throws IOException { static void setProject(AbstractMojo mojo, File baseFolder) throws Exception { - Build buildMock = mock(Build.class); - when(buildMock.getFinalName()).thenReturn("finalName"); - MavenProject project = mock(MavenProject.class); - Mockito.when(project.getGroupId()).thenReturn("com.vaadin.testing"); - Mockito.when(project.getArtifactId()).thenReturn("my-application"); - when(project.getBasedir()).thenReturn(baseFolder); - when(project.getBuild()).thenReturn(buildMock); - when(project.getRuntimeClasspathElements()) - .thenReturn(getClassPath(baseFolder.toPath())); + mojo.setPluginContext(new HashMap<>()); + + MavenProject project = new MavenProject(); + project.setGroupId("com.vaadin.testing"); + project.setArtifactId("my-application"); + project.setFile(baseFolder.toPath().resolve("pom.xml").toFile()); + project.setBuild(new Build()); + project.getBuild().setFinalName("finalName"); + + List classPath = getClassPath(baseFolder.toPath()).stream() + // Exclude maven jars so classes will be loaded by them fake + // maven.api realm that will be the same for the test class + // and the mojo execution + .filter(path -> !path.matches(".*([\\\\/])maven-.*\\.jar")) + .toList(); + AtomicInteger dependencyCounter = new AtomicInteger(); + project.setArtifacts(classPath.stream().map(path -> { + DefaultArtifactHandler artifactHandler = new DefaultArtifactHandler(); + artifactHandler.setAddedToClasspath(true); + DefaultArtifact artifact = new DefaultArtifact("com.vaadin.testing", + "dep-" + dependencyCounter.incrementAndGet(), "1.0", + "compile", "jar", null, artifactHandler); + artifact.setFile(new File(path)); + return artifact; + }).collect(Collectors.toSet())); ReflectionUtils.setVariableValueInObject(mojo, "project", project); + + ClassWorld classWorld = new ClassWorld(); + ClassRealm mavenApiRealm = classWorld.newRealm("maven.api", null); + mavenApiRealm.importFrom(MavenProject.class.getClassLoader(), ""); + ClassRealm pluginClassRealm = classWorld.newRealm("flow-plugin", null); + + PluginDescriptor pluginDescriptor = new PluginDescriptor(); + pluginDescriptor.setArtifacts(List.of()); + pluginDescriptor.setClassRealm(pluginClassRealm); + pluginDescriptor.setPlugin(new Plugin()); + pluginDescriptor.setClassRealm(pluginClassRealm); + MojoDescriptor mojoDescriptor = new MojoDescriptor(); + mojoDescriptor.setPluginDescriptor(pluginDescriptor); + MojoExecution mojoExecution = new MojoExecution(mojoDescriptor); + ReflectionUtils.setVariableValueInObject(mojo, "mojoExecution", + mojoExecution); } @Test diff --git a/flow-plugins/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/CleanFrontendMojoTest.java b/flow-plugins/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/CleanFrontendMojoTest.java index 47213c76d1e..4008091191b 100644 --- a/flow-plugins/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/CleanFrontendMojoTest.java +++ b/flow-plugins/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/CleanFrontendMojoTest.java @@ -22,6 +22,7 @@ import java.nio.file.Paths; import java.util.Arrays; +import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.util.FileUtils; @@ -112,7 +113,8 @@ public void mavenGoal_when_packageJsonMissing() throws Exception { } @Test - public void should_removeNodeModulesFolder() throws MojoFailureException { + public void should_removeNodeModulesFolder() + throws MojoFailureException, MojoExecutionException { final File nodeModules = new File(projectBase, NODE_MODULES); Assert.assertTrue("Failed to create 'node_modules'", nodeModules.mkdirs()); @@ -123,7 +125,7 @@ public void should_removeNodeModulesFolder() throws MojoFailureException { @Test public void should_notRemoveNodeModulesFolder_hilla() - throws MojoFailureException, IOException { + throws MojoFailureException, IOException, MojoExecutionException { enableHilla(); final File nodeModules = new File(projectBase, NODE_MODULES); Assert.assertTrue("Failed to create 'node_modules'", @@ -135,7 +137,7 @@ public void should_notRemoveNodeModulesFolder_hilla() @Test public void should_removeCompressedDevBundle() - throws MojoFailureException, IOException { + throws MojoFailureException, IOException, MojoExecutionException { final File devBundleDir = new File(projectBase, Constants.BUNDLE_LOCATION); final File devBundle = new File(projectBase, @@ -150,7 +152,8 @@ public void should_removeCompressedDevBundle() } @Test - public void should_removeOldDevBundle() throws MojoFailureException { + public void should_removeOldDevBundle() + throws MojoFailureException, MojoExecutionException { final File devBundleDir = new File(projectBase, "src/main/dev-bundle/"); Assert.assertTrue("Failed to create 'dev-bundle' folder", devBundleDir.mkdirs()); @@ -161,7 +164,7 @@ public void should_removeOldDevBundle() throws MojoFailureException { @Test public void should_removeFrontendGeneratedFolder() - throws MojoFailureException, IOException { + throws MojoFailureException, IOException, MojoExecutionException { Assert.assertTrue("Failed to create 'frontend/generated'", frontendGenerated.mkdirs()); FileUtils.fileWrite(new File(frontendGenerated, "my_theme.js"), @@ -175,7 +178,8 @@ public void should_removeFrontendGeneratedFolder() @Test public void should_removeGeneratedFolderForCustomFrontendFolder() - throws MojoFailureException, IOException, IllegalAccessException { + throws MojoFailureException, IOException, IllegalAccessException, + MojoExecutionException { File customFrontendFolder = new File(projectBase, "src/main/frontend"); File customFrontendGenerated = new File(customFrontendFolder, @@ -199,7 +203,7 @@ public void should_removeGeneratedFolderForCustomFrontendFolder() @Test public void should_removeNpmPackageLockFile() - throws MojoFailureException, IOException { + throws MojoFailureException, IOException, MojoExecutionException { final File packageLock = new File(projectBase, "package-lock.json"); FileUtils.fileWrite(packageLock, "{ \"fake\": \"lock\"}"); @@ -210,7 +214,7 @@ public void should_removeNpmPackageLockFile() @Test public void should_notRemoveNpmPackageLockFile_hilla() - throws MojoFailureException, IOException { + throws MojoFailureException, IOException, MojoExecutionException { enableHilla(); final File packageLock = new File(projectBase, "package-lock.json"); FileUtils.fileWrite(packageLock, "{ \"fake\": \"lock\"}"); @@ -222,7 +226,7 @@ public void should_notRemoveNpmPackageLockFile_hilla() @Test public void should_removePnpmFile() - throws MojoFailureException, IOException { + throws MojoFailureException, IOException, MojoExecutionException { final File pnpmFile = new File(projectBase, ".pnpmfile.cjs"); FileUtils.fileWrite(pnpmFile, "{ \"fake\": \"pnpmfile\"}"); @@ -232,7 +236,7 @@ public void should_removePnpmFile() @Test public void should_removePnpmPackageLockFile() - throws MojoFailureException, IOException { + throws MojoFailureException, IOException, MojoExecutionException { final File pnpmLock = new File(projectBase, "pnpm-lock.yaml"); FileUtils.fileWrite(pnpmLock, "lockVersion: -1"); mojo.execute(); @@ -241,7 +245,7 @@ public void should_removePnpmPackageLockFile() @Test public void should_cleanPackageJson_removeVaadinAndHashObjects() - throws MojoFailureException, IOException { + throws MojoFailureException, IOException, MojoExecutionException { JsonObject json = createInitialPackageJson(); FileUtils.fileWrite(packageJson, json.toJson()); mojo.execute(); @@ -257,7 +261,7 @@ public void should_cleanPackageJson_removeVaadinAndHashObjects() @Test public void should_cleanPackageJson_removeVaadinDependenciesInOverrides() - throws MojoFailureException, IOException { + throws MojoFailureException, IOException, MojoExecutionException { JsonObject json = createInitialPackageJson(true); FileUtils.fileWrite(packageJson, json.toJson()); @@ -272,7 +276,7 @@ public void should_cleanPackageJson_removeVaadinDependenciesInOverrides() @Test public void should_keepUserDependencies_whenPackageJsonEdited() - throws MojoFailureException, IOException { + throws MojoFailureException, IOException, MojoExecutionException { JsonObject json = createInitialPackageJson(); json.put("dependencies", Json.createObject()); json.getObject("dependencies").put("foo", "bar"); diff --git a/flow-plugins/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/GenerateNpmBOMMojoTest.java b/flow-plugins/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/GenerateNpmBOMMojoTest.java index 00ac4b79d8d..ef7622a154d 100644 --- a/flow-plugins/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/GenerateNpmBOMMojoTest.java +++ b/flow-plugins/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/GenerateNpmBOMMojoTest.java @@ -7,7 +7,6 @@ import java.util.Set; import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.project.MavenProject; import org.codehaus.plexus.util.FileUtils; import org.codehaus.plexus.util.ReflectionUtils; import org.junit.Assert; @@ -23,6 +22,7 @@ import com.vaadin.flow.server.frontend.FrontendTools; import com.vaadin.flow.server.frontend.scanner.ClassFinder; +import static com.vaadin.flow.plugin.maven.BuildFrontendMojoTest.setProject; import static com.vaadin.flow.server.Constants.PACKAGE_JSON; import static com.vaadin.flow.server.Constants.VAADIN_SERVLET_RESOURCES; import static com.vaadin.flow.server.frontend.FrontendUtils.DEFAULT_FRONTEND_DIR; @@ -47,9 +47,7 @@ public class GenerateNpmBOMMojoTest { public void setUp() throws Exception { this.mojo = Mockito.spy(new GenerateNpmBOMMojo()); - MavenProject project = Mockito.mock(MavenProject.class); File projectBase = temporaryFolder.getRoot(); - Mockito.when(project.getBasedir()).thenReturn(projectBase); File frontendDirectory = new File(projectBase, DEFAULT_FRONTEND_DIR); resourceOutputDirectory = new File(projectBase, VAADIN_SERVLET_RESOURCES); @@ -84,7 +82,6 @@ public void setUp() throws Exception { ReflectionUtils.setVariableValueInObject(mojo, "packageManifest", manifestFilePath); ReflectionUtils.setVariableValueInObject(mojo, "specVersion", "1.4"); - ReflectionUtils.setVariableValueInObject(mojo, "project", project); ReflectionUtils.setVariableValueInObject(mojo, "frontendDirectory", frontendDirectory); ReflectionUtils.setVariableValueInObject(mojo, "projectBasedir", @@ -96,8 +93,9 @@ public void setUp() throws Exception { ReflectionUtils.setVariableValueInObject(mojo, "npmFolder", projectBase); ReflectionUtils.setVariableValueInObject(mojo, "productionMode", false); - Mockito.when(mojo.getJarFiles()).thenReturn( - Set.of(jarResourcesSource.getParentFile().getParentFile())); + Mockito.doReturn( + Set.of(jarResourcesSource.getParentFile().getParentFile())) + .when(mojo).getJarFiles(); FileUtils.fileWrite(manifestFilePath, "UTF-8", TestUtils.getInitialPackageJson().toJson()); @@ -109,6 +107,10 @@ public void setUp() throws Exception { .lookup(ClassFinder.class); return lookup; }).when(mojo).createLookup(Mockito.any(ClassFinder.class)); + + setProject(mojo, projectBase); + // Prevent unwanted resources to be present on classpath + mojo.project.setArtifacts(Set.of()); } @Test diff --git a/flow-plugins/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/PrepareFrontendMojoTest.java b/flow-plugins/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/PrepareFrontendMojoTest.java index e94e48d1915..98f75dc5d3f 100644 --- a/flow-plugins/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/PrepareFrontendMojoTest.java +++ b/flow-plugins/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/PrepareFrontendMojoTest.java @@ -74,7 +74,6 @@ public class PrepareFrontendMojoTest { private File defaultJavaSource; private File defaultJavaResource; private File generatedTsFolder; - private MavenProject project; @Before public void setup() throws Exception { @@ -84,18 +83,6 @@ public void setup() throws Exception { tokenFile = new File(temporaryFolder.getRoot(), VAADIN_SERVLET_RESOURCES + TOKEN_FILE); - project = Mockito.mock(MavenProject.class); - - List packages = Arrays - .stream(System.getProperty("java.class.path") - .split(File.pathSeparatorChar + "")) - .collect(Collectors.toList()); - Mockito.when(project.getRuntimeClasspathElements()) - .thenReturn(packages); - Mockito.when(project.getCompileClasspathElements()) - .thenReturn(Collections.emptyList()); - Mockito.when(project.getBasedir()).thenReturn(projectBase); - packageJson = new File(projectBase, PACKAGE_JSON).getAbsolutePath(); webpackOutputDirectory = new File(projectBase, VAADIN_WEBAPP_RESOURCES); resourceOutputDirectory = new File(projectBase, @@ -271,8 +258,8 @@ public void should_updateAndKeepDependencies_when_packageJsonExists() public void jarPackaging_copyProjectFrontendResources() throws MojoExecutionException, MojoFailureException, IllegalAccessException { - Mockito.when(project.getPackaging()).thenReturn("jar"); - + mojo.project.setPackaging("jar"); + MavenProject project = Mockito.spy(mojo.project); ReflectionUtils.setVariableValueInObject(mojo, "project", project); mojo.execute(); diff --git a/flow-plugins/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/ReflectorTest.java b/flow-plugins/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/ReflectorTest.java new file mode 100644 index 00000000000..8f6dc96b0ad --- /dev/null +++ b/flow-plugins/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/ReflectorTest.java @@ -0,0 +1,295 @@ +/* + * Copyright 2000-2024 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.plugin.maven; + +import javax.inject.Inject; + +import java.io.File; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; +import java.util.List; +import java.util.Set; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.DefaultArtifact; +import org.apache.maven.artifact.handler.DefaultArtifactHandler; +import org.apache.maven.model.Build; +import org.apache.maven.plugin.Mojo; +import org.apache.maven.plugin.MojoExecution; +import org.apache.maven.plugin.descriptor.MojoDescriptor; +import org.apache.maven.plugin.descriptor.PluginDescriptor; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.classworlds.ClassWorld; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.vaadin.flow.utils.FlowFileUtils; + +import static com.vaadin.flow.plugin.maven.BuildFrontendMojoTest.getClassPath; +import static com.vaadin.flow.utils.FlowFileUtils.convertToUrl; + +public class ReflectorTest { + + Reflector reflector; + + @Before + public void setUp() { + ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); + URLClassLoader urlClassLoader = new URLClassLoader( + getClassPath(Path.of(".")).stream().distinct().map(File::new) + .map(FlowFileUtils::convertToUrl).toArray(URL[]::new), + ClassLoader.getPlatformClassLoader()) { + @Override + protected Class findClass(String name) + throws ClassNotFoundException { + // For test purposes, make maven API are loaded from shared + // class loader + if (!name.startsWith("com.vaadin.flow.plugin.maven.")) { + return systemClassLoader.loadClass(name); + } + return super.findClass(name); + } + }; + reflector = new Reflector(urlClassLoader); + } + + @Test + public void createMojo_createInstanceAndCopyFields() throws Exception { + MyMojo source = new MyMojo(); + source.fillFields(); + Mojo target = reflector.createMojo(source); + MatcherAssert.assertThat("foo field", target, + Matchers.hasProperty("foo", Matchers.equalTo(source.foo))); + MatcherAssert.assertThat("bar field", target, + Matchers.hasProperty("bar", Matchers.equalTo(source.bar))); + MatcherAssert.assertThat("notAnnotated field", target, + Matchers.hasProperty("notAnnotated", + Matchers.equalTo(source.notAnnotated))); + MatcherAssert.assertThat("mojoExecution field", target, + Matchers.hasProperty("mojoExecution", + Matchers.equalTo(source.mojoExecution))); + MatcherAssert.assertThat("maven project field", target, Matchers + .hasProperty("project", Matchers.equalTo(source.project))); + MatcherAssert.assertThat("classFinder field", target, + Matchers.hasProperty("classFinder", Matchers.notNullValue())); + } + + @Test + public void createMojo_subclass_createInstanceAndCopyFields() + throws Exception { + SubClassMojo source = new SubClassMojo(); + source.fillFields(); + Mojo target = reflector.createMojo(source); + MatcherAssert.assertThat("foo field", target, + Matchers.hasProperty("foo", Matchers.equalTo(source.foo))); + MatcherAssert.assertThat("bar field", target, + Matchers.hasProperty("bar", Matchers.equalTo(source.bar))); + MatcherAssert.assertThat("childProperty field", target, + Matchers.hasProperty("childProperty", + Matchers.equalTo(source.childProperty))); + MatcherAssert.assertThat("notAnnotated field", target, + Matchers.hasProperty("notAnnotated", + Matchers.equalTo(source.notAnnotated))); + MatcherAssert.assertThat("mojoExecution field", target, + Matchers.hasProperty("mojoExecution", + Matchers.equalTo(source.mojoExecution))); + MatcherAssert.assertThat("maven project field", target, Matchers + .hasProperty("project", Matchers.equalTo(source.project))); + MatcherAssert.assertThat("classFinder field", target, + Matchers.hasProperty("classFinder", Matchers.notNullValue())); + } + + @Test + public void createMojo_incompatibleFields_fails() { + IncompatibleFieldsMojo source = new IncompatibleFieldsMojo(); + source.fillFields(); + NoSuchFieldException exception = Assert.assertThrows( + NoSuchFieldException.class, () -> reflector.createMojo(source)); + Assert.assertTrue( + "Expected exception to be thrown because of class loader mismatch", + exception.getMessage() + .contains("loaded from different class loaders")); + } + + @Test + public void reflector_fromProject_getsIsolatedClassLoader() + throws Exception { + String outputDirectory = "/my/project/target"; + + MavenProject project = new MavenProject(); + project.setGroupId("com.vaadin.test"); + project.setArtifactId("reflector-tests"); + project.setBuild(new Build()); + project.getBuild().setOutputDirectory(outputDirectory); + project.setArtifacts(Set.of( + createArtifact("com.vaadin.test", "compile", "1.0", "compile", + true), + createArtifact("com.vaadin.test", "provided", "1.0", "provided", + true), + createArtifact("com.vaadin.test", "test", "1.0", "test", true), + createArtifact("com.vaadin.test", "system", "1.0", "system", + true), + createArtifact("com.vaadin.test", "not-classpath", "1.0", + "compile", false))); + + MojoExecution mojoExecution = new MojoExecution(new MojoDescriptor()); + PluginDescriptor pluginDescriptor = new PluginDescriptor(); + mojoExecution.getMojoDescriptor().setPluginDescriptor(pluginDescriptor); + pluginDescriptor.setGroupId("com.vaadin.test"); + pluginDescriptor.setArtifactId("test-plugin"); + pluginDescriptor.setArtifacts(List.of( + createArtifact("com.vaadin.test", "plugin", "1.0", "compile", + true), + createArtifact("com.vaadin.test", "compile", "2.0", "compile", + true))); + ClassWorld classWorld = new ClassWorld("maven.api", null); + classWorld.getRealm("maven.api") + .addURL(Path + .of("src", "test", "resources", + "jar-without-frontend-resources.jar") + .toUri().toURL()); + // .addURL(new URL("file:///some/flat/maven-repo/maven-api.jar")); + pluginDescriptor.setClassRealm(classWorld.newRealm("maven-plugin")); + + Reflector execReflector = Reflector.of(project, mojoExecution); + + URLClassLoader isolatedClassLoader = execReflector + .getIsolatedClassLoader(); + + Set urlSet = Set.of(isolatedClassLoader.getURLs()); + Assert.assertEquals(4, urlSet.size()); + Assert.assertTrue( + urlSet.contains(convertToUrl(new File(outputDirectory)))); + Assert.assertTrue(urlSet.contains(convertToUrl(new File( + "/some/flat/maven-repo/com.vaadin.test-compile-1.0.jar")))); + Assert.assertTrue(urlSet.contains(convertToUrl(new File( + "/some/flat/maven-repo/com.vaadin.test-system-1.0.jar")))); + Assert.assertTrue(urlSet.contains(convertToUrl(new File( + "/some/flat/maven-repo/com.vaadin.test-plugin-1.0.jar")))); + + // from platform class loader + Assert.assertNotNull( + isolatedClassLoader.loadClass("java.net.http.HttpClient")); + // from maven.api class loader + Assert.assertNotNull( + isolatedClassLoader.getResource("org/json/CookieList.class")); + Assert.assertNotNull( + isolatedClassLoader.loadClass("org.json.CookieList")); + } + + private Artifact createArtifact(String groupId, String artifactId, + String version, String scope, boolean addedToClasspath) { + DefaultArtifactHandler artifactHandler = new DefaultArtifactHandler(); + artifactHandler.setAddedToClasspath(addedToClasspath); + DefaultArtifact artifact = new DefaultArtifact(groupId, artifactId, + version, scope, "jar", null, artifactHandler); + artifact.setFile( + new File(String.format("/some/flat/maven-repo/%s-%s-%s.jar", + groupId, artifactId, version))); + return artifact; + } + + public static class MyMojo extends FlowModeAbstractMojo { + + @Parameter + String foo; + + @Parameter + Boolean bar; + + String notAnnotated = "NOT ANNOTATED"; + + public MyMojo() { + project = new MavenProject(); + project.setGroupId("com.vaadin.test"); + project.setArtifactId("reflector-tests"); + } + + void fillFields() { + mojoExecution = new MojoExecution(new MojoDescriptor()); + project = new MavenProject(); + foo = "foo"; + bar = true; + } + + protected void executeInternal() { + + } + + public String getFoo() { + return foo; + } + + public Boolean getBar() { + return bar; + } + + public String getNotAnnotated() { + return notAnnotated; + } + + public MojoExecution getMojoExecution() { + return mojoExecution; + } + + public MavenProject getProject() { + return project; + } + + } + + public static class SubClassMojo extends MyMojo { + + @Parameter + private String childProperty; + + @Override + void fillFields() { + super.fillFields(); + childProperty = "CHILD"; + } + + public String getChildProperty() { + return childProperty; + } + } + + public static class FakeMavenComponent { + } + + public static class IncompatibleFieldsMojo extends MyMojo { + + @Inject + private FakeMavenComponent buildContext; + + @Override + void fillFields() { + super.fillFields(); + buildContext = new FakeMavenComponent(); + } + + public FakeMavenComponent getBuildContext() { + return buildContext; + } + } + +} \ No newline at end of file diff --git a/flow-plugins/flow-plugin-base/pom.xml b/flow-plugins/flow-plugin-base/pom.xml index 06467c40bfc..c423e286db8 100644 --- a/flow-plugins/flow-plugin-base/pom.xml +++ b/flow-plugins/flow-plugin-base/pom.xml @@ -3,7 +3,7 @@ com.vaadin flow-plugins - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-plugin-base diff --git a/flow-plugins/flow-plugin-base/src/main/java/com/vaadin/flow/plugin/base/BuildFrontendUtil.java b/flow-plugins/flow-plugin-base/src/main/java/com/vaadin/flow/plugin/base/BuildFrontendUtil.java index e496e70fda9..eaca7600e15 100644 --- a/flow-plugins/flow-plugin-base/src/main/java/com/vaadin/flow/plugin/base/BuildFrontendUtil.java +++ b/flow-plugins/flow-plugin-base/src/main/java/com/vaadin/flow/plugin/base/BuildFrontendUtil.java @@ -358,7 +358,9 @@ public static void runNodeUpdater(PluginAdapterBuild adapter) .withForceProductionBuild(adapter.forceProductionBuild()) .withReact(adapter.isReactEnabled()) .withNpmExcludeWebComponents( - adapter.isNpmExcludeWebComponents()); + adapter.isNpmExcludeWebComponents()) + .withFrontendExtraFileExtensions( + adapter.frontendExtraFileExtensions()); new NodeTasks(options).execute(); } catch (ExecutionFailedException exception) { throw exception; diff --git a/flow-plugins/flow-plugin-base/src/main/java/com/vaadin/flow/plugin/base/PluginAdapterBase.java b/flow-plugins/flow-plugin-base/src/main/java/com/vaadin/flow/plugin/base/PluginAdapterBase.java index d084c6ca631..3e795ebd948 100644 --- a/flow-plugins/flow-plugin-base/src/main/java/com/vaadin/flow/plugin/base/PluginAdapterBase.java +++ b/flow-plugins/flow-plugin-base/src/main/java/com/vaadin/flow/plugin/base/PluginAdapterBase.java @@ -349,9 +349,9 @@ default Lookup createLookup(ClassFinder classFinder) { List frontendExtraFileExtensions(); /** - * Whether to include web component npm packages in packages.json. + * Whether to exclude Vaadin web component npm packages in packages.json. * - * @return {@code true} to include web component npm packages. + * @return {@code true} to exclude Vaadin web component npm packages. */ boolean isNpmExcludeWebComponents(); } diff --git a/flow-plugins/flow-plugin-base/src/main/java/com/vaadin/flow/server/scanner/ReflectionsClassFinder.java b/flow-plugins/flow-plugin-base/src/main/java/com/vaadin/flow/server/scanner/ReflectionsClassFinder.java index 75cbb65a381..35aff4716a4 100644 --- a/flow-plugins/flow-plugin-base/src/main/java/com/vaadin/flow/server/scanner/ReflectionsClassFinder.java +++ b/flow-plugins/flow-plugin-base/src/main/java/com/vaadin/flow/server/scanner/ReflectionsClassFinder.java @@ -56,8 +56,12 @@ public class ReflectionsClassFinder implements ClassFinder { * the list of urls for finding classes. */ public ReflectionsClassFinder(URL... urls) { - classLoader = new URLClassLoader(urls, - Thread.currentThread().getContextClassLoader()); + this(new URLClassLoader(urls, + Thread.currentThread().getContextClassLoader()), urls); + } + + public ReflectionsClassFinder(ClassLoader classLoader, URL... urls) { + this.classLoader = classLoader; ConfigurationBuilder configurationBuilder = new ConfigurationBuilder() .addClassLoaders(classLoader).setExpandSuperTypes(false) .addUrls(urls); diff --git a/flow-plugins/pom.xml b/flow-plugins/pom.xml index 65042d8aeec..a68a5d18409 100644 --- a/flow-plugins/pom.xml +++ b/flow-plugins/pom.xml @@ -5,7 +5,7 @@ com.vaadin flow-project - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-plugins pom diff --git a/flow-polymer-template/pom.xml b/flow-polymer-template/pom.xml index 8338465924d..356484f8904 100644 --- a/flow-polymer-template/pom.xml +++ b/flow-polymer-template/pom.xml @@ -6,7 +6,7 @@ com.vaadin flow-project - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-polymer-template Flow Polymer Templates Support diff --git a/flow-polymer2lit/README.md b/flow-polymer2lit/README.md index 1becc8e266c..afa063cf894 100644 --- a/flow-polymer2lit/README.md +++ b/flow-polymer2lit/README.md @@ -42,7 +42,7 @@ mvn vaadin:convert-polymer To convert a project that is based on Vaadin < 24, use the full Maven goal: ```bash -mvn com.vaadin:vaadin-maven-plugin:24.6-SNAPSHOT:convert-polymer +mvn com.vaadin:vaadin-maven-plugin:24.7-SNAPSHOT:convert-polymer ``` Or, in the case of using Gradle, add the following to `build.gradle`: @@ -50,7 +50,7 @@ Or, in the case of using Gradle, add the following to `build.gradle`: ```gradle buildscript { repositories { - classpath 'com.vaadin:flow-gradle-plugin:24.6-SNAPSHOT' + classpath 'com.vaadin:flow-gradle-plugin:24.7-SNAPSHOT' } } ``` diff --git a/flow-polymer2lit/pom.xml b/flow-polymer2lit/pom.xml index 627d9cc42a9..8483076eefc 100644 --- a/flow-polymer2lit/pom.xml +++ b/flow-polymer2lit/pom.xml @@ -6,7 +6,7 @@ com.vaadin flow-project - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-polymer2lit Polymer to Lit converter diff --git a/flow-push/pom.xml b/flow-push/pom.xml index 1d4c5101d8e..c78c2737958 100644 --- a/flow-push/pom.xml +++ b/flow-push/pom.xml @@ -5,7 +5,7 @@ com.vaadin flow-project - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-push Flow Push diff --git a/flow-react/pom.xml b/flow-react/pom.xml index bff702aaba9..04e4de5fecf 100644 --- a/flow-react/pom.xml +++ b/flow-react/pom.xml @@ -6,7 +6,7 @@ com.vaadin flow-project - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-react diff --git a/flow-server-production-mode/pom.xml b/flow-server-production-mode/pom.xml index 7949d2685aa..0d1e56a563b 100644 --- a/flow-server-production-mode/pom.xml +++ b/flow-server-production-mode/pom.xml @@ -5,7 +5,7 @@ com.vaadin flow-project - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-server-production-mode Flow Server Production Mode diff --git a/flow-server/pom.xml b/flow-server/pom.xml index f84d973fdfc..102dccace2e 100644 --- a/flow-server/pom.xml +++ b/flow-server/pom.xml @@ -5,7 +5,7 @@ com.vaadin flow-project - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-server Flow Server diff --git a/flow-server/src/main/java/com/vaadin/experimental/FeatureFlags.java b/flow-server/src/main/java/com/vaadin/experimental/FeatureFlags.java index a2431265070..f87af5ef39f 100644 --- a/flow-server/src/main/java/com/vaadin/experimental/FeatureFlags.java +++ b/flow-server/src/main/java/com/vaadin/experimental/FeatureFlags.java @@ -67,11 +67,6 @@ public class FeatureFlags implements Serializable { "collaborationEngineBackend", "https://github.com/vaadin/platform/issues/1988", true, null); - public static final Feature WEB_PUSH = new Feature( - "Server side WebPush API", "webPush", - "https://vaadin.com/docs/latest/configuration/setting-up-webpush", - true, "com.vaadin.flow.server.webpush.WebPush"); - public static final Feature FORM_FILLER_ADDON = new Feature( "Form Filler Add-on", "formFillerAddon", "https://github.com/vaadin/form-filler-addon", true, @@ -90,6 +85,11 @@ public class FeatureFlags implements Serializable { "Hilla Full-stack Signals", "fullstackSignals", "https://github.com/vaadin/hilla/discussions/1902", true, null); + public static final Feature DASHBOARD_COMPONENT = new Feature( + "Dashboard component (Pro)", "dashboardComponent", + "https://github.com/vaadin/platform/issues/6626", true, + "com.vaadin.flow.component.dashboard.Dashboard"); + private List features = new ArrayList<>(); File propertiesFolder = null; @@ -112,11 +112,11 @@ public FeatureFlags(Lookup lookup) { this.lookup = lookup; features.add(new Feature(EXAMPLE)); features.add(new Feature(COLLABORATION_ENGINE_BACKEND)); - features.add(new Feature(WEB_PUSH)); features.add(new Feature(FORM_FILLER_ADDON)); features.add(new Feature(HILLA_I18N)); features.add(new Feature(HILLA_FULLSTACK_SIGNALS)); features.add(new Feature(COPILOT_EXPERIMENTAL)); + features.add(new Feature(DASHBOARD_COMPONENT)); loadProperties(); } diff --git a/flow-server/src/main/java/com/vaadin/flow/component/internal/ComponentTracker.java b/flow-server/src/main/java/com/vaadin/flow/component/internal/ComponentTracker.java index 611424b5d94..5e0ec629f85 100644 --- a/flow-server/src/main/java/com/vaadin/flow/component/internal/ComponentTracker.java +++ b/flow-server/src/main/java/com/vaadin/flow/component/internal/ComponentTracker.java @@ -294,20 +294,47 @@ public static void trackAttach(Component component) { */ public static void refreshLocation(Location location, int offset) { refreshLocation(createLocation, location, offset); + refreshLocations(createLocations, location, offset); refreshLocation(attachLocation, location, offset); + refreshLocations(attachLocations, location, offset); + } + + private static boolean needsUpdate(Location l, Location referenceLocation) { + return Objects.equals(l.className, referenceLocation.className) + && l.lineNumber > referenceLocation.lineNumber; + } + + private static Location updateLocation(Location l, int offset) { + return new Location(l.className, l.filename, l.methodName, + l.lineNumber + offset); } private static void refreshLocation(Map targetRef, - Location location, int offset) { + Location referenceLocation, int offset) { Map updatedLocations = new HashMap<>(); - targetRef.entrySet().stream().filter( - e -> Objects.equals(e.getValue().className, location.className)) - .filter(e -> e.getValue().lineNumber > location.lineNumber) - .forEach(e -> { - Location l = e.getValue(); - updatedLocations.put(e.getKey(), new Location(l.className, - l.filename, l.methodName, l.lineNumber + offset)); - }); + for (Component c : targetRef.keySet()) { + Location l = targetRef.get(c); + if (needsUpdate(l, referenceLocation)) { + updatedLocations.put(c, updateLocation(l, offset)); + } + } + + targetRef.putAll(updatedLocations); + } + + private static void refreshLocations(Map targetRef, + Location referenceLocation, int offset) { + Map updatedLocations = new HashMap<>(); + for (Component c : targetRef.keySet()) { + Location[] locations = targetRef.get(c); + + for (int i = 0; i < locations.length; i++) { + if (needsUpdate(locations[i], referenceLocation)) { + locations[i] = updateLocation(locations[i], offset); + } + } + } + targetRef.putAll(updatedLocations); } diff --git a/flow-server/src/main/java/com/vaadin/flow/router/internal/DefaultRouteResolver.java b/flow-server/src/main/java/com/vaadin/flow/router/internal/DefaultRouteResolver.java index cfd7c2e7885..63944c2a540 100644 --- a/flow-server/src/main/java/com/vaadin/flow/router/internal/DefaultRouteResolver.java +++ b/flow-server/src/main/java/com/vaadin/flow/router/internal/DefaultRouteResolver.java @@ -25,6 +25,7 @@ import com.vaadin.flow.router.NavigationStateBuilder; import com.vaadin.flow.router.NotFoundException; import com.vaadin.flow.router.RouteResolver; +import com.vaadin.flow.router.RouterLayout; import com.vaadin.flow.server.RouteRegistry; import com.vaadin.flow.internal.menu.MenuRegistry; import com.vaadin.flow.server.menu.AvailableViewInfo; @@ -58,15 +59,16 @@ public NavigationState resolve(ResolveRequest request) { : "/" + clientPath); if (viewInfo != null && viewInfo.flowLayout()) { - Class layout = (Class) registry + Class layout = registry .getLayout(path); if (layout == null) { throw new NotFoundException( "No layout for client path '%s'" .formatted(path)); } - RouteTarget target = new RouteTarget(layout, - Collections.emptyList()); + RouteTarget target = new RouteTarget( + (Class) layout, RouteUtil + .getParentLayoutsForNonRouteTarget(layout)); navigationResult = new NavigationRouteTarget( navigationResult.getPath(), target, Collections.emptyMap()); diff --git a/flow-server/src/main/java/com/vaadin/flow/server/Constants.java b/flow-server/src/main/java/com/vaadin/flow/server/Constants.java index 25e15f8b9e2..edc8e74af5c 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/Constants.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/Constants.java @@ -185,6 +185,11 @@ public final class Constants implements Serializable { */ public static final String VAADIN_WEBAPP = "webapp/"; + /** + * The generated PWA icons folder. + */ + public static final String VAADIN_PWA_ICONS = "pwa-icons/"; + /** * The path to meta-inf/VAADIN/ where static resources are put on the * servlet. diff --git a/flow-server/src/main/java/com/vaadin/flow/server/PwaIcon.java b/flow-server/src/main/java/com/vaadin/flow/server/PwaIcon.java index 6b946bde642..9d877e52bdf 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/PwaIcon.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/PwaIcon.java @@ -16,12 +16,14 @@ package com.vaadin.flow.server; import javax.imageio.ImageIO; + import java.awt.Color; import java.awt.Graphics2D; import java.awt.Image; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; import java.io.UncheckedIOException; @@ -99,6 +101,15 @@ public enum Domain { setRelativeName(); } + protected PwaIcon(PwaIcon icon) { + this.width = icon.width; + this.height = icon.height; + this.baseName = icon.baseName; + this.domain = icon.domain; + this.shouldBeCached = icon.shouldBeCached; + this.attributes.putAll(icon.attributes); + } + /** * Gets an {@link Element} presentation of the icon. * @@ -236,6 +247,25 @@ public void setImage(BufferedImage image) { } } + void setImage(InputStream image) throws IOException { + if (image != null) { + data = image.readAllBytes(); + fileHash = Arrays.hashCode(data); + setRelativeName(); + } + } + + /** + * Gets if the icon can be written on a stream or not. + * + * @return {@literal true} if the icon can be written, otherwise + * {@literal false}. + * @see #write(OutputStream) + */ + boolean isAvailable() { + return data != null || registry.getBaseImage() != null; + } + /** * Writes the icon image to output stream. * @@ -246,7 +276,7 @@ public void write(OutputStream outputStream) { if (data == null) { // New image with wanted size // Store byte array and hashcode of image (GeneratedImage) - setImage(drawIconImage(registry.getBaseImage())); + setImage(drawIconImage(getBaseImage())); } try { outputStream.write(data); @@ -257,6 +287,11 @@ public void write(OutputStream outputStream) { } } + // visible for test + protected BufferedImage getBaseImage() { + return registry.getBaseImage(); + } + private BufferedImage drawIconImage(BufferedImage baseImage) { // Pick top-left pixel as fill color if needed for image // resizing @@ -296,4 +331,5 @@ private BufferedImage drawIconImage(BufferedImage baseImage) { graphics.dispose(); return bimage; } + } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/PwaRegistry.java b/flow-server/src/main/java/com/vaadin/flow/server/PwaRegistry.java index 6446891c917..e759c5ff22d 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/PwaRegistry.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/PwaRegistry.java @@ -15,12 +15,10 @@ */ package com.vaadin.flow.server; -import javax.imageio.ImageIO; import jakarta.servlet.ServletContext; -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.Image; +import javax.imageio.ImageIO; + import java.awt.image.BufferedImage; import java.io.BufferedReader; import java.io.IOException; @@ -29,21 +27,21 @@ import java.io.Serializable; import java.io.UncheckedIOException; import java.net.MalformedURLException; -import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; 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.communication.PwaHandler; import com.vaadin.flow.server.startup.ApplicationConfiguration; import com.vaadin.flow.server.startup.ApplicationRouteRegistry; @@ -85,6 +83,7 @@ public class PwaRegistry implements Serializable { private List icons = new ArrayList<>(); private final PwaConfiguration pwaConfiguration; + private URL baseImageUrl; private BufferedImage baseImage; /** @@ -113,7 +112,21 @@ public PwaRegistry(PWA pwa, ServletContext servletContext) initializeResources(servletContext); } + // Lazy load base image to prevent using AWT api unless icon + // generation is required at runtime. + // baseImageUrl is computed during registry initialization and used on to + // load the image. BufferedImage getBaseImage() { + if (baseImage == null && baseImageUrl != null) { + try { + baseImage = getBaseImage(baseImageUrl); + } catch (IOException ex) { + getLogger().error("Image is not found or can't be loaded: {}", + baseImageUrl); + } finally { + baseImageUrl = null; + } + } return baseImage; } @@ -124,24 +137,19 @@ private void initializeResources(ServletContext servletContext) } long start = System.currentTimeMillis(); + // Load base logo from servlet context if available + // fall back to local image if unavailable URL logo = getResourceUrl(servletContext, pwaConfiguration.relIconPath()); + baseImageUrl = logo != null ? logo + : BootstrapHandler.class.getResource("default-logo.png"); URL offlinePage = pwaConfiguration.isOfflinePathEnabled() ? getResourceUrl(servletContext, pwaConfiguration.relOfflinePath()) : null; - // Load base logo from servlet context if available - // fall back to local image if unavailable - baseImage = getBaseImage(logo); - - if (baseImage == null) { - getLogger().error("Image is not found or can't be loaded: " + logo); - } else { - // initialize icons - icons = initializeIcons(); - } + icons = initializeIcons(servletContext); // Load offline page as string, from servlet context if // available, fall back to default page @@ -175,14 +183,43 @@ private URL getResourceUrl(ServletContext context, String path) return resourceUrl; } - private List initializeIcons() { + private List initializeIcons(ServletContext servletContext) { + Optional optionalResourceProvider = Optional + .ofNullable(new VaadinServletContext(servletContext) + .getAttribute(Lookup.class)) + .map(lookup -> lookup.lookup(ResourceProvider.class)); for (PwaIcon icon : getIconTemplates(pwaConfiguration.getIconPath())) { icon.setRegistry(this); - icons.add(icon); + // Try to find a pre-generated image + String iconPath = Constants.VAADIN_WEBAPP_RESOURCES + + Constants.VAADIN_PWA_ICONS + + icon.getRelHref().substring(1); + optionalResourceProvider.ifPresent( + provider -> tryLoadGeneratedIcon(provider, icon, iconPath)); + if (icon.isAvailable()) { + icons.add(icon); + } } return icons; } + private static void tryLoadGeneratedIcon(ResourceProvider resourceProvider, + PwaIcon icon, String iconPath) { + URL iconResource = resourceProvider.getApplicationResource(iconPath); + if (iconResource != null) { + try (InputStream data = iconResource.openStream()) { + icon.setImage(data); + getLogger().trace("Loading generated PWA image from {}", + iconPath); + } catch (IOException ex) { + // Ignore, icon will be generated at runtime + getLogger().debug( + "Cannot load generated PWA image from {}. Icon will be regenerated at runtime.", + iconPath, ex); + } + } + } + /** * Creates manifest.webmanifest json object. * @@ -443,7 +480,14 @@ public PwaConfiguration getPwaConfiguration() { return pwaConfiguration; } - static List getIconTemplates(String baseName) { + /** + * Gets all PWA icon variants for the give base icon. + * + * @param baseName + * path of the base icon. + * @return list of PWA icons variants. + */ + public static List getIconTemplates(String baseName) { List icons = new ArrayList<>(); // Basic manifest icons for android support icons.add( diff --git a/flow-server/src/main/java/com/vaadin/flow/server/SynchronizedRequestHandler.java b/flow-server/src/main/java/com/vaadin/flow/server/SynchronizedRequestHandler.java index d271b8a189f..a5432b00587 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/SynchronizedRequestHandler.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/SynchronizedRequestHandler.java @@ -15,7 +15,11 @@ */ package com.vaadin.flow.server; +import java.io.BufferedReader; import java.io.IOException; +import java.io.Reader; +import java.io.Serializable; +import java.util.Optional; /** * RequestHandler which takes care of locking and unlocking of the VaadinSession @@ -28,6 +32,21 @@ */ public abstract class SynchronizedRequestHandler implements RequestHandler { + public static final int MAX_BUFFER_SIZE = 64 * 1024; + + /** + * ResponseWriter is optionally returned by request handlers which implement + * {@link SynchronizedRequestHandler#synchronizedHandleRequest(VaadinSession, VaadinRequest, VaadinResponse, String)} + * + * The ResponseWriter will be executed by + * {@link #handleRequest(VaadinSession, VaadinRequest, VaadinResponse)} + * without holding Vaadin session lock. + */ + @FunctionalInterface + public interface ResponseWriter extends Serializable { + void writeResponse() throws IOException; + } + @Override public boolean handleRequest(VaadinSession session, VaadinRequest request, VaadinResponse response) throws IOException { @@ -35,11 +54,27 @@ public boolean handleRequest(VaadinSession session, VaadinRequest request, return false; } - session.lock(); try { - return synchronizedHandleRequest(session, request, response); + if (isReadAndWriteOutsideSessionLock()) { + BufferedReader reader = request.getReader(); + String requestBody = reader == null ? null + : getRequestBody(reader); + session.lock(); + Optional responseWriter = synchronizedHandleRequest( + session, request, response, requestBody); + session.unlock(); + if (responseWriter.isPresent()) { + responseWriter.get().writeResponse(); + } + return responseWriter.isPresent(); + } else { + session.lock(); + return synchronizedHandleRequest(session, request, response); + } } finally { - session.unlock(); + if (session.hasLock()) { + session.unlock(); + } } } @@ -65,6 +100,51 @@ public boolean handleRequest(VaadinSession session, VaadinRequest request, public abstract boolean synchronizedHandleRequest(VaadinSession session, VaadinRequest request, VaadinResponse response) throws IOException; + /** + * Gets if request body should be read and the response written without + * holding {@link VaadinSession} lock + * + * @return {@literal true} if + * {@link #synchronizedHandleRequest(VaadinSession, VaadinRequest, VaadinResponse, String)} + * should be called. Returns {@literal false} if + * {@link #synchronizedHandleRequest(VaadinSession, VaadinRequest, VaadinResponse)} + * should be called. + */ + public boolean isReadAndWriteOutsideSessionLock() { + return false; + } + + /** + * Identical to + * {@link #synchronizedHandleRequest(VaadinSession, VaadinRequest, VaadinResponse)} + * except the {@link VaadinSession} is locked before this is called and the + * response requestBody has been read before locking the session and is + * provided as a separate parameter. + * + * @param session + * The session for the request + * @param request + * The request to handle + * @param response + * The response object to which a response can be written. + * @param requestBody + * Request body pre-read from the request object + * @return a ResponseWriter wrapped into an Optional, if this handler will + * write the response and no further request handlers should be + * called, otherwise an empty Optional. The ResponseWriter will be + * executed after the VaadinSession is unlocked. + * + * @throws IOException + * If an IO error occurred + * @see #handleRequest(VaadinSession, VaadinRequest, VaadinResponse) + */ + public Optional synchronizedHandleRequest( + VaadinSession session, VaadinRequest request, + VaadinResponse response, String requestBody) + throws IOException, UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + /** * Check whether a request may be handled by this handler. This can be used * as an optimization to avoid locking the session just to investigate some @@ -85,4 +165,18 @@ protected boolean canHandleRequest(VaadinRequest request) { return true; } + public static String getRequestBody(Reader reader) throws IOException { + StringBuilder sb = new StringBuilder(MAX_BUFFER_SIZE); + char[] buffer = new char[MAX_BUFFER_SIZE]; + + while (true) { + int read = reader.read(buffer); + if (read == -1) { + break; + } + sb.append(buffer, 0, read); + } + + return sb.toString(); + } } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/communication/PushHandler.java b/flow-server/src/main/java/com/vaadin/flow/server/communication/PushHandler.java index 9af8e65f458..06b0965b91f 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/communication/PushHandler.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/communication/PushHandler.java @@ -42,6 +42,7 @@ import com.vaadin.flow.server.ErrorEvent; import com.vaadin.flow.server.HandlerHelper; import com.vaadin.flow.server.SessionExpiredException; +import com.vaadin.flow.server.SynchronizedRequestHandler; import com.vaadin.flow.server.SystemMessages; import com.vaadin.flow.server.VaadinContext; import com.vaadin.flow.server.VaadinRequest; @@ -56,7 +57,6 @@ import com.vaadin.flow.shared.ApplicationConstants; import com.vaadin.flow.shared.JsonConstants; import com.vaadin.flow.shared.communication.PushMode; - import elemental.json.JsonException; /** @@ -164,7 +164,9 @@ interface PushEventCallback { assert vaadinRequest != null; try { - new ServerRpcHandler().handleRpc(ui, reader, vaadinRequest); + new ServerRpcHandler().handleRpc(ui, + SynchronizedRequestHandler.getRequestBody(reader), + vaadinRequest); connection.push(false); } catch (JsonException e) { getLogger().error("Error writing JSON to response", e); diff --git a/flow-server/src/main/java/com/vaadin/flow/server/communication/ServerRpcHandler.java b/flow-server/src/main/java/com/vaadin/flow/server/communication/ServerRpcHandler.java index ff8b1ad47d7..291d887d4c3 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/communication/ServerRpcHandler.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/communication/ServerRpcHandler.java @@ -39,6 +39,7 @@ import com.vaadin.flow.internal.StateNode; import com.vaadin.flow.router.PreserveOnRefresh; import com.vaadin.flow.server.ErrorEvent; +import com.vaadin.flow.server.SynchronizedRequestHandler; import com.vaadin.flow.server.VaadinRequest; import com.vaadin.flow.server.VaadinService; import com.vaadin.flow.server.communication.rpc.AttachExistingElementRpcHandler; @@ -94,6 +95,11 @@ public static class RpcRequest implements Serializable { * the request through which the JSON was received */ public RpcRequest(String jsonString, VaadinRequest request) { + this(jsonString, request.getService().getDeploymentConfiguration() + .isSyncIdCheckEnabled()); + } + + public RpcRequest(String jsonString, boolean isSyncIdCheckEnabled) { json = JsonUtil.parse(jsonString); JsonValue token = json.get(ApplicationConstants.CSRF_TOKEN); @@ -107,8 +113,7 @@ public RpcRequest(String jsonString, VaadinRequest request) { this.csrfToken = csrfToken; } - if (request.getService().getDeploymentConfiguration() - .isSyncIdCheckEnabled()) { + if (isSyncIdCheckEnabled) { syncId = (int) json .getNumber(ApplicationConstants.SERVER_SYNC_ID); } else { @@ -199,8 +204,6 @@ private boolean isUnloadBeaconRequest() { } - private static final int MAX_BUFFER_SIZE = 64 * 1024; - /** * Exception thrown then the security key sent by the client does not match * the expected one. @@ -251,16 +254,35 @@ public ResynchronizationRequiredException() { */ public void handleRpc(UI ui, Reader reader, VaadinRequest request) throws IOException, InvalidUIDLSecurityKeyException { - ui.getSession().setLastRequestTimestamp(System.currentTimeMillis()); + handleRpc(ui, SynchronizedRequestHandler.getRequestBody(reader), + request); + } - String changeMessage = getMessage(reader); + /** + * Reads JSON containing zero or more serialized RPC calls (including legacy + * variable changes) and executes the calls. + * + * @param ui + * The {@link UI} receiving the calls. Cannot be null. + * @param message + * The JSON message from the request. + * @param request + * The request through which the RPC was received + * @throws InvalidUIDLSecurityKeyException + * If the received security key does not match the one stored in + * the session. + */ + public void handleRpc(UI ui, String message, VaadinRequest request) + throws InvalidUIDLSecurityKeyException { + ui.getSession().setLastRequestTimestamp(System.currentTimeMillis()); - if (changeMessage == null || changeMessage.equals("")) { + if (message == null || message.isEmpty()) { // The client sometimes sends empty messages, this is probably a bug return; } - RpcRequest rpcRequest = new RpcRequest(changeMessage, request); + RpcRequest rpcRequest = new RpcRequest(message, request.getService() + .getDeploymentConfiguration().isSyncIdCheckEnabled()); // Security: double cookie submission pattern unless disabled by // property @@ -268,9 +290,9 @@ public void handleRpc(UI ui, Reader reader, VaadinRequest request) throw new InvalidUIDLSecurityKeyException(); } - String hashMessage = changeMessage; + String hashMessage = message; if (hashMessage.length() > 64 * 1024) { - hashMessage = changeMessage.substring(0, 64 * 1024); + hashMessage = message.substring(0, 64 * 1024); } byte[] messageHash = MessageDigestUtil.sha256(hashMessage); @@ -374,7 +396,6 @@ public void handleRpc(UI ui, Reader reader, VaadinRequest request) getLogger().debug("UI closed with a beacon request"); } } - } private void enforceIfNeeded(VaadinRequest request, RpcRequest rpcRequest) { @@ -550,8 +571,9 @@ private static void callErrorHandler(UI ui, JsonObject invocationJson, protected String getMessage(Reader reader) throws IOException { - StringBuilder sb = new StringBuilder(MAX_BUFFER_SIZE); - char[] buffer = new char[MAX_BUFFER_SIZE]; + StringBuilder sb = new StringBuilder( + SynchronizedRequestHandler.MAX_BUFFER_SIZE); + char[] buffer = new char[SynchronizedRequestHandler.MAX_BUFFER_SIZE]; while (true) { int read = reader.read(buffer); diff --git a/flow-server/src/main/java/com/vaadin/flow/server/communication/StreamReceiverHandler.java b/flow-server/src/main/java/com/vaadin/flow/server/communication/StreamReceiverHandler.java index f9c3256714b..12e355ae2b2 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/communication/StreamReceiverHandler.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/communication/StreamReceiverHandler.java @@ -38,6 +38,8 @@ import org.apache.commons.fileupload2.core.FileUploadFileCountLimitException; import org.apache.commons.fileupload2.core.FileUploadSizeException; import org.apache.commons.fileupload2.jakarta.JakartaServletFileUpload; +import org.apache.commons.fileupload2.core.FileItemInputIterator; +import org.apache.commons.fileupload2.jakarta.JakartaServletFileUpload; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -630,11 +632,23 @@ protected Collection getParts(VaadinRequest request) protected FileItemInputIterator getItemIterator(VaadinRequest request) throws FileUploadException, IOException { + JakartaServletFileUpload upload = createServletFileUpload(request); + return upload.getItemIterator((HttpServletRequest) request); + } + + // protected for testing purposes only + protected JakartaServletFileUpload createServletFileUpload( + VaadinRequest request) { JakartaServletFileUpload upload = new JakartaServletFileUpload(); upload.setSizeMax(requestSizeMax); upload.setFileSizeMax(fileSizeMax); upload.setFileCountMax(fileCountMax); - return upload.getItemIterator((HttpServletRequest) request); + if (request.getCharacterEncoding() == null) { + // Request body's file upload headers are expected to be encoded in + // UTF-8 if not explicitly set otherwise in the request. + upload.setHeaderCharset(StandardCharsets.UTF_8); + } + return upload; } public void setRequestSizeMax(long requestSizeMax) { diff --git a/flow-server/src/main/java/com/vaadin/flow/server/communication/UidlRequestHandler.java b/flow-server/src/main/java/com/vaadin/flow/server/communication/UidlRequestHandler.java index 4a1b75f2f1f..8a90334aac1 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/communication/UidlRequestHandler.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/communication/UidlRequestHandler.java @@ -20,6 +20,7 @@ import java.io.OutputStream; import java.io.StringWriter; import java.io.Writer; +import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -102,39 +103,55 @@ protected ServerRpcHandler createRpcHandler() { @Override public boolean synchronizedHandleRequest(VaadinSession session, VaadinRequest request, VaadinResponse response) throws IOException { + String requestBody = SynchronizedRequestHandler + .getRequestBody(request.getReader()); + Optional responseWriter = synchronizedHandleRequest( + session, request, response, requestBody); + if (responseWriter.isPresent()) { + responseWriter.get().writeResponse(); + } + return responseWriter.isPresent(); + } + + @Override + public boolean isReadAndWriteOutsideSessionLock() { + return true; + } + + @Override + public Optional synchronizedHandleRequest( + VaadinSession session, VaadinRequest request, + VaadinResponse response, String requestBody) + throws IOException, UnsupportedOperationException { UI uI = session.getService().findUI(request); if (uI == null) { // This should not happen but it will if the UI has been closed. We // really don't want to see it in the server logs though - commitJsonResponse(response, - VaadinService.createUINotFoundJSON(false)); - return true; + return Optional.of(() -> commitJsonResponse(response, + VaadinService.createUINotFoundJSON(false))); } StringWriter stringWriter = new StringWriter(); try { - getRpcHandler(session).handleRpc(uI, request.getReader(), request); + getRpcHandler(session).handleRpc(uI, requestBody, request); writeUidl(uI, stringWriter, false); } catch (JsonException e) { getLogger().error("Error writing JSON to response", e); // Refresh on client side - writeRefresh(response); - return true; + return Optional.of(() -> writeRefresh(response)); } catch (InvalidUIDLSecurityKeyException e) { getLogger().warn("Invalid security key received from {}", request.getRemoteHost()); // Refresh on client side - writeRefresh(response); - return true; + return Optional.of(() -> writeRefresh(response)); } catch (DauEnforcementException e) { getLogger().warn( "Daily Active User limit reached. Blocking new user request"); response.setHeader(DAUUtils.STATUS_CODE_KEY, String .valueOf(HttpStatusCode.SERVICE_UNAVAILABLE.getCode())); String json = DAUUtils.jsonEnforcementResponse(request, e); - commitJsonResponse(response, json); - return true; + return Optional.of(() -> commitJsonResponse(response, json)); } catch (ResynchronizationRequiredException e) { // NOSONAR // Resync on the client side writeUidl(uI, stringWriter, true); @@ -142,8 +159,8 @@ public boolean synchronizedHandleRequest(VaadinSession session, stringWriter.close(); } - commitJsonResponse(response, stringWriter.toString()); - return true; + return Optional.of( + () -> commitJsonResponse(response, stringWriter.toString())); } private void writeRefresh(VaadinResponse response) throws IOException { diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeTasks.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeTasks.java index 5b0114ce888..3169fa8d667 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeTasks.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeTasks.java @@ -35,7 +35,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.vaadin.experimental.FeatureFlags; import com.vaadin.flow.di.Lookup; import com.vaadin.flow.internal.UsageStatistics; import com.vaadin.flow.server.Constants; @@ -80,6 +79,7 @@ public class NodeTasks implements FallibleCommand { TaskGenerateEndpoint.class, TaskCopyFrontendFiles.class, TaskCopyLocalFrontendFiles.class, + TaskGeneratePWAIcons.class, TaskUpdateSettingsFile.class, TaskUpdateVite.class, TaskUpdateImports.class, @@ -259,6 +259,9 @@ public NodeTasks(Options options) { } else { pwa = new PwaConfiguration(); } + if (options.isProductionMode() && pwa.isEnabled()) { + commands.add(new TaskGeneratePWAIcons(options, pwa)); + } commands.add(new TaskUpdateSettingsFile(options, themeName, pwa)); if (options.isFrontendHotdeploy() || options.isBundleBuild()) { commands.add(new TaskUpdateVite(options, webComponentTags)); diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskGeneratePWAIcons.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskGeneratePWAIcons.java new file mode 100644 index 00000000000..52a0cbbead2 --- /dev/null +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/TaskGeneratePWAIcons.java @@ -0,0 +1,205 @@ +/* + * Copyright 2000-2024 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.frontend; + +import javax.imageio.ImageIO; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UncheckedIOException; +import java.net.URL; +import java.net.URLConnection; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.vaadin.flow.server.BootstrapHandler; +import com.vaadin.flow.server.Constants; +import com.vaadin.flow.server.ExecutionFailedException; +import com.vaadin.flow.server.PwaConfiguration; +import com.vaadin.flow.server.PwaIcon; +import com.vaadin.flow.server.PwaRegistry; +import com.vaadin.flow.server.frontend.scanner.ClassFinder; + +/** + * Generates necessary PWA icons. + *

+ * For internal use only. May be renamed or removed in a future release. + */ +public class TaskGeneratePWAIcons implements FallibleCommand { + + private static final Logger LOGGER = LoggerFactory + .getLogger(TaskGeneratePWAIcons.class); + private static final String HEADLESS_PROPERTY = "java.awt.headless"; + + private final Path generatedIconsPath; + private final PwaConfiguration pwaConfiguration; + private final ClassFinder classFinder; + + public TaskGeneratePWAIcons(Options options, + PwaConfiguration pwaConfiguration) { + this.pwaConfiguration = pwaConfiguration; + generatedIconsPath = options.getWebappResourcesDirectory().toPath() + .resolve(Constants.VAADIN_PWA_ICONS); + this.classFinder = options.getClassFinder(); + } + + @Override + public void execute() throws ExecutionFailedException { + if (!pwaConfiguration.isEnabled()) { + return; + } + URL iconURL = findIcon(pwaConfiguration); + if (iconURL == null) { + LOGGER.warn( + "Skipping PWA icons generation because icon '{}' cannot be found in classpath", + pwaConfiguration.getIconPath()); + return; + } + + String headless = System.getProperty(HEADLESS_PROPERTY); + if (headless == null) { + // set headless mode if the property is not explicitly set + System.setProperty(HEADLESS_PROPERTY, Boolean.TRUE.toString()); + } + + LOGGER.debug("Generating PWA icons from '{}'", + pwaConfiguration.getIconPath()); + + try { + BufferedImage baseImage = loadBaseImage(iconURL); + createGeneratedIconsFolder(); + + CompletableFuture[] iconsGenerators = PwaRegistry + .getIconTemplates(pwaConfiguration.getIconPath()).stream() + .map(icon -> new InternalPwaIcon(icon, baseImage)) + .map(this::generateIcon).toArray(CompletableFuture[]::new); + + try { + CompletableFuture.allOf(iconsGenerators).join(); + } catch (CompletionException ex) { + Throwable cause = ex.getCause(); + if (cause instanceof UncheckedIOException uncheckedIOException) { + throw new ExecutionFailedException( + "PWA icons generation failed", + uncheckedIOException.getCause()); + } + throw new ExecutionFailedException( + "PWA icons generation failed", cause); + } catch (CancellationException ex) { + throw new ExecutionFailedException( + "PWA icons generation failed", ex); + } + } finally { + if (headless == null) { + System.clearProperty(HEADLESS_PROPERTY); + } else if (!headless.equals(Boolean.TRUE.toString())) { + System.setProperty(HEADLESS_PROPERTY, headless); + } + } + LOGGER.info("PWA icons generated"); + } + + private void createGeneratedIconsFolder() throws ExecutionFailedException { + try { + Path generatedPath = generatedIconsPath + .resolve(Path.of(pwaConfiguration.getIconPath().replace('/', + File.separatorChar))) + .getParent(); + Files.createDirectories(generatedPath); + } catch (IOException e) { + throw new ExecutionFailedException( + "Cannot create PWA generated icons folder " + + generatedIconsPath, + e); + } + } + + private static BufferedImage loadBaseImage(URL iconURL) + throws ExecutionFailedException { + BufferedImage baseImage; + try (InputStream inputStream = iconURL.openStream()) { + baseImage = ImageIO.read(inputStream); + } catch (IOException e) { + throw new ExecutionFailedException( + "Cannot load PWA icon from " + iconURL, e); + } + if (baseImage == null) { + throw new ExecutionFailedException( + "Cannot load PWA icon from " + iconURL); + } + return baseImage; + } + + private URL findIcon(PwaConfiguration pwaConfiguration) { + URL iconURL = classFinder.getResource(pwaConfiguration.getIconPath()); + if (iconURL == null) { + iconURL = classFinder.getResource( + "META-INF/resources/" + pwaConfiguration.getIconPath()); + } + if (iconURL == null) { + iconURL = BootstrapHandler.class.getResource("default-logo.png"); + if (iconURL == null) { + LOGGER.warn( + "PWA icon '{}' cannot be found in classpath, fallback to default icon.", + pwaConfiguration.getIconPath()); + } + } + return iconURL; + } + + private CompletableFuture generateIcon(InternalPwaIcon icon) { + Path iconPath = generatedIconsPath.resolve(icon.getRelHref() + .substring(1).replace('/', File.separatorChar)); + return CompletableFuture.runAsync(() -> { + try (OutputStream os = Files.newOutputStream(iconPath)) { + icon.write(os); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } + + private BufferedImage getBaseImage(URL logo) throws IOException { + URLConnection logoResource = logo != null ? logo.openConnection() + : BootstrapHandler.class.getResource("default-logo.png") + .openConnection(); + return ImageIO.read(logoResource.getInputStream()); + } + + private static class InternalPwaIcon extends PwaIcon { + private final BufferedImage baseImage; + + public InternalPwaIcon(PwaIcon icon, BufferedImage baseImage) { + super(icon); + this.baseImage = baseImage; + } + + @Override + protected BufferedImage getBaseImage() { + return baseImage; + } + } +} diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/VersionsJsonConverter.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/VersionsJsonConverter.java index bd2219d8928..3b86e190a43 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/VersionsJsonConverter.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/VersionsJsonConverter.java @@ -70,6 +70,7 @@ class VersionsJsonConverter { * Mode value for dependency for all modes. */ public static final String MODE_ALL = "all"; // same as empty string + private static final Object VAADIN_ROUTER = "@vaadin/router"; private final JsonObject convertedObject; @@ -161,6 +162,10 @@ private void addDependency(JsonObject obj) { exclusions.add(npmName); return; } + if (reactEnabled && Objects.equals(npmName, VAADIN_ROUTER)) { + exclusions.add(npmName); + return; + } if (!isIncludedByMode(mode)) { if (excludeWebComponents) { // collecting exclusions also from non-included dependencies diff --git a/flow-server/src/main/resources/com/vaadin/flow/server/frontend/Flow.tsx b/flow-server/src/main/resources/com/vaadin/flow/server/frontend/Flow.tsx index e3e571470c3..687a1598886 100644 --- a/flow-server/src/main/resources/com/vaadin/flow/server/frontend/Flow.tsx +++ b/flow-server/src/main/resources/com/vaadin/flow/server/frontend/Flow.tsx @@ -28,7 +28,7 @@ import { useBlocker, useLocation, useNavigate, - type NavigateOptions, + type NavigateOptions, useHref, } from "react-router-dom"; import type { AgnosticRouteObject } from '@remix-run/router'; import { createPortal } from "react-dom"; @@ -273,10 +273,12 @@ function Flow() { }); const location = useLocation(); const navigated = useRef(false); + const blockerHandled = useRef(false); const fromAnchor = useRef(false); const containerRef = useRef(undefined); const roundTrip = useRef | undefined>(undefined); const queuedNavigate = useQueuedNavigate(roundTrip, navigated); + const basename = useHref('/'); // portalsReducer function is used as state outside the Flow component. const [portals, dispatchPortalAction] = useReducer(portalsReducer, []); @@ -361,8 +363,18 @@ function Flow() { useEffect(() => { if (blocker.state === 'blocked') { + if(blockerHandled.current) { + // Blocker is handled and the new navigation + // gets queued to be executed after the current handling ends. + const {pathname, state} = blocker.location; + queuedNavigate(pathname.substring(basename.length), true, { state: state, replace: true }); + return; + } + blockerHandled.current = true; let blockingPromise: any; roundTrip.current = new Promise((resolve,reject) => blockingPromise = {resolve:resolve,reject:reject}); + // Release blocker handling after promise is fulfilled + roundTrip.current.then(() => blockerHandled.current = false, () => blockerHandled.current = false); // Proceed to the blocked location, unless the navigation originates from a click on a link. // In that case continue with function execution and perform a server round-trip diff --git a/flow-server/src/main/resources/com/vaadin/flow/server/frontend/dependencies/default/package.json b/flow-server/src/main/resources/com/vaadin/flow/server/frontend/dependencies/default/package.json index 5e127c9a1ca..933aac31bbf 100644 --- a/flow-server/src/main/resources/com/vaadin/flow/server/frontend/dependencies/default/package.json +++ b/flow-server/src/main/resources/com/vaadin/flow/server/frontend/dependencies/default/package.json @@ -10,7 +10,7 @@ "devDependencies": { "async": "3.2.6", "glob": "10.4.5", - "typescript": "5.6.3", + "typescript": "5.7.2", "workbox-core": "7.3.0", "workbox-precaching": "7.3.0", "strip-css-comments": "5.0.0" diff --git a/flow-server/src/test/java/com/vaadin/flow/ComponentTrackerTest.java b/flow-server/src/test/java/com/vaadin/flow/ComponentTrackerTest.java index b83bc1d0a97..34eebc776eb 100644 --- a/flow-server/src/test/java/com/vaadin/flow/ComponentTrackerTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/ComponentTrackerTest.java @@ -19,6 +19,8 @@ import com.vaadin.flow.component.HasComponents; import com.vaadin.flow.component.Tag; import com.vaadin.flow.component.internal.ComponentTracker; +import com.vaadin.flow.component.internal.ComponentTracker.Location; + import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -69,13 +71,8 @@ public void createLocationTracked() { Component c2; c2 = new Component1(); - ComponentTracker.Location c1Location = ComponentTracker.findCreate(c1); - Assert.assertEquals(68, c1Location.lineNumber()); - Assert.assertEquals(getClass().getName(), c1Location.className()); - - ComponentTracker.Location c2Location = ComponentTracker.findCreate(c2); - Assert.assertEquals(70, c2Location.lineNumber()); - Assert.assertEquals(getClass().getName(), c2Location.className()); + assertCreateLocation(c1, 70, getClass().getName()); + assertCreateLocation(c2, 72, getClass().getName()); } @Test @@ -86,24 +83,18 @@ public void attachLocationTracked() { Layout layout = new Layout(c1); - ComponentTracker.Location c1Location = ComponentTracker.findAttach(c1); - Assert.assertEquals(87, c1Location.lineNumber()); - Assert.assertEquals(getClass().getName(), c1Location.className()); + assertCreateLocation(c1, 80, getClass().getName()); layout.add(c2); - ComponentTracker.Location c2Location = ComponentTracker.findAttach(c2); - Assert.assertEquals(93, c2Location.lineNumber()); - Assert.assertEquals(getClass().getName(), c2Location.className()); + assertAttachLocation(c2, 88, getClass().getName()); // Last attach is tracked layout.add(c3); layout.remove(c3); layout.add(c3); - ComponentTracker.Location c3Location = ComponentTracker.findAttach(c3); - Assert.assertEquals(102, c3Location.lineNumber()); - Assert.assertEquals(getClass().getName(), c3Location.className()); + assertAttachLocation(c3, 95, getClass().getName()); } @Test @@ -112,45 +103,54 @@ public void offsetApplied() { Component c2 = new Component1(); Component c3 = new Component1(); - ComponentTracker.Location c1Location = ComponentTracker.findCreate(c1); - Assert.assertEquals(111, c1Location.lineNumber()); - Assert.assertEquals(getClass().getName(), c1Location.className()); + assertCreateLocation(c1, 102, getClass().getName()); - ComponentTracker.refreshLocation(c1Location, 3); + ComponentTracker.refreshLocation(ComponentTracker.findCreate(c1), 3); - ComponentTracker.Location c2Location = ComponentTracker.findCreate(c2); - Assert.assertEquals(112 + 3, c2Location.lineNumber()); - Assert.assertEquals(getClass().getName(), c2Location.className()); + assertCreateLocation(c2, 103 + 3, getClass().getName()); - ComponentTracker.refreshLocation(c2Location, 1); + ComponentTracker.refreshLocation(ComponentTracker.findCreate(c2), 1); - ComponentTracker.Location c3Location = ComponentTracker.findCreate(c3); - Assert.assertEquals(113 + 3 + 1, c3Location.lineNumber()); - Assert.assertEquals(getClass().getName(), c3Location.className()); + assertCreateLocation(c3, 104 + 3 + 1, getClass().getName()); } @Test public void memoryIsReleased() throws Exception { Field createLocationField = ComponentTracker.class .getDeclaredField("createLocation"); + Field createLocationsField = ComponentTracker.class + .getDeclaredField("createLocations"); Field attachLocationField = ComponentTracker.class .getDeclaredField("attachLocation"); + Field attachLocationsField = ComponentTracker.class + .getDeclaredField("attachLocations"); createLocationField.setAccessible(true); + createLocationsField.setAccessible(true); attachLocationField.setAccessible(true); - Map createMap = (Map) createLocationField + attachLocationsField.setAccessible(true); + + Map createMap = (Map) createLocationField.get(null); + Map attachMap = (Map) attachLocationField.get(null); + Map createLocationsMap = (Map) createLocationsField .get(null); - Map attachMap = (Map) attachLocationField + Map attachLocationsMap = (Map) attachLocationsField .get(null); createMap.clear(); + createLocationsMap.clear(); attachMap.clear(); + attachLocationsMap.clear(); new Layout(new Component1()); Assert.assertEquals(2, createMap.size()); + Assert.assertEquals(2, createLocationsMap.size()); Assert.assertEquals(1, attachMap.size()); + Assert.assertEquals(1, attachLocationsMap.size()); Assert.assertTrue(isCleared(createMap)); + Assert.assertTrue(isCleared(createLocationsMap)); Assert.assertTrue(isCleared(attachMap)); + Assert.assertTrue(isCleared(attachLocationsMap)); } private boolean isCleared(Map map) throws InterruptedException { @@ -164,4 +164,26 @@ private boolean isCleared(Map map) throws InterruptedException { return false; } + private void assertCreateLocation(Component c, int lineNumber, + String name) { + ComponentTracker.Location location = ComponentTracker.findCreate(c); + Assert.assertEquals(lineNumber, location.lineNumber()); + Assert.assertEquals(name, location.className()); + + Location[] locations = ComponentTracker.findCreateLocations(c); + Assert.assertEquals(lineNumber, locations[1].lineNumber()); + Assert.assertEquals(name, locations[1].className()); + } + + private void assertAttachLocation(Component c, int lineNumber, + String name) { + ComponentTracker.Location location = ComponentTracker.findAttach(c); + Assert.assertEquals(lineNumber, location.lineNumber()); + Assert.assertEquals(name, location.className()); + + Location[] locations = ComponentTracker.findAttachLocations(c); + Assert.assertEquals(lineNumber, locations[0].lineNumber()); + Assert.assertEquals(name, locations[0].className()); + } + } diff --git a/flow-server/src/test/java/com/vaadin/flow/router/DefaultRouteResolverTest.java b/flow-server/src/test/java/com/vaadin/flow/router/DefaultRouteResolverTest.java index 999a7dc4fe1..8c576e535be 100644 --- a/flow-server/src/test/java/com/vaadin/flow/router/DefaultRouteResolverTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/router/DefaultRouteResolverTest.java @@ -15,16 +15,13 @@ */ package com.vaadin.flow.router; -import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; -import com.fasterxml.jackson.annotation.JsonProperty; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; @@ -41,7 +38,6 @@ import com.vaadin.flow.server.RouteRegistry; import com.vaadin.flow.internal.menu.MenuRegistry; import com.vaadin.flow.server.menu.AvailableViewInfo; -import com.vaadin.flow.server.menu.RouteParamType; public class DefaultRouteResolverTest extends RoutingTestBase { @@ -149,6 +145,34 @@ public void clientRouteRequest_getDefinedLayout() { } } + @Test + public void clientRouteRequest_getDefinedLayoutAndParentLayouts() { + String path = "route"; + + router.getRegistry().setLayout(DefaultWithParentLayout.class); + + try (MockedStatic menuRegistry = Mockito + .mockStatic(MenuRegistry.class)) { + menuRegistry.when(() -> MenuRegistry.getClientRoutes(false)) + .thenReturn(Collections.singletonMap("/route", + new AvailableViewInfo("", null, false, "/route", + false, false, null, null, null, true))); + NavigationState greeting = resolveNavigationState(path); + Assert.assertEquals( + "Layout should be returned for a non server route when matching @Layout exists", + DefaultWithParentLayout.class, + greeting.getRouteTarget().getTarget()); + Assert.assertEquals( + "@ParentLayout annotation should be followed. @Layout class should not be in parent layout list.", + 1, greeting.getRouteTarget().getParentLayouts().size()); + Assert.assertEquals( + "@ParentLayout annotation should be followed. @Layout class should not be in parent layout list.", + DefaultParentLayout.class, + greeting.getRouteTarget().getParentLayouts().get(0)); + + } + } + @Test public void clientRouteRequest_withRouteParameters_getDefinedLayout() { router.getRegistry().setLayout(DefaultLayout.class); @@ -308,6 +332,18 @@ private static class DefaultLayout extends Component implements RouterLayout { } + @Tag("div") + @Layout + @ParentLayout(DefaultParentLayout.class) + private static class DefaultWithParentLayout extends Component + implements RouterLayout { + } + + @Tag("div") + private static class DefaultParentLayout extends Component + implements RouterLayout { + } + private Class resolveNavigationTarget(String path) { return resolveNavigationState(path).getNavigationTarget(); } diff --git a/flow-server/src/test/java/com/vaadin/flow/server/communication/PwaHandlerTest.java b/flow-server/src/test/java/com/vaadin/flow/server/communication/PwaHandlerTest.java index 05ed85dc769..e5fb4a54cd7 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/communication/PwaHandlerTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/communication/PwaHandlerTest.java @@ -15,6 +15,7 @@ */ package com.vaadin.flow.server.communication; +import java.awt.image.BufferedImage; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; @@ -132,6 +133,7 @@ private PwaIcon createIcon(PwaRegistry registry, int size) PwaIcon icon = ctor.newInstance(size, size, PwaConfiguration.DEFAULT_ICON); icon.setRegistry(registry); + icon.setImage(new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB)); return icon; } } diff --git a/flow-server/src/test/java/com/vaadin/flow/server/communication/ServerRpcHandlerTest.java b/flow-server/src/test/java/com/vaadin/flow/server/communication/ServerRpcHandlerTest.java index 85ac8d956f9..0996a9768ff 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/communication/ServerRpcHandlerTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/communication/ServerRpcHandlerTest.java @@ -103,14 +103,7 @@ public void handleRpc_resynchronize_throwsExceptionAndDirtiesTreeAndClearsDepend public void handleRpc_duplicateMessage_doNotThrow() throws InvalidUIDLSecurityKeyException, IOException { String msg = "{\"" + ApplicationConstants.CLIENT_TO_SERVER_ID + "\":1}"; - ServerRpcHandler handler = new ServerRpcHandler() { - @Override - protected String getMessage(Reader reader) throws IOException { - return msg; - } - - ; - }; + ServerRpcHandler handler = new ServerRpcHandler(); ui = new UI(); ui.getInternals().setSession(session); @@ -118,26 +111,19 @@ protected String getMessage(Reader reader) throws IOException { MessageDigestUtil.sha256(msg)); // This invocation shouldn't throw. No other checks - handler.handleRpc(ui, Mockito.mock(Reader.class), request); + handler.handleRpc(ui, msg, request); } @Test(expected = UnsupportedOperationException.class) public void handleRpc_unexpectedMessage_throw() throws InvalidUIDLSecurityKeyException, IOException { - ServerRpcHandler handler = new ServerRpcHandler() { - @Override - protected String getMessage(Reader reader) throws IOException { - return "{\"" + ApplicationConstants.CLIENT_TO_SERVER_ID - + "\":1}"; - } - - ; - }; + String msg = "{\"" + ApplicationConstants.CLIENT_TO_SERVER_ID + "\":1}"; + ServerRpcHandler handler = new ServerRpcHandler(); ui = new UI(); ui.getInternals().setSession(session); - handler.handleRpc(ui, Mockito.mock(Reader.class), request); + handler.handleRpc(ui, msg, request); } @Test(expected = DauEnforcementException.class) diff --git a/flow-server/src/test/java/com/vaadin/flow/server/communication/StreamReceiverHandlerTest.java b/flow-server/src/test/java/com/vaadin/flow/server/communication/StreamReceiverHandlerTest.java index 5c172e07f7f..2eb61ad8b8d 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/communication/StreamReceiverHandlerTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/communication/StreamReceiverHandlerTest.java @@ -10,12 +10,14 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; +import org.apache.commons.fileupload2.jakarta.JakartaServletFileUpload; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -87,6 +89,7 @@ public class StreamReceiverHandlerTest { private List parts; private boolean isGetContentLengthLongCalled; + private String requestCharacterEncoding; @Before public void setup() throws Exception { @@ -181,6 +184,11 @@ public long getContentLengthLong() { isGetContentLengthLongCalled = true; return 0; } + + @Override + public String getCharacterEncoding() { + return requestCharacterEncoding; + } }; } @@ -291,6 +299,27 @@ public void doHandleMultipartFileUpload_noPart_uploadFailed_responseStatusIs500_ Assert.assertTrue(isGetContentLengthLongCalled); } + @Test + public void createServletFileUpload_useUTF8HeaderCharacterEncodingWhenRequestCharEncodingIsNotSet() { + JakartaServletFileUpload servletFileUpload = handler + .createServletFileUpload(request); + Assert.assertNotNull(servletFileUpload); + Assert.assertEquals( + "Header encoding should be UTF-8 when request character encoding is null", + StandardCharsets.UTF_8, servletFileUpload.getHeaderCharset()); + } + + @Test + public void createServletFileUpload_dontSetHeaderCharEncodingWhenRequestCharEncodingIsSet() { + requestCharacterEncoding = "ASCII"; + JakartaServletFileUpload servletFileUpload = handler + .createServletFileUpload(request); + Assert.assertNotNull(servletFileUpload); + Assert.assertNull( + "Header encoding should not be set by Flow when request character encoding is set", + servletFileUpload.getHeaderCharset()); + } + @Test public void doHandleMultipartFileUpload_hasParts_uploadFailed_responseStatusIs500() throws IOException { 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 5d079044cfd..07440e1a9ef 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 @@ -22,6 +22,7 @@ import java.io.Reader; import java.io.StringWriter; import java.util.Collections; +import java.util.Optional; import java.util.Properties; import org.junit.Assert; @@ -33,6 +34,7 @@ import com.vaadin.flow.server.DefaultDeploymentConfiguration; import com.vaadin.flow.server.HandlerHelper.RequestType; import com.vaadin.flow.server.MockVaadinContext; +import com.vaadin.flow.server.SynchronizedRequestHandler; import com.vaadin.flow.server.VaadinContext; import com.vaadin.flow.server.VaadinRequest; import com.vaadin.flow.server.VaadinResponse; @@ -112,10 +114,11 @@ public void writeSessionExpired_whenUINotFound() throws IOException { when(service.findUI(request)).thenReturn(null); - boolean result = handler.synchronizedHandleRequest(session, request, - response); - Assert.assertTrue("Result should be true", result); - + Optional result = handler + .synchronizedHandleRequest(session, request, response, null); + Assert.assertTrue("ResponseWriter should be present", + result.isPresent()); + result.get().writeResponse(); String responseContent = CommunicationUtil .getStringWhenWriteString(outputStream); @@ -241,7 +244,8 @@ public void synchronizedHandleRequest_DauEnforcementException_setsStatusCode503( ServerRpcHandler serverRpcHandler = new ServerRpcHandler() { @Override - public void handleRpc(UI ui, Reader reader, VaadinRequest request) { + public void handleRpc(UI ui, String requestBody, + VaadinRequest request) { throw new DauEnforcementException( new EnforcementException("test")); } @@ -254,7 +258,7 @@ protected ServerRpcHandler createRpcHandler() { } }; - handler.synchronizedHandleRequest(session, request, response); + handler.synchronizedHandleRequest(session, request, response, ""); Mockito.verify(response).setHeader(DAUUtils.STATUS_CODE_KEY, "503"); } diff --git a/flow-server/src/test/java/com/vaadin/flow/server/frontend/TaskGeneratePWAIconsTest.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/TaskGeneratePWAIconsTest.java new file mode 100644 index 00000000000..42ab3812c15 --- /dev/null +++ b/flow-server/src/test/java/com/vaadin/flow/server/frontend/TaskGeneratePWAIconsTest.java @@ -0,0 +1,183 @@ +/* + * Copyright 2000-2024 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.frontend; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.function.Predicate; +import java.util.regex.Pattern; + +import org.junit.Assert; +import org.junit.Before; +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.server.Constants; +import com.vaadin.flow.server.ExecutionFailedException; +import com.vaadin.flow.server.PwaConfiguration; +import com.vaadin.flow.server.frontend.scanner.ClassFinder; +import com.vaadin.flow.server.frontend.scanner.FrontendDependenciesScanner; + +public class TaskGeneratePWAIconsTest { + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + private final TestPwaConfiguration pwaConfiguration = new TestPwaConfiguration(); + private TaskGeneratePWAIcons task; + private Path resourcesDirectory; + private Path iconsOutDirectory; + + @Before + public void setUp() throws Exception { + // creating non-existing folder to make sure the execute() creates + // the folder if missing + File projectDirectory = temporaryFolder.newFolder("my-project"); + resourcesDirectory = temporaryFolder + .newFolder("my-project", "out", "classes").toPath(); + Files.createDirectories(resourcesDirectory); + Path resourceOutDirectory = projectDirectory.toPath() + .resolve(Path.of("out", "VAADIN")); + Path wabappResourceOutDirectory = resourceOutDirectory + .resolve("wabapp"); + iconsOutDirectory = wabappResourceOutDirectory + .resolve(Constants.VAADIN_PWA_ICONS); + + FrontendDependenciesScanner scanner = Mockito + .mock(FrontendDependenciesScanner.class); + Mockito.when(scanner.getPwaConfiguration()).then(i -> pwaConfiguration); + + URLClassLoader classFinderClassLoader = new URLClassLoader( + new URL[] { resourcesDirectory.toUri().toURL() }, null); + ClassFinder classFinder = new ClassFinder.DefaultClassFinder( + classFinderClassLoader); + Options options = new Options(Mockito.mock(Lookup.class), classFinder, + projectDirectory) + .withBuildResultFolders(wabappResourceOutDirectory.toFile(), + resourceOutDirectory.toFile()); + task = new TaskGeneratePWAIcons(options, pwaConfiguration); + } + + @Test + public void execute_PWA_disabled_iconsNotGenerated() + throws ExecutionFailedException { + pwaConfiguration.enabled = false; + task.execute(); + Assert.assertFalse("PWA icons should not have been generated", + Files.exists(iconsOutDirectory)); + } + + @Test + public void execute_PWA_iconInClassPath_generateIcons() + throws ExecutionFailedException, IOException { + createBaseIcon(resourcesDirectory); + task.execute(); + assertIconsGenerated(); + } + + @Test + public void execute_PWA_iconInMetaInfResourcesFolder_generateIcons() + throws ExecutionFailedException, IOException { + createBaseIcon( + resourcesDirectory.resolve(Path.of("META-INF", "resources"))); + task.execute(); + assertIconsGenerated(); + } + + @Test + public void execute_PWA_baseIconNotFound_generateIconsFromDefaultLogo() + throws ExecutionFailedException, IOException { + task.execute(); + assertIconsGenerated(); + } + + @Test + public void execute_PWA_invalidBaseIconNotFound_throws() + throws IOException { + createBaseIcon( + resourcesDirectory.resolve(Path.of("META-INF", "resources")), + new ByteArrayInputStream("NOT AN IMAGE".getBytes())); + ExecutionFailedException exception = Assert + .assertThrows(ExecutionFailedException.class, task::execute); + Assert.assertTrue( + exception.getMessage().contains("Cannot load PWA icon")); + Assert.assertFalse("PWA icons should not have been generated", + Files.exists(iconsOutDirectory)); + } + + private void createBaseIcon(Path resourcesFolder) throws IOException { + createBaseIcon(resourcesFolder, getClass() + .getResourceAsStream("/META-INF/resources/icons/icon.png")); + } + + private void createBaseIcon(Path resourcesFolder, InputStream data) + throws IOException { + Path baseIcon = resourcesFolder.resolve(resourcesFolder) + .resolve(pwaConfiguration.getIconPath().replace('/', + File.separatorChar)); + Files.createDirectories(baseIcon.getParent()); + Files.copy(data, baseIcon); + } + + private void assertIconsGenerated() throws IOException { + String iconPath = pwaConfiguration.getIconPath(); + Path generatedIconsPath = iconsOutDirectory + .resolve(iconPath.replace('/', File.separatorChar)).getParent(); + Assert.assertTrue("PWA icons folder should have been generated", + Files.exists(generatedIconsPath)); + String iconName = iconPath.substring(iconPath.lastIndexOf("/") + 1, + iconPath.lastIndexOf(".")); + String iconExt = iconPath.substring(iconPath.lastIndexOf(".") + 1); + Predicate iconNamePattern = Pattern + .compile(iconName + "-\\d+x\\d+\\." + iconExt).asPredicate(); + List generatedIcons = Files.list(generatedIconsPath) + .map(p -> p.getFileName().toString()).toList(); + Assert.assertFalse("Expected PWA icons to be generated", + generatedIcons.isEmpty()); + List invalidIcons = generatedIcons.stream() + .filter(iconNamePattern.negate()).toList(); + Assert.assertTrue("Generated icons have invalid names: " + invalidIcons, + invalidIcons.isEmpty()); + + } + + private static class TestPwaConfiguration extends PwaConfiguration { + private Boolean enabled; + + public TestPwaConfiguration() { + super(true, DEFAULT_NAME, "Flow PWA", "", DEFAULT_BACKGROUND_COLOR, + DEFAULT_THEME_COLOR, "custom/icons/logo.png", DEFAULT_PATH, + DEFAULT_OFFLINE_PATH, DEFAULT_DISPLAY, DEFAULT_START_URL, + new String[] {}, false); + } + + @Override + public boolean isEnabled() { + return enabled != null ? enabled : super.isEnabled(); + } + + } +} diff --git a/flow-server/src/test/java/com/vaadin/flow/server/frontend/VersionsJsonConverterTest.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/VersionsJsonConverterTest.java index f5b694c66fb..bb616a67a76 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/frontend/VersionsJsonConverterTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/frontend/VersionsJsonConverterTest.java @@ -228,6 +228,41 @@ public void reactRouterNotUsed_reactComponentsIgnored() { convertedJson.getString("@polymer/iron-list")); } + @Test + public void reactRouterUsed_noVaadinRouterAdded() { + String json = """ + { + "core": { + "flow": { + "javaVersion": "3.0.0.alpha17" + }, + }, + "vaadin-router": { + "npmName": "@vaadin/router", + "jsVersion": "2.0.0" + }, + "react": { + "react-components": { + "jsVersion": "24.4.0-alpha7", + "npmName": "@vaadin/react-components", + "mode": "react" + } + }, + "platform": "foo" + } + """.formatted(VAADIN_CORE_NPM_PACKAGE); + + VersionsJsonConverter convert = new VersionsJsonConverter( + Json.parse(json), true, false); + JsonObject convertedJson = convert.getConvertedJson(); + + Assert.assertFalse( + "Found @vaadin/router even though it should not be in use.", + convertedJson.hasKey("@vaadin/router")); + Assert.assertTrue("Missing react-components", + convertedJson.hasKey("@vaadin/react-components")); + } + @Test public void testModeProperty() { String json = """ diff --git a/flow-test-generic/pom.xml b/flow-test-generic/pom.xml index d05e4f29560..fb3994a9e03 100644 --- a/flow-test-generic/pom.xml +++ b/flow-test-generic/pom.xml @@ -5,7 +5,7 @@ flow-project com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 diff --git a/flow-test-util/pom.xml b/flow-test-util/pom.xml index 709edaeec22..161122be098 100644 --- a/flow-test-util/pom.xml +++ b/flow-test-util/pom.xml @@ -5,7 +5,7 @@ com.vaadin flow-project - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-util diff --git a/flow-tests/pom.xml b/flow-tests/pom.xml index f3b48426459..aa7c2e83a08 100644 --- a/flow-tests/pom.xml +++ b/flow-tests/pom.xml @@ -6,7 +6,7 @@ com.vaadin flow-project - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-tests Flow tests @@ -430,6 +430,23 @@ + + + ci-devtools + + + teamcity.build.id + + + + + org.seleniumhq.selenium + selenium-devtools-v127 + 4.25.0 + test + + + deepClean diff --git a/flow-tests/servlet-containers/pom.xml b/flow-tests/servlet-containers/pom.xml index 40106429f58..36102caf3bb 100644 --- a/flow-tests/servlet-containers/pom.xml +++ b/flow-tests/servlet-containers/pom.xml @@ -5,7 +5,7 @@ com.vaadin flow-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-servlet-containers-test flow-servlet-containers-test diff --git a/flow-tests/servlet-containers/tomcat10/pom.xml b/flow-tests/servlet-containers/tomcat10/pom.xml index 87abc7aeba7..03821bdd4e7 100644 --- a/flow-tests/servlet-containers/tomcat10/pom.xml +++ b/flow-tests/servlet-containers/tomcat10/pom.xml @@ -5,7 +5,7 @@ com.vaadin flow-servlet-containers-test - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-tomcat10-server Flow Tomcat 9 Test diff --git a/flow-tests/test-application-theme/pom.xml b/flow-tests/test-application-theme/pom.xml index 7b3565e3472..02fe4e34f79 100644 --- a/flow-tests/test-application-theme/pom.xml +++ b/flow-tests/test-application-theme/pom.xml @@ -5,7 +5,7 @@ flow-tests com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 diff --git a/flow-tests/test-application-theme/reusable-theme/pom.xml b/flow-tests/test-application-theme/reusable-theme/pom.xml index 0c5128fc478..8d140abcba1 100644 --- a/flow-tests/test-application-theme/reusable-theme/pom.xml +++ b/flow-tests/test-application-theme/reusable-theme/pom.xml @@ -5,7 +5,7 @@ test-application-theme com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 diff --git a/flow-tests/test-application-theme/test-reusable-as-parent-vite/pom.xml b/flow-tests/test-application-theme/test-reusable-as-parent-vite/pom.xml index 7ced91134fd..ec20e21d012 100644 --- a/flow-tests/test-application-theme/test-reusable-as-parent-vite/pom.xml +++ b/flow-tests/test-application-theme/test-reusable-as-parent-vite/pom.xml @@ -3,7 +3,7 @@ test-application-theme com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 diff --git a/flow-tests/test-application-theme/test-theme-component-live-reload/pom.xml b/flow-tests/test-application-theme/test-theme-component-live-reload/pom.xml index 53a7ae1f19e..4e9c43927e7 100644 --- a/flow-tests/test-application-theme/test-theme-component-live-reload/pom.xml +++ b/flow-tests/test-application-theme/test-theme-component-live-reload/pom.xml @@ -3,7 +3,7 @@ test-application-theme com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 flow-test-application-theme-component-live-reload diff --git a/flow-tests/test-application-theme/test-theme-live-reload/pom.xml b/flow-tests/test-application-theme/test-theme-live-reload/pom.xml index 4a0efa72acf..4457ce18563 100644 --- a/flow-tests/test-application-theme/test-theme-live-reload/pom.xml +++ b/flow-tests/test-application-theme/test-theme-live-reload/pom.xml @@ -3,7 +3,7 @@ test-application-theme com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 flow-test-application-theme-live-reload diff --git a/flow-tests/test-application-theme/test-theme-reusable-vite/pom.xml b/flow-tests/test-application-theme/test-theme-reusable-vite/pom.xml index 3843a2cd4bd..7bbb2643f01 100644 --- a/flow-tests/test-application-theme/test-theme-reusable-vite/pom.xml +++ b/flow-tests/test-application-theme/test-theme-reusable-vite/pom.xml @@ -3,7 +3,7 @@ test-application-theme com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 diff --git a/flow-tests/test-ccdm-flow-navigation/pom-production.xml b/flow-tests/test-ccdm-flow-navigation/pom-production.xml index b6994633276..f5996d5d22c 100644 --- a/flow-tests/test-ccdm-flow-navigation/pom-production.xml +++ b/flow-tests/test-ccdm-flow-navigation/pom-production.xml @@ -3,7 +3,7 @@ com.vaadin flow-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 diff --git a/flow-tests/test-ccdm-flow-navigation/pom.xml b/flow-tests/test-ccdm-flow-navigation/pom.xml index 31e4f5e5a39..b73319a5095 100644 --- a/flow-tests/test-ccdm-flow-navigation/pom.xml +++ b/flow-tests/test-ccdm-flow-navigation/pom.xml @@ -4,7 +4,7 @@ com.vaadin flow-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 diff --git a/flow-tests/test-ccdm/pom-production.xml b/flow-tests/test-ccdm/pom-production.xml index a7ccdfbf86a..cabffb6dd84 100644 --- a/flow-tests/test-ccdm/pom-production.xml +++ b/flow-tests/test-ccdm/pom-production.xml @@ -3,7 +3,7 @@ com.vaadin flow-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 diff --git a/flow-tests/test-ccdm/pom.xml b/flow-tests/test-ccdm/pom.xml index 48e678f3644..e5560614614 100644 --- a/flow-tests/test-ccdm/pom.xml +++ b/flow-tests/test-ccdm/pom.xml @@ -5,7 +5,7 @@ com.vaadin flow-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 diff --git a/flow-tests/test-common/pom.xml b/flow-tests/test-common/pom.xml index cd499e87506..2043a073c3f 100644 --- a/flow-tests/test-common/pom.xml +++ b/flow-tests/test-common/pom.xml @@ -6,7 +6,7 @@ com.vaadin flow-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-common Flow common test UI classes diff --git a/flow-tests/test-custom-frontend-directory/pom.xml b/flow-tests/test-custom-frontend-directory/pom.xml index b2b7a592b35..84c4727a195 100644 --- a/flow-tests/test-custom-frontend-directory/pom.xml +++ b/flow-tests/test-custom-frontend-directory/pom.xml @@ -5,7 +5,7 @@ flow-tests com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 diff --git a/flow-tests/test-custom-frontend-directory/test-themes-custom-frontend-directory/pom-generatedTsDir.xml b/flow-tests/test-custom-frontend-directory/test-themes-custom-frontend-directory/pom-generatedTsDir.xml index 12fa9397f0d..74224ed4f0f 100644 --- a/flow-tests/test-custom-frontend-directory/test-themes-custom-frontend-directory/pom-generatedTsDir.xml +++ b/flow-tests/test-custom-frontend-directory/test-themes-custom-frontend-directory/pom-generatedTsDir.xml @@ -4,7 +4,7 @@ com.vaadin test-custom-frontend-directory - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-themes-custom-generatedTs-directory Flow themes tests in NPM mode with custom generatedTs directory diff --git a/flow-tests/test-custom-frontend-directory/test-themes-custom-frontend-directory/pom.xml b/flow-tests/test-custom-frontend-directory/test-themes-custom-frontend-directory/pom.xml index 7bf61a77047..2b49b651be4 100644 --- a/flow-tests/test-custom-frontend-directory/test-themes-custom-frontend-directory/pom.xml +++ b/flow-tests/test-custom-frontend-directory/test-themes-custom-frontend-directory/pom.xml @@ -5,7 +5,7 @@ com.vaadin test-custom-frontend-directory - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-themes-custom-frontend-directory-vite Flow themes tests in Vite with custom frontend directory diff --git a/flow-tests/test-custom-route-registry/pom.xml b/flow-tests/test-custom-route-registry/pom.xml index 63b384cb89e..e0bdc33bbba 100644 --- a/flow-tests/test-custom-route-registry/pom.xml +++ b/flow-tests/test-custom-route-registry/pom.xml @@ -4,7 +4,7 @@ flow-tests com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT test-custom-route-registry Test using Flow with a custom RouteRegistry implementation diff --git a/flow-tests/test-dev-mode/pom.xml b/flow-tests/test-dev-mode/pom.xml index 7874ddb2cae..93ee1fc4445 100644 --- a/flow-tests/test-dev-mode/pom.xml +++ b/flow-tests/test-dev-mode/pom.xml @@ -4,7 +4,7 @@ com.vaadin flow-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-dev-mode Flow tests for dev mode diff --git a/flow-tests/test-eager-bootstrap/pom.xml b/flow-tests/test-eager-bootstrap/pom.xml index 549bf704246..0351086ec37 100644 --- a/flow-tests/test-eager-bootstrap/pom.xml +++ b/flow-tests/test-eager-bootstrap/pom.xml @@ -4,7 +4,7 @@ com.vaadin flow-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-eager-bootstrap Flow eager bootstrap (includes UIDL in first request) test diff --git a/flow-tests/test-embedding/embedding-reusable-custom-theme/pom.xml b/flow-tests/test-embedding/embedding-reusable-custom-theme/pom.xml index f023a63f3aa..1c1b22ef409 100644 --- a/flow-tests/test-embedding/embedding-reusable-custom-theme/pom.xml +++ b/flow-tests/test-embedding/embedding-reusable-custom-theme/pom.xml @@ -21,7 +21,7 @@ test-embedding com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 diff --git a/flow-tests/test-embedding/embedding-test-assets/pom.xml b/flow-tests/test-embedding/embedding-test-assets/pom.xml index 247a344a4c3..784b80d8c00 100644 --- a/flow-tests/test-embedding/embedding-test-assets/pom.xml +++ b/flow-tests/test-embedding/embedding-test-assets/pom.xml @@ -21,7 +21,7 @@ test-embedding com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 diff --git a/flow-tests/test-embedding/pom.xml b/flow-tests/test-embedding/pom.xml index d373ecdd7b4..c1c8db00803 100644 --- a/flow-tests/test-embedding/pom.xml +++ b/flow-tests/test-embedding/pom.xml @@ -21,7 +21,7 @@ flow-tests com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 diff --git a/flow-tests/test-embedding/test-embedding-application-theme/pom.xml b/flow-tests/test-embedding/test-embedding-application-theme/pom.xml index d765888521d..fb2322038bc 100644 --- a/flow-tests/test-embedding/test-embedding-application-theme/pom.xml +++ b/flow-tests/test-embedding/test-embedding-application-theme/pom.xml @@ -19,7 +19,7 @@ test-embedding com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 diff --git a/flow-tests/test-embedding/test-embedding-generic/pom.xml b/flow-tests/test-embedding/test-embedding-generic/pom.xml index 13f941ccab9..5d94efe61b6 100644 --- a/flow-tests/test-embedding/test-embedding-generic/pom.xml +++ b/flow-tests/test-embedding/test-embedding-generic/pom.xml @@ -4,7 +4,7 @@ com.vaadin test-embedding - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-embedding-generic Flow Embedding, generic tests diff --git a/flow-tests/test-embedding/test-embedding-production-mode/pom.xml b/flow-tests/test-embedding/test-embedding-production-mode/pom.xml index 3118d23ddb3..d4107a1a669 100644 --- a/flow-tests/test-embedding/test-embedding-production-mode/pom.xml +++ b/flow-tests/test-embedding/test-embedding-production-mode/pom.xml @@ -4,7 +4,7 @@ com.vaadin test-embedding - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-embedding-production Flow Embedding, production tests diff --git a/flow-tests/test-embedding/test-embedding-reusable-theme/pom.xml b/flow-tests/test-embedding/test-embedding-reusable-theme/pom.xml index eec399698cc..34351de8762 100644 --- a/flow-tests/test-embedding/test-embedding-reusable-theme/pom.xml +++ b/flow-tests/test-embedding/test-embedding-reusable-theme/pom.xml @@ -19,7 +19,7 @@ test-embedding com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 diff --git a/flow-tests/test-embedding/test-embedding-theme-variant/pom.xml b/flow-tests/test-embedding/test-embedding-theme-variant/pom.xml index fc0afece64b..74bed772bf6 100644 --- a/flow-tests/test-embedding/test-embedding-theme-variant/pom.xml +++ b/flow-tests/test-embedding/test-embedding-theme-variant/pom.xml @@ -20,7 +20,7 @@ com.vaadin test-embedding - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-embedding-theme-variant Flow Embedding, theme variant diff --git a/flow-tests/test-express-build/frontend-add-on/pom.xml b/flow-tests/test-express-build/frontend-add-on/pom.xml index 37bb0298f94..0ec174968cb 100644 --- a/flow-tests/test-express-build/frontend-add-on/pom.xml +++ b/flow-tests/test-express-build/frontend-add-on/pom.xml @@ -5,7 +5,7 @@ com.vaadin test-express-build - 24.6-SNAPSHOT + 24.7-SNAPSHOT frontend-add-on jar diff --git a/flow-tests/test-express-build/java-add-on/pom.xml b/flow-tests/test-express-build/java-add-on/pom.xml index bbc62da3dfd..e9dbb3ee6e3 100644 --- a/flow-tests/test-express-build/java-add-on/pom.xml +++ b/flow-tests/test-express-build/java-add-on/pom.xml @@ -5,7 +5,7 @@ com.vaadin test-express-build - 24.6-SNAPSHOT + 24.7-SNAPSHOT java-add-on jar diff --git a/flow-tests/test-express-build/pom.xml b/flow-tests/test-express-build/pom.xml index 3520ad6cfa2..e4ef2e7049f 100644 --- a/flow-tests/test-express-build/pom.xml +++ b/flow-tests/test-express-build/pom.xml @@ -5,7 +5,7 @@ flow-tests com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 diff --git a/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/pom.xml b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/pom.xml index 4ef597ebb9d..1cf05a7c863 100644 --- a/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/pom.xml +++ b/flow-tests/test-express-build/test-dev-bundle-frontend-add-on/pom.xml @@ -19,7 +19,7 @@ test-express-build com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 diff --git a/flow-tests/test-express-build/test-dev-bundle-java-add-on/pom.xml b/flow-tests/test-express-build/test-dev-bundle-java-add-on/pom.xml index 1c13b5e3c1c..6fa6e8c7f2d 100644 --- a/flow-tests/test-express-build/test-dev-bundle-java-add-on/pom.xml +++ b/flow-tests/test-express-build/test-dev-bundle-java-add-on/pom.xml @@ -19,7 +19,7 @@ test-express-build com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 diff --git a/flow-tests/test-express-build/test-dev-bundle-no-plugin/pom.xml b/flow-tests/test-express-build/test-dev-bundle-no-plugin/pom.xml index eacdb1dea06..556d9645d58 100644 --- a/flow-tests/test-express-build/test-dev-bundle-no-plugin/pom.xml +++ b/flow-tests/test-express-build/test-dev-bundle-no-plugin/pom.xml @@ -19,7 +19,7 @@ test-express-build com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 diff --git a/flow-tests/test-express-build/test-dev-bundle/pom.xml b/flow-tests/test-express-build/test-dev-bundle/pom.xml index 882d514c0e2..2419b5ccd91 100644 --- a/flow-tests/test-express-build/test-dev-bundle/pom.xml +++ b/flow-tests/test-express-build/test-dev-bundle/pom.xml @@ -5,7 +5,7 @@ com.vaadin test-express-build - 24.6-SNAPSHOT + 24.7-SNAPSHOT test-dev-bundle jar diff --git a/flow-tests/test-express-build/test-embedding-express-build/pom.xml b/flow-tests/test-express-build/test-embedding-express-build/pom.xml index d3fdb974fd5..1b4b490a0fb 100644 --- a/flow-tests/test-express-build/test-embedding-express-build/pom.xml +++ b/flow-tests/test-express-build/test-embedding-express-build/pom.xml @@ -19,7 +19,7 @@ test-express-build com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 diff --git a/flow-tests/test-express-build/test-flow-maven-plugin/pom.xml b/flow-tests/test-express-build/test-flow-maven-plugin/pom.xml index 80ee6b2fed1..c95ffb007ce 100644 --- a/flow-tests/test-express-build/test-flow-maven-plugin/pom.xml +++ b/flow-tests/test-express-build/test-flow-maven-plugin/pom.xml @@ -4,7 +4,7 @@ test-express-build com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT test-flow-maven-plugin maven-plugin diff --git a/flow-tests/test-express-build/test-parent-theme-express-build/pom.xml b/flow-tests/test-express-build/test-parent-theme-express-build/pom.xml index 4b5ba41eee5..04ef6fe6e41 100644 --- a/flow-tests/test-express-build/test-parent-theme-express-build/pom.xml +++ b/flow-tests/test-express-build/test-parent-theme-express-build/pom.xml @@ -3,7 +3,7 @@ test-express-build com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 flow-test-parent-theme-express-bundle diff --git a/flow-tests/test-express-build/test-parent-theme-in-frontend-prod/pom.xml b/flow-tests/test-express-build/test-parent-theme-in-frontend-prod/pom.xml index 323199436a5..45bb0e0396d 100644 --- a/flow-tests/test-express-build/test-parent-theme-in-frontend-prod/pom.xml +++ b/flow-tests/test-express-build/test-parent-theme-in-frontend-prod/pom.xml @@ -3,7 +3,7 @@ test-express-build com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 flow-test-parent-theme-in-frontend-prod diff --git a/flow-tests/test-express-build/test-parent-theme-in-frontend/pom.xml b/flow-tests/test-express-build/test-parent-theme-in-frontend/pom.xml index 35b6894c95f..f493f40aa3f 100644 --- a/flow-tests/test-express-build/test-parent-theme-in-frontend/pom.xml +++ b/flow-tests/test-express-build/test-parent-theme-in-frontend/pom.xml @@ -3,7 +3,7 @@ test-express-build com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 flow-test-parent-theme-in-frontend diff --git a/flow-tests/test-express-build/test-parent-theme-prod/pom.xml b/flow-tests/test-express-build/test-parent-theme-prod/pom.xml index 1297e447288..613cff7a6ec 100644 --- a/flow-tests/test-express-build/test-parent-theme-prod/pom.xml +++ b/flow-tests/test-express-build/test-parent-theme-prod/pom.xml @@ -3,7 +3,7 @@ test-express-build com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 flow-test-parent-theme-prod diff --git a/flow-tests/test-express-build/test-prod-bundle-no-plugin/pom.xml b/flow-tests/test-express-build/test-prod-bundle-no-plugin/pom.xml index 54c380e88c9..10069560683 100644 --- a/flow-tests/test-express-build/test-prod-bundle-no-plugin/pom.xml +++ b/flow-tests/test-express-build/test-prod-bundle-no-plugin/pom.xml @@ -19,7 +19,7 @@ test-express-build com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 diff --git a/flow-tests/test-express-build/test-prod-bundle/pom.xml b/flow-tests/test-express-build/test-prod-bundle/pom.xml index 5fc3a1979c4..655746b6550 100644 --- a/flow-tests/test-express-build/test-prod-bundle/pom.xml +++ b/flow-tests/test-express-build/test-prod-bundle/pom.xml @@ -5,7 +5,7 @@ com.vaadin test-express-build - 24.6-SNAPSHOT + 24.7-SNAPSHOT test-prod-bundle jar diff --git a/flow-tests/test-express-build/test-reusable-theme-express-build/pom.xml b/flow-tests/test-express-build/test-reusable-theme-express-build/pom.xml index 6a0b0e04fe3..d4700550629 100644 --- a/flow-tests/test-express-build/test-reusable-theme-express-build/pom.xml +++ b/flow-tests/test-express-build/test-reusable-theme-express-build/pom.xml @@ -3,7 +3,7 @@ test-express-build com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 flow-test-reusable-theme-express-build diff --git a/flow-tests/test-express-build/test-reusable-theme-no-assets/pom.xml b/flow-tests/test-express-build/test-reusable-theme-no-assets/pom.xml index b7754ec7116..9837fd95b55 100644 --- a/flow-tests/test-express-build/test-reusable-theme-no-assets/pom.xml +++ b/flow-tests/test-express-build/test-reusable-theme-no-assets/pom.xml @@ -3,7 +3,7 @@ test-express-build com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 flow-test-reusable-theme-no-assets diff --git a/flow-tests/test-express-build/test-reusing-theme-express-build/pom.xml b/flow-tests/test-express-build/test-reusing-theme-express-build/pom.xml index 4a25dbc4d7f..678eb3c738b 100644 --- a/flow-tests/test-express-build/test-reusing-theme-express-build/pom.xml +++ b/flow-tests/test-express-build/test-reusing-theme-express-build/pom.xml @@ -3,7 +3,7 @@ test-express-build com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 flow-test-reusing-theme-express-bundle diff --git a/flow-tests/test-express-build/test-theme-dev-bundle/pom.xml b/flow-tests/test-express-build/test-theme-dev-bundle/pom.xml index 9f7914c1cc9..8a8bf4393b7 100644 --- a/flow-tests/test-express-build/test-theme-dev-bundle/pom.xml +++ b/flow-tests/test-express-build/test-theme-dev-bundle/pom.xml @@ -3,7 +3,7 @@ test-express-build com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 flow-test-theme-dev-bundle diff --git a/flow-tests/test-express-build/test-theme-legacy-components-css-prod/pom.xml b/flow-tests/test-express-build/test-theme-legacy-components-css-prod/pom.xml index 922e6eee088..889e702781d 100644 --- a/flow-tests/test-express-build/test-theme-legacy-components-css-prod/pom.xml +++ b/flow-tests/test-express-build/test-theme-legacy-components-css-prod/pom.xml @@ -3,7 +3,7 @@ test-express-build com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 flow-test-theme-legacy-components-css-prod diff --git a/flow-tests/test-frontend/addon-with-templates/pom.xml b/flow-tests/test-frontend/addon-with-templates/pom.xml index 5e1ee0cca9b..ec3e14db833 100644 --- a/flow-tests/test-frontend/addon-with-templates/pom.xml +++ b/flow-tests/test-frontend/addon-with-templates/pom.xml @@ -7,7 +7,7 @@ com.vaadin test-frontend - 24.6-SNAPSHOT + 24.7-SNAPSHOT addon-with-templates diff --git a/flow-tests/test-frontend/pom.xml b/flow-tests/test-frontend/pom.xml index 2fc3e33f22b..86ba202ee61 100644 --- a/flow-tests/test-frontend/pom.xml +++ b/flow-tests/test-frontend/pom.xml @@ -5,7 +5,7 @@ com.vaadin flow-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT test-frontend Frontend build (npm + Webpack + Vite) diff --git a/flow-tests/test-frontend/test-bun/pom-production.xml b/flow-tests/test-frontend/test-bun/pom-production.xml index 5f794b77174..bc6a958bd9a 100644 --- a/flow-tests/test-frontend/test-bun/pom-production.xml +++ b/flow-tests/test-frontend/test-bun/pom-production.xml @@ -4,7 +4,7 @@ test-frontend com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-bun-production Flow tests in bun and production mode diff --git a/flow-tests/test-frontend/test-bun/pom.xml b/flow-tests/test-frontend/test-bun/pom.xml index fcff8756da5..e8843469888 100644 --- a/flow-tests/test-frontend/test-bun/pom.xml +++ b/flow-tests/test-frontend/test-bun/pom.xml @@ -4,7 +4,7 @@ test-frontend com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-bun-dev-mode Flow tests in bun and development mode diff --git a/flow-tests/test-frontend/test-npm/pom-production.xml b/flow-tests/test-frontend/test-npm/pom-production.xml index 292c3bee64e..9d8070936f2 100644 --- a/flow-tests/test-frontend/test-npm/pom-production.xml +++ b/flow-tests/test-frontend/test-npm/pom-production.xml @@ -4,7 +4,7 @@ test-frontend com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-npm-production Flow tests in npm and production mode diff --git a/flow-tests/test-frontend/test-npm/pom.xml b/flow-tests/test-frontend/test-npm/pom.xml index 14b7483808f..7c423be3f0d 100644 --- a/flow-tests/test-frontend/test-npm/pom.xml +++ b/flow-tests/test-frontend/test-npm/pom.xml @@ -4,7 +4,7 @@ test-frontend com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-npm-dev-mode Flow tests in npm and development mode diff --git a/flow-tests/test-frontend/test-pnpm/pom-production.xml b/flow-tests/test-frontend/test-pnpm/pom-production.xml index ffcddef88dc..593dc11aa9f 100644 --- a/flow-tests/test-frontend/test-pnpm/pom-production.xml +++ b/flow-tests/test-frontend/test-pnpm/pom-production.xml @@ -4,7 +4,7 @@ test-frontend com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-pnpm-production Flow tests in pnpm and production mode diff --git a/flow-tests/test-frontend/test-pnpm/pom.xml b/flow-tests/test-frontend/test-pnpm/pom.xml index 538602647b5..700bb93f506 100644 --- a/flow-tests/test-frontend/test-pnpm/pom.xml +++ b/flow-tests/test-frontend/test-pnpm/pom.xml @@ -4,7 +4,7 @@ test-frontend com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-pnpm-dev-mode Flow tests in pnpm and development mode diff --git a/flow-tests/test-frontend/vite-basics/pom.xml b/flow-tests/test-frontend/vite-basics/pom.xml index f9d84923740..9ab92ecc424 100644 --- a/flow-tests/test-frontend/vite-basics/pom.xml +++ b/flow-tests/test-frontend/vite-basics/pom.xml @@ -6,7 +6,7 @@ com.vaadin test-frontend - 24.6-SNAPSHOT + 24.7-SNAPSHOT vite-basics Vite dev mode functionality diff --git a/flow-tests/test-frontend/vite-context-path/pom-production.xml b/flow-tests/test-frontend/vite-context-path/pom-production.xml index 454696bcd37..cd5a8534975 100644 --- a/flow-tests/test-frontend/vite-context-path/pom-production.xml +++ b/flow-tests/test-frontend/vite-context-path/pom-production.xml @@ -6,7 +6,7 @@ com.vaadin test-frontend - 24.6-SNAPSHOT + 24.7-SNAPSHOT vite-context-path-production Vite with a context path (production mode) diff --git a/flow-tests/test-frontend/vite-context-path/pom.xml b/flow-tests/test-frontend/vite-context-path/pom.xml index 751eaa80cc8..80ba39351dd 100644 --- a/flow-tests/test-frontend/vite-context-path/pom.xml +++ b/flow-tests/test-frontend/vite-context-path/pom.xml @@ -6,7 +6,7 @@ com.vaadin test-frontend - 24.6-SNAPSHOT + 24.7-SNAPSHOT vite-context-path Vite with a context path diff --git a/flow-tests/test-frontend/vite-embedded-webcomponent-resync-longpolling/pom.xml b/flow-tests/test-frontend/vite-embedded-webcomponent-resync-longpolling/pom.xml index d66cd7aa8bf..53520c1cef2 100644 --- a/flow-tests/test-frontend/vite-embedded-webcomponent-resync-longpolling/pom.xml +++ b/flow-tests/test-frontend/vite-embedded-webcomponent-resync-longpolling/pom.xml @@ -6,7 +6,7 @@ com.vaadin test-frontend - 24.6-SNAPSHOT + 24.7-SNAPSHOT vite-embedded-webcomponent-resync-long-polling Vite embedded app - session resync with long polling PUSH diff --git a/flow-tests/test-frontend/vite-embedded-webcomponent-resync-ws/pom.xml b/flow-tests/test-frontend/vite-embedded-webcomponent-resync-ws/pom.xml index 63fbbdf7e89..73886afef8d 100644 --- a/flow-tests/test-frontend/vite-embedded-webcomponent-resync-ws/pom.xml +++ b/flow-tests/test-frontend/vite-embedded-webcomponent-resync-ws/pom.xml @@ -6,7 +6,7 @@ com.vaadin test-frontend - 24.6-SNAPSHOT + 24.7-SNAPSHOT vite-embedded-webcomponent-resync-ws Vite embedded app - session resync with websocket PUSH diff --git a/flow-tests/test-frontend/vite-embedded-webcomponent-resync-wsxhr/pom.xml b/flow-tests/test-frontend/vite-embedded-webcomponent-resync-wsxhr/pom.xml index a1e0d5eb372..3a3651e7b97 100644 --- a/flow-tests/test-frontend/vite-embedded-webcomponent-resync-wsxhr/pom.xml +++ b/flow-tests/test-frontend/vite-embedded-webcomponent-resync-wsxhr/pom.xml @@ -6,7 +6,7 @@ com.vaadin test-frontend - 24.6-SNAPSHOT + 24.7-SNAPSHOT vite-embedded-webcomponent-resync-wsxhr Vite embedded app - session resync with websocket XHR PUSH diff --git a/flow-tests/test-frontend/vite-embedded-webcomponent-resync/pom.xml b/flow-tests/test-frontend/vite-embedded-webcomponent-resync/pom.xml index dce8bfc80a9..ea0264724e9 100644 --- a/flow-tests/test-frontend/vite-embedded-webcomponent-resync/pom.xml +++ b/flow-tests/test-frontend/vite-embedded-webcomponent-resync/pom.xml @@ -6,7 +6,7 @@ com.vaadin test-frontend - 24.6-SNAPSHOT + 24.7-SNAPSHOT vite-embedded-webcomponent-resync Vite embedded app - session resync diff --git a/flow-tests/test-frontend/vite-embedded/pom-production.xml b/flow-tests/test-frontend/vite-embedded/pom-production.xml index cd55c0fdb0d..cb4abbe3eed 100644 --- a/flow-tests/test-frontend/vite-embedded/pom-production.xml +++ b/flow-tests/test-frontend/vite-embedded/pom-production.xml @@ -6,7 +6,7 @@ com.vaadin test-frontend - 24.6-SNAPSHOT + 24.7-SNAPSHOT vite-embedded-production Vite embedded app (production mode) diff --git a/flow-tests/test-frontend/vite-embedded/pom.xml b/flow-tests/test-frontend/vite-embedded/pom.xml index d9ff636c788..027ceeaf91f 100644 --- a/flow-tests/test-frontend/vite-embedded/pom.xml +++ b/flow-tests/test-frontend/vite-embedded/pom.xml @@ -6,7 +6,7 @@ com.vaadin test-frontend - 24.6-SNAPSHOT + 24.7-SNAPSHOT vite-embedded Vite embedded app diff --git a/flow-tests/test-frontend/vite-production-custom-frontend/pom.xml b/flow-tests/test-frontend/vite-production-custom-frontend/pom.xml index e2bf2a8118a..8fb5be02b1c 100644 --- a/flow-tests/test-frontend/vite-production-custom-frontend/pom.xml +++ b/flow-tests/test-frontend/vite-production-custom-frontend/pom.xml @@ -6,7 +6,7 @@ com.vaadin test-frontend - 24.6-SNAPSHOT + 24.7-SNAPSHOT vite-production-custom-frontend Vite production mode functionality (custom frontend directory) diff --git a/flow-tests/test-frontend/vite-production/pom.xml b/flow-tests/test-frontend/vite-production/pom.xml index e1e740db379..d853e29405f 100644 --- a/flow-tests/test-frontend/vite-production/pom.xml +++ b/flow-tests/test-frontend/vite-production/pom.xml @@ -6,7 +6,7 @@ com.vaadin test-frontend - 24.6-SNAPSHOT + 24.7-SNAPSHOT vite-production Vite production mode functionality diff --git a/flow-tests/test-frontend/vite-pwa-custom-offline-path/pom-production.xml b/flow-tests/test-frontend/vite-pwa-custom-offline-path/pom-production.xml index 464120fa7bf..ac27d380d1f 100644 --- a/flow-tests/test-frontend/vite-pwa-custom-offline-path/pom-production.xml +++ b/flow-tests/test-frontend/vite-pwa-custom-offline-path/pom-production.xml @@ -6,7 +6,7 @@ com.vaadin test-frontend - 24.6-SNAPSHOT + 24.7-SNAPSHOT vite-pwa-custom-offline-path-production Vite PWA app with a custom offline path (production mode) diff --git a/flow-tests/test-frontend/vite-pwa-custom-offline-path/pom.xml b/flow-tests/test-frontend/vite-pwa-custom-offline-path/pom.xml index c863453177e..29ebdce8fc7 100644 --- a/flow-tests/test-frontend/vite-pwa-custom-offline-path/pom.xml +++ b/flow-tests/test-frontend/vite-pwa-custom-offline-path/pom.xml @@ -4,7 +4,7 @@ test-frontend com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT vite-pwa-custom-offline-path Vite PWA app with a custom offline path diff --git a/flow-tests/test-frontend/vite-pwa-disabled-offline/pom-production.xml b/flow-tests/test-frontend/vite-pwa-disabled-offline/pom-production.xml index 30626a13fdc..5a40b4bcafb 100644 --- a/flow-tests/test-frontend/vite-pwa-disabled-offline/pom-production.xml +++ b/flow-tests/test-frontend/vite-pwa-disabled-offline/pom-production.xml @@ -4,7 +4,7 @@ test-frontend com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT vite-pwa-disabled-offline-production Vite PWA app with disabled offline (production mode) diff --git a/flow-tests/test-frontend/vite-pwa-disabled-offline/pom.xml b/flow-tests/test-frontend/vite-pwa-disabled-offline/pom.xml index 846edade1a0..76469f1d5ab 100644 --- a/flow-tests/test-frontend/vite-pwa-disabled-offline/pom.xml +++ b/flow-tests/test-frontend/vite-pwa-disabled-offline/pom.xml @@ -4,7 +4,7 @@ test-frontend com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT vite-pwa-disabled-offline Vite PWA app with disabled offline diff --git a/flow-tests/test-frontend/vite-pwa-production/pom.xml b/flow-tests/test-frontend/vite-pwa-production/pom.xml index 16a504ca768..18b01658e3d 100644 --- a/flow-tests/test-frontend/vite-pwa-production/pom.xml +++ b/flow-tests/test-frontend/vite-pwa-production/pom.xml @@ -6,7 +6,7 @@ com.vaadin test-frontend - 24.6-SNAPSHOT + 24.7-SNAPSHOT vite-pwa-production Vite PWA app (production mode) diff --git a/flow-tests/test-frontend/vite-pwa/pom.xml b/flow-tests/test-frontend/vite-pwa/pom.xml index 2334c2b0e95..829cfddb4dc 100644 --- a/flow-tests/test-frontend/vite-pwa/pom.xml +++ b/flow-tests/test-frontend/vite-pwa/pom.xml @@ -6,7 +6,7 @@ com.vaadin test-frontend - 24.6-SNAPSHOT + 24.7-SNAPSHOT vite-pwa Vite PWA app diff --git a/flow-tests/test-frontend/vite-test-assets/pom.xml b/flow-tests/test-frontend/vite-test-assets/pom.xml index 5766dbb45d9..1e34b79182e 100644 --- a/flow-tests/test-frontend/vite-test-assets/pom.xml +++ b/flow-tests/test-frontend/vite-test-assets/pom.xml @@ -21,7 +21,7 @@ test-frontend com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 diff --git a/flow-tests/test-legacy-frontend/pom.xml b/flow-tests/test-legacy-frontend/pom.xml index ae082d2e636..b638c5c40f8 100644 --- a/flow-tests/test-legacy-frontend/pom.xml +++ b/flow-tests/test-legacy-frontend/pom.xml @@ -4,7 +4,7 @@ com.vaadin flow-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-legacy-frontend Flow custom theme in legacy frontend folder diff --git a/flow-tests/test-live-reload-multimodule-devbundle/pom.xml b/flow-tests/test-live-reload-multimodule-devbundle/pom.xml index ead96925106..0d88929a2e2 100644 --- a/flow-tests/test-live-reload-multimodule-devbundle/pom.xml +++ b/flow-tests/test-live-reload-multimodule-devbundle/pom.xml @@ -6,7 +6,7 @@ flow-tests com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-live-reload-multimodule-devbundle The main module for a live reload multi module project (dev bundle) diff --git a/flow-tests/test-live-reload-multimodule/library/pom-devbundle.xml b/flow-tests/test-live-reload-multimodule/library/pom-devbundle.xml index 76767d24713..2d1bb84f4a4 100644 --- a/flow-tests/test-live-reload-multimodule/library/pom-devbundle.xml +++ b/flow-tests/test-live-reload-multimodule/library/pom-devbundle.xml @@ -6,7 +6,7 @@ flow-test-live-reload-multimodule-devbundle com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT ../../test-live-reload-multimodule-devbundle/pom.xml flow-test-live-reload-multimodule-library-devbundle diff --git a/flow-tests/test-live-reload-multimodule/library/pom.xml b/flow-tests/test-live-reload-multimodule/library/pom.xml index e7fbd31a670..a597ef07bab 100644 --- a/flow-tests/test-live-reload-multimodule/library/pom.xml +++ b/flow-tests/test-live-reload-multimodule/library/pom.xml @@ -6,7 +6,7 @@ flow-test-live-reload-multimodule-hotdeploy com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-live-reload-multimodule-library-hotdeploy The frontend library module for a live reload multi module project (hotdeploy) diff --git a/flow-tests/test-live-reload-multimodule/pom.xml b/flow-tests/test-live-reload-multimodule/pom.xml index 97442236172..9f7834c9cee 100644 --- a/flow-tests/test-live-reload-multimodule/pom.xml +++ b/flow-tests/test-live-reload-multimodule/pom.xml @@ -6,7 +6,7 @@ flow-tests com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-live-reload-multimodule-hotdeploy The main module for a live reload multi module project (hotdeploy) diff --git a/flow-tests/test-live-reload-multimodule/theme/pom-devbundle.xml b/flow-tests/test-live-reload-multimodule/theme/pom-devbundle.xml index c2232823af8..7503883f61e 100644 --- a/flow-tests/test-live-reload-multimodule/theme/pom-devbundle.xml +++ b/flow-tests/test-live-reload-multimodule/theme/pom-devbundle.xml @@ -6,7 +6,7 @@ flow-test-live-reload-multimodule-devbundle com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT ../../test-live-reload-multimodule-devbundle/pom.xml flow-test-live-reload-multimodule-theme-devbundle diff --git a/flow-tests/test-live-reload-multimodule/theme/pom.xml b/flow-tests/test-live-reload-multimodule/theme/pom.xml index f8da160b2cf..94462dd0850 100644 --- a/flow-tests/test-live-reload-multimodule/theme/pom.xml +++ b/flow-tests/test-live-reload-multimodule/theme/pom.xml @@ -6,7 +6,7 @@ flow-test-live-reload-multimodule-hotdeploy com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-live-reload-multimodule-theme-hotdeploy The theme module for a live reload multi module project (hotdeploy) diff --git a/flow-tests/test-live-reload-multimodule/ui/pom-devbundle.xml b/flow-tests/test-live-reload-multimodule/ui/pom-devbundle.xml index d69c158d160..1b98cf74b97 100644 --- a/flow-tests/test-live-reload-multimodule/ui/pom-devbundle.xml +++ b/flow-tests/test-live-reload-multimodule/ui/pom-devbundle.xml @@ -6,7 +6,7 @@ flow-test-live-reload-multimodule-devbundle com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT ../../test-live-reload-multimodule-devbundle/pom.xml flow-test-live-reload-multimodule-ui-devbundle diff --git a/flow-tests/test-live-reload-multimodule/ui/pom.xml b/flow-tests/test-live-reload-multimodule/ui/pom.xml index 165d7068150..32cca5ebd3e 100644 --- a/flow-tests/test-live-reload-multimodule/ui/pom.xml +++ b/flow-tests/test-live-reload-multimodule/ui/pom.xml @@ -6,7 +6,7 @@ flow-test-live-reload-multimodule-hotdeploy com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-live-reload-multimodule-ui-hotdeploy The UI module for a live reload multi module project (hotdeploy) diff --git a/flow-tests/test-live-reload/pom.xml b/flow-tests/test-live-reload/pom.xml index fb50378e540..1b4db1c4868 100644 --- a/flow-tests/test-live-reload/pom.xml +++ b/flow-tests/test-live-reload/pom.xml @@ -6,7 +6,7 @@ flow-tests com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-live-reload-mode Flow tests for live reload in dev mode diff --git a/flow-tests/test-lumo/pom.xml b/flow-tests/test-lumo/pom.xml index 71b46623822..84a6faac697 100644 --- a/flow-tests/test-lumo/pom.xml +++ b/flow-tests/test-lumo/pom.xml @@ -6,7 +6,7 @@ com.vaadin flow-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-lumo Lumo class for use in test modules requiring LUMO diff --git a/flow-tests/test-misc/pom.xml b/flow-tests/test-misc/pom.xml index 5c84e6bf6bf..e38e293b1b8 100644 --- a/flow-tests/test-misc/pom.xml +++ b/flow-tests/test-misc/pom.xml @@ -4,7 +4,7 @@ flow-tests com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-misc-test Flow Miscelaneous tests in npm diff --git a/flow-tests/test-multi-war/deployment/pom.xml b/flow-tests/test-multi-war/deployment/pom.xml index 7b0369929c6..7ddd5219970 100644 --- a/flow-tests/test-multi-war/deployment/pom.xml +++ b/flow-tests/test-multi-war/deployment/pom.xml @@ -4,7 +4,7 @@ flow-test-multi-war com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-multi-war-bundle Bundle testing multiple war deployment diff --git a/flow-tests/test-multi-war/pom.xml b/flow-tests/test-multi-war/pom.xml index 0c922d6b05f..84a8586504c 100644 --- a/flow-tests/test-multi-war/pom.xml +++ b/flow-tests/test-multi-war/pom.xml @@ -6,7 +6,7 @@ flow-tests com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-multi-war Flow tests with more than one war deployed at the same time diff --git a/flow-tests/test-multi-war/test-war1/pom.xml b/flow-tests/test-multi-war/test-war1/pom.xml index b70503157fd..3935d64827f 100644 --- a/flow-tests/test-multi-war/test-war1/pom.xml +++ b/flow-tests/test-multi-war/test-war1/pom.xml @@ -6,7 +6,7 @@ flow-test-multi-war com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-multi-war1 First war for multi war tests diff --git a/flow-tests/test-multi-war/test-war2/pom.xml b/flow-tests/test-multi-war/test-war2/pom.xml index 96441f8fbf2..220e52896ad 100644 --- a/flow-tests/test-multi-war/test-war2/pom.xml +++ b/flow-tests/test-multi-war/test-war2/pom.xml @@ -6,7 +6,7 @@ flow-test-multi-war com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-multi-war2 Second war for multi war tests diff --git a/flow-tests/test-no-theme/pom.xml b/flow-tests/test-no-theme/pom.xml index b9b1f314758..169ec5895d7 100644 --- a/flow-tests/test-no-theme/pom.xml +++ b/flow-tests/test-no-theme/pom.xml @@ -4,7 +4,7 @@ com.vaadin flow-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-no-theme Flow tests for no theme in NPM mode diff --git a/flow-tests/test-npm-only-features/pom.xml b/flow-tests/test-npm-only-features/pom.xml index d98ac547833..f5696177419 100644 --- a/flow-tests/test-npm-only-features/pom.xml +++ b/flow-tests/test-npm-only-features/pom.xml @@ -21,7 +21,7 @@ flow-tests com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 diff --git a/flow-tests/test-npm-only-features/test-npm-bytecode-scanning/pom-devmode.xml b/flow-tests/test-npm-only-features/test-npm-bytecode-scanning/pom-devmode.xml index a27e52e040b..24f04fb40c4 100644 --- a/flow-tests/test-npm-only-features/test-npm-bytecode-scanning/pom-devmode.xml +++ b/flow-tests/test-npm-only-features/test-npm-bytecode-scanning/pom-devmode.xml @@ -19,7 +19,7 @@ flow-test-npm-only-features com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 diff --git a/flow-tests/test-npm-only-features/test-npm-bytecode-scanning/pom-production.xml b/flow-tests/test-npm-only-features/test-npm-bytecode-scanning/pom-production.xml index 99482029874..679cfef84a4 100644 --- a/flow-tests/test-npm-only-features/test-npm-bytecode-scanning/pom-production.xml +++ b/flow-tests/test-npm-only-features/test-npm-bytecode-scanning/pom-production.xml @@ -19,7 +19,7 @@ flow-test-npm-only-features com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 diff --git a/flow-tests/test-npm-only-features/test-npm-custom-frontend-directory/pom.xml b/flow-tests/test-npm-only-features/test-npm-custom-frontend-directory/pom.xml index 02d14f95b1e..2e166937f5f 100644 --- a/flow-tests/test-npm-only-features/test-npm-custom-frontend-directory/pom.xml +++ b/flow-tests/test-npm-only-features/test-npm-custom-frontend-directory/pom.xml @@ -19,7 +19,7 @@ flow-test-npm-only-features com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 diff --git a/flow-tests/test-npm-only-features/test-npm-general/pom.xml b/flow-tests/test-npm-only-features/test-npm-general/pom.xml index 130826e623a..2c837bc17dd 100644 --- a/flow-tests/test-npm-only-features/test-npm-general/pom.xml +++ b/flow-tests/test-npm-only-features/test-npm-general/pom.xml @@ -19,7 +19,7 @@ com.vaadin flow-test-npm-only-features - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 diff --git a/flow-tests/test-npm-only-features/test-npm-no-buildmojo/pom.xml b/flow-tests/test-npm-only-features/test-npm-no-buildmojo/pom.xml index 49ed6bc90bf..860154e2287 100644 --- a/flow-tests/test-npm-only-features/test-npm-no-buildmojo/pom.xml +++ b/flow-tests/test-npm-only-features/test-npm-no-buildmojo/pom.xml @@ -19,7 +19,7 @@ flow-test-npm-only-features com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 diff --git a/flow-tests/test-npm-only-features/test-npm-performance-regression/pom.xml b/flow-tests/test-npm-only-features/test-npm-performance-regression/pom.xml index 6fdf62d7e07..cef9bc7f7be 100644 --- a/flow-tests/test-npm-only-features/test-npm-performance-regression/pom.xml +++ b/flow-tests/test-npm-only-features/test-npm-performance-regression/pom.xml @@ -3,7 +3,7 @@ flow-test-npm-only-features com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT 4.0.0 diff --git a/flow-tests/test-pwa-disabled-offline/pom-production.xml b/flow-tests/test-pwa-disabled-offline/pom-production.xml index d7ec76bd493..b3112fbe669 100644 --- a/flow-tests/test-pwa-disabled-offline/pom-production.xml +++ b/flow-tests/test-pwa-disabled-offline/pom-production.xml @@ -4,7 +4,7 @@ flow-tests com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-pwa-disabled-offline-prod Flow tests for PWA annotation with disabled offline (production mode) diff --git a/flow-tests/test-pwa-disabled-offline/pom.xml b/flow-tests/test-pwa-disabled-offline/pom.xml index f4f727cad86..7ee32fa941d 100644 --- a/flow-tests/test-pwa-disabled-offline/pom.xml +++ b/flow-tests/test-pwa-disabled-offline/pom.xml @@ -4,7 +4,7 @@ flow-tests com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-pwa-disabled-offline Flow tests for PWA annotation with disabled offline diff --git a/flow-tests/test-pwa/pom-production.xml b/flow-tests/test-pwa/pom-production.xml index 6a44449009f..61882850e4c 100644 --- a/flow-tests/test-pwa/pom-production.xml +++ b/flow-tests/test-pwa/pom-production.xml @@ -4,7 +4,7 @@ flow-tests com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-pwa-prod Flow tests for PWA annotation (production mode) @@ -27,13 +27,6 @@ 0.1.2 test - - - org.seleniumhq.selenium - selenium-devtools-v123 - 4.21.0 - test - diff --git a/flow-tests/test-pwa/pom.xml b/flow-tests/test-pwa/pom.xml index 59747ccf2d9..716dd4551e4 100644 --- a/flow-tests/test-pwa/pom.xml +++ b/flow-tests/test-pwa/pom.xml @@ -4,7 +4,7 @@ flow-tests com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-pwa Flow tests for PWA annotation @@ -31,13 +31,6 @@ 0.1.2 test - - - org.seleniumhq.selenium - selenium-devtools-v123 - 4.21.0 - test - diff --git a/flow-tests/test-react-adapter/pom-production.xml b/flow-tests/test-react-adapter/pom-production.xml index de2c9fb4fd7..ec44f9c4f4d 100644 --- a/flow-tests/test-react-adapter/pom-production.xml +++ b/flow-tests/test-react-adapter/pom-production.xml @@ -4,7 +4,7 @@ flow-tests com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-react-adapter-prod Flow tests for React adapter in production mode diff --git a/flow-tests/test-react-adapter/pom.xml b/flow-tests/test-react-adapter/pom.xml index c0cafea45cc..87bec0f892e 100644 --- a/flow-tests/test-react-adapter/pom.xml +++ b/flow-tests/test-react-adapter/pom.xml @@ -4,7 +4,7 @@ flow-tests com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-react-adapter Flow tests for React adapter diff --git a/flow-tests/test-react-router/pom-production.xml b/flow-tests/test-react-router/pom-production.xml index c67e975dc33..a9bc48f6154 100644 --- a/flow-tests/test-react-router/pom-production.xml +++ b/flow-tests/test-react-router/pom-production.xml @@ -4,7 +4,7 @@ flow-tests com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-react-router-prod Flow tests for routing using react-router in production mode @@ -34,6 +34,11 @@ ${project.version} + + com.vaadin + flow-react + ${project.version} + diff --git a/flow-tests/test-react-router/pom.xml b/flow-tests/test-react-router/pom.xml index de1d0bb234a..4c1096eac73 100644 --- a/flow-tests/test-react-router/pom.xml +++ b/flow-tests/test-react-router/pom.xml @@ -4,7 +4,7 @@ flow-tests com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-react-router Flow tests for routing using react-router @@ -38,6 +38,12 @@ ${project.version} + + com.vaadin + flow-react + ${project.version} + + diff --git a/flow-tests/test-react-router/src/main/frontend/NavigateView.tsx b/flow-tests/test-react-router/src/main/frontend/NavigateView.tsx new file mode 100644 index 00000000000..5fdef49d702 --- /dev/null +++ b/flow-tests/test-react-router/src/main/frontend/NavigateView.tsx @@ -0,0 +1,22 @@ +import {useNavigate} from "react-router-dom"; +import { + ReactAdapterElement, + RenderHooks +} from "Frontend/generated/flow/ReactAdapter"; + + class NavigateView extends ReactAdapterElement { + protected render(hooks: RenderHooks): React.ReactElement | null { + const navigate = useNavigate(); + + return ( + <> +

This is a simple view for a React route

+ + + ); + } + } + +customElements.define('navigate-view', NavigateView); \ No newline at end of file diff --git a/flow-tests/test-react-router/src/main/java/com/vaadin/flow/ReactNavigateView.java b/flow-tests/test-react-router/src/main/java/com/vaadin/flow/ReactNavigateView.java new file mode 100644 index 00000000000..41cfa2bf0f7 --- /dev/null +++ b/flow-tests/test-react-router/src/main/java/com/vaadin/flow/ReactNavigateView.java @@ -0,0 +1,33 @@ +/* + * Copyright 2000-2024 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 com.vaadin.flow.component.Tag; +import com.vaadin.flow.component.dependency.JsModule; +import com.vaadin.flow.component.react.ReactAdapterComponent; +import com.vaadin.flow.router.Route; + +/** + * Test view for vaadin/flow#20404 Set network to slow 4G and quickly click on + * button. No console exceptions should be shown. + */ +@Route("com.vaadin.flow.ReactNavigateView") +@Tag("navigate-view") +@JsModule("NavigateView.tsx") +public class ReactNavigateView extends ReactAdapterComponent { + +} diff --git a/flow-tests/test-redeployment-no-cache/pom.xml b/flow-tests/test-redeployment-no-cache/pom.xml index 383763ae9b3..d358fa1a0cd 100644 --- a/flow-tests/test-redeployment-no-cache/pom.xml +++ b/flow-tests/test-redeployment-no-cache/pom.xml @@ -4,7 +4,7 @@ flow-tests com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-redeployment-no-cache Test to ensure dev mode caching is not used when it's disabled diff --git a/flow-tests/test-redeployment/pom.xml b/flow-tests/test-redeployment/pom.xml index 1efd99e1d7a..8dc2847b32a 100644 --- a/flow-tests/test-redeployment/pom.xml +++ b/flow-tests/test-redeployment/pom.xml @@ -4,7 +4,7 @@ flow-tests com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-redeployment Flow tests that require redeployment during the test diff --git a/flow-tests/test-resources/pom.xml b/flow-tests/test-resources/pom.xml index 087efcfbc52..6d65b2bec4b 100644 --- a/flow-tests/test-resources/pom.xml +++ b/flow-tests/test-resources/pom.xml @@ -5,7 +5,7 @@ com.vaadin flow-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-resources Flow Test Resources diff --git a/flow-tests/test-root-context/pom.xml b/flow-tests/test-root-context/pom.xml index 941ced753cd..dd7a0c9d0ed 100644 --- a/flow-tests/test-root-context/pom.xml +++ b/flow-tests/test-root-context/pom.xml @@ -4,7 +4,7 @@ com.vaadin flow-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-root-context-npm Flow root context tests in NPM dev mode diff --git a/flow-tests/test-router-custom-context/pom.xml b/flow-tests/test-router-custom-context/pom.xml index a2c42996bf0..c86a216c8df 100644 --- a/flow-tests/test-router-custom-context/pom.xml +++ b/flow-tests/test-router-custom-context/pom.xml @@ -4,7 +4,7 @@ com.vaadin flow-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT test-router-custom-context Flow Router on custom context test diff --git a/flow-tests/test-servlet/pom.xml b/flow-tests/test-servlet/pom.xml index 313755d431f..cae552d7a21 100644 --- a/flow-tests/test-servlet/pom.xml +++ b/flow-tests/test-servlet/pom.xml @@ -4,7 +4,7 @@ com.vaadin flow-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-servlet Flow servlet registration test diff --git a/flow-tests/test-theme-no-polymer/pom.xml b/flow-tests/test-theme-no-polymer/pom.xml index 1055788054c..59bcbb9361e 100644 --- a/flow-tests/test-theme-no-polymer/pom.xml +++ b/flow-tests/test-theme-no-polymer/pom.xml @@ -4,7 +4,7 @@ com.vaadin flow-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-theme-no-polymer Flow custom theme test without polymer components diff --git a/flow-tests/test-themes/pom-devbundle.xml b/flow-tests/test-themes/pom-devbundle.xml index a3d4a6ad8be..870a2453609 100644 --- a/flow-tests/test-themes/pom-devbundle.xml +++ b/flow-tests/test-themes/pom-devbundle.xml @@ -6,7 +6,7 @@ com.vaadin flow-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-themes-devbundle Flow themes tests, dev bundle diff --git a/flow-tests/test-themes/pom-production.xml b/flow-tests/test-themes/pom-production.xml index 55818e6e0ac..53af3e7dcad 100644 --- a/flow-tests/test-themes/pom-production.xml +++ b/flow-tests/test-themes/pom-production.xml @@ -6,7 +6,7 @@ com.vaadin flow-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-themes-production Flow themes tests, production diff --git a/flow-tests/test-themes/pom.xml b/flow-tests/test-themes/pom.xml index 949f67c4cfd..8b6b8dc7711 100644 --- a/flow-tests/test-themes/pom.xml +++ b/flow-tests/test-themes/pom.xml @@ -6,7 +6,7 @@ com.vaadin flow-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-themes-hotdeploy Flow themes tests, dev hotdeploy diff --git a/flow-tests/test-webpush/pom.xml b/flow-tests/test-webpush/pom.xml index 8646e5e0223..5362a29118a 100644 --- a/flow-tests/test-webpush/pom.xml +++ b/flow-tests/test-webpush/pom.xml @@ -4,7 +4,7 @@ com.vaadin flow-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-test-webpush Flow webpush integration test diff --git a/flow-tests/test-webpush/src/main/java/com/vaadin/flow/webpush/WebPushView.java b/flow-tests/test-webpush/src/main/java/com/vaadin/flow/webpush/WebPushView.java index c9ae1667276..c26c65cbd6e 100644 --- a/flow-tests/test-webpush/src/main/java/com/vaadin/flow/webpush/WebPushView.java +++ b/flow-tests/test-webpush/src/main/java/com/vaadin/flow/webpush/WebPushView.java @@ -18,14 +18,13 @@ import java.util.List; -import nl.martijndwars.webpush.Subscription; - import com.vaadin.flow.component.Text; import com.vaadin.flow.component.html.Div; import com.vaadin.flow.component.html.NativeButton; import com.vaadin.flow.router.Route; import com.vaadin.flow.server.webpush.WebPush; import com.vaadin.flow.server.webpush.WebPushMessage; +import com.vaadin.flow.server.webpush.WebPushSubscription; @Route("") public class WebPushView extends Div { @@ -52,7 +51,7 @@ public class WebPushView extends Div { "https://upload.wikimedia.org/wikipedia/commons/0/0e/Message-icon-blue-symbol-double.png" ); - private Subscription subscription; + private WebPushSubscription subscription; public WebPushView() { webPush = new WebPush(PUBLIC_KEY, PRIVATE_KEY, "test"); diff --git a/flow-tests/vaadin-spring-tests/pom.xml b/flow-tests/vaadin-spring-tests/pom.xml index 8a951b895e2..c24717c4598 100644 --- a/flow-tests/vaadin-spring-tests/pom.xml +++ b/flow-tests/vaadin-spring-tests/pom.xml @@ -6,7 +6,7 @@ com.vaadin flow-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT vaadin-spring-tests Vaadin Spring tests @@ -16,8 +16,8 @@ true - 24.5.4 - 9.46 + 24.5.5 + 9.47 diff --git a/flow-tests/vaadin-spring-tests/test-mvc-without-endpoints/pom.xml b/flow-tests/vaadin-spring-tests/test-mvc-without-endpoints/pom.xml index 92f64b87f11..4676e1fbf7c 100644 --- a/flow-tests/vaadin-spring-tests/test-mvc-without-endpoints/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-mvc-without-endpoints/pom.xml @@ -6,7 +6,7 @@ com.vaadin vaadin-spring-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT test-ts-mvc-without-endpoints Integration tests for a Spring MVC project without using endpoints diff --git a/flow-tests/vaadin-spring-tests/test-plain-spring-boot-reload-time/pom.xml b/flow-tests/vaadin-spring-tests/test-plain-spring-boot-reload-time/pom.xml index 9e276b5c8c7..ede01058d95 100644 --- a/flow-tests/vaadin-spring-tests/test-plain-spring-boot-reload-time/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-plain-spring-boot-reload-time/pom.xml @@ -6,7 +6,7 @@ com.vaadin vaadin-spring-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT test-plain-spring-boot-reload-time Testing reload time of a plain Spring boot project diff --git a/flow-tests/vaadin-spring-tests/test-spring-boot-contextpath/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-boot-contextpath/pom.xml index 1eafaecb891..4bce5281892 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-boot-contextpath/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-boot-contextpath/pom.xml @@ -5,7 +5,7 @@ com.vaadin vaadin-spring-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT vaadin-test-spring-boot-contextpath Vaadin Spring Boot integration tests when deployed using a context path diff --git a/flow-tests/vaadin-spring-tests/test-spring-boot-jar/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-boot-jar/pom.xml index 2cfa3e64b6b..578d00ed50f 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-boot-jar/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-boot-jar/pom.xml @@ -5,7 +5,7 @@ com.vaadin vaadin-spring-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT vaadin-test-spring-boot-jar Vaadin Spring Boot executable Jar integration tests diff --git a/flow-tests/vaadin-spring-tests/test-spring-boot-multimodule-reload-time/generator/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-boot-multimodule-reload-time/generator/pom.xml index a2db763cab4..f5c642d6bc1 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-boot-multimodule-reload-time/generator/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-boot-multimodule-reload-time/generator/pom.xml @@ -6,7 +6,7 @@ test-spring-boot-multimodule-reload-time com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT test-spring-boot-multimodule-reload-time-generator The code generator Maven plugin for a Spring boot multimodule reload diff --git a/flow-tests/vaadin-spring-tests/test-spring-boot-multimodule-reload-time/library/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-boot-multimodule-reload-time/library/pom.xml index 2c43d8481b1..cac2cfd3d1f 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-boot-multimodule-reload-time/library/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-boot-multimodule-reload-time/library/pom.xml @@ -6,7 +6,7 @@ test-spring-boot-multimodule-reload-time com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT test-spring-boot-multimodule-reload-time-library The frontend library module for a Spring boot multimodule reload time diff --git a/flow-tests/vaadin-spring-tests/test-spring-boot-multimodule-reload-time/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-boot-multimodule-reload-time/pom.xml index 772344c7eaa..00fe83306f1 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-boot-multimodule-reload-time/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-boot-multimodule-reload-time/pom.xml @@ -6,7 +6,7 @@ vaadin-spring-tests com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT test-spring-boot-multimodule-reload-time The main module for a Spring boot multimodule reload time project diff --git a/flow-tests/vaadin-spring-tests/test-spring-boot-multimodule-reload-time/theme/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-boot-multimodule-reload-time/theme/pom.xml index 922885b67ab..89708b3fc7b 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-boot-multimodule-reload-time/theme/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-boot-multimodule-reload-time/theme/pom.xml @@ -6,7 +6,7 @@ test-spring-boot-multimodule-reload-time com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT test-spring-boot-multimodule-reload-time-theme The theme module for a Spring boot multimodule reload time project diff --git a/flow-tests/vaadin-spring-tests/test-spring-boot-multimodule-reload-time/ui/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-boot-multimodule-reload-time/ui/pom.xml index ac1d95106a9..f42eeae86eb 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-boot-multimodule-reload-time/ui/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-boot-multimodule-reload-time/ui/pom.xml @@ -6,7 +6,7 @@ com.vaadin test-spring-boot-multimodule-reload-time - 24.6-SNAPSHOT + 24.7-SNAPSHOT test-spring-boot-multimodule-reload-time-ui The UI module for a Spring boot multimodule reload time project diff --git a/flow-tests/vaadin-spring-tests/test-spring-boot-only-prepare/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-boot-only-prepare/pom.xml index a75a61795de..636c5498c59 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-boot-only-prepare/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-boot-only-prepare/pom.xml @@ -5,7 +5,7 @@ com.vaadin vaadin-spring-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT vaadin-test-spring-boot-prepare Vaadin Spring Boot integration tests with only prepare goal diff --git a/flow-tests/vaadin-spring-tests/test-spring-boot-reload-time/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-boot-reload-time/pom.xml index 2349d018294..ba81baf0499 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-boot-reload-time/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-boot-reload-time/pom.xml @@ -6,7 +6,7 @@ com.vaadin vaadin-spring-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT test-spring-boot-reload-time Testing reload time of a Vaadin Spring boot project diff --git a/flow-tests/vaadin-spring-tests/test-spring-boot-reverseproxy/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-boot-reverseproxy/pom.xml index b714dc0fb48..ef262e98a92 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-boot-reverseproxy/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-boot-reverseproxy/pom.xml @@ -4,7 +4,7 @@ com.vaadin vaadin-spring-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT vaadin-test-spring-boot-reverseproxy Vaadin Spring Boot integration tests when deployed behind a reverse proxy diff --git a/flow-tests/vaadin-spring-tests/test-spring-boot-scan/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-boot-scan/pom.xml index af044a074dc..7db6e70476e 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-boot-scan/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-boot-scan/pom.xml @@ -6,7 +6,7 @@ com.vaadin vaadin-spring-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT vaadin-test-spring-boot-scan Vaadin Spring Boot integration tests with custom packages to scan diff --git a/flow-tests/vaadin-spring-tests/test-spring-boot-undertow/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-boot-undertow/pom.xml index 22dc87c0169..f4e0f02d199 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-boot-undertow/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-boot-undertow/pom.xml @@ -4,7 +4,7 @@ com.vaadin vaadin-spring-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT vaadin-test-spring-boot-undertow Vaadin Spring Boot integration tests when running on Undertow diff --git a/flow-tests/vaadin-spring-tests/test-spring-boot/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-boot/pom.xml index 70b8801f68c..ab56bebf27d 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-boot/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-boot/pom.xml @@ -5,7 +5,7 @@ com.vaadin vaadin-spring-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT vaadin-test-spring-boot Vaadin Spring Boot integration tests diff --git a/flow-tests/vaadin-spring-tests/test-spring-common/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-common/pom.xml index 5159ad5efb1..f2fec609323 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-common/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-common/pom.xml @@ -4,7 +4,7 @@ com.vaadin vaadin-spring-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT vaadin-test-spring-common Flow Spring Common UI components @@ -152,7 +152,7 @@ org.springdoc springdoc-openapi-starter-webmvc-ui - 2.6.0 + 2.7.0 diff --git a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/pom.xml index 260c641307b..3c116e997f6 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/allowed-ui/pom.xml @@ -21,7 +21,7 @@ vaadin-test-spring-filter-packages com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT vaadin-test-spring-filter-packages-allowed-ui Test UI with allowed-packages properties diff --git a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-allowed/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-allowed/pom.xml index 5cffb8ecd04..18a48bc0f1b 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-allowed/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-allowed/pom.xml @@ -21,7 +21,7 @@ vaadin-test-spring-filter-packages com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT vaadin-test-spring-filter-packages-lib-allowed Library with vaadin.allowed-packages property diff --git a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-blocked/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-blocked/pom.xml index 79b42eedcfb..a7c90b9eb73 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-blocked/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-blocked/pom.xml @@ -21,7 +21,7 @@ vaadin-test-spring-filter-packages com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT vaadin-test-spring-filter-packages-lib-blocked Library with vaadin.blocked-packages property diff --git a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-exclude/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-exclude/pom.xml index 981e4474394..644ecdf318b 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-exclude/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/lib-exclude/pom.xml @@ -21,7 +21,7 @@ vaadin-test-spring-filter-packages com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT vaadin-test-spring-filter-packages-lib-exclude Library with vaadin.blocked-jar property diff --git a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/pom.xml index f24fd53e5be..a387a9b0e31 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/pom.xml @@ -21,7 +21,7 @@ vaadin-spring-tests com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT vaadin-test-spring-filter-packages The main module for a Spring boot package filter tests diff --git a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/pom.xml index 294f79898ca..f4266322725 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-filter-packages/ui/pom.xml @@ -21,7 +21,7 @@ vaadin-test-spring-filter-packages com.vaadin - 24.6-SNAPSHOT + 24.7-SNAPSHOT vaadin-test-spring-filter-packages-ui jar diff --git a/flow-tests/vaadin-spring-tests/test-spring-helpers/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-helpers/pom.xml index 81f35ee595f..8fffe09cf2c 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-helpers/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-helpers/pom.xml @@ -4,7 +4,7 @@ com.vaadin vaadin-spring-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT vaadin-test-spring-helpers Flow Spring test helpers diff --git a/flow-tests/vaadin-spring-tests/test-spring-security-flow-contextpath/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-security-flow-contextpath/pom.xml index 810c51cb697..407b8e17057 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-security-flow-contextpath/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-security-flow-contextpath/pom.xml @@ -5,7 +5,7 @@ com.vaadin vaadin-spring-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT test-spring-security-flow-contextpath Integration tests for Vaadin Spring Security and Flow With Context Path diff --git a/flow-tests/vaadin-spring-tests/test-spring-security-flow-methodsecurity/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-security-flow-methodsecurity/pom.xml index 62ff20c1619..c53caf7430d 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-security-flow-methodsecurity/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-security-flow-methodsecurity/pom.xml @@ -5,7 +5,7 @@ com.vaadin vaadin-spring-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT test-spring-security-flow-methodsecurity Vaadin Spring Security test for Enabled Method Security diff --git a/flow-tests/vaadin-spring-tests/test-spring-security-flow-reverseproxy/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-security-flow-reverseproxy/pom.xml index c0e5d04e3ff..cf86337892f 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-security-flow-reverseproxy/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-security-flow-reverseproxy/pom.xml @@ -4,7 +4,7 @@ com.vaadin vaadin-spring-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT test-spring-security-flow-revereproxy Integration tests for Vaadin Spring Security and Flow when using a reverse proxy diff --git a/flow-tests/vaadin-spring-tests/test-spring-security-flow-routepathaccesschecker/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-security-flow-routepathaccesschecker/pom.xml index ee54f6ab2f2..903900a5c67 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-security-flow-routepathaccesschecker/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-security-flow-routepathaccesschecker/pom.xml @@ -5,7 +5,7 @@ com.vaadin vaadin-spring-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT test-spring-security-flow-routepathaccesscheker Integration tests for Vaadin Spring Security and Flow secured by RoutePathAccessChecker diff --git a/flow-tests/vaadin-spring-tests/test-spring-security-flow-standalone-routepathaccesschecker/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-security-flow-standalone-routepathaccesschecker/pom.xml index 3f076046370..c2c81f9c387 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-security-flow-standalone-routepathaccesschecker/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-security-flow-standalone-routepathaccesschecker/pom.xml @@ -5,7 +5,7 @@ com.vaadin vaadin-spring-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT test-spring-security-flow-standalone-routepathaccesscheker Integration tests for Spring Security with standalone RoutePathAccessChecker diff --git a/flow-tests/vaadin-spring-tests/test-spring-security-flow-urlmapping/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-security-flow-urlmapping/pom.xml index 224430765f6..4d4cde57e7f 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-security-flow-urlmapping/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-security-flow-urlmapping/pom.xml @@ -5,7 +5,7 @@ com.vaadin vaadin-spring-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT test-spring-security-flow-urlmapping Integration tests for Vaadin Spring Security and Flow With Vaadin URL mapping diff --git a/flow-tests/vaadin-spring-tests/test-spring-security-flow/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-security-flow/pom.xml index b48ffaa7c3f..e1285c43aae 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-security-flow/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-security-flow/pom.xml @@ -5,7 +5,7 @@ com.vaadin vaadin-spring-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT test-spring-security-flow Integration tests for Vaadin Spring Security and Flow diff --git a/flow-tests/vaadin-spring-tests/test-spring-security-webicons-urlmapping/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-security-webicons-urlmapping/pom.xml index d5873153c66..0d280a53222 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-security-webicons-urlmapping/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-security-webicons-urlmapping/pom.xml @@ -5,7 +5,7 @@ com.vaadin vaadin-spring-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT test-spring-security-webicons-urlmapping Integration tests for Vaadin Spring Security and Custom PWA icon, Favicon paths and URL mapping diff --git a/flow-tests/vaadin-spring-tests/test-spring-security-webicons/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-security-webicons/pom.xml index 70e13a468b0..be44a96449b 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-security-webicons/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-security-webicons/pom.xml @@ -5,7 +5,7 @@ com.vaadin vaadin-spring-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT test-spring-security-webicons Integration tests for Vaadin Spring Security and Custom PWA icon and Favicon paths diff --git a/flow-tests/vaadin-spring-tests/test-spring-war/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-war/pom.xml index 062e5ded05e..c1855944cec 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-war/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-war/pom.xml @@ -5,7 +5,7 @@ com.vaadin vaadin-spring-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT vaadin-test-spring-war Vaadin Spring Boot deployable integration tests diff --git a/flow-tests/vaadin-spring-tests/test-spring-white-list/pom.xml b/flow-tests/vaadin-spring-tests/test-spring-white-list/pom.xml index 31259769363..64bba5bddeb 100644 --- a/flow-tests/vaadin-spring-tests/test-spring-white-list/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring-white-list/pom.xml @@ -22,7 +22,7 @@ com.vaadin vaadin-spring-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT vaadin-test-spring-white-list diff --git a/flow-tests/vaadin-spring-tests/test-spring/pom.xml b/flow-tests/vaadin-spring-tests/test-spring/pom.xml index ddadde35e38..70e2a813cdf 100644 --- a/flow-tests/vaadin-spring-tests/test-spring/pom.xml +++ b/flow-tests/vaadin-spring-tests/test-spring/pom.xml @@ -4,7 +4,7 @@ com.vaadin vaadin-spring-tests - 24.6-SNAPSHOT + 24.7-SNAPSHOT vaadin-test-spring Flow Spring deployable integration tests diff --git a/flow-webpush/pom.xml b/flow-webpush/pom.xml index ba7d9c2d032..09dec3bc8a7 100644 --- a/flow-webpush/pom.xml +++ b/flow-webpush/pom.xml @@ -5,7 +5,7 @@ com.vaadin flow-project - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow-webpush diff --git a/flow-webpush/src/main/java/com/vaadin/flow/server/webpush/WebPush.java b/flow-webpush/src/main/java/com/vaadin/flow/server/webpush/WebPush.java index 01f6fdfbf42..45908906e91 100644 --- a/flow-webpush/src/main/java/com/vaadin/flow/server/webpush/WebPush.java +++ b/flow-webpush/src/main/java/com/vaadin/flow/server/webpush/WebPush.java @@ -32,14 +32,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.vaadin.experimental.FeatureFlags; import com.vaadin.flow.component.ComponentUtil; import com.vaadin.flow.component.UI; import com.vaadin.flow.component.page.Page; import com.vaadin.flow.component.page.PendingJavaScriptResult; import com.vaadin.flow.function.SerializableConsumer; import com.vaadin.flow.internal.StringUtil; -import com.vaadin.flow.server.VaadinService; import elemental.json.Json; import elemental.json.JsonObject; @@ -77,11 +75,6 @@ public class WebPush { * Subject used in the JWT payload (for VAPID). */ public WebPush(String publicKey, String privateKey, String subject) { - if (!FeatureFlags.get(VaadinService.getCurrent().getContext()) - .isEnabled(FeatureFlags.WEB_PUSH)) { - throw new WebPushException("WebPush feature is not enabled. " - + "Add `com.vaadin.experimental.webPush=true` to `vaadin-featureflags.properties`file in resources to enable feature."); - } this.publicKey = publicKey; Security.addProvider(new BouncyCastleProvider()); @@ -109,13 +102,20 @@ public WebPush(String publicKey, String privateKey, String subject) { * @throws WebPushException * if sending a notification fails */ - public void sendNotification(Subscription subscription, + public void sendNotification(WebPushSubscription subscription, WebPushMessage message) throws WebPushException { int statusCode = -1; HttpResponse response = null; try { + Subscription.Keys keys = null; + if (subscription.keys() != null) { + keys = new Subscription.Keys(subscription.keys().p256dh(), + subscription.keys().auth()); + } + Subscription nativeSubscription = new Subscription( + subscription.endpoint(), keys); Notification notification = Notification.builder() - .subscription(subscription).payload(message.toJson()) + .subscription(nativeSubscription).payload(message.toJson()) .build(); response = pushService.send(notification, PushService.DEFAULT_ENCODING, @@ -282,11 +282,13 @@ private SerializableConsumer handlePossiblyEmptySubscription( }; } - private Subscription generateSubscription(JsonObject subscriptionJson) { - Subscription.Keys keys = new Subscription.Keys( + private WebPushSubscription generateSubscription( + JsonObject subscriptionJson) { + WebPushKeys keys = new WebPushKeys( subscriptionJson.getObject("keys").getString("p256dh"), subscriptionJson.getObject("keys").getString("auth")); - return new Subscription(subscriptionJson.getString("endpoint"), keys); + return new WebPushSubscription(subscriptionJson.getString("endpoint"), + keys); } private Logger getLogger() { diff --git a/flow-webpush/src/main/java/com/vaadin/flow/server/webpush/WebPushKeys.java b/flow-webpush/src/main/java/com/vaadin/flow/server/webpush/WebPushKeys.java new file mode 100644 index 00000000000..51410fe527a --- /dev/null +++ b/flow-webpush/src/main/java/com/vaadin/flow/server/webpush/WebPushKeys.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2024 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.webpush; + +import java.io.Serializable; + +/** + * Holds the keys that used to encrypt the Web Push notification payload, so + * that only the current browser can read (decrypt) the content of + * notifications. + * + * @param p256dh + * public key on the P-256 curve. + * @param auth + * An authentication secret. + * @see PushSubscription + * Keys mdn web docs + */ +public record WebPushKeys(String p256dh, String auth) implements Serializable { +} diff --git a/flow-webpush/src/main/java/com/vaadin/flow/server/webpush/WebPushSubscription.java b/flow-webpush/src/main/java/com/vaadin/flow/server/webpush/WebPushSubscription.java new file mode 100644 index 00000000000..d5a3acc5265 --- /dev/null +++ b/flow-webpush/src/main/java/com/vaadin/flow/server/webpush/WebPushSubscription.java @@ -0,0 +1,41 @@ +/* + * Copyright 2000-2024 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.webpush; + +import java.io.Serializable; + +/** + * Represents a Web Push subscription that Push Manager gives back, when a user + * subscribes to push notifications in browser. + * + * @param endpoint + * a custom URL pointing to a push server, which can be used to send + * a push message to the particular service worker instance that + * subscribed to the push service. For this reason, it is a good idea + * to keep your endpoint a secret, so others do not hijack it and + * abuse the push functionality. + * @param keys + * an object containing the keys that used to encrypt the payload. + * @see PushSubscription + * mdn web docs + * @see PushManager + * mdn web docs + */ +public record WebPushSubscription(String endpoint, + WebPushKeys keys) implements Serializable { +} diff --git a/flow-webpush/src/main/java/com/vaadin/flow/server/webpush/WebPushSubscriptionResponse.java b/flow-webpush/src/main/java/com/vaadin/flow/server/webpush/WebPushSubscriptionResponse.java index 0aa58d47a1d..c6f66a0a45f 100644 --- a/flow-webpush/src/main/java/com/vaadin/flow/server/webpush/WebPushSubscriptionResponse.java +++ b/flow-webpush/src/main/java/com/vaadin/flow/server/webpush/WebPushSubscriptionResponse.java @@ -18,8 +18,6 @@ import java.io.Serializable; -import nl.martijndwars.webpush.Subscription; - /** * Callback for receiving web push subscription details * @@ -34,5 +32,5 @@ public interface WebPushSubscriptionResponse extends Serializable { * @param subscription * web push subscription object */ - void subscription(Subscription subscription); + void subscription(WebPushSubscription subscription); } diff --git a/flow-webpush/src/main/resources/META-INF/frontend/FlowWebPush.js b/flow-webpush/src/main/resources/META-INF/frontend/FlowWebPush.js index 44c399672b4..4b52b0a0893 100644 --- a/flow-webpush/src/main/resources/META-INF/frontend/FlowWebPush.js +++ b/flow-webpush/src/main/resources/META-INF/frontend/FlowWebPush.js @@ -30,8 +30,6 @@ window.Vaadin.Flow.webPush = window.Vaadin.Flow.webPush || { }); if (subscription) { - console.log(subscription); - // console.log(JSON.parse(JSON.stringify(subscription))); return JSON.parse(JSON.stringify(subscription)); } throw new Error("Subscription failed. See console for exception."); diff --git a/flow/pom.xml b/flow/pom.xml index 29cc4acd204..3cdd3eaebcd 100644 --- a/flow/pom.xml +++ b/flow/pom.xml @@ -5,7 +5,7 @@ com.vaadin flow-project - 24.6-SNAPSHOT + 24.7-SNAPSHOT flow pom diff --git a/pom.xml b/pom.xml index 98767e2f759..923b963eced 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ flow-project Flow pom - 24.6-SNAPSHOT + 24.7-SNAPSHOT com.vaadin @@ -85,7 +85,7 @@ false - 3.3.5 + 3.4.0 2.9.0 8.0.1.Final 2.0.16 @@ -212,7 +212,7 @@ commons-io commons-io - 2.17.0 + 2.18.0 org.apache.commons @@ -251,7 +251,7 @@ com.vaadin license-checker - 1.13.0 + 1.13.2 diff --git a/vaadin-dev-server/pom.xml b/vaadin-dev-server/pom.xml index 39a8e69204b..3e613663a3c 100644 --- a/vaadin-dev-server/pom.xml +++ b/vaadin-dev-server/pom.xml @@ -6,7 +6,7 @@ com.vaadin flow-project - 24.6-SNAPSHOT + 24.7-SNAPSHOT vaadin-dev-server Vaadin Development Mode Server @@ -36,7 +36,7 @@ com.vaadin open - 8.5.0.3 + 8.5.0.4 diff --git a/vaadin-dev-server/src/main/frontend/vaadin-dev-tools.ts b/vaadin-dev-server/src/main/frontend/vaadin-dev-tools.ts index b3d3cd11ae6..5e9d8dc09e2 100644 --- a/vaadin-dev-server/src/main/frontend/vaadin-dev-tools.ts +++ b/vaadin-dev-server/src/main/frontend/vaadin-dev-tools.ts @@ -656,9 +656,13 @@ export class VaadinDevTools extends LitElement { .filter((key) => key !== 'TypeScript') .map((id) => anyVaadin.Flow.clients[id]) .forEach((client) => { - client.sendEventMessage(1, "ui-refresh", { - fullRefresh: strategy === 'full-refresh' - }) + if (client.sendEventMessage) { + client.sendEventMessage(1, "ui-refresh", { + fullRefresh: strategy === 'full-refresh' + }) + } else { + console.warn("Ignoring ui-refresh event for application ",id); + } }); } else { const lastReload = window.sessionStorage.getItem(VaadinDevTools.TRIGGERED_COUNT_KEY_IN_SESSION_STORAGE); diff --git a/vaadin-dev-server/src/main/java/com/vaadin/base/devserver/viteproxy/ViteWebsocketConnection.java b/vaadin-dev-server/src/main/java/com/vaadin/base/devserver/viteproxy/ViteWebsocketConnection.java index 358a079f0f0..3b14308ced6 100644 --- a/vaadin-dev-server/src/main/java/com/vaadin/base/devserver/viteproxy/ViteWebsocketConnection.java +++ b/vaadin-dev-server/src/main/java/com/vaadin/base/devserver/viteproxy/ViteWebsocketConnection.java @@ -26,6 +26,8 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.function.Consumer; import org.slf4j.Logger; @@ -144,9 +146,21 @@ public void send(String message) */ public void close() throws InterruptedException, ExecutionException { getLogger().debug("Closing the connection"); - CompletableFuture closeRequest = clientWebsocket.get() - .sendClose(CloseCodes.NORMAL_CLOSURE.getCode(), ""); - closeRequest.get(); + if (clientWebsocket.isDone()) { + WebSocket client = clientWebsocket.get(); + if (!client.isOutputClosed()) { + CompletableFuture closeRequest = client + .sendClose(CloseCodes.NORMAL_CLOSURE.getCode(), ""); + try { + closeRequest.get(500, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + getLogger().debug("Timed out waiting for close request"); + } + } + } else { + // Websocket client connection has not been established + clientWebsocket.cancel(true); + } } @Override diff --git a/vaadin-dev-server/src/test/java/com/vaadin/base/devserver/viteproxy/ViteWebsocketConnectionTest.java b/vaadin-dev-server/src/test/java/com/vaadin/base/devserver/viteproxy/ViteWebsocketConnectionTest.java index 3d3da7bb10e..7f16bd1d41a 100644 --- a/vaadin-dev-server/src/test/java/com/vaadin/base/devserver/viteproxy/ViteWebsocketConnectionTest.java +++ b/vaadin-dev-server/src/test/java/com/vaadin/base/devserver/viteproxy/ViteWebsocketConnectionTest.java @@ -17,6 +17,8 @@ package com.vaadin.base.devserver.viteproxy; import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; import java.net.InetSocketAddress; import java.net.http.WebSocket; import java.nio.charset.StandardCharsets; @@ -24,9 +26,11 @@ import java.security.NoSuchAlgorithmException; import java.time.Duration; import java.util.Base64; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpExchange; @@ -34,8 +38,12 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; import org.mockito.ThrowingConsumer; +import com.vaadin.flow.internal.ReflectTools; + public class ViteWebsocketConnectionTest { private HttpServer httpServer; @@ -110,6 +118,62 @@ public void waitForConnection_clientWebsocketNotAvailable_fails() errorLatch.await(2, TimeUnit.SECONDS); } + @Test(timeout = 2000) + public void close_clientWebsocketNotAvailable_dontBlock() + throws ExecutionException, InterruptedException { + AtomicReference connectionError = new AtomicReference<>(); + CountDownLatch suspendConnectionLatch = new CountDownLatch(1); + handlerSupplier = exchange -> { + suspendConnectionLatch.await(); + }; + ViteWebsocketConnection connection = new ViteWebsocketConnection( + httpServer.getAddress().getPort(), "/VAADIN", "proto", x -> { + }, () -> { + }, connectionError::set); + connection.close(); + suspendConnectionLatch.countDown(); + Assert.assertNull("Websocket connection failed", connectionError.get()); + } + + @SuppressWarnings("unchecked") + @Test(timeout = 2000) + public void close_clientWebsocketClose_dontBlockIndefinitely() + throws ExecutionException, InterruptedException, + NoSuchFieldException, InvocationTargetException, + IllegalAccessException { + handlerSupplier = ViteWebsocketConnectionTest::handshake; + AtomicReference connectionError = new AtomicReference<>(); + ViteWebsocketConnection connection = new ViteWebsocketConnection( + httpServer.getAddress().getPort(), "/VAADIN", "proto", x -> { + }, () -> { + }, connectionError::set); + + // Replace websocket with spy to mock close behavior + Field clientWebsocketField = ViteWebsocketConnection.class + .getDeclaredField("clientWebsocket"); + CompletableFuture clientWebsocketFuture = (CompletableFuture) ReflectTools + .getJavaFieldValue(connection, clientWebsocketField); + WebSocket mockWebSocket = Mockito.spy(clientWebsocketFuture.get()); + Mockito.when(mockWebSocket.sendClose(ArgumentMatchers.anyInt(), + ArgumentMatchers.anyString())).then(i -> { + CompletableFuture closeFuture = (CompletableFuture) i + .callRealMethod(); + return closeFuture.thenRunAsync(() -> { + try { + // Wait longer than test timeout. + // Close should not wait that much + Thread.sleep(5000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }); + }); + ReflectTools.setJavaFieldValue(connection, clientWebsocketField, + CompletableFuture.completedFuture(mockWebSocket)); + connection.close(); + Assert.assertNull("Websocket connection failed", connectionError.get()); + } + private static void handshake(HttpExchange exchange) throws IOException { Headers requestHeaders = exchange.getRequestHeaders(); if ("GET".equalsIgnoreCase(exchange.getRequestMethod()) && "upgrade" @@ -131,4 +195,5 @@ private static void handshake(HttpExchange exchange) throws IOException { exchange.sendResponseHeaders(101, -1); } } + } diff --git a/vaadin-spring/pom.xml b/vaadin-spring/pom.xml index 1e5c1215687..ae94316ebf4 100644 --- a/vaadin-spring/pom.xml +++ b/vaadin-spring/pom.xml @@ -7,7 +7,7 @@ com.vaadin flow-project - 24.6-SNAPSHOT + 24.7-SNAPSHOT vaadin-spring diff --git a/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/AuthenticationContextTest.java b/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/AuthenticationContextTest.java index df571284f0c..51c9ba89553 100644 --- a/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/AuthenticationContextTest.java +++ b/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/AuthenticationContextTest.java @@ -30,7 +30,7 @@ import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; -import org.springframework.security.config.annotation.ObjectPostProcessor; +import org.springframework.security.config.ObjectPostProcessor; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; diff --git a/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/VaadinWebSecurityTest.java b/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/VaadinWebSecurityTest.java index a7f86af78fb..15b19397897 100644 --- a/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/VaadinWebSecurityTest.java +++ b/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/VaadinWebSecurityTest.java @@ -28,7 +28,7 @@ import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; -import org.springframework.security.config.annotation.ObjectPostProcessor; +import org.springframework.security.config.ObjectPostProcessor; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; diff --git a/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/stateless/JwtStatelessAuthenticationTest.java b/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/stateless/JwtStatelessAuthenticationTest.java index fe0ea15bad9..3d4a2dd39c4 100644 --- a/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/stateless/JwtStatelessAuthenticationTest.java +++ b/vaadin-spring/src/test/java/com/vaadin/flow/spring/security/stateless/JwtStatelessAuthenticationTest.java @@ -80,8 +80,7 @@ @ExtendWith(SpringExtension.class) @WebAppConfiguration @WebMvcTest -@ContextConfiguration(classes = { SecurityAutoConfiguration.class, - SpringBootAutoConfiguration.class, +@ContextConfiguration(classes = { SpringBootAutoConfiguration.class, SpringSecurityAutoConfiguration.class, JwtStatelessAuthenticationTest.WorkaroundConfig.class, JwtStatelessAuthenticationTest.SecurityConfig.class })