/** * 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.assetmanager.impl; import static java.lang.String.format; import static org.opencastproject.assetmanager.api.fn.Enrichments.enrich; import org.opencastproject.assetmanager.api.AssetManager; import org.opencastproject.assetmanager.api.Snapshot; import org.opencastproject.assetmanager.api.fn.Snapshots; import org.opencastproject.assetmanager.api.query.ADeleteQuery; import org.opencastproject.assetmanager.api.query.AQueryBuilder; import org.opencastproject.assetmanager.api.query.RichAResult; import org.opencastproject.assetmanager.api.query.Target; import org.opencastproject.assetmanager.impl.query.AbstractADeleteQuery.DeleteSnapshotHandler; import org.opencastproject.index.IndexProducer; import org.opencastproject.mediapackage.MediaPackage; import org.opencastproject.message.broker.api.MessageReceiver; import org.opencastproject.message.broker.api.MessageSender; import org.opencastproject.message.broker.api.MessageSender.DestinationType; import org.opencastproject.message.broker.api.assetmanager.AssetManagerItem; import org.opencastproject.message.broker.api.assetmanager.AssetManagerItem.TakeSnapshot; import org.opencastproject.message.broker.api.index.AbstractIndexProducer; import org.opencastproject.message.broker.api.index.IndexRecreateObject; import org.opencastproject.message.broker.api.index.IndexRecreateObject.Service; import org.opencastproject.security.api.AccessControlList; import org.opencastproject.security.api.AuthorizationService; import org.opencastproject.security.api.DefaultOrganization; import org.opencastproject.security.api.Organization; import org.opencastproject.security.api.OrganizationDirectoryService; import org.opencastproject.security.api.SecurityService; import org.opencastproject.security.util.SecurityUtil; import org.opencastproject.util.data.Effect0; import org.opencastproject.workspace.api.Workspace; import com.entwinemedia.fn.P1Lazy; import org.apache.commons.lang3.text.WordUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.Serializable; import java.util.Date; import java.util.List; import java.util.Map; /** * Bind an asset manager to ActiveMQ messaging. * <p> * Please make sure to {@link #close()} the AssetManager. */ public class AssetManagerWithMessaging extends AssetManagerDecorator implements DeleteSnapshotHandler, IndexProducer, AutoCloseable { /** Log facility */ private static final Logger logger = LoggerFactory.getLogger(AssetManagerWithMessaging.class); private final MessageSender messageSender; private final MessageReceiver messageReceiver; private final AuthorizationService authSvc; private final OrganizationDirectoryService orgDir; private final SecurityService secSvc; private final Workspace workspace; private final AbstractIndexProducer indexProducerMsgReceiver; public AssetManagerWithMessaging(final AssetManager delegate, final MessageSender messageSender, MessageReceiver messageReceiver, AuthorizationService authSvc, OrganizationDirectoryService orgDir, SecurityService secSvc, Workspace workspace, final String systemUserName) { super(delegate); this.messageSender = messageSender; this.messageReceiver = messageReceiver; this.authSvc = authSvc; this.orgDir = orgDir; this.secSvc = secSvc; this.workspace = workspace; this.indexProducerMsgReceiver = new AbstractIndexProducer() { @Override public String getClassName() { return AssetManagerWithMessaging.class.getName(); } @Override public MessageReceiver getMessageReceiver() { return AssetManagerWithMessaging.this.messageReceiver; } @Override public Service getService() { return Service.AssetManager; } @Override public MessageSender getMessageSender() { return AssetManagerWithMessaging.this.messageSender; } @Override public SecurityService getSecurityService() { return AssetManagerWithMessaging.this.secSvc; } @Override public String getSystemUserName() { return systemUserName; } @Override public void repopulate(final String indexName) throws Exception { final AQueryBuilder q = delegate.createQuery(); final RichAResult r = enrich(q.select(q.snapshot()).where(q.version().isLatest()).run()); logger.info(format("Populating index '%s' with %d snapshots | start", indexName, r.getSize())); final Map<String, List<Snapshot>> byOrg = r.getSnapshots().groupMulti(Snapshots.getOrganizationId); final IndexRecreationBatch batch = mkRecreationBatch(indexName, AssetManagerItem.ASSETMANAGER_QUEUE_PREFIX, (int) r.getSize()); for (final Map.Entry<String, List<Snapshot>> es : byOrg.entrySet()) { final Organization organization = AssetManagerWithMessaging.this.orgDir.getOrganization(es.getKey()); for (final Snapshot e : es.getValue()) { batch.update(organization, new P1Lazy<Serializable>() { @Override public Serializable get1() { return mkTakeSnapshotMessage(e); } }); } } logger.info("Populating index | end"); Organization organization = new DefaultOrganization(); SecurityUtil.runAs(getSecurityService(), organization, SecurityUtil.createSystemUser(getSystemUserName(), organization), new Effect0() { @Override protected void run() { String destinationId = AssetManagerItem.ASSETMANAGER_QUEUE_PREFIX + WordUtils.capitalize(indexName); messageSender.sendObjectMessage(destinationId, MessageSender.DestinationType.Queue, IndexRecreateObject.end(indexName, IndexRecreateObject.Service.AssetManager)); } }); } }; this.indexProducerMsgReceiver.activate(); } @Override public void close() throws Exception { indexProducerMsgReceiver.deactivate(); } @Override public Snapshot takeSnapshot(String owner, MediaPackage mp) { final Snapshot snapshot = super.takeSnapshot(owner, mp); notifyTakeSnapshot(snapshot); return snapshot; } @Override public AQueryBuilder createQuery() { return new AQueryBuilderDecorator(super.createQuery()) { @Override public ADeleteQuery delete(String owner, Target target) { return new ADeleteQueryWithMessaging(super.delete(owner, target)); } }; } public void notifyTakeSnapshot(Snapshot snapshot) { logger.info(format("Send update message for snapshot %s, %s to ActiveMQ", snapshot.getMediaPackage().getIdentifier().toString(), snapshot.getVersion())); messageSender.sendObjectMessage(AssetManagerItem.ASSETMANAGER_QUEUE, DestinationType.Queue, mkTakeSnapshotMessage(snapshot)); } @Override public void notifyDeleteSnapshot(String mpId, VersionImpl version) { logger.info(format("Send delete message for snapshot %s, %s to ActiveMQ", mpId, version)); messageSender.sendObjectMessage(AssetManagerItem.ASSETMANAGER_QUEUE, DestinationType.Queue, AssetManagerItem.deleteSnapshot(mpId, version.value(), new Date())); } @Override public void notifyDeleteEpisode(String mpId) { logger.info(format("Send delete message for episode %s to ActiveMQ", mpId)); messageSender.sendObjectMessage(AssetManagerItem.ASSETMANAGER_QUEUE, DestinationType.Queue, AssetManagerItem.deleteEpisode(mpId, new Date())); } /** * Create a {@link TakeSnapshot} message. * <p> * Do not call outside of a security context. */ private TakeSnapshot mkTakeSnapshotMessage(Snapshot snapshot) { final AccessControlList acl = authSvc.getActiveAcl(snapshot.getMediaPackage()).getA(); return AssetManagerItem.add(workspace, snapshot.getMediaPackage(), acl, getVersionLong(snapshot), snapshot.getArchivalDate()); } private long getVersionLong(Snapshot snapshot) { try { return Long.parseLong(snapshot.getVersion().toString()); } catch (NumberFormatException e) { // The index requires a version to be a long value. // Since the asset manager default implementation uses long values that should be not a problem. // However, a decent exception message is helpful if a different implementation of the asset manager // is used. throw new RuntimeException("The current implementation of the index requires versions being of type 'long'."); } } /* * ------------------------------------------------------------------------------------------------------------------ */ @Override public void repopulate(String indexName) throws Exception { indexProducerMsgReceiver.repopulate(indexName); } /* * ------------------------------------------------------------------------------------------------------------------ */ /** * Call {@link org.opencastproject.assetmanager.impl.query.AbstractADeleteQuery#run(DeleteSnapshotHandler)} with a * delete handler that sends messages to ActiveMQ. Also make sure to propagate the behaviour to subsequent instances. */ private final class ADeleteQueryWithMessaging extends ADeleteQueryDecorator { ADeleteQueryWithMessaging(ADeleteQuery delegate) { super(delegate); } @Override public long run() { return RuntimeTypes.convert(delegate).run(AssetManagerWithMessaging.this); } @Override protected ADeleteQueryDecorator mkDecorator(ADeleteQuery delegate) { return new ADeleteQueryWithMessaging(delegate); } } }