Add .tox/ to .gitignore
[odlparent.git] / karaf / karaf-maven-plugin / src / main / java / org / apache / karaf / tooling / KarMojo.java
1 /*
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
9  *
10  *  http://www.apache.org/licenses/LICENSE-2.0
11  *
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
17  * under the License.
18  */
19 package org.apache.karaf.tooling;
20
21 import java.io.*;
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;
27
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;
57
58 /**
59  * Assemble a kar archive from a features.xml file
60  */
61 @Mojo(name = "kar", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.RUNTIME, threadSafe = true)
62 public class KarMojo extends MojoSupport {
63
64     /**
65      * The maven archive configuration to use.
66      * <p/>
67      * See <a href="http://maven.apache.org/ref/current/maven-archiver/apidocs/org/apache/maven/archiver/MavenArchiveConfiguration.html">the Javadocs for MavenArchiveConfiguration</a>.
68      */
69     @Parameter
70     private MavenArchiveConfiguration archive = new MavenArchiveConfiguration();
71
72     /**
73      * The Jar archiver.
74      */
75     @Component(role = Archiver.class, hint="jar")
76     private JarArchiver jarArchiver = null;
77
78     /**
79      * Directory containing the generated archive.
80      */
81     @Parameter(defaultValue = "${project.build.directory}")
82     private File outputDirectory = null;
83
84     /**
85      * Name of the generated archive.
86      */
87     @Parameter(defaultValue = "${project.build.finalName}")
88     private String finalName = null;
89
90     /**
91      * Ignore the dependency flag on the bundles in the features XML
92      */
93     @Parameter(defaultValue = "false")
94     private boolean ignoreDependencyFlag;
95
96     /**
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.
99      */
100     @Parameter
101     protected String classifier;
102
103     /**
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
106      */
107     @Parameter(defaultValue = "${project.build.directory}/classes")
108     private File resourcesDir;
109
110
111     /**
112      * The features file to use as instructions
113      */
114     @Parameter(defaultValue = "${project.build.directory}/feature/feature.xml")
115     private String featuresFile;
116
117
118     /**
119      * The wrapper repository in the kar.
120      */
121     @Parameter(defaultValue = "${repositoryPath}")
122     private String repositoryPath = "repository/";
123
124     private static final Pattern mvnPattern = Pattern.compile("mvn:([^/ ]+)/([^/ ]+)/([^/ ]*)(/([^/ ]+)(/([^/ ]+))?)?");
125
126
127     //
128     // Mojo
129     //
130
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();
136
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();
145         }
146
147         List<Artifact> resources = readResources(featuresFileResolved);
148
149         // Build the archive
150         File archive = createArchive(resources, featuresFileResolved, groupId, artifactId, version);
151
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);
157             return;
158         }
159
160         // Attach the generated archive for install/deploy
161         if (classifier != null) {
162             projectHelper.attachArtifact(getProject(), "kar", classifier, archive);
163         } else {
164             getProject().getArtifact().setFile(archive);
165         }
166     }
167
168     private File resolveFile(String file) {
169         File fileResolved = null;
170
171         if (isMavenUrl(file)) {
172             fileResolved = new File(fromMaven(file));
173             try {
174                 Artifact artifactTemp = resourceToArtifact(file, false);
175                 if (!fileResolved.exists()) {
176                     try {
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);
183                     }
184                 }
185             } catch (MojoExecutionException e) {
186                 getLog().error(e);
187             }
188         } else {
189             fileResolved = new File(file);
190         }
191
192         return fileResolved;
193     }
194
195     /**
196      * Read bundles and configuration files in the features file.
197      *
198      * @return
199      * @throws MojoExecutionException
200      */
201     private List<Artifact> readResources(File featuresFile) throws MojoExecutionException {
202         List<Artifact> resources = new ArrayList<Artifact>();
203         try {
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));
209                     }
210                 }
211                 for (ConfigFileInfo configFile : feature.getConfigurationFiles()) {
212                     resources.add(resourceToArtifact(configFile.getLocation(), false));
213                 }
214             }
215             return resources;
216         } catch (MojoExecutionException e) {
217             throw e;
218         } catch (Exception e) {
219             throw new MojoExecutionException("Could not interpret features.xml", e);
220         }
221     }
222
223     /**
224      * Generates the configuration archive.
225      *
226      * @param bundles
227      */
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);
232
233         MavenArchiver archiver = new MavenArchiver();
234         archiver.setArchiver(jarArchiver);
235         archiver.setOutputFile(archiveFile);
236
237         try {
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();
245 //            }
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());
254
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));
258
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");
272
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);
284
285                     metadata.setVersioning(versioning);
286
287                     MetadataXpp3Writer metadataWriter = new MetadataXpp3Writer();
288                     try {
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");
294                     }
295                 }
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");
298             }
299
300             for (Artifact artifact : bundles) {
301                 artifactResolver.resolve(artifact, remoteRepos, localRepo);
302
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());
306
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");
310
311                     try {
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");
316                     }
317
318                     jarArchiver.addFile(metadataTmp, repositoryPath + layout.pathOf(artifact).substring(0, layout.pathOf(artifact).lastIndexOf('/')) + "/maven-metadata-local.xml");
319
320                     try {
321                         metadataTmp.delete();
322                     } catch (final Exception ex) {
323                         getLog().warn("Cannot delete temporary created file.", ex);
324                     }
325                 }
326
327                 String targetFileName = repositoryPath + layout.pathOf(artifact);
328                 jarArchiver.addFile(artifact.getFile(), targetFileName);
329             }
330
331             if (resourcesDir.isDirectory()) {
332                 archiver.getArchiver().addDirectory(resourcesDir);
333             }
334             archiver.createArchive(project, archive);
335
336             return archiveFile;
337         } catch (Exception e) {
338             throw new MojoExecutionException("Failed to create archive", e);
339         }
340     }
341
342     protected static boolean isMavenUrl(String name) {
343         Matcher m = mvnPattern.matcher(name);
344         return m.matches();
345     }
346
347     /**
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
351      *   are optional.
352      *
353      * @param name artifact data
354      * @return path as supplied or a default Maven repository path
355      */
356     private static String fromMaven(String name) {
357         Matcher m = mvnPattern.matcher(name);
358         if (!m.matches()) {
359             return name;
360         }
361
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) == '.') {
366                 b.setCharAt(i, '/');
367             }
368         }
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);
379         }
380         if (present(classifier)) {
381             b.append(".").append(extension);
382         } else {
383             b.append(".jar");
384         }
385         return b.toString();
386     }
387
388     private static boolean present(String part) {
389         return part != null && !part.isEmpty();
390     }
391
392     protected static File getArchiveFile(final File basedir, final String finalName, String classifier) {
393         if (classifier == null) {
394             classifier = "";
395         } else if (classifier.trim().length() > 0 && !classifier.startsWith("-")) {
396             classifier = "-" + classifier;
397         }
398
399         return new File(basedir, finalName + classifier + ".kar");
400     }
401
402
403 }