Thomas Vachuska
Committed by Gerrit Code Review

ONOS-1290 Implemented OnosAppMojo for packaging and installing ONOS apps as Maven artifacts.

Change-Id: Id9452beea46f37bd0f0737f478f2a2541dc5deb9
...@@ -24,10 +24,12 @@ ...@@ -24,10 +24,12 @@
24 </parent> 24 </parent>
25 25
26 <artifactId>onos-maven-plugin</artifactId> 26 <artifactId>onos-maven-plugin</artifactId>
27 - <version>1.2.0-SNAPSHOT</version> 27 + <version>1.2-SNAPSHOT</version>
28 <packaging>maven-plugin</packaging> 28 <packaging>maven-plugin</packaging>
29 29
30 - <description>Maven plugin for packaging ONOS applications or generating component configuration resources</description> 30 + <description>Maven plugin for packaging ONOS applications or generating
31 + component configuration resources
32 + </description>
31 33
32 <dependencies> 34 <dependencies>
33 <dependency> 35 <dependency>
...@@ -37,11 +39,35 @@ ...@@ -37,11 +39,35 @@
37 </dependency> 39 </dependency>
38 40
39 <dependency> 41 <dependency>
42 + <groupId>org.apache.maven</groupId>
43 + <artifactId>maven-project</artifactId>
44 + <version>2.0</version>
45 + </dependency>
46 +
47 + <dependency>
40 <groupId>com.thoughtworks.qdox</groupId> 48 <groupId>com.thoughtworks.qdox</groupId>
41 <artifactId>qdox</artifactId> 49 <artifactId>qdox</artifactId>
42 <version>2.0-M3</version> 50 <version>2.0-M3</version>
43 </dependency> 51 </dependency>
44 52
53 + <dependency>
54 + <groupId>com.google.guava</groupId>
55 + <artifactId>guava</artifactId>
56 + <version>18.0</version>
57 + </dependency>
58 +
59 + <dependency>
60 + <groupId>commons-collections</groupId>
61 + <artifactId>commons-collections</artifactId>
62 + <version>3.2.1</version>
63 + </dependency>
64 +
65 + <dependency>
66 + <groupId>commons-configuration</groupId>
67 + <artifactId>commons-configuration</artifactId>
68 + <version>1.10</version>
69 + </dependency>
70 +
45 <!-- dependencies to annotations --> 71 <!-- dependencies to annotations -->
46 <dependency> 72 <dependency>
47 <groupId>org.apache.maven.plugin-tools</groupId> 73 <groupId>org.apache.maven.plugin-tools</groupId>
......
...@@ -15,49 +15,325 @@ ...@@ -15,49 +15,325 @@
15 */ 15 */
16 package org.onosproject.maven; 16 package org.onosproject.maven;
17 17
18 +import com.google.common.io.ByteStreams;
19 +import org.apache.commons.configuration.ConfigurationException;
20 +import org.apache.commons.configuration.XMLConfiguration;
18 import org.apache.maven.plugin.AbstractMojo; 21 import org.apache.maven.plugin.AbstractMojo;
19 import org.apache.maven.plugin.MojoExecutionException; 22 import org.apache.maven.plugin.MojoExecutionException;
23 +import org.apache.maven.plugins.annotations.Component;
20 import org.apache.maven.plugins.annotations.LifecyclePhase; 24 import org.apache.maven.plugins.annotations.LifecyclePhase;
21 import org.apache.maven.plugins.annotations.Mojo; 25 import org.apache.maven.plugins.annotations.Mojo;
22 import org.apache.maven.plugins.annotations.Parameter; 26 import org.apache.maven.plugins.annotations.Parameter;
27 +import org.apache.maven.project.MavenProject;
28 +import org.apache.maven.project.MavenProjectHelper;
23 29
30 +import java.io.File;
31 +import java.io.FileInputStream;
32 +import java.io.FileNotFoundException;
33 +import java.io.FileOutputStream;
34 +import java.io.IOException;
35 +import java.io.InputStream;
24 import java.util.List; 36 import java.util.List;
37 +import java.util.stream.Collectors;
38 +import java.util.zip.ZipEntry;
39 +import java.util.zip.ZipOutputStream;
40 +
41 +import static org.codehaus.plexus.util.FileUtils.*;
25 42
26 /** 43 /**
27 - * Produces ONOS application archive. 44 + * Produces ONOS application archive using the app.xml file information.
28 */ 45 */
29 @Mojo(name = "app", defaultPhase = LifecyclePhase.PACKAGE) 46 @Mojo(name = "app", defaultPhase = LifecyclePhase.PACKAGE)
30 public class OnosAppMojo extends AbstractMojo { 47 public class OnosAppMojo extends AbstractMojo {
31 48
32 - @Parameter 49 + private static final String APP = "app";
33 - private String name; 50 + private static final String NAME = "[@name]";
51 + private static final String VERSION = "[@version]";
52 + private static final String FEATURES_REPO = "[@featuresRepo]";
53 + private static final String DESCRIPTION = "description";
54 + private static final String ARTIFACT = "artifact";
34 55
35 - @Parameter 56 + private static final String APP_XML = "app.xml";
36 - private String version; 57 + private static final String FEATURES_XML = "features.xml";
37 58
38 - @Parameter 59 + private static final String MVN_URL = "mvn:";
39 - private String origin; 60 + private static final String M2_REPOSITORY = ".m2/repository";
61 + private static final String M2_PREFIX = "m2";
40 62
41 - @Parameter 63 + private static final String SNAPSHOT = "-SNAPSHOT";
42 - private String description; 64 + private static final String JAR = "jar";
65 + private static final String XML = "xml";
66 + private static final String APP_ZIP = "oar";
67 + private static final String PACKAGE_DIR = "oar";
43 68
44 - @Parameter 69 + private static final int BUFFER_ZIZE = 8192;
45 - private String featuresRepo;
46 70
47 - @Parameter 71 + private String name, version;
48 - private String features; 72 + private String description, featuresRepo;
73 + private List<String> artifacts;
49 74
50 - @Parameter 75 + /**
51 - private String permissions; 76 + * The project base directory.
77 + */
78 + @Parameter(defaultValue = "${basedir}")
79 + protected File baseDir;
52 80
53 - @Parameter 81 + /**
54 - private List<String> artifacts; 82 + * The directory where the generated catalogue file will be put.
83 + */
84 + @Parameter(defaultValue = "${project.build.directory}")
85 + protected File dstDirectory;
86 +
87 + /**
88 + * The project group ID.
89 + */
90 + @Parameter(defaultValue = "${project.groupId}")
91 + protected String projectGroupId;
92 +
93 + /**
94 + * The project artifact ID.
95 + */
96 + @Parameter(defaultValue = "${project.artifactId}")
97 + protected String projectArtifactId;
98 +
99 + /**
100 + * The project version.
101 + */
102 + @Parameter(defaultValue = "${project.version}")
103 + protected String projectVersion;
55 104
105 + /**
106 + * The project version.
107 + */
108 + @Parameter(defaultValue = "${project.description}")
109 + protected String projectDescription;
56 110
111 + /**
112 + * Maven project
113 + */
114 + @Component
115 + private MavenProject project;
116 +
117 + /**
118 + * Maven project helper.
119 + */
120 + @Component
121 + private MavenProjectHelper projectHelper;
122 +
123 +
124 + private File m2Directory;
125 + protected File stageDirectory;
126 + protected String projectPath;
127 + protected String featureVersion;
128 +
129 + @Override
57 public void execute() throws MojoExecutionException { 130 public void execute() throws MojoExecutionException {
58 - getLog().info("Building ONOS application archive " + name + " version " + version); 131 + File appFile = new File(baseDir, APP_XML);
132 + File featuresFile = new File(baseDir, FEATURES_XML);
133 +
134 + if (!appFile.exists()) {
135 + return;
136 + }
137 +
138 + m2Directory = new File(System.getProperty("user.home"), M2_REPOSITORY);
139 + stageDirectory = new File(dstDirectory, PACKAGE_DIR);
140 + featureVersion = projectVersion.replace(SNAPSHOT, "");
141 + projectPath = M2_PREFIX + "/" + artifactDir(projectGroupId, projectArtifactId, projectVersion);
59 142
143 + loadAppFile(appFile);
144 +
145 + // If there are any artifacts, stage the
146 + if (!artifacts.isEmpty()) {
147 + getLog().info("Building ONOS application package for " + name + " (v" + version + ")");
148 + artifacts.forEach(a -> getLog().debug("Including artifact: " + a));
149 +
150 + stageDirectory.mkdirs();
151 + processAppXml(appFile);
152 + processFeaturesXml(featuresFile);
153 + processArtifacts();
154 + generateAppPackage();
155 + }
60 } 156 }
61 -}
62 157
158 + // Loads the app.xml file.
159 + private void loadAppFile(File appFile) throws MojoExecutionException {
160 + XMLConfiguration xml = new XMLConfiguration();
161 + xml.setRootElementName(APP);
162 +
163 + try (FileInputStream stream = new FileInputStream(appFile)) {
164 + xml.load(stream);
165 + xml.setAttributeSplittingDisabled(true);
166 + xml.setDelimiterParsingDisabled(true);
167 +
168 + name = xml.getString(NAME);
169 + version = eval(xml.getString(VERSION));
170 + description = xml.getString(DESCRIPTION)
171 + .replaceAll("\\$\\{project.description\\}", projectDescription);
172 + featuresRepo = eval(xml.getString(FEATURES_REPO));
173 +
174 + artifacts = xml.configurationsAt(ARTIFACT).stream()
175 + .map(cfg -> eval(cfg.getRootNode().getValue().toString()))
176 + .collect(Collectors.toList());
177 +
178 + } catch (ConfigurationException e) {
179 + throw new MojoExecutionException("Unable to parse app.xml file", e);
180 + } catch (FileNotFoundException e) {
181 + throw new MojoExecutionException("Unable to find app.xml file", e);
182 + } catch (IOException e) {
183 + throw new MojoExecutionException("Unable to read app.xml file", e);
184 + }
185 + }
186 +
187 + // Processes and stages the app.xml file.
188 + private void processAppXml(File appFile) throws MojoExecutionException {
189 + try {
190 + File file = new File(stageDirectory, APP_XML);
191 + forceMkdir(stageDirectory);
192 + String s = eval(fileRead(appFile));
193 + fileWrite(file.getAbsolutePath(), s);
194 + } catch (IOException e) {
195 + throw new MojoExecutionException("Unable to process app.xml", e);
196 + }
197 + }
198 +
199 + private void processFeaturesXml(File featuresFile) throws MojoExecutionException {
200 + boolean specified = featuresRepo != null && featuresRepo.length() > 0;
201 +
202 + // If featuresRepo attribute is specified and there is a features.xml
203 + // file present, add the features repo as an artifact
204 + try {
205 + if (specified && featuresFile.exists()) {
206 + processFeaturesXml(new FileInputStream(featuresFile));
207 + } else if (specified) {
208 + processFeaturesXml(getClass().getResourceAsStream(FEATURES_XML));
209 + }
210 + } catch (FileNotFoundException e) {
211 + throw new MojoExecutionException("Unable to find features.xml file", e);
212 + } catch (IOException e) {
213 + throw new MojoExecutionException("Unable to process features.xml file", e);
214 + }
215 + }
216 +
217 + // Processes and stages the features.xml file.
218 + private void processFeaturesXml(InputStream stream) throws IOException {
219 + String featuresArtifact =
220 + artifactFile(projectArtifactId, projectVersion, XML, "features");
221 + File dstDir = new File(stageDirectory, projectPath);
222 + forceMkdir(dstDir);
223 + String s = eval(new String(ByteStreams.toByteArray(stream)));
224 + fileWrite(new File(dstDir, featuresArtifact).getAbsolutePath(), s);
225 + }
226 +
227 + // Stages all artifacts.
228 + private void processArtifacts() {
229 + artifacts.forEach(this::processArtifact);
230 + }
231 +
232 + // Stages the specified artifact.
233 + private void processArtifact(String artifact) {
234 + if (!artifact.startsWith(MVN_URL)) {
235 + getLog().error("Unsupported artifact URL:" + artifact);
236 + return;
237 + }
238 +
239 + String[] fields = artifact.substring(4).split("/");
240 + if (fields.length < 3) {
241 + getLog().error("Illegal artifact URL:" + artifact);
242 + return;
243 + }
244 +
245 + try {
246 + String file = artifactFile(fields);
247 +
248 + if (projectGroupId.equals(fields[0]) && projectArtifactId.equals(fields[1])) {
249 + // Local artifact is not installed yet, package it from target directory.
250 + File dstDir = new File(stageDirectory, projectPath);
251 + forceMkdir(dstDir);
252 + copyFile(new File(dstDirectory, file), new File(dstDir, file));
253 + } else {
254 + // Other artifacts are packaged from ~/.m2/repository directory.
255 + String m2Path = artifactDir(fields);
256 + File srcDir = new File(m2Directory, m2Path);
257 + File dstDir = new File(stageDirectory, m2Path);
258 + forceMkdir(dstDir);
259 + copyFile(new File(srcDir, file), new File(dstDir, file));
260 + }
261 + } catch (IOException e) {
262 + getLog().error("Unable to stage artifact " + artifact + " due to ", e);
263 + }
264 + }
265 +
266 + // Generates the ONOS package ZIP file.
267 + private void generateAppPackage() throws MojoExecutionException {
268 + File appZip = new File(dstDirectory, artifactFile(projectArtifactId, projectVersion,
269 + APP_ZIP, null));
270 + try (FileOutputStream fos = new FileOutputStream(appZip);
271 + ZipOutputStream zos = new ZipOutputStream(fos)) {
272 + zipDirectory("", stageDirectory, zos);
273 + projectHelper.attachArtifact(this.project, APP_ZIP, null, appZip);
274 + } catch (IOException e) {
275 + throw new MojoExecutionException("Unable to compress application package", e);
276 + }
277 + }
63 278
279 + // Generates artifact directory name from the specified fields.
280 + private String artifactDir(String[] fields) {
281 + return artifactDir(fields[0], fields[1], fields[2]);
282 + }
283 +
284 + // Generates artifact directory name from the specified elements.
285 + private String artifactDir(String gid, String aid, String version) {
286 + return gid.replace('.', '/') + "/" + aid.replace('.', '/') + "/" + version;
287 + }
288 +
289 + // Generates artifact file name from the specified fields.
290 + private String artifactFile(String[] fields) {
291 + return fields.length < 5 ?
292 + artifactFile(fields[1], fields[2],
293 + (fields.length < 4 ? JAR : fields[3]), null) :
294 + artifactFile(fields[1], fields[2], fields[3], fields[4]);
295 + }
296 +
297 + // Generates artifact file name from the specified elements.
298 + private String artifactFile(String aid, String version, String type,
299 + String classifier) {
300 + return classifier == null ? aid + "-" + version + "." + type :
301 + aid + "-" + version + "-" + classifier + "." + type;
302 + }
303 +
304 + // Returns the given string with project variable substitutions.
305 + private String eval(String string) {
306 + return string == null ? null :
307 + string.replaceAll("\\$\\{project.groupId\\}", projectGroupId)
308 + .replaceAll("\\$\\{project.artifactId\\}", projectArtifactId)
309 + .replaceAll("\\$\\{project.version\\}", projectVersion)
310 + .replaceAll("\\$\\{project.description\\}", description)
311 + .replaceAll("\\$\\{feature.version\\}", featureVersion);
312 + }
313 +
314 + // Recursively archives the specified directory into a given ZIP stream.
315 + private void zipDirectory(String root, File dir, ZipOutputStream zos)
316 + throws IOException {
317 + byte[] buffer = new byte[BUFFER_ZIZE];
318 + File[] files = dir.listFiles();
319 + if (files != null && files.length > 0) {
320 + for (File file : files) {
321 + if (file.isDirectory()) {
322 + String path = root + file.getName() + "/";
323 + zos.putNextEntry(new ZipEntry(path));
324 + zipDirectory(path, file, zos);
325 + zos.closeEntry();
326 + } else {
327 + FileInputStream fin = new FileInputStream(file);
328 + zos.putNextEntry(new ZipEntry(root + file.getName()));
329 + int length;
330 + while ((length = fin.read(buffer)) > 0) {
331 + zos.write(buffer, 0, length);
332 + }
333 + zos.closeEntry();
334 + fin.close();
335 + }
336 + }
337 + }
338 + }
339 +}
......
...@@ -45,13 +45,13 @@ public class OnosCfgMojo extends AbstractMojo { ...@@ -45,13 +45,13 @@ public class OnosCfgMojo extends AbstractMojo {
45 /** 45 /**
46 * The directory where the generated catalogue file will be put. 46 * The directory where the generated catalogue file will be put.
47 */ 47 */
48 - @Parameter( defaultValue = "${basedir}" ) 48 + @Parameter(defaultValue = "${basedir}")
49 protected File srcDirectory; 49 protected File srcDirectory;
50 50
51 /** 51 /**
52 * The directory where the generated catalogue file will be put. 52 * The directory where the generated catalogue file will be put.
53 */ 53 */
54 - @Parameter( defaultValue = "${project.build.outputDirectory}" ) 54 + @Parameter(defaultValue = "${project.build.outputDirectory}")
55 protected File dstDirectory; 55 protected File dstDirectory;
56 56
57 @Override 57 @Override
......
1 +<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2 +<!--
3 + ~ Copyright 2015 Open Networking Laboratory
4 + ~
5 + ~ Licensed under the Apache License, Version 2.0 (the "License");
6 + ~ you may not use this file except in compliance with the License.
7 + ~ You may obtain a copy of the License at
8 + ~
9 + ~ http://www.apache.org/licenses/LICENSE-2.0
10 + ~
11 + ~ Unless required by applicable law or agreed to in writing, software
12 + ~ distributed under the License is distributed on an "AS IS" BASIS,
13 + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 + ~ See the License for the specific language governing permissions and
15 + ~ limitations under the License.
16 + -->
17 +<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${feature.version}">
18 + <repository>mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features</repository>
19 + <feature name="${project.artifactId}" version="${feature.version}"
20 + description="${project.description}">
21 + <feature>onos-api</feature>
22 + <bundle>mvn:${project.groupId}/${project.artifactId}/${project.version}</bundle>
23 + </feature>
24 +</features>