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}