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}