001 /*
002 * Copyright 2012 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
018 package com.unboundid.scim.sdk;
019
020 import com.unboundid.scim.data.BulkConfig;
021 import com.unboundid.scim.marshal.Marshaller;
022 import com.unboundid.scim.marshal.Unmarshaller;
023 import com.unboundid.scim.marshal.json.JsonMarshaller;
024 import com.unboundid.scim.marshal.json.JsonUnmarshaller;
025 import com.unboundid.scim.marshal.xml.XmlMarshaller;
026 import com.unboundid.scim.marshal.xml.XmlUnmarshaller;
027 import org.apache.wink.client.ClientResponse;
028 import org.apache.wink.client.Resource;
029 import org.apache.wink.client.RestClient;
030
031 import javax.ws.rs.WebApplicationException;
032 import javax.ws.rs.core.MediaType;
033 import javax.ws.rs.core.Response;
034 import javax.ws.rs.core.StreamingOutput;
035 import javax.ws.rs.core.UriBuilder;
036 import java.io.IOException;
037 import java.io.InputStream;
038 import java.io.OutputStream;
039 import java.net.URI;
040 import java.util.List;
041
042
043
044 /**
045 * This class provides a way for a SCIM client to invoke a bulk request.
046 */
047 public 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 = clientResource.post(output);
166 InputStream entity = response.getEntity(InputStream.class);
167 try
168 {
169 if(response.getStatusType() == Response.Status.OK)
170 {
171 final BulkConfig bulkConfig =
172 new BulkConfig(true, operations.size(), Long.MAX_VALUE);
173 unmarshaller.bulkUnmarshal(entity, bulkConfig, handler);
174 }
175 else
176 {
177 throw createErrorResponseException(response, entity);
178 }
179 }
180 finally
181 {
182 try
183 {
184 if (entity != null)
185 {
186 entity.close();
187 }
188 } catch (IOException e)
189 {
190 // Let's just log this and ignore.
191 Debug.debugException(e);
192 }
193 }
194 }
195
196
197
198 /**
199 * Returns a SCIM exception representing the error response.
200 *
201 * @param response The client response.
202 * @param entity The response content.
203 *
204 * @return The SCIM exception representing the error response.
205 */
206 private SCIMException createErrorResponseException(
207 final ClientResponse response,
208 final InputStream entity)
209 {
210 SCIMException scimException = null;
211
212 if (entity != null)
213 {
214 try
215 {
216 scimException = unmarshaller.unmarshalError(entity);
217 }
218 catch (InvalidResourceException e)
219 {
220 // The response content could not be parsed as a SCIM error
221 // response, which is the case if the response is a more general
222 // HTTP error. It is better to just provide the HTTP response
223 // details in this case.
224 Debug.debugException(e);
225 }
226 }
227
228 if (scimException == null)
229 {
230 scimException = SCIMException.createException(
231 response.getStatusCode(), response.getMessage());
232 }
233
234 return scimException;
235 }
236 }