blob: 0260863e05b2f377df35eb29ae9afa03368e1504 [file] [log] [blame]
Thomas Vachuska02aeb032015-01-06 22:36:30 -08001/*
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.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;
20import com.google.common.io.ByteStreams;
21import com.google.common.io.Files;
22import org.apache.commons.configuration.ConfigurationException;
23import org.apache.commons.configuration.XMLConfiguration;
24import org.onlab.util.Tools;
25import org.onosproject.app.ApplicationDescription;
26import org.onosproject.app.ApplicationEvent;
27import org.onosproject.app.ApplicationException;
28import org.onosproject.app.ApplicationStoreDelegate;
29import org.onosproject.app.DefaultApplicationDescription;
30import org.onosproject.core.Permission;
31import org.onosproject.core.Version;
32import org.onosproject.store.AbstractStore;
33import org.slf4j.Logger;
34import org.slf4j.LoggerFactory;
35
36import java.io.ByteArrayInputStream;
37import java.io.File;
38import java.io.FileInputStream;
39import java.io.FileNotFoundException;
40import java.io.IOException;
41import java.io.InputStream;
42import java.net.URI;
Thomas Vachuska90b453f2015-01-30 18:57:14 -080043import java.nio.file.NoSuchFileException;
Thomas Vachuskaebf5e542015-02-03 19:38:13 -080044import java.util.List;
Thomas Vachuska02aeb032015-01-06 22:36:30 -080045import java.util.Set;
46import java.util.zip.ZipEntry;
47import java.util.zip.ZipInputStream;
48
49import static com.google.common.io.ByteStreams.toByteArray;
50import static com.google.common.io.Files.createParentDirs;
51import static com.google.common.io.Files.write;
52
53/**
54 * Facility for reading application archive stream and managing application
55 * directory structure.
56 */
57public class ApplicationArchive
58 extends AbstractStore<ApplicationEvent, ApplicationStoreDelegate> {
59
60 private static final String NAME = "[@name]";
61 private static final String ORIGIN = "[@origin]";
62 private static final String VERSION = "[@version]";
63 private static final String FEATURES_REPO = "[@featuresRepo]";
64 private static final String FEATURES = "[@features]";
65 private static final String DESCRIPTION = "description";
66
67 private static Logger log = LoggerFactory.getLogger(ApplicationArchive.class);
68 private static final String APP_XML = "app.xml";
Thomas Vachuska90b453f2015-01-30 18:57:14 -080069 private static final String M2_PREFIX = "m2";
70
71 private static final String KARAF_ROOT = ".";
72 private static final String M2_ROOT = "system/";
Thomas Vachuska02aeb032015-01-06 22:36:30 -080073 private static final String APPS_ROOT = "data/apps/";
74
Thomas Vachuska90b453f2015-01-30 18:57:14 -080075 private File karafRoot = new File(KARAF_ROOT);
76 private File m2Dir = new File(karafRoot, M2_ROOT);
77 private File appsDir = new File(karafRoot, APPS_ROOT);
Thomas Vachuska02aeb032015-01-06 22:36:30 -080078
79 /**
80 * Sets the root directory where application artifacts are kept.
81 *
82 * @param appsRoot top-level applications directory path
83 */
Thomas Vachuska90b453f2015-01-30 18:57:14 -080084 protected void setRootPath(String appsRoot) {
85 this.karafRoot = new File(appsRoot);
86 this.appsDir = new File(karafRoot, APPS_ROOT);
87 this.m2Dir = new File(karafRoot, M2_ROOT);
Thomas Vachuska02aeb032015-01-06 22:36:30 -080088 }
89
90 /**
91 * Returns the root directory where application artifacts are kept.
92 *
93 * @return top-level applications directory path
94 */
Thomas Vachuska90b453f2015-01-30 18:57:14 -080095 protected String getRootPath() {
96 return karafRoot.getPath();
Thomas Vachuska02aeb032015-01-06 22:36:30 -080097 }
98
99 /**
100 * Returns the set of installed application names.
101 *
102 * @return installed application names
103 */
104 public Set<String> getApplicationNames() {
105 ImmutableSet.Builder<String> names = ImmutableSet.builder();
106 File[] files = appsDir.listFiles(File::isDirectory);
107 if (files != null) {
108 for (File file : files) {
109 names.add(file.getName());
110 }
111 }
112 return names.build();
113 }
114
115 /**
116 * Loads the application descriptor from the specified application archive
117 * stream and saves the stream in the appropriate application archive
118 * directory.
119 *
120 * @param appName application name
121 * @return application descriptor
122 * @throws org.onosproject.app.ApplicationException if unable to read application description
123 */
124 public ApplicationDescription getApplicationDescription(String appName) {
125 try {
126 return loadAppDescription(new XMLConfiguration(appFile(appName, APP_XML)));
127 } catch (Exception e) {
128 throw new ApplicationException("Unable to get app description", e);
129 }
130 }
131
132 /**
133 * Loads the application descriptor from the specified application archive
134 * stream and saves the stream in the appropriate application archive
135 * directory.
136 *
137 * @param stream application archive stream
138 * @return application descriptor
139 * @throws org.onosproject.app.ApplicationException if unable to read the
140 * archive stream or store
141 * the application archive
142 */
143 public ApplicationDescription saveApplication(InputStream stream) {
144 try (InputStream ais = stream) {
145 byte[] cache = toByteArray(ais);
146 InputStream bis = new ByteArrayInputStream(cache);
147 ApplicationDescription desc = parseAppDescription(bis);
148 bis.reset();
149
150 expandApplication(bis, desc);
151 bis.reset();
152
153 saveApplication(bis, desc);
154 installArtifacts(desc);
155 return desc;
156 } catch (IOException e) {
157 throw new ApplicationException("Unable to save application", e);
158 }
159 }
160
161 /**
162 * Purges the application archive directory.
163 *
164 * @param appName application name
165 */
166 public void purgeApplication(String appName) {
167 try {
168 Tools.removeDirectory(new File(appsDir, appName));
169 } catch (IOException e) {
170 throw new ApplicationException("Unable to purge application " + appName, e);
171 }
172 }
173
174 /**
175 * Returns application archive stream for the specified application.
176 *
177 * @param appName application name
178 * @return application archive stream
179 */
180 public InputStream getApplicationInputStream(String appName) {
181 try {
182 return new FileInputStream(appFile(appName, appName + ".zip"));
183 } catch (FileNotFoundException e) {
184 throw new ApplicationException("Application " + appName + " not found");
185 }
186 }
187
188 // Scans the specified ZIP stream for app.xml entry and parses it producing
189 // an application descriptor.
190 private ApplicationDescription parseAppDescription(InputStream stream)
191 throws IOException {
192 try (ZipInputStream zis = new ZipInputStream(stream)) {
193 ZipEntry entry;
194 while ((entry = zis.getNextEntry()) != null) {
195 if (entry.getName().equals(APP_XML)) {
Thomas Vachuskaebf5e542015-02-03 19:38:13 -0800196 byte[] data = ByteStreams.toByteArray(zis);
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800197 XMLConfiguration cfg = new XMLConfiguration();
198 try {
199 cfg.load(new ByteArrayInputStream(data));
200 return loadAppDescription(cfg);
201 } catch (ConfigurationException e) {
202 throw new IOException("Unable to parse " + APP_XML, e);
203 }
204 }
205 zis.closeEntry();
206 }
207 }
208 throw new IOException("Unable to locate " + APP_XML);
209 }
210
211 private ApplicationDescription loadAppDescription(XMLConfiguration cfg) {
212 cfg.setAttributeSplittingDisabled(true);
Thomas Vachuskaebf5e542015-02-03 19:38:13 -0800213 cfg.setDelimiterParsingDisabled(true);
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800214 String name = cfg.getString(NAME);
215 Version version = Version.version(cfg.getString(VERSION));
216 String desc = cfg.getString(DESCRIPTION);
217 String origin = cfg.getString(ORIGIN);
218 Set<Permission> perms = ImmutableSet.of();
219 String featRepo = cfg.getString(FEATURES_REPO);
220 URI featuresRepo = featRepo != null ? URI.create(featRepo) : null;
Thomas Vachuskaebf5e542015-02-03 19:38:13 -0800221 List<String> features = ImmutableList.copyOf(cfg.getStringArray(FEATURES));
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800222
223 return new DefaultApplicationDescription(name, version, desc, origin,
224 perms, featuresRepo, features);
225 }
226
227 // Expands the specified ZIP stream into app-specific directory.
228 private void expandApplication(InputStream stream, ApplicationDescription desc)
229 throws IOException {
230 ZipInputStream zis = new ZipInputStream(stream);
231 ZipEntry entry;
232 File appDir = new File(appsDir, desc.name());
233 while ((entry = zis.getNextEntry()) != null) {
Thomas Vachuskaebf5e542015-02-03 19:38:13 -0800234 if (!entry.isDirectory()) {
235 byte[] data = ByteStreams.toByteArray(zis);
236 zis.closeEntry();
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800237
Thomas Vachuskaebf5e542015-02-03 19:38:13 -0800238 File file = new File(appDir, entry.getName());
239 createParentDirs(file);
240 write(data, file);
241 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800242 }
243 zis.close();
244 }
245
246 // Saves the specified ZIP stream into a file under app-specific directory.
247 private void saveApplication(InputStream stream, ApplicationDescription desc)
248 throws IOException {
249 Files.write(toByteArray(stream), appFile(desc.name(), desc.name() + ".zip"));
250 }
251
252 // Installs application artifacts into M2 repository.
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800253 private void installArtifacts(ApplicationDescription desc) throws IOException {
254 try {
255 Tools.copyDirectory(appFile(desc.name(), M2_PREFIX), m2Dir);
256 } catch (NoSuchFileException e) {
257 log.debug("Application {} has no M2 artifacts", desc.name());
258 }
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800259 }
260
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800261 /**
262 * Marks the app as active by creating token file in the app directory.
263 *
264 * @param appName application name
265 * @return true if file was created
266 */
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800267 protected boolean setActive(String appName) {
268 try {
269 return appFile(appName, "active").createNewFile();
270 } catch (IOException e) {
271 throw new ApplicationException("Unable to mark app as active", e);
272 }
273 }
274
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800275 /**
276 * Clears the app as active by deleting token file in the app directory.
277 *
278 * @param appName application name
279 * @return true if file was deleted
280 */
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800281 protected boolean clearActive(String appName) {
282 return appFile(appName, "active").delete();
283 }
284
Thomas Vachuska90b453f2015-01-30 18:57:14 -0800285 /**
286 * Indicates whether the app was marked as active by checking for token file.
287 *
288 * @param appName application name
289 * @return true if the app is marked as active
290 */
Thomas Vachuska02aeb032015-01-06 22:36:30 -0800291 protected boolean isActive(String appName) {
292 return appFile(appName, "active").exists();
293 }
294
295
296 // Returns the name of the file located under the specified app directory.
297 private File appFile(String appName, String fileName) {
298 return new File(new File(appsDir, appName), fileName);
299 }
300
301}