| /** |
| * 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 |
| * <p> |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * <p> |
| * 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 org.apache.winegrower.extension.build.maven; |
| |
| import org.apache.maven.artifact.Artifact; |
| import org.apache.maven.plugin.MojoExecutionException; |
| import org.apache.maven.plugins.annotations.Mojo; |
| import org.apache.maven.plugins.annotations.Parameter; |
| import org.apache.maven.project.MavenProject; |
| import org.apache.winegrower.api.LifecycleCallbacks; |
| import org.apache.xbean.finder.util.Files; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.lang.reflect.InvocationTargetException; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.net.URLClassLoader; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.function.Consumer; |
| import java.util.function.Predicate; |
| import java.util.stream.Stream; |
| |
| import static java.util.Optional.ofNullable; |
| import static java.util.stream.Collectors.toList; |
| import static org.apache.maven.plugins.annotations.ResolutionScope.RUNTIME_PLUS_SYSTEM; |
| |
| @Mojo(name = "pour", requiresDependencyResolution = RUNTIME_PLUS_SYSTEM) |
| public class PourMojo extends LibsMojo { |
| |
| @Parameter(property = "winegrower.workDir", defaultValue = "${project.build.directory}/winegrower/workdir") |
| private File workDir; |
| |
| @Parameter(property = "winegrower.jarFilter") |
| private String jarFilter; |
| |
| @Parameter(property = "winegrower.scanningIncludes") |
| private Collection<String> scanningIncludes; |
| |
| @Parameter(property = "winegrower.scanningExcludes") |
| private Collection<String> scanningExcludes; |
| |
| @Parameter(property = "winegrower.ignoredBundles") |
| private Collection<String> ignoredBundles; |
| |
| @Parameter(property = "winegrower.manifestContributors") |
| private Collection<String> manifestContributors; |
| |
| @Parameter(property = "winegrower.dependencyScopes", defaultValue = "provided,compile,system,runtime") |
| private Collection<String> dependencyScopes; |
| |
| @Parameter(property = "winegrower.prioritizedBundles") |
| private List<String> prioritizedBundles; |
| |
| @Parameter(property = "winegrower.lifecycleCallbacks") |
| private List<Class<?>> lifecycleCallbacks; |
| |
| @Parameter(property = "winegrower.useLifecycleCallbacks", defaultValue = "true") |
| private boolean useLifecycleCallbacks; |
| |
| @Parameter(property = "winegrower.systemVariables") |
| private Map<String, String> systemVariables; |
| |
| @Parameter(property = "winegrower.waitOnSystemIn", defaultValue = "true") |
| private boolean waitOnSystemIn; // if you use the shell this breaks it if set to true |
| |
| @Parameter(defaultValue = "${project}", readonly = true, required = true) |
| private MavenProject project; |
| |
| @Override |
| public void execute() throws MojoExecutionException { |
| final Thread thread = Thread.currentThread(); |
| final ClassLoader loader = thread.getContextClassLoader(); |
| final ClassLoader appLoader = createClassLoader(loader); |
| thread.setContextClassLoader(appLoader); |
| try { |
| final Consumer<Runnable> waitOnExit = defaultImpl -> { |
| if (waitOnSystemIn) { |
| defaultImpl.run(); |
| } else { // just block |
| try { |
| new CountDownLatch(1).await(); |
| } catch (final InterruptedException e) { |
| Thread.currentThread() |
| .interrupt(); |
| } |
| } |
| }; |
| final Class<?> runClass = appLoader.loadClass("org.apache.winegrower.extension.build.common.Run"); |
| final Class<?> confClass = appLoader.loadClass("org.apache.winegrower.Ripener$Configuration"); |
| Runnable.class.cast(runClass.getConstructor(confClass, Map.class, Consumer.class) |
| .newInstance(createConfiguration(confClass), systemVariables, waitOnExit)).run(); |
| } catch (final Exception e) { |
| throw new MojoExecutionException(e.getMessage(), e); |
| } finally { |
| if (appLoader != loader) { |
| try { |
| URLClassLoader.class.cast(appLoader).close(); |
| } catch (final IOException e) { |
| getLog().warn(e.getMessage(), e); |
| } |
| } |
| thread.setContextClassLoader(loader); |
| } |
| } |
| |
| private ClassLoader createClassLoader(final ClassLoader parent) { |
| final List<File> jars = Stream.concat( |
| collectLibs(), |
| Stream.concat(project.getArtifacts().stream() |
| .filter(a -> !((dependencyScopes == null && !(Artifact.SCOPE_COMPILE.equals( |
| a.getScope()) || Artifact.SCOPE_RUNTIME.equals( |
| a.getScope()))) || (dependencyScopes != null && !dependencyScopes.contains( |
| a.getScope())))) |
| .map(Artifact::getFile), |
| Stream.of(project.getBuild().getOutputDirectory()).map(File::new).filter(File::exists))) |
| .collect(toList()); |
| final List<URL> urls = jars.stream().map(file -> { |
| try { |
| return file.toURI().toURL(); |
| } catch (final MalformedURLException e) { |
| throw new IllegalArgumentException(e); |
| } |
| }).collect(toList()); |
| if (getLog().isDebugEnabled()) { |
| getLog().debug("Startup classpath: " + jars); |
| } |
| final boolean excludeOsgi = jars.stream() |
| .anyMatch(it -> it.getName().startsWith("org.osgi.") || it.getName().startsWith("osgi.")); |
| final boolean hasWinegrower = jars.stream() |
| .anyMatch(it -> it.getName().startsWith("winegrower-core")); |
| if (excludeOsgi) { |
| // add build-common |
| final File buildCommon = Files.toFile(parent.getResource("org/apache/winegrower/extension/build/common/Run.class")); |
| try { |
| urls.add(buildCommon.toURI().toURL()); |
| } catch (final MalformedURLException e) { |
| throw new IllegalArgumentException(e); |
| } |
| } |
| final ClassLoader workaroundMaven = new WorkAroundMavenClassLoader(parent); |
| final ClassLoader parentLoader = !excludeOsgi || !hasWinegrower ? workaroundMaven : new IgnoreWinegrowerckClassLoader(workaroundMaven); |
| return urls.isEmpty() ? workaroundMaven : new URLClassLoader(urls.toArray(new URL[0]), parentLoader) { |
| @Override |
| public boolean equals(final Object obj) { |
| return super.equals(obj) || parent.equals(obj); |
| } |
| }; |
| } |
| |
| private Object createConfiguration(final Class<?> configClass) |
| throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { |
| final Object configuration = configClass.getConstructor().newInstance(); |
| doCall(configuration, "setUseLifecycleCallbacks", new Class<?>[]{boolean.class}, new Object[]{useLifecycleCallbacks}); |
| ofNullable(lifecycleCallbacks).map(it -> it.stream() |
| .map(clazz -> { |
| try { |
| return clazz |
| .getConstructor() |
| .newInstance(); |
| } catch (final InstantiationException | NoSuchMethodException | IllegalAccessException e) { |
| throw new IllegalArgumentException(e); |
| } catch (final InvocationTargetException e) { |
| throw new IllegalArgumentException( |
| e.getTargetException()); |
| } |
| }) |
| .collect(toList())) |
| .ifPresent(value -> doCall(configuration, "setLifecycleCallbacks", new Class<?>[]{List.class}, new Object[]{value})); |
| ofNullable(workDir) |
| .ifPresent(value -> doCall(configuration, "setWorkDir", new Class<?>[]{File.class}, new Object[]{value})); |
| ofNullable(prioritizedBundles) |
| .filter(it -> !it.isEmpty()) |
| .map(it -> it.stream().filter(v -> !"empty".equals(v)).collect(toList())) |
| .ifPresent(value -> doCall(configuration, "setPrioritizedBundles", new Class<?>[]{List.class}, new Object[]{value})); |
| ofNullable(ignoredBundles) |
| .ifPresent(value -> doCall(configuration, "setIgnoredBundles", new Class<?>[]{Collection.class}, new Object[]{value})); |
| ofNullable(scanningIncludes) |
| .ifPresent(value -> doCall(configuration, "setScanningIncludes", new Class<?>[]{Collection.class}, new Object[]{value})); |
| ofNullable(scanningExcludes) |
| .ifPresent(value -> doCall(configuration, "setScanningExcludes", new Class<?>[]{Collection.class}, new Object[]{value})); |
| ofNullable(manifestContributors) |
| .filter(it -> !it.isEmpty()) |
| .ifPresent(contributors -> { |
| try { |
| final Class<?> type = Thread.currentThread().getContextClassLoader().loadClass( |
| "org.apache.winegrower.scanner.manifest.ManifestContributor"); |
| final Collection<?> value = contributors.stream() |
| .filter(it -> !"empty".equals(it)) |
| .map(clazz -> { |
| try { |
| return Thread.currentThread() |
| .getContextClassLoader() |
| .loadClass(clazz) |
| .getConstructor() |
| .newInstance(); |
| } catch (final InstantiationException | NoSuchMethodException | IllegalAccessException | ClassNotFoundException e) { |
| throw new IllegalArgumentException(e); |
| } catch (final InvocationTargetException e) { |
| throw new IllegalArgumentException( |
| e.getTargetException()); |
| } |
| }) |
| .map(type::cast) |
| .collect(toList()); |
| doCall(configuration, "setManifestContributors", new Class<?>[]{Collection.class}, new Object[]{value}); |
| } catch (final ClassNotFoundException e) { |
| throw new IllegalArgumentException(e); |
| } |
| }); |
| ofNullable(jarFilter) |
| .map(String::valueOf) |
| .filter(it -> !it.isEmpty()) |
| .ifPresent(filter -> { |
| try { |
| final Predicate<String> predicate = (Predicate<String>) Thread.currentThread() |
| .getContextClassLoader().loadClass(filter).getConstructor().newInstance(); |
| doCall(configuration, "setJarFilter", new Class<?>[]{Predicate.class}, new Object[]{predicate}); |
| } catch (final InstantiationException | NoSuchMethodException | IllegalAccessException | ClassNotFoundException e) { |
| throw new IllegalArgumentException(e); |
| } catch (final InvocationTargetException e) { |
| throw new IllegalArgumentException(e.getTargetException()); |
| } |
| }); |
| return configuration; |
| } |
| |
| private static void doCall(final Object instance, final String mtd, final Class<?>[] paramTypes, final Object[] args) { |
| try { |
| instance.getClass().getMethod(mtd, paramTypes).invoke(instance, args); |
| } catch (final InvocationTargetException ite) { |
| final Throwable targetException = ite.getTargetException(); |
| if (RuntimeException.class.isInstance(targetException)) { |
| throw RuntimeException.class.cast(targetException); |
| } |
| throw new IllegalStateException(targetException); |
| } catch (final Exception ex) { |
| if (RuntimeException.class.isInstance(ex)) { |
| throw RuntimeException.class.cast(ex); |
| } |
| throw new IllegalStateException(ex); |
| } |
| } |
| |
| private static class WorkAroundMavenClassLoader extends ClassLoader { |
| private WorkAroundMavenClassLoader(final ClassLoader parent) { |
| super(parent); |
| } |
| |
| @Override |
| public Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException { |
| if (name.startsWith("javax.")) { |
| final String sub = name.substring("javax.".length()); |
| if (sub.startsWith("enterprise.") || sub.startsWith("decorator.")) { // maven has an outdated cdi-api |
| throw new ClassNotFoundException(name); |
| } |
| } |
| return getParent().loadClass(name); |
| } |
| } |
| |
| private static class IgnoreWinegrowerckClassLoader extends ClassLoader { |
| private IgnoreWinegrowerckClassLoader(final ClassLoader workaroundMaven) { |
| super(workaroundMaven); |
| } |
| |
| @Override |
| protected Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException { |
| if (name.startsWith("org.")) { |
| final String sub = name.substring("org.".length()); |
| if (sub.startsWith("osgi.")) { |
| throw new ClassNotFoundException(name); |
| } else if (sub.startsWith("apache.")) { |
| final String apache = sub.substring("apache.".length()); |
| if (apache.startsWith("winegrower.")) { |
| throw new ClassNotFoundException(name); |
| } |
| if (apache.startsWith("xbean.")) { |
| throw new ClassNotFoundException(name); |
| } |
| } |
| } |
| return super.loadClass(name, resolve); |
| } |
| } |
| } |