001/* 002 * Copyright 2012-2013 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 InputStream entity = null; 167 try 168 { 169 response = clientResource.post(output); 170 entity = response.getEntity(InputStream.class); 171 172 if(response.getStatusType() == Response.Status.OK) 173 { 174 final BulkConfig bulkConfig = 175 new BulkConfig(true, operations.size(), Long.MAX_VALUE); 176 unmarshaller.bulkUnmarshal(entity, bulkConfig, handler); 177 } 178 else 179 { 180 throw createErrorResponseException(response, entity); 181 } 182 } 183 catch(SCIMException e) 184 { 185 throw e; 186 } 187 catch(Exception e) 188 { 189 throw SCIMException.createException(SCIMEndpoint.getStatusCode(e), 190 SCIMEndpoint.getExceptionMessage(e), e); 191 } 192 finally 193 { 194 try 195 { 196 if (entity != null) 197 { 198 entity.close(); 199 } 200 } 201 catch (IOException e) 202 { 203 // Let's just log this and ignore. 204 Debug.debugException(e); 205 } 206 } 207 } 208 209 210 211 /** 212 * Returns a SCIM exception representing the error response. 213 * 214 * @param response The client response. 215 * @param entity The response content. 216 * 217 * @return The SCIM exception representing the error response. 218 */ 219 private SCIMException createErrorResponseException( 220 final ClientResponse response, 221 final InputStream entity) 222 { 223 SCIMException scimException = null; 224 225 if (entity != null) 226 { 227 try 228 { 229 scimException = unmarshaller.unmarshalError(entity); 230 } 231 catch (InvalidResourceException e) 232 { 233 // The response content could not be parsed as a SCIM error 234 // response, which is the case if the response is a more general 235 // HTTP error. It is better to just provide the HTTP response 236 // details in this case. 237 Debug.debugException(e); 238 } 239 } 240 241 if (scimException == null) 242 { 243 scimException = SCIMException.createException( 244 response.getStatusCode(), response.getMessage()); 245 } 246 247 return scimException; 248 } 249}