blob: d52c3b77be7c8655fc2c868fb016f87792e50d98 [file] [log] [blame]
Thomas Vachuska8c8b0372015-03-10 11:11:24 -07001/*
2 * Copyright 2015 Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onosproject.maven;
17
Thomas Vachuska586afd82015-04-17 11:06:53 -070018import com.google.common.collect.ImmutableList;
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070019import org.apache.commons.configuration.ConfigurationException;
20import org.apache.commons.configuration.XMLConfiguration;
Jian Lic35415d2016-01-14 17:22:31 -080021import org.apache.commons.lang.StringUtils;
Brian O'Connorbf7eca52015-04-15 16:25:46 -070022import org.apache.maven.artifact.repository.ArtifactRepository;
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070023import org.apache.maven.plugin.AbstractMojo;
24import org.apache.maven.plugin.MojoExecutionException;
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070025import org.apache.maven.plugins.annotations.Component;
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070026import org.apache.maven.plugins.annotations.LifecyclePhase;
27import org.apache.maven.plugins.annotations.Mojo;
28import org.apache.maven.plugins.annotations.Parameter;
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070029import org.apache.maven.project.MavenProject;
30import org.apache.maven.project.MavenProjectHelper;
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070031
Jian Lic35415d2016-01-14 17:22:31 -080032import javax.imageio.ImageIO;
33import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
34import java.awt.image.BufferedImage;
35import java.awt.image.DataBufferByte;
36import java.awt.image.WritableRaster;
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070037import java.io.File;
38import java.io.FileInputStream;
39import java.io.FileNotFoundException;
40import java.io.FileOutputStream;
41import java.io.IOException;
42import java.io.InputStream;
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070043import java.util.List;
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070044import java.util.stream.Collectors;
45import java.util.zip.ZipEntry;
46import java.util.zip.ZipOutputStream;
47
Thomas Vachuska586afd82015-04-17 11:06:53 -070048import static com.google.common.io.ByteStreams.toByteArray;
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070049import static org.codehaus.plexus.util.FileUtils.*;
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070050
51/**
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070052 * Produces ONOS application archive using the app.xml file information.
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070053 */
54@Mojo(name = "app", defaultPhase = LifecyclePhase.PACKAGE)
55public class OnosAppMojo extends AbstractMojo {
56
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070057 private static final String APP = "app";
58 private static final String NAME = "[@name]";
59 private static final String VERSION = "[@version]";
60 private static final String FEATURES_REPO = "[@featuresRepo]";
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070061 private static final String ARTIFACT = "artifact";
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070062
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070063 private static final String APP_XML = "app.xml";
Jian Lic35415d2016-01-14 17:22:31 -080064 private static final String ICON_PNG = "icon.png";
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070065 private static final String FEATURES_XML = "features.xml";
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070066
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070067 private static final String MVN_URL = "mvn:";
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070068 private static final String M2_PREFIX = "m2";
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070069
Thomas Vachuska586afd82015-04-17 11:06:53 -070070 private static final String ONOS_APP_NAME = "onos.app.name";
71 private static final String ONOS_APP_ORIGIN = "onos.app.origin";
Thomas Vachuska761f0042015-11-11 19:10:17 -080072 private static final String ONOS_APP_REQUIRES = "onos.app.requires";
Thomas Vachuska586afd82015-04-17 11:06:53 -070073
Jian Lic35415d2016-01-14 17:22:31 -080074 private static final String ONOS_APP_CATEGORY = "onos.app.category";
75 private static final String ONOS_APP_URL = "onos.app.url";
76 private static final String ONOS_APP_README = "onos.app.readme";
77
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070078 private static final String JAR = "jar";
79 private static final String XML = "xml";
80 private static final String APP_ZIP = "oar";
81 private static final String PACKAGE_DIR = "oar";
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070082
Thomas Vachuska586afd82015-04-17 11:06:53 -070083 private static final String DEFAULT_ORIGIN = "ON.Lab";
Thomas Vachuska41e8c182015-04-21 11:09:19 -070084 private static final String DEFAULT_VERSION = "${project.version}";
Thomas Vachuska586afd82015-04-17 11:06:53 -070085
Jian Lic35415d2016-01-14 17:22:31 -080086 private static final String DEFAULT_CATEGORY = "Default";
87 private static final String DEFAULT_URL = "http://onosproject.org";
88
Thomas Vachuska586afd82015-04-17 11:06:53 -070089 private static final String DEFAULT_FEATURES_REPO =
90 "mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features";
91 private static final String DEFAULT_ARTIFACT =
92 "mvn:${project.groupId}/${project.artifactId}/${project.version}";
93
Thomas Vachuska41e8c182015-04-21 11:09:19 -070094 private static final int BUFFER_SIZE = 8192;
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070095
Thomas Vachuska586afd82015-04-17 11:06:53 -070096 private String name;
97 private String origin;
Thomas Vachuska761f0042015-11-11 19:10:17 -080098 private String requiredApps;
Jian Lic35415d2016-01-14 17:22:31 -080099 private String category;
100 private String url;
101 private String readme;
Thomas Vachuska586afd82015-04-17 11:06:53 -0700102 private String version = DEFAULT_VERSION;
Thomas Vachuska586afd82015-04-17 11:06:53 -0700103 private String featuresRepo = DEFAULT_FEATURES_REPO;
Thomas Vachuska8c8b0372015-03-10 11:11:24 -0700104 private List<String> artifacts;
105
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700106 /**
107 * The project base directory.
108 */
109 @Parameter(defaultValue = "${basedir}")
110 protected File baseDir;
Thomas Vachuska8c8b0372015-03-10 11:11:24 -0700111
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700112 /**
113 * The directory where the generated catalogue file will be put.
114 */
115 @Parameter(defaultValue = "${project.build.directory}")
116 protected File dstDirectory;
117
118 /**
119 * The project group ID.
120 */
121 @Parameter(defaultValue = "${project.groupId}")
122 protected String projectGroupId;
123
124 /**
125 * The project artifact ID.
126 */
127 @Parameter(defaultValue = "${project.artifactId}")
128 protected String projectArtifactId;
129
130 /**
131 * The project version.
132 */
133 @Parameter(defaultValue = "${project.version}")
134 protected String projectVersion;
135
136 /**
137 * The project version.
138 */
139 @Parameter(defaultValue = "${project.description}")
140 protected String projectDescription;
141
Brian O'Connorbf7eca52015-04-15 16:25:46 -0700142 @Parameter(defaultValue = "${localRepository}")
143 protected ArtifactRepository localRepository;
144
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700145 /**
146 * Maven project
147 */
Brian O'Connorbf7eca52015-04-15 16:25:46 -0700148 @Parameter(defaultValue = "${project}")
149 protected MavenProject project;
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700150
151 /**
152 * Maven project helper.
153 */
154 @Component
Brian O'Connorbf7eca52015-04-15 16:25:46 -0700155 protected MavenProjectHelper projectHelper;
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700156
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700157 private File m2Directory;
158 protected File stageDirectory;
159 protected String projectPath;
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700160
161 @Override
Thomas Vachuska8c8b0372015-03-10 11:11:24 -0700162 public void execute() throws MojoExecutionException {
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700163 File appFile = new File(baseDir, APP_XML);
164 File featuresFile = new File(baseDir, FEATURES_XML);
Thomas Vachuska8c8b0372015-03-10 11:11:24 -0700165
Thomas Vachuska586afd82015-04-17 11:06:53 -0700166 name = (String) project.getProperties().get(ONOS_APP_NAME);
167
168 // If neither the app.xml file exists, nor the onos.app.name property
169 // is defined, there is nothing for this Mojo to do, so bail.
170 if (!appFile.exists() && name == null) {
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700171 return;
172 }
173
Brian O'Connorbf7eca52015-04-15 16:25:46 -0700174 m2Directory = new File(localRepository.getBasedir());
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700175 stageDirectory = new File(dstDirectory, PACKAGE_DIR);
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700176 projectPath = M2_PREFIX + "/" + artifactDir(projectGroupId, projectArtifactId, projectVersion);
177
Thomas Vachuska586afd82015-04-17 11:06:53 -0700178 origin = (String) project.getProperties().get(ONOS_APP_ORIGIN);
179 origin = origin != null ? origin : DEFAULT_ORIGIN;
180
Thomas Vachuska761f0042015-11-11 19:10:17 -0800181 requiredApps = (String) project.getProperties().get(ONOS_APP_REQUIRES);
Thomas Vachuskafa776af2015-11-18 00:57:10 -0800182 requiredApps = requiredApps == null ? "" : requiredApps.replaceAll("[\\s]", "");
Thomas Vachuska761f0042015-11-11 19:10:17 -0800183
Jian Lic35415d2016-01-14 17:22:31 -0800184 category = (String) project.getProperties().get(ONOS_APP_CATEGORY);
185 category = category != null ? category : DEFAULT_CATEGORY;
186
187 url = (String) project.getProperties().get(ONOS_APP_URL);
188 url = url != null ? url : DEFAULT_URL;
189
190 // if readme does not exist, we simply fallback to use description
191 readme = (String) project.getProperties().get(ONOS_APP_README);
192 readme = readme != null ? readme : projectDescription;
193
Thomas Vachuska586afd82015-04-17 11:06:53 -0700194 if (appFile.exists()) {
195 loadAppFile(appFile);
196 } else {
197 artifacts = ImmutableList.of(eval(DEFAULT_ARTIFACT));
198 }
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700199
200 // If there are any artifacts, stage the
201 if (!artifacts.isEmpty()) {
Thomas Vachuska586afd82015-04-17 11:06:53 -0700202 getLog().info("Building ONOS application package for " + name + " (v" + eval(version) + ")");
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700203 artifacts.forEach(a -> getLog().debug("Including artifact: " + a));
204
Brian O'Connorbf7eca52015-04-15 16:25:46 -0700205 if (stageDirectory.exists() || stageDirectory.mkdirs()) {
206 processAppXml(appFile);
207 processFeaturesXml(featuresFile);
208 processArtifacts();
209 generateAppPackage();
210 } else {
211 throw new MojoExecutionException("Unable to create directory: " + stageDirectory);
212 }
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700213 }
214 }
215
216 // Loads the app.xml file.
217 private void loadAppFile(File appFile) throws MojoExecutionException {
218 XMLConfiguration xml = new XMLConfiguration();
219 xml.setRootElementName(APP);
220
221 try (FileInputStream stream = new FileInputStream(appFile)) {
222 xml.load(stream);
223 xml.setAttributeSplittingDisabled(true);
224 xml.setDelimiterParsingDisabled(true);
225
226 name = xml.getString(NAME);
227 version = eval(xml.getString(VERSION));
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700228 featuresRepo = eval(xml.getString(FEATURES_REPO));
229
230 artifacts = xml.configurationsAt(ARTIFACT).stream()
231 .map(cfg -> eval(cfg.getRootNode().getValue().toString()))
232 .collect(Collectors.toList());
233
234 } catch (ConfigurationException e) {
235 throw new MojoExecutionException("Unable to parse app.xml file", e);
236 } catch (FileNotFoundException e) {
237 throw new MojoExecutionException("Unable to find app.xml file", e);
238 } catch (IOException e) {
239 throw new MojoExecutionException("Unable to read app.xml file", e);
240 }
241 }
242
243 // Processes and stages the app.xml file.
244 private void processAppXml(File appFile) throws MojoExecutionException {
245 try {
246 File file = new File(stageDirectory, APP_XML);
247 forceMkdir(stageDirectory);
Thomas Vachuska586afd82015-04-17 11:06:53 -0700248 String contents;
249
250 if (appFile.exists()) {
251 contents = fileRead(appFile);
252 } else {
253 byte[] bytes = toByteArray(getClass().getResourceAsStream(APP_XML));
254 contents = new String(bytes);
255 }
256 fileWrite(file.getAbsolutePath(), eval(contents));
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700257 } catch (IOException e) {
258 throw new MojoExecutionException("Unable to process app.xml", e);
259 }
260 }
261
262 private void processFeaturesXml(File featuresFile) throws MojoExecutionException {
263 boolean specified = featuresRepo != null && featuresRepo.length() > 0;
264
265 // If featuresRepo attribute is specified and there is a features.xml
266 // file present, add the features repo as an artifact
267 try {
268 if (specified && featuresFile.exists()) {
269 processFeaturesXml(new FileInputStream(featuresFile));
270 } else if (specified) {
271 processFeaturesXml(getClass().getResourceAsStream(FEATURES_XML));
272 }
273 } catch (FileNotFoundException e) {
274 throw new MojoExecutionException("Unable to find features.xml file", e);
275 } catch (IOException e) {
276 throw new MojoExecutionException("Unable to process features.xml file", e);
277 }
278 }
279
280 // Processes and stages the features.xml file.
281 private void processFeaturesXml(InputStream stream) throws IOException {
282 String featuresArtifact =
283 artifactFile(projectArtifactId, projectVersion, XML, "features");
284 File dstDir = new File(stageDirectory, projectPath);
285 forceMkdir(dstDir);
Thomas Vachuska586afd82015-04-17 11:06:53 -0700286 String s = eval(new String(toByteArray(stream)));
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700287 fileWrite(new File(dstDir, featuresArtifact).getAbsolutePath(), s);
288 }
289
290 // Stages all artifacts.
Thomas Vachuskaa7a02202015-04-14 23:13:02 -0700291 private void processArtifacts() throws MojoExecutionException {
292 for (String artifact : artifacts) {
293 processArtifact(artifact);
294 }
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700295 }
296
297 // Stages the specified artifact.
Thomas Vachuskaa7a02202015-04-14 23:13:02 -0700298 private void processArtifact(String artifact) throws MojoExecutionException {
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700299 if (!artifact.startsWith(MVN_URL)) {
Thomas Vachuskaa7a02202015-04-14 23:13:02 -0700300 throw new MojoExecutionException("Unsupported artifact URL:" + artifact);
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700301 }
302
303 String[] fields = artifact.substring(4).split("/");
304 if (fields.length < 3) {
Thomas Vachuskaa7a02202015-04-14 23:13:02 -0700305 throw new MojoExecutionException("Illegal artifact URL:" + artifact);
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700306 }
307
308 try {
309 String file = artifactFile(fields);
310
311 if (projectGroupId.equals(fields[0]) && projectArtifactId.equals(fields[1])) {
312 // Local artifact is not installed yet, package it from target directory.
313 File dstDir = new File(stageDirectory, projectPath);
314 forceMkdir(dstDir);
315 copyFile(new File(dstDirectory, file), new File(dstDir, file));
316 } else {
317 // Other artifacts are packaged from ~/.m2/repository directory.
318 String m2Path = artifactDir(fields);
319 File srcDir = new File(m2Directory, m2Path);
Thomas Vachuskaa7a02202015-04-14 23:13:02 -0700320 File dstDir = new File(stageDirectory, M2_PREFIX + "/" + m2Path);
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700321 forceMkdir(dstDir);
322 copyFile(new File(srcDir, file), new File(dstDir, file));
323 }
324 } catch (IOException e) {
Thomas Vachuskaa7a02202015-04-14 23:13:02 -0700325 throw new MojoExecutionException("Unable to stage artifact " + artifact, e);
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700326 }
327 }
328
329 // Generates the ONOS package ZIP file.
330 private void generateAppPackage() throws MojoExecutionException {
331 File appZip = new File(dstDirectory, artifactFile(projectArtifactId, projectVersion,
Jian Lic35415d2016-01-14 17:22:31 -0800332 APP_ZIP, null));
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700333 try (FileOutputStream fos = new FileOutputStream(appZip);
334 ZipOutputStream zos = new ZipOutputStream(fos)) {
335 zipDirectory("", stageDirectory, zos);
336 projectHelper.attachArtifact(this.project, APP_ZIP, null, appZip);
337 } catch (IOException e) {
338 throw new MojoExecutionException("Unable to compress application package", e);
339 }
340 }
341
342 // Generates artifact directory name from the specified fields.
343 private String artifactDir(String[] fields) {
344 return artifactDir(fields[0], fields[1], fields[2]);
345 }
346
347 // Generates artifact directory name from the specified elements.
348 private String artifactDir(String gid, String aid, String version) {
349 return gid.replace('.', '/') + "/" + aid.replace('.', '/') + "/" + version;
350 }
351
352 // Generates artifact file name from the specified fields.
353 private String artifactFile(String[] fields) {
354 return fields.length < 5 ?
355 artifactFile(fields[1], fields[2],
Jian Lic35415d2016-01-14 17:22:31 -0800356 (fields.length < 4 ? JAR : fields[3]), null) :
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700357 artifactFile(fields[1], fields[2], fields[3], fields[4]);
358 }
359
360 // Generates artifact file name from the specified elements.
361 private String artifactFile(String aid, String version, String type,
362 String classifier) {
363 return classifier == null ? aid + "-" + version + "." + type :
364 aid + "-" + version + "-" + classifier + "." + type;
365 }
366
367 // Returns the given string with project variable substitutions.
368 private String eval(String string) {
369 return string == null ? null :
Thomas Vachuska586afd82015-04-17 11:06:53 -0700370 string.replaceAll("\\$\\{onos.app.name\\}", name)
371 .replaceAll("\\$\\{onos.app.origin\\}", origin)
Thomas Vachuska761f0042015-11-11 19:10:17 -0800372 .replaceAll("\\$\\{onos.app.requires\\}", requiredApps)
Jian Lic35415d2016-01-14 17:22:31 -0800373 .replaceAll("\\$\\{onos.app.category\\}", category)
374 .replaceAll("\\$\\{onos.app.url\\}", url)
375 .replaceAll("\\$\\{onos.app.readme\\}", readme)
Thomas Vachuska586afd82015-04-17 11:06:53 -0700376 .replaceAll("\\$\\{project.groupId\\}", projectGroupId)
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700377 .replaceAll("\\$\\{project.artifactId\\}", projectArtifactId)
378 .replaceAll("\\$\\{project.version\\}", projectVersion)
Thomas Vachuska586afd82015-04-17 11:06:53 -0700379 .replaceAll("\\$\\{project.description\\}", projectDescription);
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700380 }
381
382 // Recursively archives the specified directory into a given ZIP stream.
383 private void zipDirectory(String root, File dir, ZipOutputStream zos)
384 throws IOException {
Thomas Vachuska41e8c182015-04-21 11:09:19 -0700385 byte[] buffer = new byte[BUFFER_SIZE];
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700386 File[] files = dir.listFiles();
387 if (files != null && files.length > 0) {
388 for (File file : files) {
389 if (file.isDirectory()) {
390 String path = root + file.getName() + "/";
391 zos.putNextEntry(new ZipEntry(path));
392 zipDirectory(path, file, zos);
393 zos.closeEntry();
394 } else {
395 FileInputStream fin = new FileInputStream(file);
396 zos.putNextEntry(new ZipEntry(root + file.getName()));
397 int length;
398 while ((length = fin.read(buffer)) > 0) {
399 zos.write(buffer, 0, length);
400 }
401 zos.closeEntry();
402 fin.close();
403 }
404 }
405 }
Thomas Vachuska8c8b0372015-03-10 11:11:24 -0700406 }
Jian Lic35415d2016-01-14 17:22:31 -0800407}