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