/** * 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; import org.opencastproject.mediapackage.EName; import org.opencastproject.metadata.dublincore.DCMIPeriod; import org.opencastproject.metadata.dublincore.DublinCore; import org.opencastproject.metadata.dublincore.DublinCoreCatalog; import org.opencastproject.metadata.dublincore.DublinCoreValue; import org.opencastproject.metadata.dublincore.EncodingSchemeUtils; import org.opencastproject.metadata.dublincore.MetadataCollection; import org.opencastproject.metadata.dublincore.MetadataField; import org.opencastproject.metadata.dublincore.MetadataField.Type; import org.opencastproject.metadata.dublincore.Precision; import com.entwinemedia.fn.data.Opt; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Collections; import java.util.Date; import java.util.Dictionary; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; /** * @author bbru * */ public final class DublinCoreMetadataUtil { private static final Logger logger = LoggerFactory.getLogger(DublinCoreMetadataUtil.class); private DublinCoreMetadataUtil() { } /** * Update a {@link DublinCoreCatalog} with the values contained within a {@link AbstractMetadataCollection} * * @param dc * The {@link DublinCoreCatalog} to update the values within. * @param metadata * The {@link AbstractMetadataCollection} data definitions and values to update the catalog with. */ public static void updateDublincoreCatalog(DublinCoreCatalog dc, MetadataCollection metadata) { for (MetadataField<?> field : metadata.getOutputFields().values()) { if (field.isUpdated() && field.getValue().isSome()) { final String namespace = field.getNamespace().getOr(DublinCore.TERMS_NS_URI); final EName ename = new EName(namespace, field.getInputID()); if (field.getType() == MetadataField.Type.START_DATE) { setTemporalStartDate(dc, field, ename); } else if (field.getType() == MetadataField.Type.START_TIME) { setTemporalStartTime(dc, field, ename); } else if (field.getType() == MetadataField.Type.DURATION) { setDuration(dc, field, ename); } else if (field.getType() == Type.DATE) { setDate(dc, field, ename); } else if (field.getType() == MetadataField.Type.MIXED_TEXT || field.getType() == Type.ITERABLE_TEXT) { setIterableString(dc, field, ename); } else { if (field.isRequired() && StringUtils.isBlank(field.getValue().get().toString())) throw new IllegalArgumentException( String.format( "The event metadata field with id '%s' and the metadata type '%s' is required and can not be empty!.", field.getInputID(), field.getType())); dc.set(ename, field.getValue().get().toString()); } } else if (field.getValue().isNone() && field.isRequired()) { throw new IllegalArgumentException(String.format( "The event metadata field with id '%s' and the metadata type '%s' is required and can not be empty!.", field.getInputID(), field.getType())); } } } /** * Set the value of an iterable string, comma separated * * @param dc * The dublin core catalog to add the iterable string value to (or remove it if empty) * @param field * The {@link MetadataField} with the value to update. * @param ename * The {@link EName} of the property in the {@link DublinCoreCatalog} to update. */ private static void setIterableString(DublinCoreCatalog dc, MetadataField<?> field, final EName ename) { if (field.getValue().isSome()) { String valueString; if (field.getValue().get() instanceof String) { valueString = (String) field.getValue().get(); } else { @SuppressWarnings("unchecked") Iterable<String> valueIterable = (Iterable<String>) field.getValue().get(); valueString = StringUtils.join(valueIterable.iterator(), ","); } if (StringUtils.isBlank(StringUtils.trimToEmpty(valueString))) { // The value of the iterative string is empty so we will remove it. dc.remove(ename); } else { dc.set(ename, valueString); } } } private static Opt<DCMIPeriod> getPeriodFromCatalog(DublinCoreCatalog dc, EName ename) { List<DublinCoreValue> periodStrings = dc.get(ename); Opt<DCMIPeriod> p = Opt.<DCMIPeriod> none(); for (DublinCoreValue periodString : periodStrings) { p = Opt.some(EncodingSchemeUtils.decodePeriod(periodString.getValue())); } return p; } /** * @param period * The dublin core period for this event. * @return The milliseconds between the start and end time for this event. */ static Long getDuration(Opt<DCMIPeriod> period) { Long duration = 0L; if (period.isSome() && period.get().hasStart() && period.get().hasEnd()) { return period.get().getEnd().getTime() - period.get().getStart().getTime(); } return duration; } /** * Sets a date object with the correct formatting into the dublin core catalog. * * @param dc * The dublin core catalog to insert the date. * @param field * The field with the date value and pattern ready to format. * @param ename * The unique id for the dublin core property. */ private static void setDate(DublinCoreCatalog dc, MetadataField<?> field, EName ename) { if (field.getValue().get() instanceof Date && field.getPattern().isNone()) { throw new IllegalArgumentException("There needs to be a pattern property set for " + field.getInputID() + ":" + field.getOutputID() + ":" + field.getValue() + " metadata field to store and retrieve the result."); } if (field.getValue().get() instanceof Date) { SimpleDateFormat sdf = new SimpleDateFormat(field.getPattern().get()); dc.set(ename, sdf.format((Date) field.getValue().get())); } else { dc.set(ename, field.getValue().get().toString()); } } /** * Gets the current hour, minute and second from a dublin core period if available. * * @param period * The current period from dublin core. * @return A new DateTime with the current hour, minute and second. */ static DateTime getCurrentStartTime(Opt<DCMIPeriod> period) { DateTime currentStartTime = new DateTime(); currentStartTime = currentStartTime.withZone(DateTimeZone.UTC); currentStartTime = currentStartTime.withHourOfDay(0); currentStartTime = currentStartTime.withMinuteOfHour(0); currentStartTime = currentStartTime.withSecondOfMinute(0); if (period.isSome() && period.get().hasStart()) { DateTime fromDC = new DateTime(period.get().getStart().getTime()); fromDC = fromDC.withZone(DateTimeZone.UTC); currentStartTime = currentStartTime.withZone(DateTimeZone.UTC); currentStartTime = currentStartTime.withHourOfDay(fromDC.getHourOfDay()); currentStartTime = currentStartTime.withMinuteOfHour(fromDC.getMinuteOfHour()); currentStartTime = currentStartTime.withSecondOfMinute(fromDC.getSecondOfMinute()); } return currentStartTime; } /** * Gets the current hour, minute and second from a dublin core period if available. * * @param period * The current period from dublin core. * @return A new DateTime with the current hour, minute and second. */ static DateTime getCurrentStartDate(Opt<DCMIPeriod> period) { DateTime currentStartDate = new DateTime(); currentStartDate = currentStartDate.withZone(DateTimeZone.UTC); currentStartDate = currentStartDate.withYear(2001); currentStartDate = currentStartDate.withMonthOfYear(1); currentStartDate = currentStartDate.withDayOfMonth(1); if (period.isSome() && period.get().hasStart()) { DateTime fromDC = new DateTime(period.get().getStart().getTime()); fromDC = fromDC.withZone(DateTimeZone.UTC); currentStartDate = currentStartDate.withZone(DateTimeZone.UTC); currentStartDate = currentStartDate.withYear(fromDC.getYear()); currentStartDate = currentStartDate.withMonthOfYear(fromDC.getMonthOfYear()); currentStartDate = currentStartDate.withDayOfMonth(fromDC.getDayOfMonth()); } return currentStartDate; } /** * Sets the start date in a dublin core catalog to the right value and keeps the start time and duration the same. * * @param dc * The dublin core catalog to adjust * @param field * The metadata field that contains the start date. * @param ename * The EName in the catalog to identify the property that has the dublin core period. */ static void setTemporalStartDate(DublinCoreCatalog dc, MetadataField<?> field, EName ename) { if (field.getValue().isNone() || (field.getValue().get() instanceof String && StringUtils.isBlank(field.getValue().get().toString()))) { logger.debug("No value was set for metadata field with dublin core id '{}' and json id '{}'", field.getInputID(), field.getOutputID()); return; } try { // Get the current date SimpleDateFormat dateFormat = MetadataField.getSimpleDateFormatter(field.getPattern().get()); Date startDate = dateFormat.parse((String) field.getValue().get()); // Get the current period Opt<DCMIPeriod> period = getPeriodFromCatalog(dc, ename); // Get the current duration Long duration = getDuration(period); // Get the current start time hours, minutes and seconds DateTime currentStartTime = getCurrentStartTime(period); // Setup the new start time DateTime startDateTime = new DateTime(startDate.getTime()); startDateTime = startDateTime.withZone(DateTimeZone.UTC); startDateTime = startDateTime.withHourOfDay(currentStartTime.getHourOfDay()); startDateTime = startDateTime.withMinuteOfHour(currentStartTime.getMinuteOfHour()); startDateTime = startDateTime.withSecondOfMinute(currentStartTime.getSecondOfMinute()); // Get the current end date based on new date and duration. DateTime endDate = new DateTime(startDateTime.toDate().getTime() + duration); dc.set(ename, EncodingSchemeUtils.encodePeriod(new DCMIPeriod(startDateTime.toDate(), endDate.toDate()), Precision.Second)); } catch (ParseException e) { logger.error("Not able to parse date {} to update the dublin core because: {}", field.getValue(), ExceptionUtils.getStackTrace(e)); } } /** * Gets the current hour, minute and second from a dublin core period if available. * * @param period * The current period from dublin core. * @return A new DateTime with the current hour, minute and second. */ private static DateTime getCurrentStartDateTime(Opt<DCMIPeriod> period) { DateTime currentStartTime = new DateTime(); currentStartTime = currentStartTime.withZone(DateTimeZone.UTC); currentStartTime = currentStartTime.withYear(2001); currentStartTime = currentStartTime.withMonthOfYear(1); currentStartTime = currentStartTime.withDayOfMonth(1); currentStartTime = currentStartTime.withHourOfDay(0); currentStartTime = currentStartTime.withMinuteOfHour(0); currentStartTime = currentStartTime.withSecondOfMinute(0); if (period.isSome() && period.get().hasStart()) { DateTime fromDC = new DateTime(period.get().getStart().getTime()); fromDC = fromDC.withZone(DateTimeZone.UTC); currentStartTime = currentStartTime.withZone(DateTimeZone.UTC); currentStartTime = currentStartTime.withYear(fromDC.getYear()); currentStartTime = currentStartTime.withMonthOfYear(fromDC.getMonthOfYear()); currentStartTime = currentStartTime.withDayOfMonth(fromDC.getDayOfMonth()); currentStartTime = currentStartTime.withHourOfDay(fromDC.getHourOfDay()); currentStartTime = currentStartTime.withMinuteOfHour(fromDC.getMinuteOfHour()); currentStartTime = currentStartTime.withSecondOfMinute(fromDC.getSecondOfMinute()); } return currentStartTime; } /** * Sets the start time in a dublin core catalog to the right value and keeps the start date and duration the same. * * @param dc * The dublin core catalog to adjust * @param field * The metadata field that contains the start time. * @param ename * The EName in the catalog to identify the property that has the dublin core period. */ static void setTemporalStartTime(DublinCoreCatalog dc, MetadataField<?> field, EName ename) { if (field.getValue().isNone() || (field.getValue().get() instanceof String && StringUtils.isBlank(field.getValue().get().toString()))) { logger.debug("No value was set for metadata field with dublin core id '{}' and json id '{}'", field.getInputID(), field.getOutputID()); return; } try { // Get the current date SimpleDateFormat dateFormat = MetadataField.getSimpleDateFormatter(field.getPattern().get()); Date startDate = dateFormat.parse((String) field.getValue().get()); // Get the current period Opt<DCMIPeriod> period = getPeriodFromCatalog(dc, ename); // Get the current duration Long duration = getDuration(period); // Get the current start date DateTime currentStartDate = getCurrentStartDate(period); // Setup the new start time DateTime startDateTime = new DateTime(startDate.getTime()); startDateTime = startDateTime.withZone(DateTimeZone.UTC); startDateTime = startDateTime.withYear(currentStartDate.getYear()); startDateTime = startDateTime.withMonthOfYear(currentStartDate.getMonthOfYear()); startDateTime = startDateTime.withDayOfMonth(currentStartDate.getDayOfMonth()); // Get the current end date based on new date and duration. DateTime endDate = new DateTime(startDateTime.toDate().getTime() + duration); dc.set(ename, EncodingSchemeUtils.encodePeriod(new DCMIPeriod(startDateTime.toDate(), endDate.toDate()), Precision.Second)); } catch (ParseException e) { logger.error("Not able to parse date {} to update the dublin core because: {}", field.getValue(), ExceptionUtils.getStackTrace(e)); } } /** * Sets the duration in a dublin core catalog to the right value and keeps the start date and start time the same. * * @param dc * The dublin core catalog to adjust * @param field * The metadata field that contains the duration. * @param ename * The EName in the catalog to identify the property that has the dublin core period. */ static void setDuration(DublinCoreCatalog dc, MetadataField<?> field, EName ename) { if (field.getValue().isNone()) { logger.error("No value was set for metadata field with dublin core id '{}' and json id '{}'", field.getInputID(), field.getOutputID()); return; } // Get the current period Opt<DCMIPeriod> period = getPeriodFromCatalog(dc, ename); // Get the current duration Long duration = 0L; try { duration = Long.parseLong(field.getValue().get().toString()); } catch (NumberFormatException e) { logger.debug("Unable to parse the duration's value '{}' as a long value. Trying it as a period next.", field.getValue().get()); } if (duration < 1L) { duration = getDuration(period); } // Get the current start date DateTime startDateTime = getCurrentStartDateTime(period); // Get the current end date based on new date and duration. DateTime endDate = new DateTime(startDateTime.toDate().getTime() + duration); dc.set(ename, EncodingSchemeUtils.encodePeriod(new DCMIPeriod(startDateTime.toDate(), endDate.toDate()), Precision.Second)); } @SuppressWarnings("unchecked") public static Map<String, MetadataField<?>> getDublinCoreProperties(Dictionary configProperties) { Map<String, MetadataField<?>> dublinCorePropertyMapByConfigurationName = new HashMap<String, MetadataField<?>>(); for (Object configObject : Collections.list(configProperties.keys())) { String property = configObject.toString(); if (getDublinCorePropertyName(property).isSome()) { MetadataField<?> dublinCoreProperty = dublinCorePropertyMapByConfigurationName .get(getDublinCorePropertyName(property).get()); if (dublinCoreProperty == null) { dublinCoreProperty = new MetadataField(); } dublinCoreProperty.setValue(getDublinCorePropertyKey(property).get(), configProperties.get(property).toString()); dublinCorePropertyMapByConfigurationName.put(getDublinCorePropertyName(property).get(), dublinCoreProperty); } } Map<String, MetadataField<?>> dublinCorePropertyMap = new TreeMap<String, MetadataField<?>>(); for (MetadataField dublinCoreProperty : dublinCorePropertyMapByConfigurationName.values()) { dublinCorePropertyMap.put(dublinCoreProperty.getOutputID(), dublinCoreProperty); } return dublinCorePropertyMap; } static boolean isDublinCoreProperty(String propertyKey) { return !StringUtils.isBlank(propertyKey) && propertyKey.split("\\.").length == 3 && propertyKey.split("\\.")[0].equalsIgnoreCase(MetadataField.CONFIG_PROPERTY_PREFIX); } static Opt<String> getDublinCorePropertyName(String propertyKey) { if (isDublinCoreProperty(propertyKey)) { return Opt.some(propertyKey.split("\\.")[1]); } return Opt.none(); } static Opt<String> getDublinCorePropertyKey(String propertyKey) { if (isDublinCoreProperty(propertyKey)) { return Opt.some(propertyKey.split("\\.")[2]); } return Opt.none(); } }