// Copyright (c) 2008, NTT DATA Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System;
using System.Configuration;
using System.IO;
using System.Net.Mime;
using System.Text;
using System.Web;
using System.Web.Configuration;
using TERASOLUNA.Fw.Common;
using TERASOLUNA.Fw.Common.Logging;

namespace TERASOLUNA.Fw.Web.Controller
{
    /// <summary>
    /// クライアントからのファイルアップロードを伴うビジネスロジック実行要求を
    /// 解決するコントローラクラスです。
    /// </summary>
    public class FileUploadRequestController : BLogicRequestController
    {
        /// <summary>
        /// <see cref="ILog"/> 実装クラスのインスタンスです。
        /// </summary>
        /// <remarks>
        /// ログ出力に利用します。
        /// </remarks>
        private static ILog _log = LogFactory.GetLogger(typeof(FileUploadRequestController));

        /// <summary>
        /// リクエストの入力ストリームから一度に読み込む最大バッファサイズです。
        /// </summary>
        /// <remarks>
        /// <para>
        /// この定数の値は 8192 です。
        /// </para>
        /// </remarks>
        protected const int BUFFER_SIZE = 1024 * 8;

        /// <summary>
        /// content-dispositon ヘッダ名称です。
        /// </summary>
        /// <remarks>
        /// <para>
        /// この定数の値は "content-disposition" です。
        /// </para>
        /// </remarks>
        protected static readonly string HEADER_CONTENT_DISPOSITION = "content-disposition";

        /// <summary>
        /// content-disposition ヘッダの設定値として設定されるファイル名パラメータのキー名です。
        /// </summary>
        /// <remarks>
        /// <para>
        /// この定数の値は "filename" です。
        /// </para>
        /// </remarks>
        protected static readonly string CONTENT_DISPOSITION_FILENAME = "filename";

        /// <summary>
        /// Base64 を表すエンコーディング識別名です。
        /// </summary>
        /// <remarks>
        /// <para>
        /// この定数の値は "B" です。
        /// </para>
        /// </remarks>
        protected static readonly string MIME_ENCODE_TYPE = "B";

        /// <summary>
        /// アップロードファイル名称を <see cref="HttpContext.Items"/> へ
        /// 設定/取得する際にキーとなる文字列です。
        /// </summary>
        /// <remarks>
        /// <para>
        /// この定数の値は "TERASOLUNA_UploadFileName" です。
        /// </para>
        /// </remarks>
        protected static readonly string KEY_CONTEXT_ITEM_UPLOAD_FILE_NAME = "TERASOLUNA_UploadFileName";

        /// <summary>
        /// 一時保管ディレクトリパスを <see cref="HttpContext.Items"/> へ
        /// 設定/取得する際にキーとなる文字列です。
        /// </summary>
        /// <remarks>
        /// <para>
        /// この定数の値は "TERASOLUNA_TempDirectoryPath" です。
        /// </para>
        /// </remarks>
        protected static readonly string KEY_CONTEXT_ITEM_TMP_DIR_INFO = "TERASOLUNA_TempDirectoryPath";

        /// <summary>
        /// 一時保管ディレクトリのルート相対パスを Web.config の Appsettings から
        /// 取得する際にキーとなる文字列です。
        /// </summary>
        /// <remarks>
        /// <para>
        /// この定数の値は "RootTmpDirectory" です。
        /// </para>
        /// </remarks>
        protected static readonly string KEY_APPSETTINGS_ROOT_TMP_DIR = "RootTmpDirectory";

        /// <summary>
        /// HTTP リクエストのヘッダ設定値を検証します。
        /// </summary>
        /// <param name="context"> 
        /// HTTP 要求を処理するために使用する、組み込みのサーバー オブジェクト
        /// (たとえば、 Request 、 Response 、 Session 、および Server )
        /// への参照を提供する <see cref="HttpContext"/> クラスのインスタンス。
        /// </param>
        /// <remarks>
        /// <para>
        /// <see cref="BLogicRequestController.ValidateContentType"/> を呼び出し、
        /// <c>content-type</c> ヘッダ設定値を検証します。
        /// </para>
        /// <para>
        /// <c>content-disposition</c> ヘッダ設定値を検証します。
        /// </para>
        /// <para>
        /// <see cref="GetContentFileName"/> を呼び出し、
        /// <c>content-disposition</c> からアップロードファイル名を取得します。
        /// </para>
        /// </remarks>
        /// <exception cref="InvalidRequestException">
        /// 以下のような場合に例外をスローします。
        /// <list type="bullet">
        /// <item>
        /// <c>content-type</c> ヘッダ が未設定です。
        /// </item>
        /// <item>
        /// <c>content-type</c> ヘッダ 設定値が空文字です。
        /// </item>
        /// <item>
        /// <c>content-type</c> ヘッダ 設定値のフォーマットが不正です。
        /// </item>
        /// <item>
        /// <c>content-type</c> ヘッダ 設定値のメディアタイプが
        /// <c>application/octet-stream</c> ではありません。
        /// </item>
        /// <item>
        /// <c>content-disposition</c> ヘッダ が未設定です。
        /// </item>
        /// <item>
        /// <c>content-disposition</c> ヘッダ 設定値が空文字です。
        /// </item>
        /// <item>
        /// <c>content-disposition</c> ヘッダ 設定値のフォーマットが不正等の理由で
        /// ファイル名称が取得できませんでした。
        /// </item>
        /// </list>
        /// </exception>
        protected override void ValidateRequestHeader(HttpContext context)
        {
            //content-typeヘッダの検証
            ValidateContentType(context.Request.ContentType, MediaTypeNames.Application.Octet);
            
            //content-dispositionヘッダの検証
            string contentDisposition = context.Request.Headers[HEADER_CONTENT_DISPOSITION];
            if (string.IsNullOrEmpty(contentDisposition))
            {
                // content-dispositionヘッダが未設定または値が空の場合
                InvalidRequestException exception = new InvalidRequestException(string.Format(
                    Properties.Resources.E_REQUIRED_HEADER_EMPTY, HEADER_CONTENT_DISPOSITION));
                if (_log.IsErrorEnabled)
                {
                    _log.Error(exception.Message, exception);
                }
                throw exception;
            }

            // アップロードファイル名の取得
            string uploadFileName = GetContentFileName(contentDisposition);

            // 後続の処理でファイル名称を利用するので、保持しておく。
            context.Items[KEY_CONTEXT_ITEM_UPLOAD_FILE_NAME] = uploadFileName;
        }

        /// <summary> 
        /// content-disposition ヘッダ設定値からアップロードファイル名を取得します。 
        /// </summary> 
        /// <param name="contentDisposition"> ヘッダ設定値。</param> 
        /// <returns>アップロードファイル名称。
        /// ファイル名の取得に失敗した場合は、例外をスローし、 null は返しません。</returns> 
        /// <remarks>
        /// <paramref name="contentDisposition"/> を解析し、filename パラメータとして
        /// 設定されている値を <see cref="DecodeFileName"/> を利用して、
        /// デコードされたアップロードファイル名称を取得します。
        /// <example>
        /// 正常にファイル名称を取得が可能 content-disposition ヘッダ設定値の例を示します。
        /// この例の場合、"ふぁいる０１.csv" というファイル名称が取得されます。
        /// <code>
        /// <![CDATA[
        /// attachment; filename=?ISO-2022-JP?B?GyRCJFUkISQkJGsjMCMxGyhCLmNzdg==?=
        /// ]]>
        /// </code>
        /// </example>
        /// </remarks>
        /// <exception cref="InvalidRequestException">
        /// 以下のような場合に例外をスローします。
        /// <list type="bullet">
        /// <item>
        /// Base64でエンコードされたファイル名称のフォーマットが不正であるため、
        /// デコードに失敗しました。
        /// </item>
        /// <item>
        /// 動作しているプラットフォームでサポートされていない文字コードが指定されています。
        /// </item>
        /// </list>
        /// </exception>
        protected virtual string GetContentFileName(string contentDisposition)
        {
            string contentFileName = null;

            string[] paramArray = contentDisposition.Split(';');

            for (int i = 0; i < paramArray.Length; i++)
            {
                int delimiterFirstPos = paramArray[i].IndexOf('=');
                if (delimiterFirstPos >= 0)
                {
                    // '=' までの文字列を取得する
                    string keyStr = paramArray[i].Substring(0, delimiterFirstPos).Trim();
                    
                    // '=' 以降の文字列を取得する
                    string valueStr = paramArray[i].Substring(delimiterFirstPos + 1).Trim();

                    // filename以降の文字列を取得する 
                    if (CONTENT_DISPOSITION_FILENAME.Equals(keyStr, StringComparison.OrdinalIgnoreCase))
                    {
                        if (valueStr.Length == 0)
                        {
                            InvalidRequestException exception = new InvalidRequestException(string.Format(
                                    Properties.Resources.E_NOT_FOUND_FILENAME));
                            if (_log.IsErrorEnabled)
                            {
                                _log.Error(exception.Message, exception);
                            }
                            throw exception;
                        }

                        contentFileName = DecodeFileName(valueStr);
                        break;
                    }
                }
            }

            if (contentFileName == null)
            {
                string message = string.Format(Properties.Resources.E_NOT_FOUND_CONTENT_DISPOSITION_FILENAME);
                InvalidRequestException exception = new InvalidRequestException(message);
                if (_log.IsErrorEnabled)
                {
                    _log.Error(message, exception);
                }
                throw exception;
            }

            return contentFileName;
        } 
 
 
        /// <summary> 
        /// エンコードされているファイル名称をデコードします。 
        /// </summary> 
        /// <param name="encodeStr">エンコードされたファイル名を含む文字列。</param> 
        /// <returns>デコードされたファイル名称。フォーマット不正により、
        /// 生成されない場合は null 参照を返却します。</returns> 
        /// <exception cref="InvalidRequestException">
        /// 以下のような場合に例外をスローします。
        /// <list type="bullet">
        /// <item>
        /// Content-Disposition ヘッダで指定された値であるファイル名のエンコード形式が不正です。
        /// </item>
        /// <item>
        /// Content-Disposition ヘッダで指定された値であるファイル名のエンコーディング名が Base64 ではありません。
        /// </item>
        /// <item>
        /// Content-Disposition ヘッダで指定された値であるファイル名が空文字列です。
        /// </item>
        /// <item>
        /// Base64 でエンコードされたファイル名称の長さが 4 の倍数ではありません。
        /// </item>
        /// <item>
        /// Content-Disposition ヘッダにファイル名が存在しません。
        /// </item>
        /// </list>
        /// </exception>
        protected virtual string DecodeFileName(string encodeStr) 
        { 
            string fileName = null;
            string encodingName = null;
            string encodingTypeName = null;
            string encodedFileName = null;
 
            // Conent-Dispositionの要素の取得
            string[] strArray = encodeStr.Split('?');
            if (strArray.Length < 5)
            {
                InvalidRequestException exception = new InvalidRequestException(string.Format(
                    Properties.Resources.E_INVALID_CONTENT_DISPOSITION_FILENAME, encodeStr));
                if (_log.IsErrorEnabled)
                {
                    _log.Error(exception.Message, exception);
                }
                throw exception;
            }

            encodingName = strArray[1];
            encodingTypeName = strArray[2];
            encodedFileName = strArray[3];

            // エンコーディング名のnull ・ 空文字列チェック
            if (string.IsNullOrEmpty(encodingName))
            {
                InvalidRequestException exception = new InvalidRequestException(string.Format(
                    Properties.Resources.E_INVALID_CONTENT_DISPOSITION_FILENAME, encodeStr));
                if (_log.IsErrorEnabled)
                {
                    _log.Error(exception.Message, exception);
                }
                throw exception;
            }

            // エンコーディングタイプのチェック
            if(!MIME_ENCODE_TYPE.Equals(encodingTypeName, StringComparison.OrdinalIgnoreCase))
            {
                InvalidRequestException exception = new InvalidRequestException(string.Format(
                    Properties.Resources.E_INVALID_CONTENT_DISPOSITION_FILENAME, encodeStr));
                if (_log.IsErrorEnabled)
                {
                    _log.Error(exception.Message, exception);
                }
                throw exception;
            }
                
            // エンコードされたファイル名の空文字列チェック
            if(string.IsNullOrEmpty(encodedFileName))
            {
                InvalidRequestException exception = new InvalidRequestException(string.Format(
                    Properties.Resources.E_INVALID_CONTENT_DISPOSITION_FILENAME, encodeStr));
                if (_log.IsErrorEnabled)
                {
                    _log.Error(exception.Message, exception);
                }
                throw exception;
            }
                       
            // バイト配列のファイル名を取得
            byte[] byteArray = null;
            try
            {
                byteArray = Convert.FromBase64String(encodedFileName);
            }
            catch (FormatException fe)
            {
                InvalidRequestException exception = new InvalidRequestException(string.Format(
                    Properties.Resources.E_INVALID_CONTENT_DISPOSITION_FILENAME, encodeStr), fe);
                if (_log.IsErrorEnabled)
                {
                    _log.Error(exception.Message, exception);
                }
                    throw exception;
            }

            // ファイル名の取得
            try
            {
                fileName = Encoding.GetEncoding(encodingName).GetString(byteArray);
            }
            catch (ArgumentException ae)
            {
                InvalidRequestException exception = new InvalidRequestException(string.Format(
                    Properties.Resources.E_INVALID_CONTENT_DISPOSITION_FILENAME, encodeStr), ae);
                if (_log.IsErrorEnabled)
                {
                    _log.Error(exception.Message, exception);
                }
                throw exception;
            }

            return fileName; 
        }

        /// <summary>
        /// リクエストのボディ部を解析します。
        /// </summary>
        /// <param name="context"> 
        /// HTTP 要求を処理するために使用する、組み込みのサーバー オブジェクト
        /// (たとえば、 Reques t、 Response 、 Session 、および Server )
        /// への参照を提供する <see cref="HttpContext"/> クラスのインスタンス。
        /// </param>
        /// <remarks>
        /// <para>
        /// <see cref="HttpContext.Items"/> の 
        /// <see cref="KEY_CONTEXT_ITEM_UPLOAD_FILE_NAME"/> から
        /// アップロードファイル名称を取得し、<see cref="CreateTemporaryDirectory"/> を呼び出し、
        /// 一時保管ディレクトリを生成します。この時に生成した一時保管ディレクトリのパスを <see cref="HttpContext.Items"/> の <see cref="KEY_CONTEXT_ITEM_TMP_DIR_INFO"/> に格納します。
        /// </para>
        /// <para>
        /// 生成した一時保管ディレクトリの配下に取得したアップロードファイル名称のファイルを
        /// <see cref="CreateTemporaryFile"/> を呼び出し生成します。
        /// </para>
        /// <para>
        /// 生成した一時ファイルのファイルパス情報を元に、<see cref="UploadFileInfo"/> 生成し、
        /// <see cref="HttpContext.Items"/> の 
        /// <see cref="BLogicRequestController.KEY_CONTEXT_ITEM_INPUTPARAM"/> に 
        /// 生成した <see cref="UploadFileInfo"/> を格納します。
        /// </para>
        /// </remarks>
        /// <exception cref="InvalidRequestException">
        /// 以下のような場合に例外をスローします。
        /// <list type="bullet">
        /// <item>
        /// <see cref="HttpContext.Items"/> の 
        /// <see cref="KEY_CONTEXT_ITEM_UPLOAD_FILE_NAME"/> に、
        /// ファイル名称として使用できない文字が使われています。
        /// </item>
        /// <item>
        /// POST されたデータサイズが最大長を超えています。
        /// </item>
        /// <item>
        /// 生成対象のファイルパスのサイズが上限を超えています。
        /// </item>
        /// </list>
        /// </exception>
        /// <exception cref="TerasolunaException">
        /// 以下のような場合に例外をスローします。
        /// <list type="bullet">
        /// <item>
        /// 一時保管ルートディレクトリへのファイル生成権限が設定されていません。
        /// </item>
        /// <item>
        /// 一時保管ルートディレクトリへのディレクトリ生成権限が設定されていません。
        /// </item>
        /// <item>
        /// リクエストの入力ストリームの読み込み中にクライアントとの接続が切断されました。
        /// </item>
        /// </list>
        /// </exception>
        /// <exception cref="ConfigurationErrorsException">
        /// 以下のような場合に例外をスローします。
        /// <list type="bullet">
        /// <item>
        /// Web.config の AppSettings に設定されているアップロード
        /// 一時保管ルートディレクトリのパスが不正です。
        /// </item>
        /// <item>
        /// Web.config の AppSettings に設定されているアップロード
        /// 一時保管ルートディレクトリが絶対パスではありません。
        /// </item>
        /// <item>
        /// Web.config の AppSettings に設定されているアップロード
        /// 一時保管ルートディレクトリが存在しません。
        /// </item>
        /// <item>
        /// Web.config の AppSettings に設定されているアップロード
        /// 一時保管ルートディレクトリのパスの設定値がファイルパスとして設定できる
        /// ディレクトリパスの最大サイズを超えています。
        /// </item>
        /// <item>
        /// Web.config の AppSettings に設定されているアップロード
        /// 一時保管ルートディレクトリパスの設定値にディレクトリ名称として
        /// 指定できない文字が含まれています。
        /// </item>
        /// </list>
        /// </exception>
        protected override void ParseRequestBody(HttpContext context)
        {
            string fileName = context.Items[KEY_CONTEXT_ITEM_UPLOAD_FILE_NAME] as string;

            // 一時保管ディレクトリを生成する。
            DirectoryInfo tempDirInfo = CreateTemporaryDirectory(context.Request);
            // 一時保管ディレクトリのパスを保持しておく。
            context.Items[KEY_CONTEXT_ITEM_TMP_DIR_INFO] = tempDirInfo.FullName;

            // 一時保管ディレクトリにアップロードファイルを生成する。
            FileInfo fileInfo = CreateTemporaryFile(fileName, context.Request, tempDirInfo);

            // Upload専用のDataSetを生成し、アップロードファイル情報を設定。
            UploadFileInfo uploadFileInfo = new UploadFileInfo();
            uploadFileInfo.TempFiles.AddTempFilesRow(fileInfo.FullName);
            uploadFileInfo.ApplicationRoot.AddApplicationRootRow(context.Server.MapPath("~"));

            // 生成したUpload専用のDataSetをビジネスロジッククラスの入力パラメータとして
            // context.Itemsに設定。
            context.Items[KEY_CONTEXT_ITEM_INPUTPARAM] = uploadFileInfo;
        }

        /// <summary>
        /// リクエストの入力ストリームからアップロードデータの
        /// 一時ファイルを指定のディレクトリに生成します。
        /// </summary>
        /// <param name="fileName">生成ファイル名称。</param>
        /// <param name="request">リクエスト情報。</param>
        /// <param name="targetDirectory">一時ファイルを生成する対象のディレクトリ情報。</param>
        /// <returns>生成した一時ファイルの情報。</returns>
        /// <remarks>
        /// <paramref name="targetDirectory"/> に指定されたディレクトリの直下に 
        /// <paramref name="fileName"/> に指定されたファイル名称で一時ファイルを作成し、 
        /// <paramref name="request"/> の <see cref="HttpRequest.InputStream"/> から読み込んだ、
        /// バイナリデータを一時ファイルとして書き込みます。
        /// </remarks>
        /// <exception cref="InvalidRequestException">
        /// 以下のような場合に例外をスローします。
        /// <list type="bullet">
        /// <item>
        /// <paramref name="fileName"/> に、ファイル名称として使用できない文字が指定されています。
        /// </item>
        /// <item>
        /// POSTされたデータサイズが最大長を超えています。
        /// </item>
        /// <item>
        /// 生成対象のファイルパスのサイズが上限を超えています。
        /// </item>
        /// </list>
        /// </exception>
        /// <exception cref="TerasolunaException">
        /// 以下のような場合に例外をスローします。
        /// <list type="bullet">
        /// <item>
        /// 一時保管ルートディレクトリへのファイル生成権限が設定されていません。
        /// </item>
        /// <item>
        /// リクエストの入力ストリームの読み込み中にクライアントとの接続が切断されました。
        /// </item>
        /// </list>
        /// </exception>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
        protected virtual FileInfo CreateTemporaryFile(string fileName, HttpRequest request, DirectoryInfo targetDirectory)
        {
            FileInfo fileInfo = null;
            string absoluteTmpDir = targetDirectory.FullName;

            // 作成したディレクトリの中にファイルを作成し、アップロードデータを書き込む。
            try
            {
                string absoluteFilePath = absoluteTmpDir + "\\" + fileName;

                fileInfo = new FileInfo(absoluteFilePath);

                // 入力ストリームに設定されているバイナリ情報を一時保管ディレクトリ内に
                // ファイルを生成して書き込む。
                using (FileStream stream = fileInfo.Open(FileMode.CreateNew))
                {
                    byte[] buffer = new byte[BUFFER_SIZE];
                    int readSize = request.InputStream.Read(buffer, 0, BUFFER_SIZE);
                    while (readSize > 0)
                    {
                        stream.Write(buffer, 0, readSize);
                        readSize = request.InputStream.Read(buffer, 0, BUFFER_SIZE);
                    }
                }
            }
            catch (ArgumentException ae)
            {
                // Content-Dispositionヘッダから取得のファイル名のファイルがファイル名として使用できない文字が使われているなど。
                InvalidRequestException exception = new InvalidRequestException(string.Format(
                    Properties.Resources.E_CREATE_UPLOAD_FILENAME, fileName), ae);
                if (_log.IsErrorEnabled)
                {
                    _log.Error(exception.Message, exception);
                }
                throw exception;
            }
            catch (HttpException he)
            {
                // POSTデータのサイズが最大長を超えた場合。
                InvalidRequestException exception = new InvalidRequestException(Properties.Resources.E_INVALID_REQUEST + he.Message, he);
                if (_log.IsErrorEnabled)
                {
                    _log.Error(exception.Message, exception);
                }
                throw exception;
            }
            catch (UnauthorizedAccessException uae)
            {
                // 設定された一時保管ルートディレクトリにファイルを生成できない場合
                TerasolunaException exception = new TerasolunaException(Properties.Resources.E_CREATE_UPLOAD_TMPFILE_FAILED, uae);
                if (_log.IsErrorEnabled)
                {
                    _log.Error(exception.Message, exception);
                }
                throw exception;
            }
            catch (IOException ioe)
            {
                TerasolunaException exception = new TerasolunaException(Properties.Resources.E_CREATE_UPLOAD_TMPFILE_FAILED, ioe);
                if (_log.IsErrorEnabled)
                {
                    _log.Error(exception.Message, exception);
                }
                throw exception;
            }

            return fileInfo;
        }

        /// <summary>
        /// アップロードデータの一時ファイルの生成対象とする、
        /// リクエスト毎に一意な一時保管ディレクトリを作成します。
        /// </summary>
        /// <param name="request">リクエスト情報。</param>
        /// <returns>生成したディレクトリ情報を格納した <see cref="DirectoryInfo"/> オブジェクト。</returns>
        /// <remarks>
        /// Web.config の AppSettings の キー <see cref="KEY_APPSETTINGS_ROOT_TMP_DIR"/> に設定された
        /// 一時保管ディレクトリのルートとなるディレクトリを取得し、取得したルートディレクトリ直下に
        /// <seealso cref="Guid.NewGuid"/> により取得したグローバル識別子をディレクトリ名とする
        /// リクエスト毎に一意な一時保管ディレクトリを生成します。
        /// </remarks>
        /// <exception cref="ConfigurationErrorsException">
        /// 以下のような場合に例外をスローします。
        /// <list type="bullet">
        /// <item>
        /// Web.config の AppSettings に設定されているアップロード
        /// 一時保管ルートディレクトリのパスが不正です。
        /// </item>
        /// <item>
        /// Web.config の AppSettings に設定されているアップロード
        /// 一時保管ルートディレクトリが絶対パスではありません。
        /// </item>
        /// <item>
        /// Web.config の AppSettings に設定されているアップロード
        /// 一時保管ルートディレクトリが存在しません。
        /// </item>
        /// <item>
        /// Web.config の AppSettings に設定されているアップロード
        /// 一時保管ルートディレクトリのパスの設定値がファイルパスとして設定できる
        /// ディレクトリパスの最大サイズを超えています。
        /// </item>
        /// <item>
        /// Web.config の AppSettings に設定されているアップロード
        /// 一時保管ルートディレクトリパスの設定値にディレクトリ名称として
        /// 指定できない文字が含まれています。
        /// </item>
        /// </list>
        /// </exception>
        /// <exception cref="TerasolunaException">
        /// 以下のような場合に例外をスローします。
        /// <list type="bullet">
        /// <item>
        /// 一時保管ルートディレクトリへのディレクトリ生成権限が設定されていません。
        /// </item>
        /// <item>
        /// ディレクトリの生成に失敗しました。
        /// </item>
        /// </list>
        /// </exception>
        protected virtual DirectoryInfo CreateTemporaryDirectory(HttpRequest request)
        {
            string tmpDir = WebConfigurationManager.AppSettings[KEY_APPSETTINGS_ROOT_TMP_DIR] as string;
            if (tmpDir != null)
            {
                // Web.configに指定されたパスが正しいかどうかチェックする。
                if (tmpDir.Length == 0)
                {
                    ConfigurationErrorsException exception = new ConfigurationErrorsException(Properties.Resources.E_INVALID_UPLOAD_TMPDIR);
                    if (_log.IsErrorEnabled)
                    {
                        _log.Error(exception.Message, exception);
                    }
                    throw exception;
                }
                try
                {
                    if (!Path.IsPathRooted(tmpDir))
                    {
                        // 一時ファイル格納用のルートディレクトリの設定値が絶対パスでない場合
                        ConfigurationErrorsException exception = new ConfigurationErrorsException(Properties.Resources.E_INVALID_UPLOAD_TMPDIR);
                        if (_log.IsErrorEnabled)
                        {
                            _log.Error(exception.Message, exception);
                        }
                        throw exception;
                    }
                }
                catch (ArgumentException ae)
                {
                    // 一時ファイル格納用のルートディレクトリの設定値にディレクトリパスに
                    // 利用不可な文字列が含まれている場合
                    ConfigurationErrorsException exception = new ConfigurationErrorsException(Properties.Resources.E_INVALID_UPLOAD_TMPDIR, ae);
                    if (_log.IsErrorEnabled)
                    {
                        _log.Error(exception.Message, exception);
                    }
                    throw exception;
                }
                if (!Directory.Exists(tmpDir))
                {
                    // 指定された一時ファイル格納用のルートディレクトリが存在しない場合
                    ConfigurationErrorsException exception = new ConfigurationErrorsException(Properties.Resources.E_INVALID_UPLOAD_TMPDIR);
                    if (_log.IsErrorEnabled)
                    {
                        _log.Error(exception.Message, exception);
                    }
                    throw exception;
                }
            }
            else
            {
                // 設定されていない場合は既定値として、システムの一時ディレクトリのパスを利用する。
                tmpDir = Path.GetTempPath();
            }

            // リクエスト毎に一意な一時保管ディレクトリパスを生成。
            // 重複する確率は低いが重複しないことは保障されていないので、
            // Directory存在チェックをして重複しないようにしている。
            string absoluteTmpDir = null;
            do
            {
                absoluteTmpDir = Path.Combine(tmpDir, Guid.NewGuid().ToString());
            }
            while (Directory.Exists(absoluteTmpDir));


            // ディレクトリを作成。
            DirectoryInfo createdDirInfo;
            try
            {
                createdDirInfo = Directory.CreateDirectory(absoluteTmpDir);
            }
            catch (UnauthorizedAccessException uae)
            {
                // 設定された一時保管ルートディレクトリに書き込みできない場合
                TerasolunaException exception = new TerasolunaException(Properties.Resources.E_CREATE_UPLOAD_TMPFILE_FAILED, uae);
                if (_log.IsErrorEnabled)
                {
                    _log.Error(exception.Message, exception);
                }
                throw exception;
            }
            catch (IOException ioe)
            {
                TerasolunaException exception = new TerasolunaException(Properties.Resources.E_CREATE_UPLOAD_TMPFILE_FAILED, ioe);
                if (_log.IsErrorEnabled)
                {
                    _log.Error(exception.Message, exception);
                }
                throw exception;
            }

            return createdDirInfo;
        }

        /// <summary>
        /// 入力値の検証を行います。
        /// </summary>
        /// <param name="context"> 
        /// HTTP 要求を処理するために使用する、組み込みのサーバー オブジェクト
        /// (たとえば、 Request 、 Response 、 Session 、および Server )
        /// への参照を提供する <see cref="HttpContext"/> クラスのインスタンス。
        /// </param>
        /// <remarks>
        /// 入力値の検証を行わないように、<see cref="BLogicRequestController.ValidateInputData"/>
        /// をオーバーライドします。
        /// </remarks>
        /// <returns>常に true を返却します。</returns>
        protected override bool ValidateInputData(HttpContext context)
        {
            // アップロード時は入力値検証は行わないため、常にtrueを返却。
            return true;
        }

        /// <summary>
        /// 業務エラー時のレスポンス情報を書き込みます。
        /// </summary>
        /// <param name="context"> 
        /// HTTP 要求を処理するために使用する、組み込みのサーバー オブジェクト
        /// (たとえば、 Request 、 Response 、 Session 、および Server )
        /// への参照を提供する <see cref="HttpContext"/> クラスのインスタンス。
        /// </param>
        /// <remarks>
        /// <see cref="Release"/> を呼び出し、生成した一時保管ディレクトリおよび
        /// その配下のファイルを削除した後、
        /// <see cref="BLogicRequestController.WriteBLogicErrorResponse"/> を呼び出し
        /// レスポンス情報を書き込みます。
        /// </remarks>
        protected override void WriteBLogicErrorResponse(HttpContext context)
        {
            // レスポンス書き込み前に一時ファイルの削除を行う。
            Release(context);
            base.WriteBLogicErrorResponse(context);
        }

        /// <summary>
        /// 正常終了時のレスポンスを書き込みます。
        /// </summary>
        /// <param name="context"> 
        /// HTTP 要求を処理するために使用する、組み込みのサーバー オブジェクト
        /// (たとえば、 Request 、 Response 、 Session 、および Server )
        /// への参照を提供する <see cref="HttpContext"/> クラスのインスタンス。
        /// </param>
        /// <remarks>
        /// <see cref="Release"/> を呼び出し、生成した一時保管ディレクトリおよび
        /// その配下のファイルを削除した後、
        /// <see cref="BLogicRequestController.WriteSuccessResponse"/> を呼び出し
        /// レスポンス情報を書き込みます。
        /// </remarks>
        protected override void WriteSuccessResponse(HttpContext context)
        {
            // レスポンス書き込み前に一時ファイルの削除を行う。
            Release(context);
            base.WriteSuccessResponse(context);
        }

        /// <summary>
        /// システムエラー時のレスポンス情報の書き込みを行います。
        /// </summary>
        /// <param name="context"> 
        /// HTTP 要求を処理するために使用する、組み込みのサーバー オブジェクト
        /// (たとえば、 Request 、 Response 、 Session 、および Server )
        /// への参照を提供する <see cref="HttpContext"/> クラスのインスタンス。
        /// </param>
        /// <param name="ex">発生例外オブジェクト。</param>
        /// <remarks>
        /// <para>
        /// <see cref="Release"/> を呼び出し、生成した一時保管ディレクトリおよび
        /// その配下のファイルを削除した後、 <see cref="BLogicRequestController.WriteSystemErrorResponse"/> を呼び出し
        /// レスポンス情報を書き込みます。
        /// </para>
        /// <para>
        /// <see cref="Release"/> において発生した例外は補足し、
        /// レスポンス情報の生成元となる例外を置き換えて <see cref="BLogicRequestController.WriteSystemErrorResponse"/> に渡されます。
        /// </para>
        /// </remarks>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        protected override void WriteSystemErrorResponse(HttpContext context, Exception ex)
        {
            // レスポンス書き込み前に一時ファイルの削除を行う。
            Exception targetEx = ex;
            try
            {
                Release(context);
            }
            catch (Exception newEx)
            {
                if (_log.IsWarnEnabled)
                {
                    _log.Warn(Properties.Resources.W_NOT_DELETE_FILE, newEx);
                }

                // 後処理で例外が発生した場合はここでキャッチした例外を
                // もとにレスポンスを作成します。
                targetEx = newEx;
            }
            base.WriteSystemErrorResponse(context, targetEx);
        }

        /// <summary>
        /// リクエスト処理中に利用したリソースの後処理を行います。
        /// </summary>
        /// <param name="context"> 
        /// HTTP 要求を処理するために使用する、組み込みのサーバー オブジェクト
        /// (たとえば、 Request 、 Response 、 Session 、および Server )
        /// への参照を提供する <see cref="HttpContext"/> クラスのインスタンス。
        /// </param>
        /// <remarks>
        /// <para>
        /// リクエスト処理中に生成した一時保管ディレクトリ
        /// および、その配下のサブディレクトリ、ファイルを削除します。
        /// </para>
        /// <para>
        /// 対象となるディレクトリパスは<see cref="HttpContext.Items"/> の <see cref="KEY_CONTEXT_ITEM_TMP_DIR_INFO"/> から取得します。
        /// </para>
        /// </remarks>
        /// <para>
        /// <see cref="HttpContext.Items"/> の
        /// <see cref="KEY_CONTEXT_ITEM_TMP_DIR_INFO"/> が設定されていない場合や、
        /// 対象となるディレクトリが見つからない場合は、無視されます。
        /// </para>
        /// <exception cref="TerasolunaException">
        /// アップロードファイルの一時ファイルを削除することができません。
        /// </exception>
        protected virtual void Release(HttpContext context)
        {
            // 一時ファイル保管ディレクトリを削除します。
            string tempDirPath = context.Items[KEY_CONTEXT_ITEM_TMP_DIR_INFO] as string;
            if (tempDirPath != null)
            {
                if (Directory.Exists(tempDirPath))
                {
                    try
                    {
                        Directory.Delete(tempDirPath, true);
                    }
                    catch (IOException ioe)
                    {
                        TerasolunaException exception = new TerasolunaException(Properties.Resources.E_DELETE_TMPFILE_FAILED, ioe);
                        if (_log.IsErrorEnabled)
                        {
                            _log.Error(exception.Message, exception);
                        }
                        throw exception;
                    }
                    catch (UnauthorizedAccessException uae)
                    {
                        TerasolunaException exception = new TerasolunaException(Properties.Resources.E_DELETE_TMPFILE_FAILED, uae);
                        if (_log.IsErrorEnabled)
                        {
                            _log.Error(exception.Message, exception);
                        }
                        throw exception;
                    }
                }
            }
        }
    }
}
