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}