View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  
22  package org.opencastproject.execute.impl;
23  
24  import org.opencastproject.execute.api.ExecuteException;
25  import org.opencastproject.execute.api.ExecuteService;
26  import org.opencastproject.job.api.AbstractJobProducer;
27  import org.opencastproject.job.api.Job;
28  import org.opencastproject.mediapackage.MediaPackage;
29  import org.opencastproject.mediapackage.MediaPackageElement;
30  import org.opencastproject.mediapackage.MediaPackageElement.Type;
31  import org.opencastproject.mediapackage.MediaPackageElementBuilderFactory;
32  import org.opencastproject.mediapackage.MediaPackageElementFlavor;
33  import org.opencastproject.mediapackage.MediaPackageElementParser;
34  import org.opencastproject.mediapackage.MediaPackageException;
35  import org.opencastproject.mediapackage.MediaPackageParser;
36  import org.opencastproject.mediapackage.UnsupportedElementException;
37  import org.opencastproject.security.api.OrganizationDirectoryService;
38  import org.opencastproject.security.api.SecurityService;
39  import org.opencastproject.security.api.UserDirectoryService;
40  import org.opencastproject.serviceregistry.api.ServiceRegistry;
41  import org.opencastproject.serviceregistry.api.ServiceRegistryException;
42  import org.opencastproject.util.ConfigurationException;
43  import org.opencastproject.util.IoSupport;
44  import org.opencastproject.util.LoadUtil;
45  import org.opencastproject.util.NotFoundException;
46  import org.opencastproject.workspace.api.Workspace;
47  
48  import org.apache.commons.lang3.StringUtils;
49  import org.osgi.framework.BundleContext;
50  import org.osgi.service.cm.ManagedService;
51  import org.osgi.service.component.ComponentContext;
52  import org.osgi.service.component.annotations.Activate;
53  import org.osgi.service.component.annotations.Component;
54  import org.osgi.service.component.annotations.Reference;
55  import org.slf4j.Logger;
56  import org.slf4j.LoggerFactory;
57  
58  import java.io.BufferedReader;
59  import java.io.File;
60  import java.io.FileInputStream;
61  import java.io.IOException;
62  import java.io.InputStreamReader;
63  import java.net.URI;
64  import java.util.ArrayList;
65  import java.util.Arrays;
66  import java.util.Dictionary;
67  import java.util.HashSet;
68  import java.util.List;
69  import java.util.Set;
70  import java.util.regex.Matcher;
71  import java.util.regex.Pattern;
72  
73  /**
74   * Implements a service that runs CLI commands with MediaPackage elements as arguments
75   */
76  @Component(
77      immediate = true,
78      service = { ExecuteService.class,ManagedService.class },
79      property = {
80          "service.description=Execute Service",
81          "service.pid=org.opencastproject.execute.impl.ExecuteServiceImpl"
82      }
83  )
84  public class ExecuteServiceImpl extends AbstractJobProducer implements ExecuteService, ManagedService {
85  
86    public enum Operation {
87      Execute_Element, Execute_Mediapackage
88    }
89  
90    /** The logging facility */
91    private static final Logger logger = LoggerFactory.getLogger(ExecuteServiceImpl.class);
92  
93    /** Reference to the receipt service */
94    private ServiceRegistry serviceRegistry = null;
95  
96    /** The security service */
97    protected SecurityService securityService = null;
98  
99    /** The user directory service */
100   protected UserDirectoryService userDirectoryService = null;
101 
102   /** The organization directory service */
103   protected OrganizationDirectoryService organizationDirectoryService = null;
104 
105   /** The workspace service */
106   protected Workspace workspace;
107 
108   /**
109    * List of allowed commands that can be run with an executor. By convention, an empty set doesn't mean any command can
110    * be run. An '*' in the service configuration means any command can be executed
111    */
112   protected final Set<String> allowedCommands = new HashSet<String>();
113 
114   /** Bundle property specifying which commands can be run with this executor */
115   public static final String COMMANDS_ALLOWED_PROPERTY = "commands.allowed";
116 
117   /** To allow command-line parameter substitutions configured globally i.e. in config.properties */
118   private BundleContext bundleContext;
119 
120   /** To allow command-line parameter substitutions configured at the service level */
121   @SuppressWarnings("rawtypes")
122   private Dictionary properties = null;
123 
124   /** The approximate load placed on the system by running an execute operation */
125   public static final float DEFAULT_EXECUTE_JOB_LOAD = 0.1f;
126 
127   /** The key to look for in the service configuration file to override the {@link DEFAULT_EXECUTE_JOB_LOAD} */
128   public static final String EXECUTE_JOB_LOAD_KEY = "job.load.execute";
129 
130   private float executeJobLoad = 1.0f;
131 
132   /**
133    * Creates a new instance of the execute service.
134    */
135   public ExecuteServiceImpl() {
136     super(JOB_TYPE);
137   }
138 
139   /**
140    * Activates this component with its properties once all of the collaborating services have been set
141    *
142    * @param cc
143    *          The component's context, containing the properties used for configuration
144    */
145   @Override
146   @Activate
147   public void activate(ComponentContext cc) {
148     super.activate(cc);
149 
150     properties = cc.getProperties();
151 
152     if (properties != null) {
153       String commandString = (String) properties.get(COMMANDS_ALLOWED_PROPERTY);
154       if (StringUtils.isNotBlank(commandString)) {
155         logger.info("Execute Service permitted commands: {}", commandString);
156         for (String command : commandString.split("\\s+")) {
157           allowedCommands.add(command);
158         }
159       }
160     }
161 
162     this.bundleContext = cc.getBundleContext();
163   }
164 
165   /**
166    * {@inheritDoc}
167    *
168    * @see org.opencastproject.execute.api.ExecuteService#execute(java.lang.String, java.lang.String,
169    *      org.opencastproject.mediapackage.MediaPackageElement, java.lang.String,
170    *      org.opencastproject.mediapackage.MediaPackageElement.Type, float)
171    * @throws IllegalArgumentException
172    *           if the input arguments are incorrect
173    * @throws ExecuteException
174    *           if an internal error occurs
175    */
176   @Override
177   public Job execute(String exec, String params, MediaPackageElement inElement, String outFileName, Type expectedType,
178           float load) throws ExecuteException, IllegalArgumentException {
179 
180     logger.debug("Creating Execute Job for command: {}", exec);
181 
182     if (StringUtils.isBlank(exec)) {
183       throw new IllegalArgumentException("The command to execute cannot be null");
184     }
185 
186     if (StringUtils.isBlank(params)) {
187       throw new IllegalArgumentException("The command arguments cannot be null");
188     }
189 
190     if (inElement == null) {
191       throw new IllegalArgumentException("The input MediaPackage element cannot be null");
192     }
193 
194     outFileName = StringUtils.trimToNull(outFileName);
195     if ((outFileName == null) && (expectedType != null) || (outFileName != null) && (expectedType == null)) {
196       throw new IllegalArgumentException("Expected element type and output filename cannot be null");
197     }
198 
199     try {
200       List<String> paramList = new ArrayList<String>(5);
201       paramList.add(exec);
202       paramList.add(params);
203       paramList.add(MediaPackageElementParser.getAsXml(inElement));
204       paramList.add(outFileName);
205       paramList.add((expectedType == null) ? null : expectedType.toString());
206 
207       return serviceRegistry.createJob(JOB_TYPE, Operation.Execute_Element.toString(), paramList, load);
208 
209     } catch (ServiceRegistryException e) {
210       throw new ExecuteException(String.format("Unable to create a job of type '%s'", JOB_TYPE), e);
211     } catch (MediaPackageException e) {
212       throw new ExecuteException("Error serializing an element", e);
213     }
214   }
215 
216   /**
217    * {@inheritDoc}
218    *
219    * @see org.opencastproject.execute.api.ExecuteService#execute(java.lang.String, java.lang.String,
220    *      org.opencastproject.mediapackage.MediaPackage, java.lang.String,
221    *      org.opencastproject.mediapackage.MediaPackageElement.Type, float)
222    */
223   @Override
224   public Job execute(String exec, String params, MediaPackage mp, String outFileName, Type expectedType, float load)
225           throws ExecuteException {
226     if (StringUtils.isBlank(exec)) {
227       throw new IllegalArgumentException("The command to execute cannot be null");
228     }
229 
230     if (StringUtils.isBlank(params)) {
231       throw new IllegalArgumentException("The command arguments cannot be null");
232     }
233 
234     if (mp == null) {
235       throw new IllegalArgumentException("The input MediaPackage cannot be null");
236     }
237 
238     outFileName = StringUtils.trimToNull(outFileName);
239     if ((outFileName == null) && (expectedType != null) || (outFileName != null) && (expectedType == null)) {
240       throw new IllegalArgumentException("Expected element type and output filename cannot be null");
241     }
242 
243     try {
244       List<String> paramList = new ArrayList<String>(5);
245       paramList.add(exec);
246       paramList.add(params);
247       paramList.add(MediaPackageParser.getAsXml(mp));
248       paramList.add(outFileName);
249       paramList.add((expectedType == null) ? null : expectedType.toString());
250 
251       return serviceRegistry.createJob(JOB_TYPE, Operation.Execute_Mediapackage.toString(), paramList, load);
252     } catch (ServiceRegistryException e) {
253       throw new ExecuteException(String.format("Unable to create a job of type '%s'", JOB_TYPE), e);
254     }
255   }
256 
257   /**
258    * {@inheritDoc}
259    *
260    * @throws ExecuteException
261    *
262    * @see org.opencastproject.job.api.AbstractJobProducer#process(org.opencastproject.job.api.Job)
263    */
264   @Override
265   protected String process(Job job) throws ExecuteException {
266     List<String> arguments = new ArrayList<String>(job.getArguments());
267 
268     // Check this operation is allowed
269     if (!allowedCommands.contains("*") && !allowedCommands.contains(arguments.get(0))) {
270       throw new ExecuteException("Command '" + arguments.get(0) + "' is not allowed");
271     }
272 
273     String outFileName = null;
274     String strAux = null;
275     MediaPackage mp = null;
276     Type expectedType = null;
277     MediaPackageElement element = null;
278     Operation op = null;
279 
280     try {
281       op = Operation.valueOf(job.getOperation());
282 
283       int nargs = arguments.size();
284 
285       if (nargs != 3 && nargs != 5) {
286         throw new IndexOutOfBoundsException(
287                 "Incorrect number of parameters for operation execute_" + op + ": " + arguments.size());
288       }
289       if (nargs == 5) {
290         strAux = arguments.remove(4);
291         expectedType = (strAux == null) ? null : Type.valueOf(strAux);
292         outFileName = StringUtils.trimToNull(arguments.remove(3));
293         if ((StringUtils.isNotBlank(outFileName) && (expectedType == null))
294                 || (StringUtils.isBlank(outFileName) && (expectedType != null))) {
295           throw new ExecuteException("The output type and filename must be both specified");
296         }
297         outFileName = (outFileName == null) ? null : job.getId() + "_" + outFileName;
298       }
299 
300       switch (op) {
301         case Execute_Mediapackage:
302           mp = MediaPackageParser.getFromXml(arguments.remove(2));
303           return doProcess(arguments, mp, outFileName, expectedType);
304         case Execute_Element:
305           element = MediaPackageElementParser.getFromXml(arguments.remove(2));
306           return doProcess(arguments, element, outFileName, expectedType);
307         default:
308           throw new IllegalStateException("Don't know how to handle operation '" + job.getOperation() + "'");
309       }
310 
311     } catch (MediaPackageException e) {
312       throw new ExecuteException("Error unmarshalling the input mediapackage/element", e);
313     } catch (IllegalArgumentException e) {
314       throw new ExecuteException("This service can't handle operations of type '" + op + "'", e);
315     } catch (IndexOutOfBoundsException e) {
316       throw new ExecuteException("The argument list for operation '" + op + "' does not meet expectations", e);
317     }
318   }
319 
320   /**
321    * Does the actual processing, given a mediapackage (Execute Once WOH)
322    *
323    * @param arguments
324    *          The list containing the program and its arguments
325    * @param mp
326    *          MediaPackage used in the operation
327    * @param outFileName
328    *          The name of the resulting file
329    * @param expectedType
330    *          The expected element type
331    * @return A {@code String} containing the command output
332    * @throws ExecuteException
333    *           if some internal error occurred
334    */
335   protected String doProcess(List<String> arguments, MediaPackage mp, String outFileName, Type expectedType)
336           throws ExecuteException {
337 
338     String params = arguments.remove(1);
339 
340     File outFile = null;
341     MediaPackageElement[] elements = null;
342 
343     try {
344       if (outFileName != null) {
345         // FIXME : Find a better way to place the output File
346         File firstElement = workspace.get(mp.getElements()[0].getURI());
347         outFile = new File(firstElement.getParentFile(), outFileName);
348       }
349 
350       // Get the substitution pattern.
351       // The following pattern matches, any construct with the form
352       // #{name}
353       // , where 'name' is the value of a certain property. It is stored in the backreference group 1.
354       // Optionally, expressions can take a parameter, like
355       // #{name(parameter)}
356       // , where 'parameter' is the name of a certain parameter.
357       // If specified, 'parameter' is stored in the group 2. Otherwise it's null.
358       // Both name and parameter match any character sequence that does not contain {, }, ( or ) .
359       Pattern pat = Pattern.compile("#\\{([^\\{\\}\\(\\)]+)(?:\\(([^\\{\\}\\(\\)]+)\\))?\\}");
360 
361       // Substitute the appearances of the patterns with the actual absolute paths
362       Matcher matcher = pat.matcher(params);
363       StringBuffer sb = new StringBuffer();
364       while (matcher.find()) {
365         // group(1) = property. group(2) = (optional) parameter
366         if (matcher.group(1).equals("id")) {
367           matcher.appendReplacement(sb, mp.getIdentifier().toString());
368         } else if (matcher.group(1).equals("flavor")) {
369           elements = mp.getElementsByFlavor(MediaPackageElementFlavor.parseFlavor(matcher.group(2)));
370           if (elements.length == 0) {
371             throw new ExecuteException("No elements in the MediaPackage match the flavor '" + matcher.group(2) + "'.");
372           }
373 
374           if (elements.length > 1) {
375             logger.warn("Found more than one element with flavor '{}'. Using {} by default...", matcher.group(2),
376                 elements[0].getIdentifier());
377           }
378 
379           File elementFile = workspace.get(elements[0].getURI());
380           matcher.appendReplacement(sb, elementFile.getAbsolutePath());
381         } else if (matcher.group(1).equals("tags")) {
382           elements = mp.getElementsByTags(Arrays.asList(StringUtils.split(matcher.group(2), ",")));
383 
384           if (elements.length == 0) {
385             throw new ExecuteException("No elements in the MediaPackage match the tags '" + matcher.group(2) + "'.");
386           }
387 
388           if (elements.length > 1) {
389             logger.warn("Found more than one element with matching tags '{}'. Using {} by default...", matcher.group(2),
390                 elements[0].getIdentifier());
391           }
392 
393           File elementFile = workspace.get(elements[0].getURI());
394           matcher.appendReplacement(sb, elementFile.getAbsolutePath());
395         } else if (matcher.group(1).equals("out")) {
396           matcher.appendReplacement(sb, outFile.getAbsolutePath());
397         } else if (matcher.group(1).equals("org_id")) {
398           matcher.appendReplacement(sb, securityService.getOrganization().getId());
399         } else if (properties.get(matcher.group(1)) != null) {
400           matcher.appendReplacement(sb, (String) properties.get(matcher.group(1)));
401         } else if (bundleContext.getProperty(matcher.group(1)) != null) {
402           matcher.appendReplacement(sb, bundleContext.getProperty(matcher.group(1)));
403         }
404       }
405       matcher.appendTail(sb);
406       params = sb.toString();
407     } catch (IllegalArgumentException e) {
408       throw new ExecuteException("Tag 'flavor' must specify a valid MediaPackage element flavor.", e);
409     } catch (NotFoundException e) {
410       throw new ExecuteException(
411               "The element '" + elements[0].getURI().toString() + "' does not exist in the workspace.", e);
412     } catch (IOException e) {
413       throw new ExecuteException("Error retrieving MediaPackage element from workspace: '"
414               + elements[0].getURI().toString() + "'.", e);
415     }
416 
417     arguments.addAll(splitParameters(params));
418 
419     return runCommand(arguments, outFile, expectedType);
420   }
421 
422   /**
423    * Does the actual processing, given a mediapackage element (Execute Many WOH)
424    *
425    * @param arguments
426    *          The list containing the program and its arguments
427    * @param outFileName
428    *          The name of the resulting file
429    * @param expectedType
430    *          The expected element type
431    * @return A {@code String} containing the command output
432    * @throws ExecuteException
433    *           if some internal error occurred
434    */
435   protected String doProcess(List<String> arguments, MediaPackageElement element, String outFileName, Type expectedType)
436           throws ExecuteException {
437 
438     // arguments(1) contains a list of space-separated arguments for the command
439     String params = arguments.remove(1);
440     arguments.addAll(splitParameters(params));
441 
442     File outFile = null;
443 
444     try {
445       // Get the track file from the workspace
446       File trackFile = workspace.get(element.getURI());
447 
448       // Put the destination file in the same folder as the source file
449       if (outFileName != null) {
450         outFile = new File(trackFile.getParentFile(), outFileName);
451       }
452 
453       // Substitute the appearances of the patterns with the actual absolute paths
454       for (int i = 1; i < arguments.size(); i++) {
455         if (arguments.get(i).contains(INPUT_FILE_PATTERN)) {
456           arguments.set(i, arguments.get(i).replace(INPUT_FILE_PATTERN, trackFile.getAbsolutePath()));
457           continue;
458         }
459 
460         if (arguments.get(i).contains(OUTPUT_FILE_PATTERN)) {
461           if (outFile != null) {
462             arguments.set(i, arguments.get(i).replace(OUTPUT_FILE_PATTERN, outFile.getAbsolutePath()));
463             continue;
464           } else {
465             logger.error("{} pattern found, but no valid output filename was specified", OUTPUT_FILE_PATTERN);
466             throw new ExecuteException(
467                     OUTPUT_FILE_PATTERN + " pattern found, but no valid output filename was specified");
468           }
469         }
470 
471         if (arguments.get(i).contains(MP_ID_PATTERN)) {
472           arguments.set(i, arguments.get(i).replace(MP_ID_PATTERN,
473               element.getMediaPackage().getIdentifier().toString()));
474         }
475 
476         if (arguments.get(i).contains(ORG_ID_PATTERN)) {
477           arguments.set(i, arguments.get(i).replace(ORG_ID_PATTERN, securityService.getOrganization().getId()));
478         }
479       }
480 
481       return runCommand(arguments, outFile, expectedType);
482     } catch (IOException e) {
483       logger.error("Error retrieving file from workspace: {}", element.getURI());
484       throw new ExecuteException("Error retrieving file from workspace: " + element.getURI(), e);
485     } catch (NotFoundException e) {
486       logger.error("Element '{}' cannot be found in the workspace.", element.getURI());
487       throw new ExecuteException("Element " + element.getURI() + " cannot be found in the workspace");
488     }
489   }
490 
491   private String runCommand(List<String> command, File outFile, Type expectedType) throws ExecuteException {
492 
493     Process p = null;
494     int result = 0;
495 
496     try {
497       logger.info("Running command {}", command.get(0));
498       logger.debug("Starting subprocess {} with arguments {}", command.get(0),
499               StringUtils.join(command.subList(1, command.size()), ", "));
500 
501       ProcessBuilder pb = new ProcessBuilder(command);
502       pb.redirectErrorStream(true);
503 
504       p = pb.start();
505       BufferedReader stdout = new BufferedReader(new InputStreamReader(p.getInputStream()));
506       String line;
507       while ((line = stdout.readLine()) != null) {
508         logger.debug(line);
509       }
510       result = p.waitFor();
511 
512       logger.debug("Command {} finished with result {}", command.get(0), result);
513 
514       if (result == 0) {
515         // Read the command output
516         if (outFile != null) {
517           if (outFile.isFile()) {
518             URI newURI = workspace.putInCollection(ExecuteService.COLLECTION, outFile.getName(),
519                 new FileInputStream(outFile));
520             if (outFile.delete()) {
521               logger.debug("Deleted the local copy of the encoded file at {}", outFile.getAbsolutePath());
522             } else {
523               logger.warn("Unable to delete the encoding output at {}", outFile.getAbsolutePath());
524             }
525             return MediaPackageElementParser.getAsXml(MediaPackageElementBuilderFactory.newInstance()
526                     .newElementBuilder().elementFromURI(newURI, expectedType, null));
527           } else {
528             throw new ExecuteException("Expected output file does not exist: " + outFile.getAbsolutePath());
529           }
530         }
531         return "";
532       } else {
533         throw new ExecuteException(String.format("Process %s returned error code %d", command.get(0), result));
534       }
535     } catch (InterruptedException e) {
536       throw new ExecuteException("The executor thread has been unexpectedly interrupted", e);
537     } catch (IOException e) {
538       // Only log the first argument, the executable, as other arguments may contain sensitive values
539       // e.g. MySQL password/user, paths, etc. that should not be shown to caller
540       logger.error("Could not start subprocess {}", command.get(0));
541       throw new ExecuteException("Could not start subprocess: " + command.get(0), e);
542     } catch (UnsupportedElementException e) {
543       throw new ExecuteException("Couldn't create a new MediaPackage element of type " + expectedType.toString(), e);
544     } catch (ConfigurationException e) {
545       throw new ExecuteException("Couldn't instantiate a new MediaPackage element builder", e);
546     } catch (MediaPackageException e) {
547       throw new ExecuteException("Couldn't serialize a new Mediapackage element of type " + expectedType.toString(), e);
548     } finally {
549       IoSupport.closeQuietly(p);
550     }
551   }
552 
553   /**
554    * Returns a list of strings broken on whitespace characters except where those whitespace characters are escaped or
555    * quoted.
556    *
557    * @return list of individual arguments
558    */
559   private List<String> splitParameters(String input) {
560 
561     // This delimiter matches any non-escaped quote
562     final String quoteDelim = "(?<!\\\\)\"";
563 
564     // This delimiter matches any number of non-escaped spaces
565     final String spaceDelim = "((?<!\\\\)\\s)+";
566 
567     ArrayList<String> parsedInput = new ArrayList<String>();
568     boolean quoted = false;
569 
570     for (String token1 : input.split(quoteDelim)) {
571       if (quoted) {
572         parsedInput.add(token1);
573         quoted = false;
574       } else {
575         for (String token2 : token1.split(spaceDelim)) {
576           // This ignores empty tokens if quotes are at the beginning or the end of the string
577           if (!token2.isEmpty()) {
578             parsedInput.add(token2);
579           }
580         }
581         quoted = true;
582       }
583     }
584 
585     return parsedInput;
586   }
587 
588   /**
589    * Sets the receipt service
590    *
591    * @param serviceRegistry
592    *          the service registry
593    */
594   @Reference
595   public void setServiceRegistry(ServiceRegistry serviceRegistry) {
596     this.serviceRegistry = serviceRegistry;
597   }
598 
599   /**
600    * {@inheritDoc}
601    *
602    * @see org.opencastproject.job.api.AbstractJobProducer#getServiceRegistry()
603    */
604   @Override
605   protected ServiceRegistry getServiceRegistry() {
606     return serviceRegistry;
607   }
608 
609   /**
610    * {@inheritDoc}
611    *
612    * @see org.opencastproject.job.api.AbstractJobProducer#getSecurityService()
613    */
614   @Override
615   protected SecurityService getSecurityService() {
616     return securityService;
617   }
618 
619   /**
620    * Callback for setting the security service.
621    *
622    * @param securityService
623    *          the securityService to set
624    */
625   @Reference
626   public void setSecurityService(SecurityService securityService) {
627     this.securityService = securityService;
628   }
629 
630   /**
631    * Callback for setting the user directory service.
632    *
633    * @param userDirectoryService
634    *          the userDirectoryService to set
635    */
636   @Reference
637   public void setUserDirectoryService(UserDirectoryService userDirectoryService) {
638     this.userDirectoryService = userDirectoryService;
639   }
640 
641   /**
642    * {@inheritDoc}
643    *
644    * @see org.opencastproject.job.api.AbstractJobProducer#getUserDirectoryService()
645    */
646   @Override
647   protected UserDirectoryService getUserDirectoryService() {
648     return userDirectoryService;
649   }
650 
651   /**
652    * {@inheritDoc}
653    *
654    * @see org.opencastproject.job.api.AbstractJobProducer#getOrganizationDirectoryService()
655    */
656   @Override
657   protected OrganizationDirectoryService getOrganizationDirectoryService() {
658     return organizationDirectoryService;
659   }
660 
661   /**
662    * Sets a reference to the organization directory service.
663    *
664    * @param organizationDirectory
665    *          the organization directory
666    */
667   @Reference
668   public void setOrganizationDirectoryService(OrganizationDirectoryService organizationDirectory) {
669     this.organizationDirectoryService = organizationDirectory;
670   }
671 
672   /**
673    * Sets a reference to the workspace service.
674    *
675    * @param workspace
676    */
677   @Reference
678   public void setWorkspace(Workspace workspace) {
679     this.workspace = workspace;
680   }
681 
682   @Override
683   public void updated(@SuppressWarnings("rawtypes") Dictionary properties)
684           throws org.osgi.service.cm.ConfigurationException {
685     executeJobLoad = LoadUtil.getConfiguredLoadValue(properties, EXECUTE_JOB_LOAD_KEY, DEFAULT_EXECUTE_JOB_LOAD,
686             serviceRegistry);
687   }
688 
689 }