/* * Copyright (c) 2017 the original author or authors. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.eclipse.buildship.core.workspace.internal; import java.io.IOException; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.net.URL; import java.net.URLClassLoader; import java.util.Collection; import org.gradle.tooling.BuildAction; import org.gradle.tooling.BuildActionExecuter; import org.gradle.tooling.BuildLauncher; import org.gradle.tooling.GradleConnectionException; import org.gradle.tooling.GradleConnector; import org.gradle.tooling.LongRunningOperation; import org.gradle.tooling.ModelBuilder; import org.gradle.tooling.ProgressListener; import org.gradle.tooling.ProjectConnection; import org.gradle.tooling.ResultHandler; import org.gradle.tooling.TestLauncher; import com.gradleware.tooling.toolingmodel.repository.FixedRequestAttributes; import com.gradleware.tooling.toolingmodel.repository.TransientRequestAttributes; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.Platform; import org.eclipse.buildship.core.CorePlugin; import org.eclipse.buildship.core.GradlePluginsRuntimeException; import org.eclipse.buildship.core.util.gradle.GradleDistributionWrapper; /** * Provides long-running TAPI operation instances that close their project connection after the execution is * finished. * * @author Donat Csikos */ @SuppressWarnings("unchecked") final class ConnectionAwareLauncherProxy implements InvocationHandler { private final LongRunningOperation launcher; private final ProjectConnection connection; private static URLClassLoader ideFriendlyCustomActionClassLoader; private ConnectionAwareLauncherProxy(ProjectConnection connection, LongRunningOperation target) { this.connection = connection; this.launcher = target; } static <T> ModelBuilder<T> newModelBuilder(Class<T> model, FixedRequestAttributes fixedAttributes, TransientRequestAttributes transientAttributes) { ProjectConnection connection = openConnection(fixedAttributes); ModelBuilder<T> builder = connection.model(model); applyRequestAttributes(builder, fixedAttributes, transientAttributes); return (ModelBuilder<T>) newProxyInstance(connection, builder); } static <T> BuildActionExecuter<Collection<T>> newCompositeModelQueryExecuter(Class<T> model, FixedRequestAttributes fixedAttributes, TransientRequestAttributes transientAttributes) { ProjectConnection connection = openConnection(fixedAttributes); BuildActionExecuter<Collection<T>> executer = connection.action(compositeModelQuery(model)); applyRequestAttributes(executer, fixedAttributes, transientAttributes); return (BuildActionExecuter<Collection<T>>) newProxyInstance(connection, executer); } static BuildLauncher newBuildLauncher(FixedRequestAttributes fixedAttributes, TransientRequestAttributes transientAttributes) { ProjectConnection connection = openConnection(fixedAttributes); BuildLauncher launcher = connection.newBuild(); applyRequestAttributes(launcher, fixedAttributes, transientAttributes); return (BuildLauncher) newProxyInstance(connection, launcher); } static TestLauncher newTestLauncher(FixedRequestAttributes fixedAttributes, TransientRequestAttributes transientAttributes) { ProjectConnection connection = openConnection(fixedAttributes); TestLauncher launcher = connection.newTestLauncher(); applyRequestAttributes(launcher, fixedAttributes, transientAttributes); return (TestLauncher) newProxyInstance(connection, launcher); } private static ProjectConnection openConnection(FixedRequestAttributes fixedAttributes) { GradleConnector connector = GradleConnector.newConnector().forProjectDirectory(fixedAttributes.getProjectDir()); GradleDistributionWrapper.from(fixedAttributes.getGradleDistribution()).apply(connector); connector.useGradleUserHomeDir(fixedAttributes.getGradleUserHome()); return connector.connect(); } private static void applyRequestAttributes(LongRunningOperation operation, FixedRequestAttributes fixedAttributes, TransientRequestAttributes transientAttributes) { operation.setJavaHome(fixedAttributes.getJavaHome()); operation.withArguments(fixedAttributes.getArguments()); operation.setJvmArguments(fixedAttributes.getJvmArguments()); operation.setStandardOutput(transientAttributes.getStandardOutput()); operation.setStandardError(transientAttributes.getStandardError()); operation.setStandardInput(transientAttributes.getStandardInput()); for (ProgressListener listener : transientAttributes.getProgressListeners()) { operation.addProgressListener(listener); } operation. withCancellationToken(transientAttributes.getCancellationToken()); } private static <T> BuildAction<Collection<T>> compositeModelQuery(Class<T> model) { if (Platform.inDevelopmentMode()) { return ideFriendlyCompositeModelQuery(model); } else { return new CompositeModelQuery<>(model); } } private static <T> BuildAction<Collection<T>> ideFriendlyCompositeModelQuery(Class<T> model) { // When Buildship is launched from the IDE - as an Eclipse application or as a plugin-in // test - the URLs returned by the Equinox class loader is incorrect. This means, the // Tooling API is unable to find the referenced build actions and fails with a CNF // exception. To work around that, we look up the build action class locations and load the // classes via an isolated URClassLoader. try { ClassLoader coreClassloader = ConnectionAwareLauncherProxy.class.getClassLoader(); ClassLoader tapiClassloader = ProjectConnection.class.getClassLoader(); URL actionRootUrl = FileLocator.resolve(coreClassloader.getResource("")); ideFriendlyCustomActionClassLoader = new URLClassLoader(new URL[] { actionRootUrl }, tapiClassloader); Class<?> actionClass = ideFriendlyCustomActionClassLoader.loadClass(CompositeModelQuery.class.getName()); return (BuildAction<Collection<T>>) actionClass.getConstructor(Class.class).newInstance(model); } catch (Exception e) { throw new GradlePluginsRuntimeException(e); } } private static Object newProxyInstance(ProjectConnection connection, LongRunningOperation launcher) { return Proxy.newProxyInstance(launcher.getClass().getClassLoader(), launcher.getClass().getInterfaces(), new ConnectionAwareLauncherProxy(connection, launcher)); } @Override public Object invoke(Object proxy, Method m, Object[] args) throws Throwable { // BuildLauncher and TestLauncher have the same method signature for execution: // #run() and #run(ResultHandler) if (m.getName().equals("run") || m.getName().equals("get")) { if (args == null) { return invokeRun(m); } else if (args.length == 1 && args[0].getClass() == ResultHandler.class) { return invokeRun(m, args[0]); } } return invokeOther(m, args); } private Object invokeRun(Method m) throws Throwable { try { return m.invoke(this.launcher); } finally { closeConnection(); } } private Object invokeRun(Method m, Object resultHandler) throws Throwable { final ResultHandler<Object> handler = (ResultHandler<Object>) resultHandler; return m.invoke(this.launcher, new ResultHandler<Object>() { @Override public void onComplete(Object result) { try { handler.onComplete(result); } finally { closeConnection(); } } @Override public void onFailure(GradleConnectionException e) { try { handler.onFailure(e); } finally { closeConnection(); } } }); } private void closeConnection() { this.connection.close(); if (ideFriendlyCustomActionClassLoader != null) { try { ideFriendlyCustomActionClassLoader.close(); } catch (IOException e) { CorePlugin.logger().error("Can't close URL class loader", e); } } } private Object invokeOther(Method m, Object[] args) throws Throwable { return m.invoke(this.launcher, args); } }