blob: 79de51ff54c03e20d28ed872ad9c2821b3e74810 [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 Vachuskaa98ae7f2015-04-14 14:00:36 -070018import com.google.common.io.ByteStreams;
19import 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
42import static org.codehaus.plexus.util.FileUtils.*;
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070043
44/**
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070045 * Produces ONOS application archive using the app.xml file information.
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070046 */
47@Mojo(name = "app", defaultPhase = LifecyclePhase.PACKAGE)
48public class OnosAppMojo extends AbstractMojo {
49
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070050 private static final String APP = "app";
51 private static final String NAME = "[@name]";
52 private static final String VERSION = "[@version]";
53 private static final String FEATURES_REPO = "[@featuresRepo]";
54 private static final String DESCRIPTION = "description";
55 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 Vachuskaa98ae7f2015-04-14 14:00:36 -070063 private static final String SNAPSHOT = "-SNAPSHOT";
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";
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070068
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070069 private static final int BUFFER_ZIZE = 8192;
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070070
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070071 private String name, version;
72 private String description, featuresRepo;
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070073 private List<String> artifacts;
74
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070075 /**
76 * The project base directory.
77 */
78 @Parameter(defaultValue = "${basedir}")
79 protected File baseDir;
Thomas Vachuska8c8b0372015-03-10 11:11:24 -070080
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -070081 /**
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;
104
105 /**
106 * The project version.
107 */
108 @Parameter(defaultValue = "${project.description}")
109 protected String projectDescription;
110
Brian O'Connorbf7eca52015-04-15 16:25:46 -0700111 @Parameter(defaultValue = "${localRepository}")
112 protected ArtifactRepository localRepository;
113
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700114 /**
115 * Maven project
116 */
Brian O'Connorbf7eca52015-04-15 16:25:46 -0700117 @Parameter(defaultValue = "${project}")
118 protected MavenProject project;
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700119
120 /**
121 * Maven project helper.
122 */
123 @Component
Brian O'Connorbf7eca52015-04-15 16:25:46 -0700124 protected MavenProjectHelper projectHelper;
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700125
126
127 private File m2Directory;
128 protected File stageDirectory;
129 protected String projectPath;
130 protected String featureVersion;
131
132 @Override
Thomas Vachuska8c8b0372015-03-10 11:11:24 -0700133 public void execute() throws MojoExecutionException {
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700134 File appFile = new File(baseDir, APP_XML);
135 File featuresFile = new File(baseDir, FEATURES_XML);
Thomas Vachuska8c8b0372015-03-10 11:11:24 -0700136
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700137 if (!appFile.exists()) {
138 return;
139 }
140
Brian O'Connorbf7eca52015-04-15 16:25:46 -0700141 m2Directory = new File(localRepository.getBasedir());
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700142 stageDirectory = new File(dstDirectory, PACKAGE_DIR);
143 featureVersion = projectVersion.replace(SNAPSHOT, "");
144 projectPath = M2_PREFIX + "/" + artifactDir(projectGroupId, projectArtifactId, projectVersion);
145
146 loadAppFile(appFile);
147
148 // If there are any artifacts, stage the
149 if (!artifacts.isEmpty()) {
150 getLog().info("Building ONOS application package for " + name + " (v" + version + ")");
151 artifacts.forEach(a -> getLog().debug("Including artifact: " + a));
152
Brian O'Connorbf7eca52015-04-15 16:25:46 -0700153 if (stageDirectory.exists() || stageDirectory.mkdirs()) {
154 processAppXml(appFile);
155 processFeaturesXml(featuresFile);
156 processArtifacts();
157 generateAppPackage();
158 } else {
159 throw new MojoExecutionException("Unable to create directory: " + stageDirectory);
160 }
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700161 }
162 }
163
164 // Loads the app.xml file.
165 private void loadAppFile(File appFile) throws MojoExecutionException {
166 XMLConfiguration xml = new XMLConfiguration();
167 xml.setRootElementName(APP);
168
169 try (FileInputStream stream = new FileInputStream(appFile)) {
170 xml.load(stream);
171 xml.setAttributeSplittingDisabled(true);
172 xml.setDelimiterParsingDisabled(true);
173
174 name = xml.getString(NAME);
175 version = eval(xml.getString(VERSION));
176 description = xml.getString(DESCRIPTION)
177 .replaceAll("\\$\\{project.description\\}", projectDescription);
178 featuresRepo = eval(xml.getString(FEATURES_REPO));
179
180 artifacts = xml.configurationsAt(ARTIFACT).stream()
181 .map(cfg -> eval(cfg.getRootNode().getValue().toString()))
182 .collect(Collectors.toList());
183
184 } catch (ConfigurationException e) {
185 throw new MojoExecutionException("Unable to parse app.xml file", e);
186 } catch (FileNotFoundException e) {
187 throw new MojoExecutionException("Unable to find app.xml file", e);
188 } catch (IOException e) {
189 throw new MojoExecutionException("Unable to read app.xml file", e);
190 }
191 }
192
193 // Processes and stages the app.xml file.
194 private void processAppXml(File appFile) throws MojoExecutionException {
195 try {
196 File file = new File(stageDirectory, APP_XML);
197 forceMkdir(stageDirectory);
198 String s = eval(fileRead(appFile));
199 fileWrite(file.getAbsolutePath(), s);
200 } catch (IOException e) {
201 throw new MojoExecutionException("Unable to process app.xml", e);
202 }
203 }
204
205 private void processFeaturesXml(File featuresFile) throws MojoExecutionException {
206 boolean specified = featuresRepo != null && featuresRepo.length() > 0;
207
208 // If featuresRepo attribute is specified and there is a features.xml
209 // file present, add the features repo as an artifact
210 try {
211 if (specified && featuresFile.exists()) {
212 processFeaturesXml(new FileInputStream(featuresFile));
213 } else if (specified) {
214 processFeaturesXml(getClass().getResourceAsStream(FEATURES_XML));
215 }
216 } catch (FileNotFoundException e) {
217 throw new MojoExecutionException("Unable to find features.xml file", e);
218 } catch (IOException e) {
219 throw new MojoExecutionException("Unable to process features.xml file", e);
220 }
221 }
222
223 // Processes and stages the features.xml file.
224 private void processFeaturesXml(InputStream stream) throws IOException {
225 String featuresArtifact =
226 artifactFile(projectArtifactId, projectVersion, XML, "features");
227 File dstDir = new File(stageDirectory, projectPath);
228 forceMkdir(dstDir);
229 String s = eval(new String(ByteStreams.toByteArray(stream)));
230 fileWrite(new File(dstDir, featuresArtifact).getAbsolutePath(), s);
231 }
232
233 // Stages all artifacts.
Thomas Vachuskaa7a02202015-04-14 23:13:02 -0700234 private void processArtifacts() throws MojoExecutionException {
235 for (String artifact : artifacts) {
236 processArtifact(artifact);
237 }
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700238 }
239
240 // Stages the specified artifact.
Thomas Vachuskaa7a02202015-04-14 23:13:02 -0700241 private void processArtifact(String artifact) throws MojoExecutionException {
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700242 if (!artifact.startsWith(MVN_URL)) {
Thomas Vachuskaa7a02202015-04-14 23:13:02 -0700243 throw new MojoExecutionException("Unsupported artifact URL:" + artifact);
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700244 }
245
246 String[] fields = artifact.substring(4).split("/");
247 if (fields.length < 3) {
Thomas Vachuskaa7a02202015-04-14 23:13:02 -0700248 throw new MojoExecutionException("Illegal artifact URL:" + artifact);
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700249 }
250
251 try {
252 String file = artifactFile(fields);
253
254 if (projectGroupId.equals(fields[0]) && projectArtifactId.equals(fields[1])) {
255 // Local artifact is not installed yet, package it from target directory.
256 File dstDir = new File(stageDirectory, projectPath);
257 forceMkdir(dstDir);
258 copyFile(new File(dstDirectory, file), new File(dstDir, file));
259 } else {
260 // Other artifacts are packaged from ~/.m2/repository directory.
261 String m2Path = artifactDir(fields);
262 File srcDir = new File(m2Directory, m2Path);
Thomas Vachuskaa7a02202015-04-14 23:13:02 -0700263 File dstDir = new File(stageDirectory, M2_PREFIX + "/" + m2Path);
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700264 forceMkdir(dstDir);
265 copyFile(new File(srcDir, file), new File(dstDir, file));
266 }
267 } catch (IOException e) {
Thomas Vachuskaa7a02202015-04-14 23:13:02 -0700268 throw new MojoExecutionException("Unable to stage artifact " + artifact, e);
Thomas Vachuskaa98ae7f2015-04-14 14:00:36 -0700269 }
270 }
271
272 // Generates the ONOS package ZIP file.
273 private void generateAppPackage() throws MojoExecutionException {
274 File appZip = new File(dstDirectory, artifactFile(projectArtifactId, projectVersion,
275 APP_ZIP, null));
276 try (FileOutputStream fos = new FileOutputStream(appZip);
277 ZipOutputStream zos = new ZipOutputStream(fos)) {
278 zipDirectory("", stageDirectory, zos);
279 projectHelper.attachArtifact(this.project, APP_ZIP, null, appZip);
280 } catch (IOException e) {
281 throw new MojoExecutionException("Unable to compress application package", e);
282 }
283 }
284
285 // Generates artifact directory name from the specified fields.
286 private String artifactDir(String[] fields) {
287 return artifactDir(fields[0], fields[1], fields[2]);
288 }
289
290 // Generates artifact directory name from the specified elements.
291 private String artifactDir(String gid, String aid, String version) {
292 return gid.replace('.', '/') + "/" + aid.replace('.', '/') + "/" + version;
293 }
294
295 // Generates artifact file name from the specified fields.
296 private String artifactFile(String[] fields) {
297 return fields.length < 5 ?
298 artifactFile(fields[1], fields[2],
299 (fields.length < 4 ? JAR : fields[3]), null) :
300 artifactFile(fields[1], fields[2], fields[3], fields[4]);
301 }
302
303 // Generates artifact file name from the specified elements.
304 private String artifactFile(String aid, String version, String type,
305 String classifier) {
306 return classifier == null ? aid + "-" + version + "." + type :
307 aid + "-" + version + "-" + classifier + "." + type;
308 }
309
310 // Returns the given string with project variable substitutions.
311 private String eval(String string) {
312 return string == null ? null :
313 string.replaceAll("\\$\\{project.groupId\\}", projectGroupId)
314 .replaceAll("\\$\\{project.artifactId\\}", projectArtifactId)
315 .replaceAll("\\$\\{project.version\\}", projectVersion)
316 .replaceAll("\\$\\{project.description\\}", description)
317 .replaceAll("\\$\\{feature.version\\}", featureVersion);
318 }
319
320 // Recursively archives the specified directory into a given ZIP stream.
321 private void zipDirectory(String root, File dir, ZipOutputStream zos)
322 throws IOException {
323 byte[] buffer = new byte[BUFFER_ZIZE];
324 File[] files = dir.listFiles();
325 if (files != null && files.length > 0) {
326 for (File file : files) {
327 if (file.isDirectory()) {
328 String path = root + file.getName() + "/";
329 zos.putNextEntry(new ZipEntry(path));
330 zipDirectory(path, file, zos);
331 zos.closeEntry();
332 } else {
333 FileInputStream fin = new FileInputStream(file);
334 zos.putNextEntry(new ZipEntry(root + file.getName()));
335 int length;
336 while ((length = fin.read(buffer)) > 0) {
337 zos.write(buffer, 0, length);
338 }
339 zos.closeEntry();
340 fin.close();
341 }
342 }
343 }
Thomas Vachuska8c8b0372015-03-10 11:11:24 -0700344 }
345}