All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opencastproject.assetmanager.impl.AssetManagerImpl Maven / Gradle / Ivy

There is a newer version: 16.7
Show 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.assetmanager.impl;

import static com.entwinemedia.fn.Prelude.chuck;
import static com.entwinemedia.fn.Stream.$;
import static java.lang.String.format;
import static org.opencastproject.assetmanager.api.fn.Enrichments.enrich;
import static org.opencastproject.mediapackage.MediaPackageSupport.Filters.hasNoChecksum;
import static org.opencastproject.mediapackage.MediaPackageSupport.Filters.isNotPublication;
import static org.opencastproject.mediapackage.MediaPackageSupport.getFileName;
import static org.opencastproject.mediapackage.MediaPackageSupport.getMediaPackageElementId;
import static org.opencastproject.security.api.SecurityConstants.GLOBAL_ADMIN_ROLE;
import static org.opencastproject.security.api.SecurityConstants.GLOBAL_CAPTURE_AGENT_ROLE;

import org.opencastproject.assetmanager.api.Asset;
import org.opencastproject.assetmanager.api.AssetId;
import org.opencastproject.assetmanager.api.AssetManager;
import org.opencastproject.assetmanager.api.AssetManagerException;
import org.opencastproject.assetmanager.api.Availability;
import org.opencastproject.assetmanager.api.Property;
import org.opencastproject.assetmanager.api.PropertyId;
import org.opencastproject.assetmanager.api.Snapshot;
import org.opencastproject.assetmanager.api.Value;
import org.opencastproject.assetmanager.api.Version;
import org.opencastproject.assetmanager.api.fn.Enrichments;
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.ARecord;
import org.opencastproject.assetmanager.api.query.AResult;
import org.opencastproject.assetmanager.api.query.ASelectQuery;
import org.opencastproject.assetmanager.api.query.Predicate;
import org.opencastproject.assetmanager.api.query.RichAResult;
import org.opencastproject.assetmanager.api.query.Target;
import org.opencastproject.assetmanager.api.storage.AssetStore;
import org.opencastproject.assetmanager.api.storage.DeletionSelector;
import org.opencastproject.assetmanager.api.storage.RemoteAssetStore;
import org.opencastproject.assetmanager.api.storage.Source;
import org.opencastproject.assetmanager.api.storage.StoragePath;
import org.opencastproject.assetmanager.impl.persistence.AssetDtos;
import org.opencastproject.assetmanager.impl.persistence.Database;
import org.opencastproject.assetmanager.impl.persistence.SnapshotDto;
import org.opencastproject.assetmanager.impl.query.AQueryBuilderImpl;
import org.opencastproject.assetmanager.impl.query.AbstractADeleteQuery;
import org.opencastproject.authorization.xacml.manager.api.AclServiceFactory;
import org.opencastproject.authorization.xacml.manager.api.ManagedAcl;
import org.opencastproject.authorization.xacml.manager.util.AccessInformationUtil;
import org.opencastproject.elasticsearch.api.SearchIndexException;
import org.opencastproject.elasticsearch.index.AbstractSearchIndex;
import org.opencastproject.elasticsearch.index.event.Event;
import org.opencastproject.elasticsearch.index.event.EventIndexUtils;
import org.opencastproject.index.rebuild.AbstractIndexProducer;
import org.opencastproject.index.rebuild.IndexProducer;
import org.opencastproject.index.rebuild.IndexRebuildException;
import org.opencastproject.index.rebuild.IndexRebuildService;
import org.opencastproject.mediapackage.Catalog;
import org.opencastproject.mediapackage.MediaPackage;
import org.opencastproject.mediapackage.MediaPackageElement;
import org.opencastproject.mediapackage.MediaPackageElements;
import org.opencastproject.mediapackage.MediaPackageParser;
import org.opencastproject.mediapackage.MediaPackageSupport;
import org.opencastproject.message.broker.api.MessageSender;
import org.opencastproject.message.broker.api.assetmanager.AssetManagerItem;
import org.opencastproject.metadata.dublincore.DublinCores;
import org.opencastproject.security.api.AccessControlEntry;
import org.opencastproject.security.api.AccessControlList;
import org.opencastproject.security.api.AccessControlParser;
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.Role;
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.util.Checksum;
import org.opencastproject.util.ChecksumType;
import org.opencastproject.util.MimeTypes;
import org.opencastproject.util.NotFoundException;
import org.opencastproject.util.RequireUtil;
import org.opencastproject.util.data.functions.Functions;
import org.opencastproject.workspace.api.Workspace;

import com.entwinemedia.fn.Fn;
import com.entwinemedia.fn.Fx;
import com.entwinemedia.fn.P1;
import com.entwinemedia.fn.P1Lazy;
import com.entwinemedia.fn.Pred;
import com.entwinemedia.fn.Prelude;
import com.entwinemedia.fn.data.Opt;
import com.entwinemedia.fn.fns.Booleans;
import com.entwinemedia.fn.fns.Strings;
import com.google.common.collect.Sets;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;

import javax.persistence.EntityManagerFactory;

/**
 * The Asset Manager implementation.
 */
@Component(
    property = {
        "service.description=Opencast Asset Manager"
    },
    immediate = true,
    service = { AssetManager.class, IndexProducer.class }
)
public class AssetManagerImpl extends AbstractIndexProducer implements AssetManager,
        AbstractADeleteQuery.DeleteSnapshotHandler {

  private static final Logger logger = LoggerFactory.getLogger(AssetManagerImpl.class);

  enum AdminRole {
    GLOBAL, ORGANIZATION, NONE
  }

  public static final String WRITE_ACTION = "write";
  public static final String READ_ACTION = "read";
  public static final String SECURITY_NAMESPACE = "org.opencastproject.assetmanager.security";

  private static final String MANIFEST_DEFAULT_NAME = "manifest";

  private SecurityService securityService;
  private AuthorizationService authorizationService;
  private OrganizationDirectoryService orgDir;
  private Workspace workspace;
  private AssetStore assetStore;
  private HttpAssetProvider httpAssetProvider;
  private MessageSender messageSender;
  private String systemUserName;
  private Database db;
  private AclServiceFactory aclServiceFactory;
  private AbstractSearchIndex adminUiIndex;
  private AbstractSearchIndex externalApiIndex;

  // Settings for role filter
  private boolean includeAPIRoles;
  private boolean includeCARoles;
  private boolean includeUIRoles;

  public static final Set MOVABLE_TYPES = Sets.newHashSet(
          MediaPackageElement.Type.Attachment,
          MediaPackageElement.Type.Catalog,
          MediaPackageElement.Type.Track
  );

  private final HashMap remoteStores = new LinkedHashMap<>();

  /**
   * OSGi callback.
   */
  @Activate
  public synchronized void activate(ComponentContext cc) {
    logger.info("Activating AssetManager.");
    systemUserName = SecurityUtil.getSystemUserName(cc);

    includeAPIRoles = BooleanUtils.toBoolean(Objects.toString(cc.getProperties().get("includeAPIRoles"), null));
    includeCARoles = BooleanUtils.toBoolean(Objects.toString(cc.getProperties().get("includeCARoles"), null));
    includeUIRoles = BooleanUtils.toBoolean(Objects.toString(cc.getProperties().get("includeUIRoles"), null));
  }

  /**
   * OSGi dependencies
   */

  @Reference(name = "entityManagerFactory", target = "(osgi.unit.name=org.opencastproject.assetmanager.impl)")
  public void setEntityManagerFactory(EntityManagerFactory emf) {
    this.db = new Database(emf);
  }

  @Reference(name = "securityService")
  public void setSecurityService(SecurityService securityService) {
    this.securityService = securityService;
  }

  @Reference(name = "authorizationService")
  public void setAuthorizationService(AuthorizationService authorizationService) {
    this.authorizationService = authorizationService;
  }

  @Reference(name = "orgDir")
  public void setOrgDir(OrganizationDirectoryService orgDir) {
    this.orgDir = orgDir;
  }

  @Reference(name = "workspace")
  public void setWorkspace(Workspace workspace) {
    this.workspace = workspace;
  }

  @Reference(name = "assetStore")
  public void setAssetStore(AssetStore assetStore) {
    this.assetStore = assetStore;
  }

  @Reference(
      name = "remoteAssetStores",
      cardinality = ReferenceCardinality.MULTIPLE,
      policy = ReferencePolicy.DYNAMIC,
      unbind = "removeRemoteAssetStore"
  )
  public synchronized void addRemoteAssetStore(RemoteAssetStore assetStore) {
    remoteStores.put(assetStore.getStoreType(), assetStore);
  }

  public void removeRemoteAssetStore(RemoteAssetStore store) {
    remoteStores.remove(store.getStoreType());
  }

  @Reference(name = "httpAssetProvider")
  public void setHttpAssetProvider(HttpAssetProvider httpAssetProvider) {
    this.httpAssetProvider = httpAssetProvider;
  }

  @Reference(name = "messageSender")
  public void setMessageSender(MessageSender messageSender) {
    this.messageSender = messageSender;
  }

  @Reference(name = "aclServiceFactory")
  public void setAclServiceFactory(AclServiceFactory aclServiceFactory) {
    this.aclServiceFactory = aclServiceFactory;
  }

  @Reference(name = "adminUiIndex", target = "(index.name=adminui)")
  public void setAdminUiIndex(AbstractSearchIndex index) {
    this.adminUiIndex = index;
  }

  @Reference(name = "externalApiIndex", target = "(index.name=externalapi)")
  public void setExternalApiIndex(AbstractSearchIndex index) {
    this.externalApiIndex = index;
  }

  /**
   * AssetManager implementation
   */

  @Override
  public Opt getMediaPackage(String mediaPackageId) {
    final AQueryBuilder q = createQuery();
    final AResult r = q.select(q.snapshot()).where(q.mediaPackageId(mediaPackageId).and(q.version().isLatest()))
            .run();

    if (r.getSize() == 0) {
      return Opt.none();
    }
    return Opt.some(r.getRecords().head2().getSnapshot().get().getMediaPackage());
  }

  @Override
  public Opt getAsset(Version version, String mpId, String mpElementId) {
    if (isAuthorized(mpId, READ_ACTION)) {
      // try to fetch the asset
      for (final AssetDtos.Medium asset : getDatabase().getAsset(RuntimeTypes.convert(version), mpId, mpElementId)) {
        for (final String storageId : getSnapshotStorageLocation(version, mpId)) {
          for (final AssetStore store : getAssetStore(storageId)) {
            for (final InputStream assetStream
                    : store.get(StoragePath.mk(asset.getOrganizationId(), mpId, version, mpElementId))) {

              Checksum checksum = null;
              try {
                checksum = Checksum.fromString(asset.getAssetDto().getChecksum());
              } catch (NoSuchAlgorithmException e) {
                logger.warn("Invalid checksum for asset {} of media package {}", mpElementId, mpId, e);
              }

              final Asset a = new AssetImpl(
                      AssetId.mk(version, mpId, mpElementId),
                      assetStream,
                      asset.getAssetDto().getMimeType(),
                      asset.getAssetDto().getSize(),
                      asset.getStorageId(),
                      asset.getAvailability(),
                      checksum);
              return Opt.some(a);
            }
          }
        }
      }
      return Opt.none();
    }
    return chuck(new UnauthorizedException(
            format("Not allowed to read assets of snapshot %s, version=%s", mpId, version)
    ));
  }

  @Override
  public Opt getAssetStore(String storeId) {
    if (assetStore.getStoreType().equals(storeId)) {
      return Opt.some(assetStore);
    } else {
      if (remoteStores.containsKey(storeId)) {
        return Opt.some(remoteStores.get(storeId));
      } else {
        return Opt.none();
      }
    }
  }

  @Override
  public AssetStore getLocalAssetStore() {
    return assetStore;
  }

  @Override
  public List getRemoteAssetStores() {
    return new ArrayList<>(remoteStores.values());
  }

  /** Snapshots */

  @Override
  public boolean snapshotExists(final String mediaPackageId) {
    return getDatabase().snapshotExists(mediaPackageId);
  }

  @Override
  public boolean snapshotExists(final String mediaPackageId, final String organization) {
    return getDatabase().snapshotExists(mediaPackageId, organization);
  }

  @Override
  public Snapshot takeSnapshot(MediaPackage mp) {
    return takeSnapshot(null, mp);
  }

  @Override
  public Snapshot takeSnapshot(String owner, MediaPackage mp) {

    final String mediaPackageId = mp.getIdentifier().toString();
    final boolean firstSnapshot = !snapshotExists(mediaPackageId);

    // Allow this if:
    //  - no previous snapshot exists
    //  - the user has write access to the previous snapshot
    if (firstSnapshot) {
      // if it's the first snapshot, ensure that old, leftover properties are removed
      deleteProperties(mediaPackageId);
    }
    if (firstSnapshot || isAuthorized(mediaPackageId, WRITE_ACTION)) {
      final Snapshot snapshot;
      if (owner == null) {
        snapshot = takeSnapshotInternal(mp);
      } else {
        snapshot = takeSnapshotInternal(owner, mp);
      }

      final AccessControlList acl = authorizationService.getActiveAcl(mp).getA();
      // store acl as properties
      // Drop old ACL rules
      deleteProperties(mediaPackageId, SECURITY_NAMESPACE);
      // Set new ACL rules
      for (final AccessControlEntry ace : acl.getEntries()) {
        getDatabase().saveProperty(Property.mk(PropertyId.mk(mediaPackageId, SECURITY_NAMESPACE,
                mkPropertyName(ace.getRole(), ace.getAction())), Value.mk(ace.isAllow())));
      }

      // We pass the original media package here, instead of using
      // snapshot.getMediaPackage(), for security reasons. The original media
      // package has elements with URLs of type http://.../files/... in it. These
      // URLs will be pulled from the Workspace cache without a HTTP call.
      //
      // Were we to use snapshot.getMediaPackage(), we'd have a HTTP call on our
      // hands that's secured via the asset manager security model. But the
      // snapshot taken here doesn't have the necessary security properties
      // installed (yet). This happens in AssetManagerWithSecurity, some layers
      // higher up. So there's a weird loop in here.
      logger.info("Send update message for snapshot {}, {} to ActiveMQ",
              snapshot.getMediaPackage().getIdentifier().toString(), snapshot.getVersion());
      messageSender.sendObjectMessage(AssetManagerItem.ASSETMANAGER_QUEUE, MessageSender.DestinationType.Queue,
              mkTakeSnapshotMessage(snapshot, mp));

      // update ES indices
      updateEventInIndex(snapshot, adminUiIndex);
      updateEventInIndex(snapshot, externalApiIndex);

      return snapshot;
    }
    return chuck(new UnauthorizedException("Not allowed to take snapshot of media package " + mediaPackageId));
  }

  private Snapshot takeSnapshotInternal(MediaPackage mediaPackage) {
    final String mediaPackageId = mediaPackage.getIdentifier().toString();
    AQueryBuilder queryBuilder = createQuery();
    AResult result = queryBuilder.select(queryBuilder.snapshot())
            .where(queryBuilder.mediaPackageId(mediaPackageId).and(queryBuilder.version().isLatest())).run();
    Opt record = result.getRecords().head();
    if (record.isSome()) {
      Opt snapshot = record.get().getSnapshot();
      if (snapshot.isSome()) {
        return takeSnapshotInternal(snapshot.get().getOwner(), mediaPackage);
      }
    }
    return takeSnapshotInternal(DEFAULT_OWNER, mediaPackage);
  }

  private Snapshot takeSnapshotInternal(final String owner, final MediaPackage mp) {
    return handleException(new P1Lazy() {
      @Override public Snapshot get1() {
        try {
          final Snapshot archived = addInternal(owner, MediaPackageSupport.copy(mp)).toSnapshot();
          return getHttpAssetProvider().prepareForDelivery(archived);
        } catch (Exception e) {
          return Prelude.chuck(e);
        }
      }
    });
  }

  /**
   * Create a {@link AssetManagerItem.TakeSnapshot} message.
   * 

* Do not call outside of a security context. */ private AssetManagerItem.TakeSnapshot mkTakeSnapshotMessage(Snapshot snapshot, MediaPackage mp) { final MediaPackage chosenMp; if (mp != null) { chosenMp = mp; } else { chosenMp = snapshot.getMediaPackage(); } long version; try { version = 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'."); } return AssetManagerItem.add(workspace, chosenMp, authorizationService.getActiveAcl(chosenMp).getA(), version, snapshot.getArchivalDate()); } /** * Update the event in the Elasticsearch index. * * @param snapshot * The newest snapshot of the event to update * @param index * The Elasticsearch index to update */ private void updateEventInIndex(Snapshot snapshot, AbstractSearchIndex index) { final MediaPackage mp = snapshot.getMediaPackage(); String eventId = mp.getIdentifier().toString(); final String organization = securityService.getOrganization().getId(); final User user = securityService.getUser(); logger.debug("Updating event {} in the {} index.", eventId, index.getIndexName()); Function, Optional> updateFunction = (Optional eventOpt) -> { Event event = eventOpt.orElse(new Event(eventId, organization)); AccessControlList acl = authorizationService.getActiveAcl(mp).getA(); List acls = aclServiceFactory.serviceFor(securityService.getOrganization()).getAcls(); for (final ManagedAcl managedAcl : AccessInformationUtil.matchAcls(acls, acl)) { event.setManagedAcl(managedAcl.getName()); } event.setAccessPolicy(AccessControlParser.toJsonSilent(acl)); event.setArchiveVersion(Long.parseLong(snapshot.getVersion().toString())); if (StringUtils.isBlank(event.getCreator())) { event.setCreator(securityService.getUser().getName()); } EventIndexUtils.updateEvent(event, mp); for (Catalog catalog: mp.getCatalogs(MediaPackageElements.EPISODE)) { try (InputStream in = workspace.read(catalog.getURI())) { EventIndexUtils.updateEvent(event, DublinCores.read(in)); } catch (IOException | NotFoundException e) { throw new IllegalStateException(String.format("Unable to load dublin core catalog for event '%s'", mp.getIdentifier()), e); } } // Update series name if not already done try { EventIndexUtils.updateSeriesName(event, organization, user, index); } catch (SearchIndexException e) { logger.error("Error updating the series name of the event {} in the {} index.", eventId, index.getIndexName(), e); } return Optional.of(event); }; // Persist the scheduling event try { index.addOrUpdateEvent(eventId, updateFunction, organization, user); logger.debug("Event {} updated in the {} index.", eventId, index.getIndexName()); } catch (SearchIndexException e) { logger.error("Error updating the event {} in the {} index.", eventId, index.getIndexName(), e); } } /** * Remove the event from the Elasticsearch index * * @param eventId * The id of the event to remove * @param index * The Elasticsearch index to update */ private void removeEventFromIndex(String eventId, AbstractSearchIndex index) { final String organization = securityService.getOrganization().getId(); final User user = securityService.getUser(); logger.debug("Received AssetManager delete episode message {}", eventId); try { index.deleteAssets(organization, user, eventId); logger.debug("Event {} removed from the {} index", eventId, index.getIndexName()); } catch (NotFoundException e) { logger.warn("Event {} not found for deletion", eventId); } catch (SearchIndexException e) { logger.error("Error deleting the event {} from the {} index.", eventId, index.getIndexName(), e); } } @Override public RichAResult getSnapshotsById(final String mpId) { RequireUtil.requireNotBlank(mpId, "mpId"); AQueryBuilder q = createQuery(); ASelectQuery query = baseQuery(q, mpId); return Enrichments.enrich(query.run()); } @Override public RichAResult getSnapshotsByIdOrderedByVersion(String mpId, boolean asc) { RequireUtil.requireNotBlank(mpId, "mpId"); AQueryBuilder q = createQuery(); ASelectQuery query = baseQuery(q, mpId); if (asc) { query = query.orderBy(q.version().asc()); } else { query = query.orderBy(q.version().desc()); } return Enrichments.enrich(query.run()); } @Override public RichAResult getSnapshotsByIdAndVersion(final String mpId, final Version version) { RequireUtil.requireNotBlank(mpId, "mpId"); RequireUtil.notNull(version, "version"); AQueryBuilder q = createQuery(); ASelectQuery query = baseQuery(q, version, mpId); return Enrichments.enrich(query.run()); } @Override public RichAResult getSnapshotsByDate(final Date start, final Date end) { RequireUtil.notNull(start, "start"); RequireUtil.notNull(end, "end"); AQueryBuilder q = createQuery(); ASelectQuery query = baseQuery(q).where(q.archived().ge(start)).where(q.archived().le(end)); return Enrichments.enrich(query.run()); } @Override public RichAResult getSnapshotsByDateOrderedById(Date start, Date end) { RequireUtil.notNull(start, "start"); RequireUtil.notNull(end, "end"); AQueryBuilder q = createQuery(); ASelectQuery query = baseQuery(q).where(q.archived().ge(start)).where(q.archived().le(end)); return Enrichments.enrich(query.orderBy(q.mediapackageId().asc()).run()); } @Override public RichAResult getSnapshotsByIdAndDate(final String mpId, final Date start, final Date end) { RequireUtil.requireNotBlank(mpId, "mpId"); RequireUtil.notNull(start, "start"); RequireUtil.notNull(end, "end"); AQueryBuilder q = createQuery(); ASelectQuery query = baseQuery(q, mpId).where(q.archived().ge(start)).where(q.archived().le(end)); return Enrichments.enrich(query.run()); } @Override public RichAResult getSnapshotsByIdAndDateOrderedByVersion(String mpId, Date start, Date end, boolean asc) { RequireUtil.requireNotBlank(mpId, "mpId"); RequireUtil.notNull(start, "start"); RequireUtil.notNull(end, "end"); AQueryBuilder q = createQuery(); ASelectQuery query = baseQuery(q, mpId).where(q.archived().ge(start)).where(q.archived().le(end)); if (asc) { query = query.orderBy(q.version().asc()); } else { query = query.orderBy(q.version().desc()); } return Enrichments.enrich(query.run()); } @Override public void moveSnapshotsById(final String mpId, final String targetStore) throws NotFoundException { RichAResult results = getSnapshotsById(mpId); if (results.getRecords().isEmpty()) { throw new NotFoundException("Mediapackage " + mpId + " not found!"); } processOperations(results, targetStore); } @Override public void moveSnapshotsByIdAndVersion(final String mpId, final Version version, final String targetStore) throws NotFoundException { RichAResult results = getSnapshotsByIdAndVersion(mpId, version); if (results.getRecords().isEmpty()) { throw new NotFoundException("Mediapackage " + mpId + "@" + version.toString() + " not found!"); } processOperations(results, targetStore); } @Override public void moveSnapshotsByDate(final Date start, final Date end, final String targetStore) throws NotFoundException { RichAResult results = getSnapshotsByDate(start, end); if (results.getRecords().isEmpty()) { throw new NotFoundException("No media packages found between " + start + " and " + end); } processOperations(results, targetStore); } @Override public void moveSnapshotsByIdAndDate(final String mpId, final Date start, final Date end, final String targetStore) throws NotFoundException { RichAResult results = getSnapshotsByDate(start, end); if (results.getRecords().isEmpty()) { throw new NotFoundException("No media package with id " + mpId + " found between " + start + " and " + end); } processOperations(results, targetStore); } @Override public void moveSnapshotToStore(final Version version, final String mpId, final String storeId) throws NotFoundException { //Find the snapshot AQueryBuilder q = createQuery(); RichAResult results = Enrichments.enrich(baseQuery(q, version, mpId).run()); if (results.getRecords().isEmpty()) { throw new NotFoundException("Mediapackage " + mpId + "@" + version.toString() + " not found!"); } processOperations(results, storeId); } //Do the actual moving private void processOperations(final RichAResult results, final String targetStoreId) { results.getRecords().forEach(record -> { Snapshot s = record.getSnapshot().get(); Opt currentStoreId = getSnapshotStorageLocation(s); if (currentStoreId.isNone()) { logger.warn("IsNone store ID"); return; } //If this snapshot is already stored in the desired store if (currentStoreId.get().equals(targetStoreId)) { //return, since we don't need to move anything return; } AssetStore currentStore; AssetStore targetStore; Opt optCurrentStore = getAssetStore(currentStoreId.get()); Opt optTargetStore = getAssetStore(targetStoreId); if (!optCurrentStore.isNone()) { currentStore = optCurrentStore.get(); } else { logger.error("Unknown current store: " + currentStoreId.get()); return; } if (!optTargetStore.isNone()) { targetStore = optTargetStore.get(); } else { logger.error("Unknown target store: " + targetStoreId); return; } //If the content is already local, or is moving from a remote to the local // Returns true if the store id is equal to the local asset store's id String localAssetStoreType = getLocalAssetStore().getStoreType(); if (localAssetStoreType.equals(currentStoreId.get()) || localAssetStoreType.equals(targetStoreId)) { logger.debug("Moving {} from {} to {}", s, currentStoreId, targetStoreId); try { copyAssetsToStore(s, targetStore); copyManifest(s, targetStore); } catch (Exception e) { Functions.chuck(e); } getDatabase().setStorageLocation(s, targetStoreId); currentStore.delete(DeletionSelector.delete(s.getOrganizationId(), s.getMediaPackage().getIdentifier().toString(), s.getVersion() )); } else { //Else, the content is *not* local and is going to a *different* remote String intermediateStore = getLocalAssetStore().getStoreType(); logger.debug("Moving {} from {} to {}, then to {}", s, currentStoreId, intermediateStore, targetStoreId); Version version = s.getVersion(); String mpId = s.getMediaPackage().getIdentifier().toString(); try { moveSnapshotToStore(version, mpId, intermediateStore); moveSnapshotToStore(version, mpId, targetStoreId); } catch (NotFoundException e) { Functions.chuck(e); } } }); } // Return the asset store ID that is currently storing the snapshot public Opt getSnapshotStorageLocation(final Version version, final String mpId) { RichAResult result = getSnapshotsByIdAndVersion(mpId, version); for (Snapshot snapshot : result.getSnapshots()) { return Opt.some(snapshot.getStorageId()); } logger.error("Mediapackage " + mpId + "@" + version + " not found!"); return Opt.none(); } public Opt getSnapshotStorageLocation(final Snapshot snap) { return getSnapshotStorageLocation(snap.getVersion(), snap.getMediaPackage().getIdentifier().toString()); } /** Properties */ @Override public boolean setProperty(Property property) { final String mpId = property.getId().getMediaPackageId(); if (isAuthorized(mpId, WRITE_ACTION)) { return getDatabase().saveProperty(property); } return chuck(new UnauthorizedException("Not allowed to set property on episode " + mpId)); } @Override public List selectProperties(final String mediaPackageId, String namespace) { if (isAuthorized(mediaPackageId, READ_ACTION)) { return getDatabase().selectProperties(mediaPackageId, namespace); } return chuck(new UnauthorizedException(format("Not allowed to read properties of event %s", mediaPackageId))); } @Override public int deleteProperties(final String mediaPackageId) { return getDatabase().deleteProperties(mediaPackageId); } @Override public int deleteProperties(final String mediaPackageId, final String namespace) { return getDatabase().deleteProperties(mediaPackageId, namespace); } /** Misc. */ @Override public AQueryBuilder createQuery() { return new AQueryBuilderDecorator(createQueryWithoutSecurityCheck()) { @Override public ASelectQuery select(Target... target) { switch (isAdmin()) { case GLOBAL: return super.select(target); case ORGANIZATION: return super.select(target).where(restrictToUsersOrganization()); default: return super.select(target).where(mkAuthPredicate(READ_ACTION)); } } @Override public ADeleteQuery delete(String owner, Target target) { switch (isAdmin()) { case GLOBAL: return super.delete(owner, target); case ORGANIZATION: return super.delete(owner, target).where(restrictToUsersOrganization()); default: return super.delete(owner, target).where(mkAuthPredicate(WRITE_ACTION)); } } }; } private AQueryBuilder createQueryWithoutSecurityCheck() { return new AQueryBuilderDecorator(new AQueryBuilderImpl(this)) { @Override public ADeleteQuery delete(String owner, Target target) { return new ADeleteQueryWithMessaging(super.delete(owner, target)); } }; } @Override public Opt toVersion(String version) { try { return Opt.some(VersionImpl.mk(Long.parseLong(version))); } catch (NumberFormatException e) { return Opt.none(); } } @Override public long countEvents(final String organization) { return getDatabase().countEvents(organization); } /** * DeleteSnapshotHandler implementation */ @Override public void handleDeletedSnapshot(String mpId, VersionImpl version) { logger.info("Send delete message for snapshot {}, {} to ActiveMQ", mpId, version); messageSender.sendObjectMessage(AssetManagerItem.ASSETMANAGER_QUEUE, MessageSender.DestinationType.Queue, AssetManagerItem.deleteSnapshot(mpId, version.value(), new Date())); } @Override public void handleDeletedEpisode(String mpId) { logger.info("Send delete message for episode {} to ActiveMQ", mpId); messageSender.sendObjectMessage(AssetManagerItem.ASSETMANAGER_QUEUE, MessageSender.DestinationType.Queue, AssetManagerItem.deleteEpisode(mpId, new Date())); // update ES indices removeEventFromIndex(mpId, adminUiIndex); removeEventFromIndex(mpId, externalApiIndex); } /** * AbstractIndexProducer Implementation */ @Override public IndexRebuildService.Service getService() { return IndexRebuildService.Service.AssetManager; } @Override public void repopulate(final AbstractSearchIndex index) throws IndexRebuildException { final Organization org = securityService.getOrganization(); final User user = (org != null ? securityService.getUser() : null); try { final Organization defaultOrg = new DefaultOrganization(); final User systemUser = SecurityUtil.createSystemUser(systemUserName, defaultOrg); securityService.setOrganization(defaultOrg); securityService.setUser(systemUser); final AQueryBuilder q = createQuery(); final RichAResult r = enrich(q.select(q.snapshot()).where(q.version().isLatest()).run()); final int total = r.countSnapshots(); int current = 0; logIndexRebuildBegin(logger, index.getIndexName(), total, "snapshot(s)"); final Map> byOrg = r.getSnapshots().groupMulti(Snapshots.getOrganizationId); for (String orgId : byOrg.keySet()) { final Organization snapshotOrg; try { snapshotOrg = orgDir.getOrganization(orgId); securityService.setOrganization(snapshotOrg); securityService.setUser(SecurityUtil.createSystemUser(systemUserName, snapshotOrg)); for (Snapshot snapshot : byOrg.get(orgId)) { current += 1; try { updateEventInIndex(snapshot, index); } catch (Throwable t) { logSkippingElement(logger, "event", snapshot.getMediaPackage().getIdentifier().toString(), org, t); } logIndexRebuildProgress(logger, index.getIndexName(), total, current); } } catch (Throwable t) { logIndexRebuildError(logger, index.getIndexName(), t, org); throw new IndexRebuildException(index.getIndexName(), getService(), org, t); } finally { securityService.setOrganization(defaultOrg); securityService.setUser(systemUser); } } } finally { securityService.setOrganization(org); securityService.setUser(user); } } /** * Used for testing */ public void setAvailability(Version version, String mpId, Availability availability) { if (isAuthorized(mpId, WRITE_ACTION)) { getDatabase().setAvailability(RuntimeTypes.convert(version), mpId, availability); } else { chuck(new UnauthorizedException("Not allowed to set availability of episode " + mpId)); } } public void setDatabase(Database database) { this.db = database; } public Database getDatabase() { return db; } public HttpAssetProvider getHttpAssetProvider() { return httpAssetProvider; } /* * Security handling */ /** * Create an authorization predicate to be used with {@link #isAuthorized(String, String)}, * restricting access to the user's organization and the given action. * * @param action * the action to restrict access to */ private Predicate mkAuthPredicate(final String action) { final AQueryBuilder q = createQueryWithoutSecurityCheck(); return securityService.getUser().getRoles().stream() .filter(roleFilter) .map((role) -> q.property(Value.BOOLEAN, SECURITY_NAMESPACE, mkPropertyName(role.getName(), action)) .eq(true)) .reduce(Predicate::or) .orElseGet(() -> q.always().not()) .and(restrictToUsersOrganization()); } /** Create a predicate that restricts access to the user's organization. */ private Predicate restrictToUsersOrganization() { return createQueryWithoutSecurityCheck().organizationId().eq(securityService.getUser().getOrganization().getId()); } /** Check authorization based on the given predicate. */ private boolean isAuthorized(final String mediaPackageId, final String action) { switch (isAdmin()) { case GLOBAL: // grant general access logger.debug("Access granted since user is global admin"); return true; case ORGANIZATION: // ensure that the requested assets belong to this organization logger.debug("User is organization admin. Checking organization. Checking organization ID of asset."); return snapshotExists(mediaPackageId, securityService.getOrganization().getId()); default: // check organization logger.debug("Non admin user. Checking organization."); final String org = securityService.getOrganization().getId(); if (!snapshotExists(mediaPackageId, org)) { return false; } // check acl rules logger.debug("Non admin user. Checking ACL rules."); final List roles = securityService.getUser().getRoles().parallelStream() .filter(roleFilter) .map((role) -> mkPropertyName(role.getName(), action)) .collect(Collectors.toList()); return getDatabase().selectProperties(mediaPackageId, SECURITY_NAMESPACE).parallelStream() .map(p -> p.getId().getName()) .anyMatch(p -> roles.parallelStream().anyMatch(r -> r.equals(p))); } } private AdminRole isAdmin() { final User user = securityService.getUser(); if (user.hasRole(GLOBAL_ADMIN_ROLE)) { return AdminRole.GLOBAL; } else if (user.hasRole(securityService.getOrganization().getAdminRole()) || user.hasRole(GLOBAL_CAPTURE_AGENT_ROLE)) { // In this context, we treat capture agents the same way as organization admins, allowing them access so that // they can ingest new media without requiring them to be explicitly specified in the ACLs. return AdminRole.ORGANIZATION; } else { return AdminRole.NONE; } } private String mkPropertyName(String role, String action) { return role + " | " + action; } /** * Configurable filter for roles */ private final java.util.function.Predicate roleFilter = (role) -> { final String name = role.getName(); return (includeAPIRoles || !name.startsWith("ROLE_API_")) && (includeCARoles || !name.startsWith("ROLE_CAPTURE_AGENT_")) && (includeUIRoles || !name.startsWith("ROLE_UI_")); }; /* * Utility */ /** * Return a basic query which returns the snapshot and its current storage location * * @param q * The query builder object to configure * @return * The {@link ASelectQuery} configured with as described above */ private ASelectQuery baseQuery(final AQueryBuilder q) { RequireUtil.notNull(q, "q"); return q.select(q.snapshot()); } /** * Return a mediapackage filtered query which returns the snapshot and its current storage location * * @param q * The query builder object to configure * @param mpId * The mediapackage ID to filter results for * @return * The {@link ASelectQuery} configured with as described above */ private ASelectQuery baseQuery(final AQueryBuilder q, final String mpId) { RequireUtil.notNull(q, "q"); ASelectQuery query = baseQuery(q); if (StringUtils.isNotEmpty(mpId)) { return query.where(q.mediaPackageId(mpId)); } else { return query; } } /** * Return a mediapackage and version filtered query which returns the snapshot and its current storage location * * @param q * The query builder object to configure * @param version * The version to filter results for * @param mpId * The mediapackage ID to filter results for * @return * The {@link ASelectQuery} configured with as described above */ private ASelectQuery baseQuery(final AQueryBuilder q, final Version version, final String mpId) { RequireUtil.notNull(q, "q"); RequireUtil.requireNotBlank(mpId, "mpId"); ASelectQuery query = baseQuery(q, mpId); if (null != version) { return query.where(q.version().eq(version)); } else { return query; } } /** Move the assets for a snapshot to the target store */ private void copyAssetsToStore(Snapshot snap, AssetStore store) { final String mpId = snap.getMediaPackage().getIdentifier().toString(); final String orgId = snap.getOrganizationId(); final Version version = snap.getVersion(); final String prettyMpId = mpId + "@v" + version; logger.debug("Moving assets for snapshot {} to store {}", prettyMpId, store.getStoreType()); for (final MediaPackageElement e : snap.getMediaPackage().getElements()) { if (!MOVABLE_TYPES.contains(e.getElementType())) { logger.debug("Skipping {} because type is {}", e.getIdentifier(), e.getElementType()); continue; } logger.debug("Moving {} to store {}", e.getIdentifier(), store.getStoreType()); final StoragePath storagePath = StoragePath.mk(orgId, mpId, version, e.getIdentifier()); if (store.contains(storagePath)) { logger.debug("Element {} (version {}) is already in store {} so skipping it", e.getIdentifier(), version, store.getStoreType()); continue; } // find asset in versions & stores final Opt existingAssetOpt = getDatabase().findAssetByChecksumAndStore(e.getChecksum().toString(), store.getStoreType()).map(new Fn() { @Override public StoragePath apply(AssetDtos.Full dto) { return StoragePath.mk( dto.getOrganizationId(), dto.getMediaPackageId(), dto.getVersion(), dto.getAssetDto().getMediaPackageElementId() ); } }); if (existingAssetOpt.isSome()) { final StoragePath existingAsset = existingAssetOpt.get(); logger.debug("Content of asset {} with checksum {} already exists in {}", existingAsset.getMediaPackageElementId(), e.getChecksum(), store.getStoreType()); if (!store.copy(existingAsset, storagePath)) { throw new AssetManagerException(format( "An asset with checksum %s has already been archived but trying to copy or link asset %s to it " + "failed", e.getChecksum(), existingAsset )); } } else { final Opt size = e.getSize() > 0 ? Opt.some(e.getSize()) : Opt.none(); store.put(storagePath, Source.mk(e.getURI(), size, Opt.nul(e.getMimeType()))); } getDatabase().setAssetStorageLocation(VersionImpl.mk(version), mpId, e.getIdentifier(), store.getStoreType()); } } private void copyManifest(Snapshot snap, AssetStore targetStore) throws IOException, NotFoundException { final String mpId = snap.getMediaPackage().getIdentifier().toString(); final String orgId = snap.getOrganizationId(); final Version version = snap.getVersion(); AssetStore currentStore = getAssetStore(snap.getStorageId()).get(); Opt manifestOpt = findManifestBaseName(snap, MANIFEST_DEFAULT_NAME, currentStore); if (manifestOpt.isNone()) { return; // Nothing to do, already moved to long-term storage } // Copy the manifest file String manifestBaseName = manifestOpt.get(); StoragePath pathToManifest = new StoragePath(orgId, mpId, version, manifestBaseName); // Already copied? if (!targetStore.contains(pathToManifest)) { Opt inputStreamOpt; InputStream inputStream = null; String manifestFileName = null; try { inputStreamOpt = currentStore.get(pathToManifest); if (inputStreamOpt.isNone()) { // This should never happen because it has been tested before throw new NotFoundException( String.format("Unexpected error. Manifest %s not found in current asset store", manifestBaseName)); } inputStream = inputStreamOpt.get(); manifestFileName = UUID.randomUUID() + ".xml"; URI manifestTmpUri = workspace.putInCollection("archive", manifestFileName, inputStream); targetStore.put(pathToManifest, Source.mk(manifestTmpUri, Opt.none(), Opt.some(MimeTypes.XML))); } finally { IOUtils.closeQuietly(inputStream); try { // Make sure to clean up the temporary file workspace.deleteFromCollection("archive", manifestFileName); } catch (NotFoundException e) { // This is OK, we are deleting it anyway } catch (IOException e) { // This usually happens when the collection directory cannot be deleted // because another process is running at the same time and wrote a file there // after it was tested but before it was actually deleted. We will consider this ok. // Does the error message mention the manifest file name? if (e.getMessage().contains(manifestFileName)) { logger.warn("The manifest file {} didn't get deleted from the archive collection: {}", manifestBaseName, e); } // Else the error is related to the file-archive collection, which is fine } } } } Opt findManifestBaseName(Snapshot snap, String manifestName, AssetStore store) { StoragePath path = new StoragePath(snap.getOrganizationId(), snap.getMediaPackage().getIdentifier().toString(), snap.getVersion(), manifestName); // If manifest_.xml, etc not found, return previous name (copied from the EpsiodeServiceImpl logic) if (!store.contains(path)) { // If first call, manifest is not found, which probably means it has already been moved if (MANIFEST_DEFAULT_NAME.equals(manifestName)) { return Opt.none(); // No manifest found in current store } else { return Opt.some(manifestName.substring(0, manifestName.length() - 1)); } } // This is the same logic as when building the manifest name: manifest, manifest_, manifest__, etc return findManifestBaseName(snap, manifestName + "_", store); } /* -------------------------------------------------------------------------------------------------------------- */ /** * Make sure each of the elements has a checksum. */ void calcChecksumsForMediaPackageElements(PartialMediaPackage pmp) { final Fx addChecksum = new Fx() { @Override public void apply(MediaPackageElement mpe) { File file = null; try { logger.trace("Calculate checksum for {}", mpe.getURI()); file = workspace.get(mpe.getURI(), true); mpe.setChecksum(Checksum.create(ChecksumType.DEFAULT_TYPE, file)); } catch (IOException | NotFoundException e) { throw new AssetManagerException(format( "Cannot calculate checksum for media package element %s", mpe.getURI() ), e); } finally { if (file != null) { FileUtils.deleteQuietly(file); } } } }; pmp.getElements().filter(hasNoChecksum.toFn()).each(addChecksum).run(); } /** Mutates mp and its elements, so make sure to work on a copy. */ private SnapshotDto addInternal(String owner, final MediaPackage mp) throws Exception { final Date now = new Date(); // claim a new version for the media package final String mpId = mp.getIdentifier().toString(); final VersionImpl version = getDatabase().claimVersion(mpId); logger.info("Creating new version {} of media package {}", version, mp); final PartialMediaPackage pmp = assetsOnly(mp); // make sure they have a checksum calcChecksumsForMediaPackageElements(pmp); // download and archive elements storeAssets(pmp, version); // store mediapackage in db final SnapshotDto snapshotDto; try { // rewrite URIs for archival Fn uriCreator = new Fn() { @Override public URI apply(MediaPackageElement mpe) { try { String fileName = getFileName(mpe).getOr("unknown"); return new URI( "urn", "matterhorn:" + mpId + ":" + version + ":" + mpe.getIdentifier() + ":" + fileName, null ); } catch (URISyntaxException e) { throw new AssetManagerException(e); } } }; for (MediaPackageElement mpe : pmp.getElements()) { mpe.setURI(uriCreator.apply(mpe)); } String currentOrgId = securityService.getOrganization().getId(); snapshotDto = getDatabase().saveSnapshot( currentOrgId, pmp, now, version, Availability.ONLINE, getLocalAssetStore().getStoreType(), owner ); } catch (AssetManagerException e) { logger.error("Could not take snapshot {}: {}", mpId, e); throw new AssetManagerException(e); } // save manifest to element store // this is done at the end after the media package element ids have been rewritten to neutral URNs storeManifest(pmp, version); return snapshotDto; } /** * Store all elements of pmp under the given version. */ private void storeAssets(final PartialMediaPackage pmp, final Version version) { final String mpId = pmp.getMediaPackage().getIdentifier().toString(); final String orgId = securityService.getOrganization().getId(); for (final MediaPackageElement e : pmp.getElements()) { logger.debug("Archiving {} {} {}", e.getFlavor(), e.getMimeType(), e.getURI()); final StoragePath storagePath = StoragePath.mk(orgId, mpId, version, e.getIdentifier()); // find asset in versions final Opt existingAssetOpt = getDatabase().findAssetByChecksumAndStore(e.getChecksum().toString(), getLocalAssetStore().getStoreType()) .map(new Fn() { @Override public StoragePath apply(AssetDtos.Full dto) { return StoragePath.mk( dto.getOrganizationId(), dto.getMediaPackageId(), dto.getVersion(), dto.getAssetDto().getMediaPackageElementId() ); } }); if (existingAssetOpt.isSome()) { final StoragePath existingAsset = existingAssetOpt.get(); logger.debug("Content of asset {} with checksum {} has been archived before", existingAsset.getMediaPackageElementId(), e.getChecksum()); if (!getLocalAssetStore().copy(existingAsset, storagePath)) { throw new AssetManagerException(format( "An asset with checksum %s has already been archived but trying to copy or link asset %s to it " + "failed", e.getChecksum(), existingAsset )); } } else { final Opt size = e.getSize() > 0 ? Opt.some(e.getSize()) : Opt.none(); getLocalAssetStore().put(storagePath, Source.mk(e.getURI(), size, Opt.nul(e.getMimeType()))); } } } private void storeManifest(final PartialMediaPackage pmp, final Version version) throws Exception { final String mpId = pmp.getMediaPackage().getIdentifier().toString(); final String orgId = securityService.getOrganization().getId(); // store the manifest.xml // TODO make use of checksums logger.debug("Archiving manifest of media package {} version {}", mpId, version); // temporarily save the manifest XML into the workspace to // Fix file not found exception when several snapshots are taken at the same time final String manifestFileName = format("manifest_%s_%s.xml", pmp.getMediaPackage().getIdentifier(), version); final URI manifestTmpUri = workspace.putInCollection( "archive", manifestFileName, IOUtils.toInputStream(MediaPackageParser.getAsXml(pmp.getMediaPackage()), "UTF-8")); try { getLocalAssetStore().put( StoragePath.mk(orgId, mpId, version, manifestAssetId(pmp, "manifest")), Source.mk(manifestTmpUri, Opt.none(), Opt.some(MimeTypes.XML))); } finally { // make sure to clean up the temporary file workspace.deleteFromCollection("archive", manifestFileName); } } /** * Create a unique id for the manifest xml. This is to avoid an id collision * in the rare case that the media package contains an XML element with the id * used for the manifest. A UUID could also be used but this is far less * readable. * * @param seedId * the id to start with */ private String manifestAssetId(PartialMediaPackage pmp, String seedId) { if ($(pmp.getElements()).map(getMediaPackageElementId.toFn()).exists(Booleans.eq(seedId))) { return manifestAssetId(pmp, seedId + "_"); } else { return seedId; } } /* --------------------------------------------------------------------------------------------------------------- */ /** * Unify exception handling by wrapping any occurring exception in an * {@link AssetManagerException}. */ static A handleException(final P1 p) throws AssetManagerException { try { return p.get1(); } catch (Exception e) { logger.error("An error occurred", e); throw unwrapExceptionUntil(AssetManagerException.class, e).getOr(new AssetManagerException(e)); } } /** * Walk up the stacktrace to find a cause of type type. Return none if no such * type can be found. */ static Opt unwrapExceptionUntil(Class type, Throwable e) { if (e == null) { return Opt.none(); } else if (type.isAssignableFrom(e.getClass())) { return Opt.some((A) e); } else { return unwrapExceptionUntil(type, e.getCause()); } } /** * Return a partial media package filtering assets. Assets are elements the archive is going to manager, i.e. all * non-publication elements. */ static PartialMediaPackage assetsOnly(MediaPackage mp) { final Pred isAsset = Pred.mk(isNotPublication.toFn()); return PartialMediaPackage.mk(mp, isAsset); } /** * Extract the file name from a media package elements URN. * * @return the file name or none if it could not be determined */ public static Opt getFileNameFromUrn(MediaPackageElement mpe) { Fn toString = new Fn() { @Override public String apply(URI uri) { return uri.toString(); } }; Opt uri = Opt.nul(mpe.getURI()); if (uri.isSome() && "urn".equals(uri.get().getScheme())) { return uri.toStream().map(toString).bind(Strings.split(":")).drop(1).reverse().head(); } return Opt.none(); } /** * Rewrite URIs of all asset elements of a snapshot's media package. * This method does not mutate anything. */ public static Snapshot rewriteUris(Snapshot snapshot, Fn uriCreator) { final MediaPackage mpCopy = MediaPackageSupport.copy(snapshot.getMediaPackage()); for (final MediaPackageElement mpe : assetsOnly(mpCopy).getElements()) { mpe.setURI(uriCreator.apply(mpe)); } return new SnapshotImpl( snapshot.getVersion(), snapshot.getOrganizationId(), snapshot.getArchivalDate(), snapshot.getAvailability(), snapshot.getStorageId(), snapshot.getOwner(), mpCopy); } /** * Call {@link * org.opencastproject.assetmanager.impl.query.AbstractADeleteQuery#run(AbstractADeleteQuery.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(AssetManagerImpl.this); } @Override protected ADeleteQueryDecorator mkDecorator(ADeleteQuery delegate) { return new ADeleteQueryWithMessaging(delegate); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy