/*
* Copyright (C) 2011 Everit Kft. (http://everit.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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.everit.osgi.dev.maven;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Execute;
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.eclipse.osgi.service.resolver.BundleDescription;
import org.eclipse.osgi.service.resolver.ImportPackageSpecification;
import org.eclipse.osgi.service.resolver.PlatformAdmin;
import org.eclipse.osgi.service.resolver.State;
import org.everit.osgi.dev.maven.configuration.EnvironmentConfiguration;
import org.everit.osgi.dev.maven.util.DistributableArtifact;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.launch.Framework;
import org.osgi.framework.launch.FrameworkFactory;
import org.osgi.framework.wiring.BundleCapability;
import org.osgi.framework.wiring.BundleRequirement;
import org.osgi.framework.wiring.FrameworkWiring;
/**
* Analyses the environment settings based on different algorythms and provides useful tips how to
* improve their configuration.
*
* <p>
* At the moment, this goal shows the unsatisfied dependencies in case no package is exported from
* the JDK. This information can be useful to find out what should be specified for
* <i>org.osgi.framework.system.packages</i> property and what could be added as a bundle.
*/
@Mojo(name = "analyze", requiresProject = true,
requiresDependencyResolution = ResolutionScope.COMPILE)
@Execute(phase = LifecyclePhase.PACKAGE)
public class AnalyzeMojo extends AbstractEOSGiMojo {
private static File createTempDirectory() throws IOException {
final File temp = File.createTempFile("eosgi-diagnose-",
Long.toString(System.nanoTime()));
if (!(temp.delete())) {
throw new IOException("Could not delete temp file: "
+ temp.getAbsolutePath());
}
if (!(temp.mkdir())) {
throw new IOException("Could not create temp directory: "
+ temp.getAbsolutePath());
}
return temp;
}
private static void deleteFolder(final File folder) {
File[] files = folder.listFiles();
if (files != null) { // some JVMs return null for empty dirs
for (File f : files) {
if (f.isDirectory()) {
AnalyzeMojo.deleteFolder(f);
} else {
f.delete();
}
}
}
folder.delete();
}
/**
* Map of plugin artifacts.
*/
@Parameter(defaultValue = "${plugin.artifactMap}", required = true, readonly = true)
private Map<String, org.apache.maven.artifact.Artifact> pluginArtifactMap;
private void diagnose(final String[] bundleLocations) throws MojoFailureException {
Framework osgiContainer = null;
File tempDirectory = null;
try {
tempDirectory = AnalyzeMojo.createTempDirectory();
} catch (IOException e) {
throw new MojoFailureException(
"Cannot create temprorary directory for embedded OSGi container", e);
}
try {
osgiContainer = startOSGiContainer(bundleLocations, tempDirectory.getAbsolutePath());
printMissingRequirements(osgiContainer);
} catch (BundleException e) {
throw new MojoFailureException("Error during creating starting embedded OSGi container", e);
} finally {
if (osgiContainer != null) {
try {
osgiContainer.stop();
osgiContainer.waitForStop(0);
} catch (BundleException e) {
getLog().error("Could not stop embedded OSGi container during code generation", e);
} catch (InterruptedException e) {
getLog().error("Stopping of embedded OSGi container was interrupted", e);
Thread.currentThread().interrupt();
}
}
AnalyzeMojo.deleteFolder(tempDirectory);
}
}
@Override
protected void doExecute() throws MojoExecutionException, MojoFailureException {
EnvironmentConfiguration[] environmentsToProcess = getEnvironmentsToProcess();
Map<String, DistributableArtifact> projectDistributableDependencies =
createDistributableArtifactsByGAVFromProjectDeps();
for (EnvironmentConfiguration environment : environmentsToProcess) {
Collection<DistributableArtifact> distributableArtifacts =
generateDistributableArtifactsForEnvironment(environment,
projectDistributableDependencies);
List<String> bundleLocations = new ArrayList<>();
for (DistributableArtifact distributableArtifact : distributableArtifacts) {
bundleLocations.add(resolveArtifactFileURI(distributableArtifact.file));
}
diagnose(bundleLocations.toArray(new String[bundleLocations.size()]));
}
}
private List<BundleCapability> getAllCapabilities(final Bundle[] bundles, final State state) {
List<BundleCapability> availableCapabilities = new ArrayList<BundleCapability>();
for (Bundle bundle : bundles) {
BundleDescription bundleDescription = state.getBundle(bundle.getBundleId());
List<BundleCapability> declaredCapabilities = bundleDescription.getDeclaredCapabilities(null);
availableCapabilities.addAll(declaredCapabilities);
}
return availableCapabilities;
}
private Set<String> printBundlesWithMissingImportsAndSummarize(
final Map<Bundle, List<ImportPackageSpecification>> missingByBundle) {
Set<String> result = new TreeSet<>();
Set<Entry<Bundle, List<ImportPackageSpecification>>> entrySet = missingByBundle.entrySet();
for (Entry<Bundle, List<ImportPackageSpecification>> entry : entrySet) {
Bundle bundle = entry.getKey();
getLog().info(bundle.getSymbolicName() + ":" + bundle.getVersion());
List<ImportPackageSpecification> packages = entry.getValue();
for (ImportPackageSpecification importPackage : packages) {
getLog().info(" " + importPackage.toString());
result.add(importPackage.getName() + ";version=" + importPackage.getVersionRange());
}
}
return result;
}
private void printMissingRequirements(final Framework osgiContainer) {
BundleContext systemBundleContext = osgiContainer.getBundleContext();
ServiceReference<PlatformAdmin> platformServiceSR = systemBundleContext
.getServiceReference(PlatformAdmin.class);
PlatformAdmin platformAdmin = systemBundleContext.getService(platformServiceSR);
State state = platformAdmin.getState();
Bundle[] bundles = systemBundleContext.getBundles();
List<BundleCapability> availableCapabilities = getAllCapabilities(bundles, state);
Map<Bundle, List<ImportPackageSpecification>> requiredMissingByBundle = new TreeMap<>();
Map<Bundle, List<ImportPackageSpecification>> optionalMissingByBundle = new TreeMap<>();
for (Bundle bundle : bundles) {
if (bundle.getState() == Bundle.INSTALLED) {
List<ImportPackageSpecification> requiredMissings = new ArrayList<>();
List<ImportPackageSpecification> optionalMissings = new ArrayList<>();
BundleDescription bundleDescription = state.getBundle(bundle.getBundleId());
ImportPackageSpecification[] allImports = bundleDescription.getImportPackages();
for (ImportPackageSpecification importPackage : allImports) {
BundleRequirement requirement = importPackage.getRequirement();
if (!requirementSatisfiable(requirement, availableCapabilities)) {
if (Constants.RESOLUTION_OPTIONAL
.equals(requirement.getDirectives().get(Constants.RESOLUTION_DIRECTIVE))) {
optionalMissings.add(importPackage);
} else {
requiredMissings.add(importPackage);
}
}
}
if (optionalMissings.size() > 0) {
optionalMissingByBundle.put(bundle, optionalMissings);
}
if (requiredMissings.size() > 0) {
requiredMissingByBundle.put(bundle, requiredMissings);
}
}
}
getLog().info("----- Missing required packages by bundles -----");
Set<String> requiredSum = printBundlesWithMissingImportsAndSummarize(requiredMissingByBundle);
getLog().info("");
getLog().info("");
getLog().info("----- Missing optional packages by bundles -----");
Set<String> optionalSum = printBundlesWithMissingImportsAndSummarize(optionalMissingByBundle);
getLog().info("");
getLog().info("");
getLog().info("----- Missing required packages (summary) -----");
printMissingSummary(requiredSum);
getLog().info("");
getLog().info("");
getLog().info("----- Missing optional packages (summary) -----");
printMissingSummary(optionalSum);
getLog().info("");
getLog().info("");
}
private void printMissingSummary(final Set<String> importPackages) {
for (String importPackage : importPackages) {
getLog().info(" Import-Package: " + importPackage);
}
}
private boolean requirementSatisfiable(final BundleRequirement requirement,
final List<BundleCapability> availableCapabilities) {
for (BundleCapability bundleCapability : availableCapabilities) {
if (requirement.matches(bundleCapability)) {
return true;
}
}
return false;
}
private String resolveArtifactFileURI(final File artifactFile) {
if (artifactFile != null) {
try {
return artifactFile.toURI().toURL().toExternalForm();
} catch (MalformedURLException e) {
getLog().error(e);
}
}
return null;
}
private Framework startOSGiContainer(final String[] bundleLocations,
final String tempDirPath) throws BundleException {
FrameworkFactory frameworkFactory = ServiceLoader
.load(FrameworkFactory.class).iterator().next();
Map<String, String> config = new HashMap<String, String>();
config.put("org.osgi.framework.system.packages", "");
config.put("osgi.configuration.area", tempDirPath);
config.put("osgi.baseConfiguration.area", tempDirPath);
config.put("osgi.sharedConfiguration.area", tempDirPath);
config.put("osgi.instance.area", tempDirPath);
config.put("osgi.user.area", tempDirPath);
config.put("osgi.hook.configurators.exclude",
"org.eclipse.core.runtime.internal.adaptor.EclipseLogHook");
Framework framework = frameworkFactory.newFramework(config);
framework.init();
BundleContext systemBundleContext = framework.getBundleContext();
org.apache.maven.artifact.Artifact equinoxCompatibilityStateArtifact =
pluginArtifactMap.get("org.eclipse.tycho:org.eclipse.osgi.compatibility.state");
URI compatibilityBundleURI = equinoxCompatibilityStateArtifact.getFile().toURI();
systemBundleContext.installBundle("reference:" + compatibilityBundleURI.toString());
framework.start();
for (String bundleLocation : bundleLocations) {
try {
systemBundleContext.installBundle(bundleLocation);
} catch (BundleException e) {
getLog().warn("Could not install bundle " + bundleLocation, e);
}
}
FrameworkWiring frameworkWiring = framework
.adapt(FrameworkWiring.class);
frameworkWiring.resolveBundles(null);
return framework;
}
}