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 de43554632..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.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 cca16682eb..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; @@ -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/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 3e9d9acc94..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,8 +21,6 @@ import java.nio.ByteBuffer; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; import javax.inject.Named; import javax.inject.Singleton; @@ -90,7 +88,6 @@ public synchronized boolean isActive() { } public synchronized void sendMessage(String message) { - System.out.println("send: " + message); if (writeChannel == null) { return; } @@ -109,15 +106,14 @@ public synchronized void sendMessage(String message) { * 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"); @@ -129,7 +125,7 @@ public static MavenBuildConnection prepareConnection(String label, Consumer { try (ServerSocketChannel s = server; SocketChannel readChannel = server.accept()) { @@ -140,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()); - System.out.println("got messagE: " + dataSet); MavenProjectBuildData buildData = MavenProjectBuildData.parseMavenBuildProject(dataSet); - datasetListener.accept(buildData); + listener.onData(buildData); } // Explicit cast for compatibility with covariant return type on JDK 9's // ByteBuffer @@ -150,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 index 5236224268..0f284903bf 100644 --- 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 @@ -39,7 +39,6 @@ public void init(Context context) throws Exception { @Override public void onEvent(Object event) throws Exception { - System.out.println("M2eEventSpy.onEvent() " + event); if (bridge.isActive()) { if (event instanceof ExecutionEvent) { ExecutionEvent executionEvent = (ExecutionEvent) event; 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); + +}