001/* 002 * Copyright 2015-2020 Ping Identity Corporation 003 * 004 * This program is free software; you can redistribute it and/or modify 005 * it under the terms of the GNU General Public License (GPLv2 only) 006 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 007 * as published by the Free Software Foundation. 008 * 009 * This program is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 012 * GNU General Public License for more details. 013 * 014 * You should have received a copy of the GNU General Public License 015 * along with this program; if not, see <http://www.gnu.org/licenses>. 016 */ 017 018package com.unboundid.scim2.client.requests; 019 020import com.unboundid.scim2.client.ScimServiceException; 021import com.unboundid.scim2.common.ScimResource; 022import com.unboundid.scim2.common.exceptions.ScimException; 023import com.unboundid.scim2.common.messages.ErrorResponse; 024import com.unboundid.scim2.common.utils.StaticUtils; 025 026import javax.ws.rs.ProcessingException; 027import javax.ws.rs.client.Invocation; 028import javax.ws.rs.client.WebTarget; 029import javax.ws.rs.core.MediaType; 030import javax.ws.rs.core.MultivaluedHashMap; 031import javax.ws.rs.core.MultivaluedMap; 032import javax.ws.rs.core.Response; 033import java.util.ArrayList; 034import java.util.List; 035import java.util.Map; 036 037import static com.unboundid.scim2.common.utils.ApiConstants.MEDIA_TYPE_SCIM; 038 039/** 040 * Abstract SCIM request builder. 041 */ 042public class RequestBuilder<T extends RequestBuilder> 043{ 044 /** 045 * The web target to send the request. 046 */ 047 private WebTarget target; 048 049 /** 050 * Arbitrary request headers. 051 */ 052 protected final MultivaluedMap<String, Object> headers = 053 new MultivaluedHashMap<String, Object>(); 054 055 /** 056 * Arbitrary query parameters. 057 */ 058 protected final MultivaluedMap<String, Object> queryParams = 059 new MultivaluedHashMap<String, Object>(); 060 061 private String contentType = MEDIA_TYPE_SCIM; 062 063 private List<String> accept = new ArrayList<String>(); 064 065 /** 066 * Create a new SCIM request builder. 067 * 068 * @param target The WebTarget to send the request. 069 */ 070 RequestBuilder(final WebTarget target) 071 { 072 this.target = target; 073 accept(MEDIA_TYPE_SCIM, MediaType.APPLICATION_JSON); 074 } 075 076 /** 077 * Add an arbitrary HTTP header to the request. 078 * 079 * @param name The header name. 080 * @param value The header value(s). 081 * @return This builder. 082 */ 083 @SuppressWarnings("unchecked") 084 public T header(final String name, final Object... value) 085 { 086 headers.addAll(name, value); 087 return (T) this; 088 } 089 090 /** 091 * Sets the media type for any content sent to the server. The default 092 * value is ApiConstants.MEDIA_TYPE_SCIM ("application/scim+json"). 093 * @param contentType a string describing the media type of content 094 * sent to the server. 095 * @return This builder. 096 */ 097 public T contentType(final String contentType) 098 { 099 this.contentType = contentType; 100 return (T) this; 101 } 102 103 /** 104 * Sets the media type(s) that are acceptable as a return from the server. 105 * The default accepted media types are 106 * ApiConstants.MEDIA_TYPE_SCIM ("application/scim+json") and 107 * MediaType.APPLICATION_JSON ("application/json") 108 * @param acceptStrings a string (or strings) describing the media type that 109 * will be accepted from the server. This parameter may 110 * not be null. 111 * @return This builder. 112 */ 113 public T accept(final String ... acceptStrings) 114 { 115 this.accept.clear(); 116 if((acceptStrings == null) || (acceptStrings.length == 0)) 117 { 118 throw new IllegalArgumentException( 119 "Accepted media types must not be null or empty"); 120 } 121 122 for(String acceptString : acceptStrings) 123 { 124 accept.add(acceptString); 125 } 126 127 return (T) this; 128 } 129 130 /** 131 * Add an arbitrary query parameter to the request. 132 * 133 * @param name The query parameter name. 134 * @param value The query parameter value(s). 135 * @return This builder. 136 */ 137 @SuppressWarnings("unchecked") 138 public T queryParam(final String name, final Object... value) 139 { 140 queryParams.addAll(name, value); 141 return (T) this; 142 } 143 144 /** 145 * Retrieve the meta.version attribute of the resource. 146 * 147 * @param resource The resource whose version to retrieve. 148 * @return The resource version. 149 * @throws IllegalArgumentException if the resource does not contain a the 150 * meta.version attribute. 151 */ 152 static String getResourceVersion(final ScimResource resource) 153 throws IllegalArgumentException 154 { 155 if(resource == null || resource.getMeta() == null || 156 resource.getMeta().getVersion() == null) 157 { 158 throw new IllegalArgumentException( 159 "Resource version must be specified by meta.version"); 160 } 161 return resource.getMeta().getVersion(); 162 } 163 164 /** 165 * Convert a JAX-RS response to a ScimException. 166 * 167 * @param response The JAX-RS response. 168 * @return the converted ScimException. 169 */ 170 static ScimException toScimException(final Response response) 171 { 172 try 173 { 174 ErrorResponse errorResponse = response.readEntity(ErrorResponse.class); 175 // If are able to read an error response, use it to build the exception. 176 // If not, use the http status code to determine the exception. 177 ScimException exception = (errorResponse == null) ? 178 ScimException.createException(response.getStatus(), null) : 179 ScimException.createException(errorResponse, null); 180 response.close(); 181 return exception; 182 } 183 catch(ProcessingException ex) 184 { 185 return new ScimServiceException( 186 response.getStatus(), ex.getMessage(), ex); 187 } 188 } 189 190 /** 191 * Returns the unbuilt WebTarget for the request. In most cases, 192 * {@link #buildTarget()} should be used instead. 193 * 194 * @return The WebTarget for the request. 195 */ 196 protected WebTarget target() 197 { 198 return target; 199 } 200 201 /** 202 * Build the WebTarget for the request. 203 * 204 * @return The WebTarget for the request. 205 */ 206 WebTarget buildTarget() 207 { 208 for(Map.Entry<String, List<Object>> queryParam : queryParams.entrySet()) 209 { 210 target = target.queryParam(queryParam.getKey(), 211 queryParam.getValue().toArray()); 212 } 213 return target; 214 } 215 216 /** 217 * Gets the media type for any content sent to the server. 218 * 219 * @return the media type for any content sent to the server. 220 */ 221 protected String getContentType() 222 { 223 return contentType; 224 } 225 226 /** 227 * Gets the media type(s) that are acceptable as a return from the server. 228 * 229 * @return the media type(s) that are acceptable as a return from the server. 230 */ 231 protected List<String> getAccept() 232 { 233 return accept; 234 } 235 /** 236 * Build the Invocation.Builder for the request. 237 * 238 * @return The Invocation.Builder for the request. 239 */ 240 Invocation.Builder buildRequest() 241 { 242 Invocation.Builder builder = 243 buildTarget().request(accept.toArray(new String[accept.size()])); 244 for(Map.Entry<String, List<Object>> header : headers.entrySet()) 245 { 246 builder = builder.header(header.getKey(), 247 StaticUtils.listToString(header.getValue(), 248 ", ")); 249 } 250 return builder; 251 } 252}