All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nablarch.fw.web.ResponseBody Maven / Gradle / Ivy

The newest version!
package nablarch.fw.web;

import java.io.BufferedInputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Collection;

import nablarch.core.log.Logger;
import nablarch.core.log.LoggerManager;
import nablarch.core.util.FileUtil;

/**
 * HTTPレスポンスのボディ内容を格納するオブジェクト。
 * 
 * レスポンスボディの内容を以下の3つの方式のいずれかによって指定する。
 * (排他利用)
 * 1. コンテンツパスによる静的リソースの指定。
 *     {@link HttpResponse#setContentPath(ResourceLocator)}
 * 2. 入力ストリームによる指定。
 *     {@link HttpResponse#setBodyStream(InputStream)}
 * 3. 内部バッファへの書き込み。
 *     {@link HttpResponse#write(byte[])}
 *     {@link HttpResponse#write(CharSequence)}
 * 
* * 内部バッファは一定サイズまではヒープ領域上に置かれるが、それを超えた * 場合は一時ファイルに出力される。 * * 入力ストリーム一時ファイルは、レスポンス処理終了後に * {@link nablarch.fw.web.handler.HttpResponseHandler} が {@link ResponseBody#cleanup()} を呼ぶことで * 自動的に削除される。 * * @see HttpResponse * @see nablarch.fw.web.handler.HttpResponseHandler * @author Iwauo Tajima */ public class ResponseBody { /** ロガー */ private static final Logger LOGGER = LoggerManager.get(ResponseBody.class); /** HTTPレスポンスの動作設定 */ private static final HttpResponseSetting CONF = new HttpResponseSetting(); /** ボディバッファの初期サイズ(16KB) */ private static final int BSIZE = 16 * 1024; /** * コンストラクタ * @param response HTTPレスポンス本体 */ public ResponseBody(HttpResponse response) { this.response = response; } /** HTTPレスポンス本体 */ private final HttpResponse response; /** コンテンツパス */ private ResourceLocator contentPath = null; /** 入力ストリーム */ private InputStream input = null; /** 内部バッファ(オンヒープ) */ private ByteBuffer buffer = null; /** 内部バッファのポジション。 * (ボディが空か否かの判定にBuffer#flip()前のポジションを使用するため、bufferに書き込み時にポジションをこの変数に待避する) */ private int bufferPosition = 0; /** 内部バッファ(一時ファイル) */ private File tempFile = null; /** 一時ファイルへの出力チャネル */ private FileChannel tempFileWriteChannel = null; /** 削除しなければならない一時ファイル */ private static final ThreadLocal> TEMP_FILE_HOLDER = new ThreadLocal>() { @Override protected Collection initialValue() { return new ArrayList(); } }; /** 閉じなければならない入力ストリーム */ private static final ThreadLocal> STREAM_HOLDER = new ThreadLocal>() { @Override protected Collection initialValue() { return new ArrayList(); } }; /** * ボディの内容が設定されていなければtrueを返す。 * @return ボディの内容が設定されていなければtrue */ public boolean isEmpty() { if(contentPath != null || input !=null || tempFile != null){ return false; } return buffer == null || bufferPosition <= 0; } /** * ボディの内容をヒープバッファに保持している場合、そのサイズを返す。 *
     * 内部バッファを使用していない場合、もしくは、
     * 内部バッファとして一時ファイルを使用している場合は null を返す。
     * 
* @return ヒープバッファのサイズ */ public Long length() { if (contentPath != null || input != null || tempFile != null) { return null; // unknown } if (buffer != null) { return Long.valueOf(buffer.position()); } return Long.valueOf(0); } /** * ボディのコンテンツパスを設定する。 * @param path コンテンツパス * @return このオブジェクト自体 */ public ResponseBody setContentPath(ResourceLocator path) { contentPath = path; return this; } /** * コンテンツパスを取得する。 * @return コンテンツパス */ public ResourceLocator getContentPath() { return contentPath; } /** * 内部バッファにバイナリデータを書き込む。 * @param bytes バイナリデータ * @return このオブジェクト自体 */ public ResponseBody write(byte[] bytes) { return write(ByteBuffer.wrap(bytes)); } /** * 内部バッファにキャラクタデータを書き込む。 * @param text キャラクタデータ * @return このオブジェクト自体 */ public ResponseBody write(CharSequence text) { return write(response.getCharset().encode(CharBuffer.wrap(text))); } /** * 内部バッファにバイナリデータを書き込む。 *
     * 要求サイズがヒープバッファの上限値を超過した場合、
     * 一時ファイルへの書き出しが許可されていれば、
     * ヒープバッファを破棄し、以降は一時ファイルをバッファとして使用する。
     * (ヒープバッファは直近のGCにより開放される。)
     * 一時ファイルへの書き出しが許可されていないばあい、
     * レスポンスコード400に相当するHTTPエラーを送出する。
     * 
* @param bytes バイナリデータ * @return このオブジェクト自体 */ public ResponseBody write(ByteBuffer bytes) { if (tempFile != null) { writeToFile(bytes); return this; } if (buffer == null) { buffer = ByteBuffer.allocate(BSIZE); } int newLimit = buffer.limit() + bytes.remaining(); if (newLimit > CONF.getBufferLimitSizeKb() * 1024) { writeToFile(bytes); } else { writeToHeapBuffer(bytes); } return this; } /** * ヒープ上のバッファにデータを書き込む * @param bytes データ */ private void writeToHeapBuffer(ByteBuffer bytes) { if (buffer.remaining() < bytes.remaining()) { expandTo(buffer.capacity() + bytes.remaining()); } buffer.put(bytes); bufferPosition = buffer.position(); } /** * 一時ファイル上のバッファにデータを書き込む * @param bytes データ */ private void writeToFile(ByteBuffer bytes) { if (tempFile == null) { useTempFile(); } try { tempFileWriteChannel.write(bytes); } catch (IOException e) { throw new RuntimeException(e); } } /** * ヒープバッファのサイズを拡張する。 * @see nablarch.fw.web.HttpResponseSetting#getBufferLimitSizeKb() * @see nablarch.fw.web.HttpResponseSetting#usesTempFile() * @param requestedSize 要求サイズ */ private void expandTo(long requestedSize) { int currentCapacity = buffer.capacity(); int maxBufferSize = CONF.getBufferLimitSizeKb() * 1024; int newSize = (requestedSize > currentCapacity * 2) ? (int) requestedSize : currentCapacity * 2; newSize = (maxBufferSize > newSize) ? newSize : maxBufferSize; buffer = ByteBuffer.allocate(newSize).put((ByteBuffer) buffer.flip()); } /** {@inheritDoc} * この実装ではボディの内容を表す文字列を返す。 *
     * 1. コンテンツパスが指定されている場合。
     *     パス文字列を返す。
     *
     * 2. 入力ストリームもしくは一時ファイル上にバッファリングしている場合。
     *     先頭16KBの内容をデコードした文字列を返す。
     *
     * 3. ヒープ上にバッファリングしている場合。
     *     バッファの内容をデコードした文字列を返す。
     * 
* @see HttpResponse#getCharset() * デコードに使用するコンバータ */ @Override public String toString() { if (contentPath != null) { return "Content-Path: " + contentPath.toString(); } try { if (input != null) { return peek(input); } if (tempFile != null) { InputStream istream = new BufferedInputStream(new FileInputStream(tempFile)); try { return peek(istream); } finally { FileUtil.closeQuietly(istream); } } } catch (Exception e) { throw new RuntimeException(e); } return new StringBuilder( (buffer == null) ? CharBuffer.allocate(0) : response.getCharset().decode((ByteBuffer) buffer.duplicate().flip()) ).toString(); } /** * 渡された入力ストリームの先頭16KBの内容をデコードした文字列を返す。 *
     * 入力ストリームが先読みをサポートしていない場合はクラス名のみを返す。
     * 
* @param input 入力ストリーム * @return 先頭16KBの内容をデコードした文字列 * @throws IOException I/O例外 */ private String peek(InputStream input) throws IOException { if (!input.markSupported()) { return "InputStream: " + input.getClass().getName(); } byte[] peeked = new byte[BSIZE]; input.mark(BSIZE); int readSize = 0; try { readSize = input.read(peeked); } finally { input.reset(); } return new StringBuilder( response.getCharset().decode(ByteBuffer.wrap(peeked, 0, readSize)) ).append((readSize == BSIZE) ? "..." : "").toString(); } /** * ボディの内容を格納した入力ストリームを返す。 * * なお、コンテントパスを使用している場合、以下のケースではnullを返却する。 *
     * 1. コンテントパスのスキームが file:// もしくは classpath:// のいずれでもない場合
     * 2. コンテントパスのスキームが file:// もしくは classpath:// だが、その参照先のファイルが存在しない場合。
     * 
* * @return 入力ストリーム */ public InputStream getInputStream() { if (contentPath != null) { try { return contentPath.getInputStream(); } catch (FileNotFoundException e) { return null; } } if (input != null) { return input; } if (tempFile != null) { try { return new FileInputStream(tempFile); } catch (FileNotFoundException e) { throw new RuntimeException(e); } } if (buffer == null) { buffer = ByteBuffer.wrap(new byte[]{}); } return new InputStreamWrapper((ByteBuffer) buffer.flip()); } /** * ボディの内容を格納した入力ストリームを設定する。 * @param input 入力ストリーム * @return このオブジェクト自体 */ public ResponseBody setInputStream(InputStream input) { this.input = input; STREAM_HOLDER.get().add(input); return this; } /** * 削除しなければならない一時ファイルに追加する。 *

* {@link #cleanup()}メソッドが呼ばれた際に指定された一時ファイルが削除される。 * @param file 一時ファイル */ public static void addTempFileToDelete(File file) { TEMP_FILE_HOLDER.get().add(file); } /** * カレントスレッドがIO用に確保しているリソースを全て開放する。 */ public static void cleanup() { Collection streams = STREAM_HOLDER.get(); for (Closeable stream : streams) { FileUtil.closeQuietly(stream); } Collection tempFiles = TEMP_FILE_HOLDER.get(); for (File file : tempFiles) { if (!file.delete()) { LOGGER.logWarn( "could not delete a temporary file: " + file.getAbsolutePath() ); } } STREAM_HOLDER.remove(); TEMP_FILE_HOLDER.remove(); } /** * {@link ByteBuffer} を {@link InputStream} に変換するラッパー * @author Iwauo Tajima */ private static class InputStreamWrapper extends InputStream { /** * コンストラクタ。 * @param buffer 内部バッファ */ public InputStreamWrapper(ByteBuffer buffer) { this.buffer = buffer; } /** * 内部バッファ。 */ private final ByteBuffer buffer; /** * 内部バッファを読み込む。 * @return 1バイト * @throws IOException I/O例外 */ @Override public synchronized int read() throws IOException { if (!buffer.hasRemaining()) { return -1; } return buffer.get(); } /** * 内部バッファを読み込む。 * @param bytes バイト配列 * @param off 開始位置 * @param len 読込レングス * @return 残バイトレングス * @throws IOException I/O例外 */ @Override public synchronized int read(byte[] bytes, int off, int len) throws IOException { if (!buffer.hasRemaining()) { return -1; } len = Math.min(len, buffer.remaining()); buffer.get(bytes, off, len); return len; } } /** * 内部バッファを一時ファイルに書き出す。 */ private void useTempFile() { File dir = getDownloadDir(); try { tempFile = File.createTempFile("nablarch_temp_", null, dir); tempFileWriteChannel = new FileOutputStream(tempFile).getChannel(); TEMP_FILE_HOLDER.get().add(tempFile); STREAM_HOLDER.get().add(tempFileWriteChannel); tempFileWriteChannel.write((ByteBuffer) buffer.flip()); buffer = null; } catch (IOException e) { throw new RuntimeException("download temp file create failed. ", e); } } /** * 一時ファイルを格納するディレクトリを取得する。 *

     * ディレクトリが存在しなかった場合、新規に作成する。
     * 
* @return 一時ディレクトリ */ private File getDownloadDir() { String tempDirRootPath = CONF.getTempDirPath(); if (tempDirRootPath == null) { return null; //システム既定のTEMPフォルダに出力する。 } File file = new File(tempDirRootPath); if (!file.exists()) { if (!file.mkdirs()) { throw new RuntimeException( "failed to create download dir." + " file=[" + file.toString() + "]"); } } return file; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy