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.workflow.handler.assetmanager;
23  
24  import org.opencastproject.assetmanager.api.AssetManager;
25  import org.opencastproject.assetmanager.api.Snapshot;
26  import org.opencastproject.job.api.JobContext;
27  import org.opencastproject.mediapackage.MediaPackage;
28  import org.opencastproject.mediapackage.MediaPackageElement;
29  import org.opencastproject.mediapackage.MediaPackageElementFlavor;
30  import org.opencastproject.mediapackage.selector.SimpleElementSelector;
31  import org.opencastproject.workflow.api.AbstractWorkflowOperationHandler;
32  import org.opencastproject.workflow.api.WorkflowInstance;
33  import org.opencastproject.workflow.api.WorkflowOperationException;
34  import org.opencastproject.workflow.api.WorkflowOperationHandler;
35  import org.opencastproject.workflow.api.WorkflowOperationInstance;
36  import org.opencastproject.workflow.api.WorkflowOperationResult;
37  
38  import org.apache.commons.lang3.StringUtils;
39  import org.osgi.service.component.annotations.Component;
40  import org.osgi.service.component.annotations.Reference;
41  import org.slf4j.Logger;
42  import org.slf4j.LoggerFactory;
43  
44  import java.util.ArrayList;
45  import java.util.Arrays;
46  import java.util.Collection;
47  import java.util.List;
48  
49  /**
50   * Replaces the media package in the current workflow with a previous version from the asset manager. There are two ways
51   * to choose the version: by version number or a combination of source-flavors and no-tags (choose the latest version
52   * where elements with source-flavor do not have the tags specified in no-tags.
53   *
54   * This operation should be the first one in a workflow executed from the archive because it REPLACES the media package
55   * used by the current workflow.
56   */
57  @Component(immediate = true, service = WorkflowOperationHandler.class, property = {
58          "service.description=Selects a mp version from the archive", "workflow.operation=select-version" })
59  public class SelectVersionWorkflowOperationHandler extends AbstractWorkflowOperationHandler {
60  
61    private static final Logger logger = LoggerFactory.getLogger(SelectVersionWorkflowOperationHandler.class);
62  
63    public static final String OPT_VERSION = "version";
64    public static final String OPT_NO_TAGS = "no-tags";
65    public static final String OPT_SOURCE_FLAVORS = "source-flavors";
66  
67    private AssetManager assetManager;
68  
69    @Override
70    public WorkflowOperationResult start(final WorkflowInstance workflowInstance, JobContext context)
71            throws WorkflowOperationException {
72  
73      final WorkflowOperationInstance currentOperation = workflowInstance.getCurrentOperation();
74      if (currentOperation == null) {
75        throw new WorkflowOperationException("Cannot get current workflow operation");
76      }
77      // Get current media package
78      MediaPackage mp = workflowInstance.getMediaPackage();
79      MediaPackage resultMp = null;
80  
81      // Make sure operation configuration is valid.
82      String version = StringUtils.trimToNull(currentOperation.getConfiguration(OPT_VERSION));
83      String noTagsOpt = StringUtils.trimToNull(currentOperation.getConfiguration(OPT_NO_TAGS));
84      String sourceFlavorsOpt = StringUtils.trimToNull(currentOperation.getConfiguration(OPT_SOURCE_FLAVORS));
85      if (version != null && (noTagsOpt != null || sourceFlavorsOpt != null)) {
86        throw new WorkflowOperationException(
87                String.format("Configuration error: '%s' cannot be used with '%s' and '%s'.",
88                OPT_VERSION, OPT_NO_TAGS, OPT_SOURCE_FLAVORS));
89      }
90  
91      // Specific version informed? If yes, use it.
92      if (version != null) {
93        try {
94          // Validate the number
95          Integer.parseInt(version);
96          resultMp = findVersion(mp.getIdentifier().toString(), version);
97          if (resultMp == null) {
98            throw new WorkflowOperationException(
99                    String.format("Could not find version %d of mp %s in the archive", mp.getIdentifier(), version));
100         }
101       } catch (NumberFormatException e) {
102         throw new WorkflowOperationException("Invalid version passed: " + version);
103       }
104     } else {
105       if (noTagsOpt == null || sourceFlavorsOpt == null) {
106         throw new WorkflowOperationException(String.format("Configuration error: both '%s' and '%s' must be passed.",
107                 OPT_NO_TAGS, OPT_SOURCE_FLAVORS));
108       }
109       Collection<String> noTags = Arrays.asList(noTagsOpt.split(","));
110 
111       SimpleElementSelector elementSelector = new SimpleElementSelector();
112       for (MediaPackageElementFlavor flavor : parseFlavors(sourceFlavorsOpt)) {
113         elementSelector.addFlavor(flavor);
114       }
115 
116       resultMp = findVersionWithNoTags(mp.getIdentifier().toString(), elementSelector, noTags);
117       if (resultMp == null) {
118         throw new WorkflowOperationException(String.format(
119                 "Could not find in the archive a version of mp %s that does not have the tags %s in element flavors %s",
120                 mp.getIdentifier(), noTagsOpt, sourceFlavorsOpt));
121       }
122     }
123     return createResult(resultMp, WorkflowOperationResult.Action.CONTINUE);
124   }
125 
126   private MediaPackage findVersion(String mpId, String version) throws WorkflowOperationException {
127     // Get the specific version from the asset manager
128     List<Snapshot> snapshots = assetManager.getSnapshotsByIdAndVersion(mpId, assetManager.toVersion(version).get());
129 
130     if (snapshots.size() > 1) {
131       // Version not found
132       throw new WorkflowOperationException(
133           String.format("Multiple media package %s, version %s found in the archive.", mpId, version));
134     }
135     if (snapshots.isEmpty()) {
136       // Version not found
137       throw new WorkflowOperationException(
138               String.format("Media package %s, version %s not found in the archive.", mpId, version));
139     }
140 
141     logger.info("Replacing current media package with version: {}", version);
142     return snapshots.get(0).getMediaPackage();
143   }
144 
145   private MediaPackage findVersionWithNoTags(String mpId, SimpleElementSelector elementSelector,
146           Collection<String> tags) throws WorkflowOperationException {
147     // Get all the snapshots from the asset manager
148     List<Snapshot> snapshots = assetManager.getSnapshotsByIdOrderedByVersion(mpId, false);
149 
150     if (snapshots.isEmpty()) {
151       // This is strange because it should run from the archive
152       throw new WorkflowOperationException("Media package not found in the archive: " + mpId);
153     }
154 
155     nextVersion: for (Snapshot snapshot : snapshots) {
156       MediaPackage mp = snapshot.getMediaPackage();
157       for (MediaPackageElement el : elementSelector.select(mp, false)) {
158         for (String t : el.getTags()) {
159           if (tags.contains(t)) {
160             continue nextVersion;
161           }
162         }
163       }
164       logger.info("Replacing current media package with version: {}", snapshot.getVersion());
165       return mp;
166     }
167     return null;
168   }
169 
170   private List<MediaPackageElementFlavor> parseFlavors(String flavorStr) {
171     List<MediaPackageElementFlavor> flavors = new ArrayList<MediaPackageElementFlavor>();
172     if (flavorStr != null) {
173       for (String flavor : asList(flavorStr)) {
174         flavors.add(MediaPackageElementFlavor.parseFlavor(flavor));
175       }
176     }
177     return flavors;
178   }
179 
180   @Reference
181   public void setAssetManager(AssetManager service) {
182     this.assetManager = service;
183   }
184 }