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