3 * Licensed to the Apache Software Foundation (ASF) under one or more
4 * contributor license agreements. See the NOTICE file distributed with
5 * this work for additional information regarding copyright ownership.
6 * The ASF licenses this file to You under the Apache License, Version 2.0
7 * (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
18 package org.apache.karaf.tooling.features;
21 import java.io.FileInputStream;
22 import java.io.FileOutputStream;
23 import java.io.IOException;
24 import java.io.OutputStream;
25 import java.util.HashMap;
26 import java.util.HashSet;
29 import java.util.jar.Attributes;
30 import java.util.jar.JarInputStream;
31 import java.util.jar.Manifest;
33 import org.apache.karaf.features.internal.model.Bundle;
34 import org.apache.karaf.features.internal.model.Feature;
35 import org.apache.karaf.features.internal.model.Features;
36 import org.apache.karaf.features.internal.model.JaxbUtil;
37 import org.apache.maven.artifact.Artifact;
38 import org.apache.maven.plugin.MojoExecutionException;
39 import org.apache.maven.plugin.MojoFailureException;
40 import org.apache.maven.plugins.annotations.LifecyclePhase;
41 import org.apache.maven.plugins.annotations.Mojo;
42 import org.apache.maven.plugins.annotations.Parameter;
43 import org.apache.maven.plugins.annotations.ResolutionScope;
44 import org.osgi.framework.Version;
47 * Export meta data about features
49 @Mojo(name = "features-export-meta-data", defaultPhase = LifecyclePhase.COMPILE, requiresDependencyResolution = ResolutionScope.RUNTIME, threadSafe = true)
50 public class ExportFeatureMetaDataMojo extends AbstractFeatureMojo {
53 * If set to true then all bundles will be merged into one combined feature.
54 * In this case duplicates will be eliminated
57 private boolean mergedFeature;
60 * If set to true then for each bundle symbolic name only the highest version will be used
63 protected boolean oneVersion;
66 * Name of the file for exported feature meta data
68 @Parameter(defaultValue = "${project.build.directory}/features.xml")
69 private File metaDataFile;
71 public void execute() throws MojoExecutionException, MojoFailureException {
72 Set<Feature> featuresSet = resolveFeatures();
74 Feature feature = oneVersion ? mergeFeatureOneVersion(featuresSet) : mergeFeature(featuresSet);
75 featuresSet = new HashSet<Feature>();
76 featuresSet.add(feature);
79 metaDataFile.getParentFile().mkdirs();
80 Features features = new Features();
81 features.getFeature().addAll(featuresSet);
82 try (OutputStream os = new FileOutputStream(metaDataFile)) {
83 JaxbUtil.marshal(features, os);
85 } catch (Exception e) {
86 throw new RuntimeException("Error writing feature meta data to " + metaDataFile + ": " + e.getMessage(), e);
90 private Feature mergeFeature(Set<Feature> featuresSet) throws MojoExecutionException {
91 Feature merged = new Feature("merged");
92 Set<String> bundleIds = new HashSet<String>();
93 for (Feature feature : featuresSet) {
94 for (Bundle bundle : feature.getBundle()) {
95 String symbolicName = getBundleSymbolicName(bundle);
96 if (symbolicName == null) {
100 String bundleId = symbolicName + ":" + getBundleVersion(bundle);
101 if (!bundleIds.contains(bundleId)) {
102 bundleIds.add(bundleId);
103 merged.getBundle().add(bundle);
110 private Feature mergeFeatureOneVersion(Set<Feature> featuresSet) throws MojoExecutionException {
111 Feature merged = new Feature("merged");
112 Map<String, Bundle> bundleVersions = new HashMap<>();
113 for (Feature feature : featuresSet) {
114 for (Bundle bundle : feature.getBundle()) {
115 String symbolicName = getBundleSymbolicName(bundle);
116 if (symbolicName == null) {
120 Bundle existingBundle = bundleVersions.get(symbolicName);
121 if (existingBundle != null) {
122 Version existingVersion = new Version(getBundleVersion(existingBundle));
123 Version newVersion = new Version(getBundleVersion(bundle));
124 if (newVersion.compareTo(existingVersion) > 0) {
125 bundleVersions.put(symbolicName, bundle);
128 bundleVersions.put(symbolicName, bundle);
132 for (Bundle bundle : bundleVersions.values()) {
133 merged.getBundle().add(bundle);
138 private void logIgnored(Bundle bundle) {
139 getLog().warn("Ignoring jar without BundleSymbolicName: " + bundle.getLocation());
142 private Map<String, Attributes> manifests = new HashMap<>();
144 private String getBundleVersion(Bundle bundle) throws MojoExecutionException {
145 return getManifest(bundle).getValue("Bundle-Version");
148 private String getBundleSymbolicName(Bundle bundle) throws MojoExecutionException {
149 return getManifest(bundle).getValue("Bundle-SymbolicName");
152 private Attributes getManifest(Bundle bundle) throws MojoExecutionException {
153 Attributes attributes = manifests.get(bundle.getLocation());
154 if (attributes == null) {
155 Artifact artifact = resourceToArtifact(bundle.getLocation(), skipNonMavenProtocols);
156 if (artifact.getFile() == null) {
157 resolveArtifact(artifact, remoteRepos);
159 try (JarInputStream jis = new JarInputStream(new FileInputStream(artifact.getFile()))) {
160 Manifest manifest = jis.getManifest();
161 if (manifest != null) {
162 attributes = manifest.getMainAttributes();
164 attributes = new Attributes();
166 manifests.put(bundle.getLocation(), attributes);
167 } catch (IOException e) {
168 throw new MojoExecutionException("Error reading bundle manifest from " + bundle.getLocation(), e);