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.marshal.Marshaller;
021import com.unboundid.scim.marshal.StreamMarshaller;
022import com.unboundid.scim.marshal.json.JsonStreamMarshaller;
023import com.unboundid.scim.marshal.xml.XmlStreamMarshaller;
024import com.unboundid.scim.wink.RequestContext;
025import com.unboundid.scim.wink.SCIMApplication;
026
027import javax.ws.rs.core.MediaType;
028import java.io.BufferedOutputStream;
029import java.io.File;
030import java.io.FileInputStream;
031import java.io.FileOutputStream;
032import java.io.OutputStream;
033import java.util.Collections;
034import java.util.Set;
035import 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 */
043public 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}