blob: 844c7233145a6e9f19f47f796b9ed82bc61622c5 [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.felix.maven.osgicheck.impl;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.Manifest;
import org.apache.commons.io.FileUtils;
import org.apache.felix.maven.osgicheck.impl.checks.ConsumerProviderTypeCheck;
import org.apache.felix.maven.osgicheck.impl.checks.ImportExportCheck;
import org.apache.felix.maven.osgicheck.impl.checks.SCRCheck;
import org.apache.felix.maven.osgicheck.impl.featureutil.ManifestUtil;
import org.apache.maven.plugin.AbstractMojo;
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.project.MavenProject;
import org.codehaus.plexus.archiver.UnArchiver;
import org.codehaus.plexus.archiver.manager.ArchiverManager;
import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
import org.osgi.framework.Constants;
/**
* This plugin checks various aspects of your OSGi project like proper use
* of versioning, best practices use of annotations for Declarative Services
* and others.
*/
@Mojo(
name = "check",
defaultPhase = LifecyclePhase.VERIFY,
threadSafe = true
)
public class CheckMojo extends AbstractMojo {
public enum Mode {
OFF,
DEFAULT,
STRICT,
ERRORS_ONLY
}
/**
* The mode to be used by each check. The value can be {@code OFF} to
* disable this plugin, {@code DEFAULT} to print warnings and fail on
* errors, {@code STRICT} to print warnings and fail on both errors and
* warnings, or {@code ERRORS_ONLY} to only print errors and fail on errors.
*/
@Parameter(defaultValue = "DEFAULT")
protected Mode mode;
/**
* The configuration for the checks. This can be used to specify the mode
* per plugin.
* This is a list of maps. Each map is a configuration for a check.
* The name of the check needs to be provided through the "name" property.
* The mode can be changed with the "mode" property for just this check.
*/
@Parameter
protected Map<String, String>[] config;
/**
* The Maven project
*/
@Parameter(property = "project", readonly = true, required = true)
protected MavenProject project;
/**
* The archiver manager for unarchiving the jar
*/
@Component
private ArchiverManager archiverManager;
/**
* Check whether this is an OSGi project
* @return {@code true} if it is a jar or a bundle
*/
private boolean isOSGiProject() {
if ( "bundle".equals(project.getPackaging()) || "jar".equals(project.getPackaging()) ) {
return true;
}
return false;
}
/**
* Return the file with the jar
* @return The file
*/
private File getBundle() {
return this.project.getArtifact().getFile();
}
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
if ( isOSGiProject() ) {
if ( mode != Mode.OFF ) {
getLog().debug("Checking OSGi project...");
doExecute();
} else {
getLog().debug("Skipping check...disabled by configuration");
}
} else {
getLog().debug("Skipping check...not an OSGi project");
}
}
private void doExecute() throws MojoExecutionException, MojoFailureException {
final File bundle = this.getBundle();
if ( bundle == null ) {
throw new MojoExecutionException("This mojo requires the project artifact to be built to perform the check." +
"Make sure to create your bundle before running this plugin.");
}
getLog().debug("Checking " + bundle);
// configuration
final Map<String, Map<String,String>> configurations = new HashMap<>();
final Set<String> configNames = new HashSet<>();
if ( config != null ) {
for(final Map<String, String> c : config) {
final String name = c.remove("name");
if ( name == null ) {
throw new MojoExecutionException("Name needs to be specified for a check configuration.");
}
configNames.add(name);
configurations.put(name, c);
}
}
final Class<?>[] checkClasses = new Class<?>[] {ImportExportCheck.class, SCRCheck.class, ConsumerProviderTypeCheck.class};
final Check[] checks = new Check[checkClasses.length];
int i = 0;
for(final Class<?> c : checkClasses) {
try {
checks[i] = (Check)c.newInstance();
configNames.remove(checks[i].getName());
i++;
} catch (final InstantiationException | IllegalAccessException e) {
throw new MojoExecutionException(e.getMessage(), e);
}
}
if ( !configNames.isEmpty() ) {
throw new MojoExecutionException("Configurations for unknown checks: " + configNames);
}
final List<CheckResult> results = new ArrayList<>();
try {
final Manifest mf = ManifestUtil.getManifest(bundle);
final String manifestVersion = mf.getMainAttributes().getValue(Constants.BUNDLE_MANIFESTVERSION);
if ( !"2".equals(manifestVersion) ) {
throw new MojoExecutionException("Bundle manifestversion 2 not found in manifest (" +manifestVersion + ")");
}
File rootDir = null;
try {
rootDir = extract(bundle);
final File dir = rootDir;
for(final Check check : checks) {
// extract configuration
Map<String, String> checkConfig = configurations.get(check.getName());
if ( checkConfig == null ) {
checkConfig = Collections.emptyMap();
}
final Map<String, String> cc = checkConfig;
getLog().debug("Configuration for " + check.getName() + " : " + cc);
final CheckResult result = new CheckResult();
result.mode = this.mode;
if ( cc.get("mode") != null ) {
result.mode = Mode.valueOf(cc.remove("mode"));
}
if ( result.mode != Mode.OFF ) {
getLog().debug("Executing " + check.getName() + "...");
check.check(new CheckContext() {
private final Map<String, String> conf = cc;
@Override
public File getRootDir() {
return dir;
}
@Override
public Manifest getManifest() {
return mf;
}
@Override
public Map<String, String> getConfiguration() {
return conf;
}
@Override
public Log getLog() {
return CheckMojo.this.getLog();
}
@Override
public void reportWarning(String message) {
result.warnings.add(message);
}
@Override
public void reportError(String message) {
result.errors.add(message);
}
});
results.add(result);
getLog().debug("Finished " + check.getName() + "...");
} else {
getLog().debug("Skipping executing " + check.getName());
}
}
} finally {
if ( rootDir != null ) {
FileUtils.deleteDirectory(rootDir);
}
}
} catch ( final IOException ioe) {
throw new MojoExecutionException(ioe.getMessage(), ioe);
}
// print warnings from all checks were enabled
for(final CheckResult result : results) {
if ( result.mode != Mode.ERRORS_ONLY ) {
for(final String msg : result.warnings) {
getLog().warn(msg);
}
}
}
// print all errors
boolean hasErrors = false;
for(final CheckResult result : results) {
for(final String msg : result.errors) {
hasErrors = true;
getLog().error(msg);
}
}
if ( hasErrors ) {
throw new MojoExecutionException("Check detected errors. See log output for error messages.");
}
for(final CheckResult result : results) {
if ( result.mode == Mode.STRICT && !result.warnings.isEmpty() ) {
throw new MojoExecutionException("Check detected warnings and strict mode is enabled. See log output for warning messages.");
}
}
}
private File extract(final File file) throws IOException, MojoExecutionException {
final File rootDir = Files.createTempDirectory("osgicheck").toFile();
UnArchiver zipUnarchiver = null;
try {
zipUnarchiver = archiverManager.getUnArchiver(file);
} catch (NoSuchArchiverException e) {
// should not happen
throw new MojoExecutionException("Impossible to unarchive the '"
+ file
+ "' file: "
+ e.getMessage());
}
zipUnarchiver.setDestDirectory(rootDir);
zipUnarchiver.setSourceFile(file);
zipUnarchiver.extract();
return rootDir;
}
public static class CheckResult {
public Mode mode;
public final List<String> warnings = new ArrayList<>();
public final List<String> errors = new ArrayList<>();
}
}