blob: f81e84383d477ff36a39cb3d5b261f060fed14b5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.maven.plugins.assembly.io;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.io.input.XmlStreamReader;
import org.apache.maven.plugins.assembly.AssemblerConfigurationSource;
import org.apache.maven.plugins.assembly.InvalidAssemblerConfigurationException;
import org.apache.maven.plugins.assembly.interpolation.AssemblyExpressionEvaluator;
import org.apache.maven.plugins.assembly.interpolation.AssemblyInterpolator;
import org.apache.maven.plugins.assembly.model.Assembly;
import org.apache.maven.plugins.assembly.model.Component;
import org.apache.maven.plugins.assembly.model.ContainerDescriptorHandlerConfig;
import org.apache.maven.plugins.assembly.model.DependencySet;
import org.apache.maven.plugins.assembly.model.FileItem;
import org.apache.maven.plugins.assembly.model.FileSet;
import org.apache.maven.plugins.assembly.model.ModuleSet;
import org.apache.maven.plugins.assembly.model.io.xpp3.AssemblyXpp3Reader;
import org.apache.maven.plugins.assembly.model.io.xpp3.AssemblyXpp3Writer;
import org.apache.maven.plugins.assembly.model.io.xpp3.ComponentXpp3Reader;
import org.apache.maven.plugins.assembly.resolved.AssemblyId;
import org.apache.maven.plugins.assembly.utils.InterpolationConstants;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.interpolation.PrefixAwareRecursionInterceptor;
import org.codehaus.plexus.interpolation.RecursionInterceptor;
import org.codehaus.plexus.interpolation.fixed.FixedStringSearchInterpolator;
import org.codehaus.plexus.interpolation.fixed.InterpolationState;
import org.codehaus.plexus.interpolation.fixed.PrefixedObjectValueSource;
import org.codehaus.plexus.interpolation.fixed.PrefixedPropertiesValueSource;
import org.codehaus.plexus.util.DirectoryScanner;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
*/
@Singleton
@Named
public class DefaultAssemblyReader implements AssemblyReader {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultAssemblyReader.class);
public static FixedStringSearchInterpolator createProjectInterpolator(MavenProject project) {
// CHECKSTYLE_OFF: LineLength
return FixedStringSearchInterpolator.create(
new PrefixedPropertiesValueSource(
InterpolationConstants.PROJECT_PROPERTIES_PREFIXES, project.getProperties(), true),
new PrefixedObjectValueSource(InterpolationConstants.PROJECT_PREFIXES, project, true));
// CHECKSTYLE_ON: LineLength
}
@Override
public List<Assembly> readAssemblies(final AssemblerConfigurationSource configSource)
throws AssemblyReadException, InvalidAssemblerConfigurationException {
final Locator locator = new Locator();
final List<LocatorStrategy> strategies = new ArrayList<>();
strategies.add(new RelativeFileLocatorStrategy(configSource.getBasedir()));
strategies.add(new FileLocatorStrategy());
final List<LocatorStrategy> refStrategies = new ArrayList<>();
refStrategies.add(new PrefixedClasspathLocatorStrategy("/assemblies/"));
final List<Assembly> assemblies = new ArrayList<>();
final String[] descriptors = configSource.getDescriptors();
final String[] descriptorRefs = configSource.getDescriptorReferences();
final File descriptorSourceDirectory = configSource.getDescriptorSourceDirectory();
final List<Assembly> inlineDescriptors = configSource.getInlineDescriptors();
if ((descriptors != null) && (descriptors.length > 0)) {
locator.setStrategies(strategies);
for (String descriptor1 : descriptors) {
LOGGER.info("Reading assembly descriptor: " + descriptor1);
addAssemblyFromDescriptor(descriptor1, locator, configSource, assemblies);
}
}
if ((descriptorRefs != null) && (descriptorRefs.length > 0)) {
locator.setStrategies(refStrategies);
for (String descriptorRef : descriptorRefs) {
addAssemblyForDescriptorReference(descriptorRef, configSource, assemblies);
}
}
if ((descriptorSourceDirectory != null) && descriptorSourceDirectory.isDirectory()) {
locator.setStrategies(
Collections.singletonList(new RelativeFileLocatorStrategy(descriptorSourceDirectory)));
final DirectoryScanner scanner = new DirectoryScanner();
scanner.setBasedir(descriptorSourceDirectory);
scanner.setIncludes(new String[] {"**/*.xml"});
scanner.addDefaultExcludes();
scanner.scan();
final String[] paths = scanner.getIncludedFiles();
for (String path : paths) {
addAssemblyFromDescriptor(path, locator, configSource, assemblies);
}
}
if (inlineDescriptors != null) {
assemblies.addAll(inlineDescriptors);
}
if (assemblies.isEmpty()) {
if (configSource.isIgnoreMissingDescriptor()) {
LOGGER.debug("Ignoring missing assembly descriptors per configuration. "
+ "See messages above for specifics.");
} else {
throw new AssemblyReadException("No assembly descriptors found.");
}
}
// check unique IDs
final Set<String> ids = new HashSet<>();
for (final Assembly assembly : assemblies) {
if (!ids.add(assembly.getId())) {
LOGGER.warn("The assembly id " + assembly.getId() + " is used more than once.");
}
}
return assemblies;
}
@Override
public Assembly getAssemblyForDescriptorReference(final String ref, final AssemblerConfigurationSource configSource)
throws AssemblyReadException, InvalidAssemblerConfigurationException {
return addAssemblyForDescriptorReference(ref, configSource, new ArrayList<>(1));
}
@Override
public Assembly getAssemblyFromDescriptorFile(final File file, final AssemblerConfigurationSource configSource)
throws AssemblyReadException, InvalidAssemblerConfigurationException {
return addAssemblyFromDescriptorFile(file, configSource, new ArrayList<>(1));
}
private Assembly addAssemblyForDescriptorReference(
final String ref, final AssemblerConfigurationSource configSource, final List<Assembly> assemblies)
throws AssemblyReadException, InvalidAssemblerConfigurationException {
final InputStream resourceAsStream = getClass().getResourceAsStream("/assemblies/" + ref + ".xml");
if (resourceAsStream == null) {
if (configSource.isIgnoreMissingDescriptor()) {
LOGGER.debug("Ignoring missing assembly descriptor with ID '" + ref + "' per configuration.");
return null;
} else {
throw new AssemblyReadException("Descriptor with ID '" + ref + "' not found");
}
}
try (Reader reader =
XmlStreamReader.builder().setInputStream(resourceAsStream).get()) {
final Assembly assembly = readAssembly(reader, ref, null, configSource);
assemblies.add(assembly);
return assembly;
} catch (final IOException e) {
throw new AssemblyReadException("Problem with descriptor with ID '" + ref + "'", e);
}
}
private Assembly addAssemblyFromDescriptorFile(
final File descriptor, final AssemblerConfigurationSource configSource, final List<Assembly> assemblies)
throws AssemblyReadException, InvalidAssemblerConfigurationException {
if (!descriptor.exists()) {
if (configSource.isIgnoreMissingDescriptor()) {
LOGGER.debug("Ignoring missing assembly descriptor: '" + descriptor + "' per configuration.");
return null;
} else {
throw new AssemblyReadException("Descriptor: '" + descriptor + "' not found");
}
}
try (Reader r = XmlStreamReader.builder().setFile(descriptor).get()) {
final Assembly assembly =
readAssembly(r, descriptor.getAbsolutePath(), descriptor.getParentFile(), configSource);
assemblies.add(assembly);
return assembly;
} catch (final IOException e) {
throw new AssemblyReadException("Error reading assembly descriptor: " + descriptor, e);
}
}
private Assembly addAssemblyFromDescriptor(
final String spec,
final Locator locator,
final AssemblerConfigurationSource configSource,
final List<Assembly> assemblies)
throws AssemblyReadException, InvalidAssemblerConfigurationException {
final Location location = locator.resolve(spec);
if (location == null) {
if (configSource.isIgnoreMissingDescriptor()) {
LOGGER.debug("Ignoring missing assembly descriptor with ID '" + spec
+ "' per configuration.\nLocator output was:\n\n"
+ locator.getMessageHolder().render());
return null;
} else {
throw new AssemblyReadException("Error locating assembly descriptor: " + spec + "\n\n"
+ locator.getMessageHolder().render());
}
}
try (Reader r = XmlStreamReader.builder()
.setInputStream(location.getInputStream())
.get()) {
File dir = null;
if (location.getFile() != null) {
dir = location.getFile().getParentFile();
}
final Assembly assembly = readAssembly(r, spec, dir, configSource);
assemblies.add(assembly);
return assembly;
} catch (final IOException e) {
throw new AssemblyReadException("Error reading assembly descriptor: " + spec, e);
}
}
public Assembly readAssembly(
Reader reader,
final String locationDescription,
final File assemblyDir,
final AssemblerConfigurationSource configSource)
throws AssemblyReadException, InvalidAssemblerConfigurationException {
Assembly assembly;
final MavenProject project = configSource.getProject();
try {
InterpolationState is = new InterpolationState();
final RecursionInterceptor interceptor =
new PrefixAwareRecursionInterceptor(InterpolationConstants.PROJECT_PREFIXES, true);
is.setRecursionInterceptor(interceptor);
FixedStringSearchInterpolator interpolator =
AssemblyInterpolator.fullInterpolator(project, createProjectInterpolator(project), configSource);
AssemblyXpp3Reader.ContentTransformer transformer =
AssemblyInterpolator.assemblyInterpolator(interpolator, is, LOGGER);
final AssemblyXpp3Reader r = new AssemblyXpp3Reader(transformer);
assembly = r.read(reader);
ComponentXpp3Reader.ContentTransformer ctrans =
AssemblyInterpolator.componentInterpolator(interpolator, is, LOGGER);
mergeComponentsWithMainAssembly(assembly, assemblyDir, configSource, ctrans);
debugPrintAssembly("After assembly is interpolated:", assembly);
AssemblyInterpolator.checkErrors(AssemblyId.createAssemblyId(assembly), is, LOGGER);
} catch (final IOException | XmlPullParserException e) {
throw new AssemblyReadException(
"Error reading descriptor: " + locationDescription + ": " + e.getMessage(), e);
}
if (assembly.isIncludeSiteDirectory()) {
includeSiteInAssembly(assembly, configSource);
}
return assembly;
}
private void debugPrintAssembly(final String message, final Assembly assembly) {
final StringWriter sWriter = new StringWriter();
try {
new AssemblyXpp3Writer().write(sWriter, assembly);
} catch (final IOException e) {
LOGGER.debug("Failed to print debug message with assembly descriptor listing, and message: " + message, e);
}
LOGGER.debug(message + "\n\n" + sWriter + "\n\n");
}
/**
* Add the contents of all included components to main assembly
*
* @param assembly the assembly
* @param assemblyDir the assembly directory
* @param transformer the component interpolator
* @throws AssemblyReadException
*/
protected void mergeComponentsWithMainAssembly(
final Assembly assembly,
final File assemblyDir,
final AssemblerConfigurationSource configSource,
ComponentXpp3Reader.ContentTransformer transformer)
throws AssemblyReadException {
final Locator locator = new Locator();
if (assemblyDir != null && assemblyDir.exists() && assemblyDir.isDirectory()) {
locator.addStrategy(new RelativeFileLocatorStrategy(assemblyDir));
}
// allow absolute paths in componentDescriptor... MASSEMBLY-486
locator.addStrategy(new RelativeFileLocatorStrategy(configSource.getBasedir()));
locator.addStrategy(new FileLocatorStrategy());
locator.addStrategy(new ClasspathResourceLocatorStrategy());
final AssemblyExpressionEvaluator aee = new AssemblyExpressionEvaluator(configSource);
final List<String> componentLocations = assembly.getComponentDescriptors();
for (String location : componentLocations) {
// allow expressions in path to component descriptor... MASSEMBLY-486
try {
location = aee.evaluate(location).toString();
} catch (final Exception eee) {
LOGGER.error("Error interpolating componentDescriptor: " + location, eee);
}
final Location resolvedLocation = locator.resolve(location);
if (resolvedLocation == null) {
throw new AssemblyReadException("Failed to locate component descriptor: " + location);
}
Component component = null;
try (Reader reader = new InputStreamReader(resolvedLocation.getInputStream())) {
component = new ComponentXpp3Reader(transformer).read(reader);
} catch (final IOException | XmlPullParserException e) {
throw new AssemblyReadException(
"Error reading component descriptor: " + location + " (resolved to: "
+ resolvedLocation.getSpecification() + ")",
e);
}
mergeComponentWithAssembly(component, assembly);
}
}
/**
* Add the content of a single Component to main assembly
*
* @param component The component
* @param assembly The assembly
*/
protected void mergeComponentWithAssembly(final Component component, final Assembly assembly) {
final List<ContainerDescriptorHandlerConfig> containerHandlerDescriptors =
component.getContainerDescriptorHandlers();
for (final ContainerDescriptorHandlerConfig cfg : containerHandlerDescriptors) {
assembly.addContainerDescriptorHandler(cfg);
}
final List<DependencySet> dependencySetList = component.getDependencySets();
for (final DependencySet dependencySet : dependencySetList) {
assembly.addDependencySet(dependencySet);
}
final List<FileSet> fileSetList = component.getFileSets();
for (final FileSet fileSet : fileSetList) {
assembly.addFileSet(fileSet);
}
final List<FileItem> fileList = component.getFiles();
for (final FileItem fileItem : fileList) {
assembly.addFile(fileItem);
}
final List<ModuleSet> moduleSets = component.getModuleSets();
for (final ModuleSet moduleSet : moduleSets) {
assembly.addModuleSet(moduleSet);
}
}
@Override
public void includeSiteInAssembly(final Assembly assembly, final AssemblerConfigurationSource configSource)
throws InvalidAssemblerConfigurationException {
final File siteDirectory = configSource.getSiteDirectory();
if (!siteDirectory.exists()) {
throw new InvalidAssemblerConfigurationException("site did not exist in the target directory - "
+ "please run site:site before creating the assembly");
}
LOGGER.info("Adding site directory to assembly : " + siteDirectory);
final FileSet siteFileSet = new FileSet();
siteFileSet.setDirectory(siteDirectory.getPath());
siteFileSet.setOutputDirectory("/site");
assembly.addFileSet(siteFileSet);
}
}