org.opencastproject.event.handler.SearchUpdatedEventHandler Maven / Gradle / Ivy
The newest version!
/*
* Licensed to The Apereo Foundation under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
*
* The Apereo Foundation licenses this file to you under the Educational
* Community License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License
* at:
*
* http://opensource.org/licenses/ecl2.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
*/
package org.opencastproject.event.handler;
import static org.opencastproject.job.api.Job.Status.FINISHED;
import static org.opencastproject.mediapackage.MediaPackageElementParser.getFromXml;
import static org.opencastproject.mediapackage.MediaPackageElements.XACML_POLICY_EPISODE;
import static org.opencastproject.workflow.handler.distribution.EngagePublicationChannel.CHANNEL_ID;
import org.opencastproject.distribution.api.DistributionException;
import org.opencastproject.distribution.api.DistributionService;
import org.opencastproject.job.api.Job;
import org.opencastproject.job.api.JobBarrier;
import org.opencastproject.job.api.JobBarrier.Result;
import org.opencastproject.mediapackage.Attachment;
import org.opencastproject.mediapackage.Catalog;
import org.opencastproject.mediapackage.MediaPackage;
import org.opencastproject.mediapackage.MediaPackageElement;
import org.opencastproject.mediapackage.MediaPackageElements;
import org.opencastproject.mediapackage.MediaPackageException;
import org.opencastproject.message.broker.api.series.SeriesItem;
import org.opencastproject.metadata.dublincore.DublinCore;
import org.opencastproject.metadata.dublincore.DublinCoreCatalog;
import org.opencastproject.metadata.dublincore.DublinCoreCatalogService;
import org.opencastproject.metadata.dublincore.DublinCoreUtil;
import org.opencastproject.search.api.SearchException;
import org.opencastproject.search.api.SearchService;
import org.opencastproject.security.api.AclScope;
import org.opencastproject.security.api.AuthorizationService;
import org.opencastproject.security.api.Organization;
import org.opencastproject.security.api.OrganizationDirectoryService;
import org.opencastproject.security.api.SecurityService;
import org.opencastproject.security.api.UnauthorizedException;
import org.opencastproject.security.api.User;
import org.opencastproject.security.util.SecurityUtil;
import org.opencastproject.serviceregistry.api.ServiceRegistry;
import org.opencastproject.serviceregistry.api.ServiceRegistryException;
import org.opencastproject.util.NotFoundException;
import org.opencastproject.workspace.api.Workspace;
import org.apache.commons.io.FilenameUtils;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.URI;
import java.util.List;
/** Responds to series events by re-distributing metadata and security policy files for published mediapackages. */
@Component(
immediate = true,
service = {
SearchUpdatedEventHandler.class
},
property = {
"service.description=Search Updated Event Handler"
}
)
public class SearchUpdatedEventHandler {
/** The logger */
protected static final Logger logger = LoggerFactory.getLogger(SearchUpdatedEventHandler.class);
/** The service registry */
protected ServiceRegistry serviceRegistry = null;
/** The distribution service */
protected DistributionService distributionService = null;
/** The search service */
protected SearchService searchService = null;
/** The security service */
protected SecurityService securityService = null;
/** The authorization service */
protected AuthorizationService authorizationService = null;
/** The organization directory */
protected OrganizationDirectoryService organizationDirectoryService = null;
/** Dublin core catalog service */
protected DublinCoreCatalogService dublinCoreService = null;
/** The workspace */
protected Workspace workspace = null;
/** The system account to use for running asynchronous events */
protected String systemAccount = null;
/**
* OSGI callback for component activation.
*
* @param bundleContext
* the OSGI bundle context
*/
@Activate
protected void activate(BundleContext bundleContext) {
this.systemAccount = bundleContext.getProperty("org.opencastproject.security.digest.user");
}
/**
* @param serviceRegistry
* the serviceRegistry to set
*/
@Reference
public void setServiceRegistry(ServiceRegistry serviceRegistry) {
this.serviceRegistry = serviceRegistry;
}
/**
* @param workspace
* the workspace to set
*/
@Reference
public void setWorkspace(Workspace workspace) {
this.workspace = workspace;
}
/**
* @param dublinCoreService
* the dublin core service to set
*/
@Reference
public void setDublinCoreCatalogService(DublinCoreCatalogService dublinCoreService) {
this.dublinCoreService = dublinCoreService;
}
/**
* @param distributionService
* the distributionService to set
*/
@Reference(target = "(distribution.channel=download)")
public void setDistributionService(DistributionService distributionService) {
this.distributionService = distributionService;
}
/**
* @param searchService
* the searchService to set
*/
@Reference
public void setSearchService(SearchService searchService) {
this.searchService = searchService;
}
/**
* @param securityService
* the securityService to set
*/
@Reference
public void setSecurityService(SecurityService securityService) {
this.securityService = securityService;
}
/**
* @param authorizationService
* the authorizationService to set
*/
@Reference
public void setAuthorizationService(AuthorizationService authorizationService) {
this.authorizationService = authorizationService;
}
/**
* @param organizationDirectoryService
* the organizationDirectoryService to set
*/
@Reference
public void setOrganizationDirectoryService(OrganizationDirectoryService organizationDirectoryService) {
this.organizationDirectoryService = organizationDirectoryService;
}
public void handleEvent(final SeriesItem seriesItem) {
// A series or its ACL has been updated. Find any mediapackages with that series, and update them.
logger.debug("Handling {}", seriesItem);
String seriesId = seriesItem.getSeriesId();
int jobBarrierPollingRate = 100; // in ms
// We must be an administrative user to make this query
final User prevUser = securityService.getUser();
final Organization prevOrg = securityService.getOrganization();
try {
securityService.setUser(SecurityUtil.createSystemUser(systemAccount, prevOrg));
for (var seriesData: searchService.getSeries(seriesId)) {
var mp = seriesData.getRight();
Organization org = seriesData.getLeft();
securityService.setOrganization(org);
// If the security policy has been updated, make sure to distribute that change
// to the distribution channels as well
if (SeriesItem.Type.UpdateAcl.equals(seriesItem.getType())) {
if (Boolean.TRUE.equals(seriesItem.getOverrideEpisodeAcl())) {
MediaPackageElement[] distributedEpisodeAcls = mp.getElementsByFlavor(XACML_POLICY_EPISODE);
for (MediaPackageElement distributedEpisodeAcl : distributedEpisodeAcls) {
List mpes = distributionService.retractSync(CHANNEL_ID, mp,
distributedEpisodeAcl.getIdentifier());
if (mpes == null) {
logger.error("Unable to retract episode XACML {}", distributedEpisodeAcl.getIdentifier());
} else {
authorizationService.removeAcl(mp, AclScope.Episode);
}
}
}
Attachment fileRepoCopy = authorizationService.setAcl(mp, AclScope.Series, seriesItem.getAcl()).getB();
// Distribute the updated XACML file
List mpes = distributionService.distributeSync(CHANNEL_ID, mp,
fileRepoCopy.getIdentifier());
if (mpes != null && mpes.size() == 1) {
mp.remove(fileRepoCopy);
mp.add(mpes.get(0));
} else {
logger.error("Unable to distribute series XACML {}", fileRepoCopy.getIdentifier());
continue;
}
}
// Update the series dublin core
if (SeriesItem.Type.UpdateCatalog.equals(seriesItem.getType())) {
DublinCoreCatalog seriesDublinCore = seriesItem.getMetadata();
mp.setSeriesTitle(seriesDublinCore.getFirst(DublinCore.PROPERTY_TITLE));
// Update the series dublin core
Catalog[] seriesCatalogs = mp.getCatalogs(MediaPackageElements.SERIES);
if (seriesCatalogs.length == 1) {
Catalog c = seriesCatalogs[0];
String filename = FilenameUtils.getName(c.getURI().toString());
URI uri = workspace.put(mp.getIdentifier().toString(), c.getIdentifier(), filename,
dublinCoreService.serialize(seriesDublinCore));
c.setURI(uri);
// setting the URI to a new source so the checksum will most like be invalid
c.setChecksum(null);
// Distribute the updated series dc
List mpes = distributionService.distributeSync(CHANNEL_ID, mp, c.getIdentifier());
if (mpes != null && mpes.size() == 1) {
mp.remove(c);
mp.add(mpes.get(0));
} else {
logger.error("Unable to distribute series catalog {}", c.getIdentifier());
continue;
}
}
}
// Remove the series catalog and isPartOf from episode catalog
if (SeriesItem.Type.Delete.equals(seriesItem.getType())) {
mp.setSeries(null);
mp.setSeriesTitle(null);
boolean retractSeriesCatalog = retractSeriesCatalog(mp);
boolean updateEpisodeCatalog = updateEpisodeCatalog(mp);
if (!retractSeriesCatalog || !updateEpisodeCatalog) {
continue;
}
}
// Update the search index with the modified mediapackage
searchService.addSynchronously(mp);
}
//We remove the episode->series links above, which effectively orphaned the series in the index, now we remove it
if (SeriesItem.Type.Delete.equals(seriesItem.getType())) {
searchService.deleteSeries(seriesId);
}
} catch (SearchException e) {
logger.warn("Unable to find mediapackages for series {} in search: {}", seriesItem, e.getMessage());
} catch (UnauthorizedException | MediaPackageException | ServiceRegistryException
| NotFoundException | IOException | DistributionException e) {
logger.warn("Unable to update mediapackages for series {} for user {}: {} {}",
seriesId, prevUser.getUsername(), e.getClass().getSimpleName(), e.getMessage());
} finally {
securityService.setOrganization(prevOrg);
securityService.setUser(prevUser);
}
}
private boolean retractSeriesCatalog(MediaPackage mp) throws DistributionException {
// Retract the series catalog
for (Catalog c : mp.getCatalogs(MediaPackageElements.SERIES)) {
Job retractJob = distributionService.retract(CHANNEL_ID, mp, c.getIdentifier());
JobBarrier barrier = new JobBarrier(null, serviceRegistry, retractJob);
Result jobResult = barrier.waitForJobs();
if (jobResult.getStatus().get(retractJob).equals(FINISHED)) {
mp.remove(c);
} else {
logger.error("Unable to retract series catalog {}", c.getIdentifier());
return false;
}
}
return true;
}
private boolean updateEpisodeCatalog(MediaPackage mp) throws DistributionException, MediaPackageException,
NotFoundException, ServiceRegistryException, IllegalArgumentException, IOException {
// Update the episode catalog
for (Catalog episodeCatalog : mp.getCatalogs(MediaPackageElements.EPISODE)) {
DublinCoreCatalog episodeDublinCore = DublinCoreUtil.loadDublinCore(workspace, episodeCatalog);
episodeDublinCore.remove(DublinCore.PROPERTY_IS_PART_OF);
String filename = FilenameUtils.getName(episodeCatalog.getURI().toString());
URI uri = workspace.put(mp.getIdentifier().toString(), episodeCatalog.getIdentifier(), filename,
dublinCoreService.serialize(episodeDublinCore));
episodeCatalog.setURI(uri);
// setting the URI to a new source so the checksum will most like be invalid
episodeCatalog.setChecksum(null);
// Distribute the updated episode dublincore
Job distributionJob = distributionService.distribute(CHANNEL_ID, mp, episodeCatalog.getIdentifier());
JobBarrier barrier = new JobBarrier(null, serviceRegistry, distributionJob);
Result jobResult = barrier.waitForJobs();
if (jobResult.getStatus().get(distributionJob).equals(FINISHED)) {
mp.remove(episodeCatalog);
mp.add(getFromXml(serviceRegistry.getJob(distributionJob.getId()).getPayload()));
} else {
logger.error("Unable to distribute episode catalog {}", episodeCatalog.getIdentifier());
return false;
}
}
return true;
}
}