| /* |
| * 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.sling.feature.maven.mojos; |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.io.Writer; |
| import java.net.URL; |
| import java.nio.charset.StandardCharsets; |
| import java.nio.file.Files; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Calendar; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import java.util.jar.JarEntry; |
| import java.util.jar.JarInputStream; |
| import java.util.jar.Manifest; |
| import java.util.regex.Pattern; |
| import java.util.stream.Collectors; |
| |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.felix.utils.manifest.Clause; |
| import org.apache.felix.utils.manifest.Parser; |
| import org.apache.maven.archiver.MavenArchiveConfiguration; |
| import org.apache.maven.archiver.MavenArchiver; |
| import org.apache.maven.execution.MavenSession; |
| import org.apache.maven.model.License; |
| import org.apache.maven.model.Model; |
| import org.apache.maven.model.Scm; |
| import org.apache.maven.model.building.ModelBuilder; |
| import org.apache.maven.model.building.ModelBuildingRequest; |
| import org.apache.maven.plugin.MojoExecutionException; |
| import org.apache.maven.plugin.MojoFailureException; |
| import org.apache.maven.plugin.logging.Log; |
| import org.apache.maven.plugins.annotations.Component; |
| import org.apache.maven.plugins.annotations.LifecyclePhase; |
| import org.apache.maven.plugins.annotations.Mojo; |
| import org.apache.maven.plugins.annotations.Parameter; |
| import org.apache.maven.plugins.annotations.ResolutionScope; |
| import org.apache.maven.project.MavenProject; |
| import org.apache.maven.repository.RepositorySystem; |
| import org.apache.maven.scm.ScmException; |
| import org.apache.maven.scm.ScmFileSet; |
| import org.apache.maven.scm.ScmTag; |
| import org.apache.maven.scm.ScmVersion; |
| import org.apache.maven.scm.command.checkout.CheckOutScmResult; |
| import org.apache.maven.scm.manager.NoSuchScmProviderException; |
| import org.apache.maven.scm.manager.ScmManager; |
| import org.apache.maven.scm.repository.ScmRepository; |
| import org.apache.maven.scm.repository.ScmRepositoryException; |
| import org.apache.maven.shared.utils.io.DirectoryScanner; |
| import org.apache.maven.shared.utils.io.FileUtils; |
| import org.apache.maven.shared.utils.logging.MessageUtils; |
| import org.apache.sling.feature.Artifact; |
| import org.apache.sling.feature.ArtifactId; |
| import org.apache.sling.feature.ExecutionEnvironmentExtension; |
| import org.apache.sling.feature.Feature; |
| import org.apache.sling.feature.builder.ArtifactProvider; |
| import org.apache.sling.feature.extension.apiregions.api.ApiExport; |
| import org.apache.sling.feature.extension.apiregions.api.ApiRegion; |
| import org.apache.sling.feature.extension.apiregions.api.ApiRegions; |
| import org.apache.sling.feature.io.IOUtils; |
| import org.apache.sling.feature.maven.mojos.apis.ApisJarContext; |
| import org.apache.sling.feature.maven.mojos.apis.ApisJarContext.ArtifactInfo; |
| import org.apache.sling.feature.maven.mojos.apis.ApisUtil; |
| import org.apache.sling.feature.maven.mojos.apis.ArtifactType; |
| import org.apache.sling.feature.maven.mojos.apis.DirectorySource; |
| import org.apache.sling.feature.maven.mojos.apis.FileSource; |
| import org.apache.sling.feature.maven.mojos.apis.JavadocExecutor; |
| import org.apache.sling.feature.maven.mojos.apis.JavadocLinks; |
| import org.apache.sling.feature.maven.mojos.apis.spi.Processor; |
| import org.apache.sling.feature.maven.mojos.apis.spi.ProcessorContext; |
| import org.apache.sling.feature.maven.mojos.apis.spi.Source; |
| import org.codehaus.plexus.archiver.UnArchiver; |
| import org.codehaus.plexus.archiver.jar.JarArchiver; |
| import org.codehaus.plexus.archiver.manager.ArchiverManager; |
| import org.codehaus.plexus.archiver.manager.NoSuchArchiverException; |
| import org.codehaus.plexus.archiver.util.DefaultFileSet; |
| import org.codehaus.plexus.components.io.fileselectors.FileSelector; |
| import org.codehaus.plexus.components.io.fileselectors.IncludeExcludeFileSelector; |
| import org.osgi.framework.Constants; |
| |
| /** |
| * Generates the APIs JARs for the selected feature files. |
| */ |
| @Mojo(name = "apis-jar", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true) |
| public class ApisJarMojo extends AbstractIncludingFeatureMojo { |
| |
| /** |
| * Select the features for api generation. Separate api jars will be generated |
| * for each feature. |
| */ |
| @Parameter |
| private FeatureSelectionConfig selection; |
| |
| /** |
| * Patterns identifying which resources to include from bundles. This can be |
| * used to include files like license or notices files. Starting with version |
| * 1.2.0 these files are only searched in the folders mentioned by |
| * {@code #resourceFolders} |
| */ |
| @Parameter |
| private String[] includeResources; |
| |
| /** |
| * Names of the regions to include, by default all regions are included. |
| */ |
| @Parameter(defaultValue = "*") |
| private Set<String> includeRegions; |
| |
| /** |
| * Names of the regions to exclude, by default no regions is excluded. |
| */ |
| @Parameter |
| private Set<String> excludeRegions; |
| |
| /** |
| * List of javadoc links used in the javadoc generation. |
| */ |
| @Parameter |
| private String[] javadocLinks; |
| |
| /** |
| * Ignore errors in javadoc generation |
| */ |
| @Parameter(defaultValue = "false") |
| private boolean ignoreJavadocErrors; |
| |
| /** |
| * If set to true and api jars are created for more than one region, then the |
| * higher region only gets the difference to the lower region. If set to false |
| * each api jar gets the full region information (duplicating information) |
| */ |
| @Parameter(defaultValue = "true") |
| private boolean incrementalApis; |
| |
| /** |
| * Additional resources for the api jar |
| */ |
| @Parameter |
| private List<File> apiResources; |
| |
| /** |
| * Additional resources for the api source jar |
| */ |
| @Parameter |
| private List<File> apiSourceResources; |
| |
| /** |
| * Additional resources for the api javadoc jar |
| */ |
| @Parameter |
| private List<File> apiJavadocResources; |
| |
| /** |
| * If enabled, the created api jars will be attached to the project |
| */ |
| @Parameter(defaultValue = "true") |
| private boolean attachApiJars; |
| |
| /** |
| * Mapping for an api region name to a user defined name |
| */ |
| @Parameter |
| private Map<String, String> apiRegionNameMappings; |
| |
| /** |
| * Mapping for the feature classifier to a user defined name |
| */ |
| @Parameter |
| private Map<String, String> apiClassifierMappings; |
| |
| /** |
| * Generate api jar |
| */ |
| @Parameter(defaultValue = "true") |
| private boolean generateApiJar; |
| |
| /** |
| * Generate the sources jar |
| */ |
| @Parameter(defaultValue = "true") |
| private boolean generateSourceJar; |
| |
| /** |
| * Generate the javadoc jar |
| */ |
| @Parameter(defaultValue = "true") |
| private boolean generateJavadocJar; |
| |
| /** |
| * Source level for javadoc generation |
| */ |
| @Parameter(defaultValue = "8") |
| private String javadocSourceLevel; |
| |
| /** |
| * Optional version to be put into the manifest of the created jars |
| * |
| * @since 1.2.0 |
| */ |
| @Parameter |
| private String apiVersion; |
| |
| /** |
| * Comma separated list of folders where files are renamed. |
| * |
| * @since 1.2.0 |
| */ |
| @Parameter(defaultValue = "META-INF,SLING-INF") |
| private String resourceFolders; |
| |
| /** |
| * Create a license report file. This is the name of that file within the jar |
| * |
| * @since 1.2.0 |
| */ |
| @Parameter |
| private String licenseReport; |
| |
| /** |
| * A artifact patterns to match artifacts without a license. Follows the pattern |
| * "groupId:artifactId:type:classifier:version". After the patter a "=" followed |
| * by the license information needs to be specified. This information is used in |
| * the license report if no license is specified for an artifact. |
| * |
| * @since 1.2.0 |
| */ |
| @Parameter |
| private List<String> licenseDefaults; |
| |
| /** |
| * Header added on top of the license report |
| * |
| * @since 1.2.0 |
| */ |
| @Parameter(defaultValue = "This archive contains files from the following artifacts:") |
| private String licenseReportHeader; |
| |
| /** |
| * Footer added at the bottom of the license report |
| * |
| * @since 1.2.0 |
| */ |
| @Parameter |
| private String licenseReportFooter; |
| |
| /** |
| * If enabled, packages from artifacts which are fully consumed (all public api) |
| * are omitted from the api and source jars and a dependency list is generated |
| * instead. |
| * |
| * @since 1.3.0 |
| */ |
| @Parameter(defaultValue = "false") |
| private boolean useApiDependencies; |
| |
| /** |
| * Comma separated list of Maven repository lists. If set, and |
| * {@link #useApiDependencies} is enabled, then one of the listed repositories |
| * must provide the artifact. |
| * |
| * @since 1.3.0 |
| */ |
| @Parameter |
| private String apiRepositoryUrls; |
| |
| /** |
| * Fail the build if errors are detected. For example, errors could be missing |
| * packages in the api jars, or too many packages in those jars. |
| * |
| * @since 1.3.2 |
| */ |
| @Parameter(defaultValue = "false") |
| private boolean failOnError; |
| |
| /** |
| * specify the manifest properties values that you need to replace in the |
| * Manifest file. |
| * |
| * @since 1.3.2 |
| */ |
| @Parameter |
| private final Properties manifestProperties = new Properties(); |
| |
| /** |
| * Fail the build if sources are mising for javadoc generation |
| * |
| * @since 1.3.6 |
| */ |
| @Parameter(defaultValue = "false") |
| private boolean failOnMissingSourcesForJavadoc; |
| |
| /** |
| * Whether the index should be generated |
| * |
| * @since 1.3.6 |
| */ |
| @Parameter(defaultValue = "true") |
| private boolean javadocIndex; |
| |
| /** |
| * Whether the tree should be generated |
| * |
| * @since 1.3.6 |
| */ |
| @Parameter(defaultValue = "true") |
| private boolean javadocTree; |
| |
| /** |
| * A artifact patterns to match artifacts put on the javadoc classpath. Follows |
| * the pattern "groupId:artifactId:type:classifier:version". Any matching |
| * artifact is removed from the classpath. Removals are processed first. |
| * |
| * @since 1.3.14 |
| */ |
| @Parameter |
| private List<String> javadocClasspathRemovals; |
| |
| /** |
| * A artifact patterns to match artifacts put on the javadoc classpath. Follows |
| * the pattern "groupId:artifactId:type:classifier:version". From the matching |
| * artifacts, only the highest version is kept per artifact. This rule is |
| * applied after the removals. |
| * |
| * @since 1.3.14 |
| */ |
| @Parameter |
| private List<String> javadocClasspathHighestVersions; |
| |
| /** |
| * A artifact patterns to match artifacts put on the javadoc classpath. Follows |
| * the pattern "groupId:artifactId:type:classifier:version". Any matching |
| * artifact is put at the top of the classpath. This rule is applied last. |
| * |
| * @since 1.3.14 |
| */ |
| @Parameter |
| private List<String> javadocClasspathTops; |
| |
| /** |
| * A comma separated list of enabled toggles used as input to generate the api |
| * jars. |
| * |
| * @since 1.5.0 |
| */ |
| @Parameter(property = "enabled.toggles") |
| private String enabledToggles; |
| |
| @Parameter(defaultValue = "${project.build.directory}/apis-jars", readonly = true) |
| private File mainOutputDir; |
| |
| @Component(hint = "default") |
| private ModelBuilder modelBuilder; |
| |
| @Component |
| private ScmManager scmManager; |
| |
| @Component |
| private ArchiverManager archiverManager; |
| |
| @Component |
| private RepositorySystem repositorySystem; |
| |
| private final Pattern pomPropertiesPattern = Pattern.compile("META-INF/maven/[^/]+/[^/]+/pom.properties"); |
| |
| /** Artifact Provider. */ |
| private final ArtifactProvider artifactProvider = new BaseArtifactProvider(); |
| |
| @Override |
| public void execute() throws MojoExecutionException, MojoFailureException { |
| checkPreconditions(); |
| |
| getLog().debug("Retrieving feature files..."); |
| final Collection<Feature> features = this.getSelectedFeatures(selection).values(); |
| |
| if (features.isEmpty()) { |
| getLog().info( |
| "There are no associated feature files in the current project, plugin execution will be skipped"); |
| } else { |
| getLog().debug("Starting APIs JARs creation..."); |
| |
| for (final Feature feature : features) { |
| onFeature(feature); |
| } |
| } |
| } |
| |
| /** |
| * Check if the region is included |
| * |
| * @param name The region name |
| * @return {@code true} if the region is included |
| */ |
| private boolean isRegionIncluded(final String name) { |
| boolean included = false; |
| for (final String i : this.includeRegions) { |
| if ("*".equals(i) || i.equals(name)) { |
| included = true; |
| break; |
| } |
| } |
| if (included && this.excludeRegions != null) { |
| for (final String e : this.excludeRegions) { |
| if (name.equals(e)) { |
| included = false; |
| break; |
| } |
| } |
| } |
| |
| return included; |
| } |
| |
| /** |
| * Get the api regions for a feature If the feature does not have an api region |
| * an artificial global region is returned. |
| * |
| * @param feature The feature |
| * @return The api regions or {@code null} if the feature is wrongly configured |
| * or all regions are excluded |
| * @throws MojoExecutionException If an error occurs |
| */ |
| private ApiRegions getApiRegions(final Feature feature) throws MojoExecutionException { |
| ApiRegions regions = new ApiRegions(); |
| |
| final ApiRegions sourceRegions; |
| try { |
| sourceRegions = ApiRegions.getApiRegions(feature); |
| } catch (final IllegalArgumentException iae) { |
| throw new MojoExecutionException(iae.getMessage(), iae); |
| } |
| if (sourceRegions != null) { |
| // calculate all api-regions first, taking the inheritance in account |
| for (final ApiRegion r : sourceRegions.listRegions()) { |
| if (r.getParent() != null && !this.incrementalApis) { |
| for (final ApiExport exp : r.getParent().listExports()) { |
| r.add(exp); |
| } |
| } |
| if (isRegionIncluded(r.getName())) { |
| getLog().debug("API Region " + r.getName() |
| + " will not processed due to the configured include/exclude list"); |
| regions.add(r); |
| } |
| } |
| |
| if (regions.isEmpty()) { |
| getLog().info("Feature file " + feature.getId().toMvnId() |
| + " has no included api regions, no API JAR will be created"); |
| regions = null; |
| } |
| } else { |
| // create exports on the fly |
| regions.add(new ApiRegion(ApiRegion.GLOBAL) { |
| |
| @Override |
| public ApiExport getExportByName(final String name) { |
| ApiExport exp = super.getExportByName(name); |
| if (exp == null) { |
| exp = new ApiExport(name); |
| this.add(exp); |
| } |
| return exp; |
| } |
| }); |
| } |
| |
| return regions; |
| } |
| |
| /** |
| * Create api jars for a feature |
| */ |
| private void onFeature(final Feature feature) throws MojoExecutionException { |
| getLog().info(MessageUtils.buffer().a("Creating API JARs for Feature ").strong(feature.getId().toMvnId()) |
| .a(" ...").toString()); |
| |
| final ApiRegions regions = getApiRegions(feature); |
| if (regions == null) { |
| // wrongly configured api regions - skip execution, info is logged already so we |
| // can just return |
| return; |
| } |
| |
| // create an output directory per feature |
| final File featureDir = new File(mainOutputDir, feature.getId().getArtifactId()); |
| final ApisJarContext ctx = new ApisJarContext(this.mainOutputDir, feature, regions); |
| ctx.getConfig().setLicenseDefaults(this.licenseDefaults); |
| ctx.getConfig().setLicenseReport(this.licenseReport); |
| ctx.getConfig().setLicenseReportHeader(this.licenseReportHeader); |
| ctx.getConfig().setLicenseReportFooter(this.licenseReportFooter); |
| ctx.getConfig().setJavadocLinks(this.javadocLinks); |
| ctx.getConfig().setJavadocClasspathRemovals(this.javadocClasspathRemovals); |
| ctx.getConfig().setJavadocClasspathHighestVersions(this.javadocClasspathHighestVersions); |
| ctx.getConfig().setJavadocClasspathTops(this.javadocClasspathTops); |
| ctx.getConfig().setApiVersion(this.apiVersion); |
| ctx.getConfig().setJavadocSourceLevel(this.javadocSourceLevel); |
| ctx.getConfig().setBundleResourceFolders(this.resourceFolders); |
| ctx.getConfig().setBundleResources(this.includeResources); |
| ctx.getConfig().setClassifierMappings(apiClassifierMappings); |
| ctx.getConfig().setRegionMappings(apiRegionNameMappings); |
| ctx.getConfig().setManifestEntries(manifestProperties); |
| ctx.getConfig().logConfiguration(getLog()); |
| ctx.getConfig().setEnabledToggles(this.enabledToggles); |
| |
| ctx.setDependencyRepositories(this.apiRepositoryUrls); |
| |
| // for each bundle included in the feature file and record directories |
| for (final Artifact artifact : feature.getBundles()) { |
| onArtifact(ctx, artifact); |
| } |
| |
| if (this.generateSourceJar || this.generateJavadocJar) { |
| getLog().info("--------------------------------------------------------"); |
| getLog().info("Used sources:"); |
| for (final ArtifactInfo info : ctx.getArtifactInfos()) { |
| if (info.getSources().isEmpty()) { |
| getLog().info("- ".concat(info.getId().toMvnId()).concat(" : NO SOURCES FOUND")); |
| } else { |
| getLog().info( |
| "- ".concat(info.getId().toMvnId()).concat(" : ").concat(info.getSources().toString())); |
| } |
| } |
| getLog().info("--------------------------------------------------------"); |
| } |
| |
| boolean hasErrors = false; |
| |
| // recollect and package stuff per region |
| for (final ApiRegion apiRegion : regions.listRegions()) { |
| final List<String> report = new ArrayList<>(); |
| |
| final File regionDir = new File(featureDir, apiRegion.getName()); |
| |
| if (generateApiJar) { |
| final Collection<ArtifactInfo> infos = ctx.getArtifactInfos(apiRegion, this.useApiDependencies); |
| this.runProcessor(ctx, apiRegion, ArtifactType.APIS, this.apiResources, infos); |
| final File apiJar = createArchive(ctx, apiRegion, ArtifactType.APIS, this.apiResources, infos, report); |
| report(ctx, apiJar, ArtifactType.APIS, apiRegion, this.useApiDependencies, report, null); |
| } |
| |
| if (generateSourceJar) { |
| final Collection<ArtifactInfo> infos = ctx.getArtifactInfos(apiRegion, this.useApiDependencies); |
| this.runProcessor(ctx, apiRegion, ArtifactType.SOURCES, this.apiResources, infos); |
| final File sourceJar = createArchive(ctx, apiRegion, ArtifactType.SOURCES, this.apiSourceResources, infos, report); |
| report(ctx, sourceJar, ArtifactType.SOURCES, apiRegion, this.useApiDependencies, report, null); |
| } else if ( generateJavadocJar ) { |
| final Collection<ArtifactInfo> infos = ctx.getArtifactInfos(apiRegion, false); |
| this.runProcessor(ctx, apiRegion, ArtifactType.SOURCES, this.apiResources, infos); |
| } |
| |
| if (this.useApiDependencies && (this.generateApiJar || this.generateSourceJar)) { |
| this.createDependenciesFile(ctx, apiRegion); |
| } |
| |
| if (generateJavadocJar) { |
| final File javadocsDir = new File(regionDir, ArtifactType.JAVADOC.getId()); |
| final ExecutionEnvironmentExtension ext = ExecutionEnvironmentExtension |
| .getExecutionEnvironmentExtension(feature); |
| final JavadocLinks links = new JavadocLinks(); |
| links.calculateLinks(ctx.getConfig().getJavadocLinks(), ctx.getArtifactInfos(apiRegion, false), |
| ext != null ? ext.getFramework() : null); |
| |
| final Collection<ArtifactInfo> infos = generateJavadoc(ctx, apiRegion, links, javadocsDir); |
| if (infos != null) { |
| ctx.setJavadocDir(javadocsDir); |
| final File javadocJar = createArchive(ctx, apiRegion, ArtifactType.JAVADOC, |
| this.apiJavadocResources, infos, report); |
| report(ctx, javadocJar, ArtifactType.JAVADOC, apiRegion, false, report, links); |
| } else { |
| getLog().warn("Javadoc JAR will NOT be generated - sources directory " + ctx.getDeflatedSourcesDir() |
| + " was empty or contained no Java files!"); |
| } |
| } |
| |
| final ArtifactId reportId = this.buildArtifactId(ctx, apiRegion, ArtifactType.REPORT); |
| final File reportFile = new File(mainOutputDir, reportId.toMvnName()); |
| if (!report.isEmpty()) { |
| report.stream().forEach(v -> getLog().info(v)); |
| try { |
| Files.write(reportFile.toPath(), report); |
| } catch (final IOException e) { |
| throw new MojoExecutionException("Unable to write " + reportFile, e); |
| } |
| hasErrors = true; |
| } else { |
| if (reportFile.exists()) { |
| reportFile.delete(); |
| } |
| } |
| } |
| |
| if (hasErrors && this.failOnError) { |
| throw new MojoExecutionException("API generation has errors, please see report files for more information"); |
| } |
| |
| getLog().info(MessageUtils.buffer().a("APIs JARs for Feature ").debug(feature.getId().toMvnId()) |
| .a(" succesfully created").toString()); |
| } |
| |
| private void report(final ApisJarContext ctx, final File jarFile, final ArtifactType artifactType, |
| final ApiRegion apiRegion, final boolean omitDependencyArtifacts, final List<String> report, |
| final JavadocLinks links) throws MojoExecutionException { |
| final Map.Entry<Set<String>, Set<String>> packageResult = getPackages(ctx, jarFile, |
| artifactType.getContentExtension()); |
| final Set<String> apiPackages = packageResult.getKey(); |
| final Set<String> otherPackages = packageResult.getValue(); |
| if (omitDependencyArtifacts) { |
| for (final ArtifactInfo info : ctx.getArtifactInfos(apiRegion, false)) { |
| if (info.isUseAsDependencyPerRegion(apiRegion)) { |
| for (final Clause c : info.getUsedExportedPackages(apiRegion)) { |
| apiPackages.add(c.getName()); |
| } |
| } |
| } |
| } |
| // make sure no reports for packages not containing java classes |
| otherPackages.addAll(ctx.getPackagesWithoutJavaClasses()); |
| // ignore packages without sources for javadoc? |
| if (artifactType == ArtifactType.JAVADOC && !failOnMissingSourcesForJavadoc) { |
| otherPackages.addAll(ctx.getPackagesWithoutSources()); |
| } |
| // add packages found in links |
| if (links != null) { |
| apiPackages.addAll(links.getLinkedPackages()); |
| } |
| final List<ApiExport> missing = new ArrayList<>(); |
| |
| for (final ApiExport exp : apiRegion.listExports()) { |
| final String packageName = exp.getName(); |
| if (!apiPackages.remove(packageName) && !otherPackages.remove(packageName)) { |
| missing.add(exp); |
| } |
| } |
| // correct remaining packages |
| if (links != null) { |
| apiPackages.removeAll(links.getLinkedPackages()); |
| } |
| if (artifactType == ArtifactType.JAVADOC) { |
| otherPackages.removeAll(ctx.getPackagesWithoutSources()); |
| } |
| otherPackages.removeAll(ctx.getPackagesWithoutJavaClasses()); |
| |
| apiPackages.addAll(otherPackages); |
| if (artifactType == ArtifactType.JAVADOC) { |
| // jquery might be part of javadoc |
| final Collection<String> jqueryPackages = Arrays.asList("jquery", "jquery.external.jquery", "jquery.images", |
| "jquery.jszip-utils.dist", "jquery.jszip.dist", "resources"); |
| apiPackages.removeAll(jqueryPackages); |
| } |
| |
| if (missing.isEmpty() && apiPackages.isEmpty()) { |
| getLog().info("Verified " + artifactType.getId() + " jar for region " + apiRegion.getName()); |
| } else { |
| Collections.sort(missing); |
| report.add(artifactType.getId().concat(" jar for region ").concat(apiRegion.getName()).concat(" has ") |
| .concat(String.valueOf(missing.size() + apiPackages.size())).concat(" errors:")); |
| for (final ApiExport exp : missing) { |
| final List<String> candidates = new ArrayList<>(); |
| for (final ArtifactInfo info : ctx.getArtifactInfos()) { |
| for (final Clause clause : info.getUsedExportedPackages(apiRegion)) { |
| if (exp.getName().equals(clause.getName())) { |
| candidates.add(info.getId().toMvnName()); |
| break; |
| } |
| } |
| } |
| report.add("- Missing package ".concat(exp.getName()).concat(" from bundle(s) ") |
| .concat(String.join(",", candidates))); |
| } |
| for (final String m : apiPackages) { |
| report.add("- Wrong package ".concat(m)); |
| } |
| } |
| } |
| |
| private File getArtifactFile(final ArtifactProvider artifactProvider, final ArtifactId artifactId) |
| throws MojoExecutionException { |
| final URL artifactURL = retrieve(artifactProvider, artifactId); |
| if (artifactURL == null) { |
| throw new MojoExecutionException("Unable to find artifact " + artifactId.toMvnId()); |
| } |
| File bundleFile = null; |
| try { |
| bundleFile = IOUtils.getFileFromURL(artifactURL, true, this.getTmpDir()); |
| } catch (IOException e) { |
| throw new MojoExecutionException(e.getMessage()); |
| } |
| |
| return bundleFile; |
| } |
| |
| private Manifest getManifest(final ArtifactId artifactId, final File bundleFile) throws MojoExecutionException { |
| try (JarInputStream jis = new JarInputStream(new FileInputStream(bundleFile))) { |
| getLog().debug("Reading Manifest headers from bundle " + bundleFile); |
| |
| final Manifest manifest = jis.getManifest(); |
| |
| if (manifest == null) { |
| throw new MojoExecutionException("Artifact + " + artifactId.toMvnId() + " does not have a manifest."); |
| } |
| return manifest; |
| } catch (final IOException e) { |
| throw new MojoExecutionException("An error occurred while reading manifest from file " + bundleFile |
| + " for artifact " + artifactId.toMvnId(), e); |
| } |
| } |
| |
| private boolean calculateOmitDependenciesFlag(final ApiRegion region, final Clause[] exportedPackageClauses, |
| final Set<Clause> usedExportedPackagesPerRegion) { |
| // check whether all packages are exported in this region |
| boolean fullUsage = true; |
| for (final Clause c : exportedPackageClauses) { |
| boolean found = false; |
| for (final Clause current : usedExportedPackagesPerRegion) { |
| if (current.getName().equals(c.getName())) { |
| found = true; |
| break; |
| } |
| } |
| |
| if (!found) { |
| fullUsage = false; |
| break; |
| } |
| // check deprecation - if deprecation is set, artifact can't be used as a |
| // dependency |
| final ApiExport exp = region.getAllExportByName(c.getName()); |
| if (exp != null && exp.getDeprecation() != null) { |
| fullUsage = false; |
| break; |
| } |
| } |
| |
| return fullUsage; |
| } |
| |
| /** |
| * Process a single artifact. This is a "global" processing and not per region |
| * |
| * @param ctx The context |
| * @param artifact The artifact |
| * @throws MojoExecutionException |
| */ |
| private void onArtifact(final ApisJarContext ctx, Artifact artifact) throws MojoExecutionException { |
| final File bundleFile = getArtifactFile(artifactProvider, artifact.getId()); |
| |
| final Manifest manifest = getManifest(artifact.getId(), bundleFile); |
| |
| // check if the bundle is exporting packages? |
| final Clause[] exportedPackageClauses = this.getExportedPackages(manifest); |
| if (exportedPackageClauses.length > 0) { |
| |
| // calculate the exported packages in the manifest file for all regions |
| final Set<String> usedExportedPackages = computeUsedExportPackages(ctx, exportedPackageClauses, artifact); |
| |
| if (!usedExportedPackages.isEmpty()) { |
| // check for previous version of artifact due to toggles |
| ArtifactId previous = null; |
| for (final String pckName : usedExportedPackages) { |
| for (final ApiRegion region : ctx.getApiRegions().listRegions()) { |
| final ApiExport exp = region.getExportByName(pckName); |
| if (exp != null) { |
| if (exp.getToggle() != null |
| && !ctx.getConfig().getEnabledToggles().contains(exp.getToggle()) |
| && exp.getPrevious() != null) { |
| if (previous != null && previous.compareTo(exp.getPrevious()) != 0) { |
| throw new MojoExecutionException( |
| "More than one previous version artifact configured for " |
| + artifact.getId().toMvnId() + " : " + previous.toMvnId() + ", " |
| + exp.getPrevious().toMvnId()); |
| } |
| previous = exp.getPrevious(); |
| } |
| break; |
| } |
| } |
| } |
| if (previous != null) { |
| final Artifact previousArtifact = new Artifact(previous); |
| previousArtifact.getMetadata().putAll(artifact.getMetadata()); |
| getLog().debug("Using " + previous.toMvnId() + " instead of " + artifact.getId().toMvnId() |
| + " due to disabled toggle(s)"); |
| artifact = previousArtifact; |
| } |
| |
| final ArtifactInfo info = ctx.addArtifactInfo(artifact); |
| info.setUsedExportedPackages(usedExportedPackages); |
| |
| // calculate per region packages |
| for (final ApiRegion region : ctx.getApiRegions().listRegions()) { |
| final Set<Clause> usedExportedPackagesPerRegion = computeUsedExportPackages(region, |
| exportedPackageClauses, artifact); |
| |
| // check whether packages are included in api jars - or added as a dependency |
| boolean useAsDependency = this.useApiDependencies |
| ? calculateOmitDependenciesFlag(region, exportedPackageClauses, |
| usedExportedPackagesPerRegion) |
| : false; |
| if (useAsDependency) { |
| useAsDependency = ctx.findDependencyArtifact(getLog(), info); |
| if (useAsDependency) { |
| // check scm info |
| if (artifact.getMetadata().get(ApisUtil.SCM_LOCATION) != null) { |
| throw new MojoExecutionException("Dependency artifact must not specify " |
| + ApisUtil.SCM_LOCATION + " : " + artifact.getId().toMvnId()); |
| } |
| } |
| } |
| info.setUsedExportedPackages(region, usedExportedPackagesPerRegion, useAsDependency); |
| } |
| |
| info.setBinDirectory(new File(ctx.getDeflatedBinDir(), info.getId().toMvnName())); |
| info.setSourceDirectory(new File(ctx.getDeflatedSourcesDir(), info.getId().toMvnName())); |
| |
| final boolean skipBinDeflate = info.getBinDirectory().exists(); |
| if (skipBinDeflate) { |
| getLog().debug("Artifact " + info.getId().toMvnName() + " already deflated"); |
| } |
| final boolean skipSourceDeflate = info.getSourceDirectory().exists(); |
| if (skipSourceDeflate) { |
| getLog().debug("Source for artifact " + info.getId().toMvnName() + " already deflated"); |
| } |
| |
| final String bundleClassPath = manifest.getMainAttributes().getValue(Constants.BUNDLE_CLASSPATH); |
| final String[] embeddedBundles; |
| if (bundleClassPath != null && !bundleClassPath.isEmpty()) { |
| embeddedBundles = bundleClassPath.split(","); |
| } else { |
| embeddedBundles = null; |
| } |
| |
| processBinary(ctx, info, bundleFile, artifact, embeddedBundles, skipBinDeflate, skipSourceDeflate); |
| |
| // check if the bundle wraps other bundles |
| if (embeddedBundles != null) { |
| computeWrappedBundles(ctx, info, embeddedBundles, skipBinDeflate, skipSourceDeflate); |
| } |
| |
| postProcessArtifact(ctx, info, artifact); |
| |
| if (!info.getSourceDirectory().exists()) { |
| info.setSourceDirectory(null); |
| } |
| |
| if (generateJavadocJar) { |
| ApisUtil.buildJavadocClasspath(getLog(), repositorySystem, mavenSession, artifact.getId()) |
| .forEach(ctx::addJavadocClasspath); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Post process |
| * <ul> |
| * <li>Find node types |
| * <li>Find empty packages |
| * <li>Find empty directories and remove them |
| * <li>Clean up sources - if encoding is not UTF-8 |
| * </ul> |
| * |
| * @param ctx The context |
| * @param info The artifact info |
| * @throws MojoExecutionException |
| */ |
| private void postProcessArtifact(final ApisJarContext ctx, final ArtifactInfo info, final Artifact artifact) |
| throws MojoExecutionException { |
| // binary post processing |
| this.postProcessBinDirectory(ctx, info, info.getBinDirectory(), ""); |
| |
| // source post processing |
| if ((generateSourceJar || generateJavadocJar)) { |
| final Set<String> foundPackages = new HashSet<>(); |
| if (info.getSourceDirectory() != null && info.getSourceDirectory().exists()) { |
| final String encoding = artifact.getMetadata().getOrDefault(ApisUtil.SCM_ENCODING, "UTF-8"); |
| this.postProcessSourcesDirectory(ctx, info, foundPackages, info.getSourceDirectory(), |
| "UTF-8".equals(encoding) ? null : encoding, ""); |
| } |
| // check for missing packages |
| for (final String pck : info.getUsedExportedPackages()) { |
| if (!foundPackages.contains(pck)) { |
| // We need to record this kind of packages and ensure we don't trigger warnings |
| // for them |
| // when checking the api jars for correctness. |
| getLog().debug("No sources found in " + pck); |
| ctx.getPackagesWithoutSources().add(pck); |
| } |
| } |
| } |
| |
| } |
| |
| private void postProcessBinDirectory(final ApisJarContext ctx, final ArtifactInfo info, final File dir, |
| final String pck) { |
| boolean hasJavaFile = false; |
| for (final File child : dir.listFiles()) { |
| if (child.isDirectory()) { |
| postProcessBinDirectory(ctx, info, child, |
| pck.isEmpty() ? child.getName() : pck.concat(".").concat(child.getName())); |
| } else if (child.getName().endsWith(ArtifactType.APIS.getContentExtension())) { |
| hasJavaFile = true; |
| } |
| } |
| if (dir.listFiles().length == 0 && !pck.isEmpty()) { |
| // empty dir -> remove |
| dir.delete(); |
| } else if (!hasJavaFile && info.getUsedExportedPackages().contains(pck)) { |
| |
| // We need to record this kind of packages and ensure we don't trigger warnings |
| // for them |
| // when checking the api jars for correctness. |
| getLog().debug("No classes found in " + pck); |
| ctx.getPackagesWithoutJavaClasses().add(pck); |
| } |
| } |
| |
| /** |
| * Process a binary Extract the binary, rename resources and (optional) download |
| * the sources |
| * |
| * @param ctx The context |
| * @param info The current artifact |
| * @param binFile The binary to extract |
| * @param binArtifact The artifact to extract |
| * @param embeddedBundles Embedded bundles (optional) |
| * @param skipBinDeflate Flag to skip deflating the binary |
| * @param skipSourceDeflate Flag to skip deflating the source |
| * @throws MojoExecutionException |
| */ |
| private void processBinary(final ApisJarContext ctx, final ArtifactInfo info, final File binFile, |
| final Artifact binArtifact, final String[] embeddedBundles, final boolean skipBinDeflate, |
| final boolean skipSourceDeflate) throws MojoExecutionException { |
| if (!skipBinDeflate) { |
| // deflate all bundles first, in order to copy APIs and resources later, |
| // depending to the region |
| final List<String> deflateIncludes = new ArrayList<>(); |
| |
| // add all used exported packages |
| deflateIncludes.addAll(Arrays.asList(info.getUsedExportedPackageIncludes())); |
| // add embedded bundles |
| if (embeddedBundles != null) { |
| for (final String jarName : embeddedBundles) { |
| if (!".".equals(jarName)) { |
| deflateIncludes.add(jarName); |
| } |
| } |
| } |
| // add resources from the folders |
| deflateIncludes.addAll(getIncludeResourcePatterns(ctx, info.getId())); |
| |
| // deflate |
| this.deflate(info.getBinDirectory(), binFile, deflateIncludes.toArray(new String[deflateIncludes.size()])); |
| |
| } |
| // renaming potential name-collapsing resources |
| this.renameResources(ctx, info, binArtifact.getId()); |
| |
| // download sources |
| if (this.generateSourceJar || this.generateJavadocJar) { |
| if (!skipSourceDeflate) { |
| this.downloadSources(ctx, info, binArtifact); |
| } else { |
| info.addSourceInfo("USE CACHE FROM PREVIOUS BUILD"); |
| } |
| } |
| |
| } |
| |
| private List<String> getIncludeResourcePatterns(final ApisJarContext ctx, final ArtifactId id) { |
| final List<String> pattern = new ArrayList<>(); |
| for (final String folder : ctx.getConfig().getBundleResourceFolders()) { |
| for (final String inc : ctx.getConfig().getBundleResources()) { |
| pattern.add(folder.concat("/").concat(inc)); |
| } |
| } |
| |
| // add NOTICE and LICENSE for license report |
| if (ctx.getConfig().getLicenseReport() != null) { |
| final String licenseDefault = ctx.getConfig().getLicenseDefault(id); |
| if (licenseDefault == null || !licenseDefault.isEmpty()) { |
| pattern.add("META-INF/NOTICE"); |
| pattern.add("META-INF/LICENSE"); |
| } |
| } |
| |
| return pattern; |
| } |
| |
| private void postProcessSourcesDirectory(final ApisJarContext ctx, final ArtifactInfo info, |
| final Set<String> foundPackages, final File dir, final String readEncoding, final String pck) |
| throws MojoExecutionException { |
| boolean hasSourceFile = false; |
| for (final File child : dir.listFiles()) { |
| if (child.isDirectory()) { |
| postProcessSourcesDirectory(ctx, info, foundPackages, child, readEncoding, |
| pck.isEmpty() ? child.getName() : pck.concat(".").concat(child.getName())); |
| } else if (child.getName().endsWith(ArtifactType.SOURCES.getContentExtension())) { |
| hasSourceFile = true; |
| if (readEncoding != null) { |
| try { |
| final String javaSource = FileUtils.fileRead(child, readEncoding); |
| FileUtils.fileWrite(child, StandardCharsets.UTF_8.name(), javaSource); |
| } catch (final IOException ioe) { |
| throw new MojoExecutionException("Unable to clean up java source " + child, ioe); |
| } |
| } |
| } |
| } |
| if (dir.listFiles().length == 0 && !pck.isEmpty()) { |
| // empty dir -> remove |
| dir.delete(); |
| } else if (hasSourceFile) { |
| foundPackages.add(pck); |
| } |
| } |
| |
| private void computeWrappedBundles(final ApisJarContext ctx, final ArtifactInfo info, |
| final String[] embeddedBundles, final boolean skipBinDeflate, final boolean skipSourceDeflate) |
| throws MojoExecutionException { |
| for (final String jarName : embeddedBundles) { |
| if (".".equals(jarName)) { |
| continue; |
| } |
| |
| final File wrappedJar = new File(info.getBinDirectory(), jarName); |
| getLog().debug("Processing wrapped bundle " + wrappedJar); |
| |
| final Properties properties = new Properties(); |
| |
| try (final JarInputStream jis = new JarInputStream(new FileInputStream(wrappedJar))) { |
| JarEntry jarEntry = null; |
| while ((jarEntry = jis.getNextJarEntry()) != null) { |
| if (!jarEntry.isDirectory() && pomPropertiesPattern.matcher(jarEntry.getName()).matches()) { |
| getLog().debug("Loading Maven GAV from " + wrappedJar + '!' + jarEntry.getName()); |
| properties.load(jis); |
| break; |
| } |
| jis.closeEntry(); |
| } |
| } catch (IOException e) { |
| throw new MojoExecutionException("An error occurred while processing wrapped bundle " + wrappedJar, e); |
| } |
| |
| if (properties.isEmpty()) { |
| getLog().warn("No Maven GAV info attached to wrapped bundle " + wrappedJar + ", it will be ignored"); |
| } else { |
| getLog().debug("Handling synthetic artifacts from Maven GAV: " + properties); |
| |
| String groupId = properties.getProperty("groupId"); |
| String artifactId = properties.getProperty("artifactId"); |
| String version = properties.getProperty("version"); |
| String classifier = properties.getProperty("classifier"); |
| if (classifier == null) { |
| classifier = inferClassifier(jarName, artifactId, version); |
| } |
| |
| Artifact syntheticArtifact = new Artifact( |
| new ArtifactId(groupId, artifactId, version, classifier, null)); |
| final File bundleFile = getArtifactFile(artifactProvider, syntheticArtifact.getId()); |
| |
| processBinary(ctx, info, bundleFile, syntheticArtifact, null, skipBinDeflate, skipSourceDeflate); |
| } |
| } |
| } |
| |
| // Guess the classifier based on the file name |
| String inferClassifier(String bundleName, String artifactId, String version) { |
| if (bundleName == null || artifactId == null || version == null) |
| return null; |
| |
| int idx = bundleName.lastIndexOf('/'); |
| if (idx >= 0) |
| bundleName = bundleName.substring(idx + 1); |
| |
| int edx = bundleName.lastIndexOf('.'); |
| if (edx > 0) |
| bundleName = bundleName.substring(0, edx); |
| |
| // bundleName is now the bare name without extension |
| String synthesized = artifactId + "-" + version; |
| if (synthesized.length() < bundleName.length() && bundleName.startsWith(synthesized)) { |
| String suffix = bundleName.substring(synthesized.length()); |
| if (suffix.length() > 1 && suffix.startsWith("-")) { |
| String classifier = suffix.substring(1); |
| getLog().debug( |
| "Inferred classifier of '" + artifactId + ":" + version + "' to be '" + classifier + "'"); |
| return classifier; |
| } |
| } |
| return null; |
| } |
| |
| private URL retrieve(final ArtifactProvider artifactProvider, final ArtifactId artifactId) { |
| getLog().debug("Retrieving artifact " + artifactId + "..."); |
| URL sourceFile = artifactProvider.provide(artifactId); |
| if (sourceFile != null) { |
| getLog().debug("Artifact " + artifactId + " successfully retrieved : " + sourceFile); |
| } |
| return sourceFile; |
| } |
| |
| private void deflate(final File destDirectory, final File artifact, final String... includes) |
| throws MojoExecutionException { |
| getLog().debug("Deflating artifact " + artifact.getName() + "..."); |
| destDirectory.mkdirs(); |
| |
| // unarchive the bundle |
| try { |
| final UnArchiver unArchiver = archiverManager.getUnArchiver(artifact); |
| unArchiver.setSourceFile(artifact); |
| unArchiver.setDestDirectory(destDirectory); |
| final IncludeExcludeFileSelector selector = new IncludeExcludeFileSelector(); |
| selector.setIncludes(includes); |
| unArchiver.setFileSelectors(new FileSelector[] { selector }); |
| unArchiver.setOverwrite(false); |
| unArchiver.extract(); |
| } catch (final NoSuchArchiverException e) { |
| throw new MojoExecutionException( |
| "An error occurred while deflating file " + artifact + " to directory " + destDirectory, e); |
| } |
| |
| getLog().debug("Artifact " + artifact + " successfully deflated"); |
| } |
| |
| private void renameResources(final ApisJarContext ctx, final ArtifactInfo info, final ArtifactId artifactId) |
| throws MojoExecutionException { |
| final List<String> patterns = getIncludeResourcePatterns(ctx, info.getId()); |
| if (patterns.isEmpty()) { |
| getLog().debug("No configured resources to rename in " + info.getBinDirectory()); |
| } |
| |
| getLog().debug("Renaming " + patterns + " files in " + info.getBinDirectory() + "..."); |
| |
| final DirectoryScanner directoryScanner = new DirectoryScanner(); |
| directoryScanner.setBasedir(info.getBinDirectory()); |
| directoryScanner.setIncludes(patterns.toArray(new String[patterns.size()])); |
| directoryScanner.scan(); |
| |
| if (directoryScanner.getIncludedFiles().length == 0) { |
| getLog().debug("No " + patterns + " resources in " + info.getBinDirectory() + " to be renamed found."); |
| return; |
| } |
| |
| for (final String resourceName : directoryScanner.getIncludedFiles()) { |
| final File resource = new File(info.getBinDirectory(), resourceName); |
| |
| String includedName = resourceName.replace(File.separatorChar, '/'); |
| if (!info.getIncludedResources().contains(resource)) { |
| final String prefix = artifactId.toMvnName().concat("-"); |
| |
| if (resource.getName().startsWith(prefix)) { |
| getLog().debug("No need to rename " + resource); |
| info.getIncludedResources().add(resource); |
| } else { |
| File renamed = new File(resource.getParentFile(), prefix.concat(resource.getName())); |
| |
| getLog().debug("Renaming resource " + resource + " to " + renamed + "..."); |
| |
| if (resource.renameTo(renamed)) { |
| getLog().debug("Resource renamed to " + renamed); |
| info.getIncludedResources().add(renamed); |
| } else { |
| throw new MojoExecutionException("Impossible to rename resource " + resource + " to " + renamed |
| + ", please check the current user has enough rights on the File System"); |
| } |
| final int lastSlash = includedName.lastIndexOf('/'); |
| if (lastSlash == -1) { |
| includedName = renamed.getName(); |
| } else { |
| includedName = includedName.substring(0, lastSlash + 1).concat(renamed.getName()); |
| } |
| } |
| } |
| if (includedName.endsWith(ArtifactType.CND.getContentExtension())) { |
| info.getNodeTypes().add(includedName); |
| } |
| } |
| |
| getLog().debug(patterns + " resources in " + info.getBinDirectory() + " successfully renamed"); |
| } |
| |
| private boolean downloadSourceAndDeflate(final ApisJarContext ctx, final ArtifactInfo info, |
| final ArtifactId sourcesArtifactId, final boolean allowFallback) throws MojoExecutionException { |
| boolean failed = false; |
| try { |
| final URL url = retrieve(artifactProvider, sourcesArtifactId); |
| if (url != null) { |
| File sourcesBundle = IOUtils.getFileFromURL(url, true, null); |
| deflate(info.getSourceDirectory(), sourcesBundle, info.getUsedExportedPackageIncludes()); |
| } else { |
| if (!allowFallback) { |
| throw new MojoExecutionException("Unable to download sources for " + info.getId().toMvnId() |
| + " due to missing artifact " + sourcesArtifactId.toMvnId()); |
| } |
| getLog().warn("Unable to download sources for " + info.getId().toMvnId() + " due to missing artifact " |
| + sourcesArtifactId.toMvnId() + ", trying source checkout next..."); |
| failed = true; |
| } |
| } catch (final MojoExecutionException mee) { |
| throw mee; |
| } catch (final Throwable t) { |
| if (!allowFallback) { |
| throw new MojoExecutionException("Unable to download sources for " + info.getId().toMvnId() |
| + " due to missing artifact " + sourcesArtifactId.toMvnId()); |
| } |
| getLog().warn("Unable to download sources for " + info.getId().toMvnId() + " from " |
| + sourcesArtifactId.toMvnId() + " due to " + t.getMessage() + ", trying source checkout next..."); |
| failed = true; |
| } |
| return failed; |
| } |
| |
| private void downloadSources(final ApisJarContext ctx, final ArtifactInfo info, final Artifact artifact) |
| throws MojoExecutionException { |
| getLog().debug("Downloading sources for " + artifact.getId().toMvnId() + "..."); |
| |
| ApisUtil.validateSourceInfo(artifact); |
| |
| final List<ArtifactId> scmIds = ApisUtil.getSourceIds(artifact); |
| final String scmLocation = artifact.getMetadata().get(ApisUtil.SCM_LOCATION); |
| if (scmIds != null) { |
| for (final ArtifactId sourcesArtifactId : scmIds) { |
| downloadSourceAndDeflate(ctx, info, sourcesArtifactId, false); |
| info.addSourceInfo(sourcesArtifactId); |
| } |
| } else if (scmLocation != null) { |
| final String connection = checkoutSourcesFromSCM(ctx, info, artifact); |
| info.addSourceInfo(connection); |
| } else { |
| String sourceClassifier = artifact.getMetadata().get(ApisUtil.SCM_CLASSIFIER); |
| if (sourceClassifier == null) { |
| sourceClassifier = "sources"; // default |
| } |
| final ArtifactId sourcesArtifactId = artifact.getId().changeClassifier(sourceClassifier).changeType("jar"); |
| if (downloadSourceAndDeflate(ctx, info, sourcesArtifactId, |
| artifact.getMetadata().get(ApisUtil.SCM_CLASSIFIER) == null)) { |
| final String connection = checkoutSourcesFromSCM(ctx, info, artifact); |
| info.addSourceInfo(connection); |
| } else { |
| info.addSourceInfo(sourcesArtifactId); |
| } |
| } |
| } |
| |
| private Model getArtifactPom(final ApisJarContext ctx, final ArtifactId artifactId) throws MojoExecutionException { |
| final ArtifactId pomArtifactId = artifactId.changeClassifier(null).changeType("pom"); |
| // check model cache |
| Model model = ctx.getModelCache().get(pomArtifactId); |
| if (model == null) { |
| getLog().debug("Retrieving POM " + pomArtifactId.toMvnId() + "..."); |
| // POM file must exist, let the plugin fail otherwise |
| final URL pomURL = retrieve(artifactProvider, pomArtifactId); |
| if (pomURL == null) { |
| throw new MojoExecutionException("Unable to find artifact " + pomArtifactId.toMvnId()); |
| } |
| |
| File pomFile = null; |
| try { |
| pomFile = IOUtils.getFileFromURL(pomURL, true, null); |
| } catch (IOException e) { |
| throw new MojoExecutionException(e.getMessage()); |
| } |
| getLog().debug("POM " + pomArtifactId.toMvnId() + " successfully retrieved, reading the model..."); |
| |
| // read model |
| model = modelBuilder.buildRawModel(pomFile, ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL, false).get(); |
| getLog().debug("POM model " + pomArtifactId.toMvnId() + " successfully read"); |
| |
| // cache model |
| ctx.getModelCache().put(pomArtifactId, model); |
| } |
| return model; |
| } |
| |
| private String checkoutSourcesFromSCM(final ApisJarContext ctx, final ArtifactInfo info, |
| final Artifact sourceArtifact) throws MojoExecutionException { |
| // fallback to Artifacts SCM metadata first |
| String connection = sourceArtifact.getMetadata().get(ApisUtil.SCM_LOCATION); |
| String tag = sourceArtifact.getMetadata().get(ApisUtil.SCM_TAG); |
| |
| // Artifacts SCM metadata may not available or are an override, let's fallback |
| // to the POM |
| getLog().debug("Falling back to SCM checkout..."); |
| final Model pomModel = getArtifactPom(ctx, sourceArtifact.getId()); |
| getLog().debug("Processing SCM info from pom..."); |
| |
| final Scm scm = pomModel.getScm(); |
| if (scm != null) { |
| if (connection == null) { |
| connection = scm.getConnection(); |
| } |
| if (tag == null) { |
| tag = scm.getTag(); |
| } |
| } |
| |
| if (connection == null) { |
| getLog().warn("Ignoring sources for artifact " + sourceArtifact.getId().toMvnId() + " : SCM not defined in " |
| + sourceArtifact.getId().toMvnId() + " bundle neither in " + pomModel.getId() + " POM file."); |
| return null; |
| } |
| |
| try { |
| ScmRepository repository = scmManager.makeScmRepository(connection); |
| |
| ScmVersion scmVersion = null; |
| if (tag != null) { |
| scmVersion = new ScmTag(tag); |
| } |
| |
| File basedir = new File(ctx.getCheckedOutSourcesDir(), sourceArtifact.getId().toMvnName()); |
| if (basedir.exists()) { |
| getLog().debug("Source checkout directory " + basedir + " already exists"); |
| } else { |
| getLog().debug("Checking out source to directory " + basedir); |
| basedir.mkdirs(); |
| ScmFileSet fileSet = new ScmFileSet(basedir); |
| |
| CheckOutScmResult result = null; |
| try { |
| if (scmVersion == null) { |
| result = scmManager.checkOut(repository, fileSet, true); |
| } else { |
| result = scmManager.checkOut(repository, fileSet, scmVersion, true); |
| } |
| } catch (ScmException se) { |
| throw new MojoExecutionException("An error occurred while checking sources from " + repository |
| + " for artifact " + sourceArtifact.getId().toMvnId() + " model", se); |
| } |
| |
| if (!result.isSuccess()) { |
| getLog().warn("Ignoring sources for artifact " + sourceArtifact.getId().toMvnId() |
| + " : An error occurred while checking out sources from " + connection + ": " |
| + result.getProviderMessage()); |
| return null; |
| } |
| } |
| |
| // retrieve the exact pom location to detect the bundle path in the repo |
| DirectoryScanner pomScanner = new DirectoryScanner(); |
| pomScanner.setBasedir(basedir); |
| pomScanner.setIncludes("**/pom.xml"); |
| pomScanner.scan(); |
| for (String pomFileLocation : pomScanner.getIncludedFiles()) { |
| final File pomFile = new File(basedir, pomFileLocation); |
| final Model model = modelBuilder |
| .buildRawModel(pomFile, ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL, false).get(); |
| |
| if (sourceArtifact.getId().getArtifactId().equals(model.getArtifactId())) { |
| basedir = pomFile.getParentFile(); |
| break; |
| } |
| } |
| |
| // copy all interested sources to the proper location |
| File javaSources = new File(basedir, "src/main/java"); |
| if (!javaSources.exists()) { // old modules could still use src/java |
| javaSources = new File(basedir, "src/java"); |
| |
| // there could be just resources artifacts |
| if (!javaSources.exists()) { |
| getLog().warn( |
| "Ignoring sources for artifact " + sourceArtifact.getId().toMvnId() + " : SCM checkout for " |
| + sourceArtifact.getId().toMvnId() + " does not contain any source."); |
| return null; |
| } |
| } |
| |
| final File sourceDirectory = new File(ctx.getDeflatedSourcesDir(), info.getId().toMvnName()); |
| info.setSourceDirectory(sourceDirectory); |
| sourceDirectory.mkdir(); |
| |
| final DirectoryScanner directoryScanner = new DirectoryScanner(); |
| directoryScanner.setBasedir(javaSources); |
| directoryScanner.setIncludes(info.getUsedExportedPackageIncludes()); |
| directoryScanner.scan(); |
| |
| for (String file : directoryScanner.getIncludedFiles()) { |
| final File source = new File(javaSources, file); |
| final File destination = new File(sourceDirectory, file); |
| destination.getParentFile().mkdirs(); |
| try { |
| FileUtils.copyFile(source, destination); |
| } catch (IOException e) { |
| throw new MojoExecutionException( |
| "An error occurred while copying sources from " + source + " to " + destination, e); |
| } |
| } |
| |
| return tag == null ? connection : connection.concat("@").concat(tag); |
| } catch (ScmRepositoryException se) { |
| throw new MojoExecutionException("An error occurred while reading SCM from " + connection |
| + " connection for bundle " + sourceArtifact.getId(), se); |
| } catch (NoSuchScmProviderException nsspe) { |
| getLog().warn("Ignoring sources for artifact " + sourceArtifact.getId().toMvnId() |
| + " : bundle points to an SCM connection " + connection |
| + " which does not specify a valid or supported SCM provider", nsspe); |
| return null; |
| } |
| } |
| |
| private Clause[] getExportedPackages(final Manifest manifest) { |
| final String exportPackageHeader = manifest.getMainAttributes().getValue(Constants.EXPORT_PACKAGE); |
| final Clause[] exportPackages = Parser.parseHeader(exportPackageHeader); |
| |
| return exportPackages; |
| } |
| |
| /** |
| * Compute exports based on a single region |
| * |
| * @return List of packages exported by this bundle and used in the region |
| */ |
| private Set<Clause> computeUsedExportPackages(final ApiRegion apiRegion, final Clause[] exportedPackages, |
| final Artifact bundle) throws MojoExecutionException { |
| |
| final Set<Clause> result = new HashSet<>(); |
| |
| final Set<String> ignoredPackages = ApisUtil.getIgnoredPackages(bundle); |
| |
| // filter for each region |
| for (final Clause exportedPackage : exportedPackages) { |
| final String packageName = exportedPackage.getName(); |
| |
| if (!ignoredPackages.contains(packageName)) { |
| final ApiExport exp = apiRegion.getExportByName(packageName); |
| if (exp != null) { |
| result.add(exportedPackage); |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Compute exports based on all regions |
| * |
| * @return Set of packages exported by this bundle and used in any region |
| */ |
| private Set<String> computeUsedExportPackages(final ApisJarContext ctx, final Clause[] exportedPackages, |
| final Artifact bundle) throws MojoExecutionException { |
| final Set<String> result = new HashSet<>(); |
| |
| // filter for each region |
| for (final Clause exportedPackage : exportedPackages) { |
| final String packageName = exportedPackage.getName(); |
| |
| for (ApiRegion apiRegion : ctx.getApiRegions().listRegions()) { |
| final ApiExport exp = apiRegion.getExportByName(packageName); |
| if (exp != null) { |
| boolean include = true; |
| // if the package is behind a toggle, don't include it |
| if (exp.getToggle() != null && !ctx.getConfig().getEnabledToggles().contains(exp.getToggle()) |
| && exp.getPrevious() == null) { |
| include = false; |
| } |
| if (include) { |
| result.add(exportedPackage.getName()); |
| } |
| } |
| } |
| } |
| |
| // check ignored packages configuration |
| result.removeAll(ApisUtil.getIgnoredPackages(bundle)); |
| |
| return result; |
| } |
| |
| private String getApiExportClause(final ApiRegion region, final Collection<ArtifactInfo> infos) { |
| final StringBuilder sb = new StringBuilder(); |
| boolean first = true; |
| for (final ArtifactInfo info : infos) { |
| for (final Clause clause : info.getUsedExportedPackages(region)) { |
| if (first) { |
| first = false; |
| } else { |
| sb.append(','); |
| } |
| sb.append(clause.toString()); |
| } |
| } |
| return sb.toString(); |
| } |
| |
| private void addFileSets(final ApiRegion apiRegion, |
| final ArtifactType archiveType, |
| final Collection<ArtifactInfo> infos, |
| final JarArchiver jarArchiver, |
| final List<Source> sources) { |
| for (final ArtifactInfo info : infos) { |
| final File dir = archiveType == ArtifactType.APIS ? info.getBinDirectory() : info.getSourceDirectory(); |
| |
| if (dir != null) { |
| final String[] usedExportedPackageIncludes = info.getUsedExportedPackageIncludes(apiRegion); |
| getLog().debug("Adding directory " + dir.getName() + " with " |
| + Arrays.toString(usedExportedPackageIncludes)); |
| final DefaultFileSet fileSet = new DefaultFileSet(dir); |
| fileSet.setIncludingEmptyDirectories(false); |
| fileSet.setIncludes(usedExportedPackageIncludes); |
| |
| if ( jarArchiver != null ) { |
| jarArchiver.addFileSet(fileSet); |
| } |
| if ( sources != null ) { |
| sources.add(new DirectorySource(fileSet)); |
| } |
| } |
| } |
| } |
| |
| private void addResources(final Collection<ArtifactInfo> infos, |
| final List<File> resources, |
| final JarArchiver jarArchiver, |
| final List<Source> sources) { |
| for (final ArtifactInfo info : infos) { |
| final int prefixLength = info.getBinDirectory().getAbsolutePath().length() + 1; |
| for (final File resource : info.getIncludedResources()) { |
| final String name = resource.getAbsolutePath().substring(prefixLength); |
| getLog().debug("Adding resource " + name); |
| |
| if ( jarArchiver != null ) { |
| jarArchiver.addFile(resource, name); |
| |
| } |
| if ( sources != null ) { |
| sources.add(new FileSource(info.getBinDirectory(), resource)); |
| } |
| } |
| } |
| |
| // add additional resources |
| if (resources != null) { |
| for (final File rsrc : resources) { |
| getLog().debug("Adding resource " + rsrc); |
| if (rsrc.isDirectory()) { |
| DirectoryScanner ds = new DirectoryScanner(); |
| ds.setBasedir(rsrc); |
| ds.setIncludes("**/*.*"); |
| ds.scan(); |
| |
| if ( jarArchiver != null ) { |
| for (String includedFile : ds.getIncludedFiles()) { |
| jarArchiver.addFile(new File(rsrc, includedFile), includedFile); |
| } |
| } |
| if (sources != null) { |
| final DefaultFileSet fileSet = new DefaultFileSet(rsrc); |
| fileSet.setIncludingEmptyDirectories(false); |
| fileSet.setIncludes(new String[] { "**/*.*" }); |
| sources.add(new DirectorySource(fileSet)); |
| } |
| } else { |
| if ( jarArchiver != null ) { |
| jarArchiver.addFile(rsrc, rsrc.getName()); |
| } |
| if (sources != null) { |
| sources.add(new FileSource(rsrc.getParentFile(), rsrc)); |
| } |
| } |
| } |
| } |
| } |
| |
| private void runProcessor(final ApisJarContext ctx, |
| final ApiRegion apiRegion, |
| final ArtifactType archiveType, |
| final List<File> resources, |
| final Collection<ArtifactInfo> infos) { |
| final List<Processor> processors = ApisUtil.getProcessors(); |
| if ( !processors.isEmpty() ) { |
| final List<Source> sources = new ArrayList<>(); |
| |
| this.addFileSets(apiRegion, archiveType, infos, null, sources); |
| this.addResources(infos, resources, null, sources); |
| |
| // run processors |
| for (final Processor p : processors) { |
| final ProcessorContext pc = new ProcessorContext() { |
| |
| @Override |
| public MavenSession getSession() { |
| return mavenSession; |
| } |
| |
| @Override |
| public MavenProject getProject() { |
| return project; |
| } |
| |
| @Override |
| public Feature getFeature() { |
| return ctx.getFeature(); |
| } |
| |
| @Override |
| public ApiRegion getApiRegion() { |
| return apiRegion; |
| } |
| |
| @Override |
| public Log getLog() { |
| return ApisJarMojo.this.getLog(); |
| } |
| }; |
| if ( archiveType == ArtifactType.APIS ) { |
| p.processBinaries(pc, sources); |
| } else { |
| p.processSources(pc, sources); |
| } |
| } |
| } |
| } |
| |
| private File createArchive(final ApisJarContext ctx, final ApiRegion apiRegion, final ArtifactType archiveType, |
| final List<File> resources, final Collection<ArtifactInfo> infos, final List<String> report) |
| throws MojoExecutionException { |
| final JarArchiver jarArchiver = new JarArchiver(); |
| |
| if (archiveType == ArtifactType.APIS || archiveType == ArtifactType.SOURCES) { |
| // api or source |
| this.addFileSets(apiRegion, archiveType, infos, jarArchiver, null); |
| } else { |
| // javadoc |
| final DefaultFileSet fileSet = new DefaultFileSet(ctx.getJavadocDir()); |
| jarArchiver.addFileSet(fileSet); |
| } |
| |
| // add included resources |
| this.addResources(infos, resources, jarArchiver, null); |
| |
| // check for license report |
| if ( ctx.getConfig().getLicenseReport() != null ) { |
| final File out = this.createLicenseReport(ctx, apiRegion, infos, report); |
| jarArchiver.addFile(out, ctx.getConfig().getLicenseReport()); |
| } |
| |
| final ArtifactId targetId = this.buildArtifactId(ctx, apiRegion, archiveType); |
| final String artifactName = String.format("%s-%s", targetId.getArtifactId(), targetId.getClassifier()); |
| |
| MavenArchiveConfiguration archiveConfiguration = new MavenArchiveConfiguration(); |
| archiveConfiguration.setAddMavenDescriptor(false); |
| if (archiveType == ArtifactType.APIS) { |
| // APIs need OSGi Manifest entry |
| String symbolicName = artifactName.replace('-', '.'); |
| archiveConfiguration.addManifestEntry("Export-Package", getApiExportClause(apiRegion, infos)); |
| archiveConfiguration.addManifestEntry("Bundle-Description", project.getDescription()); |
| archiveConfiguration.addManifestEntry("Bundle-Version", targetId.getOSGiVersion().toString()); |
| archiveConfiguration.addManifestEntry("Bundle-ManifestVersion", "2"); |
| archiveConfiguration.addManifestEntry("Bundle-SymbolicName", symbolicName); |
| archiveConfiguration.addManifestEntry("Bundle-Name", artifactName); |
| |
| final Set<String> nodeTypes = new HashSet<>(); |
| for(final ArtifactInfo info : infos) { |
| nodeTypes.addAll(info.getNodeTypes()); |
| } |
| if (!nodeTypes.isEmpty()) { |
| archiveConfiguration.addManifestEntry("Sling-Nodetypes", String.join(",", nodeTypes)); |
| } |
| if (project.getOrganization() != null) { |
| archiveConfiguration.addManifestEntry("Bundle-Vendor", project.getOrganization().getName()); |
| } |
| |
| // add provide / require capability to make the jar unresolvable |
| archiveConfiguration.addManifestEntry("Provide-Capability", "osgi.unresolvable"); |
| archiveConfiguration.addManifestEntry("Require-Capability", "osgi.unresolvable;filter:=\"(&(must.not.resolve=*)(!(must.not.resolve=*)))\",osgi.ee;filter:=\"(&(osgi.ee=JavaSE/compact2)(version=1.8))\""); |
| } |
| archiveConfiguration.addManifestEntry("Implementation-Version", targetId.getVersion()); |
| archiveConfiguration.addManifestEntry("Specification-Version", targetId.getVersion()); |
| |
| archiveConfiguration.addManifestEntry("Implementation-Title", artifactName); |
| archiveConfiguration.addManifestEntry("Specification-Title", artifactName); |
| if (project.getOrganization() != null) { |
| archiveConfiguration.addManifestEntry("Implementation-Vendor", project.getOrganization().getName()); |
| archiveConfiguration.addManifestEntry("Specification-Vendor", project.getOrganization().getName()); |
| } |
| |
| // replace/add manifest entries with the one provided in manifestProperties configuration |
| archiveConfiguration.addManifestEntries(ctx.getConfig().getManifestEntries()); |
| |
| final File target = new File(mainOutputDir, targetId.toMvnName()); |
| MavenArchiver archiver = new MavenArchiver(); |
| archiver.setArchiver(jarArchiver); |
| archiver.setOutputFile(target); |
| |
| try { |
| archiver.createArchive(mavenSession, project, archiveConfiguration); |
| if (this.attachApiJars) { |
| projectHelper.attachArtifact(project, targetId.getType(), targetId.getClassifier(), target); |
| } |
| } catch (Exception e) { |
| throw new MojoExecutionException("An error occurred while creating APIs " |
| + target |
| +" archive", e); |
| } |
| |
| return target; |
| } |
| |
| private ArtifactId buildArtifactId(final ApisJarContext ctx, final ApiRegion apiRegion, final ArtifactType artifactType) { |
| final StringBuilder classifierBuilder = new StringBuilder(); |
| if (ctx.getFeatureId().getClassifier() != null) { |
| classifierBuilder.append(ctx.getConfig().mapApiClassifier(ctx.getFeatureId().getClassifier())) |
| .append('-'); |
| } |
| final String finalClassifier = classifierBuilder.append(ctx.getConfig().mapApiRegionName(apiRegion.getName())) |
| .append('-') |
| .append(artifactType.getId()) |
| .toString(); |
| |
| return new ArtifactId(this.project.getGroupId(), |
| this.project.getArtifactId(), |
| ctx.getConfig().getApiVersion() != null ? ctx.getConfig().getApiVersion() : this.project.getVersion(), |
| finalClassifier, |
| artifactType.getExtension()); |
| } |
| |
| /** |
| * Create the dependencies file for a region |
| * @param ctx The context |
| * @param apiRegion The region |
| */ |
| private void createDependenciesFile(final ApisJarContext ctx, final ApiRegion apiRegion) throws MojoExecutionException { |
| final Collection<ArtifactInfo> infos = ctx.getArtifactInfos(apiRegion, false); |
| |
| final List<ArtifactId> dependencies = new ArrayList<>(); |
| |
| for(final ArtifactInfo info : infos) { |
| if ( info.isUseAsDependencyPerRegion(apiRegion) ) { |
| dependencies.addAll(info.getDependencyArtifacts()); |
| } |
| } |
| Collections.sort(dependencies); |
| |
| final ArtifactId targetId = this.buildArtifactId(ctx, apiRegion, ArtifactType.DEPENDENCIES); |
| final File target = new File(mainOutputDir, targetId.toMvnName()); |
| |
| if ( !dependencies.isEmpty() ) { |
| getLog().info("Writing dependencies file ".concat(target.getAbsolutePath())); |
| try ( final Writer w = new FileWriter(target)) { |
| for(final ArtifactId id : dependencies) { |
| w.write(id.toMvnId()); |
| w.write(System.lineSeparator()); |
| } |
| } catch (final IOException e) { |
| throw new MojoExecutionException("Unable to write dependencies file", e); |
| } |
| if (this.attachApiJars) { |
| projectHelper.attachArtifact(project, targetId.getType(), targetId.getClassifier(), target); |
| } |
| } else { |
| getLog().info("No dependencies found"); |
| if ( target.exists() ) { |
| target.delete(); |
| } |
| } |
| } |
| |
| |
| /** |
| * Generate the javadoc |
| * @param ctx The generation context |
| * @param region The region |
| * @param javadocDir The output directory |
| * @return {@code true} if generation succeeded |
| * @throws MojoExecutionException on error |
| */ |
| private Collection<ArtifactInfo> generateJavadoc(final ApisJarContext ctx, final ApiRegion region, final JavadocLinks links, final File javadocDir) |
| throws MojoExecutionException { |
| final Collection<ArtifactInfo> usedInfos = new ArrayList<>(); |
| |
| final List<String> sourceDirectories = new ArrayList<>(); |
| final Set<String> javadocPackages = new HashSet<>(); |
| for(final ArtifactInfo info : ctx.getArtifactInfos(region, false)) { |
| boolean addDirectory = false; |
| for(final Clause clause : info.getUsedExportedPackages(region)) { |
| if ( !ctx.getPackagesWithoutSources().contains(clause.getName()) && !links.getLinkedPackages().contains(clause.getName())) { |
| addDirectory = true; |
| javadocPackages.add(clause.getName()); |
| } |
| } |
| if ( addDirectory && info.getSourceDirectory() != null ) { |
| usedInfos.add(info); |
| sourceDirectories.add(info.getSourceDirectory().getAbsolutePath()); |
| } |
| } |
| |
| if (javadocPackages.isEmpty()) { |
| return null; |
| } |
| |
| javadocDir.mkdirs(); |
| |
| final JavadocExecutor javadocExecutor = new JavadocExecutor(javadocDir.getParentFile()) |
| .addArgument("-public") |
| .addArgument("-encoding", false) |
| .addArgument("UTF-8") |
| .addArgument("-charset", false) |
| .addArgument("UTF-8") |
| .addArgument("-docencoding", false) |
| .addArgument("UTF-8") |
| .addArgument("-d", false) |
| .addArgument(javadocDir.getAbsolutePath()) |
| .addArgument("-sourcepath", false) |
| .addArgument(String.join(File.pathSeparator, sourceDirectories)); |
| |
| javadocExecutor.addArgument("-source", false) |
| .addArgument(ctx.getConfig().getJavadocSourceLevel()); |
| |
| final String versionSuffix = ctx.getConfig().getApiVersion() != null ? ctx.getConfig().getApiVersion() : ctx.getFeatureId().getVersion(); |
| |
| if (!StringUtils.isBlank(project.getName())) { |
| javadocExecutor.addArgument("-doctitle", false) |
| .addQuotedArgument(project.getName().trim().concat(" ").concat(versionSuffix)); |
| } |
| |
| if (!StringUtils.isBlank(project.getDescription())) { |
| javadocExecutor.addArgument("-windowtitle", false) |
| .addQuotedArgument(project.getDescription().trim().concat(" ").concat(versionSuffix)); |
| } |
| |
| if (!StringUtils.isBlank(project.getInceptionYear()) |
| && project.getOrganization() != null |
| && !StringUtils.isBlank(project.getOrganization().getName())) { |
| javadocExecutor.addArgument("-bottom", false) |
| .addQuotedArgument(String.format("Copyright © %s - %s %s. All Rights Reserved", |
| project.getInceptionYear().trim(), |
| Calendar.getInstance().get(Calendar.YEAR), |
| project.getOrganization().getName().trim())); |
| } |
| |
| if ( !links.getJavadocLinks().isEmpty()) { |
| javadocExecutor.addArguments("-link", links.getJavadocLinks()); |
| } |
| |
| // classpath |
| final Collection<String> classpath = ApisUtil.getJavadocClassPath(getLog(), repositorySystem, mavenSession, |
| ctx, region); |
| if (!classpath.isEmpty()) { |
| javadocExecutor.addArgument("-classpath", false) |
| .addArgument(classpath, File.pathSeparator); |
| } |
| |
| // turn off doclint |
| javadocExecutor.addArgument("-Xdoclint:none"); |
| |
| javadocExecutor.addArgument("--allow-script-in-comments"); |
| |
| if ( !this.javadocIndex ) { |
| javadocExecutor.addArgument("-noindex"); |
| } |
| if ( !this.javadocTree ) { |
| javadocExecutor.addArgument("-notree"); |
| } |
| |
| // list packages |
| javadocExecutor.addArguments(javadocPackages); |
| |
| javadocExecutor.execute(javadocDir, getLog(), this.ignoreJavadocErrors); |
| |
| return usedInfos; |
| } |
| |
| /** |
| * Get all packages contained in the archive |
| * @param file The archive to check |
| * @param extension The extension to check for |
| * @return A tuple of packages containing files with the extension and packages with files not having the extension |
| * @throws MojoExecutionException |
| */ |
| private Map.Entry<Set<String>, Set<String>> getPackages(final ApisJarContext ctx, final File file, final String extension) throws MojoExecutionException { |
| final Set<String> packages = new TreeSet<>(); |
| final Set<String> otherPackages = new TreeSet<>(); |
| |
| final Set<String> excludes = new HashSet<>(); |
| for(final String v : ctx.getConfig().getBundleResourceFolders()) { |
| excludes.add(v.concat("/")); |
| } |
| |
| try (final JarInputStream jis = new JarInputStream(new FileInputStream(file))) { |
| JarEntry entry; |
| while ((entry = jis.getNextJarEntry()) != null) { |
| if ( !entry.isDirectory() ) { |
| boolean exclude = false; |
| for(final String v : excludes) { |
| if ( entry.getName().startsWith(v)) { |
| exclude = true; |
| break; |
| } |
| } |
| if ( !exclude ) { |
| final int lastPos = entry.getName().lastIndexOf('/'); |
| if (lastPos != -1) { |
| final String packageName = entry.getName().substring(0, lastPos).replace('/', '.'); |
| |
| if (entry.getName().endsWith(extension)) { |
| packages.add(packageName); |
| } else { |
| otherPackages.add(packageName); |
| } |
| } |
| } |
| } |
| jis.closeEntry(); |
| } |
| } catch (final IOException ioe) { |
| throw new MojoExecutionException("Unable to scan file " + file + " : " + ioe.getMessage()); |
| } |
| |
| otherPackages.removeAll(packages); |
| |
| return Collections.singletonMap(packages, otherPackages).entrySet().iterator().next(); |
| } |
| |
| private File createLicenseReport(final ApisJarContext ctx, |
| final ApiRegion region, |
| final Collection<ArtifactInfo> infos, |
| final List<String> report) throws MojoExecutionException { |
| final File out = new File(this.getTmpDir(), region.getName() + "-license-report.txt"); |
| if ( !out.exists() ) { |
| |
| final List<String> output = new ArrayList<>(); |
| |
| output.add(ctx.getConfig().getLicenseReportHeader()); |
| output.add(""); |
| for(final ArtifactInfo info : infos) { |
| final String licenseDefault = ctx.getConfig().getLicenseDefault(info.getId()); |
| |
| final StringBuilder sb = new StringBuilder(info.getId().toMvnId()); |
| boolean exclude = false; |
| if ( licenseDefault != null ) { |
| if ( licenseDefault.isEmpty()) { |
| exclude = true; |
| getLog().debug("Excluding from license report " + info.getId().toMvnId()); |
| } else { |
| sb.append(" - License(s) : "); |
| sb.append(licenseDefault); |
| } |
| } else { |
| final List<License> licenses = this.getLicenses(ctx, info); |
| |
| if ( !licenses.isEmpty() ) { |
| sb.append(" - License(s) : "); |
| sb.append(String.join(", ", |
| licenses.stream() |
| .map(l -> l.getName().concat(" (").concat(l.getUrl()).concat(")")) |
| .collect(Collectors.toList()))); |
| } else { |
| report.add("No license info found for ".concat(info.getId().toMvnId())); |
| } |
| } |
| if ( !exclude ) { |
| output.add(sb.toString()); |
| } |
| } |
| if ( ctx.getConfig().getLicenseReportFooter() != null ) { |
| output.add(""); |
| output.add(ctx.getConfig().getLicenseReportFooter()); |
| } |
| try { |
| Files.write(out.toPath(), output); |
| } catch (IOException e) { |
| throw new MojoExecutionException("Unable to write license report: " + e.getMessage(), e); |
| } |
| } |
| return out; |
| } |
| |
| private List<License> getLicenses(final ApisJarContext ctx, final ArtifactInfo info) { |
| getLog().debug("Getting license for " + info.getId().toMvnId()); |
| List<License> result = info.getLicenses(); |
| if ( result == null ) { |
| try { |
| ArtifactId id = info.getId(); |
| do { |
| final Model model = getArtifactPom(ctx, id); |
| final List<License> ll = model.getLicenses(); |
| |
| if ( ll != null && !ll.isEmpty() ) { |
| getLog().debug("Found license for " + id.toMvnId()); |
| result = ll; |
| } else { |
| if ( model.getParent() != null ) { |
| final ArtifactId newId = new ArtifactId(model.getParent().getGroupId(), model.getParent().getArtifactId(), model.getParent().getVersion(), null, "pom"); |
| if ( newId.equals(id) ) { |
| break; |
| } else { |
| id = newId; |
| } |
| } else { |
| break; |
| } |
| } |
| } while (result == null); |
| } catch (MojoExecutionException m) { |
| // nothing to do |
| } |
| if ( result == null ) { |
| result = Collections.emptyList(); |
| } |
| info.setLicenses(result); |
| } |
| getLog().debug("License for " + info.getId().toMvnId() + " = " + result); |
| return result; |
| } |
| } |
| |