1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.opencastproject.staticfiles.impl;
23
24 import static java.lang.String.format;
25 import static org.opencastproject.util.RequireUtil.notNull;
26
27 import org.opencastproject.security.api.Organization;
28 import org.opencastproject.security.api.OrganizationDirectoryService;
29 import org.opencastproject.security.api.SecurityService;
30 import org.opencastproject.staticfiles.api.StaticFileService;
31 import org.opencastproject.util.NotFoundException;
32 import org.opencastproject.util.OsgiUtil;
33 import org.opencastproject.util.ProgressInputStream;
34
35 import com.google.common.util.concurrent.AbstractScheduledService;
36 import com.google.common.util.concurrent.MoreExecutors;
37 import com.google.common.util.concurrent.Service.Listener;
38 import com.google.common.util.concurrent.Service.State;
39
40 import org.apache.commons.io.FileUtils;
41 import org.apache.commons.lang3.StringUtils;
42 import org.osgi.service.component.ComponentContext;
43 import org.osgi.service.component.ComponentException;
44 import org.osgi.service.component.annotations.Activate;
45 import org.osgi.service.component.annotations.Component;
46 import org.osgi.service.component.annotations.Deactivate;
47 import org.osgi.service.component.annotations.Reference;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51 import java.io.File;
52 import java.io.IOException;
53 import java.io.InputStream;
54 import java.nio.file.DirectoryStream;
55 import java.nio.file.Files;
56 import java.nio.file.Path;
57 import java.nio.file.Paths;
58 import java.util.Date;
59 import java.util.UUID;
60 import java.util.concurrent.TimeUnit;
61
62
63
64
65 @Component(
66 immediate = true,
67 service = StaticFileService.class,
68 property = {
69 "service.description=Static File Service",
70 "service.PID=org.opencastproject.staticfiles.impl.StaticFileServiceImpl"
71 }
72 )
73 public class StaticFileServiceImpl implements StaticFileService {
74
75
76 private static final Logger logger = LoggerFactory.getLogger(StaticFileServiceImpl.class);
77
78
79 public static final String STATICFILES_ROOT_DIRECTORY_KEY = "org.opencastproject.staticfiles.rootdir";
80
81
82 private SecurityService securityService = null;
83 private OrganizationDirectoryService orgDirectory = null;
84
85
86 private String rootDirPath;
87
88 private PurgeTemporaryStorageService purgeService;
89
90
91
92
93
94
95
96 @Activate
97 public void activate(ComponentContext cc) {
98 logger.info("Upload Static Resource Service started.");
99 rootDirPath = OsgiUtil.getContextProperty(cc, STATICFILES_ROOT_DIRECTORY_KEY);
100
101 final File rootFile = new File(rootDirPath);
102 if (!rootFile.exists()) {
103 try {
104 FileUtils.forceMkdir(rootFile);
105 } catch (IOException e) {
106 throw new ComponentException(
107 String.format("%s does not exists and could not be created", rootFile.getAbsolutePath()));
108 }
109 }
110 if (!rootFile.canRead()) {
111 throw new ComponentException(String.format("Cannot read from %s", rootFile.getAbsolutePath()));
112 }
113
114 purgeService = new PurgeTemporaryStorageService();
115 purgeService.addListener(new Listener() {
116 @Override
117 public void failed(State from, Throwable failure) {
118 logger.warn("Temporary storage purging service failed:", failure);
119 }
120 }, MoreExecutors.directExecutor());
121 purgeService.startAsync();
122 logger.info("Purging of temporary storage section scheduled");
123 }
124
125
126
127
128 @Deactivate
129 public void deactivate() {
130 purgeService.stopAsync();
131 purgeService = null;
132 }
133
134
135 @Reference
136 public void setSecurityService(SecurityService securityService) {
137 this.securityService = securityService;
138 }
139
140
141 @Reference
142 public void setOrganizationDirectoryService(OrganizationDirectoryService directoryService) {
143 orgDirectory = directoryService;
144 }
145
146 @Override
147 public String storeFile(String filename, InputStream inputStream) throws IOException {
148 notNull(filename, "filename");
149 notNull(inputStream, "inputStream");
150 final String uuid = UUID.randomUUID().toString();
151 final String org = securityService.getOrganization().getId();
152
153 Path file = getTemporaryStorageDir(org).resolve(Paths.get(uuid, filename));
154 try (ProgressInputStream progressInputStream = new ProgressInputStream(inputStream)) {
155 Files.createDirectories(file.getParent());
156 Files.copy(progressInputStream, file);
157 } catch (IOException e) {
158 logger.error("Unable to save file '{}' to {}", filename, file, e);
159 throw e;
160 }
161
162 return uuid;
163 }
164
165 @Override
166 public InputStream getFile(final String uuid) throws NotFoundException, IOException {
167 if (StringUtils.isBlank(uuid)) {
168 throw new IllegalArgumentException("The uuid must not be blank");
169 }
170
171 final String org = securityService.getOrganization().getId();
172
173 return Files.newInputStream(getFile(org, uuid));
174 }
175
176 @Override
177 public void persistFile(final String uuid) throws NotFoundException, IOException {
178 final String org = securityService.getOrganization().getId();
179 try (DirectoryStream<Path> folders = Files.newDirectoryStream(getTemporaryStorageDir(org),
180 getDirsEqualsUuidFilter(uuid))) {
181 for (Path folder : folders) {
182 Files.move(folder, getDurableStorageDir(org).resolve(folder.getFileName()));
183 }
184 }
185 }
186
187 @Override
188 public void deleteFile(String uuid) throws NotFoundException, IOException {
189 final String org = securityService.getOrganization().getId();
190 Path file = getFile(org, uuid);
191 Files.deleteIfExists(file);
192 }
193
194 @Override
195 public String getFileName(String uuid) throws NotFoundException {
196 final String org = securityService.getOrganization().getId();
197 try {
198 Path file = getFile(org, uuid);
199 return file.getFileName().toString();
200 } catch (IOException e) {
201 logger.warn("Error while reading file:", e);
202 throw new NotFoundException(e);
203 }
204 }
205
206 @Override
207 public Long getContentLength(String uuid) throws NotFoundException {
208 final String org = securityService.getOrganization().getId();
209 try {
210 Path file = getFile(org, uuid);
211 return Files.size(file);
212 } catch (IOException e) {
213 logger.warn("Error while reading file:", e);
214 throw new NotFoundException(e);
215 }
216 }
217
218
219
220
221
222
223
224
225
226 private static DirectoryStream.Filter<Path> getDirsEqualsUuidFilter(final String uuid) {
227 return new DirectoryStream.Filter<Path>() {
228 @Override
229 public boolean accept(Path entry) throws IOException {
230 return Files.isDirectory(entry) && entry.getFileName().toString().equals(uuid);
231 }
232 };
233 };
234
235
236
237
238
239
240
241
242 private Path getTemporaryStorageDir(final String org) {
243 return Paths.get(rootDirPath, org, "temp");
244 }
245
246 private Path getDurableStorageDir(final String org) {
247 return Paths.get(rootDirPath, org);
248 }
249
250 private Path getFile(final String org, final String uuid) throws NotFoundException, IOException {
251
252 try (DirectoryStream<Path> dirs = Files.newDirectoryStream(getDurableStorageDir(org),
253 getDirsEqualsUuidFilter(uuid))) {
254 for (Path dir : dirs) {
255 try (DirectoryStream<Path> files = Files.newDirectoryStream(dir)) {
256 for (Path file : files) {
257 return file;
258 }
259 }
260 }
261 }
262
263
264 try (DirectoryStream<Path> dirs = Files.newDirectoryStream(getTemporaryStorageDir(org),
265 getDirsEqualsUuidFilter(uuid))) {
266 for (Path dir : dirs) {
267 try (DirectoryStream<Path> files = Files.newDirectoryStream(dir)) {
268 for (Path file : files) {
269 return file;
270 }
271 }
272 }
273 }
274
275 throw new NotFoundException(format("No file with UUID '%s' found.", uuid));
276 }
277
278
279
280
281
282
283
284
285
286 void purgeTemporaryStorageSection(final String org, final long lifetime) throws IOException {
287 logger.debug("Purge temporary storage section of organization '{}'", org);
288 final Path temporaryStorageDir = getTemporaryStorageDir(org);
289 if (Files.exists(temporaryStorageDir)) {
290 try (DirectoryStream<Path> tempFilesStream = Files.newDirectoryStream(temporaryStorageDir,
291 new DirectoryStream.Filter<Path>() {
292 @Override
293 public boolean accept(Path path) throws IOException {
294 return (Files.getLastModifiedTime(path).toMillis() < (new Date()).getTime() - lifetime);
295 }
296 })) {
297 for (Path file : tempFilesStream) {
298 FileUtils.deleteQuietly(file.toFile());
299 }
300 }
301 }
302 }
303
304
305
306
307
308
309
310 void purgeTemporaryStorageSection() throws IOException {
311 logger.info("Start purging temporary storage section of all known organizations");
312 for (Organization org : orgDirectory.getOrganizations()) {
313 purgeTemporaryStorageSection(org.getId(), TimeUnit.DAYS.toMillis(1));
314 }
315 }
316
317
318 private class PurgeTemporaryStorageService extends AbstractScheduledService {
319
320 @Override
321 protected void runOneIteration() throws Exception {
322 StaticFileServiceImpl.this.purgeTemporaryStorageSection();
323 }
324
325 @Override
326 protected Scheduler scheduler() {
327 return Scheduler.newFixedRateSchedule(0, 1, TimeUnit.HOURS);
328 }
329
330 }
331
332 }