Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/**
* 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.endpoint;
import static com.entwinemedia.fn.Prelude.chuck;
import static com.entwinemedia.fn.Stream.$;
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import static org.apache.commons.lang3.exception.ExceptionUtils.getMessage;
import static org.opencastproject.capture.CaptureParameters.AGENT_REGISTRATION_TYPE;
import static org.opencastproject.capture.CaptureParameters.AGENT_REGISTRATION_TYPE_ADHOC;
import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_CREATED;
import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_SPATIAL;
import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_TEMPORAL;
import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_TITLE;
import static org.opencastproject.util.Jsons.arr;
import static org.opencastproject.util.Jsons.obj;
import static org.opencastproject.util.Jsons.p;
import static org.opencastproject.util.Jsons.v;
import static org.opencastproject.util.RestUtil.generateErrorResponse;
import static org.opencastproject.util.data.Monadics.mlist;
import org.opencastproject.capture.admin.api.Agent;
import org.opencastproject.capture.admin.api.AgentState;
import org.opencastproject.capture.admin.api.CaptureAgentStateService;
import org.opencastproject.mediapackage.Catalog;
import org.opencastproject.mediapackage.MediaPackage;
import org.opencastproject.mediapackage.MediaPackageBuilderFactory;
import org.opencastproject.mediapackage.MediaPackageElement;
import org.opencastproject.mediapackage.MediaPackageElementBuilderFactory;
import org.opencastproject.mediapackage.MediaPackageElementFlavor;
import org.opencastproject.mediapackage.MediaPackageElements;
import org.opencastproject.mediapackage.MediaPackageException;
import org.opencastproject.mediapackage.MediaPackageParser;
import org.opencastproject.mediapackage.MediaPackageSupport;
import org.opencastproject.metadata.dublincore.DCMIPeriod;
import org.opencastproject.metadata.dublincore.DublinCore;
import org.opencastproject.metadata.dublincore.DublinCoreCatalog;
import org.opencastproject.metadata.dublincore.DublinCoreUtil;
import org.opencastproject.metadata.dublincore.DublinCores;
import org.opencastproject.metadata.dublincore.EncodingSchemeUtils;
import org.opencastproject.metadata.dublincore.Precision;
import org.opencastproject.rest.RestConstants;
import org.opencastproject.scheduler.api.Recording;
import org.opencastproject.scheduler.api.SchedulerConflictException;
import org.opencastproject.scheduler.api.SchedulerException;
import org.opencastproject.scheduler.api.SchedulerService;
import org.opencastproject.scheduler.api.TechnicalMetadata;
import org.opencastproject.scheduler.impl.CaptureNowProlongingService;
import org.opencastproject.security.api.UnauthorizedException;
import org.opencastproject.systems.OpencastConstants;
import org.opencastproject.util.DateTimeSupport;
import org.opencastproject.util.Jsons;
import org.opencastproject.util.Jsons.Arr;
import org.opencastproject.util.Jsons.Prop;
import org.opencastproject.util.Jsons.Val;
import org.opencastproject.util.NotFoundException;
import org.opencastproject.util.RestUtil;
import org.opencastproject.util.UrlSupport;
import org.opencastproject.util.doc.rest.RestParameter;
import org.opencastproject.util.doc.rest.RestParameter.Type;
import org.opencastproject.util.doc.rest.RestQuery;
import org.opencastproject.util.doc.rest.RestResponse;
import org.opencastproject.util.doc.rest.RestService;
import org.opencastproject.workspace.api.Workspace;
import com.entwinemedia.fn.data.Opt;
import com.google.gson.Gson;
import net.fortuna.ical4j.model.property.RRule;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.osgi.service.component.ComponentContext;
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.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.net.URI;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;
/**
* REST Endpoint for Scheduler Service
*/
@Path("/")
@RestService(name = "schedulerservice", title = "Scheduler Service", abstractText = "This service creates, edits and retrieves and helps managing scheduled capture events.", notes = {
"All paths above are relative to the REST endpoint base (something like http://your.server/files)",
"If the service is down or not working it will return a status 503, this means the the underlying service is "
+ "not working and is either restarting or has failed",
"A status code 500 means a general failure has occurred which is not recoverable and was not anticipated. In "
+ "other words, there is a bug! You should file an error report with your server logs from the time when the "
+ "error occurred: Opencast Issue Tracker" })
@Component(
immediate = true,
service = SchedulerRestService.class,
property = {
"service.description=Scheduler REST Endpoint",
"opencast.service.type=org.opencastproject.scheduler",
"opencast.service.path=/recordings"
}
)
public class SchedulerRestService {
private static final Logger logger = LoggerFactory.getLogger(SchedulerRestService.class);
/** Key for the default workflow definition in config.properties */
private static final String DEFAULT_WORKFLOW_DEFINITION = "org.opencastproject.workflow.default.definition";
private SchedulerService service;
private CaptureAgentStateService agentService;
private CaptureNowProlongingService prolongingService;
private Workspace workspace;
private final Gson gson = new Gson();
private String defaultWorkflowDefinitionId;
protected String serverUrl = UrlSupport.DEFAULT_BASE_URL;
protected String serviceUrl = null;
/**
* Method to set the service this REST endpoint uses
*
* @param service
*/
@Reference(
policy = ReferencePolicy.DYNAMIC,
unbind = "unsetService"
)
public void setService(SchedulerService service) {
this.service = service;
}
/**
* Method to unset the service this REST endpoint uses
*
* @param service
*/
public void unsetService(SchedulerService service) {
this.service = null;
}
/**
* Method to set the prolonging service this REST endpoint uses
*
* @param prolongingService
*/
@Reference(
policy = ReferencePolicy.DYNAMIC,
unbind = "unsetProlongingService"
)
public void setProlongingService(CaptureNowProlongingService prolongingService) {
this.prolongingService = prolongingService;
}
/**
* Method to unset the prolonging service this REST endpoint uses
*
* @param prolongingService
*/
public void unsetProlongingService(CaptureNowProlongingService prolongingService) {
this.prolongingService = null;
}
/**
* Method to set the capture agent state service this REST endpoint uses
*
* @param agentService
*/
@Reference(
cardinality = ReferenceCardinality.OPTIONAL,
policy = ReferencePolicy.DYNAMIC,
unbind = "unsetCaptureAgentStateService"
)
public void setCaptureAgentStateService(CaptureAgentStateService agentService) {
this.agentService = agentService;
}
/**
* Method to unset the capture agent state service this REST endpoint uses
*
* @param agentService
*/
public void unsetCaptureAgentStateService(CaptureAgentStateService agentService) {
this.agentService = null;
}
/**
* Method to set the workspace this REST endpoint uses
*
* @param workspace
*/
@Reference
public void setWorkspace(Workspace workspace) {
this.workspace = workspace;
}
/**
* The method that will be called, if the service will be activated
*
* @param cc
* The ComponentContext of this service
*/
@Activate
public void activate(ComponentContext cc) {
// Get the configured server URL
if (cc != null) {
String ccServerUrl = cc.getBundleContext().getProperty(OpencastConstants.SERVER_URL_PROPERTY);
logger.debug("configured server url is {}", ccServerUrl);
if (ccServerUrl == null) {
serverUrl = UrlSupport.DEFAULT_BASE_URL;
} else {
serverUrl = ccServerUrl;
}
serviceUrl = (String) cc.getProperties().get(RestConstants.SERVICE_PATH_PROPERTY);
defaultWorkflowDefinitionId = StringUtils
.trimToNull(cc.getBundleContext().getProperty(DEFAULT_WORKFLOW_DEFINITION));
if (defaultWorkflowDefinitionId == null)
defaultWorkflowDefinitionId = "schedule-and-upload";
}
}
/**
* Gets a XML with the media package for the specified event.
*
* @param eventId
* The unique ID of the event.
* @return media package XML for the event
*/
@GET
@Produces(MediaType.TEXT_XML)
@Path("{id:.+}/mediapackage.xml")
@RestQuery(name = "getmediapackagexml", description = "Retrieves media package for specified event", returnDescription = "media package in XML", pathParameters = {
@RestParameter(name = "id", isRequired = true, description = "ID of event for which media package will be retrieved", type = Type.STRING) }, responses = {
@RestResponse(responseCode = HttpServletResponse.SC_OK, description = "DublinCore of event is in the body of response"),
@RestResponse(responseCode = HttpServletResponse.SC_NOT_FOUND, description = "Event with specified ID does not exist"),
@RestResponse(responseCode = HttpServletResponse.SC_UNAUTHORIZED, description = "You do not have permission to remove the event. Maybe you need to authenticate.") })
public Response getMediaPackageXml(@PathParam("id") String eventId) throws UnauthorizedException {
try {
MediaPackage result = service.getMediaPackage(eventId);
return Response.ok(MediaPackageParser.getAsXml(result)).build();
} catch (NotFoundException e) {
logger.info("Event with id '{}' does not exist.", eventId);
return Response.status(Status.NOT_FOUND).build();
} catch (SchedulerException e) {
logger.error("Unable to retrieve event with id '{}': {}", eventId, getMessage(e));
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
/**
* Gets a XML with the Dublin Core metadata for the specified event.
*
* @param eventId
* The unique ID of the event.
* @return Dublin Core XML for the event
*/
@GET
@Produces(MediaType.TEXT_XML)
@Path("{id:.+}/dublincore.xml")
@RestQuery(name = "recordingsasxml", description = "Retrieves DublinCore for specified event", returnDescription = "DublinCore in XML", pathParameters = {
@RestParameter(name = "id", isRequired = true, description = "ID of event for which DublinCore will be retrieved", type = Type.STRING) }, responses = {
@RestResponse(responseCode = HttpServletResponse.SC_OK, description = "DublinCore of event is in the body of response"),
@RestResponse(responseCode = HttpServletResponse.SC_NOT_FOUND, description = "Event with specified ID does not exist"),
@RestResponse(responseCode = HttpServletResponse.SC_UNAUTHORIZED, description = "You do not have permission to remove the event. Maybe you need to authenticate.") })
public Response getDublinCoreMetadataXml(@PathParam("id") String eventId) throws UnauthorizedException {
try {
DublinCoreCatalog result = service.getDublinCore(eventId);
return Response.ok(result.toXmlString()).build();
} catch (NotFoundException e) {
logger.info("Event with id '{}' does not exist.", eventId);
return Response.status(Status.NOT_FOUND).build();
} catch (UnauthorizedException e) {
throw e;
} catch (Exception e) {
logger.error("Unable to retrieve event with id '{}': {}", eventId, getMessage(e));
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
/**
* Gets a Dublin Core metadata for the specified event as JSON.
*
* @param eventId
* The unique ID of the event.
* @return Dublin Core JSON for the event
*/
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("{id:.+}/dublincore.json")
@RestQuery(name = "recordingsasjson", description = "Retrieves DublinCore for specified event", returnDescription = "DublinCore in JSON", pathParameters = {
@RestParameter(name = "id", isRequired = true, description = "ID of event for which DublinCore will be retrieved", type = Type.STRING) }, responses = {
@RestResponse(responseCode = HttpServletResponse.SC_OK, description = "DublinCore of event is in the body of response"),
@RestResponse(responseCode = HttpServletResponse.SC_NOT_FOUND, description = "Event with specified ID does not exist"),
@RestResponse(responseCode = HttpServletResponse.SC_UNAUTHORIZED, description = "You do not have permission to remove the event. Maybe you need to authenticate.") })
public Response getDublinCoreMetadataJSON(@PathParam("id") String eventId) throws UnauthorizedException {
try {
DublinCoreCatalog result = service.getDublinCore(eventId);
return Response.ok(result.toJson()).build();
} catch (NotFoundException e) {
logger.info("Event with id '{}' does not exist.", eventId);
return Response.status(Status.NOT_FOUND).build();
} catch (UnauthorizedException e) {
throw e;
} catch (Exception e) {
logger.error("Unable to retrieve event with id '{}': {}", eventId, getMessage(e));
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
/**
* Gets a XML with the media package for the specified event.
*
* @param eventId
* The unique ID of the event.
* @return media package XML for the event
*/
@GET
@Produces(MediaType.TEXT_XML)
@Path("{id:.+}/technical.json")
@RestQuery(name = "gettechnicalmetadatajson", description = "Retrieves the technical metadata for specified event", returnDescription = "technical metadata as JSON", pathParameters = {
@RestParameter(name = "id", isRequired = true, description = "ID of event for which the technical metadata will be retrieved", type = Type.STRING) }, responses = {
@RestResponse(responseCode = HttpServletResponse.SC_OK, description = "technical metadata of event is in the body of response"),
@RestResponse(responseCode = HttpServletResponse.SC_NOT_FOUND, description = "Event with specified ID does not exist"),
@RestResponse(responseCode = HttpServletResponse.SC_UNAUTHORIZED, description = "You do not have permission to remove the event. Maybe you need to authenticate.") })
public Response getTechnicalMetadataJSON(@PathParam("id") String eventId) throws UnauthorizedException {
try {
TechnicalMetadata metadata = service.getTechnicalMetadata(eventId);
Val state = v("");
Val lastHeard = v("");
if (metadata.getRecording().isSome()) {
state = v(metadata.getRecording().get().getState());
lastHeard = v(DateTimeSupport.toUTC(metadata.getRecording().get().getLastCheckinTime()));
}
Arr presenters = arr(mlist(metadata.getPresenters()).map(Jsons.stringVal));
List wfProperties = new ArrayList<>();
for (Entry entry : metadata.getWorkflowProperties().entrySet()) {
wfProperties.add(p(entry.getKey(), entry.getValue()));
}
List agentConfig = new ArrayList<>();
for (Entry entry : metadata.getCaptureAgentConfiguration().entrySet()) {
agentConfig.add(p(entry.getKey(), entry.getValue()));
}
return RestUtil.R.ok(obj(p("id", metadata.getEventId()), p("location", metadata.getAgentId()),
p("start", DateTimeSupport.toUTC(metadata.getStartDate().getTime())),
p("end", DateTimeSupport.toUTC(metadata.getEndDate().getTime())),
p("presenters", presenters), p("wfProperties", obj(wfProperties.toArray(new Prop[wfProperties.size()]))),
p("agentConfig", obj(agentConfig.toArray(new Prop[agentConfig.size()]))), p("state", state),
p("lastHeardFrom", lastHeard)));
} catch (NotFoundException e) {
logger.info("Event with id '{}' does not exist.", eventId);
return Response.status(Status.NOT_FOUND).build();
} catch (SchedulerException e) {
logger.error("Unable to retrieve event with id '{}': {}", eventId, getMessage(e));
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
/**
* Gets the workflow configuration for the specified event.
*
* @param eventId
* The unique ID of the event.
* @return the workflow configuration
*/
@GET
@Produces(MediaType.TEXT_PLAIN)
@Path("{id:.+}/workflow.properties")
@RestQuery(name = "recordingsagentproperties", description = "Retrieves workflow configuration for specified event", returnDescription = "workflow configuration in the form of key, value pairs", pathParameters = {
@RestParameter(name = "id", isRequired = true, description = "ID of event for which workflow configuration will be retrieved", type = Type.STRING) }, responses = {
@RestResponse(responseCode = HttpServletResponse.SC_OK, description = "workflow configuration of event is in the body of response"),
@RestResponse(responseCode = HttpServletResponse.SC_NOT_FOUND, description = "Event with specified ID does not exist"),
@RestResponse(responseCode = HttpServletResponse.SC_UNAUTHORIZED, description = "You do not have permission to remove the event. Maybe you need to authenticate.") })
public Response getWorkflowConfiguration(@PathParam("id") String eventId) throws UnauthorizedException {
try {
Map result = service.getWorkflowConfig(eventId);
String serializedProperties = serializeProperties(result);
return Response.ok(serializedProperties).build();
} catch (NotFoundException e) {
logger.info("Event with id '{}' does not exist.", eventId);
return Response.status(Status.NOT_FOUND).build();
} catch (SchedulerException e) {
logger.error("Unable to retrieve workflow configuration for event with id '{}': {}", eventId, getMessage(e));
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
/**
* Gets java Properties file with technical metadata for the specified event.
*
* @param eventId
* The unique ID of the event.
* @return Java Properties File with the metadata for the event
*/
@GET
@Produces(MediaType.TEXT_PLAIN)
@Path("{id:.+}/agent.properties")
@RestQuery(name = "recordingsagentproperties", description = "Retrieves Capture Agent properties for specified event", returnDescription = "Capture Agent properties in the form of key, value pairs", pathParameters = {
@RestParameter(name = "id", isRequired = true, description = "ID of event for which agent properties will be retrieved", type = Type.STRING) }, responses = {
@RestResponse(responseCode = HttpServletResponse.SC_OK, description = "Capture Agent properties of event is in the body of response"),
@RestResponse(responseCode = HttpServletResponse.SC_NOT_FOUND, description = "Event with specified ID does not exist"),
@RestResponse(responseCode = HttpServletResponse.SC_UNAUTHORIZED, description = "You do not have permission to remove the event. Maybe you need to authenticate.") })
public Response getCaptureAgentMetadata(@PathParam("id") String eventId) throws UnauthorizedException {
try {
Map result = service.getCaptureAgentConfiguration(eventId);
String serializedProperties = serializeProperties(result);
return Response.ok(serializedProperties).build();
} catch (NotFoundException e) {
logger.info("Event with id '{}' does not exist.", eventId);
return Response.status(Status.NOT_FOUND).build();
} catch (SchedulerException e) {
logger.error("Unable to retrieve event with id '{}': {}", eventId, getMessage(e));
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
/**
*
* Removes the specified event. Returns true if the event was found and could be removed.
*
* @param eventId
* The unique ID of the event.
* @return true if the event was found and could be deleted.
*/
@DELETE
@Path("{id:.+}")
@Produces(MediaType.TEXT_PLAIN)
@RestQuery(name = "deleterecordings", description = "Removes scheduled event with specified ID.", returnDescription = "OK if event were successfully removed or NOT FOUND if event with specified ID does not exist", pathParameters = {
@RestParameter(name = "id", isRequired = true, description = "Event ID", type = Type.STRING) }, responses = {
@RestResponse(responseCode = HttpServletResponse.SC_OK, description = "Event was successfully removed"),
@RestResponse(responseCode = HttpServletResponse.SC_NOT_FOUND, description = "Event with specified ID does not exist"),
@RestResponse(responseCode = HttpServletResponse.SC_UNAUTHORIZED, description = "You do not have permission to remove the event. Maybe you need to authenticate."),
@RestResponse(responseCode = HttpServletResponse.SC_CONFLICT, description = "Event with specified ID is locked by a transaction, unable to delete event.") })
public Response deleteEvent(@PathParam("id") String eventId) throws UnauthorizedException {
try {
service.removeEvent(eventId);
return Response.status(Response.Status.OK).build();
} catch (NotFoundException e) {
logger.info("Event with id '{}' does not exist.", eventId);
return Response.status(Status.NOT_FOUND).build();
} catch (UnauthorizedException e) {
throw e;
} catch (Exception e) {
logger.error("Unable to delete event with id '{}': {}", eventId, getMessage(e));
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
/**
* Gets the iCalendar with all (even old) events for the specified filter.
*
* @param captureAgentId
* The ID that specifies the capture agent.
* @param seriesId
* The ID that specifies series.
*
* @return an iCalendar
*/
@GET
@Produces("text/calendar")
// NOTE: charset not supported by current jaxrs impl (is ignored), set explicitly in response
@Path("calendars")
@RestQuery(name = "getcalendar", description = "Returns iCalendar for specified set of events", returnDescription = "ICalendar for events", restParameters = {
@RestParameter(name = "agentid", description = "Filter events by capture agent", isRequired = false, type = Type.STRING),
@RestParameter(name = "seriesid", description = "Filter events by series", isRequired = false, type = Type.STRING),
@RestParameter(name = "cutoff", description = "A cutoff date in UNIX milliseconds to limit the number of events returned in the calendar.", isRequired = false, type = Type.INTEGER) }, responses = {
@RestResponse(responseCode = HttpServletResponse.SC_NOT_MODIFIED, description = "Events were not modified since last request"),
@RestResponse(responseCode = HttpServletResponse.SC_OK, description = "Events were modified, new calendar is in the body") })
public Response getCalendar(@QueryParam("agentid") String captureAgentId, @QueryParam("seriesid") String seriesId,
@QueryParam("cutoff") Long cutoff, @Context HttpServletRequest request) {
Date endDate = null;
if (cutoff != null) {
try {
endDate = new Date(cutoff);
} catch (NumberFormatException e) {
return Response.status(Status.BAD_REQUEST).build();
}
}
try {
String lastModified = null;
// If the etag matches the if-not-modified header,return a 304
if (StringUtils.isNotBlank(captureAgentId)) {
lastModified = service.getScheduleLastModified(captureAgentId);
String ifNoneMatch = request.getHeader(HttpHeaders.IF_NONE_MATCH);
if (StringUtils.isNotBlank(ifNoneMatch) && ifNoneMatch.equals(lastModified)) {
return Response.notModified(lastModified).expires(null).build();
}
}
String result = service.getCalendar(Opt.nul(StringUtils.trimToNull(captureAgentId)),
Opt.nul(StringUtils.trimToNull(seriesId)), Opt.nul(endDate));
ResponseBuilder response = Response.ok(result).header(HttpHeaders.CONTENT_TYPE, "text/calendar; charset=UTF-8");
if (StringUtils.isNotBlank(lastModified))
response.header(HttpHeaders.ETAG, lastModified);
return response.build();
} catch (Exception e) {
logger.error("Unable to get calendar for capture agent '{}':", captureAgentId, e);
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
}
@GET
@Produces("application/json")
@Path("calendar.json")
@RestQuery(
name = "getCalendarJSON",
description = "Returns a calendar in JSON format for specified events. This endpoint is not yet stable and might change in the future with no priot notice.",
returnDescription = "Calendar for events in JSON format",
restParameters = {
@RestParameter(name = "agentid", description = "Filter events by capture agent", isRequired = false, type = Type.STRING),
@RestParameter(name = "cutoff", description = "A cutoff date in UNIX milliseconds to limit the number of events returned in the calendar.", isRequired = false, type = Type.INTEGER)
}, responses = {
@RestResponse(responseCode = HttpServletResponse.SC_NOT_MODIFIED, description = "Events were not modified since last request"),
@RestResponse(responseCode = HttpServletResponse.SC_OK, description = "Events were modified, new calendar is in the body")
})
public Response getCalendarJson(
@QueryParam("agentid") String captureAgentId,
@QueryParam("cutoff") Long cutoff,
@Context HttpServletRequest request) {
try {
var endDate = Optional.ofNullable(cutoff)
.map(Date::new)
.map(Opt::some)
.orElse(Opt.none());
var agent = Optional.ofNullable(captureAgentId)
.map(String::trim)
.filter(id -> !id.isEmpty())
.map(Opt::some)
.orElse(Opt.none());
String lastModified = null;
// If the `etag` matches the if-not-modified header,return a 304
if (agent.isSome()) {
lastModified = service.getScheduleLastModified(agent.get());
String ifNoneMatch = request.getHeader(HttpHeaders.IF_NONE_MATCH);
if (StringUtils.isNotBlank(ifNoneMatch) && ifNoneMatch.equals(lastModified)) {
return Response.notModified(lastModified).expires(null).build();
}
}
var result = new ArrayList