/**
* 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.util;
import static com.entwinemedia.fn.Equality.eq;
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.isNotPublication;
import org.opencastproject.assetmanager.api.Asset;
import org.opencastproject.assetmanager.api.AssetId;
import org.opencastproject.assetmanager.api.AssetManager;
import org.opencastproject.assetmanager.api.Availability;
import org.opencastproject.assetmanager.api.Snapshot;
import org.opencastproject.assetmanager.api.query.AQueryBuilder;
import org.opencastproject.assetmanager.api.query.ASelectQuery;
import org.opencastproject.mediapackage.MediaPackage;
import org.opencastproject.mediapackage.MediaPackageElement;
import org.opencastproject.mediapackage.MediaPackageElement.Type;
import org.opencastproject.mediapackage.MediaPackageSupport;
import org.opencastproject.util.MimeType;
import org.opencastproject.workflow.api.ConfiguredWorkflow;
import org.opencastproject.workflow.api.WorkflowDatabaseException;
import org.opencastproject.workflow.api.WorkflowInstance;
import org.opencastproject.workflow.api.WorkflowParsingException;
import org.opencastproject.workflow.api.WorkflowService;
import org.opencastproject.workspace.api.Workspace;
import com.entwinemedia.fn.Fn;
import com.entwinemedia.fn.Pred;
import com.entwinemedia.fn.Stream;
import com.entwinemedia.fn.data.Opt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.InputStream;
import java.net.URI;
/**
* Utility class to apply workflows to episodes.
*/
public final class Workflows {
/** Log facility */
private static final Logger logger = LoggerFactory.getLogger(Workflows.class);
private static final String ASSETS_COLLECTION_ID = "assets";
private final AssetManager am;
private final Workspace ws;
private final WorkflowService wfs;
public Workflows(AssetManager am, Workspace ws, WorkflowService wfs) {
this.am = am;
this.ws = ws;
this.wfs = wfs;
}
/**
* Apply a workflow to each episode contained in the result set of a select query.
*/
public Stream<WorkflowInstance> applyWorkflow(ASelectQuery q, ConfiguredWorkflow wf) {
return enrich(q.run()).getSnapshots().map(putInWorkspace).bind(applyWorkflow(wf));
}
/**
* Apply a workflow to the latest version of each media package.
*/
public Stream<WorkflowInstance> applyWorkflowToLatestVersion(Iterable<String> mpIds, ConfiguredWorkflow wf) {
return $(mpIds).bind(findLatest).map(putInWorkspace).bind(applyWorkflow(wf));
}
/**
* Put all assets of an episode into the workspace to make them processable.
* Please note that this may be an expensive operation depending on the size of the assets.
*
* @param snapshot
* the episode to put into the workspace
* @return A media package whose element URIs point to the workspace. Please note that the episode's original media package
* object (see {@link Snapshot#getMediaPackage()}) will not be modified.
*/
public MediaPackage putInWorkspace(Snapshot snapshot) {
final String mpId = snapshot.getMediaPackage().getIdentifier().compact();
// create a copy of the media package
final MediaPackage mp = MediaPackageSupport.copy(snapshot.getMediaPackage());
for (final MediaPackageElement mpe : $(mp.getElements()).filter(Pred.mk(isNotPublication.toFn()))) {
for (final MediaPackageElement mpeRewritten : putInWorkspace(snapshot, mpe)) {
mp.remove(mpe);
mp.add(mpeRewritten);
}
}
return mp;
}
/**
* Put a single media package element of an episode into the workspace and return the URI.
* Please note that the original media package element will not be modified. Instead a copy
* will be returned.
*
* @return some media package element if the media package element could be put into the workspace, none otherwise
*/
public Opt<MediaPackageElement> putInWorkspace(Snapshot snapshot, MediaPackageElement mpe) {
final String mpId = snapshot.getMediaPackage().getIdentifier().compact();
final MediaPackageElement mpeCopy = MediaPackageSupport.copy(mpe);
if (snapshot.getMediaPackage().contains(mpe)) {
final Opt<Asset> asset = am.getAsset(snapshot.getVersion(), mpId, mpe.getIdentifier());
if (asset.isSome() && eq(Availability.ONLINE, asset.get().getAvailability())) {
// found and available
// put the asset into the workspace
try (InputStream in = asset.get().getInputStream()) {
final String ext = asset.get().getMimeType().bind(suffix).getOr("unknown");
final URI uri = ws.putInCollection(
ASSETS_COLLECTION_ID,
mkFileName(asset.get().getId(), mpe.getElementType(), ext),
in);
mpeCopy.setURI(uri);
return Opt.some(mpeCopy);
} catch (Exception e) {
logger.error("Unable to put asset {} into the workspace", asset.get().getId());
return Opt.none();
}
} else if (asset.isSome()) {
// found, unavailable
logger.error(format("Asset %s is not available", asset.get().getId()));
return Opt.none();
} else {
// not found
logger.error(format("Element %s of version %s of media package %s does not exist",
mpe.getIdentifier(),
snapshot.getVersion(),
mpId));
return Opt.none();
}
} else {
throw new IllegalArgumentException(format("Media package element %s is not part of episode %s", mpe.getIdentifier(), snapshot));
}
}
/**
* {@link #putInWorkspace(Snapshot)} as a function.
*/
// CHECKSTYLE:OFF
public final Fn<Snapshot, MediaPackage> putInWorkspace = new Fn<Snapshot, MediaPackage>() {
@Override public MediaPackage apply(Snapshot snapshot) {
return putInWorkspace(snapshot);
}
};
// CHECKSTYLE:ON
/**
* Apply a workflow to a media package. The function returns some workflow instance if the
* workflow could be started successfully, none otherwise.
*/
public Fn<MediaPackage, Opt<WorkflowInstance>> applyWorkflow(final ConfiguredWorkflow wf) {
return new Fn<MediaPackage, Opt<WorkflowInstance>>() {
@Override public Opt<WorkflowInstance> apply(MediaPackage mp) {
try {
return Opt.some(wfs.start(wf.getWorkflowDefinition(), mp, wf.getParameters()));
} catch (WorkflowDatabaseException | WorkflowParsingException e) {
logger.error("Cannot start workflow on media package " + mp.getIdentifier().toString(), e);
return Opt.none();
}
}
};
}
/* ------------------------------------------------------------------------------------------------------------------ */
private String mkFileName(AssetId id, Type elementType, String ext) {
// TODO unsafe - escape/encode strings
return format("%s_%s_%s_%s.%s", id.getMediaPackageId(), id.getMediaPackageElementId(), id.getVersion().toString(), elementType, ext);
}
private final Fn<MimeType, Opt<String>> suffix = new Fn<MimeType, Opt<String>>() {
@Override
public Opt<String> apply(MimeType mimeType) {
return mimeType.getSuffix().toOpt();
}
};
private final Fn<String, Iterable<Snapshot>> findLatest = new Fn<String, Iterable<Snapshot>>() {
@Override public Iterable<Snapshot> apply(String mpId) {
AQueryBuilder q = am.createQuery();
return enrich(q.select(q.snapshot()).where(q.mediaPackageId(mpId).and(q.version().isLatest())).run()).getSnapshots();
}
};
}