/**
* 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.scheduler.impl;
import org.opencastproject.metadata.dublincore.DCMIPeriod;
import org.opencastproject.metadata.dublincore.DublinCore;
import org.opencastproject.metadata.dublincore.DublinCoreCatalog;
import org.opencastproject.metadata.dublincore.DublinCoreCatalogList;
import org.opencastproject.metadata.dublincore.DublinCoreValue;
import org.opencastproject.metadata.dublincore.EncodingSchemeUtils;
import org.opencastproject.security.api.UnauthorizedException;
import org.opencastproject.series.api.SeriesException;
import org.opencastproject.series.api.SeriesQuery;
import org.opencastproject.series.api.SeriesService;
import org.opencastproject.util.NotFoundException;
import net.fortuna.ical4j.model.Calendar;
import net.fortuna.ical4j.model.DateTime;
import net.fortuna.ical4j.model.ParameterList;
import net.fortuna.ical4j.model.component.VEvent;
import net.fortuna.ical4j.model.parameter.Cn;
import net.fortuna.ical4j.model.parameter.Encoding;
import net.fortuna.ical4j.model.parameter.FmtType;
import net.fortuna.ical4j.model.parameter.Value;
import net.fortuna.ical4j.model.parameter.XParameter;
import net.fortuna.ical4j.model.property.Attach;
import net.fortuna.ical4j.model.property.CalScale;
import net.fortuna.ical4j.model.property.Description;
import net.fortuna.ical4j.model.property.Location;
import net.fortuna.ical4j.model.property.Organizer;
import net.fortuna.ical4j.model.property.ProdId;
import net.fortuna.ical4j.model.property.RelatedTo;
import net.fortuna.ical4j.model.property.Uid;
import net.fortuna.ical4j.model.property.Version;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.URI;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* Create an iCalendar from the provided dublin core events.
*
*/
public class CalendarGenerator {
/** Logging utility */
private static final Logger logger = LoggerFactory.getLogger(CalendarGenerator.class);
/** iCalendar */
protected Calendar cal;
/** Series service for Series DC retrieval */
protected SeriesService seriesService;
private final Map<String, DublinCoreCatalog> series = new HashMap<String, DublinCoreCatalog>();;
/**
* Default constructor that creates a CalendarGenerator object
*
* @param seriesService
* Series service for retrieving series Dublin Core (if event has one)
*/
public CalendarGenerator(SeriesService seriesService) {
cal = new Calendar();
cal.getProperties().add(new ProdId("Opencast Matterhorn Calendar File 0.5"));
cal.getProperties().add(Version.VERSION_2_0);
cal.getProperties().add(CalScale.GREGORIAN);
this.seriesService = seriesService;
}
/**
* gets the iCalendar creates by this object.
*
* @return the iCalendar
*/
public Calendar getCalendar() {
return cal;
}
/**
* Sets an iCalender to work with
*
* @param cal
* the iCalendar to set
*/
public void setCalendar(Calendar cal) {
this.cal = cal;
}
/**
* Adds an SchedulerEvent as a new entry to this iCalendar
*
* @param catalog
* {@link DublinCoreCatalog} of event
* @param captureAgentMetadata
* properties for capture agent metadata
*
* @return true if the event could be added.
*/
public boolean addEvent(DublinCoreCatalog catalog, String catalogString, String captureAgentMetadata) {
String eventId = catalog.getFirst(DublinCore.PROPERTY_IDENTIFIER);
logger.debug("Creating iCaleandar VEvent from scheduled event '{}'", eventId);
DCMIPeriod period = EncodingSchemeUtils.decodeMandatoryPeriod(catalog.getFirst(DublinCore.PROPERTY_TEMPORAL));
if (!period.hasStart()) {
logger.debug("Couldn't get startdate from event!");
return false;
}
if (!period.hasEnd()) {
logger.debug("Couldn't get enddate from event!");
return false;
}
DateTime startDate = new DateTime(period.getStart());
DateTime endDate = new DateTime(period.getEnd());
Date marginEndDate = new org.joda.time.DateTime(endDate.getTime()).plusHours(1).toDate();
if (marginEndDate.before(new Date())) {
logger.debug("Event has already passed more than an hour, skipping!");
return false;
}
startDate.setUtc(true);
endDate.setUtc(true);
String seriesID = null;
VEvent event = new VEvent(startDate, endDate, catalog.getFirst(DublinCore.PROPERTY_TITLE));
try {
ParameterList pl = new ParameterList();
for (DublinCoreValue creator : catalog.get(DublinCore.PROPERTY_CREATOR)) {
pl.add(new Cn(creator.getValue()));
}
event.getProperties().add(new Uid(eventId));
// TODO Organizer should be URI (email-address?) created fake address
if (StringUtils.isNotEmpty(catalog.getFirst(DublinCore.PROPERTY_CREATOR))) {
URI organizer = new URI("mailto", catalog.getFirst(DublinCore.PROPERTY_CREATOR) + "@matterhorn.opencast", null);
event.getProperties().add(new Organizer(pl, organizer));
}
if (StringUtils.isNotEmpty(catalog.getFirst(DublinCore.PROPERTY_DESCRIPTION))) {
event.getProperties().add(new Description(catalog.getFirst(DublinCore.PROPERTY_DESCRIPTION)));
}
// location corresponds to spatial? or is location part of CA configuration?
if (StringUtils.isNotEmpty(catalog.getFirst(DublinCore.PROPERTY_SPATIAL))) {
event.getProperties().add(new Location(catalog.getFirst(DublinCore.PROPERTY_SPATIAL)));
}
if (StringUtils.isNotEmpty(catalog.getFirst(DublinCore.PROPERTY_IS_PART_OF))) {
seriesID = catalog.getFirst(DublinCore.PROPERTY_IS_PART_OF);
event.getProperties().add(new RelatedTo(seriesID));
}
ParameterList dcParameters = new ParameterList();
dcParameters.add(new FmtType("application/xml"));
dcParameters.add(Value.BINARY);
dcParameters.add(Encoding.BASE64);
dcParameters.add(new XParameter("X-APPLE-FILENAME", "episode.xml"));
Attach metadataAttachment = new Attach(dcParameters, catalogString.getBytes("UTF-8"));
event.getProperties().add(metadataAttachment);
if (checkSeriesOptOut(seriesID))
return false;
String seriesDC = getSeriesDublinCoreAsString(seriesID);
if (seriesDC != null) {
logger.debug("Attaching series {} information to event {}", seriesID, eventId);
ParameterList sDcParameters = new ParameterList();
sDcParameters.add(new FmtType("application/xml"));
sDcParameters.add(Value.BINARY);
sDcParameters.add(Encoding.BASE64);
sDcParameters.add(new XParameter("X-APPLE-FILENAME", "series.xml"));
Attach seriesAttachment = new Attach(sDcParameters, seriesDC.getBytes("UTF-8"));
event.getProperties().add(seriesAttachment);
} else {
logger.debug("No series provided for event {}.", eventId);
}
ParameterList caParameters = new ParameterList();
caParameters.add(new FmtType("application/text"));
caParameters.add(Value.BINARY);
caParameters.add(Encoding.BASE64);
caParameters.add(new XParameter("X-APPLE-FILENAME", "org.opencastproject.capture.agent.properties"));
Attach agentsAttachment = new Attach(caParameters, captureAgentMetadata.getBytes("UTF-8"));
event.getProperties().add(agentsAttachment);
} catch (Exception e) {
logger.error("Unable to add event '{}' to recording calendar: {}", eventId, ExceptionUtils.getStackTrace(e));
return false;
}
cal.getComponents().add(event);
logger.debug("new VEvent = {} ", event.toString());
return true;
}
private boolean checkSeriesOptOut(String seriesID) {
if (StringUtils.isBlank(seriesID))
return false;
if (seriesService == null) {
logger.warn("No SeriesService available");
return true;
}
try {
return seriesService.isOptOut(seriesID);
} catch (NotFoundException e) {
logger.warn(
"Unable to find series '{}'. Event will not be added to the calendar because it might be opted out. {}",
seriesID, ExceptionUtils.getStackTrace(e));
return true;
} catch (SeriesException e) {
logger.warn(
"Unable to find series '{}'. Event will not be added to the calendar because it might be opted out. {}",
seriesID, ExceptionUtils.getStackTrace(e));
return true;
}
}
/**
* Returns series DC associated with this event or null if {@link SeriesService} is not available or does not contain
* entry for series with specified ID.
*
* @param seriesID
* {@link DublinCoreCatalog} to be retrieved
* @return DC serialized to string or null
* @throws UnauthorizedException
* if the current user is not allowed to view this series
* @throws NotFoundException
* if the series cannot be found
*/
private String getSeriesDublinCoreAsString(String seriesID) throws UnauthorizedException, NotFoundException {
if (StringUtils.isBlank(seriesID))
return null;
if (seriesService == null) {
logger.warn("No SeriesService available");
return null;
}
if (series.isEmpty()) {
try {
DublinCoreCatalogList seriesCatalogs = seriesService.getSeries(new SeriesQuery().setCount(Integer.MAX_VALUE));
for (DublinCoreCatalog dc : seriesCatalogs.getCatalogList()) {
series.put(dc.getFirst(DublinCore.PROPERTY_IDENTIFIER), dc);
}
} catch (SeriesException e) {
logger.error("Error loading DublinCoreCatalog for series '{}': {}", seriesID, ExceptionUtils.getStackTrace(e));
return null;
}
}
DublinCoreCatalog seriesDC = series.get(seriesID);
if (seriesDC == null) {
try {
seriesDC = seriesService.getSeries(seriesID);
} catch (SeriesException e) {
logger.error("Error loading DublinCoreCatalog for series '{}': {}", seriesID, ExceptionUtils.getStackTrace(e));
return null;
}
}
try {
return seriesDC.toXmlString();
} catch (IOException e) {
logger.error("Error serializing DublinCoreCatalog of series '{}': {}", seriesID, ExceptionUtils.getStackTrace(e));
return null;
}
}
}