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    }