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.marshal.Marshaller;
021    import com.unboundid.scim.marshal.StreamMarshaller;
022    import com.unboundid.scim.marshal.json.JsonStreamMarshaller;
023    import com.unboundid.scim.marshal.xml.XmlStreamMarshaller;
024    import com.unboundid.scim.wink.RequestContext;
025    import com.unboundid.scim.wink.SCIMApplication;
026    
027    import javax.ws.rs.core.MediaType;
028    import java.io.BufferedOutputStream;
029    import java.io.File;
030    import java.io.FileInputStream;
031    import java.io.FileOutputStream;
032    import java.io.OutputStream;
033    import java.util.Collections;
034    import java.util.Set;
035    import java.util.logging.Level;
036    
037    
038    
039    /**
040     * Implements a SCIMResponse to handle bulk responses without keeping the
041     * entire response in memory.
042     */
043    public class BulkStreamResponse implements SCIMResponse
044    {
045      private final File file;
046      private final StreamMarshaller streamMarshaller;
047    
048    
049      /**
050       * Create a new bulk stream response.
051       *
052       * @param application     The SCIM JAX-RS application.
053       * @param requestContext  The bulk request context.
054       *
055       * @throws SCIMException  If the bulk stream response could not be created.
056       */
057      public BulkStreamResponse(final SCIMApplication application,
058                                final RequestContext requestContext)
059          throws SCIMException
060      {
061        try
062        {
063          file = File.createTempFile(
064              "scim-bulk-response-",
065              "." + requestContext.getProduceMediaType().getSubtype(),
066              application.getTmpDataDir());
067          file.deleteOnExit();
068        }
069        catch (Exception e)
070        {
071          Debug.debugException(e);
072          throw new ServerErrorException(
073              "Cannot create a temporary file for the bulk response" +
074              e.getMessage());
075        }
076    
077        final OutputStream outputStream;
078        try
079        {
080          outputStream = new BufferedOutputStream(new FileOutputStream(file));
081        }
082        catch (Exception e)
083        {
084          Debug.debugException(e);
085          if (!file.delete())
086          {
087            Debug.debug(Level.WARNING, DebugType.OTHER,
088                        "Could not delete temporary file " +
089                        file.getAbsolutePath());
090          }
091    
092          throw new ServerErrorException(
093              "Cannot create output stream for temporary file '" + file + "': " +
094              e.getMessage());
095        }
096    
097        try
098        {
099          if (requestContext.getProduceMediaType().equals(
100              MediaType.APPLICATION_JSON_TYPE))
101          {
102            streamMarshaller = new JsonStreamMarshaller(outputStream);
103          }
104          else
105          {
106            streamMarshaller = new XmlStreamMarshaller(outputStream);
107          }
108    
109          // Bulk responses contain no data so there is only core schema.
110          final Set<String> schemaURIs =
111              Collections.singleton(SCIMConstants.SCHEMA_URI_CORE);
112          streamMarshaller.writeBulkStart(-1, schemaURIs);
113        }
114        catch (SCIMException e)
115        {
116          Debug.debugException(e);
117    
118          try
119          {
120            outputStream.close();
121          }
122          catch (Exception e1)
123          {
124            Debug.debugException(e1);
125          }
126          finally
127          {
128            if (!file.delete())
129            {
130              Debug.debug(Level.WARNING, DebugType.OTHER,
131                          "Could not delete temporary file " +
132                          file.getAbsolutePath());
133            }
134          }
135    
136          throw e;
137        }
138      }
139    
140    
141    
142      /**
143       * Write a bulk operation to the response.
144       *
145       * @param o  The bulk operation to write.
146       *
147       * @throws SCIMException  If the bulk operation could not be written.
148       */
149      public void writeBulkOperation(final BulkOperation o)
150          throws SCIMException
151      {
152        streamMarshaller.writeBulkOperation(o);
153      }
154    
155    
156    
157      /**
158       * Release resources when this bulk stream response is no longer needed.
159       */
160      public void finalizeResponse()
161      {
162        if (file.exists() && !file.delete())
163        {
164          Debug.debug(Level.WARNING, DebugType.OTHER,
165                      "Could not delete temporary file " +
166                      file.getAbsolutePath());
167        }
168      }
169    
170    
171    
172      /**
173       * {@inheritDoc}
174       */
175      @Override
176      public void marshal(final Marshaller marshaller,
177                          final OutputStream outputStream)
178          throws Exception
179      {
180        try
181        {
182          // Finish writing the response to the temporary file.
183          streamMarshaller.writeBulkFinish();
184          streamMarshaller.close();
185    
186          // Copy the temporary file to the output stream.
187          final FileInputStream inputStream = new FileInputStream(file);
188          final byte[] buffer = new byte[8192];
189          int bytesRead;
190          while ((bytesRead = inputStream.read(buffer)) != -1)
191          {
192            outputStream.write(buffer, 0, bytesRead);
193          }
194        }
195        finally
196        {
197          // Delete the temporary response file.
198          finalizeResponse();
199        }
200      }
201    }