Java tutorial
/* * 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.felix.bundleplugin.baseline; import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.factory.ArtifactFactory; import org.apache.maven.artifact.metadata.ArtifactMetadataRetrievalException; import org.apache.maven.artifact.metadata.ArtifactMetadataSource; import org.apache.maven.artifact.resolver.ArtifactNotFoundException; import org.apache.maven.artifact.resolver.ArtifactResolutionException; import org.apache.maven.artifact.resolver.ArtifactResolver; import org.apache.maven.artifact.versioning.ArtifactVersion; import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; import org.apache.maven.artifact.versioning.OverConstrainedVersionException; import org.apache.maven.artifact.versioning.VersionRange; import org.apache.maven.execution.MavenSession; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.util.StringUtils; import aQute.bnd.differ.Baseline; import aQute.bnd.differ.Baseline.Info; import aQute.bnd.differ.DiffPluginImpl; import aQute.bnd.osgi.Instructions; import aQute.bnd.osgi.Jar; import aQute.bnd.osgi.Processor; import aQute.bnd.service.diff.Delta; import aQute.bnd.service.diff.Diff; import aQute.bnd.version.Version; import aQute.service.reporter.Reporter; /** * Abstract BND Baseline check between two bundles. */ abstract class AbstractBaselinePlugin extends AbstractMojo { /** * Flag to easily skip execution. */ @Parameter(property = "baseline.skip", defaultValue = "false") protected boolean skip; /** * Whether to fail on errors. */ @Parameter(property = "baseline.failOnError", defaultValue = "true") protected boolean failOnError; /** * Whether to fail on warnings. */ @Parameter(property = "baseline.failOnWarning", defaultValue = "false") protected boolean failOnWarning; @Parameter(defaultValue = "${project}", readonly = true, required = true) protected MavenProject project; @Parameter(defaultValue = "${session}", readonly = true, required = true) protected MavenSession session; @Parameter(defaultValue = "${project.build.directory}", readonly = true, required = true) private File buildDirectory; @Parameter(defaultValue = "${project.build.finalName}", readonly = true, required = true) private String finalName; @Component protected ArtifactResolver resolver; @Component protected ArtifactFactory factory; @Component private ArtifactMetadataSource metadataSource; /** * Group id to compare the current code against. */ @Parameter(defaultValue = "${project.groupId}", property = "comparisonGroupId") protected String comparisonGroupId; /** * Artifact to compare the current code against. */ @Parameter(defaultValue = "${project.artifactId}", property = "comparisonArtifactId") protected String comparisonArtifactId; /** * Version to compare the current code against. */ @Parameter(defaultValue = "(,${project.version})", property = "comparisonVersion") protected String comparisonVersion; /** * Artifact to compare the current code against. */ @Parameter(defaultValue = "${project.packaging}", property = "comparisonPackaging") protected String comparisonPackaging; /** * Classifier for the artifact to compare the current code against. */ @Parameter(property = "comparisonClassifier") protected String comparisonClassifier; /** * A list of packages filter, if empty the whole bundle will be traversed. Values are specified in OSGi package * instructions notation, e.g. <code>!org.apache.felix.bundleplugin</code>. */ @Parameter private String[] filters; /** * Project types which this plugin supports. */ @Parameter protected List<String> supportedProjectTypes = Arrays.asList(new String[] { "jar", "bundle" }); public final void execute() throws MojoExecutionException, MojoFailureException { this.execute(null); } protected void execute(Object context) throws MojoExecutionException, MojoFailureException { if (skip) { getLog().info("Skipping Baseline execution"); return; } if (!supportedProjectTypes.contains(project.getArtifact().getType())) { getLog().info("Skipping Baseline (project type " + project.getArtifact().getType() + " not supported)"); return; } // get the bundles that have to be compared final Jar currentBundle = getCurrentBundle(); if (currentBundle == null) { getLog().info("Not generating Baseline report as there is no bundle generated by the project"); return; } final Artifact previousArtifact = getPreviousArtifact(); final Jar previousBundle; if (previousArtifact != null) { previousBundle = openJar(previousArtifact.getFile()); } else { previousBundle = null; } if (previousBundle == null) { getLog().info( "Not generating Baseline report as there is no previous version of the library to compare against"); return; } // preparing the filters final Instructions packageFilters; if (filters == null || filters.length == 0) { packageFilters = new Instructions(); } else { packageFilters = new Instructions(Arrays.asList(filters)); } String generationDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'").format(new Date()); final Reporter reporter = new Processor(); final Info[] infos; try { final Set<Info> infoSet = new Baseline(reporter, new DiffPluginImpl()).baseline(currentBundle, previousBundle, packageFilters); infos = infoSet.toArray(new Info[infoSet.size()]); Arrays.sort(infos, new InfoComparator()); } catch (final Exception e) { throw new MojoExecutionException("Impossible to calculate the baseline", e); } finally { closeJars(currentBundle, previousBundle); } try { // go! context = this.init(context); startBaseline(context, generationDate, project.getArtifactId(), project.getVersion(), previousArtifact.getVersion()); for (final Info info : infos) { DiffMessage diffMessage = null; if (info.suggestedVersion != null) { if (info.newerVersion.compareTo(info.suggestedVersion) > 0) { diffMessage = new DiffMessage("Excessive version increase", DiffMessage.Type.warning); reporter.warning("%s: %s; detected %s, suggested %s", info.packageName, diffMessage, info.newerVersion, info.suggestedVersion); } else if (info.newerVersion.compareTo(info.suggestedVersion) < 0) { diffMessage = new DiffMessage("Version increase required", DiffMessage.Type.error); reporter.error("%s: %s; detected %s, suggested %s", info.packageName, diffMessage, info.newerVersion, info.suggestedVersion); } } switch (info.packageDiff.getDelta()) { case UNCHANGED: if (info.newerVersion.compareTo(info.suggestedVersion) != 0) { diffMessage = new DiffMessage("Version has been increased but analysis detected no changes", DiffMessage.Type.warning); reporter.warning("%s: %s; detected %s, suggested %s", info.packageName, diffMessage, info.newerVersion, info.suggestedVersion); } break; case REMOVED: diffMessage = new DiffMessage("Package removed", DiffMessage.Type.info); reporter.trace("%s: %s ", info.packageName, diffMessage); break; case CHANGED: case MICRO: case MINOR: case MAJOR: case ADDED: default: // ok break; } startPackage(context, info.mismatch, info.packageName, getShortDelta(info.packageDiff.getDelta()), StringUtils.lowerCase(String.valueOf(info.packageDiff.getDelta())), info.newerVersion, info.olderVersion, info.suggestedVersion, diffMessage, info.attributes); if (Delta.REMOVED != info.packageDiff.getDelta()) { doPackageDiff(context, info.packageDiff); } endPackage(context); } endBaseline(context); } finally { this.close(context); } // check if it has to fail if some error has been detected boolean fail = false; if (!reporter.isOk()) { for (String errorMessage : reporter.getErrors()) { getLog().error(errorMessage); } if (failOnError) { fail = true; } } // check if it has to fail if some warning has been detected if (!reporter.getWarnings().isEmpty()) { for (String warningMessage : reporter.getWarnings()) { getLog().warn(warningMessage); } if (failOnWarning) { fail = true; } } getLog().info(String.format("Baseline analysis complete, %s error(s), %s warning(s)", reporter.getErrors().size(), reporter.getWarnings().size())); if (fail) { throw new MojoFailureException("Baseline failed, see generated report"); } } private void doPackageDiff(Object context, Diff diff) { int depth = 1; for (Diff curDiff : diff.getChildren()) { if (Delta.UNCHANGED != curDiff.getDelta()) { doDiff(context, curDiff, depth); } } } private void doDiff(Object context, Diff diff, int depth) { String type = StringUtils.lowerCase(String.valueOf(diff.getType())); String shortDelta = getShortDelta(diff.getDelta()); String delta = StringUtils.lowerCase(String.valueOf(diff.getDelta())); String name = diff.getName(); startDiff(context, depth, type, name, delta, shortDelta); for (Diff curDiff : diff.getChildren()) { if (Delta.UNCHANGED != curDiff.getDelta()) { doDiff(context, curDiff, depth + 1); } } endDiff(context, depth); } // extensions APIs protected abstract Object init(final Object initialContext); protected abstract void close(final Object context); protected abstract void startBaseline(final Object context, String generationDate, String bundleName, String currentVersion, String previousVersion); protected abstract void startPackage(final Object context, boolean mismatch, String name, String shortDelta, String delta, Version newerVersion, Version olderVersion, Version suggestedVersion, DiffMessage diffMessage, Map<String, String> attributes); protected abstract void startDiff(final Object context, int depth, String type, String name, String delta, String shortDelta); protected abstract void endDiff(final Object context, int depth); protected abstract void endPackage(final Object context); protected abstract void endBaseline(final Object context); // internals private Jar getCurrentBundle() throws MojoExecutionException { /* * Resolving the aQute.bnd.osgi.Jar via the produced artifact rather than what is produced in the target/classes * directory would make the Mojo working also in projects where the bundle-plugin is used just to generate the * manifest file and the final jar is assembled via the jar-plugin */ File currentBundle = new File(buildDirectory, getBundleName()); if (!currentBundle.exists()) { getLog().debug("Produced bundle not found: " + currentBundle); return null; } return openJar(currentBundle); } private Artifact getPreviousArtifact() throws MojoFailureException, MojoExecutionException { // Find the previous version JAR and resolve it, and it's dependencies final VersionRange range; try { range = VersionRange.createFromVersionSpec(comparisonVersion); } catch (InvalidVersionSpecificationException e) { throw new MojoFailureException("Invalid comparison version: " + e.getMessage()); } final Artifact previousArtifact; try { previousArtifact = factory.createDependencyArtifact(comparisonGroupId, comparisonArtifactId, range, comparisonPackaging, comparisonClassifier, Artifact.SCOPE_COMPILE); if (!previousArtifact.getVersionRange().isSelectedVersionKnown(previousArtifact)) { getLog().debug("Searching for versions in range: " + previousArtifact.getVersionRange()); @SuppressWarnings("unchecked") // type is konwn List<ArtifactVersion> availableVersions = metadataSource.retrieveAvailableVersions(previousArtifact, session.getLocalRepository(), project.getRemoteArtifactRepositories()); filterSnapshots(availableVersions); ArtifactVersion version = range.matchVersion(availableVersions); if (version != null) { previousArtifact.selectVersion(version.toString()); } } } catch (OverConstrainedVersionException ocve) { throw new MojoFailureException("Invalid comparison version: " + ocve.getMessage()); } catch (ArtifactMetadataRetrievalException amre) { throw new MojoExecutionException("Error determining previous version: " + amre.getMessage(), amre); } if (previousArtifact.getVersion() == null) { getLog().info("Unable to find a previous version of the project in the repository"); return null; } try { resolver.resolve(previousArtifact, project.getRemoteArtifactRepositories(), session.getLocalRepository()); } catch (ArtifactResolutionException are) { throw new MojoExecutionException( "Artifact " + previousArtifact + " cannot be resolved : " + are.getMessage(), are); } catch (ArtifactNotFoundException anfe) { throw new MojoExecutionException( "Artifact " + previousArtifact + " does not exist on local/remote repositories", anfe); } return previousArtifact; } private void filterSnapshots(List<ArtifactVersion> versions) { for (Iterator<ArtifactVersion> versionIterator = versions.iterator(); versionIterator.hasNext();) { ArtifactVersion version = versionIterator.next(); if (version.getQualifier() != null && version.getQualifier().endsWith("SNAPSHOT")) { versionIterator.remove(); } } } private static Jar openJar(final File file) throws MojoExecutionException { try { return new Jar(file); } catch (final IOException e) { throw new MojoExecutionException("An error occurred while opening JAR directory: " + file, e); } } private static void closeJars(final Jar... jars) { for (Jar jar : jars) { jar.close(); } } private String getBundleName() { String extension; try { extension = project.getArtifact().getArtifactHandler().getExtension(); } catch (Throwable e) { extension = project.getArtifact().getType(); } if (StringUtils.isEmpty(extension) || "bundle".equals(extension) || "pom".equals(extension)) { extension = "jar"; // just in case maven gets confused } String classifier = this.comparisonClassifier != null ? this.comparisonClassifier : project.getArtifact().getClassifier(); if (null != classifier && classifier.trim().length() > 0) { return finalName + '-' + classifier + '.' + extension; } return finalName + '.' + extension; } private static String getShortDelta(Delta delta) { switch (delta) { case ADDED: return "+"; case CHANGED: return "~"; case MAJOR: return ">"; case MICRO: return "0xB5"; case MINOR: return "<"; case REMOVED: return "-"; case UNCHANGED: return " "; default: String deltaString = delta.toString(); return String.valueOf(deltaString.charAt(0)); } } }