blob: e63a78c776d73f684938f1b02414a34ed7a6514a [file] [log] [blame]
Thomas Vachuska02aeb032015-01-06 22:36:30 -08001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
Thomas Vachuska02aeb032015-01-06 22:36:30 -08003 *
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.common.app;
17
Thomas Vachuskaebf5e542015-02-03 19:38:13 -080018import com.google.common.collect.ImmutableList;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080019import com.google.common.collect.ImmutableSet;
Thomas Vachuska761f0042015-11-11 19:10:17 -080020import com.google.common.collect.Lists;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080021import com.google.common.io.ByteStreams;
22import com.google.common.io.Files;
23import org.apache.commons.configuration.ConfigurationException;
Changhoon Yoonb856b812015-08-10 03:47:19 +090024import org.apache.commons.configuration.HierarchicalConfiguration;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080025import org.apache.commons.configuration.XMLConfiguration;
Jian Lic35415d2016-01-14 17:22:31 -080026import org.apache.commons.lang.StringUtils;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080027import org.onlab.util.Tools;
Ray Milkey351d4562018-07-25 12:31:48 -070028import org.onlab.util.ZipValidator;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080029import org.onosproject.app.ApplicationDescription;
30import org.onosproject.app.ApplicationEvent;
31import org.onosproject.app.ApplicationException;
32import org.onosproject.app.ApplicationStoreDelegate;
33import org.onosproject.app.DefaultApplicationDescription;
Changhoon Yoonbdeb88a2015-05-12 20:35:31 +090034import org.onosproject.core.ApplicationRole;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080035import org.onosproject.core.Version;
Changhoon Yoonb856b812015-08-10 03:47:19 +090036import org.onosproject.security.AppPermission;
37import org.onosproject.security.Permission;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080038import org.onosproject.store.AbstractStore;
39import org.slf4j.Logger;
40import org.slf4j.LoggerFactory;
41
42import java.io.ByteArrayInputStream;
43import java.io.File;
44import java.io.FileInputStream;
45import java.io.FileNotFoundException;
46import java.io.IOException;
47import java.io.InputStream;
48import java.net.URI;
HIGUCHI Yuta436f8d52015-12-07 21:17:48 -080049import java.nio.charset.StandardCharsets;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080050import java.nio.file.NoSuchFileException;
Thomas Vachuskaebf5e542015-02-03 19:38:13 -080051import java.util.List;
Changhoon Yoonbdeb88a2015-05-12 20:35:31 +090052import java.util.Locale;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080053import java.util.Set;
54import java.util.zip.ZipEntry;
55import java.util.zip.ZipInputStream;
56
Thomas Vachuskae18a3302015-06-23 12:48:28 -070057import static com.google.common.base.Preconditions.checkState;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080058import static com.google.common.io.ByteStreams.toByteArray;
59import static com.google.common.io.Files.createParentDirs;
60import static com.google.common.io.Files.write;
61
62/**
63 * Facility for reading application archive stream and managing application
64 * directory structure.
65 */
66public class ApplicationArchive
67 extends AbstractStore<ApplicationEvent, ApplicationStoreDelegate> {
68
Thomas Vachuskaa7a0f562015-04-14 23:27:44 -070069 private static Logger log = LoggerFactory.getLogger(ApplicationArchive.class);
70
Thomas Vachuska62ad95f2015-02-18 12:11:36 -080071 // Magic strings to search for at the beginning of the archive stream
72 private static final String XML_MAGIC = "<?xml ";
Brian O'Connore4a4f992016-04-06 22:50:31 -070073 private static final String ZIP_MAGIC = "PK";
Thomas Vachuska62ad95f2015-02-18 12:11:36 -080074
75 // Magic strings to search for and how deep to search it into the archive stream
76 private static final String APP_MAGIC = "<app ";
77 private static final int APP_MAGIC_DEPTH = 1024;
78
Thomas Vachuska02aeb032015-01-06 22:36:30 -080079 private static final String NAME = "[@name]";
80 private static final String ORIGIN = "[@origin]";
81 private static final String VERSION = "[@version]";
82 private static final String FEATURES_REPO = "[@featuresRepo]";
83 private static final String FEATURES = "[@features]";
Thomas Vachuska761f0042015-11-11 19:10:17 -080084 private static final String APPS = "[@apps]";
Thomas Vachuska02aeb032015-01-06 22:36:30 -080085 private static final String DESCRIPTION = "description";
86
Jian Li01b0f5952016-01-20 11:02:07 -080087 private static final String UTILITY = "utility";
88
Jian Lic35415d2016-01-14 17:22:31 -080089 private static final String CATEGORY = "[@category]";
90 private static final String URL = "[@url]";
Simon Huntafae2f72016-03-04 21:18:23 -080091 private static final String TITLE = "[@title]";
Jian Lic35415d2016-01-14 17:22:31 -080092
Changhoon Yoonbdeb88a2015-05-12 20:35:31 +090093 private static final String ROLE = "security.role";
Changhoon Yoonb856b812015-08-10 03:47:19 +090094 private static final String APP_PERMISSIONS = "security.permissions.app-perm";
95 private static final String NET_PERMISSIONS = "security.permissions.net-perm";
96 private static final String JAVA_PERMISSIONS = "security.permissions.java-perm";
Changhoon Yoonbdeb88a2015-05-12 20:35:31 +090097
Thomas Vachuska6cc74882017-01-10 16:01:02 -080098 private static final String JAR = ".jar";
Thomas Vachuskaa7a0f562015-04-14 23:27:44 -070099 private static final String OAR = ".oar";
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800100 private static final String APP_XML = "app.xml";
Jian Li97d6b2d2016-01-20 10:13:43 -0800101 private static final String APP_PNG = "app.png";
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800102 private static final String M2_PREFIX = "m2";
Thomas Vachuska6cc74882017-01-10 16:01:02 -0800103 private static final String FEATURES_XML = "features.xml";
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800104
Thomas Vachuska40a398b2015-04-03 22:26:30 -0700105 private static final String ROOT = "../";
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800106 private static final String M2_ROOT = "system/";
Thomas Vachuskad5d9bcb2015-03-18 17:46:20 -0700107 private static final String APPS_ROOT = "apps/";
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800108
Thomas Vachuska40a398b2015-04-03 22:26:30 -0700109 private File root = new File(ROOT);
110 private File appsDir = new File(root, APPS_ROOT);
111 private File m2Dir = new File(M2_ROOT);
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800112
113 /**
Thomas Vachuska40a398b2015-04-03 22:26:30 -0700114 * Sets the root directory where apps directory is contained.
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800115 *
Thomas Vachuska40a398b2015-04-03 22:26:30 -0700116 * @param root top-level directory path
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800117 */
Thomas Vachuska40a398b2015-04-03 22:26:30 -0700118 protected void setRootPath(String root) {
119 this.root = new File(root);
120 this.appsDir = new File(this.root, APPS_ROOT);
121 this.m2Dir = new File(M2_ROOT);
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800122 }
123
124 /**
Thomas Vachuska40a398b2015-04-03 22:26:30 -0700125 * Returns the root directory where apps directory is contained.
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800126 *
Thomas Vachuska40a398b2015-04-03 22:26:30 -0700127 * @return top-level directory path
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800128 */
Thomas Vachuskae18a3302015-06-23 12:48:28 -0700129 public String getRootPath() {
Thomas Vachuska40a398b2015-04-03 22:26:30 -0700130 return root.getPath();
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800131 }
132
133 /**
134 * Returns the set of installed application names.
135 *
136 * @return installed application names
137 */
138 public Set<String> getApplicationNames() {
139 ImmutableSet.Builder<String> names = ImmutableSet.builder();
140 File[] files = appsDir.listFiles(File::isDirectory);
141 if (files != null) {
142 for (File file : files) {
143 names.add(file.getName());
144 }
145 }
146 return names.build();
147 }
148
149 /**
Thomas Vachuskacf960112015-03-06 22:36:51 -0800150 * Returns the timestamp in millis since start of epoch, of when the
151 * specified application was last modified or changed state.
152 *
153 * @param appName application name
154 * @return number of millis since start of epoch
155 */
156 public long getUpdateTime(String appName) {
157 return appFile(appName, APP_XML).lastModified();
158 }
159
160 /**
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800161 * Loads the application descriptor from the specified application archive
162 * stream and saves the stream in the appropriate application archive
163 * directory.
164 *
165 * @param appName application name
166 * @return application descriptor
167 * @throws org.onosproject.app.ApplicationException if unable to read application description
168 */
169 public ApplicationDescription getApplicationDescription(String appName) {
170 try {
Thomas Vachuskaad35c342015-06-11 17:25:36 -0700171 XMLConfiguration cfg = new XMLConfiguration();
172 cfg.setAttributeSplittingDisabled(true);
173 cfg.setDelimiterParsingDisabled(true);
174 cfg.load(appFile(appName, APP_XML));
175 return loadAppDescription(cfg);
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800176 } catch (Exception e) {
177 throw new ApplicationException("Unable to get app description", e);
178 }
179 }
180
181 /**
182 * Loads the application descriptor from the specified application archive
183 * stream and saves the stream in the appropriate application archive
184 * directory.
185 *
186 * @param stream application archive stream
187 * @return application descriptor
188 * @throws org.onosproject.app.ApplicationException if unable to read the
189 * archive stream or store
190 * the application archive
191 */
Thomas Vachuska0249b532015-02-20 16:46:18 -0800192 public synchronized ApplicationDescription saveApplication(InputStream stream) {
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800193 try (InputStream ais = stream) {
194 byte[] cache = toByteArray(ais);
195 InputStream bis = new ByteArrayInputStream(cache);
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800196
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800197 boolean plainXml = isPlainXml(cache);
198 ApplicationDescription desc = plainXml ?
199 parsePlainAppDescription(bis) : parseZippedAppDescription(bis);
Thomas Vachuskae18a3302015-06-23 12:48:28 -0700200 checkState(!appFile(desc.name(), APP_XML).exists(),
Jian Li97d6b2d2016-01-20 10:13:43 -0800201 "Application %s already installed", desc.name());
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800202
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800203 if (plainXml) {
204 expandPlainApplication(cache, desc);
205 } else {
206 bis.reset();
Thomas Vachuska08b4dec2017-08-31 15:20:17 -0700207 boolean isSelfContainedJar = expandZippedApplication(bis, desc);
Thomas Vachuska6cc74882017-01-10 16:01:02 -0800208
209 if (isSelfContainedJar) {
210 bis.reset();
211 stageSelfContainedJar(bis, desc);
212 }
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800213
214 bis.reset();
Thomas Vachuska6cc74882017-01-10 16:01:02 -0800215 saveApplication(bis, desc, isSelfContainedJar);
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800216 }
217
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800218 installArtifacts(desc);
219 return desc;
220 } catch (IOException e) {
221 throw new ApplicationException("Unable to save application", e);
222 }
223 }
224
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800225 // Indicates whether the stream encoded in the given bytes is plain XML.
226 private boolean isPlainXml(byte[] bytes) {
Brian O'Connore4a4f992016-04-06 22:50:31 -0700227 return !substring(bytes, ZIP_MAGIC.length()).equals(ZIP_MAGIC) &&
228 (substring(bytes, XML_MAGIC.length()).equals(XML_MAGIC) ||
229 substring(bytes, APP_MAGIC_DEPTH).contains(APP_MAGIC));
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800230 }
231
232 // Returns the substring of maximum possible length from the specified bytes.
233 private String substring(byte[] bytes, int length) {
HIGUCHI Yuta436f8d52015-12-07 21:17:48 -0800234 return new String(bytes, 0, Math.min(bytes.length, length), StandardCharsets.UTF_8);
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800235 }
236
Ray Milkey4b19da62018-07-08 10:06:19 -0700237 private String filterAppNameForFilesystem(String name) {
238 return name.replace("/", "^");
239 }
240
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800241 /**
242 * Purges the application archive directory.
243 *
244 * @param appName application name
245 */
Thomas Vachuska0249b532015-02-20 16:46:18 -0800246 public synchronized void purgeApplication(String appName) {
Ray Milkey4b19da62018-07-08 10:06:19 -0700247 File appDir = new File(appsDir, filterAppNameForFilesystem(appName));
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800248 try {
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800249 Tools.removeDirectory(appDir);
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800250 } catch (IOException e) {
251 throw new ApplicationException("Unable to purge application " + appName, e);
252 }
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800253 if (appDir.exists()) {
254 throw new ApplicationException("Unable to purge application " + appName);
255 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800256 }
257
258 /**
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800259 * Returns application archive stream for the specified application. This
Thomas Vachuska08b4dec2017-08-31 15:20:17 -0700260 * will be either the application OAR file, JAR file or the plain XML file.
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800261 *
262 * @param appName application name
263 * @return application archive stream
264 */
Thomas Vachuska0249b532015-02-20 16:46:18 -0800265 public synchronized InputStream getApplicationInputStream(String appName) {
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800266 try {
Thomas Vachuskaa7a0f562015-04-14 23:27:44 -0700267 File appFile = appFile(appName, appName + OAR);
Thomas Vachuskaad37e372017-08-03 12:07:01 -0700268 if (!appFile.exists()) {
269 appFile = appFile(appName, appName + JAR);
270 }
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800271 return new FileInputStream(appFile.exists() ? appFile : appFile(appName, APP_XML));
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800272 } catch (FileNotFoundException e) {
273 throw new ApplicationException("Application " + appName + " not found");
274 }
275 }
276
277 // Scans the specified ZIP stream for app.xml entry and parses it producing
278 // an application descriptor.
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800279 private ApplicationDescription parseZippedAppDescription(InputStream stream)
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800280 throws IOException {
281 try (ZipInputStream zis = new ZipInputStream(stream)) {
282 ZipEntry entry;
283 while ((entry = zis.getNextEntry()) != null) {
284 if (entry.getName().equals(APP_XML)) {
Thomas Vachuskaebf5e542015-02-03 19:38:13 -0800285 byte[] data = ByteStreams.toByteArray(zis);
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800286 return parsePlainAppDescription(new ByteArrayInputStream(data));
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800287 }
288 zis.closeEntry();
289 }
290 }
291 throw new IOException("Unable to locate " + APP_XML);
292 }
293
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800294 // Scans the specified XML stream and parses it producing an application descriptor.
295 private ApplicationDescription parsePlainAppDescription(InputStream stream)
296 throws IOException {
297 XMLConfiguration cfg = new XMLConfiguration();
Thomas Vachuskaad35c342015-06-11 17:25:36 -0700298 cfg.setAttributeSplittingDisabled(true);
299 cfg.setDelimiterParsingDisabled(true);
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800300 try {
301 cfg.load(stream);
302 return loadAppDescription(cfg);
303 } catch (ConfigurationException e) {
304 throw new IOException("Unable to parse " + APP_XML, e);
305 }
306 }
307
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800308 private ApplicationDescription loadAppDescription(XMLConfiguration cfg) {
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800309 String name = cfg.getString(NAME);
310 Version version = Version.version(cfg.getString(VERSION));
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800311 String origin = cfg.getString(ORIGIN);
Simon Huntafae2f72016-03-04 21:18:23 -0800312
313 String title = cfg.getString(TITLE);
314 // FIXME: title should be set as attribute to APP, but fallback for now...
315 title = title == null ? name : title;
316
Jian Li01b0f5952016-01-20 11:02:07 -0800317 String category = cfg.getString(CATEGORY, UTILITY);
Jian Lic35415d2016-01-14 17:22:31 -0800318 String url = cfg.getString(URL);
319 byte[] icon = getApplicationIcon(name);
Changhoon Yoonbdeb88a2015-05-12 20:35:31 +0900320 ApplicationRole role = getRole(cfg.getString(ROLE));
321 Set<Permission> perms = getPermissions(cfg);
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800322 String featRepo = cfg.getString(FEATURES_REPO);
323 URI featuresRepo = featRepo != null ? URI.create(featRepo) : null;
Thomas Vachuskaad35c342015-06-11 17:25:36 -0700324 List<String> features = ImmutableList.copyOf(cfg.getString(FEATURES).split(","));
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800325
Thomas Vachuska761f0042015-11-11 19:10:17 -0800326 String apps = cfg.getString(APPS, "");
327 List<String> requiredApps = apps.isEmpty() ?
328 ImmutableList.of() : ImmutableList.copyOf(apps.split(","));
329
Jian Li8bcb4f22016-01-20 10:36:18 -0800330 // put full description to readme field
331 String readme = cfg.getString(DESCRIPTION);
Jian Lic35415d2016-01-14 17:22:31 -0800332
Jian Li8bcb4f22016-01-20 10:36:18 -0800333 // put short description to description field
334 String desc = compactDescription(readme);
Jian Lic35415d2016-01-14 17:22:31 -0800335
Ray Milkey47c95412017-09-15 10:40:48 -0700336 return DefaultApplicationDescription.builder()
337 .withName(name)
338 .withVersion(version)
339 .withTitle(title)
340 .withDescription(desc)
341 .withOrigin(origin)
342 .withCategory(category)
343 .withUrl(url)
344 .withReadme(readme)
345 .withIcon(icon)
346 .withRole(role)
347 .withPermissions(perms)
348 .withFeaturesRepo(featuresRepo)
349 .withFeatures(features)
350 .withRequiredApps(requiredApps)
351 .build();
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800352 }
353
354 // Expands the specified ZIP stream into app-specific directory.
Thomas Vachuska6cc74882017-01-10 16:01:02 -0800355 // Returns true of the application is a self-contained jar rather than an oar file.
356 private boolean expandZippedApplication(InputStream stream, ApplicationDescription desc)
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800357 throws IOException {
Thomas Vachuska6cc74882017-01-10 16:01:02 -0800358 boolean isSelfContained = false;
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800359 ZipInputStream zis = new ZipInputStream(stream);
360 ZipEntry entry;
Ray Milkey4b19da62018-07-08 10:06:19 -0700361 File appDir = new File(appsDir, filterAppNameForFilesystem(desc.name()));
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800362 while ((entry = zis.getNextEntry()) != null) {
Thomas Vachuskaebf5e542015-02-03 19:38:13 -0800363 if (!entry.isDirectory()) {
364 byte[] data = ByteStreams.toByteArray(zis);
365 zis.closeEntry();
Ray Milkey351d4562018-07-25 12:31:48 -0700366 if (ZipValidator.validateZipEntry(entry, appDir)) {
367 File file = new File(appDir, entry.getName());
368 if (isTopLevel(file)) {
369 createParentDirs(file);
370 write(data, file);
371 } else {
372 isSelfContained = true;
373 }
Thomas Vachuska6cc74882017-01-10 16:01:02 -0800374 } else {
Ray Milkey351d4562018-07-25 12:31:48 -0700375 throw new ApplicationException("Application Zip archive is attempting to leave application root");
Thomas Vachuska6cc74882017-01-10 16:01:02 -0800376 }
Thomas Vachuskaebf5e542015-02-03 19:38:13 -0800377 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800378 }
379 zis.close();
Thomas Vachuska6cc74882017-01-10 16:01:02 -0800380 return isSelfContained;
381 }
382
383 // Returns true if the specified file is a top-level app file, i.e. app.xml,
384 // features.xml, .jar or a directory; false if anything else.
385 private boolean isTopLevel(File file) {
386 String name = file.getName();
387 return name.equals(APP_XML) || name.endsWith(FEATURES_XML) || name.endsWith(JAR) || file.isDirectory();
388 }
389
390 // Expands the self-contained JAR stream into the app-specific directory,
391 // using the bundle coordinates retrieved from the features.xml file.
392 private void stageSelfContainedJar(InputStream stream, ApplicationDescription desc)
393 throws IOException {
394 // First extract the bundle coordinates
395 String coords = getSelfContainedBundleCoordinates(desc);
396 if (coords == null) {
397 return;
398 }
399
400 // Split the coordinates into segments and build the file name.
401 String[] f = coords.substring(4).split("/");
402 String base = "m2/" + f[0].replace('.', '/') + "/" + f[1] + "/" + f[2] + "/" + f[1] + "-" + f[2];
403 String jarName = base + (f.length < 4 ? "" : "-" + f[3]) + ".jar";
404 String featuresName = base + "-features.xml";
405
406 // Create the file directory structure and copy the file there.
407 File jar = appFile(desc.name(), jarName);
Thomas Vachuskaad37e372017-08-03 12:07:01 -0700408 boolean ok = jar.getParentFile().exists() || jar.getParentFile().mkdirs();
Thomas Vachuska6cc74882017-01-10 16:01:02 -0800409 if (ok) {
410 Files.write(toByteArray(stream), jar);
411 Files.copy(appFile(desc.name(), FEATURES_XML), appFile(desc.name(), featuresName));
412 if (!appFile(desc.name(), FEATURES_XML).delete()) {
413 log.warn("Unable to delete self-contained application {} features.xml", desc.name());
414 }
415 } else {
416 throw new IOException("Unable to save self-contained application " + desc.name());
417 }
418 }
419
420 // Returns the bundle coordinates from the features.xml file.
421 private String getSelfContainedBundleCoordinates(ApplicationDescription desc) {
422 try {
423 XMLConfiguration cfg = new XMLConfiguration();
424 cfg.setAttributeSplittingDisabled(true);
425 cfg.setDelimiterParsingDisabled(true);
426 cfg.load(appFile(desc.name(), FEATURES_XML));
Thomas Vachuskaad37e372017-08-03 12:07:01 -0700427 return cfg.getString("feature.bundle")
428 .replaceFirst("wrap:", "")
429 .replaceFirst("\\$Bundle-.*$", "");
Thomas Vachuska6cc74882017-01-10 16:01:02 -0800430 } catch (ConfigurationException e) {
431 log.warn("Self-contained application {} has no features.xml", desc.name());
432 return null;
433 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800434 }
435
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800436 // Saves the specified XML stream into app-specific directory.
437 private void expandPlainApplication(byte[] stream, ApplicationDescription desc)
438 throws IOException {
439 File file = appFile(desc.name(), APP_XML);
Thomas Vachuskae18a3302015-06-23 12:48:28 -0700440 checkState(!file.getParentFile().exists(), "Application already installed");
Thomas Vachuska62ad95f2015-02-18 12:11:36 -0800441 createParentDirs(file);
442 write(stream, file);
443 }
444
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800445 // Saves the specified ZIP stream into a file under app-specific directory.
Thomas Vachuska6cc74882017-01-10 16:01:02 -0800446 private void saveApplication(InputStream stream, ApplicationDescription desc,
447 boolean isSelfContainedJar)
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800448 throws IOException {
Ray Milkey4b19da62018-07-08 10:06:19 -0700449 String name = filterAppNameForFilesystem(desc.name()) + (isSelfContainedJar ? JAR : OAR);
Thomas Vachuska6cc74882017-01-10 16:01:02 -0800450 Files.write(toByteArray(stream), appFile(desc.name(), name));
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800451 }
452
453 // Installs application artifacts into M2 repository.
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800454 private void installArtifacts(ApplicationDescription desc) throws IOException {
455 try {
456 Tools.copyDirectory(appFile(desc.name(), M2_PREFIX), m2Dir);
457 } catch (NoSuchFileException e) {
458 log.debug("Application {} has no M2 artifacts", desc.name());
459 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800460 }
461
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800462 /**
463 * Marks the app as active by creating token file in the app directory.
464 *
465 * @param appName application name
466 * @return true if file was created
467 */
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800468 protected boolean setActive(String appName) {
469 try {
Thomas Vachuskae965b3d2016-03-03 11:42:48 -0800470 File active = appFile(appName, "active");
471 createParentDirs(active);
472 return active.createNewFile() && updateTime(appName);
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800473 } catch (IOException e) {
Thomas Vachuskae965b3d2016-03-03 11:42:48 -0800474 log.warn("Unable to mark app {} as active", appName, e);
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800475 throw new ApplicationException("Unable to mark app as active", e);
476 }
477 }
478
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800479 /**
480 * Clears the app as active by deleting token file in the app directory.
481 *
482 * @param appName application name
483 * @return true if file was deleted
484 */
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800485 protected boolean clearActive(String appName) {
Thomas Vachuskacf960112015-03-06 22:36:51 -0800486 return appFile(appName, "active").delete() && updateTime(appName);
487 }
488
489 /**
490 * Updates the time-stamp of the app descriptor file.
491 *
492 * @param appName application name
493 * @return true if the app descriptor was updated
494 */
Thomas Vachuska161baf52015-03-27 16:15:39 -0700495 protected boolean updateTime(String appName) {
Thomas Vachuskacf960112015-03-06 22:36:51 -0800496 return appFile(appName, APP_XML).setLastModified(System.currentTimeMillis());
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800497 }
498
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800499 /**
500 * Indicates whether the app was marked as active by checking for token file.
501 *
502 * @param appName application name
503 * @return true if the app is marked as active
504 */
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800505 protected boolean isActive(String appName) {
506 return appFile(appName, "active").exists();
507 }
508
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800509 // Returns the name of the file located under the specified app directory.
510 private File appFile(String appName, String fileName) {
Ray Milkey4b19da62018-07-08 10:06:19 -0700511 return new File(new File(appsDir, filterAppNameForFilesystem(appName)), fileName);
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800512 }
513
Jian Li97d6b2d2016-01-20 10:13:43 -0800514 // Returns the icon file located under the specified app directory.
515 private File iconFile(String appName, String fileName) {
516 return new File(new File(appsDir, appName), fileName);
517 }
518
Changhoon Yoonbdeb88a2015-05-12 20:35:31 +0900519 // Returns the set of Permissions specified in the app.xml file
520 private ImmutableSet<Permission> getPermissions(XMLConfiguration cfg) {
Thomas Vachuska761f0042015-11-11 19:10:17 -0800521 List<Permission> permissionList = Lists.newArrayList();
Changhoon Yoonb856b812015-08-10 03:47:19 +0900522
523 for (Object o : cfg.getList(APP_PERMISSIONS)) {
Changhoon Yoona7841ed2015-05-15 02:51:08 +0900524 String name = (String) o;
Changhoon Yoonb856b812015-08-10 03:47:19 +0900525 permissionList.add(new Permission(AppPermission.class.getName(), name));
526 }
527 for (Object o : cfg.getList(NET_PERMISSIONS)) {
528 //TODO: TO BE FLESHED OUT WHEN NETWORK PERMISSIONS ARE SUPPORTED
529 break;
530 }
531
532 List<HierarchicalConfiguration> fields =
533 cfg.configurationsAt(JAVA_PERMISSIONS);
534 for (HierarchicalConfiguration sub : fields) {
535 String classname = sub.getString("classname");
536 String name = sub.getString("name");
537 String actions = sub.getString("actions");
538
539 if (classname != null && name != null) {
540 permissionList.add(new Permission(classname, name, actions));
Changhoon Yoonbdeb88a2015-05-12 20:35:31 +0900541 }
542 }
Changhoon Yoona7841ed2015-05-15 02:51:08 +0900543 return ImmutableSet.copyOf(permissionList);
Changhoon Yoonbdeb88a2015-05-12 20:35:31 +0900544 }
545
Jian Lic35415d2016-01-14 17:22:31 -0800546 // Returns the byte stream from icon.png file in oar application archive.
547 private byte[] getApplicationIcon(String appName) {
Jian Li97d6b2d2016-01-20 10:13:43 -0800548 File iconFile = iconFile(appName, APP_PNG);
Jian Lic35415d2016-01-14 17:22:31 -0800549 try {
Brian O'Connora7903ae2016-04-29 15:02:32 -0700550 final InputStream iconStream;
551 if (iconFile.exists()) {
552 iconStream = new FileInputStream(iconFile);
553 } else {
554 // assume that we can always fallback to default icon
555 iconStream = ApplicationArchive.class.getResourceAsStream("/" + APP_PNG);
556 }
557 return ByteStreams.toByteArray(iconStream);
Jian Lic35415d2016-01-14 17:22:31 -0800558 } catch (IOException e) {
Brian O'Connora7903ae2016-04-29 15:02:32 -0700559 log.warn("Unable to read app icon for app {}", appName, e);
Jian Lic35415d2016-01-14 17:22:31 -0800560 }
Brian O'Connora7903ae2016-04-29 15:02:32 -0700561 return new byte[0];
Jian Lic35415d2016-01-14 17:22:31 -0800562 }
563
Changhoon Yoonbdeb88a2015-05-12 20:35:31 +0900564 // Returns application role type
565 public ApplicationRole getRole(String value) {
566 if (value == null) {
567 return ApplicationRole.UNSPECIFIED;
568 } else {
569 try {
570 return ApplicationRole.valueOf(value.toUpperCase(Locale.ENGLISH));
571 } catch (IllegalArgumentException e) {
572 log.debug("Unknown role value: %s", value);
573 return ApplicationRole.UNSPECIFIED;
574 }
575 }
576 }
Jian Lic35415d2016-01-14 17:22:31 -0800577
578 // Returns the first sentence of the given sentence
579 private String compactDescription(String sentence) {
580 if (StringUtils.isNotEmpty(sentence)) {
581 if (StringUtils.contains(sentence, ".")) {
582 return StringUtils.substringBefore(sentence, ".") + ".";
583 } else {
Jian Li8bcb4f22016-01-20 10:36:18 -0800584 return sentence;
Jian Lic35415d2016-01-14 17:22:31 -0800585 }
586 }
587 return sentence;
588 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800589}