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.wink;
019
020 import com.unboundid.scim.data.BulkConfig;
021 import com.unboundid.scim.data.ServiceProviderConfig;
022 import com.unboundid.scim.marshal.Marshaller;
023 import com.unboundid.scim.marshal.Unmarshaller;
024 import com.unboundid.scim.marshal.json.JsonMarshaller;
025 import com.unboundid.scim.marshal.json.JsonUnmarshaller;
026 import com.unboundid.scim.marshal.xml.XmlMarshaller;
027 import com.unboundid.scim.marshal.xml.XmlUnmarshaller;
028 import com.unboundid.scim.sdk.BulkStreamResponse;
029 import com.unboundid.scim.sdk.Debug;
030 import com.unboundid.scim.sdk.DebugType;
031 import com.unboundid.scim.sdk.SCIMBackend;
032 import com.unboundid.scim.sdk.SCIMException;
033 import com.unboundid.scim.sdk.SCIMResponse;
034 import com.unboundid.scim.sdk.ServerErrorException;
035 import com.unboundid.scim.sdk.UnauthorizedException;
036
037 import javax.ws.rs.WebApplicationException;
038 import javax.ws.rs.core.MediaType;
039 import javax.ws.rs.core.Response;
040 import javax.ws.rs.core.StreamingOutput;
041 import java.io.File;
042 import java.io.FileOutputStream;
043 import java.io.IOException;
044 import java.io.InputStream;
045 import java.io.OutputStream;
046 import java.util.logging.Level;
047
048
049
050 /**
051 * This class is the base class for JAX-RS resources implementing the Bulk
052 * operation.
053 */
054 public class AbstractBulkResource
055 {
056 /**
057 * The SCIM JAX-RS application associated with this resource.
058 */
059 private final SCIMApplication application;
060
061 /**
062 * The resource stats for the bulk operation end-point.
063 */
064 private final ResourceStats bulkResourceStats;
065
066 /**
067 * The SCIMBackend to use to process individual operations within a bulk
068 * operation.
069 */
070 private final SCIMBackend backend;
071
072
073
074 /**
075 * Create a new instance of the bulk resource.
076 *
077 * @param application The SCIM JAX-RS application associated with this
078 * resource.
079 * @param bulkResourceStats The resource stats for the bulk operation
080 * end-point.
081 * @param backend The SCIMBackend to use to process individual
082 * operations within a bulk operation.
083 */
084 public AbstractBulkResource(final SCIMApplication application,
085 final ResourceStats bulkResourceStats,
086 final SCIMBackend backend)
087 {
088 this.application = application;
089 this.bulkResourceStats = bulkResourceStats;
090 this.backend = backend;
091 }
092
093
094
095 /**
096 * Process a POST operation.
097 *
098 * @param requestContext The request context.
099 * @param inputStream The content to be consumed.
100 *
101 * @return The response to the operation.
102 */
103 Response postBulk(final RequestContext requestContext,
104 final InputStream inputStream)
105 {
106 final Unmarshaller unmarshaller;
107 if (requestContext.getConsumeMediaType().equals(
108 MediaType.APPLICATION_JSON_TYPE))
109 {
110 unmarshaller = new JsonUnmarshaller();
111 bulkResourceStats.incrementStat(ResourceStats.POST_CONTENT_JSON);
112 }
113 else
114 {
115 unmarshaller = new XmlUnmarshaller();
116 bulkResourceStats.incrementStat(ResourceStats.POST_CONTENT_XML);
117 }
118
119 Response.ResponseBuilder responseBuilder;
120 try
121 {
122 String authID = requestContext.getAuthID();
123 if(authID == null)
124 {
125 throw new UnauthorizedException("Invalid credentials");
126 }
127
128 // Check the Content-Length against the maxPayloadSize.
129 final ServiceProviderConfig serviceProviderConfig =
130 application.getServiceProviderConfig();
131 final BulkConfig bulkConfig = serviceProviderConfig.getBulkConfig();
132 if (requestContext.getContentLength() > bulkConfig.getMaxPayloadSize())
133 {
134 throw SCIMException.createException(
135 413, "The content length of the bulk request (" +
136 requestContext.getContentLength() +
137 ") exceeds the maxPayloadSize (" +
138 bulkConfig.getMaxPayloadSize() + ")");
139 }
140
141 // Fail the request if the maximum concurrent requests would be exceeded.
142 application.acquireBulkRequestPermit();
143 try
144 {
145 // Write the request to a temporary file.
146 final File requestFile = File.createTempFile(
147 "scim-bulk-request-",
148 "." + requestContext.getConsumeMediaType().getSubtype(),
149 application.getTmpDataDir());
150 try
151 {
152 requestFile.deleteOnExit();
153 final FileOutputStream fileOutputStream =
154 new FileOutputStream(requestFile);
155 try
156 {
157 final byte[] buffer = new byte[8192];
158 int bytesRead;
159 long totalBytes = 0;
160 while ((bytesRead = inputStream.read(buffer)) != -1)
161 {
162 totalBytes += bytesRead;
163 if (totalBytes > bulkConfig.getMaxPayloadSize())
164 {
165 throw SCIMException.createException(
166 413,
167 "The size of the bulk request exceeds the maxPayloadSize " +
168 "(" + bulkConfig.getMaxPayloadSize() + ")");
169 }
170 fileOutputStream.write(buffer, 0, bytesRead);
171 }
172 }
173 finally
174 {
175 fileOutputStream.close();
176 }
177
178 // Write the response to a temporary file.
179 final BulkStreamResponse bulkStreamResponse =
180 new BulkStreamResponse(application, requestContext);
181 try
182 {
183 final BulkContentRequestHandler handler =
184 new BulkContentRequestHandler(application, requestContext,
185 backend, bulkStreamResponse);
186 unmarshaller.bulkUnmarshal(requestFile, bulkConfig, handler);
187
188 // Build the response.
189 responseBuilder = Response.status(Response.Status.OK);
190 setResponseEntity(responseBuilder,
191 requestContext.getProduceMediaType(),
192 bulkStreamResponse);
193 bulkResourceStats.incrementStat(ResourceStats.POST_OK);
194 }
195 catch (Exception e)
196 {
197 Debug.debugException(e);
198 bulkStreamResponse.finalizeResponse();
199 throw e;
200 }
201 }
202 finally
203 {
204 if (!requestFile.delete())
205 {
206 Debug.debug(Level.WARNING, DebugType.OTHER,
207 "Could not delete temporary file " +
208 requestFile.getAbsolutePath());
209 }
210 }
211 }
212 catch (SCIMException e)
213 {
214 throw e;
215 }
216 catch (Exception e)
217 {
218 Debug.debugException(e);
219 throw new ServerErrorException(
220 "Error processing bulk request: " + e.getMessage());
221 }
222 finally
223 {
224 application.releaseBulkRequestPermit();
225 }
226 }
227 catch (SCIMException e)
228 {
229 Debug.debugException(e);
230 // Build the response.
231 responseBuilder = Response.status(e.getStatusCode());
232 setResponseEntity(responseBuilder, requestContext.getProduceMediaType(),
233 e);
234 bulkResourceStats.incrementStat("post-" + e.getStatusCode());
235 }
236
237 if (requestContext.getProduceMediaType() == MediaType.APPLICATION_JSON_TYPE)
238 {
239 bulkResourceStats.incrementStat(ResourceStats.POST_RESPONSE_JSON);
240 }
241 else if (requestContext.getProduceMediaType() ==
242 MediaType.APPLICATION_XML_TYPE)
243 {
244 bulkResourceStats.incrementStat(ResourceStats.POST_RESPONSE_XML);
245 }
246
247 return responseBuilder.build();
248 }
249
250
251
252 /**
253 * Sets the response entity (content) for a SCIM bulk response.
254 *
255 * @param builder A JAX-RS response builder.
256 * @param mediaType The media type to be returned.
257 * @param scimResponse The SCIM response to be returned.
258 */
259 private void setResponseEntity(final Response.ResponseBuilder builder,
260 final MediaType mediaType,
261 final SCIMResponse scimResponse)
262 {
263 final Marshaller marshaller;
264 builder.type(mediaType);
265 if (mediaType.equals(MediaType.APPLICATION_JSON_TYPE))
266 {
267 marshaller = new JsonMarshaller();
268 }
269 else
270 {
271 marshaller = new XmlMarshaller();
272 }
273
274 final StreamingOutput output = new StreamingOutput()
275 {
276 public void write(final OutputStream outputStream)
277 throws IOException, WebApplicationException
278 {
279 try
280 {
281 scimResponse.marshal(marshaller, outputStream);
282 }
283 catch (Exception e)
284 {
285 Debug.debugException(e);
286 throw new WebApplicationException(
287 e, Response.Status.INTERNAL_SERVER_ERROR);
288 }
289 }
290 };
291 builder.entity(output);
292 }
293 }