StaticMetadataServiceDublinCoreImpl.java
/*
* 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.metadata.dublincore;
import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_ACCESS_RIGHTS;
import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_AVAILABLE;
import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_CONTRIBUTOR;
import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_CREATED;
import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_CREATOR;
import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_DESCRIPTION;
import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_EXTENT;
import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_IDENTIFIER;
import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_IS_PART_OF;
import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_LANGUAGE;
import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_LICENSE;
import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_PUBLISHER;
import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_REPLACES;
import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_RIGHTS_HOLDER;
import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_SPATIAL;
import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_SUBJECT;
import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_TEMPORAL;
import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_TITLE;
import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_TYPE;
import org.opencastproject.mediapackage.Catalog;
import org.opencastproject.mediapackage.MediaPackage;
import org.opencastproject.mediapackage.MediaPackageElements;
import org.opencastproject.mediapackage.MediaPackageSerializer;
import org.opencastproject.metadata.api.MetadataValue;
import org.opencastproject.metadata.api.StaticMetadata;
import org.opencastproject.metadata.api.StaticMetadataService;
import org.opencastproject.metadata.api.util.Interval;
import org.opencastproject.util.data.NonEmptyList;
import org.opencastproject.workspace.api.Workspace;
import org.apache.commons.io.IOUtils;
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.InputStream;
import java.net.URI;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* This service provides {@link org.opencastproject.metadata.api.StaticMetadata} for a given mediapackage,
* based on a contained dublin core catalog describing the episode.
*/
@Component(
immediate = true,
service = StaticMetadataService.class,
property = {
"service.description=Static Metadata Service, dublin core based",
"metadata.source=dublincore",
"priority=1"
}
)
public class StaticMetadataServiceDublinCoreImpl implements StaticMetadataService {
private static final Logger logger = LoggerFactory.getLogger(StaticMetadataServiceDublinCoreImpl.class);
protected int priority = 0;
protected Workspace workspace = null;
protected MediaPackageSerializer serializer = null;
@Reference
public void setWorkspace(Workspace workspace) {
this.workspace = workspace;
}
@Reference(
cardinality = ReferenceCardinality.OPTIONAL,
policy = ReferencePolicy.DYNAMIC,
target = "(service.pid=org.opencastproject.mediapackage.ChainingMediaPackageSerializer)",
unbind = "unsetMediaPackageSerializer"
)
public void setMediaPackageSerializer(MediaPackageSerializer serializer) {
this.serializer = serializer;
}
public void unsetMediaPackageSerializer(MediaPackageSerializer serializer) {
if (this.serializer == serializer) {
this.serializer = null;
}
}
@Activate
public void activate(@SuppressWarnings("rawtypes") Map properties) {
logger.debug("activate()");
if (properties != null) {
String priorityString = (String) properties.get(PRIORITY_KEY);
if (priorityString != null) {
try {
priority = Integer.parseInt(priorityString);
} catch (NumberFormatException e) {
logger.warn("Unable to set priority to {}", priorityString);
throw e;
}
}
}
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.metadata.api.MetadataService#getMetadata(org.opencastproject.mediapackage.MediaPackage)
*/
@Override
public StaticMetadata getMetadata(final MediaPackage mp) {
Catalog[] catalogs = mp.getCatalogs(MediaPackageElements.EPISODE);
if (catalogs.length > 0) {
return newStaticMetadataFromEpisode(DublinCoreUtil.loadDublinCore(workspace, catalogs[0]));
}
return null;
}
private static StaticMetadata newStaticMetadataFromEpisode(DublinCoreCatalog episode) {
// Ensure that the mandatory properties are present
final Optional<String> id = Optional.ofNullable(episode.getFirst(PROPERTY_IDENTIFIER));
final Optional<Date> created = Optional.ofNullable(episode.getFirst(PROPERTY_CREATED))
.map(a -> {
Date date = EncodingSchemeUtils.decodeDate(a);
if (date == null) {
throw new RuntimeException(a + " does not conform to W3C-DTF encoding scheme.");
}
return date;
});
final Optional temporalOpt = Optional.ofNullable(episode.getFirstVal(PROPERTY_TEMPORAL))
.map(dc2temporalValueOption());
final Optional<Date> start;
if (episode.getFirst(PROPERTY_TEMPORAL) != null) {
DCMIPeriod period = EncodingSchemeUtils
.decodeMandatoryPeriod(episode.getFirst(PROPERTY_TEMPORAL));
start = Optional.ofNullable(period.getStart());
} else {
start = created;
}
final Optional<String> language = Optional.ofNullable(episode.getFirst(PROPERTY_LANGUAGE));
final Optional<Long> extent = episode.get(PROPERTY_EXTENT).stream().findFirst()
.map(a -> {
Long duration = EncodingSchemeUtils.decodeDuration(a);
if (duration == null) {
throw new RuntimeException(a + " does not conform to ISO8601 encoding scheme for durations.");
}
return duration;
});
final Optional<String> type = Optional.ofNullable(episode.getFirst(PROPERTY_TYPE));
final Optional<String> isPartOf = Optional.ofNullable(episode.getFirst(PROPERTY_IS_PART_OF));
final Optional<String> replaces = Optional.ofNullable(episode.getFirst(PROPERTY_REPLACES));
final Optional<Interval> available = episode.get(PROPERTY_AVAILABLE).stream().findFirst()
.flatMap(v -> {
DCMIPeriod p = EncodingSchemeUtils.decodePeriod(v);
if (p == null) {
throw new RuntimeException(v + " does not conform to W3C-DTF encoding scheme for periods");
}
return Optional.of(Interval.fromValues(p.getStart(), p.getEnd()));
});
final NonEmptyList<MetadataValue<String>> titles = new NonEmptyList<>(
episode.get(PROPERTY_TITLE).stream()
.map(dc2mvString(PROPERTY_TITLE.getLocalName()))
.collect(Collectors.toList())
);
final List<MetadataValue<String>> subjects = episode.get(PROPERTY_SUBJECT).stream()
.map(dc2mvString(PROPERTY_SUBJECT.getLocalName()))
.collect(Collectors.toList());
final List<MetadataValue<String>> creators = episode.get(PROPERTY_CREATOR).stream()
.map(dc2mvString(PROPERTY_CREATOR.getLocalName()))
.collect(Collectors.toList());
final List<MetadataValue<String>> publishers = episode.get(PROPERTY_PUBLISHER).stream()
.map(dc2mvString(PROPERTY_PUBLISHER.getLocalName()))
.collect(Collectors.toList());
final List<MetadataValue<String>> contributors = episode.get(PROPERTY_CONTRIBUTOR).stream()
.map(dc2mvString(PROPERTY_CONTRIBUTOR.getLocalName()))
.collect(Collectors.toList());
final List<MetadataValue<String>> description = episode.get(PROPERTY_DESCRIPTION).stream()
.map(dc2mvString(PROPERTY_DESCRIPTION.getLocalName()))
.collect(Collectors.toList());
final List<MetadataValue<String>> rightsHolders = episode.get(PROPERTY_RIGHTS_HOLDER).stream()
.map(dc2mvString(PROPERTY_RIGHTS_HOLDER.getLocalName()))
.collect(Collectors.toList());
final List<MetadataValue<String>> spatials = episode.get(PROPERTY_SPATIAL).stream()
.map(dc2mvString(PROPERTY_SPATIAL.getLocalName()))
.collect(Collectors.toList());
final List<MetadataValue<String>> accessRights = episode.get(PROPERTY_ACCESS_RIGHTS).stream()
.map(dc2mvString(PROPERTY_ACCESS_RIGHTS.getLocalName()))
.collect(Collectors.toList());
final List<MetadataValue<String>> licenses = episode.get(PROPERTY_LICENSE).stream()
.map(dc2mvString(PROPERTY_LICENSE.getLocalName()))
.collect(Collectors.toList());
return new StaticMetadata() {
@Override
public Optional<String> getId() {
return id;
}
@Override
public Optional<Date[]> getTemporalPeriod() {
if (temporalOpt.isPresent()) {
if (temporalOpt.get() instanceof DCMIPeriod) {
DCMIPeriod p = (DCMIPeriod) temporalOpt.get();
return Optional.ofNullable(new Date[] { p.getStart(), p.getEnd() });
}
}
return Optional.empty();
}
@Override
public Optional<Date> getTemporalInstant() {
if (temporalOpt.isPresent()) {
if (temporalOpt.get() instanceof Date) {
return temporalOpt;
}
}
return Optional.empty();
}
@Override
public Optional<Long> getTemporalDuration() {
if (temporalOpt.isPresent()) {
if (temporalOpt.get() instanceof Long) {
return temporalOpt;
}
}
return Optional.empty();
}
@Override
public Optional<Long> getExtent() {
return extent;
}
@Override
public Optional<String> getLanguage() {
return language;
}
@Override
public Optional<String> getIsPartOf() {
return isPartOf;
}
@Override
public Optional<String> getReplaces() {
return replaces;
}
@Override
public Optional<String> getType() {
return type;
}
@Override
public Optional<Interval> getAvailable() {
return available;
}
@Override
public NonEmptyList<MetadataValue<String>> getTitles() {
return titles;
}
@Override
public List<MetadataValue<String>> getSubjects() {
return subjects;
}
@Override
public List<MetadataValue<String>> getCreators() {
return creators;
}
@Override
public List<MetadataValue<String>> getPublishers() {
return publishers;
}
@Override
public List<MetadataValue<String>> getContributors() {
return contributors;
}
@Override
public List<MetadataValue<String>> getDescription() {
return description;
}
@Override
public List<MetadataValue<String>> getRightsHolders() {
return rightsHolders;
}
@Override
public List<MetadataValue<String>> getSpatials() {
return spatials;
}
@Override
public List<MetadataValue<String>> getAccessRights() {
return accessRights;
}
@Override
public List<MetadataValue<String>> getLicenses() {
return licenses;
}
};
}
/**
*
* {@inheritDoc}
*
* @see org.opencastproject.metadata.api.MetadataService#getPriority()
*/
@Override
public int getPriority() {
return priority;
}
/**
* Return a function that creates a Option with the value of temporal from a DublinCoreValue.
*/
private static java.util.function.Function<DublinCoreValue, Object> dc2temporalValueOption() {
return dcv -> {
Temporal temporal = EncodingSchemeUtils.decodeTemporal(dcv);
if (temporal == null) {
throw new RuntimeException(dcv
+ " does not conform to ISO8601 encoding scheme for temporal.");
}
return temporal.fold(new Temporal.Match<Object>() {
@Override
public Object period(DCMIPeriod period) {
return period;
}
@Override
public Object instant(Date instant) {
return instant;
}
@Override
public Object duration(long duration) {
return duration;
}
});
};
}
/**
* Return a function that creates a MetadataValue[String] from a DublinCoreValue setting its name to
* <code>name</code>.
*/
private static Function<DublinCoreValue, MetadataValue<String>> dc2mvString(final String name) {
return dcv -> new MetadataValue<>(dcv.getValue(), name, dcv.getLanguage());
}
private Optional<DublinCoreCatalog> load(Catalog catalog) {
InputStream in = null;
try {
URI uri = catalog.getURI();
if (serializer != null) {
uri = serializer.decodeURI(uri);
}
in = workspace.read(uri);
return Optional.of((DublinCoreCatalog) DublinCores.read(in));
} catch (Exception e) {
logger.warn("Unable to load metadata from catalog '{}'", catalog);
return Optional.empty();
} finally {
IOUtils.closeQuietly(in);
}
}
}