/** * 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.index.service.catalog.adapter.series; import static java.util.Objects.requireNonNull; import static org.opencastproject.util.OsgiUtil.getCfg; import org.opencastproject.index.service.catalog.adapter.CatalogUIAdapterConfiguration; import org.opencastproject.index.service.catalog.adapter.DublinCoreMetadataCollection; import org.opencastproject.index.service.catalog.adapter.DublinCoreMetadataUtil; import org.opencastproject.index.service.catalog.adapter.events.ConfigurableEventDCCatalogUIAdapter; import org.opencastproject.index.service.resources.list.api.ListProvidersService; import org.opencastproject.mediapackage.EName; import org.opencastproject.mediapackage.MediaPackageElementFlavor; import org.opencastproject.metadata.dublincore.DublinCore; import org.opencastproject.metadata.dublincore.DublinCoreByteFormat; import org.opencastproject.metadata.dublincore.DublinCoreCatalog; import org.opencastproject.metadata.dublincore.DublinCoreValue; import org.opencastproject.metadata.dublincore.DublinCores; import org.opencastproject.metadata.dublincore.MetadataCollection; import org.opencastproject.metadata.dublincore.MetadataField; import org.opencastproject.metadata.dublincore.SeriesCatalogUIAdapter; import org.opencastproject.security.api.SecurityService; import org.opencastproject.series.api.SeriesException; import org.opencastproject.series.api.SeriesService; import org.opencastproject.util.RequireUtil; import com.entwinemedia.fn.data.Opt; import org.apache.commons.lang3.exception.ExceptionUtils; import org.osgi.service.cm.ConfigurationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.Dictionary; import java.util.Map; import java.util.Set; import java.util.TreeSet; /** * A series catalog UI adapter that is managed by a configuration. */ public class ConfigurableSeriesDCCatalogUIAdapter implements SeriesCatalogUIAdapter { /** The logger */ private static final Logger logger = LoggerFactory.getLogger(ConfigurableEventDCCatalogUIAdapter.class); /** The configuration dublincore catalog UI adapter service PID */ public static final String PID = "org.opencastproject.index.service.catalog.adapter"; /* The collection of keys to read from the OSGI configuration file */ public static final String CONF_TYPE_KEY = "type"; public static final String CONF_ORGANIZATION_KEY = "organization"; public static final String CONF_FLAVOR_KEY = "flavor"; public static final String CONF_TITLE_KEY = "title"; private SeriesService seriesService; private SecurityService securityService; /** The catalog UI adapter configuration */ private CatalogUIAdapterConfiguration config; /** The organization name */ private String organization; /** The flavor of this catalog */ private MediaPackageElementFlavor flavor; /** The title of this catalog */ private String title; /** Reference to the list providers service */ private ListProvidersService listProvidersService; /** The metadata fields for all properties of the underlying DublinCore */ private Map<String, MetadataField<?>> dublinCoreProperties; @Override public String getOrganization() { return organization; } @Override public String getFlavor() { return flavor.toString(); } @Override public String getUITitle() { return title; } @Override public MetadataCollection getRawFields() { DublinCoreMetadataCollection dublinCoreMetadata = new DublinCoreMetadataCollection(); Set<String> emptyFields = new TreeSet<>(dublinCoreProperties.keySet()); populateEmptyFields(dublinCoreMetadata, emptyFields); return dublinCoreMetadata; } @Override public Opt<MetadataCollection> getFields(String seriesId) { final Opt<DublinCoreCatalog> optDCCatalog = loadDublinCoreCatalog( RequireUtil.requireNotBlank(seriesId, "seriesId")); if (optDCCatalog.isSome()) { DublinCoreMetadataCollection dublinCoreMetadata = new DublinCoreMetadataCollection(); Set<String> emptyFields = new TreeSet<>(dublinCoreProperties.keySet()); getFieldValuesFromDublinCoreCatalog(dublinCoreMetadata, emptyFields, optDCCatalog.get()); populateEmptyFields(dublinCoreMetadata, emptyFields); return Opt.some((MetadataCollection) dublinCoreMetadata); } else { return Opt.none(); } } @Override public boolean storeFields(String seriesId, MetadataCollection metadata) { final Opt<DublinCoreCatalog> optDCCatalog = loadDublinCoreCatalog( RequireUtil.requireNotBlank(seriesId, "seriesId")); if (optDCCatalog.isSome()) { final DublinCoreCatalog dc = optDCCatalog.get(); dc.addBindings(config.getXmlNamespaceContext()); DublinCoreMetadataUtil.updateDublincoreCatalog(dc, metadata); saveDublinCoreCatalog(seriesId, dc); return true; } else { return false; } } /** * Reconfigures the {@link SeriesCatalogUIAdapter} instance with an updated set of configuration properties; * * @param properties * the configuration properties * @throws ConfigurationException * if there is a configuration error */ public void updated(Dictionary<String, ?> properties) throws ConfigurationException { config = CatalogUIAdapterConfiguration.loadFromDictionary(properties); organization = getCfg(properties, CONF_ORGANIZATION_KEY); flavor = MediaPackageElementFlavor.parseFlavor(getCfg(properties, CONF_FLAVOR_KEY)); title = getCfg(properties, CONF_TITLE_KEY); dublinCoreProperties = DublinCoreMetadataUtil.getDublinCoreProperties(properties); } /** OSGi callback to set list provider service instance */ public void setListProvidersService(ListProvidersService listProvidersService) { this.listProvidersService = listProvidersService; } /** Return the {@link SeriesService} to get access to the metadata catalogs of a series */ protected SeriesService getSeriesService() { return seriesService; } /** OSGi callback to bind instance of {@link SeriesService} */ public void setSeriesService(SeriesService seriesService) { this.seriesService = seriesService; } /** Return instance of {@link SecurityService}. */ protected SecurityService getSecurityService() { return securityService; } /** OSGi callback to bind instance of {@link SecurityService} */ public void setSecurityService(SecurityService securityService) { this.securityService = securityService; } protected Opt<DublinCoreCatalog> loadDublinCoreCatalog(String seriesId) { try { Opt<byte[]> seriesElementData = getSeriesService().getSeriesElementData(requireNonNull(seriesId), flavor.getType()); if (seriesElementData.isSome()) { final DublinCoreCatalog dc = DublinCoreByteFormat.read(seriesElementData.get()); // Make sure that the catalog has its flavor set. // It may happen, when updating a system, that already saved catalogs // do not have a flavor. dc.setFlavor(flavor); dc.addBindings(config.getXmlNamespaceContext()); return Opt.some(dc); } else { final DublinCoreCatalog dc = DublinCores.mkStandard(); dc.addBindings(config.getXmlNamespaceContext()); dc.setRootTag(new EName(config.getCatalogXmlRootNamespace(), config.getCatalogXmlRootElementName())); dc.setFlavor(flavor); return Opt.some(dc); } } catch (SeriesException e) { logger.error("Error while loading DublinCore catalog of series '{}': {}", seriesId, ExceptionUtils.getStackTrace(e)); return Opt.none(); } } protected boolean saveDublinCoreCatalog(String seriesId, DublinCoreCatalog dc) { try { final byte[] dcData = dc.toXmlString().getBytes("UTF-8"); if (getSeriesService().getSeriesElementData(seriesId, flavor.getType()).isSome()) { return getSeriesService().updateSeriesElement(seriesId, flavor.getType(), dcData); } else { return getSeriesService().addSeriesElement(seriesId, flavor.getType(), dcData); } } catch (IOException e) { logger.error("Error while serializing the dublin core catalog to XML: {}", ExceptionUtils.getStackTrace(e)); return false; } catch (SeriesException e) { logger.error("Error while saving the series element: {}", ExceptionUtils.getStackTrace(e)); return false; } } private void populateEmptyFields(DublinCoreMetadataCollection dublinCoreMetadata, Set<String> emptyFields) { // Add all of the rest of the fields that didn't have values as empty. for (String field : emptyFields) { if (dublinCoreProperties.get(field) == null) { logger.warn("Skipping field {} because it is not defined in the properties file.", field); } try { dublinCoreMetadata.addField(dublinCoreProperties.get(field), "", listProvidersService); } catch (Exception e) { logger.error("Skipping metadata field '{}' because of error: {}", field, ExceptionUtils.getStackTrace(e)); } } } private void getFieldValuesFromDublinCoreCatalog(DublinCoreMetadataCollection dublinCoreMetadata, Set<String> emptyFields, DublinCoreCatalog dc) { for (EName propertyKey : dc.getValues().keySet()) { for (String metdataFieldKey : dublinCoreProperties.keySet()) { MetadataField<?> metadataField = dublinCoreProperties.get(metdataFieldKey); String namespace = DublinCore.TERMS_NS_URI; if (metadataField.getNamespace().isSome()) { namespace = metadataField.getNamespace().get(); } if (namespace.equalsIgnoreCase(propertyKey.getNamespaceURI()) && metadataField.getInputID().equalsIgnoreCase(propertyKey.getLocalName())) { for (DublinCoreValue dublinCoreValue : dc.get(propertyKey)) { emptyFields.remove(metdataFieldKey); try { dublinCoreMetadata.addField(metadataField, dublinCoreValue.getValue(), listProvidersService); } catch (IllegalArgumentException e) { logger.error("Skipping metadata field '{}' because of error: {}", metadataField.getInputID(), ExceptionUtils.getStackTrace(e)); } } } } } } }