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.videoeditor.impl;
23
24 import static org.opencastproject.videoeditor.impl.VideoEditorProperties.SUBTITLE_GRACE_PERIOD;
25
26 import org.opencastproject.inspection.api.MediaInspectionException;
27 import org.opencastproject.inspection.api.MediaInspectionService;
28 import org.opencastproject.job.api.AbstractJobProducer;
29 import org.opencastproject.job.api.Job;
30 import org.opencastproject.job.api.JobBarrier;
31 import org.opencastproject.mediapackage.MediaPackageElementFlavor;
32 import org.opencastproject.mediapackage.MediaPackageElementParser;
33 import org.opencastproject.mediapackage.MediaPackageException;
34 import org.opencastproject.mediapackage.MediaPackageReference;
35 import org.opencastproject.mediapackage.MediaPackageReferenceImpl;
36 import org.opencastproject.mediapackage.Track;
37 import org.opencastproject.mediapackage.identifier.IdImpl;
38 import org.opencastproject.security.api.OrganizationDirectoryService;
39 import org.opencastproject.security.api.SecurityService;
40 import org.opencastproject.security.api.UserDirectoryService;
41 import org.opencastproject.serviceregistry.api.ServiceRegistry;
42 import org.opencastproject.serviceregistry.api.ServiceRegistryException;
43 import org.opencastproject.smil.api.SmilException;
44 import org.opencastproject.smil.api.SmilService;
45 import org.opencastproject.smil.entity.api.Smil;
46 import org.opencastproject.smil.entity.media.api.SmilMediaObject;
47 import org.opencastproject.smil.entity.media.container.api.SmilMediaContainer;
48 import org.opencastproject.smil.entity.media.element.api.SmilMediaElement;
49 import org.opencastproject.smil.entity.media.param.api.SmilMediaParam;
50 import org.opencastproject.smil.entity.media.param.api.SmilMediaParamGroup;
51 import org.opencastproject.subtitleparser.webvttparser.WebVTTParser;
52 import org.opencastproject.subtitleparser.webvttparser.WebVTTSubtitle;
53 import org.opencastproject.subtitleparser.webvttparser.WebVTTSubtitleCue;
54 import org.opencastproject.subtitleparser.webvttparser.WebVTTWriter;
55 import org.opencastproject.util.LoadUtil;
56 import org.opencastproject.util.NotFoundException;
57 import org.opencastproject.videoeditor.api.ProcessFailedException;
58 import org.opencastproject.videoeditor.api.VideoEditorService;
59 import org.opencastproject.videoeditor.ffmpeg.FFmpegEdit;
60 import org.opencastproject.workspace.api.Workspace;
61
62 import org.apache.commons.io.FileUtils;
63 import org.apache.commons.io.FilenameUtils;
64 import org.apache.commons.io.IOUtils;
65 import org.osgi.service.cm.ConfigurationException;
66 import org.osgi.service.cm.ManagedService;
67 import org.osgi.service.component.ComponentContext;
68 import org.osgi.service.component.annotations.Activate;
69 import org.osgi.service.component.annotations.Component;
70 import org.osgi.service.component.annotations.Deactivate;
71 import org.osgi.service.component.annotations.Reference;
72 import org.slf4j.Logger;
73 import org.slf4j.LoggerFactory;
74
75 import java.io.File;
76 import java.io.FileInputStream;
77 import java.io.FileOutputStream;
78 import java.io.IOException;
79 import java.io.InputStream;
80 import java.net.URI;
81 import java.net.URISyntaxException;
82 import java.util.ArrayList;
83 import java.util.Arrays;
84 import java.util.Dictionary;
85 import java.util.Enumeration;
86 import java.util.Iterator;
87 import java.util.LinkedList;
88 import java.util.List;
89 import java.util.Objects;
90 import java.util.Properties;
91 import java.util.stream.Collectors;
92
93 import javax.xml.bind.JAXBException;
94
95
96
97
98 @Component(
99 immediate = true,
100 service = { VideoEditorService.class,ManagedService.class },
101 property = {
102 "service.description=Video Editor Service"
103 }
104 )
105 public class VideoEditorServiceImpl extends AbstractJobProducer implements VideoEditorService, ManagedService {
106
107 public static final String JOB_LOAD_KEY = "job.load.videoeditor";
108
109 private static final float DEFAULT_JOB_LOAD = 0.8f;
110
111 private float jobload = DEFAULT_JOB_LOAD;
112
113 public static final String SEGMENTS_MIN_DURATION_KEY = "segments.min.duration";
114
115 private static final int DEFAULT_SEGMENTS_MIN_DURATION = 2000;
116
117 private int segmentsMinDuration = DEFAULT_SEGMENTS_MIN_DURATION;
118
119 public static final String SEGMENTS_MIN_CUT_DURATION_KEY = "segments.min.cut.duration";
120
121 private static final int DEFAULT_SEGMENTS_MIN_CUT_DURATION = 2000;
122
123 private int segmentsMinCutDuration = DEFAULT_SEGMENTS_MIN_CUT_DURATION;
124
125 private static final String VTT_SHORTEN_FLAVOR_TYPES = "vtt.shorten.flavor.types";
126 private static final String DEFAULT_VTT_SHORTEN_FLAVOR_TYPES = "chapters";
127 private List<String> shortenFlavorTypes = new ArrayList<>();
128
129
130
131
132 private static final Logger logger = LoggerFactory.getLogger(VideoEditorServiceImpl.class);
133 private static final String JOB_TYPE = "org.opencastproject.videoeditor";
134 private static final String COLLECTION_ID = "videoeditor";
135 private static final String SINK_FLAVOR_SUBTYPE = "trimmed";
136
137 private enum Operation {
138 PROCESS_SMIL
139 }
140
141
142
143
144 private MediaInspectionService inspectionService = null;
145
146
147
148 private Workspace workspace = null;
149
150
151
152 private ServiceRegistry serviceRegistry;
153
154
155
156 protected OrganizationDirectoryService organizationDirectoryService = null;
157
158
159
160 protected SecurityService securityService = null;
161
162
163
164 protected UserDirectoryService userDirectoryService = null;
165
166
167
168 protected SmilService smilService = null;
169
170
171
172 private Properties properties = new Properties();
173
174 public VideoEditorServiceImpl() {
175 super(JOB_TYPE);
176 }
177
178
179
180
181
182
183
184
185
186
187
188
189
190 protected Track processSmil(Job job, Smil smil, String trackParamGroupId) throws ProcessFailedException {
191
192 SmilMediaParamGroup trackParamGroup;
193 ArrayList<String> inputfile = new ArrayList<>();
194 ArrayList<VideoClip> videoclips = new ArrayList<>();
195 ArrayList<VideoClip> refElements = new ArrayList<>();
196 try {
197 trackParamGroup = (SmilMediaParamGroup) smil.get(trackParamGroupId);
198 } catch (SmilException ex) {
199
200 throw new ProcessFailedException("Smil does not contain a paramGroup element with Id " + trackParamGroupId);
201 }
202 MediaPackageElementFlavor sourceTrackFlavor = null;
203 String sourceTrackUri = null;
204 MediaPackageReference ref = null;
205
206 for (SmilMediaParam param : trackParamGroup.getParams()) {
207 if (SmilMediaParam.PARAM_NAME_TRACK_SRC.equals(param.getName())) {
208 sourceTrackUri = param.getValue();
209 } else if (SmilMediaParam.PARAM_NAME_TRACK_FLAVOR.equals(param.getName())) {
210 sourceTrackFlavor = MediaPackageElementFlavor.parseFlavor(param.getValue());
211 } else if (SmilMediaParam.PARAM_NAME_TRACK_ID.equals(param.getName())) {
212 ref = new MediaPackageReferenceImpl("track", param.getValue());
213 }
214 }
215 File sourceFile;
216 try {
217 sourceFile = workspace.get(new URI(sourceTrackUri));
218 } catch (IOException ex) {
219 throw new ProcessFailedException("Can't read " + sourceTrackUri);
220 } catch (NotFoundException ex) {
221 throw new ProcessFailedException("Workspace does not contain a track " + sourceTrackUri);
222 } catch (URISyntaxException ex) {
223 throw new ProcessFailedException("Source URI " + sourceTrackUri + " is not valid.");
224 }
225
226 Job inspectionJob;
227 Track sourceTrack;
228 try {
229 inspectionJob = inspect(job, new URI(sourceTrackUri));
230 sourceTrack = (Track) MediaPackageElementParser.getFromXml(inspectionJob.getPayload());
231 } catch (URISyntaxException e) {
232 throw new ProcessFailedException("Source URI " + sourceTrackUri + " is not valid.");
233 } catch (MediaInspectionException e) {
234 throw new ProcessFailedException("Media inspection of " + sourceTrackUri + " failed", e);
235 } catch (MediaPackageException e) {
236 throw new ProcessFailedException("Deserialization of source track " + sourceTrackUri + " failed", e);
237 }
238
239
240 File tempDirectory = new File(new File(workspace.rootDirectory()), "editor");
241 tempDirectory = new File(tempDirectory, Long.toString(job.getId()));
242
243 URI newTrackURI;
244 inputfile.add(sourceFile.getAbsolutePath());
245 int srcIndex = inputfile.indexOf(sourceFile.getAbsolutePath());
246 logger.info("Start processing srcfile {}", sourceFile.getAbsolutePath());
247 try {
248
249 for (SmilMediaObject element : smil.getBody().getMediaElements()) {
250
251 if (element.isContainer()) {
252 SmilMediaContainer container = (SmilMediaContainer) element;
253 if (SmilMediaContainer.ContainerType.PAR == container.getContainerType()) {
254
255 for (SmilMediaObject elementChild : container.getElements()) {
256 if (!elementChild.isContainer()) {
257 SmilMediaElement media = (SmilMediaElement) elementChild;
258 if (trackParamGroupId.equals(media.getParamGroup())) {
259 long begin = media.getClipBeginMS();
260 long end = media.getClipEndMS();
261 URI clipTrackURI = media.getSrc();
262 File clipSourceFile = null;
263 if (clipTrackURI != null) {
264 try {
265 clipSourceFile = workspace.get(clipTrackURI);
266 } catch (IOException ex) {
267 throw new ProcessFailedException("Can't read " + clipTrackURI);
268 } catch (NotFoundException ex) {
269 throw new ProcessFailedException("Workspace does not contain a track " + clipTrackURI);
270 }
271 }
272 int index;
273
274 if (clipSourceFile != null) {
275 index = inputfile.indexOf(clipSourceFile.getAbsolutePath());
276 if (index == -1) {
277 inputfile.add(clipSourceFile.getAbsolutePath());
278
279 }
280 index = inputfile.indexOf(clipSourceFile.getAbsolutePath());
281 } else {
282 index = srcIndex;
283 }
284
285
286 if (media.getMediaType() == SmilMediaElement.MediaType.REF) {
287 refElements.add(new VideoClip(index, begin, end));
288 } else {
289 videoclips.add(new VideoClip(index, begin, end));
290 }
291 }
292 } else {
293 throw new ProcessFailedException("Smil container '"
294 + ((SmilMediaContainer) elementChild).getContainerType().toString()
295 + "'is not supported yet");
296 }
297 }
298 } else {
299 throw new ProcessFailedException("Smil container '"
300 + container.getContainerType().toString() + "'is not supported yet");
301 }
302 }
303 }
304
305
306 if (videoclips.size() > 0 && refElements.size() > 0) {
307 throw new ProcessFailedException("Can not process media elements together with ref elements. "
308 + "There likely is an error in the SMIL file");
309 }
310
311
312 String outputFileExtension = null;
313 if (videoclips.size() > 0) {
314 outputFileExtension = properties.getProperty(VideoEditorProperties.DEFAULT_EXTENSION, ".mp4");
315 }
316 if (refElements.size() > 0) {
317 String extension = FilenameUtils.getExtension(sourceTrackUri);
318 if (VideoEditorProperties.WEBVTT_EXTENSION.equals(extension)) {
319 outputFileExtension = properties.getProperty(VideoEditorProperties.WEBVTT_EXTENSION, ".vtt");
320 }
321 }
322 outputFileExtension = properties.getProperty(VideoEditorProperties.OUTPUT_FILE_EXTENSION, outputFileExtension);
323
324 if (!outputFileExtension.startsWith(".")) {
325 outputFileExtension = '.' + outputFileExtension;
326 }
327
328 String filename = String.format("%s-%s%s", sourceTrackFlavor,
329 FilenameUtils.removeExtension(sourceFile.getName()), outputFileExtension);
330 File outputPath = new File(tempDirectory, filename);
331
332 if (!outputPath.getParentFile().exists()) {
333 outputPath.getParentFile().mkdirs();
334 }
335
336
337 if (videoclips.size() > 0) {
338
339 List<VideoClip> cleanclips = sortSegments(videoclips, segmentsMinDuration, segmentsMinCutDuration);
340 String error = null;
341 String outputResolution = "";
342
343
344
345 FFmpegEdit ffmpeg = new FFmpegEdit(properties);
346 error = ffmpeg.processEdits(inputfile, outputPath.getAbsolutePath(), outputResolution, cleanclips,
347 sourceTrack.hasAudio(), sourceTrack.hasVideo());
348
349 if (error != null) {
350 FileUtils.deleteQuietly(tempDirectory);
351 throw new ProcessFailedException("Editing pipeline exited abnormally! Error: " + error);
352 }
353 }
354
355
356
357
358 if (refElements.size() > 0) {
359
360 List<VideoClip> cleanclips = sortSegments(refElements, segmentsMinDuration, segmentsMinCutDuration);
361 String extension = FilenameUtils.getExtension(sourceTrackUri);
362 if (VideoEditorProperties.WEBVTT_EXTENSION.equals(extension)) {
363
364 WebVTTParser parser = new WebVTTParser();
365 WebVTTSubtitle subtitle;
366 try (FileInputStream fin = new FileInputStream(sourceFile)) {
367 subtitle = parser.parse(fin);
368 }
369
370 if (shortenFlavorTypes.contains(sourceTrackFlavor.getType())) {
371
372 List<WebVTTSubtitleCue> result = new ArrayList<>();
373 for (WebVTTSubtitleCue ch : subtitle.getCues()) {
374 long newStart = totalKeptBefore(ch.getStartTime(), cleanclips);
375 long newEnd = totalKeptBefore(ch.getEndTime(), cleanclips);
376 if (newEnd > newStart) {
377 ch.setStartTime(newStart);
378 ch.setEndTime(newEnd);
379 result.add(ch);
380 }
381 }
382 subtitle.setCues(result);
383 } else {
384
385 List<WebVTTSubtitleCue> cutCues = new ArrayList<>();
386 double removedTime = 0;
387 for (int i = 0; i < cleanclips.size(); i++) {
388 if (i == 0) {
389 removedTime = removedTime
390 + cleanclips.get(i).getStartInMilliseconds();
391 } else {
392 removedTime = removedTime
393 + cleanclips.get(i).getStartInMilliseconds()
394 - cleanclips.get(i - 1).getEndInMilliseconds();
395 }
396 for (WebVTTSubtitleCue cue : subtitle.getCues()) {
397 if ((cleanclips.get(i).getStartInMilliseconds() - SUBTITLE_GRACE_PERIOD) <= cue.getStartTime()
398 && (cleanclips.get(i).getEndInMilliseconds() + SUBTITLE_GRACE_PERIOD) >= cue.getEndTime()) {
399 cue.setStartTime((long) (cue.getStartTime() - removedTime));
400 cue.setEndTime((long) (cue.getEndTime() - removedTime));
401 cutCues.add(cue);
402 }
403 }
404 }
405 subtitle.setCues(cutCues);
406 }
407
408
409 try (FileOutputStream fos = new FileOutputStream(outputPath)) {
410 WebVTTWriter writer = new WebVTTWriter();
411 writer.write(subtitle, fos);
412 }
413 } else {
414 throw new ProcessFailedException("The video editor does not support the following file: " + sourceTrackUri);
415 }
416 }
417
418
419 String newTrackId = IdImpl.fromUUID().toString();
420 InputStream in = new FileInputStream(outputPath);
421 try {
422 newTrackURI = workspace.putInCollection(COLLECTION_ID,
423 String.format("%s-%s%s", sourceTrackFlavor.getType(), newTrackId, outputFileExtension), in);
424 } catch (IllegalArgumentException ex) {
425 throw new ProcessFailedException("Copy track into workspace failed! " + ex.getMessage());
426 } finally {
427 IOUtils.closeQuietly(in);
428 FileUtils.deleteQuietly(tempDirectory);
429 }
430
431
432 try {
433 inspectionJob = inspect(job,newTrackURI);
434 } catch (MediaInspectionException e) {
435 throw new ProcessFailedException("Media inspection of " + newTrackURI + " failed", e);
436 }
437 Track editedTrack = (Track) MediaPackageElementParser.getFromXml(inspectionJob.getPayload());
438 logger.info("Finished editing track {}", editedTrack);
439 editedTrack.setIdentifier(newTrackId);
440 editedTrack.setReference(ref);
441 if (videoclips.size() > 0) {
442 editedTrack.setFlavor(new MediaPackageElementFlavor(sourceTrackFlavor.getType(), SINK_FLAVOR_SUBTYPE));
443 }
444 if (refElements.size() > 0) {
445 String extension = FilenameUtils.getExtension(sourceTrackUri);
446 if (VideoEditorProperties.WEBVTT_EXTENSION.equals(extension)) {
447 editedTrack.setFlavor(new MediaPackageElementFlavor(sourceTrackFlavor.getType(),
448 sourceTrackFlavor.getSubtype() + "+" + SINK_FLAVOR_SUBTYPE));
449 }
450 }
451
452 return editedTrack;
453
454 } catch (MediaInspectionException ex) {
455 throw new ProcessFailedException("Inspecting encoded Track failed with: " + ex.getMessage());
456 } catch (MediaPackageException ex) {
457 throw new ProcessFailedException("Unable to serialize edited Track! " + ex.getMessage());
458 } catch (Exception ex) {
459 throw new ProcessFailedException("Unable to process SMIL: " + ex.getMessage(), ex);
460 } finally {
461 FileUtils.deleteQuietly(tempDirectory);
462 }
463 }
464
465
466
467
468 protected Job inspect(Job job, URI workspaceURI) throws MediaInspectionException, ProcessFailedException {
469 Job inspectionJob;
470 try {
471 inspectionJob = inspectionService.inspect(workspaceURI);
472 } catch (MediaInspectionException e) {
473 incident().recordJobCreationIncident(job, e);
474 throw new MediaInspectionException("Media inspection of " + workspaceURI + " failed", e);
475 }
476 JobBarrier barrier = new JobBarrier(job, serviceRegistry, inspectionJob);
477 if (!barrier.waitForJobs().isSuccess()) {
478 throw new ProcessFailedException("Media inspection of " + workspaceURI + " failed");
479 }
480 return inspectionJob;
481 }
482
483
484
485
486 private static List<VideoClip> sortSegments(List<VideoClip> edits, int segmentsMinDuration,
487 int segmentsMinCutDuration) {
488 LinkedList<VideoClip> ll = new LinkedList<>();
489 List<VideoClip> clips = new ArrayList<>();
490 Iterator<VideoClip> it = edits.iterator();
491 VideoClip clip;
492 VideoClip nextclip;
493 while (it.hasNext()) {
494 clip = it.next();
495 if (clip.getDurationInMilliseconds() > segmentsMinDuration) {
496 ll.add(clip);
497 }
498 }
499 clip = ll.pop();
500 while (!ll.isEmpty()) {
501 if (ll.peek() != null) {
502 nextclip = ll.pop();
503
504 if (nextclip.getSrc() == clip.getSrc()
505 && nextclip.getStartInMilliseconds() - clip.getEndInMilliseconds() < segmentsMinCutDuration) {
506 clip.setEnd(nextclip.getEndInMilliseconds());
507 } else {
508 clips.add(clip);
509 clip = nextclip;
510 }
511 }
512 }
513 clips.add(clip);
514 return clips;
515 }
516
517
518
519
520 private long totalKeptBefore(long t, List<VideoClip> keepSegments) {
521 long kept = 0;
522 for (VideoClip s : keepSegments) {
523 if (s.getEndInMilliseconds() <= 0) {
524 continue;
525 }
526 if (s.getStartInMilliseconds() >= t) {
527 break;
528 }
529 long overlapStart = Math.max(s.getStartInMilliseconds(), 0);
530 long overlapEnd = Math.min(s.getEndInMilliseconds(), t);
531 if (overlapEnd > overlapStart) {
532 kept += (overlapEnd - overlapStart);
533 }
534 }
535 return kept;
536 }
537
538
539
540
541
542
543
544 @Override
545 public List<Job> processSmil(Smil smil) throws ProcessFailedException {
546 if (smil == null) {
547 throw new ProcessFailedException("Smil document is null!");
548 }
549
550 List<Job> jobs = new LinkedList<Job>();
551 try {
552 for (SmilMediaParamGroup paramGroup : smil.getHead().getParamGroups()) {
553 for (SmilMediaParam param : paramGroup.getParams()) {
554 if (SmilMediaParam.PARAM_NAME_TRACK_ID.equals(param.getName())) {
555 jobs.add(serviceRegistry.createJob(getJobType(), Operation.PROCESS_SMIL.toString(),
556 Arrays.asList(smil.toXML(), paramGroup.getId()), jobload));
557 }
558 }
559 }
560 return jobs;
561 } catch (JAXBException ex) {
562 throw new ProcessFailedException("Failed to serialize smil " + smil.getId());
563 } catch (ServiceRegistryException ex) {
564 throw new ProcessFailedException("Failed to create job: " + ex.getMessage());
565 } catch (Exception ex) {
566 throw new ProcessFailedException(ex.getMessage());
567 }
568 }
569
570 @Override
571 protected String process(Job job) throws Exception {
572 if (Operation.PROCESS_SMIL.toString().equals(job.getOperation())) {
573 Smil smil = smilService.fromXml(job.getArguments().get(0)).getSmil();
574 if (smil == null) {
575 throw new ProcessFailedException("Smil document is null!");
576 }
577
578 Track editedTrack = processSmil(job, smil, job.getArguments().get(1));
579 return MediaPackageElementParser.getAsXml(editedTrack);
580 }
581
582 throw new ProcessFailedException("Can't handle this operation: " + job.getOperation());
583 }
584
585 @Override
586 protected ServiceRegistry getServiceRegistry() {
587 return serviceRegistry;
588 }
589
590 @Override
591 protected SecurityService getSecurityService() {
592 return securityService;
593 }
594
595 @Override
596 protected UserDirectoryService getUserDirectoryService() {
597 return userDirectoryService;
598 }
599
600 @Override
601 protected OrganizationDirectoryService getOrganizationDirectoryService() {
602 return organizationDirectoryService;
603 }
604
605 @Override
606 @Activate
607 public void activate(ComponentContext context) {
608 logger.debug("activating...");
609 super.activate(context);
610 FFmpegEdit.init(context.getBundleContext());
611 }
612
613 @Deactivate
614 protected void deactivate(ComponentContext context) {
615 logger.debug("deactivating...");
616 }
617
618 @Override
619 public void updated(Dictionary<String, ?> properties) throws ConfigurationException {
620 this.properties = new Properties();
621 if (properties == null) {
622 logger.info("No configuration available, using defaults");
623 return;
624 }
625
626 Enumeration<String> keys = properties.keys();
627 while (keys.hasMoreElements()) {
628 String key = keys.nextElement();
629 this.properties.put(key, properties.get(key));
630 }
631 logger.debug("Properties updated!");
632
633 jobload = LoadUtil.getConfiguredLoadValue(properties, JOB_LOAD_KEY, DEFAULT_JOB_LOAD, serviceRegistry);
634 segmentsMinDuration = Integer.parseInt(this.properties.getProperty(SEGMENTS_MIN_DURATION_KEY,
635 String.valueOf(DEFAULT_SEGMENTS_MIN_DURATION)));
636 segmentsMinCutDuration = Integer.parseInt(this.properties.getProperty(SEGMENTS_MIN_CUT_DURATION_KEY,
637 String.valueOf(DEFAULT_SEGMENTS_MIN_CUT_DURATION)));
638 String tmp = Objects.toString(properties.get(VTT_SHORTEN_FLAVOR_TYPES),
639 DEFAULT_VTT_SHORTEN_FLAVOR_TYPES);
640 shortenFlavorTypes = Arrays.stream(tmp.split(","))
641 .map(String::trim)
642 .collect(Collectors.toList());
643 }
644
645 @Reference
646 public void setMediaInspectionService(MediaInspectionService inspectionService) {
647 this.inspectionService = inspectionService;
648 }
649
650 @Reference
651 public void setServiceRegistry(ServiceRegistry serviceRegistry) {
652 this.serviceRegistry = serviceRegistry;
653 }
654
655 @Reference
656 public void setWorkspace(Workspace workspace) {
657 this.workspace = workspace;
658 }
659
660 @Reference
661 public void setSecurityService(SecurityService securityService) {
662 this.securityService = securityService;
663 }
664
665 @Reference
666 public void setUserDirectoryService(UserDirectoryService userDirectoryService) {
667 this.userDirectoryService = userDirectoryService;
668 }
669
670 @Reference
671 public void setOrganizationDirectoryService(OrganizationDirectoryService organizationDirectoryService) {
672 this.organizationDirectoryService = organizationDirectoryService;
673 }
674
675 @Reference
676 public void setSmilService(SmilService smilService) {
677 this.smilService = smilService;
678 }
679 }