
de.captaingoldfish.scim.sdk.server.utils.UriInfos Maven / Gradle / Ivy
// Generated by delombok at Thu Nov 02 20:38:53 CET 2023
package de.captaingoldfish.scim.sdk.server.utils;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import de.captaingoldfish.scim.sdk.common.utils.EncodingUtils;
import org.apache.commons.lang3.StringUtils;
import de.captaingoldfish.scim.sdk.common.constants.EndpointPaths;
import de.captaingoldfish.scim.sdk.common.constants.HttpHeader;
import de.captaingoldfish.scim.sdk.common.constants.ScimType;
import de.captaingoldfish.scim.sdk.common.constants.enums.HttpMethod;
import de.captaingoldfish.scim.sdk.common.exceptions.BadRequestException;
import de.captaingoldfish.scim.sdk.common.exceptions.InternalServerException;
import de.captaingoldfish.scim.sdk.server.schemas.ResourceType;
import de.captaingoldfish.scim.sdk.server.schemas.ResourceTypeFactory;
/**
* author Pascal Knueppel
* created at: 08.11.2019 - 22:28
*
* represents the parsed uri infos of a request
*/
public class UriInfos
{
@java.lang.SuppressWarnings("all")
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(UriInfos.class);
/**
* the resource endpoint reference e.g. "/Users" or "/Groups"
*/
private final String resourceEndpoint;
/**
* if the given request is a query POST request
*/
private final boolean searchRequest;
/**
* the base uri to this SCIM endpoint
*/
private final String baseUri;
/**
* the get parameters or the uri
*/
private final Map queryParameters;
/**
* the resource type to which the url points
*/
private final ResourceType resourceType;
/**
* the http method that was used for this request
*/
private final HttpMethod httpMethod;
// setter is necessary for bulkId resolving. This is not specified in SCIM but is added as a feature
/**
* the id of the resource for PUT, DELETE, PATCH and GET requests
*/
private String resourceId;
/**
* contains the http request headers from the client that must be validated
*/
private Map httpHeaders;
private UriInfos(String resourceEndpoint,
String resourceId,
boolean searchRequest,
String baseUri,
String queryParameters,
ResourceType resourceType,
HttpMethod httpMethod,
Map httpHeaders)
{
this.resourceEndpoint = resourceEndpoint;
this.resourceId = resourceId;
this.searchRequest = searchRequest;
this.baseUri = baseUri;
this.queryParameters = queryParameters == null ? new HashMap<>() : RequestUtils.getQueryParameters(queryParameters);
this.resourceType = resourceType;
this.httpMethod = Objects.requireNonNull(httpMethod);
validateUriInfos(resourceType);
this.httpHeaders = validateHttpHeaders(httpHeaders);
}
/**
* resolves the request uri to individual information's that are necessary to resolve the request
*
* @param requestUrl the fully qualified request url
* @param httpHeaders the http request headers
* @return the individual request information's
*/
public static UriInfos getRequestUrlInfos(ResourceTypeFactory resourceTypeFactory,
String requestUrl,
HttpMethod httpMethod,
Map httpHeaders)
{
final URL url = toUrl(requestUrl);
final String[] pathParts = url.getPath().split("/");
final ResourceType resourceType = getResourceType(resourceTypeFactory, pathParts);
if (isBulkRequest(httpMethod, resourceType))
{
return UriInfos.builder()
.baseUri(StringUtils.substringBeforeLast(requestUrl, EndpointPaths.BULK))
.resourceEndpoint(EndpointPaths.BULK)
.httpMethod(httpMethod)
.httpHeaders(httpHeaders)
.build();
}
final boolean endsOfSearch = EndpointPaths.SEARCH.endsWith(pathParts[pathParts.length - 1]);
final boolean endsOfResource = resourceType.getEndpoint().endsWith(pathParts[pathParts.length - 1]);
final String resourceId = endsOfSearch ? null : (endsOfResource ? null : pathParts[pathParts.length - 1]);
final String decodedResourceId = EncodingUtils.urlDecode(resourceId);
final boolean searchRequest = endsOfSearch && HttpMethod.POST.equals(httpMethod)
|| HttpMethod.GET.equals(httpMethod) && resourceId == null;
final String baseUri = StringUtils.substringBeforeLast(requestUrl, resourceType.getEndpoint());
UriInfos uriInfos = UriInfos.builder()
.baseUri(baseUri)
.searchRequest(searchRequest)
.resourceEndpoint(resourceType.getEndpoint())
.resourceId(decodedResourceId)
.queryParameters(url.getQuery())
.resourceType(resourceType)
.httpMethod(httpMethod)
.httpHeaders(httpHeaders)
.build();
return uriInfos;
}
/**
* checks if we got a bulk request
*
* @param httpMethod the http method must be post for bulk requests
* @param resourceType the resource type must be null. There are no resource types registered for bulk
* @return true if this is a bulk request, false else
*/
private static boolean isBulkRequest(HttpMethod httpMethod, ResourceType resourceType)
{
if (resourceType == null) // this is only null if the request is a bulk request
{
if (HttpMethod.POST.equals(httpMethod))
{
return true;
}
else
{
throw new BadRequestException("Bulk endpoint can only be reached with a HTTP-POST request", null, null);
}
}
return false;
}
/**
* will get the resource type to which the request url points
*
* @param urlParts the request url parts separated by "/"
* @return the found resource type or null if the request points to the bulk-endpoint
* @throws BadRequestException if the request does neither point to the bulk endpoint nor a registered
* resource type
*/
private static ResourceType getResourceType(ResourceTypeFactory resourceTypeFactory, String[] urlParts)
{
if (EndpointPaths.BULK.endsWith(urlParts[urlParts.length - 1]))
{
return null;
}
final String nextToLastPathPart = "/" + urlParts[urlParts.length - 2];
final String lastPathPart = "/" + urlParts[urlParts.length - 1];
ResourceType resourceType = Optional.ofNullable(resourceTypeFactory.getResourceType(nextToLastPathPart))
.orElse(resourceTypeFactory.getResourceType(lastPathPart));
if (resourceType != null)
{
return resourceType;
}
throw new BadRequestException(String.format("the request url \'%s\' does not point to a registered resource type. "
+ "Registered resource types are: [%s]",
String.join("/", urlParts),
resourceTypeFactory.getAllResourceTypes()
.stream()
.map(ResourceType::getEndpoint)
.collect(Collectors.joining(","))),
null, ScimType.Custom.INVALID_PARAMETERS);
}
/**
* turns the given string into an {@link URL} object
*
* @param url the url string
* @return the {@link URL} instance of the given string
*/
private static URL toUrl(String url)
{
try
{
return new URL(url);
}
catch (MalformedURLException e)
{
throw new InternalServerException(e.getMessage(), e, null);
}
}
/**
* this method will validate the request headers sent by the client. These headers may also be used in the
* following processing if the service provider supports entity tags
*
* @param httpHeaders the http headers sent by the client
* @return the validated map
*/
private Map validateHttpHeaders(Map httpHeaders)
{
if (httpHeaders == null)
{
throw new InternalServerException("missing http headers. This is not a client error!", null, null);
}
if (httpHeaders.get(EndpointPaths.BULK) != null && httpHeaders.size() <= 2)
{
// in this case this method was called from the bulk endpoint and further validation is skipped
// this is done because the original http headers have already been validated and the sub-operations of the
// bulk-request do not need to be validated
httpHeaders.remove(EndpointPaths.BULK);
return httpHeaders;
}
String contentType = httpHeaders.keySet()
.stream()
.filter(header -> StringUtils.equalsIgnoreCase(header,
HttpHeader.CONTENT_TYPE_HEADER))
.findAny()
.map(httpHeaders::get)
.orElse(null);
if ((HttpMethod.POST.equals(httpMethod) || HttpMethod.PUT.equals(httpMethod) || HttpMethod.PATCH.equals(httpMethod))
&& (contentType == null || !StringUtils.startsWith(contentType, HttpHeader.SCIM_CONTENT_TYPE)))
{
throw new BadRequestException(String.format("Invalid content type. Was \'%s\' but should be %s",
contentType,
HttpHeader.SCIM_CONTENT_TYPE),
null, null);
}
// other headers do not need to be validated currently
return httpHeaders;
}
/**
* this method will verify that the parsed data of the request is valid for accessing SCIM endpoint
*
* @param resourceType used to allow empty path ids on singleton endpoints
*/
private void validateUriInfos(ResourceType resourceType)
{
switch (httpMethod)
{
case POST:
if (StringUtils.isNotBlank(getResourceId()))
{
throw new BadRequestException("ID values in the path are not allowed on \'" + httpMethod + "\' requests",
null, ScimType.Custom.INVALID_PARAMETERS);
}
break;
case PUT:
case PATCH:
case DELETE:
if (!resourceType.getFeatures().isSingletonEndpoint() && StringUtils.isBlank(getResourceId()))
{
throw new BadRequestException("missing ID value in request path for method \'" + httpMethod + "\'", null,
ScimType.Custom.INVALID_PARAMETERS);
}
}
}
@Override
public String toString()
{
return baseUri + resourceEndpoint + (StringUtils.isBlank(resourceId) ? "" : "/" + resourceId);
}
@java.lang.SuppressWarnings("all")
public static class UriInfosBuilder
{
@java.lang.SuppressWarnings("all")
private String resourceEndpoint;
@java.lang.SuppressWarnings("all")
private String resourceId;
@java.lang.SuppressWarnings("all")
private boolean searchRequest;
@java.lang.SuppressWarnings("all")
private String baseUri;
@java.lang.SuppressWarnings("all")
private String queryParameters;
@java.lang.SuppressWarnings("all")
private ResourceType resourceType;
@java.lang.SuppressWarnings("all")
private HttpMethod httpMethod;
@java.lang.SuppressWarnings("all")
private Map httpHeaders;
@java.lang.SuppressWarnings("all")
UriInfosBuilder()
{}
/**
* @return {@code this}.
*/
@java.lang.SuppressWarnings("all")
public UriInfos.UriInfosBuilder resourceEndpoint(final String resourceEndpoint)
{
this.resourceEndpoint = resourceEndpoint;
return this;
}
/**
* @return {@code this}.
*/
@java.lang.SuppressWarnings("all")
public UriInfos.UriInfosBuilder resourceId(final String resourceId)
{
this.resourceId = resourceId;
return this;
}
/**
* @return {@code this}.
*/
@java.lang.SuppressWarnings("all")
public UriInfos.UriInfosBuilder searchRequest(final boolean searchRequest)
{
this.searchRequest = searchRequest;
return this;
}
/**
* @return {@code this}.
*/
@java.lang.SuppressWarnings("all")
public UriInfos.UriInfosBuilder baseUri(final String baseUri)
{
this.baseUri = baseUri;
return this;
}
/**
* @return {@code this}.
*/
@java.lang.SuppressWarnings("all")
public UriInfos.UriInfosBuilder queryParameters(final String queryParameters)
{
this.queryParameters = queryParameters;
return this;
}
/**
* @return {@code this}.
*/
@java.lang.SuppressWarnings("all")
public UriInfos.UriInfosBuilder resourceType(final ResourceType resourceType)
{
this.resourceType = resourceType;
return this;
}
/**
* @return {@code this}.
*/
@java.lang.SuppressWarnings("all")
public UriInfos.UriInfosBuilder httpMethod(final HttpMethod httpMethod)
{
this.httpMethod = httpMethod;
return this;
}
/**
* @return {@code this}.
*/
@java.lang.SuppressWarnings("all")
public UriInfos.UriInfosBuilder httpHeaders(final Map httpHeaders)
{
this.httpHeaders = httpHeaders;
return this;
}
@java.lang.SuppressWarnings("all")
public UriInfos build()
{
return new UriInfos(this.resourceEndpoint, this.resourceId, this.searchRequest, this.baseUri,
this.queryParameters, this.resourceType, this.httpMethod, this.httpHeaders);
}
@java.lang.Override
@java.lang.SuppressWarnings("all")
public java.lang.String toString()
{
return "UriInfos.UriInfosBuilder(resourceEndpoint=" + this.resourceEndpoint + ", resourceId=" + this.resourceId
+ ", searchRequest=" + this.searchRequest + ", baseUri=" + this.baseUri + ", queryParameters="
+ this.queryParameters + ", resourceType=" + this.resourceType + ", httpMethod=" + this.httpMethod
+ ", httpHeaders=" + this.httpHeaders + ")";
}
}
@java.lang.SuppressWarnings("all")
public static UriInfos.UriInfosBuilder builder()
{
return new UriInfos.UriInfosBuilder();
}
/**
* the resource endpoint reference e.g. "/Users" or "/Groups"
*/
@java.lang.SuppressWarnings("all")
public String getResourceEndpoint()
{
return this.resourceEndpoint;
}
/**
* if the given request is a query POST request
*/
@java.lang.SuppressWarnings("all")
public boolean isSearchRequest()
{
return this.searchRequest;
}
/**
* the base uri to this SCIM endpoint
*/
@java.lang.SuppressWarnings("all")
public String getBaseUri()
{
return this.baseUri;
}
/**
* the get parameters or the uri
*/
@java.lang.SuppressWarnings("all")
public Map getQueryParameters()
{
return this.queryParameters;
}
/**
* the resource type to which the url points
*/
@java.lang.SuppressWarnings("all")
public ResourceType getResourceType()
{
return this.resourceType;
}
/**
* the http method that was used for this request
*/
@java.lang.SuppressWarnings("all")
public HttpMethod getHttpMethod()
{
return this.httpMethod;
}
/**
* the id of the resource for PUT, DELETE, PATCH and GET requests
*/
@java.lang.SuppressWarnings("all")
public String getResourceId()
{
return this.resourceId;
}
/**
* contains the http request headers from the client that must be validated
*/
@java.lang.SuppressWarnings("all")
public Map getHttpHeaders()
{
return this.httpHeaders;
}
/**
* the id of the resource for PUT, DELETE, PATCH and GET requests
*/
@java.lang.SuppressWarnings("all")
public void setResourceId(final String resourceId)
{
this.resourceId = resourceId;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy