blob: 5558b13abdff93d7dd4c5486ce580785892b16ab [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;
Brian O'Connorbf7eca52015-04-15 16:25:46 -070021import org.apache.maven.artifact.repository.ArtifactRepository;
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070022import org.apache.maven.plugin.AbstractMojo;
23import org.apache.maven.plugin.MojoExecutionException;
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070024import org.apache.maven.plugins.annotations.Component;
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070025import org.apache.maven.plugins.annotations.LifecyclePhase;
26import org.apache.maven.plugins.annotations.Mojo;
27import org.apache.maven.plugins.annotations.Parameter;
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070028import org.apache.maven.project.MavenProject;
29import org.apache.maven.project.MavenProjectHelper;
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070030
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070031import java.io.File;
32import java.io.FileInputStream;
33import java.io.FileNotFoundException;
34import java.io.FileOutputStream;
35import java.io.IOException;
36import java.io.InputStream;
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070037import java.util.List;
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070038import java.util.stream.Collectors;
39import java.util.zip.ZipEntry;
40import java.util.zip.ZipOutputStream;
41
Thomas Vachuska586afd82015-04-17 11:06:53 -070042import static com.google.common.io.ByteStreams.toByteArray;
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070043import static org.codehaus.plexus.util.FileUtils.*;
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070044
45/**
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070046 * Produces ONOS application archive using the app.xml file information.
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070047 */
48@Mojo(name = "app", defaultPhase = LifecyclePhase.PACKAGE)
49public class OnosAppMojo extends AbstractMojo {
50
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070051 private static final String APP = "app";
52 private static final String NAME = "[@name]";
53 private static final String VERSION = "[@version]";
54 private static final String FEATURES_REPO = "[@featuresRepo]";
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070055 private static final String ARTIFACT = "artifact";
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070056
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070057 private static final String APP_XML = "app.xml";
58 private static final String FEATURES_XML = "features.xml";
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070059
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070060 private static final String MVN_URL = "mvn:";
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070061 private static final String M2_PREFIX = "m2";
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070062
Thomas Vachuska586afd82015-04-17 11:06:53 -070063 private static final String ONOS_APP_NAME = "onos.app.name";
64 private static final String ONOS_APP_ORIGIN = "onos.app.origin";
Thomas Vachuska761f0042015-11-11 19:10:17 -080065 private static final String ONOS_APP_REQUIRES = "onos.app.requires";
Thomas Vachuska586afd82015-04-17 11:06:53 -070066
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070067 private static final String JAR = "jar";
68 private static final String XML = "xml";
69 private static final String APP_ZIP = "oar";
70 private static final String PACKAGE_DIR = "oar";
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070071
Thomas Vachuska586afd82015-04-17 11:06:53 -070072 private static final String DEFAULT_ORIGIN = "ON.Lab";
Thomas Vachuska41e8c182015-04-21 11:09:19 -070073 private static final String DEFAULT_VERSION = "${project.version}";
Thomas Vachuska586afd82015-04-17 11:06:53 -070074
75 private static final String DEFAULT_FEATURES_REPO =
76 "mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features";
77 private static final String DEFAULT_ARTIFACT =
78 "mvn:${project.groupId}/${project.artifactId}/${project.version}";
79
Thomas Vachuska41e8c182015-04-21 11:09:19 -070080 private static final int BUFFER_SIZE = 8192;
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070081
Thomas Vachuska586afd82015-04-17 11:06:53 -070082 private String name;
83 private String origin;
Thomas Vachuska761f0042015-11-11 19:10:17 -080084 private String requiredApps;
Thomas Vachuska586afd82015-04-17 11:06:53 -070085 private String version = DEFAULT_VERSION;
Thomas Vachuska586afd82015-04-17 11:06:53 -070086 private String featuresRepo = DEFAULT_FEATURES_REPO;
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070087 private List<String> artifacts;
88
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070089 /**
90 * The project base directory.
91 */
92 @Parameter(defaultValue = "${basedir}")
93 protected File baseDir;
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070094
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070095 /**
96 * The directory where the generated catalogue file will be put.
97 */
98 @Parameter(defaultValue = "${project.build.directory}")
99 protected File dstDirectory;
100
101 /**
102 * The project group ID.
103 */
104 @Parameter(defaultValue = "${project.groupId}")
105 protected String projectGroupId;
106
107 /**
108 * The project artifact ID.
109 */
110 @Parameter(defaultValue = "${project.artifactId}")
111 protected String projectArtifactId;
112
113 /**
114 * The project version.
115 */
116 @Parameter(defaultValue = "${project.version}")
117 protected String projectVersion;
118
119 /**
120 * The project version.
121 */
122 @Parameter(defaultValue = "${project.description}")
123 protected String projectDescription;
124
Brian O'Connorbf7eca52015-04-15 16:25:46 -0700125 @Parameter(defaultValue = "${localRepository}")
126 protected ArtifactRepository localRepository;
127
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700128 /**
129 * Maven project
130 */
Brian O'Connorbf7eca52015-04-15 16:25:46 -0700131 @Parameter(defaultValue = "${project}")
132 protected MavenProject project;
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700133
134 /**
135 * Maven project helper.
136 */
137 @Component
Brian O'Connorbf7eca52015-04-15 16:25:46 -0700138 protected MavenProjectHelper projectHelper;
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700139
140
141 private File m2Directory;
142 protected File stageDirectory;
143 protected String projectPath;
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700144
145 @Override
Thomas Vachuska8c8b0372015-03-10 11:11:24 -0700146 public void execute() throws MojoExecutionException {
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700147 File appFile = new File(baseDir, APP_XML);
148 File featuresFile = new File(baseDir, FEATURES_XML);
Thomas Vachuska8c8b0372015-03-10 11:11:24 -0700149
Thomas Vachuska586afd82015-04-17 11:06:53 -0700150 name = (String) project.getProperties().get(ONOS_APP_NAME);
151
152 // If neither the app.xml file exists, nor the onos.app.name property
153 // is defined, there is nothing for this Mojo to do, so bail.
154 if (!appFile.exists() && name == null) {
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700155 return;
156 }
157
Brian O'Connorbf7eca52015-04-15 16:25:46 -0700158 m2Directory = new File(localRepository.getBasedir());
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700159 stageDirectory = new File(dstDirectory, PACKAGE_DIR);
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700160 projectPath = M2_PREFIX + "/" + artifactDir(projectGroupId, projectArtifactId, projectVersion);
161
Thomas Vachuska586afd82015-04-17 11:06:53 -0700162 origin = (String) project.getProperties().get(ONOS_APP_ORIGIN);
163 origin = origin != null ? origin : DEFAULT_ORIGIN;
164
Thomas Vachuska761f0042015-11-11 19:10:17 -0800165 requiredApps = (String) project.getProperties().get(ONOS_APP_REQUIRES);
166 requiredApps = requiredApps == null ? "" : requiredApps;
167
Thomas Vachuska586afd82015-04-17 11:06:53 -0700168 if (appFile.exists()) {
169 loadAppFile(appFile);
170 } else {
171 artifacts = ImmutableList.of(eval(DEFAULT_ARTIFACT));
172 }
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700173
174 // If there are any artifacts, stage the
175 if (!artifacts.isEmpty()) {
Thomas Vachuska586afd82015-04-17 11:06:53 -0700176 getLog().info("Building ONOS application package for " + name + " (v" + eval(version) + ")");
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700177 artifacts.forEach(a -> getLog().debug("Including artifact: " + a));
178
Brian O'Connorbf7eca52015-04-15 16:25:46 -0700179 if (stageDirectory.exists() || stageDirectory.mkdirs()) {
180 processAppXml(appFile);
181 processFeaturesXml(featuresFile);
182 processArtifacts();
183 generateAppPackage();
184 } else {
185 throw new MojoExecutionException("Unable to create directory: " + stageDirectory);
186 }
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700187 }
188 }
189
190 // Loads the app.xml file.
191 private void loadAppFile(File appFile) throws MojoExecutionException {
192 XMLConfiguration xml = new XMLConfiguration();
193 xml.setRootElementName(APP);
194
195 try (FileInputStream stream = new FileInputStream(appFile)) {
196 xml.load(stream);
197 xml.setAttributeSplittingDisabled(true);
198 xml.setDelimiterParsingDisabled(true);
199
200 name = xml.getString(NAME);
201 version = eval(xml.getString(VERSION));
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700202 featuresRepo = eval(xml.getString(FEATURES_REPO));
203
204 artifacts = xml.configurationsAt(ARTIFACT).stream()
205 .map(cfg -> eval(cfg.getRootNode().getValue().toString()))
206 .collect(Collectors.toList());
207
208 } catch (ConfigurationException e) {
209 throw new MojoExecutionException("Unable to parse app.xml file", e);
210 } catch (FileNotFoundException e) {
211 throw new MojoExecutionException("Unable to find app.xml file", e);
212 } catch (IOException e) {
213 throw new MojoExecutionException("Unable to read app.xml file", e);
214 }
215 }
216
217 // Processes and stages the app.xml file.
218 private void processAppXml(File appFile) throws MojoExecutionException {
219 try {
220 File file = new File(stageDirectory, APP_XML);
221 forceMkdir(stageDirectory);
Thomas Vachuska586afd82015-04-17 11:06:53 -0700222 String contents;
223
224 if (appFile.exists()) {
225 contents = fileRead(appFile);
226 } else {
227 byte[] bytes = toByteArray(getClass().getResourceAsStream(APP_XML));
228 contents = new String(bytes);
229 }
230 fileWrite(file.getAbsolutePath(), eval(contents));
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700231 } catch (IOException e) {
232 throw new MojoExecutionException("Unable to process app.xml", e);
233 }
234 }
235
236 private void processFeaturesXml(File featuresFile) throws MojoExecutionException {
237 boolean specified = featuresRepo != null && featuresRepo.length() > 0;
238
239 // If featuresRepo attribute is specified and there is a features.xml
240 // file present, add the features repo as an artifact
241 try {
242 if (specified && featuresFile.exists()) {
243 processFeaturesXml(new FileInputStream(featuresFile));
244 } else if (specified) {
245 processFeaturesXml(getClass().getResourceAsStream(FEATURES_XML));
246 }
247 } catch (FileNotFoundException e) {
248 throw new MojoExecutionException("Unable to find features.xml file", e);
249 } catch (IOException e) {
250 throw new MojoExecutionException("Unable to process features.xml file", e);
251 }
252 }
253
254 // Processes and stages the features.xml file.
255 private void processFeaturesXml(InputStream stream) throws IOException {
256 String featuresArtifact =
257 artifactFile(projectArtifactId, projectVersion, XML, "features");
258 File dstDir = new File(stageDirectory, projectPath);
259 forceMkdir(dstDir);
Thomas Vachuska586afd82015-04-17 11:06:53 -0700260 String s = eval(new String(toByteArray(stream)));
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700261 fileWrite(new File(dstDir, featuresArtifact).getAbsolutePath(), s);
262 }
263
264 // Stages all artifacts.
Thomas Vachuskaa7a02202015-04-14 23:13:02 -0700265 private void processArtifacts() throws MojoExecutionException {
266 for (String artifact : artifacts) {
267 processArtifact(artifact);
268 }
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700269 }
270
271 // Stages the specified artifact.
Thomas Vachuskaa7a02202015-04-14 23:13:02 -0700272 private void processArtifact(String artifact) throws MojoExecutionException {
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700273 if (!artifact.startsWith(MVN_URL)) {
Thomas Vachuskaa7a02202015-04-14 23:13:02 -0700274 throw new MojoExecutionException("Unsupported artifact URL:" + artifact);
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700275 }
276
277 String[] fields = artifact.substring(4).split("/");
278 if (fields.length < 3) {
Thomas Vachuskaa7a02202015-04-14 23:13:02 -0700279 throw new MojoExecutionException("Illegal artifact URL:" + artifact);
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700280 }
281
282 try {
283 String file = artifactFile(fields);
284
285 if (projectGroupId.equals(fields[0]) && projectArtifactId.equals(fields[1])) {
286 // Local artifact is not installed yet, package it from target directory.
287 File dstDir = new File(stageDirectory, projectPath);
288 forceMkdir(dstDir);
289 copyFile(new File(dstDirectory, file), new File(dstDir, file));
290 } else {
291 // Other artifacts are packaged from ~/.m2/repository directory.
292 String m2Path = artifactDir(fields);
293 File srcDir = new File(m2Directory, m2Path);
Thomas Vachuskaa7a02202015-04-14 23:13:02 -0700294 File dstDir = new File(stageDirectory, M2_PREFIX + "/" + m2Path);
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700295 forceMkdir(dstDir);
296 copyFile(new File(srcDir, file), new File(dstDir, file));
297 }
298 } catch (IOException e) {
Thomas Vachuskaa7a02202015-04-14 23:13:02 -0700299 throw new MojoExecutionException("Unable to stage artifact " + artifact, e);
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700300 }
301 }
302
303 // Generates the ONOS package ZIP file.
304 private void generateAppPackage() throws MojoExecutionException {
305 File appZip = new File(dstDirectory, artifactFile(projectArtifactId, projectVersion,
306 APP_ZIP, null));
307 try (FileOutputStream fos = new FileOutputStream(appZip);
308 ZipOutputStream zos = new ZipOutputStream(fos)) {
309 zipDirectory("", stageDirectory, zos);
310 projectHelper.attachArtifact(this.project, APP_ZIP, null, appZip);
311 } catch (IOException e) {
312 throw new MojoExecutionException("Unable to compress application package", e);
313 }
314 }
315
316 // Generates artifact directory name from the specified fields.
317 private String artifactDir(String[] fields) {
318 return artifactDir(fields[0], fields[1], fields[2]);
319 }
320
321 // Generates artifact directory name from the specified elements.
322 private String artifactDir(String gid, String aid, String version) {
323 return gid.replace('.', '/') + "/" + aid.replace('.', '/') + "/" + version;
324 }
325
326 // Generates artifact file name from the specified fields.
327 private String artifactFile(String[] fields) {
328 return fields.length < 5 ?
329 artifactFile(fields[1], fields[2],
330 (fields.length < 4 ? JAR : fields[3]), null) :
331 artifactFile(fields[1], fields[2], fields[3], fields[4]);
332 }
333
334 // Generates artifact file name from the specified elements.
335 private String artifactFile(String aid, String version, String type,
336 String classifier) {
337 return classifier == null ? aid + "-" + version + "." + type :
338 aid + "-" + version + "-" + classifier + "." + type;
339 }
340
341 // Returns the given string with project variable substitutions.
342 private String eval(String string) {
343 return string == null ? null :
Thomas Vachuska586afd82015-04-17 11:06:53 -0700344 string.replaceAll("\\$\\{onos.app.name\\}", name)
345 .replaceAll("\\$\\{onos.app.origin\\}", origin)
Thomas Vachuska761f0042015-11-11 19:10:17 -0800346 .replaceAll("\\$\\{onos.app.requires\\}", requiredApps)
Thomas Vachuska586afd82015-04-17 11:06:53 -0700347 .replaceAll("\\$\\{project.groupId\\}", projectGroupId)
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700348 .replaceAll("\\$\\{project.artifactId\\}", projectArtifactId)
349 .replaceAll("\\$\\{project.version\\}", projectVersion)
Thomas Vachuska586afd82015-04-17 11:06:53 -0700350 .replaceAll("\\$\\{project.description\\}", projectDescription);
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700351 }
352
353 // Recursively archives the specified directory into a given ZIP stream.
354 private void zipDirectory(String root, File dir, ZipOutputStream zos)
355 throws IOException {
Thomas Vachuska41e8c182015-04-21 11:09:19 -0700356 byte[] buffer = new byte[BUFFER_SIZE];
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700357 File[] files = dir.listFiles();
358 if (files != null && files.length > 0) {
359 for (File file : files) {
360 if (file.isDirectory()) {
361 String path = root + file.getName() + "/";
362 zos.putNextEntry(new ZipEntry(path));
363 zipDirectory(path, file, zos);
364 zos.closeEntry();
365 } else {
366 FileInputStream fin = new FileInputStream(file);
367 zos.putNextEntry(new ZipEntry(root + file.getName()));
368 int length;
369 while ((length = fin.read(buffer)) > 0) {
370 zos.write(buffer, 0, length);
371 }
372 zos.closeEntry();
373 fin.close();
374 }
375 }
376 }
Thomas Vachuska8c8b0372015-03-10 11:11:24 -0700377 }
378}