/*
* Copyright (c) 2016 EMC Corporation
* All Rights Reserved
*/
package com.emc.sa.api;
import static com.emc.sa.api.mapper.ScheduledEventMapper.*;
import static com.emc.storageos.api.mapper.DbObjectMapper.toNamedRelatedResource;
import static com.emc.storageos.db.client.URIUtil.asString;
import static com.emc.storageos.db.client.URIUtil.uri;
import java.net.URI;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import com.emc.sa.model.dao.ModelClient;
import com.emc.storageos.db.client.constraint.AlternateIdConstraint;
import com.emc.storageos.db.client.util.ExecutionWindowHelper;
import com.emc.sa.model.util.ScheduleTimeHelper;
import com.emc.storageos.db.client.URIUtil;
import com.emc.storageos.db.client.constraint.ContainmentConstraint;
import com.emc.storageos.db.client.constraint.URIQueryResultList;
import com.emc.storageos.db.client.model.NamedURI;
import com.emc.storageos.db.client.model.Volume;
import com.emc.storageos.svcs.errorhandling.resources.BadRequestException;
import org.apache.commons.codec.binary.*;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import com.emc.sa.api.utils.ValidationUtils;
import com.emc.sa.catalog.CatalogServiceManager;
import com.emc.sa.descriptor.*;
import com.emc.sa.util.TextUtils;
import com.emc.storageos.api.service.authorization.PermissionsHelper;
import com.emc.storageos.api.service.impl.resource.ArgValidator;
import com.emc.storageos.api.service.impl.response.ResRepFilter;
import com.emc.storageos.api.service.impl.response.RestLinkFactory;
import com.emc.storageos.db.client.model.EncryptionProvider;
import com.emc.storageos.db.client.model.uimodels.*;
import com.emc.storageos.db.exceptions.DatabaseException;
import com.emc.storageos.model.*;
import com.emc.storageos.model.search.SearchResultResourceRep;
import com.emc.storageos.model.search.SearchResults;
import com.emc.storageos.security.authentication.StorageOSUser;
import com.emc.storageos.security.authorization.CheckPermission;
import com.emc.storageos.security.authorization.DefaultPermissions;
import com.emc.storageos.security.authorization.Role;
import com.emc.storageos.services.OperationTypeEnum;
import com.emc.storageos.svcs.errorhandling.resources.APIException;
import com.emc.storageos.volumecontroller.impl.monitoring.RecordableEventManager;
import com.emc.vipr.client.catalog.impl.SearchConstants;
import com.emc.vipr.model.catalog.*;
import com.google.common.collect.Lists;
import com.emc.sa.api.OrderService;
@DefaultPermissions(
readRoles = {},
writeRoles = {})
@Path("/catalog/events")
public class ScheduledEventService extends CatalogTaggedResourceService {
private static final Logger log = LoggerFactory.getLogger(ScheduledEventService.class);
private static Charset UTF_8 = Charset.forName("UTF-8");
private static final String EVENT_SERVICE_TYPE = "catalog-event";
// Specific workaround for VMAX3 Snapshot session:
// If a snapshot session is connected with any target, it should not be deleted for avoiding DU.
// For scheduler, the recurrence event with retention policy could not be fulfilled.
public String LINKED_SNAPSHOT_NAME = "linkedSnapshotName";
@Autowired
private ModelClient client;
@Autowired
private CatalogServiceManager catalogServiceManager;
@Autowired
private OrderService orderService;
@Override
protected ResourceTypeEnum getResourceType() {
return ResourceTypeEnum.SCHEDULED_EVENT;
}
@Override
public String getServiceType() {
return EVENT_SERVICE_TYPE;
}
/**
* Query scheduled event resource via its URI.
* @param id scheduled event URI
* @return ScheduledEvent
*/
@Override
protected ScheduledEvent queryResource(URI id) {
return getScheduledEventById(id, false);
}
/**
* Get tenant owner of scheduled event
* @param id scheduled event URI
* @return URI of the owner tenant
*/
@Override
protected URI getTenantOwner(URI id) {
ScheduledEvent event = queryResource(id);
return uri(event.getTenant());
}
@SuppressWarnings("unchecked")
@Override
public Class<ScheduledEvent> getResourceClass() {
return ScheduledEvent.class;
}
/**
* Create a scheduled event for one or a series of future orders.
* Also a latest order is created and set to APPROVAL or SCHEDULED status
* @param createParam including schedule time info and order parameters
* @return ScheduledEventRestRep
*/
@POST
@Path("")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public ScheduledEventRestRep createEvent(ScheduledEventCreateParam createParam) {
StorageOSUser user = getUserFromContext();
URI tenantId = createParam.getOrderCreateParam().getTenantId();
if (tenantId != null) {
verifyAuthorizedInTenantOrg(tenantId, user);
}
else {
tenantId = uri(user.getTenantId());
}
ArgValidator.checkFieldNotNull(createParam.getOrderCreateParam().getCatalogService(), "catalogService");
CatalogService catalogService = catalogServiceManager.getCatalogServiceById(createParam.getOrderCreateParam().getCatalogService());
if (catalogService == null) {
throw APIException.badRequests.orderServiceNotFound(
asString(createParam.getOrderCreateParam().getCatalogService()));
}
validateParam(createParam.getScheduleInfo());
validOrderParam(createParam.getScheduleInfo(), createParam.getOrderCreateParam().getParameters());
validateAutomaticExpirationNumber(createParam.getOrderCreateParam().getAdditionalScheduleInfo());
ScheduledEvent newObject = null;
try {
newObject = createScheduledEvent(user, tenantId, createParam, catalogService);
} catch (APIException ex){
log.error(ex.getMessage(), ex);
throw ex;
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return map(newObject);
}
/**
* Validate automatic expiration number has to be in range [1, 256], if user input it.
*/
private void validateAutomaticExpirationNumber(String expiration) {
if (expiration == null) {
return;
}
try {
int expNum = Integer.parseInt(expiration);
if (expNum < 1 || expNum > 256) {
throw APIException.badRequests.schduleInfoInvalid("automatic expiration number");
}
} catch (Exception e) {
throw APIException.badRequests.schduleInfoInvalid("automatic expiration number");
}
}
/**
* Validate schedule time info related parameters.
* Order related parameters would be verified later in order creation part.
* @param scheduleInfo Schedule Schema
*/
private void validateParam(ScheduleInfo scheduleInfo) {
DateFormat formatter = new SimpleDateFormat(ScheduleInfo.FULL_DAY_FORMAT);;
Date date = null;
try {
date = formatter.parse(scheduleInfo.getStartDate());
} catch (Exception e) {
throw APIException.badRequests.schduleInfoInvalid(ScheduleInfo.START_DATE);
}
if (scheduleInfo.getHourOfDay() < 0 || scheduleInfo.getHourOfDay() > 23) {
throw APIException.badRequests.schduleInfoInvalid(ScheduleInfo.HOUR_OF_DAY);
}
if (scheduleInfo.getMinuteOfHour() < 0 || scheduleInfo.getMinuteOfHour() > 59) {
throw APIException.badRequests.schduleInfoInvalid(ScheduleInfo.MINUTE_OF_HOUR);
}
/* TODO: enable it later when we support customized duration
if (scheduleInfo.getDurationLength() < 1 || scheduleInfo.getHourOfDay() > 60*24) {
throw APIException.badRequests.schduleInfoInvalid(ScheduleInfo.DURATION_LENGTH);
}
*/
Calendar currTime, endTime;
currTime = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
if (scheduleInfo.getReoccurrence() < 0) {
throw APIException.badRequests.schduleInfoInvalid(ScheduleInfo.REOCCURRENCE);
} else if (scheduleInfo.getReoccurrence() == 1) {
try {
Calendar startTime = ScheduleTimeHelper.getScheduledStartTime(scheduleInfo);
if (startTime == null || currTime.after(startTime)) {
throw APIException.badRequests.schduleInfoInvalid(ScheduleInfo.START_DATE);
}
} catch (Exception e) {
throw APIException.badRequests.schduleInfoInvalid(ScheduleInfo.START_DATE);
}
return;
} else if (scheduleInfo.getReoccurrence() > ScheduleInfo.MAX_REOCCURRENCE ) {
throw APIException.badRequests.schduleInfoInvalid(ScheduleInfo.REOCCURRENCE);
}
if (scheduleInfo.getCycleFrequency() < 1
|| scheduleInfo.getCycleFrequency() > ScheduleInfo.MAX_CYCLE_FREQUENCE ) {
throw APIException.badRequests.schduleInfoInvalid(ScheduleInfo.CYCLE_FREQUENCE);
}
try {
endTime = ScheduleTimeHelper.getScheduledEndTime(scheduleInfo);
if (endTime != null && currTime.after(endTime)) {
throw APIException.badRequests.schduleInfoInvalid(ScheduleInfo.END_DATE);
}
} catch (Exception e) {
throw APIException.badRequests.schduleInfoInvalid(ScheduleInfo.END_DATE);
}
switch (scheduleInfo.getCycleType()) {
case MONTHLY:
if (scheduleInfo.getSectionsInCycle() == null || scheduleInfo.getSectionsInCycle().size() != 1) {
throw APIException.badRequests.schduleInfoInvalid(ScheduleInfo.SECTIONS_IN_CYCLE);
}
int day = Integer.valueOf(scheduleInfo.getSectionsInCycle().get(0));
if (day < 1 || day > 31) {
throw APIException.badRequests.schduleInfoInvalid(ScheduleInfo.SECTIONS_IN_CYCLE);
}
break;
case WEEKLY:
if (scheduleInfo.getSectionsInCycle() == null || scheduleInfo.getSectionsInCycle().size() != 1) {
throw APIException.badRequests.schduleInfoInvalid(ScheduleInfo.SECTIONS_IN_CYCLE);
}
int dayOfWeek = Integer.valueOf(scheduleInfo.getSectionsInCycle().get(0));
if (dayOfWeek < 1 || dayOfWeek > 7) {
throw APIException.badRequests.schduleInfoInvalid(ScheduleInfo.SECTIONS_IN_CYCLE);
}
break;
case DAILY:
case HOURLY:
case MINUTELY:
if (scheduleInfo.getSectionsInCycle() != null && !scheduleInfo.getSectionsInCycle().isEmpty()) {
throw APIException.badRequests.schduleInfoInvalid(ScheduleInfo.SECTIONS_IN_CYCLE);
}
break;
default:
throw APIException.badRequests.schduleInfoInvalid(ScheduleInfo.CYCLE_TYPE);
}
if (scheduleInfo.getDateExceptions() != null) {
for (String dateException: scheduleInfo.getDateExceptions()) {
try {
date = formatter.parse(dateException);
} catch (Exception e) {
throw APIException.badRequests.schduleInfoInvalid(ScheduleInfo.DATE_EXCEPTIONS);
}
}
}
return;
}
/**
* Check if schedule time info is matched with the desired execution window set by admin.
* @param scheduleInfo schedule time info
* @param window desired execution window set by admin
* @return empty for matching, otherwise including detail unmatched reason.
*/
private String match(ScheduleInfo scheduleInfo, ExecutionWindow window) {
String msg="";
ExecutionWindowHelper windowHelper = new ExecutionWindowHelper(window);
if (!windowHelper.inHourMinWindow(scheduleInfo.getHourOfDay(), scheduleInfo.getMinuteOfHour())) {
msg = "Schedule hour/minute info does not match with execution window.";
return msg;
}
if (scheduleInfo.getReoccurrence() == 1)
return msg;
switch (scheduleInfo.getCycleType()) {
case MINUTELY:
case HOURLY:
log.warn("Not all of the orders would be scheduled due to schedule cycle type {}", scheduleInfo.getCycleType());
break;
case DAILY:
if (!window.getExecutionWindowType().equals(ExecutionWindowType.DAILY.name())) {
msg = "Schedule cycle type has conflicts with execution window.";
}
break;
case WEEKLY:
if (window.getExecutionWindowType().equals(ExecutionWindowType.MONTHLY.name())) {
msg = "Schedule cycle type has conflicts with execution window.";
} else if (window.getExecutionWindowType().equals(ExecutionWindowType.WEEKLY.name())) {
if (window.getDayOfWeek() != Integer.valueOf(scheduleInfo.getSectionsInCycle().get(0))) {
msg = "Scheduled date has conflicts with execution window.";
}
}
break;
case MONTHLY:
if (window.getExecutionWindowType().equals(ExecutionWindowType.WEEKLY.name())) {
msg = "Schedule cycle type has conflicts with execution window.";
} else if (window.getExecutionWindowType().equals(ExecutionWindowType.MONTHLY.name())) {
if (window.getDayOfMonth() != Integer.valueOf(scheduleInfo.getSectionsInCycle().get(0))) {
msg = "Scheduled date has conflicts with execution window.";
}
}
break;
default:
log.error("not expected schedule cycle.");
}
return msg;
}
/**
* Internal main function to create scheduled event.
* @param tenantId owner tenant Id
* @param param scheduled event creation param
* @param catalogService target catalog service
* @return ScheduledEvent
* @throws Exception
*/
private ScheduledEvent createScheduledEvent(StorageOSUser user, URI tenantId, ScheduledEventCreateParam param, CatalogService catalogService) throws Exception{
URI executionWindow = null; // INFINITE execution window
if (catalogService.getExecutionWindowRequired()) {
if (catalogService.getDefaultExecutionWindowId() == null ||
catalogService.getDefaultExecutionWindowId().equals(ExecutionWindow.NEXT)) {
List<URI> executionWindows =
_dbClient.queryByConstraint(AlternateIdConstraint.Factory.getExecutionWindowTenantIdIdConstraint(tenantId.toString()));
Calendar currTime = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
executionWindow = getNextExecutionWindow(executionWindows, currTime);
} else {
executionWindow = catalogService.getDefaultExecutionWindowId().getURI();
}
ExecutionWindow window = client.findById(executionWindow);
String msg = match(param.getScheduleInfo(), window);
if (!msg.isEmpty()) {
throw APIException.badRequests.scheduleInfoNotMatchWithExecutionWindow(msg);
}
}
URI scheduledEventId = URIUtil.createId(ScheduledEvent.class);
param.getOrderCreateParam().setScheduledEventId(scheduledEventId);
Calendar scheduledTime = ScheduleTimeHelper.getFirstScheduledTime(param.getScheduleInfo());
param.getOrderCreateParam().setScheduledTime(ScheduleTimeHelper.convertCalendarToStr(scheduledTime));
param.getOrderCreateParam().setExecutionWindow(executionWindow);
OrderRestRep restRep = orderService.createOrder(param.getOrderCreateParam());
ScheduledEvent newObject = new ScheduledEvent();
newObject.setId(scheduledEventId);
newObject.setTenant(tenantId.toString());
newObject.setCatalogServiceId(param.getOrderCreateParam().getCatalogService());
newObject.setEventType(param.getScheduleInfo().getReoccurrence() == 1 ? ScheduledEventType.ONCE : ScheduledEventType.REOCCURRENCE);
if (catalogService.getApprovalRequired()) {
log.info(String.format("ScheduledEventr %s requires approval", newObject.getId()));
newObject.setEventStatus(ScheduledEventStatus.APPROVAL);
} else {
newObject.setEventStatus(ScheduledEventStatus.APPROVED);
}
newObject.setScheduleInfo(new String(org.apache.commons.codec.binary.Base64.encodeBase64(param.getScheduleInfo().serialize()), UTF_8));
if (executionWindow != null) {
newObject.setExecutionWindowId(new NamedURI(executionWindow, "ExecutionWindow"));
}
newObject.setLatestOrderId(restRep.getId());
newObject.setOrderCreationParam(new String(org.apache.commons.codec.binary.Base64.encodeBase64(param.getOrderCreateParam().serialize()), UTF_8));
newObject.setStorageOSUser(new String(org.apache.commons.codec.binary.Base64.encodeBase64(user.serialize()), UTF_8));
client.save(newObject);
log.info("Created a new scheduledEvent {}:{}", newObject.getId(),param.getScheduleInfo().toString());
return newObject;
}
/**
* Get a scheduled event via its URI
* @param id target schedule event URI
* @return ScheduledEventRestRep
*/
@GET
@Path("/{id}")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public ScheduledEventRestRep getScheduledEvent(@PathParam("id") String id) {
ScheduledEvent scheduledEvent = queryResource(uri(id));
StorageOSUser user = getUserFromContext();
verifyAuthorizedInTenantOrg(uri(scheduledEvent.getTenant()), user);
try {
log.info("Fetched a scheduledEvent {}:{}", scheduledEvent.getId(),
ScheduleInfo.deserialize(org.apache.commons.codec.binary.Base64.decodeBase64(scheduledEvent.getScheduleInfo().getBytes(UTF_8))).toString());
} catch (Exception e) {
log.error("Failed to parse scheduledEvent.");
}
return map(scheduledEvent);
}
private ScheduledEvent getScheduledEventById(URI id, boolean checkInactive) {
ScheduledEvent scheduledEvent = client.scheduledEvents().findById(id);
ArgValidator.checkEntity(scheduledEvent, id, isIdEmbeddedInURL(id), checkInactive);
return scheduledEvent;
}
/**
* Update a scheduled event for one or a series of future orders.
* @param updateParam including schedule time info
* @return ScheduledEventRestRep
*/
@PUT
@Path("/{id}")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public ScheduledEventRestRep updateEvent(@PathParam("id") String id, ScheduledEventUpdateParam updateParam) {
ScheduledEvent scheduledEvent = queryResource(uri(id));
ArgValidator.checkEntity(scheduledEvent, uri(id), true);
validateParam(updateParam.getScheduleInfo());
try {
OrderCreateParam orderCreateParam = OrderCreateParam.deserialize(org.apache.commons.codec.binary.Base64.decodeBase64(scheduledEvent.getOrderCreationParam().getBytes(UTF_8)));
validateAutomaticExpirationNumber(updateParam.getAdditionalScheduleInfo());
orderCreateParam.setAdditionalScheduleInfo(updateParam.getAdditionalScheduleInfo());
scheduledEvent.setOrderCreationParam(new String(org.apache.commons.codec.binary.Base64.encodeBase64(orderCreateParam.serialize()), UTF_8));
updateScheduledEvent(scheduledEvent, updateParam.getScheduleInfo());
} catch (APIException ex){
log.error(ex.getMessage(), ex);
throw ex;
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return map(scheduledEvent);
}
/**
* Internal main function to update scheduled event.
* @param scheduledEvent target scheduled event
* @param scheduleInfo target schedule schema
* @return updated scheduledEvent
* @throws Exception
*/
private ScheduledEvent updateScheduledEvent(ScheduledEvent scheduledEvent, ScheduleInfo scheduleInfo) throws Exception{
URI executionWindow = null; // INFINITE execution window
CatalogService catalogService = catalogServiceManager.getCatalogServiceById(scheduledEvent.getCatalogServiceId());
if (catalogService.getExecutionWindowRequired()) {
if (catalogService.getDefaultExecutionWindowId() == null ||
catalogService.getDefaultExecutionWindowId().equals(ExecutionWindow.NEXT)) {
List<URI> executionWindows =
_dbClient.queryByConstraint(AlternateIdConstraint.Factory.getExecutionWindowTenantIdIdConstraint(scheduledEvent.getTenant()));
Calendar currTime = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
executionWindow = getNextExecutionWindow(executionWindows, currTime);
} else {
executionWindow = catalogService.getDefaultExecutionWindowId().getURI();
}
ExecutionWindow window = client.findById(executionWindow);
String msg = match(scheduleInfo, window);
if (!msg.isEmpty()) {
throw APIException.badRequests.scheduleInfoNotMatchWithExecutionWindow(msg);
}
}
Order order = client.orders().findById(scheduledEvent.getLatestOrderId());
Calendar scheduledTime = ScheduleTimeHelper.getFirstScheduledTime(scheduleInfo);
order.setScheduledTime(scheduledTime);
client.save(order);
// TODO: update execution window when admin change it in catalog service
scheduledEvent.setScheduleInfo(new String(org.apache.commons.codec.binary.Base64.encodeBase64(scheduleInfo.serialize()), UTF_8));
scheduledEvent.setEventType(scheduleInfo.getReoccurrence() == 1? ScheduledEventType.ONCE:ScheduledEventType.REOCCURRENCE);
client.save(scheduledEvent);
log.info("Updated a scheduledEvent {}:{}", scheduledEvent.getId(), scheduleInfo.toString());
return scheduledEvent;
}
/**
* Cancel a scheduled event which should be in APPROVAL or APPROVED status.
* @param id Scheduled Event URI
* @return OK if cancellation completed successfully
*/
@POST
@Path("/{id}/cancel")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Response cancelScheduledEvent(@PathParam("id") String id) {
ScheduledEvent scheduledEvent = queryResource(uri(id));
ArgValidator.checkEntity(scheduledEvent, uri(id), true);
StorageOSUser user = getUserFromContext();
verifyAuthorizedInTenantOrg(uri(scheduledEvent.getTenant()), user);
if(! (scheduledEvent.getEventStatus().equals(ScheduledEventStatus.APPROVAL) ||
scheduledEvent.getEventStatus().equals(ScheduledEventStatus.APPROVED) ||
scheduledEvent.getEventStatus().equals(ScheduledEventStatus.REJECTED)) ) {
throw APIException.badRequests.unexpectedValueForProperty(ScheduledEvent.EVENT_STATUS, "APPROVAL|APPROVED|REJECTED",
scheduledEvent.getEventStatus().name());
}
Order order = client.orders().findById(scheduledEvent.getLatestOrderId());
ArgValidator.checkEntity(order, uri(id), true);
order.setOrderStatus(OrderStatus.CANCELLED.name());
client.save(order);
scheduledEvent.setEventStatus(ScheduledEventStatus.CANCELLED);
client.save(scheduledEvent);
try {
log.info("Cancelled a scheduledEvent {}:{}", scheduledEvent.getId(),
ScheduleInfo.deserialize(org.apache.commons.codec.binary.Base64.decodeBase64(scheduledEvent.getScheduleInfo().getBytes(UTF_8))).toString());
} catch (Exception e) {
log.error("Failed to parse scheduledEvent.");
}
return Response.ok().build();
}
/**
* Deactivates the scheduled event and its orders
*
* @param id the URN of a scheduled event to be deactivated
* @return OK if deactivation completed successfully
* @throws DatabaseException when a DB error occurs
*/
@POST
@Path("/{id}/deactivate")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.TENANT_ADMIN })
public Response deactivateScheduledEvent(@PathParam("id") String id) throws DatabaseException {
ScheduledEvent scheduledEvent = queryResource(uri(id));
ArgValidator.checkEntity(scheduledEvent, uri(id), true);
// deactivate all the orders from the scheduled event
URIQueryResultList resultList = new URIQueryResultList();
_dbClient.queryByConstraint(
ContainmentConstraint.Factory.getScheduledEventOrderConstraint(uri(id)), resultList);
for (URI uri : resultList) {
log.info("deleting order: {}", uri);
Order order = _dbClient.queryObject(Order.class, uri);
client.delete(order);
}
try {
log.info("Deleting a scheduledEvent {}:{}", scheduledEvent.getId(),
ScheduleInfo.deserialize(org.apache.commons.codec.binary.Base64.decodeBase64(scheduledEvent.getScheduleInfo().getBytes(UTF_8))).toString());
} catch (Exception e) {
log.error("Failed to parse scheduledEvent.");
}
// deactivate the scheduled event
client.delete(scheduledEvent);
return Response.ok().build();
}
public URI getNextExecutionWindow(Collection<URI> windows, Calendar time) {
Calendar nextWindowTime = null;
URI nextWindow = null;
for (URI window : windows) {
ExecutionWindow executionWindow = _dbClient.queryObject(ExecutionWindow.class, window);
if (executionWindow == null) continue;
ExecutionWindowHelper helper = new ExecutionWindowHelper(executionWindow);
Calendar windowTime = helper.calculateCurrentOrNext(time);
if (nextWindowTime == null || nextWindowTime.after(windowTime)) {
nextWindowTime = windowTime;
nextWindow = window;
}
}
return nextWindow;
}
public void validOrderParam(ScheduleInfo scheduleInfo, List<Parameter> parameters) {
if (scheduleInfo.getReoccurrence() != 1) {
for (Parameter param: parameters) {
if (param.getLabel().equals(LINKED_SNAPSHOT_NAME)) {
if (param.getValue() != null) {
String snapshotName = param.getValue();
if (!(snapshotName.isEmpty() || snapshotName.equals("\"\""))) {
throw APIException.badRequests.scheduleInfoNotAllowedWithSnapshotSessionTarget();
}
}
}
}
}
}
}