diff --git a/m2e-core-tests b/m2e-core-tests index 813942ef2f..9feae7ba91 160000 --- a/m2e-core-tests +++ b/m2e-core-tests @@ -1 +1 @@ -Subproject commit 813942ef2fbc42c989f2104d59d213aa70340d12 +Subproject commit 9feae7ba91aba0894ba76b9fca1b0f33f186c3de diff --git a/org.eclipse.m2e.launching/plugin.xml b/org.eclipse.m2e.launching/plugin.xml index fec1e03370..e44cc75756 100644 --- a/org.eclipse.m2e.launching/plugin.xml +++ b/org.eclipse.m2e.launching/plugin.xml @@ -243,5 +243,14 @@ class="org.eclipse.m2e.internal.launch.MavenConsoleLineTracker" processType="java"/> + + + + diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildConnectionLaunchParticipant.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildConnectionLaunchParticipant.java new file mode 100644 index 0000000000..ac3426a8c1 --- /dev/null +++ b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildConnectionLaunchParticipant.java @@ -0,0 +1,79 @@ +/******************************************************************************** + * Copyright (c) 2022, 2024 Hannes Wellmann and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Hannes Wellmann - initial API and implementation + * Christoph Läubrich - refactor into {@link IMavenLaunchParticipant} + ********************************************************************************/ + +package org.eclipse.m2e.internal.launch; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.ILaunch; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.ILaunchesListener2; +import org.eclipse.debug.core.sourcelookup.ISourceLookupParticipant; + +import org.eclipse.m2e.core.internal.launch.MavenEmbeddedRuntime; +import org.eclipse.m2e.internal.maven.listener.M2EMavenBuildDataBridge; + + +public class MavenBuildConnectionLaunchParticipant implements IMavenLaunchParticipant { + + static { + DebugPlugin.getDefault().getLaunchManager().addLaunchListener(new ILaunchesListener2() { + public void launchesRemoved(ILaunch[] launches) { + ensureClosed(launches); + } + + private void ensureClosed(ILaunch[] launches) { + Arrays.stream(launches).flatMap(l -> MavenBuildConnectionProcess.get(l).stream()) + .forEach(MavenBuildConnectionProcess::terminate); + } + + public void launchesTerminated(ILaunch[] launches) { + ensureClosed(launches); + } + + public void launchesAdded(ILaunch[] launches) { // ignore + } + + public void launchesChanged(ILaunch[] launches) { // ignore + } + }); + } + + public String getProgramArguments(ILaunchConfiguration configuration, ILaunch launch, IProgressMonitor monitor) { + try { + if(MavenLaunchUtils.getMavenRuntime(launch.getLaunchConfiguration()) instanceof MavenEmbeddedRuntime) { + + MavenBuildConnectionProcess process = new MavenBuildConnectionProcess(launch); + return M2EMavenBuildDataBridge.prepareConnection(launch.getLaunchConfiguration().getName(), process); + } + } catch(CoreException | IOException ex) { // ignore + } + return null; + } + + public String getVMArguments(ILaunchConfiguration configuration, ILaunch launch, IProgressMonitor monitor) { + return null; + } + + public List getSourceLookupParticipants(ILaunchConfiguration configuration, ILaunch launch, + IProgressMonitor monitor) { + return List.of(); + } + +} diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildConnectionProcess.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildConnectionProcess.java new file mode 100644 index 0000000000..cac21828cf --- /dev/null +++ b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildConnectionProcess.java @@ -0,0 +1,155 @@ +/******************************************************************************** + * Copyright (c) 2024 Christoph Läubrich and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Christoph Läubrich - initial API and implementation + ********************************************************************************/ + +package org.eclipse.m2e.internal.launch; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.eclipse.debug.core.DebugEvent; +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.ILaunch; +import org.eclipse.debug.core.model.IProcess; +import org.eclipse.debug.core.model.IStreamsProxy; + +import org.eclipse.m2e.core.embedder.ArtifactKey; +import org.eclipse.m2e.internal.maven.listener.MavenBuildConnection; +import org.eclipse.m2e.internal.maven.listener.MavenBuildConnectionListener; +import org.eclipse.m2e.internal.maven.listener.MavenProjectBuildData; + + +/** + * This is the representation of the MavenBuildConnection we use to communicate with the remote maven process. + */ +public class MavenBuildConnectionProcess implements IProcess, MavenBuildConnectionListener { + + Map> projects = new ConcurrentHashMap<>(); + + private ILaunch launch; + + private MavenBuildConnection connection; + + private Map attributes = new HashMap<>(); + + private AtomicBoolean terminated = new AtomicBoolean(); + + private String label; + + /** + * @param launch + */ + public MavenBuildConnectionProcess(ILaunch launch) { + this.launch = launch; + } + + public T getAdapter(Class adapter) { + return null; + } + + public boolean canTerminate() { + return false; + } + + public boolean isTerminated() { + return connection != null && connection.isCompleted(); + } + + public void terminate() { + if(connection != null && terminated.compareAndSet(false, true)) { + connection.close(); + fireEvent(new DebugEvent(this, DebugEvent.TERMINATE)); + for(CompletableFuture future : projects.values()) { + future.cancel(true); + } + } + } + + public String getLabel() { + // TODO fetch the maven version from the remove process like mvn -V ... + if(label != null) { + return "Maven<" + label + ">"; + } + return "Maven"; + } + + public ILaunch getLaunch() { + return launch; + } + + public IStreamsProxy getStreamsProxy() { + return null; + } + + public void setAttribute(String key, String value) { + attributes.put(key, value); + fireEvent(new DebugEvent(this, DebugEvent.CHANGE)); + } + + public String getAttribute(String key) { + return attributes.get(key); + } + + public int getExitValue() { + return 0; + } + + private static void fireEvent(DebugEvent event) { + DebugPlugin manager = DebugPlugin.getDefault(); + if(manager != null) { + manager.fireDebugEventSet(new DebugEvent[] {event}); + } + } + + public static Optional get(ILaunch launch) { + for(IProcess process : launch.getProcesses()) { + if(process instanceof MavenBuildConnectionProcess p) { + return Optional.of(p); + } + } + return Optional.empty(); + } + + /** + * @param launch2 + * @param groupId + * @param artifactId + * @param version + * @return + */ + public CompletableFuture getBuildProject(String groupId, String artifactId, String version) { + return projects.computeIfAbsent(new ArtifactKey(groupId, artifactId, version, null), + x -> new CompletableFuture<>()); + } + + public void onOpen(String label, MavenBuildConnection connection) { + this.label = label; + this.connection = connection; + getLaunch().addProcess(this); + fireEvent(new DebugEvent(this, DebugEvent.CREATE)); + + } + + public void onClose() { + terminate(); + } + + public void onData(MavenProjectBuildData buildData) { + projects.computeIfAbsent(new ArtifactKey(buildData.groupId, buildData.artifactId, buildData.version, null), x -> new CompletableFuture<>()) + .complete(buildData); + } + +} diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildProjectDataConnection.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildProjectDataConnection.java deleted file mode 100644 index cd9abfbcd0..0000000000 --- a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildProjectDataConnection.java +++ /dev/null @@ -1,107 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2022, 2022 Hannes Wellmann and others - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Hannes Wellmann - initial API and implementation - ********************************************************************************/ - -package org.eclipse.m2e.internal.launch; - -import java.io.IOException; -import java.util.Arrays; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Stream; - -import org.eclipse.core.runtime.CoreException; -import org.eclipse.debug.core.DebugPlugin; -import org.eclipse.debug.core.ILaunch; -import org.eclipse.debug.core.ILaunchesListener2; - -import org.eclipse.m2e.core.embedder.ArtifactKey; -import org.eclipse.m2e.core.internal.launch.MavenEmbeddedRuntime; -import org.eclipse.m2e.internal.launch.MavenRuntimeLaunchSupport.VMArguments; -import org.eclipse.m2e.internal.maven.listener.M2EMavenBuildDataBridge; -import org.eclipse.m2e.internal.maven.listener.M2EMavenBuildDataBridge.MavenBuildConnection; -import org.eclipse.m2e.internal.maven.listener.M2EMavenBuildDataBridge.MavenProjectBuildData; - - -public class MavenBuildProjectDataConnection { - - private static record MavenBuildConnectionData(Map projects, - MavenBuildConnection connection) { - } - - private static final Map LAUNCH_PROJECT_DATA = new ConcurrentHashMap<>(); - - static { - DebugPlugin.getDefault().getLaunchManager().addLaunchListener(new ILaunchesListener2() { - public void launchesRemoved(ILaunch[] launches) { - closeServers(Arrays.stream(launches).map(LAUNCH_PROJECT_DATA::remove)); - } - - public void launchesTerminated(ILaunch[] launches) { - closeServers(Arrays.stream(launches).map(LAUNCH_PROJECT_DATA::get)); - } - - private static void closeServers(Stream connectionData) { - connectionData.filter(Objects::nonNull).forEach(c -> { - try { - c.connection().close(); - } catch(IOException ex) { // ignore - } - }); - } - - public void launchesAdded(ILaunch[] launches) { // ignore - } - - public void launchesChanged(ILaunch[] launches) { // ignore - } - }); - } - - static void openListenerConnection(ILaunch launch, VMArguments arguments) { - try { - if(MavenLaunchUtils.getMavenRuntime(launch.getLaunchConfiguration()) instanceof MavenEmbeddedRuntime) { - - Map projects = new ConcurrentHashMap<>(); - - MavenBuildConnection connection = M2EMavenBuildDataBridge.prepareConnection( - launch.getLaunchConfiguration().getName(), - d -> projects.put(new ArtifactKey(d.groupId, d.artifactId, d.version, null), d)); - - if(LAUNCH_PROJECT_DATA.putIfAbsent(launch, new MavenBuildConnectionData(projects, connection)) != null) { - connection.close(); - throw new IllegalStateException( - "Maven bridge already created for launch of" + launch.getLaunchConfiguration().getName()); - } - arguments.append(connection.getMavenVMArguments()); - } - } catch(CoreException | IOException ex) { // ignore - } - } - - static MavenProjectBuildData getBuildProject(ILaunch launch, String groupId, String artifactId, String version) { - MavenBuildConnectionData build = LAUNCH_PROJECT_DATA.get(launch); - if(build == null) { - return null; - } - ArtifactKey key = new ArtifactKey(groupId, artifactId, version, null); - while(true) { - MavenProjectBuildData buildProject = build.projects().get(key); - if(buildProject != null || build.connection().isReadCompleted()) { - return buildProject; - } - Thread.onSpinWait(); // Await completion of project data read. It has to become available soon, since its GAV was printed on the console - } - } - -} diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenConsoleLineTracker.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenConsoleLineTracker.java index 923df503c7..db9fcc4116 100644 --- a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenConsoleLineTracker.java +++ b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenConsoleLineTracker.java @@ -29,6 +29,8 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; @@ -65,7 +67,7 @@ import org.eclipse.m2e.core.internal.IMavenConstants; import org.eclipse.m2e.core.project.IBuildProjectFileResolver; -import org.eclipse.m2e.internal.maven.listener.M2EMavenBuildDataBridge.MavenProjectBuildData; +import org.eclipse.m2e.internal.maven.listener.MavenProjectBuildData; /** @@ -227,10 +229,13 @@ private String getText(IRegion lineRegion) throws BadLocationException { if(gaMatcher.matches()) { String groupId = gaMatcher.group(GROUP_ID); String artifactId = gaMatcher.group(ARTIFACT_ID); - - mavenProject = getProject(groupId, artifactId, version); - if(mavenProject != null) { - addProjectLink(line3Region, gaMatcher, GROUP_ID, ARTIFACT_ID, removedLine3Locations); + try { + mavenProject = getProject(groupId, artifactId, version).join(); //TODO can we do this with future notification? + if(mavenProject != null) { + addProjectLink(line3Region, gaMatcher, GROUP_ID, ARTIFACT_ID, removedLine3Locations); + } + } catch(CancellationException e) { + mavenProject = null; } } } @@ -265,18 +270,18 @@ private void addProjectLink(IRegion line, Matcher matcher, int startGroup, int e console.addLink(link, line.getOffset() + start, end - start); } - private ProjectReference getProject(String groupId, String artifactId, String version) { - MavenProjectBuildData buildProject = MavenBuildProjectDataConnection.getBuildProject(launch, groupId, artifactId, - version); - if(buildProject == null) { - return null; - } - IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); - URI basedirURI = buildProject.projectBasedir.toUri(); - Optional project = Arrays.stream(wsRoot.findContainersForLocationURI(basedirURI)) - .filter(IProject.class::isInstance).map(IProject.class::cast).findFirst(); - //if project is absent, the project build in Maven is not in the workspace - return project.isPresent() ? new ProjectReference(project.get(), buildProject) : null; + private CompletableFuture getProject(String groupId, String artifactId, String version) { + return MavenBuildConnectionProcess.get(launch).map(process -> process.getBuildProject(groupId, artifactId, version)) + .map(pdf -> { + return pdf.thenApply(buildProject -> { + IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); + URI basedirURI = buildProject.projectBasedir.toUri(); + Optional project = Arrays.stream(wsRoot.findContainersForLocationURI(basedirURI)) + .filter(IProject.class::isInstance).map(IProject.class::cast).findFirst(); + //if project is absent, the project build in Maven is not in the workspace + return project.isPresent() ? new ProjectReference(project.get(), buildProject) : null; + }); + }).orElseGet(() -> CompletableFuture.completedFuture(null)); } @Override diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenLaunchExtensionsSupport.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenLaunchExtensionsSupport.java index 892a25aa85..19484380e4 100644 --- a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenLaunchExtensionsSupport.java +++ b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenLaunchExtensionsSupport.java @@ -98,7 +98,6 @@ public void appendVMArguments(VMArguments arguments, ILaunchConfiguration config for(IMavenLaunchParticipant participant : participants) { arguments.append(participant.getVMArguments(configuration, launch, monitor)); } - MavenBuildProjectDataConnection.openListenerConnection(launch, arguments); } } diff --git a/org.eclipse.m2e.maven.runtime/pom.xml b/org.eclipse.m2e.maven.runtime/pom.xml index b1146a7ed1..15d8e92376 100644 --- a/org.eclipse.m2e.maven.runtime/pom.xml +++ b/org.eclipse.m2e.maven.runtime/pom.xml @@ -20,7 +20,7 @@ org.eclipse.m2e.maven.runtime - 3.9.600-SNAPSHOT + 3.9.601-SNAPSHOT jar M2E Embedded Maven Runtime (includes Incubating components) diff --git a/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/M2EMavenBuildDataBridge.java b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/M2EMavenBuildDataBridge.java index 72d113fe06..aa0a8e704b 100644 --- a/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/M2EMavenBuildDataBridge.java +++ b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/M2EMavenBuildDataBridge.java @@ -21,22 +21,14 @@ import java.nio.ByteBuffer; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.StringJoiner; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; import javax.inject.Named; import javax.inject.Singleton; +import org.apache.maven.AbstractMavenLifecycleParticipant; +import org.apache.maven.MavenExecutionException; import org.apache.maven.eventspy.EventSpy; -import org.apache.maven.execution.ExecutionEvent; -import org.apache.maven.execution.ExecutionEvent.Type; -import org.apache.maven.project.MavenProject; +import org.apache.maven.execution.MavenSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,9 +40,9 @@ * @author Hannes Wellmann * */ -@Named @Singleton -public class M2EMavenBuildDataBridge implements EventSpy { +@Named("m2e") +public class M2EMavenBuildDataBridge extends AbstractMavenLifecycleParticipant { private static final String SOCKET_FILE_PROPERTY_NAME = "m2e.build.project.data.socket.port"; private static final String DATA_SET_SEPARATOR = ";;"; @@ -60,7 +52,7 @@ public class M2EMavenBuildDataBridge implements EventSpy { private SocketChannel writeChannel; @Override - public void init(Context context) throws IOException { + public synchronized void afterSessionStart(MavenSession session) throws MavenExecutionException { String socketPort = System.getProperty(SOCKET_FILE_PROPERTY_NAME); if (socketPort != null) { try { @@ -76,79 +68,37 @@ public void init(Context context) throws IOException { } @Override - public void close() throws IOException { - writeChannel.close(); + public void afterSessionEnd(MavenSession session) throws MavenExecutionException { + close(); } - @Override - public void onEvent(Object event) throws Exception { - if (writeChannel != null && event instanceof ExecutionEvent - && ((ExecutionEvent) event).getType() == Type.ProjectStarted) { - - String message = serializeProjectData(((ExecutionEvent) event).getProject()); - - ByteBuffer buffer = ByteBuffer.wrap(message.getBytes()); - synchronized (writeChannel) { - while (buffer.hasRemaining()) { - writeChannel.write(buffer); - } + private synchronized void close() { + if (writeChannel != null) { + try { + writeChannel.close(); + } catch (IOException e) { + // nothing we want to do here... } } + writeChannel = null; } - private static String serializeProjectData(MavenProject project) { - StringJoiner data = new StringJoiner(","); - add(data, "groupId", project.getGroupId()); - add(data, "artifactId", project.getArtifactId()); - add(data, "version", project.getVersion()); - add(data, "file", project.getFile()); - add(data, "basedir", project.getBasedir()); - add(data, "build.directory", project.getBuild().getDirectory()); - return data.toString() + DATA_SET_SEPARATOR; - } - - private static void add(StringJoiner data, String key, Object value) { - data.add(key + "=" + value); + public synchronized boolean isActive() { + return writeChannel != null; } - /** - *

- * This method is supposed to be called from M2E within the Eclipse-IDE JVM. - *

- * - * @param dataSet the data-set to parse - * @return the {@link MavenProjectBuildData} parsed from the given string - */ - private static MavenProjectBuildData parseMavenBuildProject(String dataSet) { - Map data = new HashMap<>(8); - for (String entry : dataSet.split(",")) { - String[] keyValue = entry.split("="); - if (keyValue.length != 2) { - throw new IllegalStateException("Invalid data-set format" + dataSet); - } - data.put(keyValue[0], keyValue[1]); + public synchronized void sendMessage(String message) { + if (writeChannel == null) { + return; } - return new MavenProjectBuildData(data); - } - - public static final class MavenProjectBuildData { - public final String groupId; - public final String artifactId; - public final String version; - public final Path projectBasedir; - public final Path projectFile; - public final Path projectBuildDirectory; - - MavenProjectBuildData(Map data) { - if (data.size() != 6) { - throw new IllegalArgumentException(); + ByteBuffer buffer = ByteBuffer.wrap((message + DATA_SET_SEPARATOR).getBytes()); + while (buffer.hasRemaining()) { + try { + writeChannel.write(buffer); + } catch (IOException e) { + // channel seems dead... + close(); } - this.groupId = Objects.requireNonNull(data.get("groupId")); - this.artifactId = Objects.requireNonNull(data.get("artifactId")); - this.version = Objects.requireNonNull(data.get("version")); - this.projectBasedir = Paths.get(data.get("basedir")); - this.projectFile = Paths.get(data.get("file")); - this.projectBuildDirectory = Paths.get(data.get("build.directory")); } } @@ -156,15 +106,14 @@ public static final class MavenProjectBuildData { * Prepares the connection to a {@code Maven build JVM} to be launched and is * intended to be called from the Eclipse IDE JVM. * - * @param label the label of the listener thread - * @param datasetListener the listener, which is notified whenever a new - * {@link MavenProjectBuildData MavenProjectBuildDataSet} - * has arived from the Maven VM in the Eclipse-IDE VM. - * @return the preapre {@link MavenBuildConnection} - * @throws IOException + * @param label the label of the listener thread + * @param listener the listener, which is notified whenever a new + * {@link MavenProjectBuildData MavenProjectBuildDataSet} has + * arived from the Maven VM in the Eclipse-IDE VM. + * @return the maven arguments + * @throws IOException if init of connection failed */ - public static MavenBuildConnection prepareConnection(String label, Consumer datasetListener) - throws IOException { + public static String prepareConnection(String label, MavenBuildConnectionListener listener) throws IOException { // TODO: use UNIX domain socket once Java-17 is required by Maven // Path socketFile = Files.createTempFile("m2e.maven.build.listener", ".socket"); @@ -176,7 +125,7 @@ public static MavenBuildConnection prepareConnection(String label, Consumer { try (ServerSocketChannel s = server; SocketChannel readChannel = server.accept()) { @@ -187,9 +136,8 @@ public static MavenBuildConnection prepareConnection(String label, Consumer= 0;) { String dataSet = message.substring(0, terminatorIndex); message.delete(0, terminatorIndex + DATA_SET_SEPARATOR.length()); - - MavenProjectBuildData buildData = parseMavenBuildProject(dataSet); - datasetListener.accept(buildData); + MavenProjectBuildData buildData = MavenProjectBuildData.parseMavenBuildProject(dataSet); + listener.onData(buildData); } // Explicit cast for compatibility with covariant return type on JDK 9's // ByteBuffer @@ -197,7 +145,7 @@ public static MavenBuildConnection prepareConnection(String label, Consumer connection reader"); reader.setDaemon(true); reader.start(); - return connection; + listener.onOpen(label, connection); + String port = Integer.toString(((InetSocketAddress) server.getLocalAddress()).getPort()); + return "-D" + SOCKET_FILE_PROPERTY_NAME + "=" + port; } - - public static final class MavenBuildConnection { - private final ServerSocketChannel server; - private final AtomicBoolean readCompleted = new AtomicBoolean(false); - - MavenBuildConnection(ServerSocketChannel server) { - this.server = server; - } - - public String getMavenVMArguments() throws IOException { - String port = Integer.toString(((InetSocketAddress) server.getLocalAddress()).getPort()); - return "-D" + SOCKET_FILE_PROPERTY_NAME + "=" + port; - } - - public boolean isReadCompleted() { - return readCompleted.get(); - } - - public void close() throws IOException { - // Close the server to ensure the reader-thread does not wait forever for a - // connection from the Maven-process in case something went wrong during - // launching or while setting up the connection. - server.close(); - } - } - } diff --git a/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/M2eEventSpy.java b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/M2eEventSpy.java new file mode 100644 index 0000000000..0f284903bf --- /dev/null +++ b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/M2eEventSpy.java @@ -0,0 +1,58 @@ +/******************************************************************************** + * Copyright (c) 2022, 2024 Hannes Wellmann and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Hannes Wellmann - initial API and implementation + * Christoph Läubrich - factor out into dedicated component + ********************************************************************************/ +package org.eclipse.m2e.internal.maven.listener; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import org.apache.maven.eventspy.EventSpy; +import org.apache.maven.execution.ExecutionEvent; +import org.apache.maven.execution.ExecutionEvent.Type; + +@Singleton +@Named("m2e") +public class M2eEventSpy implements EventSpy { + + private M2EMavenBuildDataBridge bridge; + + @Inject + public M2eEventSpy(M2EMavenBuildDataBridge bridge) { + this.bridge = bridge; + } + + @Override + public void init(Context context) throws Exception { + + } + + @Override + public void onEvent(Object event) throws Exception { + if (bridge.isActive()) { + if (event instanceof ExecutionEvent) { + ExecutionEvent executionEvent = (ExecutionEvent) event; + if (executionEvent.getType() == Type.ProjectStarted) { + String message = MavenProjectBuildData.serializeProjectData(((ExecutionEvent) event).getProject()); + bridge.sendMessage(message); + } + } + } + } + + @Override + public void close() throws Exception { + + } + +} diff --git a/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenBuildConnection.java b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenBuildConnection.java new file mode 100644 index 0000000000..de5abb5916 --- /dev/null +++ b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenBuildConnection.java @@ -0,0 +1,46 @@ +/******************************************************************************** + * Copyright (c) 2022, 2024 Hannes Wellmann and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Hannes Wellmann - initial API and implementation + * Christoph Läubrich - factor out into dedicated component + ********************************************************************************/ +package org.eclipse.m2e.internal.maven.listener; + +import java.io.IOException; +import java.nio.channels.ServerSocketChannel; +import java.util.concurrent.atomic.AtomicBoolean; + +public final class MavenBuildConnection { + private final ServerSocketChannel server; + private final AtomicBoolean closed = new AtomicBoolean(false); + private MavenBuildConnectionListener listener; + + MavenBuildConnection(ServerSocketChannel server, MavenBuildConnectionListener listener) { + this.server = server; + this.listener = listener; + } + + public boolean isCompleted() { + return closed.get(); + } + + public void close() { + if (closed.compareAndSet(false, true)) { + listener.onClose(); + // Close the server to ensure the reader-thread does not wait forever for a + // connection from the Maven-process in case something went wrong during + // launching or while setting up the connection. + try { + server.close(); + } catch (IOException e) { + } + } + } +} \ No newline at end of file diff --git a/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenBuildConnectionListener.java b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenBuildConnectionListener.java new file mode 100644 index 0000000000..5141f03020 --- /dev/null +++ b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenBuildConnectionListener.java @@ -0,0 +1,23 @@ +/******************************************************************************** + * Copyright (c) 2024 Christoph Läubrich and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Christoph Läubrich - initial API and implementation + ********************************************************************************/ +package org.eclipse.m2e.internal.maven.listener; + +public interface MavenBuildConnectionListener { + + void onOpen(String label, MavenBuildConnection connection); + + void onClose(); + + void onData(MavenProjectBuildData buildData); + +} diff --git a/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenProjectBuildData.java b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenProjectBuildData.java new file mode 100644 index 0000000000..befa0e5241 --- /dev/null +++ b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenProjectBuildData.java @@ -0,0 +1,79 @@ +/******************************************************************************** + * Copyright (c) 2022, 2024 Hannes Wellmann and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Hannes Wellmann - initial API and implementation + * Christoph Läubrich - factor out into dedicated component + ********************************************************************************/ +package org.eclipse.m2e.internal.maven.listener; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.StringJoiner; + +import org.apache.maven.project.MavenProject; + +public final class MavenProjectBuildData { + public final String groupId; + public final String artifactId; + public final String version; + public final Path projectBasedir; + public final Path projectFile; + public final Path projectBuildDirectory; + + MavenProjectBuildData(Map data) { + if (data.size() != 6) { + throw new IllegalArgumentException(); + } + this.groupId = Objects.requireNonNull(data.get("groupId")); + this.artifactId = Objects.requireNonNull(data.get("artifactId")); + this.version = Objects.requireNonNull(data.get("version")); + this.projectBasedir = Paths.get(data.get("basedir")); + this.projectFile = Paths.get(data.get("file")); + this.projectBuildDirectory = Paths.get(data.get("build.directory")); + } + + /** + *

+ * This method is supposed to be called from M2E within the Eclipse-IDE JVM. + *

+ * + * @param dataSet the data-set to parse + * @return the {@link MavenProjectBuildData} parsed from the given string + */ + static MavenProjectBuildData parseMavenBuildProject(String dataSet) { + Map data = new HashMap<>(8); + for (String entry : dataSet.split(",")) { + String[] keyValue = entry.split("="); + if (keyValue.length != 2) { + throw new IllegalStateException("Invalid data-set format" + dataSet); + } + data.put(keyValue[0], keyValue[1]); + } + return new MavenProjectBuildData(data); + } + + static String serializeProjectData(MavenProject project) { + StringJoiner data = new StringJoiner(","); + add(data, "groupId", project.getGroupId()); + add(data, "artifactId", project.getArtifactId()); + add(data, "version", project.getVersion()); + add(data, "file", project.getFile()); + add(data, "basedir", project.getBasedir()); + add(data, "build.directory", project.getBuild().getDirectory()); + return data.toString(); + } + + private static void add(StringJoiner data, String key, Object value) { + data.add(key + "=" + value); + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 4950bcd1b8..c39883b38e 100644 --- a/pom.xml +++ b/pom.xml @@ -103,7 +103,7 @@ org.eclipse.m2e org.eclipse.m2e.maven.runtime - 3.9.600-SNAPSHOT + 3.9.601-SNAPSHOT