com.vmware.connectors.servicenow.ServiceNowController Maven / Gradle / Ivy
/*
* Copyright © 2017 VMware, Inc. All Rights Reserved.
* SPDX-License-Identifier: BSD-2-Clause
*/
package com.vmware.connectors.servicenow;
import com.google.common.collect.ImmutableMap;
import com.vmware.connectors.common.json.JsonDocument;
import com.vmware.connectors.common.payloads.request.CardRequest;
import com.vmware.connectors.common.payloads.response.*;
import com.vmware.connectors.common.utils.CardTextAccessor;
import com.vmware.connectors.common.utils.CommonUtils;
import com.vmware.connectors.common.utils.Reactive;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.*;
import java.util.stream.Collectors;
import static com.vmware.connectors.common.utils.CommonUtils.APPROVAL_ACTIONS;
import static org.springframework.http.HttpHeaders.AUTHORIZATION;
import static org.springframework.http.MediaType.APPLICATION_JSON;
@RestController
public class ServiceNowController {
private static final Logger logger = LoggerFactory.getLogger(ServiceNowController.class);
/**
* The JsonPath prefix for the ServiceNow results.
*/
private static final String RESULT_PREFIX = "$.result.";
private static final String AUTH_HEADER = "x-servicenow-authorization";
private static final String BASE_URL_HEADER = "x-servicenow-base-url";
private static final String ROUTING_PREFIX = "x-routing-prefix";
private static final String REASON_PARAM_KEY = "reason";
/**
* The query param to specify which fields you want to come back in your
* ServiceNow REST calls.
*/
private static final String SNOW_SYS_PARAM_FIELDS = "sysparm_fields";
/**
* The query param to specify a limit of the results coming back in your
* ServiceNow REST calls.
*
* The default is 10,000.
*/
private static final String SNOW_SYS_PARAM_LIMIT = "sysparm_limit";
/**
* The maximum approval requests to fetch from ServiceNow. Since we have
* to filter results out based on the ticket_id param passed in by the
* client, this has to be sufficiently large to not lose results.
*
* I wasn't able to find a REST call that would allow me to bulk lookup the
* approval requests (or requests) by multiple request numbers
* (ex. REQ0010001,REQ0010002,REQ0010003), so I'm forced to do things a
* little less ideal than I would like (calling 1x per result of the
* sysapproval_approver call to be able to match it to the request numbers
* passed in by the client).
*/
private static final int MAX_APPROVAL_RESULTS = 10000;
private final WebClient rest;
private final CardTextAccessor cardTextAccessor;
@Autowired
public ServiceNowController(
WebClient rest,
CardTextAccessor cardTextAccessor
) {
this.rest = rest;
this.cardTextAccessor = cardTextAccessor;
}
@PostMapping(
path = "/cards/requests",
produces = MediaType.APPLICATION_JSON_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE
)
public Mono getCards(
@RequestHeader(AUTH_HEADER) String auth,
@RequestHeader(BASE_URL_HEADER) String baseUrl,
@RequestHeader(ROUTING_PREFIX) String routingPrefix,
Locale locale,
@Valid @RequestBody CardRequest cardRequest,
final HttpServletRequest request
) {
logger.trace("getCards called, baseUrl={}, routingPrefix={}, request={}", baseUrl, routingPrefix, cardRequest);
Set requestNumbers = cardRequest.getTokens("ticket_id");
if (CollectionUtils.isEmpty(requestNumbers)) {
return Mono.just(new Cards());
}
String email = cardRequest.getTokenSingleValue("email");
if (email == null) {
return Mono.just(new Cards());
}
return callForUserSysId(baseUrl, email, auth)
.flux()
.flatMap(userSysId -> callForApprovalRequests(baseUrl, auth, userSysId))
.flatMap(approvalRequest -> callForAndAggregateRequestInfo(baseUrl, auth, approvalRequest))
.filter(info -> requestNumbers.contains(info.getInfo().getNumber()))
.flatMap(approvalRequestWithInfo -> callForAndAggregateRequestedItems(baseUrl, auth, approvalRequestWithInfo))
.reduce(
new Cards(),
(cards, info) -> appendCard(cards, info, routingPrefix, locale, request)
)
.subscriberContext(Reactive.setupContext());
}
private Mono callForUserSysId(
String baseUrl,
String email,
String auth
) {
logger.trace("callForUserSysId called: baseUrl={}", baseUrl);
return rest.get()
.uri(UriComponentsBuilder
.fromHttpUrl(baseUrl)
.path("/api/now/table/{userTableName}")
.queryParam(SNOW_SYS_PARAM_FIELDS, joinFields(SysUser.Fields.SYS_ID))
.queryParam(SNOW_SYS_PARAM_LIMIT, 1)
/*
* TODO - This is flawed. It turns out that emails do
* not have to uniquely identify users in ServiceNow.
* I am able to create 2 different sys_user records
* that have the same email.
*/
.queryParam(SysUser.Fields.EMAIL.toString(), email)
.buildAndExpand(
ImmutableMap.of(
"userTableName", SysUser.TABLE_NAME
)
)
.encode()
.toUri())
.header(AUTHORIZATION, auth)
.retrieve()
.bodyToMono(JsonDocument.class)
.flatMap(Reactive.wrapFlatMapper(userInfoResponse -> {
String userSysId = userInfoResponse.read("$.result[0]." + SysUser.Fields.SYS_ID);
if (userSysId == null) {
logger.warn("sys_id for {} not found in {}, returning empty cards", email, baseUrl);
}
return Mono.justOrEmpty(userSysId);
}));
}
private Flux callForApprovalRequests(
String baseUrl,
String auth,
String userSysId
) {
logger.trace("callForApprovalRequests called: baseUrl={}, userSysId={}", baseUrl, userSysId);
String fields = joinFields(
SysApprovalApprover.Fields.SYS_ID,
SysApprovalApprover.Fields.SYSAPPROVAL,
SysApprovalApprover.Fields.COMMENTS,
SysApprovalApprover.Fields.DUE_DATE,
SysApprovalApprover.Fields.SYS_CREATED_BY
);
return rest.get()
.uri(UriComponentsBuilder
.fromHttpUrl(baseUrl)
.path("/api/now/table/{apTableName}")
.queryParam(SNOW_SYS_PARAM_FIELDS, fields)
.queryParam(SNOW_SYS_PARAM_LIMIT, MAX_APPROVAL_RESULTS)
.queryParam(SysApprovalApprover.Fields.SOURCE_TABLE.toString(), ScRequest.TABLE_NAME)
.queryParam(SysApprovalApprover.Fields.STATE.toString(), SysApprovalApprover.States.REQUESTED)
.queryParam(SysApprovalApprover.Fields.APPROVER.toString(), userSysId)
.buildAndExpand(
ImmutableMap.of(
"apTableName", SysApprovalApprover.TABLE_NAME
)
)
.encode()
.toUri())
.header(AUTHORIZATION, auth)
.retrieve()
.bodyToMono(JsonDocument.class)
/*
* I had trouble getting JsonPath to return me something more meaningful than a List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy