2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with 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,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
19 package org.apache.karaf.tooling;
22 import java.util.ArrayList;
23 import java.util.Date;
24 import java.util.List;
25 import java.util.regex.Matcher;
26 import java.util.regex.Pattern;
28 import org.apache.karaf.deployer.kar.KarArtifactInstaller;
29 import org.apache.karaf.features.BundleInfo;
30 import org.apache.karaf.features.ConfigFileInfo;
31 import org.apache.karaf.features.internal.model.Feature;
32 import org.apache.karaf.features.internal.model.Features;
33 import org.apache.karaf.features.internal.model.JaxbUtil;
34 import org.apache.karaf.tooling.utils.MavenUtil;
35 import org.apache.karaf.tooling.utils.MojoSupport;
36 import org.apache.maven.archiver.MavenArchiveConfiguration;
37 import org.apache.maven.archiver.MavenArchiver;
38 import org.apache.maven.artifact.Artifact;
39 import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
40 import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout;
41 import org.apache.maven.artifact.repository.metadata.Metadata;
42 import org.apache.maven.artifact.repository.metadata.Snapshot;
43 import org.apache.maven.artifact.repository.metadata.SnapshotVersion;
44 import org.apache.maven.artifact.repository.metadata.Versioning;
45 import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Writer;
46 import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
47 import org.apache.maven.artifact.resolver.ArtifactResolutionException;
48 import org.apache.maven.plugin.MojoExecutionException;
49 import org.apache.maven.plugin.MojoFailureException;
50 import org.apache.maven.plugins.annotations.Component;
51 import org.apache.maven.plugins.annotations.LifecyclePhase;
52 import org.apache.maven.plugins.annotations.Mojo;
53 import org.apache.maven.plugins.annotations.Parameter;
54 import org.apache.maven.plugins.annotations.ResolutionScope;
55 import org.codehaus.plexus.archiver.Archiver;
56 import org.codehaus.plexus.archiver.jar.JarArchiver;
59 * Assemble a kar archive from a features.xml file
61 @Mojo(name = "kar", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.RUNTIME, threadSafe = true)
62 public class KarMojo extends MojoSupport {
65 * The maven archive configuration to use.
67 * See <a href="http://maven.apache.org/ref/current/maven-archiver/apidocs/org/apache/maven/archiver/MavenArchiveConfiguration.html">the Javadocs for MavenArchiveConfiguration</a>.
70 private MavenArchiveConfiguration archive = new MavenArchiveConfiguration();
75 @Component(role = Archiver.class, hint="jar")
76 private JarArchiver jarArchiver = null;
79 * Directory containing the generated archive.
81 @Parameter(defaultValue = "${project.build.directory}")
82 private File outputDirectory = null;
85 * Name of the generated archive.
87 @Parameter(defaultValue = "${project.build.finalName}")
88 private String finalName = null;
91 * Ignore the dependency flag on the bundles in the features XML
93 @Parameter(defaultValue = "false")
94 private boolean ignoreDependencyFlag;
97 * Classifier to add to the artifact generated. If given, the artifact will be attached.
98 * If it's not given, it will merely be written to the output directory according to the finalName.
101 protected String classifier;
104 * Location of resources directory for additional content to include in the kar.
105 * Note that it includes everything under classes so as to include maven-remote-resources
107 @Parameter(defaultValue = "${project.build.directory}/classes")
108 private File resourcesDir;
112 * The features file to use as instructions
114 @Parameter(defaultValue = "${project.build.directory}/feature/feature.xml")
115 private String featuresFile;
119 * The wrapper repository in the kar.
121 @Parameter(defaultValue = "${repositoryPath}")
122 private String repositoryPath = "repository/";
124 private static final Pattern mvnPattern = Pattern.compile("mvn:([^/ ]+)/([^/ ]+)/([^/ ]*)(/([^/ ]+)(/([^/ ]+))?)?");
131 public void execute() throws MojoExecutionException, MojoFailureException {
132 File featuresFileResolved = resolveFile(featuresFile);
133 String groupId = project.getGroupId();
134 String artifactId = project.getArtifactId();
135 String version = project.getVersion();
137 if (isMavenUrl(featuresFile)) {
138 Artifact artifactTemp = resourceToArtifact(featuresFile, false);
139 if (artifactTemp.getGroupId() != null)
140 groupId = artifactTemp.getGroupId();
141 if (artifactTemp.getArtifactHandler() != null)
142 artifactId = artifactTemp.getArtifactId();
143 if (artifactTemp.getVersion() != null)
144 version = artifactTemp.getVersion();
147 List<Artifact> resources = readResources(featuresFileResolved);
150 File archive = createArchive(resources, featuresFileResolved, groupId, artifactId, version);
152 // if no classifier is specified and packaging is not kar, display a warning
153 // and attach artifact
154 if (classifier == null && !this.getProject().getPackaging().equals("kar")) {
155 this.getLog().warn("Your project should use the \"kar\" packaging or configure a \"classifier\" for kar attachment");
156 projectHelper.attachArtifact(getProject(), "kar", null, archive);
160 // Attach the generated archive for install/deploy
161 if (classifier != null) {
162 projectHelper.attachArtifact(getProject(), "kar", classifier, archive);
164 getProject().getArtifact().setFile(archive);
168 private File resolveFile(String file) {
169 File fileResolved = null;
171 if (isMavenUrl(file)) {
172 fileResolved = new File(fromMaven(file));
174 Artifact artifactTemp = resourceToArtifact(file, false);
175 if (!fileResolved.exists()) {
177 artifactResolver.resolve(artifactTemp, remoteRepos, localRepo);
178 fileResolved = artifactTemp.getFile();
179 } catch (ArtifactResolutionException e) {
180 getLog().error("Artifact was not resolved", e);
181 } catch (ArtifactNotFoundException e) {
182 getLog().error("Artifact was not found", e);
185 } catch (MojoExecutionException e) {
189 fileResolved = new File(file);
196 * Read bundles and configuration files in the features file.
199 * @throws MojoExecutionException
201 private List<Artifact> readResources(File featuresFile) throws MojoExecutionException {
202 List<Artifact> resources = new ArrayList<Artifact>();
204 Features features = JaxbUtil.unmarshal(featuresFile.toURI().toASCIIString(), false);
205 for (Feature feature : features.getFeature()) {
206 for (BundleInfo bundle : feature.getBundles()) {
207 if (ignoreDependencyFlag || (!ignoreDependencyFlag && !bundle.isDependency())) {
208 resources.add(resourceToArtifact(bundle.getLocation(), false));
211 for (ConfigFileInfo configFile : feature.getConfigurationFiles()) {
212 resources.add(resourceToArtifact(configFile.getLocation(), false));
216 } catch (MojoExecutionException e) {
218 } catch (Exception e) {
219 throw new MojoExecutionException("Could not interpret features.xml", e);
224 * Generates the configuration archive.
228 @SuppressWarnings("deprecation")
229 private File createArchive(List<Artifact> bundles, File featuresFile, String groupId, String artifactId, String version) throws MojoExecutionException {
230 ArtifactRepositoryLayout layout = new DefaultRepositoryLayout();
231 File archiveFile = getArchiveFile(outputDirectory, finalName, classifier);
233 MavenArchiver archiver = new MavenArchiver();
234 archiver.setArchiver(jarArchiver);
235 archiver.setOutputFile(archiveFile);
238 //TODO should .kar be a bundle?
239 // archive.addManifestEntry(Constants.BUNDLE_NAME, project.getName());
240 // archive.addManifestEntry(Constants.BUNDLE_VENDOR, project.getOrganization().getName());
241 // ArtifactVersion version = project.getArtifact().getSelectedVersion();
242 // String versionString = "" + version.getMajorVersion() + "." + version.getMinorVersion() + "." + version.getIncrementalVersion();
243 // if (version.getQualifier() != null) {
244 // versionString += "." + version.getQualifier();
246 // archive.addManifestEntry(Constants.BUNDLE_VERSION, versionString);
247 // archive.addManifestEntry(Constants.BUNDLE_MANIFESTVERSION, "2");
248 // archive.addManifestEntry(Constants.BUNDLE_DESCRIPTION, project.getDescription());
249 // // NB, no constant for this one
250 // archive.addManifestEntry("Bundle-License", ((License) project.getLicenses().get(0)).getUrl());
251 // archive.addManifestEntry(Constants.BUNDLE_DOCURL, project.getUrl());
252 // //TODO this might need some help
253 // archive.addManifestEntry(Constants.BUNDLE_SYMBOLICNAME, project.getArtifactId());
255 //include the feature.xml
256 Artifact featureArtifact = factory.createArtifactWithClassifier(groupId, artifactId, version, "xml", KarArtifactInstaller.FEATURE_CLASSIFIER);
257 jarArchiver.addFile(featuresFile, repositoryPath + layout.pathOf(featureArtifact));
259 if (featureArtifact.isSnapshot()) {
260 // the artifact is a snapshot, create the maven-metadata-local.xml
261 getLog().debug("Feature artifact is a SNAPSHOT, handling the maven-metadata-local.xml");
262 File metadataTarget = new File(featuresFile.getParentFile(), "maven-metadata-local.xml");
263 getLog().debug("Looking for " + metadataTarget.getAbsolutePath());
264 if (!metadataTarget.exists()) {
265 // the maven-metadata-local.xml doesn't exist, create it
266 getLog().debug(metadataTarget.getAbsolutePath() + " doesn't exist, create it");
267 Metadata metadata = new Metadata();
268 metadata.setGroupId(featureArtifact.getGroupId());
269 metadata.setArtifactId(featureArtifact.getArtifactId());
270 metadata.setVersion(featureArtifact.getVersion());
271 metadata.setModelVersion("1.1.0");
273 Versioning versioning = new Versioning();
274 versioning.setLastUpdatedTimestamp(new Date(System.currentTimeMillis()));
275 Snapshot snapshot = new Snapshot();
276 snapshot.setLocalCopy(true);
277 versioning.setSnapshot(snapshot);
278 SnapshotVersion snapshotVersion = new SnapshotVersion();
279 snapshotVersion.setClassifier(featureArtifact.getClassifier());
280 snapshotVersion.setVersion(featureArtifact.getVersion());
281 snapshotVersion.setExtension(featureArtifact.getType());
282 snapshotVersion.setUpdated(versioning.getLastUpdated());
283 versioning.addSnapshotVersion(snapshotVersion);
285 metadata.setVersioning(versioning);
287 MetadataXpp3Writer metadataWriter = new MetadataXpp3Writer();
289 Writer writer = new FileWriter(metadataTarget);
290 metadataWriter.write(writer, metadata);
291 } catch (Exception e) {
292 getLog().warn("Could not create maven-metadata-local.xml", e);
293 getLog().warn("It means that this SNAPSHOT could be overwritten by an older one present on remote repositories");
296 getLog().debug("Adding file " + metadataTarget.getAbsolutePath() + " in the jar path " + repositoryPath + layout.pathOf(featureArtifact).substring(0, layout.pathOf(featureArtifact).lastIndexOf('/')) + "/maven-metadata-local.xml");
297 jarArchiver.addFile(metadataTarget, repositoryPath + layout.pathOf(featureArtifact).substring(0, layout.pathOf(featureArtifact).lastIndexOf('/')) + "/maven-metadata-local.xml");
300 for (Artifact artifact : bundles) {
301 artifactResolver.resolve(artifact, remoteRepos, localRepo);
303 //TODO this may not be reasonable, but... resolved snapshot artifacts have timestamped versions
304 //which do not work in startup.properties.
305 artifact.setVersion(artifact.getBaseVersion());
307 if (artifact.isSnapshot()) {
308 // the artifact is a snapshot, create the maven-metadata-local.xml
309 final File metadataTmp = File.createTempFile("maven-metadata-local.xml", ".tmp");
312 MavenUtil.generateMavenMetadata(artifact, metadataTmp);
313 } catch (Exception e) {
314 getLog().warn("Could not create maven-metadata-local.xml", e);
315 getLog().warn("It means that this SNAPSHOT could be overwritten by an older one present on remote repositories");
318 jarArchiver.addFile(metadataTmp, repositoryPath + layout.pathOf(artifact).substring(0, layout.pathOf(artifact).lastIndexOf('/')) + "/maven-metadata-local.xml");
321 metadataTmp.delete();
322 } catch (final Exception ex) {
323 getLog().warn("Cannot delete temporary created file.", ex);
327 String targetFileName = repositoryPath + layout.pathOf(artifact);
328 jarArchiver.addFile(artifact.getFile(), targetFileName);
331 if (resourcesDir.isDirectory()) {
332 archiver.getArchiver().addDirectory(resourcesDir);
334 archiver.createArchive(project, archive);
337 } catch (Exception e) {
338 throw new MojoExecutionException("Failed to create archive", e);
342 protected static boolean isMavenUrl(String name) {
343 Matcher m = mvnPattern.matcher(name);
348 * Return a path for an artifact:
349 * - if the input is already a path (doesn't contain ':'), the same path is returned.
350 * - if the input is a Maven URL, the input is converted to a default repository location path, type and classifier
353 * @param name artifact data
354 * @return path as supplied or a default Maven repository path
356 private static String fromMaven(String name) {
357 Matcher m = mvnPattern.matcher(name);
362 StringBuilder b = new StringBuilder();
363 b.append(m.group(1));
364 for (int i = 0; i < b.length(); i++) {
365 if (b.charAt(i) == '.') {
369 b.append("/"); // groupId
370 String artifactId = m.group(2);
371 String version = m.group(3);
372 String extension = m.group(5);
373 String classifier = m.group(7);
374 b.append(artifactId).append("/"); // artifactId
375 b.append(version).append("/"); // version
376 b.append(artifactId).append("-").append(version);
377 if (present(classifier)) {
378 b.append("-").append(classifier);
380 if (present(classifier)) {
381 b.append(".").append(extension);
388 private static boolean present(String part) {
389 return part != null && !part.isEmpty();
392 protected static File getArchiveFile(final File basedir, final String finalName, String classifier) {
393 if (classifier == null) {
395 } else if (classifier.trim().length() > 0 && !classifier.startsWith("-")) {
396 classifier = "-" + classifier;
399 return new File(basedir, finalName + classifier + ".kar");