001/* 002 * Copyright 2012-2016 UnboundID Corp. 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.scim.sdk; 019 020import com.unboundid.scim.data.BulkConfig; 021import com.unboundid.scim.marshal.Marshaller; 022import com.unboundid.scim.marshal.Unmarshaller; 023import com.unboundid.scim.marshal.json.JsonMarshaller; 024import com.unboundid.scim.marshal.json.JsonUnmarshaller; 025import com.unboundid.scim.marshal.xml.XmlMarshaller; 026import com.unboundid.scim.marshal.xml.XmlUnmarshaller; 027import org.apache.wink.client.ClientResponse; 028import org.apache.wink.client.Resource; 029import org.apache.wink.client.RestClient; 030 031import javax.ws.rs.WebApplicationException; 032import javax.ws.rs.core.MediaType; 033import javax.ws.rs.core.Response; 034import javax.ws.rs.core.StreamingOutput; 035import javax.ws.rs.core.UriBuilder; 036import java.io.IOException; 037import java.io.InputStream; 038import java.io.OutputStream; 039import java.net.URI; 040import java.util.List; 041 042 043 044/** 045 * This class provides a way for a SCIM client to invoke a bulk request. 046 */ 047public class BulkEndpoint 048{ 049 private final SCIMService service; 050 private final RestClient client; 051 private final MediaType contentType; 052 private final MediaType acceptType; 053 private final Unmarshaller unmarshaller; 054 private final Marshaller marshaller; 055 056 057 058 /** 059 * Create a new instance of a bulk request. 060 * 061 * @param service The SCIM service. 062 * @param client The REST client. 063 */ 064 BulkEndpoint(final SCIMService service, final RestClient client) 065 { 066 this.service = service; 067 this.client = client; 068 069 this.contentType = service.getContentType(); 070 this.acceptType = service.getAcceptType(); 071 072 if (contentType.equals(MediaType.APPLICATION_JSON_TYPE)) 073 { 074 this.marshaller = new JsonMarshaller(); 075 } 076 else 077 { 078 this.marshaller = new XmlMarshaller(); 079 } 080 081 if (acceptType.equals(MediaType.APPLICATION_JSON_TYPE)) 082 { 083 this.unmarshaller = new JsonUnmarshaller(); 084 } 085 else 086 { 087 this.unmarshaller = new XmlUnmarshaller(); 088 } 089 } 090 091 092 093 /** 094 * Retrieves the response to the bulk request. This should only be called 095 * after all operations to be performed have been provided. 096 * 097 * @param operations The bulk operations to be performed. 098 * @param failOnErrors The number of errors that the service provider will 099 * accept before the operation is terminated and an 100 * error response is returned. A value of -1 indicates 101 * the the service provider will continue to perform 102 * as many operations as possible without regard to 103 * failures. 104 * 105 * @return The bulk response. 106 * 107 * @throws SCIMException If the request fails. 108 */ 109 public BulkResponse processRequest(final List<BulkOperation> operations, 110 final int failOnErrors) 111 throws SCIMException 112 { 113 final BulkContentResponseHandler handler = 114 new BulkContentResponseHandler(); 115 processRequest(handler, operations, failOnErrors); 116 117 return new BulkResponse(handler.getOperations()); 118 } 119 120 121 122 /** 123 * Processes the bulk request. This should only be called 124 * after all operations to be performed have been provided. 125 * 126 * @param handler The bulk content handler that is to be used to process 127 * each operation response in the bulk response. 128 * @param operations The bulk operations to be performed. 129 * @param failOnErrors The number of errors that the service provider will 130 * accept before the operation is terminated and an 131 * error response is returned. A value of -1 indicates 132 * the the service provider will continue to perform 133 * as many operations as possible without regard to 134 * failures. 135 * 136 * @throws SCIMException If the request fails. 137 */ 138 private void processRequest(final BulkContentHandler handler, 139 final List<BulkOperation> operations, 140 final int failOnErrors) 141 throws SCIMException 142 { 143 final URI uri = 144 UriBuilder.fromUri(service.getBaseURL()).path("Bulk").build(); 145 final Resource clientResource = client.resource(uri); 146 clientResource.accept(acceptType); 147 clientResource.contentType(contentType); 148 149 final StreamingOutput output = new StreamingOutput() 150 { 151 public void write(final OutputStream outputStream) 152 throws IOException, WebApplicationException 153 { 154 try 155 { 156 marshaller.bulkMarshal(outputStream, failOnErrors, operations); 157 } 158 catch (Exception e) 159 { 160 throw new WebApplicationException(e, Response.Status.BAD_REQUEST); 161 } 162 } 163 }; 164 165 ClientResponse response = null; 166 try 167 { 168 response = clientResource.post(output); 169 InputStream entity = response.getEntity(InputStream.class); 170 171 if(response.getStatusType() == Response.Status.OK) 172 { 173 final BulkConfig bulkConfig = 174 new BulkConfig(true, operations.size(), Long.MAX_VALUE); 175 unmarshaller.bulkUnmarshal(entity, bulkConfig, handler); 176 } 177 else 178 { 179 throw createErrorResponseException(response, entity); 180 } 181 } 182 catch(SCIMException e) 183 { 184 throw e; 185 } 186 catch(Exception e) 187 { 188 throw SCIMException.createException(SCIMEndpoint.getStatusCode(e), 189 SCIMEndpoint.getExceptionMessage(e), e); 190 } 191 finally 192 { 193 if (response != null) 194 { 195 response.close(); 196 } 197 } 198 } 199 200 201 202 /** 203 * Returns a SCIM exception representing the error response. 204 * 205 * @param response The client response. 206 * @param entity The response content. 207 * 208 * @return The SCIM exception representing the error response. 209 */ 210 private SCIMException createErrorResponseException( 211 final ClientResponse response, 212 final InputStream entity) 213 { 214 SCIMException scimException = null; 215 216 if (entity != null) 217 { 218 try 219 { 220 scimException = unmarshaller.unmarshalError(entity); 221 } 222 catch (InvalidResourceException e) 223 { 224 // The response content could not be parsed as a SCIM error 225 // response, which is the case if the response is a more general 226 // HTTP error. It is better to just provide the HTTP response 227 // details in this case. 228 Debug.debugException(e); 229 } 230 } 231 232 if (scimException == null) 233 { 234 scimException = SCIMException.createException( 235 response.getStatusCode(), response.getMessage()); 236 } 237 238 return scimException; 239 } 240}