1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.opencastproject.workflow.handler.timelinepreviews;
22
23 import org.opencastproject.job.api.Job;
24 import org.opencastproject.job.api.JobContext;
25 import org.opencastproject.mediapackage.MediaPackage;
26 import org.opencastproject.mediapackage.MediaPackageElement;
27 import org.opencastproject.mediapackage.MediaPackageElementFlavor;
28 import org.opencastproject.mediapackage.MediaPackageElementParser;
29 import org.opencastproject.mediapackage.MediaPackageException;
30 import org.opencastproject.mediapackage.Track;
31 import org.opencastproject.mediapackage.selector.TrackSelector;
32 import org.opencastproject.serviceregistry.api.ServiceRegistry;
33 import org.opencastproject.timelinepreviews.api.TimelinePreviewsException;
34 import org.opencastproject.timelinepreviews.api.TimelinePreviewsService;
35 import org.opencastproject.util.IoSupport;
36 import org.opencastproject.util.NotFoundException;
37 import org.opencastproject.workflow.api.AbstractWorkflowOperationHandler;
38 import org.opencastproject.workflow.api.ConfiguredTagsAndFlavors;
39 import org.opencastproject.workflow.api.WorkflowInstance;
40 import org.opencastproject.workflow.api.WorkflowOperationException;
41 import org.opencastproject.workflow.api.WorkflowOperationHandler;
42 import org.opencastproject.workflow.api.WorkflowOperationResult;
43 import org.opencastproject.workspace.api.Workspace;
44
45 import org.apache.commons.io.FilenameUtils;
46 import org.apache.commons.lang3.BooleanUtils;
47 import org.apache.commons.lang3.StringUtils;
48 import org.osgi.service.component.ComponentContext;
49 import org.osgi.service.component.annotations.Activate;
50 import org.osgi.service.component.annotations.Component;
51 import org.osgi.service.component.annotations.Reference;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54
55 import java.io.File;
56 import java.io.FileInputStream;
57 import java.io.FileNotFoundException;
58 import java.io.IOException;
59 import java.net.URI;
60 import java.util.ArrayList;
61 import java.util.Collection;
62 import java.util.List;
63
64
65
66
67 @Component(
68 immediate = true,
69 service = WorkflowOperationHandler.class,
70 property = {
71 "service.description=Timeline Preview Images Workflow Operation Handler",
72 "workflow.operation=timelinepreviews"
73 }
74 )
75 public class TimelinePreviewsWorkflowOperationHandler extends AbstractWorkflowOperationHandler {
76
77
78 private static final Logger logger = LoggerFactory.getLogger(TimelinePreviewsWorkflowOperationHandler.class);
79
80
81 private static final String SOURCE_FLAVOR_PROPERTY = "source-flavor";
82
83
84 private static final String SOURCE_TAGS_PROPERTY = "source-tags";
85
86
87 private static final String TARGET_FLAVOR_PROPERTY = "target-flavor";
88
89
90 private static final String TARGET_TAGS_PROPERTY = "target-tags";
91
92
93 private static final String PROCCESS_FIRST_MATCH = "process-first-match-only";
94
95
96 private static final String IMAGE_SIZE_PROPERTY = "image-count";
97
98
99 private static final int DEFAULT_IMAGE_SIZE = 10;
100
101
102 private TimelinePreviewsService timelinePreviewsService = null;
103
104
105 private Workspace workspace = null;
106
107 @Override
108 @Activate
109 public void activate(ComponentContext cc) {
110 super.activate(cc);
111 logger.info("Registering timeline previews workflow operation handler");
112 }
113
114 @Override
115 public WorkflowOperationResult start(WorkflowInstance workflowInstance, JobContext context)
116 throws WorkflowOperationException {
117 MediaPackage mediaPackage = workflowInstance.getMediaPackage();
118 logger.info("Start timeline previews workflow operation for mediapackage {}",
119 mediaPackage.getIdentifier().toString());
120
121 ConfiguredTagsAndFlavors tagsAndFlavors = getTagsAndFlavors(workflowInstance,
122 Configuration.many, Configuration.many, Configuration.many, Configuration.one);
123 List<MediaPackageElementFlavor> sourceFlavorProperty = tagsAndFlavors.getSrcFlavors();
124 List<String> sourceTagsProperty = tagsAndFlavors.getSrcTags();
125 if (sourceFlavorProperty.isEmpty() && sourceTagsProperty.isEmpty()) {
126 throw new WorkflowOperationException(String.format("Required property %s or %s not set",
127 SOURCE_FLAVOR_PROPERTY, SOURCE_TAGS_PROPERTY));
128 }
129 MediaPackageElementFlavor targetFlavor = tagsAndFlavors.getSingleTargetFlavor();
130 List<String> targetTagsProperty = tagsAndFlavors.getTargetTags();
131
132 String imageSizeArg = StringUtils.trimToNull(
133 workflowInstance.getCurrentOperation().getConfiguration(IMAGE_SIZE_PROPERTY));
134 int imageSize;
135 if (imageSizeArg != null) {
136 try {
137 imageSize = Integer.parseInt(imageSizeArg);
138 } catch (NumberFormatException e) {
139 imageSize = DEFAULT_IMAGE_SIZE;
140 logger.info("No valid integer given for property {}, using default value: {}",
141 IMAGE_SIZE_PROPERTY, DEFAULT_IMAGE_SIZE);
142 }
143 } else {
144 imageSize = DEFAULT_IMAGE_SIZE;
145 logger.info("Property {} not set, using default value: {}", IMAGE_SIZE_PROPERTY, DEFAULT_IMAGE_SIZE);
146 }
147
148 boolean processOnlyOne = BooleanUtils.toBoolean(StringUtils.trimToNull(
149 workflowInstance.getCurrentOperation().getConfiguration(PROCCESS_FIRST_MATCH)));
150
151 TrackSelector trackSelector = new TrackSelector();
152 for (MediaPackageElementFlavor flavor : sourceFlavorProperty) {
153 trackSelector.addFlavor(flavor);
154 }
155 for (String tag : sourceTagsProperty) {
156 trackSelector.addTag(tag);
157 }
158 Collection<Track> sourceTracks = trackSelector.select(mediaPackage, true);
159 if (sourceTracks.isEmpty()) {
160 logger.info("No tracks found in mediapackage {} with specified {} {}", mediaPackage.getIdentifier().toString(),
161 SOURCE_FLAVOR_PROPERTY,
162 sourceFlavorProperty);
163 createResult(mediaPackage, WorkflowOperationResult.Action.SKIP);
164 }
165
166 List<Job> timelinepreviewsJobs = new ArrayList<Job>(sourceTracks.size());
167 for (Track sourceTrack : sourceTracks) {
168 try {
169
170 logger.info("Create timeline previews job for track '{}' in mediapackage '{}'",
171 sourceTrack.getIdentifier(), mediaPackage.getIdentifier().toString());
172
173 Job timelinepreviewsJob = timelinePreviewsService.createTimelinePreviewImages(sourceTrack, imageSize);
174 timelinepreviewsJobs.add(timelinepreviewsJob);
175
176 if (processOnlyOne) {
177 break;
178 }
179
180 } catch (MediaPackageException | TimelinePreviewsException ex) {
181 logger.error("Creating timeline previews job for track '{}' in media package '{}' failed with error {}",
182 sourceTrack.getIdentifier(), mediaPackage.getIdentifier().toString(), ex.getMessage());
183 }
184 }
185
186 logger.info("Wait for timeline previews jobs for media package {}", mediaPackage.getIdentifier().toString());
187 if (!waitForStatus(timelinepreviewsJobs.toArray(new Job[timelinepreviewsJobs.size()])).isSuccess()) {
188 cleanupWorkspace(timelinepreviewsJobs);
189 throw new WorkflowOperationException(
190 String.format("Timeline previews jobs for media package '%s' have not completed successfully",
191 mediaPackage.getIdentifier().toString()));
192 }
193
194
195 try {
196
197 for (Job job : timelinepreviewsJobs) {
198 String jobPayload = job.getPayload();
199 if (StringUtils.isNotEmpty(jobPayload)) {
200 MediaPackageElement timelinePreviewsMpe = null;
201 File timelinePreviewsFile = null;
202 try {
203 timelinePreviewsMpe = MediaPackageElementParser.getFromXml(jobPayload);
204 timelinePreviewsFile = workspace.get(timelinePreviewsMpe.getURI());
205 } catch (MediaPackageException ex) {
206
207 throw new WorkflowOperationException("Can't parse timeline previews attachment from job " + job.getId());
208 } catch (NotFoundException ex) {
209 throw new WorkflowOperationException("Timeline preview images file '" + timelinePreviewsMpe.getURI()
210 + "' not found", ex);
211 } catch (IOException ex) {
212 throw new WorkflowOperationException("Can't get workflow image file '" + timelinePreviewsMpe.getURI()
213 + "' from workspace");
214 }
215
216 FileInputStream timelinePreviewsInputStream = null;
217 logger.info("Put timeline preview images file {} from media package {} to the media package work space",
218 timelinePreviewsMpe.getURI(), mediaPackage.getIdentifier().toString());
219
220 try {
221 timelinePreviewsInputStream = new FileInputStream(timelinePreviewsFile);
222 String fileName = FilenameUtils.getName(timelinePreviewsMpe.getURI().getPath());
223 URI timelinePreviewsWfrUri = workspace.put(mediaPackage.getIdentifier().toString(),
224 timelinePreviewsMpe.getIdentifier(), fileName, timelinePreviewsInputStream);
225 timelinePreviewsMpe.setURI(timelinePreviewsWfrUri);
226 } catch (FileNotFoundException ex) {
227 throw new WorkflowOperationException("Timeline preview images file " + timelinePreviewsFile.getPath()
228 + " not found", ex);
229 } catch (IOException ex) {
230 throw new WorkflowOperationException("Can't read just created timeline preview images file "
231 + timelinePreviewsFile.getPath(), ex);
232 } catch (IllegalArgumentException ex) {
233 throw new WorkflowOperationException(ex);
234 } finally {
235 IoSupport.closeQuietly(timelinePreviewsInputStream);
236 }
237
238
239 if ("*".equals(targetFlavor.getType())) {
240 targetFlavor = new MediaPackageElementFlavor(
241 timelinePreviewsMpe.getFlavor().getType(), targetFlavor.getSubtype());
242 }
243 if ("*".equals(targetFlavor.getSubtype())) {
244 targetFlavor = new MediaPackageElementFlavor(
245 targetFlavor.getType(), timelinePreviewsMpe.getFlavor().getSubtype());
246 }
247 timelinePreviewsMpe.setFlavor(targetFlavor);
248 if (!targetTagsProperty.isEmpty()) {
249 for (String tag : targetTagsProperty) {
250 timelinePreviewsMpe.addTag(tag);
251 }
252 }
253
254 mediaPackage.add(timelinePreviewsMpe);
255 }
256 }
257 } finally {
258 cleanupWorkspace(timelinepreviewsJobs);
259 }
260
261
262 logger.info("Timeline previews workflow operation for mediapackage {} completed",
263 mediaPackage.getIdentifier().toString());
264 return createResult(mediaPackage, WorkflowOperationResult.Action.CONTINUE);
265 }
266
267
268
269
270
271 private void cleanupWorkspace(List<Job> jobs) {
272 for (Job job : jobs) {
273 String jobPayload = job.getPayload();
274 if (StringUtils.isNotEmpty(jobPayload)) {
275 try {
276 MediaPackageElement timelinepreviewsMpe = MediaPackageElementParser.getFromXml(jobPayload);
277 URI timelinepreviewsUri = timelinepreviewsMpe.getURI();
278 workspace.delete(timelinepreviewsUri);
279 } catch (MediaPackageException ex) {
280
281 logger.error("Can't parse timeline previews attachment from job {}", job.getId());
282 } catch (NotFoundException ex) {
283
284 } catch (IOException ex) {
285 logger.warn("Deleting timeline previews image file from workspace failed: {}", ex.getMessage());
286
287 }
288 }
289 }
290 }
291
292 @Reference
293 public void setTimelinePreviewsService(TimelinePreviewsService timelinePreviewsService) {
294 this.timelinePreviewsService = timelinePreviewsService;
295 }
296
297 @Reference
298 public void setWorkspace(Workspace workspace) {
299 this.workspace = workspace;
300 }
301
302 @Reference
303 @Override
304 public void setServiceRegistry(ServiceRegistry serviceRegistry) {
305 super.setServiceRegistry(serviceRegistry);
306 }
307
308 }