blob: 56ef5726e68263025d46f2d1a6ef24c851574ad4 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache 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://www.apache.org/licenses/LICENSE-2.0
* 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.apache.directory.scim.client.rest;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import jakarta.ws.rs.ProcessingException;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.client.Invocation;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.GenericType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import org.apache.directory.scim.protocol.adapter.FilterWrapper;
import org.apache.directory.scim.spec.annotation.ScimResourceType;
import org.apache.directory.scim.protocol.BaseResourceTypeResource;
import org.apache.directory.scim.protocol.Constants;
import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
import org.apache.directory.scim.spec.filter.attribute.AttributeReferenceListWrapper;
import org.apache.directory.scim.protocol.data.ErrorResponse;
import org.apache.directory.scim.protocol.data.ListResponse;
import org.apache.directory.scim.protocol.data.PatchRequest;
import org.apache.directory.scim.protocol.data.SearchRequest;
import org.apache.directory.scim.protocol.exception.ScimException;
import org.apache.directory.scim.spec.filter.Filter;
import org.apache.directory.scim.spec.filter.SortOrder;
import org.apache.directory.scim.spec.resources.ScimResource;
public abstract class BaseScimClient<T extends ScimResource> implements AutoCloseable {
static final String ATTRIBUTES_QUERY_PARAM = "attributes";
static final String EXCLUDED_ATTRIBUTES_QUERY_PARAM = "excludedAttributes";
private final Client client;
private final Class<T> scimResourceClass;
private final GenericType<ListResponse<T>> scimResourceListResponseGenericType;
private final WebTarget target;
private final InternalScimClient scimClient;
private RestCall invoke = Invocation::invoke;
public BaseScimClient(Client client, String baseUrl, Class<T> scimResourceClass, GenericType<ListResponse<T>> scimResourceListGenericType) {
ScimResourceType scimResourceType = scimResourceClass.getAnnotation(ScimResourceType.class);
String endpoint = scimResourceType != null ? scimResourceType.endpoint() : null;
if (endpoint == null) {
throw new IllegalArgumentException("scimResourceClass: " + scimResourceClass.getSimpleName() + " must have annotation " + ScimResourceType.class.getSimpleName() + " and annotation must have non-null endpoint");
}
this.client = client;
this.scimResourceClass = scimResourceClass;
this.scimResourceListResponseGenericType = scimResourceListGenericType;
this.target = this.client.target(baseUrl).path(endpoint);
this.scimClient = new InternalScimClient();
}
public BaseScimClient(Client client, String baseUrl, Class<T> scimResourceClass, GenericType<ListResponse<T>> scimResourceListGenericType, RestCall invoke) {
this(client, baseUrl, scimResourceClass, scimResourceListGenericType);
this.invoke = invoke;
}
@Override
public void close() {
this.client.close();
}
public Optional<T> getById(String id) throws ScimException {
return this.getById(id, null, null);
}
public Optional<T> getById(String id, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException {
Optional<T> resource;
Response response = this.scimClient.getById(id, attributes, excludedAttributes);
try {
if (RestClientUtil.isSuccessful(response)) {
resource = Optional.of(response.readEntity(this.scimResourceClass));
} else if (response.getStatus() == Status.NOT_FOUND.getStatusCode()) {
resource = Optional.empty();
} else {
ErrorResponse errorResponse = response.readEntity(ErrorResponse.class);
throw new ScimException(errorResponse, Status.fromStatusCode(response.getStatus()));
}
} finally {
RestClientUtil.close(response);
}
return resource;
}
public ListResponse<T> query(AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes, Filter filter, AttributeReference sortBy, SortOrder sortOrder, Integer startIndex, Integer count) throws ScimException {
ListResponse<T> listResponse;
FilterWrapper filterWrapper = new FilterWrapper(filter);
Response response = this.scimClient.query(attributes, excludedAttributes, filterWrapper, sortBy, sortOrder, startIndex, count);
listResponse = handleResponse(response, scimResourceListResponseGenericType, response::readEntity);
return listResponse;
}
public T create(T resource) throws ScimException {
return this.create(resource, null, null);
}
public T create(T resource, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException {
Response response = this.scimClient.create(resource, attributes, excludedAttributes);
return handleResponse(response, scimResourceClass, response::readEntity);
}
public ListResponse<T> find(SearchRequest searchRequest) throws ScimException {
ListResponse<T> listResponse;
Response response = this.scimClient.find(searchRequest);
listResponse = handleResponse(response, scimResourceListResponseGenericType, response::readEntity);
return listResponse;
}
public T update(String id, T resource) throws ScimException {
return this.update(id, resource, null, null);
}
public T update(String id, T resource, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException {
Response response = this.scimClient.update(resource, id, attributes, excludedAttributes);
return handleResponse(response, scimResourceClass, response::readEntity);
}
public T patch(String id, PatchRequest patchRequest) throws ScimException {
return this.patch(id, patchRequest, null, null);
}
public T patch(String id, PatchRequest patchRequest, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException {
Response response = this.scimClient.patch(patchRequest, id, attributes, excludedAttributes);
return handleResponse(response, scimResourceClass, response::readEntity);
}
public void delete(String id) throws ScimException {
Response response = this.scimClient.delete(id);
handleResponse(response);
}
static <E, T> E handleResponse(Response response, T type, Function<T, E> readEntity) throws ScimException {
E entity;
try {
if (RestClientUtil.isSuccessful(response)) {
entity = readEntity.apply(type);
} else {
Status status = Status.fromStatusCode(response.getStatus());
ErrorResponse errorResponse = response.readEntity(ErrorResponse.class);
throw new ScimException(errorResponse, status);
}
} finally {
RestClientUtil.close(response);
}
return entity;
}
static void handleResponse(Response response) throws ScimException {
try {
if (!RestClientUtil.isSuccessful(response)) {
Status status = Status.fromStatusCode(response.getStatus());
ErrorResponse errorResponse = response.readEntity(ErrorResponse.class);
throw new ScimException(errorResponse, status);
}
} catch (ProcessingException e) {
ErrorResponse er = new ErrorResponse(Status.INTERNAL_SERVER_ERROR, e.getMessage());
throw new ScimException(er, Status.INTERNAL_SERVER_ERROR);
} finally {
RestClientUtil.close(response);
}
}
static ScimException toScimException(RestException restException) {
return new ScimException(restException.getError(), restException.getStatus());
}
public RestCall getInvoke() {
return this.invoke;
}
public void setInvoke(RestCall invoke) {
this.invoke = invoke;
}
private class InternalScimClient implements BaseResourceTypeResource<T> {
private static final String FILTER_QUERY_PARAM = "filter";
private static final String SORT_BY_QUERY_PARAM = "sortBy";
private static final String SORT_ORDER_QUERY_PARAM = "sortOrder";
private static final String START_INDEX_QUERY_PARAM = "startIndex";
private static final String COUNT_QUERY_PARAM = "count";
@Override
public Response getById(String id, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException {
Response response;
Invocation request = BaseScimClient.this.target
.path(id)
.queryParam(ATTRIBUTES_QUERY_PARAM, nullOutQueryParamIfListIsNullOrEmpty(attributes))
.queryParam(EXCLUDED_ATTRIBUTES_QUERY_PARAM, nullOutQueryParamIfListIsNullOrEmpty(excludedAttributes))
.request(getContentType())
.buildGet();
try {
response = BaseScimClient.this.invoke.apply(request);
return response;
} catch (RestException restException) {
throw toScimException(restException);
}
}
@Override
public Response query(AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes, FilterWrapper filter, AttributeReference sortBy, SortOrder sortOrder, Integer startIndex, Integer count) throws ScimException {
Response response;
Invocation request = BaseScimClient.this.target
.queryParam(ATTRIBUTES_QUERY_PARAM, nullOutQueryParamIfListIsNullOrEmpty(attributes))
.queryParam(EXCLUDED_ATTRIBUTES_QUERY_PARAM, nullOutQueryParamIfListIsNullOrEmpty(excludedAttributes))
.queryParam(FILTER_QUERY_PARAM, filter.getFilter())
.queryParam(SORT_BY_QUERY_PARAM, sortBy)
.queryParam(SORT_ORDER_QUERY_PARAM, sortOrder != null ? sortOrder.name() : null)
.queryParam(START_INDEX_QUERY_PARAM, startIndex)
.queryParam(COUNT_QUERY_PARAM, count)
.request(getContentType())
.buildGet();
try {
response = BaseScimClient.this.invoke.apply(request);
return response;
} catch (RestException restException) {
throw toScimException(restException);
}
}
@Override
public Response create(T resource, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException {
Response response;
Invocation request = BaseScimClient.this.target
.queryParam(ATTRIBUTES_QUERY_PARAM, nullOutQueryParamIfListIsNullOrEmpty(attributes))
.queryParam(EXCLUDED_ATTRIBUTES_QUERY_PARAM, nullOutQueryParamIfListIsNullOrEmpty(excludedAttributes))
.request(getContentType())
.buildPost(Entity.entity(resource, getContentType()));
try {
response = BaseScimClient.this.invoke.apply(request);
return response;
} catch (RestException restException) {
throw toScimException(restException);
}
}
@Override
public Response find(SearchRequest searchRequest) throws ScimException {
Response response;
Invocation request = BaseScimClient.this.target
.path(".search")
.request(getContentType())
.buildPost(Entity.entity(searchRequest, getContentType()));
try {
response = BaseScimClient.this.invoke.apply(request);
return response;
} catch (RestException restException) {
throw toScimException(restException);
}
}
@Override
public Response update(T resource, String id, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException {
Response response;
Invocation request = BaseScimClient.this.target
.path(id)
.queryParam(ATTRIBUTES_QUERY_PARAM, nullOutQueryParamIfListIsNullOrEmpty(attributes))
.queryParam(EXCLUDED_ATTRIBUTES_QUERY_PARAM, nullOutQueryParamIfListIsNullOrEmpty(excludedAttributes))
.request(getContentType())
.buildPut(Entity.entity(resource, getContentType()));
try {
response = BaseScimClient.this.invoke.apply(request);
return response;
} catch (RestException restException) {
throw toScimException(restException);
}
}
@Override
public Response patch(PatchRequest patchRequest, String id, AttributeReferenceListWrapper attributes, AttributeReferenceListWrapper excludedAttributes) throws ScimException {
Response response;
Invocation request = BaseScimClient.this.target
.path(id)
.queryParam(ATTRIBUTES_QUERY_PARAM, nullOutQueryParamIfListIsNullOrEmpty(attributes))
.queryParam(EXCLUDED_ATTRIBUTES_QUERY_PARAM, nullOutQueryParamIfListIsNullOrEmpty(excludedAttributes))
.request(getContentType())
.build("PATCH", Entity.entity(patchRequest, getContentType()));
try {
response = BaseScimClient.this.invoke.apply(request);
return response;
} catch (RestException restException) {
throw toScimException(restException);
}
}
@Override
public Response delete(String id) throws ScimException {
Response response;
Invocation request = BaseScimClient.this.target
.path(id)
.request(getContentType())
.buildDelete();
try {
response = BaseScimClient.this.invoke.apply(request);
return response;
} catch (RestException restException) {
throw toScimException(restException);
}
}
private AttributeReferenceListWrapper nullOutQueryParamIfListIsNullOrEmpty(AttributeReferenceListWrapper wrapper) {
if (wrapper == null) {
return null;
}
Set<AttributeReference> attributeReferences = wrapper.getAttributeReferences();
if (attributeReferences == null || attributeReferences.isEmpty()) {
return null;
}
return wrapper;
}
}
protected String getContentType() {
return Constants.SCIM_CONTENT_TYPE;
}
}