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.event.handler;
23  
24  
25  import org.opencastproject.assetmanager.api.AssetManager;
26  import org.opencastproject.assetmanager.api.AssetManagerException;
27  import org.opencastproject.assetmanager.api.Snapshot;
28  import org.opencastproject.mediapackage.Attachment;
29  import org.opencastproject.mediapackage.Catalog;
30  import org.opencastproject.mediapackage.MediaPackage;
31  import org.opencastproject.mediapackage.MediaPackageElementFlavor;
32  import org.opencastproject.mediapackage.MediaPackageElements;
33  import org.opencastproject.mediapackage.MediaPackageException;
34  import org.opencastproject.message.broker.api.series.SeriesItem;
35  import org.opencastproject.metadata.dublincore.DublinCore;
36  import org.opencastproject.metadata.dublincore.DublinCoreCatalog;
37  import org.opencastproject.metadata.dublincore.DublinCoreCatalogService;
38  import org.opencastproject.metadata.dublincore.DublinCoreUtil;
39  import org.opencastproject.security.api.AclScope;
40  import org.opencastproject.security.api.AuthorizationService;
41  import org.opencastproject.security.api.Organization;
42  import org.opencastproject.security.api.OrganizationDirectoryService;
43  import org.opencastproject.security.api.SecurityService;
44  import org.opencastproject.security.api.User;
45  import org.opencastproject.security.util.SecurityUtil;
46  import org.opencastproject.util.NotFoundException;
47  import org.opencastproject.util.data.Tuple;
48  import org.opencastproject.workspace.api.Workspace;
49  
50  import org.apache.commons.io.FilenameUtils;
51  import org.osgi.framework.BundleContext;
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.IOException;
59  import java.net.URI;
60  import java.util.Collections;
61  import java.util.Comparator;
62  import java.util.List;
63  
64  /**
65   * Responds to series events by re-distributing metadata and security policy files to episodes.
66   */
67  @Component(
68      immediate = true,
69      service = {
70          AssetManagerUpdatedEventHandler.class
71      },
72      property = {
73          "service.description=AssetManagerUpdatedEventHandler"
74      }
75  )
76  public class AssetManagerUpdatedEventHandler {
77  
78    /** The logger */
79    protected static final Logger logger = LoggerFactory.getLogger(AssetManagerUpdatedEventHandler.class);
80  
81    /** The archive */
82    protected AssetManager assetManager = null;
83  
84    /** The security service */
85    protected SecurityService securityService = null;
86  
87    /** The authorization service */
88    protected AuthorizationService authorizationService = null;
89  
90    /** The organization directory */
91    protected OrganizationDirectoryService organizationDirectoryService = null;
92  
93    /** Dublin core catalog service */
94    protected DublinCoreCatalogService dublinCoreService = null;
95  
96    /** The workspace */
97    protected Workspace workspace = null;
98  
99    /** The system account to use for running asynchronous events */
100   protected String systemAccount = null;
101 
102   /**
103    * OSGI callback for component activation.
104    *
105    * @param bundleContext
106    *          the OSGI bundle context
107    */
108   @Activate
109   protected void activate(BundleContext bundleContext) {
110     this.systemAccount = bundleContext.getProperty("org.opencastproject.security.digest.user");
111   }
112 
113   /**
114    * @param workspace
115    *          the workspace to set
116    */
117   @Reference
118   public void setWorkspace(Workspace workspace) {
119     this.workspace = workspace;
120   }
121 
122   /**
123    * @param dublinCoreService
124    *          the dublin core service to set
125    */
126   @Reference
127   public void setDublinCoreCatalogService(DublinCoreCatalogService dublinCoreService) {
128     this.dublinCoreService = dublinCoreService;
129   }
130 
131   /**
132    * @param assetManager
133    *          the asset manager to set
134    */
135   @Reference
136   public void setAssetManager(AssetManager assetManager) {
137     this.assetManager = assetManager;
138   }
139 
140   /**
141    * @param securityService
142    *          the securityService to set
143    */
144   @Reference
145   public void setSecurityService(SecurityService securityService) {
146     this.securityService = securityService;
147   }
148 
149   /**
150    * @param authorizationService
151    *          the authorizationService to set
152    */
153   @Reference
154   public void setAuthorizationService(AuthorizationService authorizationService) {
155     this.authorizationService = authorizationService;
156   }
157 
158   /**
159    * @param organizationDirectoryService
160    *          the organizationDirectoryService to set
161    */
162   @Reference
163   public void setOrganizationDirectoryService(OrganizationDirectoryService organizationDirectoryService) {
164     this.organizationDirectoryService = organizationDirectoryService;
165   }
166 
167   public void handleEvent(final SeriesItem seriesItem) {
168     // A series or its ACL has been updated. Find any mediapackages with that series, and update them.
169     logger.debug("Handling {}", seriesItem);
170     String seriesId = seriesItem.getSeriesId();
171 
172     // We must be an administrative user to make this query
173     final User prevUser = securityService.getUser();
174     final Organization prevOrg = securityService.getOrganization();
175     try {
176       securityService.setUser(SecurityUtil.createSystemUser(systemAccount, prevOrg));
177 
178       List<Snapshot> snapshots = assetManager.getLatestSnapshotsBySeriesId(seriesId);
179 
180       Collections.sort(
181           snapshots,
182           Comparator.comparing(s->s.getMediaPackage().getIdentifier().toString())
183       );
184 
185       for (Snapshot snapshot : snapshots) {
186         final String orgId = snapshot.getOrganizationId();
187         final Organization organization = organizationDirectoryService.getOrganization(orgId);
188         if (organization == null) {
189           logger.warn("Skipping update of episode {} since organization {} is unknown",
190                   snapshot.getMediaPackage().getIdentifier().toString(), orgId);
191           continue;
192         }
193         securityService.setOrganization(organization);
194 
195         MediaPackage mp = snapshot.getMediaPackage();
196 
197         // Update the series XACML file
198         Tuple<MediaPackage, Attachment> mpAclAttachmentTuple = null;
199         if (SeriesItem.Type.UpdateAcl.equals(seriesItem.getType())) {
200           // Build a new XACML file for this mediapackage
201           try {
202             if (seriesItem.getOverrideEpisodeAcl()) {
203               authorizationService.removeAcl(mp, AclScope.Episode);
204             }
205             mpAclAttachmentTuple = authorizationService.setAcl(mp,
206                 AclScope.Series, seriesItem.getAcl());
207           } catch (MediaPackageException e) {
208             logger.error("Error setting ACL for media package {}", mp.getIdentifier(), e);
209           }
210         }
211 
212         // Update the series dublin core or extended metadata
213         if (SeriesItem.Type.UpdateCatalog.equals(seriesItem.getType())
214                 || SeriesItem.Type.UpdateElement.equals(seriesItem.getType())) {
215           DublinCoreCatalog seriesDublinCore = null;
216           MediaPackageElementFlavor catalogType = null;
217           if (SeriesItem.Type.UpdateCatalog.equals(seriesItem.getType())) {
218             seriesDublinCore = seriesItem.getMetadata();
219             mp.setSeriesTitle(seriesDublinCore.getFirst(DublinCore.PROPERTY_TITLE));
220             catalogType = MediaPackageElements.SERIES;
221           } else {
222             seriesDublinCore = seriesItem.getExtendedMetadata();
223             catalogType = MediaPackageElementFlavor.flavor(seriesItem.getElementType(), "series");
224           }
225 
226           // Update the series dublin core
227           Catalog[] seriesCatalogs = mp.getCatalogs(catalogType);
228           if (seriesCatalogs.length == 1) {
229             Catalog c = seriesCatalogs[0];
230             String filename = FilenameUtils.getName(c.getURI().toString());
231             URI uri = workspace.put(mp.getIdentifier().toString(), c.getIdentifier(), filename,
232                     dublinCoreService.serialize(seriesDublinCore));
233             c.setURI(uri);
234             // setting the URI to a new source so the checksum will most like be invalid
235             c.setChecksum(null);
236           }
237         }
238 
239         // Remove the series catalogs and isPartOf from episode catalog
240         if (SeriesItem.Type.Delete.equals(seriesItem.getType())) {
241           mp.setSeries(null);
242           mp.setSeriesTitle(null);
243           for (Catalog seriesCatalog : mp.getCatalogs(MediaPackageElements.SERIES)) {
244             mp.remove(seriesCatalog);
245           }
246           authorizationService.removeAcl(mp, AclScope.Series);
247           for (Catalog episodeCatalog : mp.getCatalogs(MediaPackageElements.EPISODE)) {
248             DublinCoreCatalog episodeDublinCore = DublinCoreUtil.loadDublinCore(workspace, episodeCatalog);
249             episodeDublinCore.remove(DublinCore.PROPERTY_IS_PART_OF);
250             String filename = FilenameUtils.getName(episodeCatalog.getURI().toString());
251             URI uri = workspace.put(mp.getIdentifier().toString(), episodeCatalog.getIdentifier(), filename,
252                     dublinCoreService.serialize(episodeDublinCore));
253             episodeCatalog.setURI(uri);
254             // setting the URI to a new source so the checksum will most like be invalid
255             episodeCatalog.setChecksum(null);
256           }
257           // here we don't know the series extended metadata types,
258           // we assume that all series catalog flavors have a fixed subtype: series
259           MediaPackageElementFlavor seriesFlavor = MediaPackageElementFlavor.flavor("*", "series");
260           for (Catalog catalog : mp.getCatalogs()) {
261             if (catalog.getFlavor().matches(seriesFlavor)) {
262               mp.remove(catalog);
263             }
264           }
265         }
266 
267         try {
268           // Update the asset manager with the modified mediapackage
269           assetManager.takeSnapshot(snapshot.getOwner(), mp);
270         } catch (AssetManagerException e) {
271           logger.error("Error updating mediapackage {}", mp.getIdentifier().toString(), e);
272         } finally {
273           if (mpAclAttachmentTuple != null) {
274             try {
275               workspace.delete(mpAclAttachmentTuple.getB().getURI());
276             } catch (Exception ex) {
277               // We only want to clean up. If the file is gone, that is fine too.
278             }
279           }
280         }
281       }
282     } catch (IOException | NotFoundException e) {
283       logger.warn("Unable to handle update event for series {} for user {}: {}",
284                   seriesItem, prevUser.getUsername(), e.getMessage());
285     } finally {
286       securityService.setOrganization(prevOrg);
287       securityService.setUser(prevUser);
288     }
289   }
290 }