カテゴリー「プログラミング」の記事

Java:CSVパーサを作る(その3) - RFC4180対応 後編

←CSVパーサを作る(その2) - RFC4180対応 前編

前回の記事で未実装だった「レコードの確定」と「レコードのフィールドへの分割」を行うメソッドについて実装を行い,CSVパーサを完成させます。

【実装の考察】
●レコードの確定
レコード確定では,入力テキストデータに対して,ダブルクォーテーション(二重引用符)のペアをヒントに各レコードの末尾を確定して,レコードの切り分けを行います。処理手順は以下のようになります。

  1. BufferedReaderのreadLineメソッドを使ってテキストを1行分(現在の位置から改行が現れるまで,またはファイルの終了まで)取り出して,行の先頭からダブルクォーテーションを探す。見つからなければその1行を1レコードとして確定する。(readLineメソッドは「CR」「LF」「CRLF」を改行と認識するので,「CRLF以外の改行も考慮する」仕様の要求を満たしています。)
  2. ダブルクォーテーションが見つかった場合,ペアになる後ろのダブルクォーテーションを探す。後ろのダブルクォーテーションが見つかったらその位置から後続のダブルクォーテーションのペアを探す。この手順を行の終わりまで繰り返す。ダブルクォーテーションペアの外側で行が終了していれば,その行を1レコードとして確定する。
  3. ペアの後ろのダブルクォーテーションが見つからずにダブルクォーテーションペアの内側で改行に達したら,その改行を文字列フィールドに含まれる改行と見なしてBufferedReaderのreadLineメソッドより次の行を取り出して前の行と連結し,ペアの後ろのダブルクォーテーションを探すところから処理から継続する。これをダブルクォーテーションペアの外側で改行が見つかるまで繰り返す。後ろのダブルクォーテーションが見つからずにファイルの末尾に達したときは,ファイルの末尾にダブルクォーテーションを付加して行の末尾とする。この連結した行を1レコードとして確定する。
以上の処理を行うコードは次のようになります。(2009年6月22日改訂)
//------------------------------------------------------------------
/**
 * BufferedReaderから1レコード分のテキストを取り出す。
 * @param reader 行データを取り出すBufferedReader。
 * @return 1レコード分のテキスト。
 * @throws IOException 入出力エラー
 */
private String buildRecord (
  BufferedReader reader)
  throws IOException
{
  String  result = reader.readLine();
  int    pos;
  if (result != null && 0 < result.length() &&
    0 <= (pos = result.indexOf("\"")))
  {
    boolean  inString = true;
    String   rawline = result;
    String   newline = null;
    StringBuffer buff = new StringBuffer(1024);
    while (true) {
      while (0 <= (pos = rawline.indexOf("\"", ++pos))) {
        inString = !inString;
      }
      if (inString && (newline = reader.readLine()) != null) {
        buff.append(rawline);
        buff.append("\n");
        pos = -1;
        rawline = newline;
        continue;
      }
      else {
        if (inString || 0 < buff.length()) {
          buff.append(rawline);
          if (inString) {
            buff.append("\"");
          }
          result = buff.toString();
        }
        break;
      }
    }
  }
  return result;
}
注意)上記コードでは,フィールド内の改行をLF("\n")に決め打ちしていますが,実際にはCSVパーサの出力結果を受け取るプログラム(データベースなど)が要求する改行コードを挿入する必要があります。
使用すべき改行コードの選択は,プログラムが稼動するプラットフォームの改行コードを取得する
「String returnStr = System.getProperty("line.separator");」
が知られていますが,実行環境によってはCSVパーサとCSVパーサの出力結果を受け取るプログラムが異なるOSで稼動する場合もありうるので,正解は常に一つとは限りません。
そのため,厳密に処理するには出力する改行コードをプロパティファイルで指定できるようにするなどの工夫が必要です。

●レコードのフィールドへの分割
フィールド分割では,レコードに切り分けたテキストに対して,最初にレコード全体をカンマで分割し,分割した個々の文字列にダブルクォーテーションをヒントに必要な連結やエスケープ処理を行って,個々のフィールドを確定します。処理手順は以下のようになります。
  1. レコード全体をStringクラスのsplitメソッドを使ってカンマで分割し,分割した個々の文字列データを順に先頭からダブルクォーテーションを探す。見つからなければその文字列は1フィールドとして確定する。
  2. ダブルクォーテーションが見つかったら,次のダブルクォーテーションを探す。次のダブルクォーテーションの直後にダブルクォーテーションがあれば,エスケープされたダブルクォーテーションとして処理し,そうでなければフィールドの終わりと見なす。
  3. フィールドで後ろのダブルクォーテーションが見つからない場合,フィールドに含まれるカンマでsplitメソッドが分割したものと見なして,フィールドの後ろに(splitメソッドが削除した)カンマと次のフィールドを連結する。
  4. フィールドの開始と終了のダブルクォーテーションは削除する。
以上の処理を行うコードは次のようになります。(2009年6月22日改訂)
//------------------------------------------------------------------
/**
 * 1レコード分のテキストを分割してフィールドの配列にする。
 * @param src 1レコード分のテキストデータ。
 * @param dest フィールドの配列の出力先。
 */
private void splitRecord (
  String    src,
  LinkedList dest)
{
  String[]  columns = src.split(",");
  int     maxlen = columns.length;
  int     startPos, endPos, columnlen;
  StringBuffer buff = new StringBuffer(1024);
  String   column;
  boolean  isInString, isEscaped;

  for (int index = 0; index < maxlen; index++) {
    column = columns[index];
    if ((endPos = column.indexOf("\"")) < 0) {
      dest.addLast(column);
    }
    else {
      isInString = (endPos == 0);
      isEscaped = false;
      columnlen = column.length();
      buff.setLength(0);
      startPos = (isInString)? 1: 0;
      while (startPos < columnlen) {
        if (0 <= (endPos = column.indexOf("\"", startPos))) {
          buff.append((startPos < endPos)?
                column.substring(startPos, endPos): isEscaped? "\"": "");
          isEscaped = !isEscaped;
          isInString = !isInString;
          startPos = ++endPos;
        }
        else {
          buff.append(column.substring(startPos));
          if (isInString && index < maxlen - 1) {
            column = columns[++index];
            columnlen = column.length();
            buff.append(",");
            startPos = 0;
          }
          else {
            break;
          }
        }
      }
      dest.addLast(buff.toString());
    }
  }
}


以上で「RFC4180対応のCSVパーサ」はひととおりできあがったかと思います。

■関連書籍をAmazonで検索:[Java]
ソースコードリーディングから学ぶ Javaの設計と実装



にほんブログ村 IT技術ブログへ にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキングへ ←この記事が役に立ったという方はクリックお願いします。
▼CSVパーサを作る[その1][その2][その3]
宿・ホテル予約ならじゃらんnet

| | トラックバック (0)

Java:CSVパーサを作る(その2) - RFC4180対応 前編

←CSVパーサを作る(その1) - 簡易パーサ

CSVの仕様について調べてみると「K3フォーマット」と「RFC4180日本語訳)」が見つかります。K3フォーマットとCSVの違いについては「桐ver.8活用ガイド」というドキュメントの「読み込み K3フォーマット」と「読み込み CSV」という項目で記述されています。
ここでは前回の記事で作った簡易CSVパーサを発展させて,今回と次回の2回に分けてRFC4180で規定されたCSVが読み込めるCSVパーサを実装してみたいと思います。

【RFC4180で規定されたCSVの仕様】

  1. レコードの区切りは改行である。改行コードはCRLFだが他の値も考慮すべきである。
  2. ファイルの末尾に改行はあってもなくても良い。
  3. ファイルの先頭行にヘッダ行があっても良い。ヘッダ行の有無はMIMEタイプ「text/csv」のパラメータ「header」で示す。header パラメータの値は「present」(存在する),または「absent」(存在しない)のどちらかである。「header」パラメータを使わない場合はヘッダの有無を自ら決める。
  4. 各行にはカンマ「,」で区切ったフィールド列が必ずある(つまり空行はない)。フィールド数はすべての行で同じ。フィールド中の空白は無視しない。行末のカンマは行の終わりを表さない(その後ろにNULLフィールドがもう1つある)。
  5. 各フィールドはダブルクォーテーション(二重引用符)「"」で囲んでも良いし囲まなくても良い。
  6. 改行,ダブルクォーテーション,カンマを含むフィールドはダブルクォーテーションで囲む。
  7. フィールドデータにダブルクォーテーションを含む場合,ダブルクォーテーションを2つ続けることでエスケープする。
  8. 文字コードは一般にUS-ASCIIを使う,他の文字コードの使用を明示するにはMIMEタイプ「text/csv」のパラメータ「charset」で指定する。
【参考:K3フォーマットとRFC4180の違い】
  1. フィールドをダブルクォーテーションで,
    • RFC4180:「囲んでも囲まなくてもよい」(数値型を囲んでもよい)
    • K3フォーマット:「文字列型データは囲む,数値型データは囲まない」(数値型,文字型以外は想定なし)
  2. 行の先頭が「#」だった場合,
    • RFC4180:規定なし。よって通常のレコードとして解釈する。
    • K3フォーマット:その行をレコードとして解釈せずに以下のように解釈する:
      • 行頭が「#」の場合:制御コマンド記述行
      • 行頭が「##」の場合:コメント行
      • 行頭が「###」の場合:データ終了。この行以降のデータは取り込まない。

【仕様の考察】
CSVパーサのように外部からデータを取り込んでこれを処理するプログラムの場合,受け取ったデータがRFC4180の仕様に沿っていた場合に正常に処理できることはもちろん,仕様とは異なるデータを受け取った場合にどのように処理するかを決める必要があります。CSVパーサの実装を検討する上で考慮すべき事項として以下のようなものが考えられます。

  1. 画像データやPDFなど,CSVではないデータを受け取った場合にどうするか。
  2. RFC4180の仕様から若干外れたCSVデータ,たとえば次のようなデータを受け取った場合にこれをどのように処理するか:
    • CRLF以外の改行コードを受け取ったらどうするか。複数の種類の改行コードが混在していたらどうするか。
    • 仕様では「空行はない」とされているが,空行があった場合はどうするか。
    • 仕様では「フィールド数はすべての行で同じ」となっているが,フィールド数にばらつきがあった場合はどうするか。
    • 仕様で「改行,ダブルクォーテーション,カンマを含むフィールドはダブルクォーテーションで囲む」とあるが,囲まれていない場合はどうするか。フィールド中の改行とカンマについてはダブルクォーテーションなしでは判定は不可能だが,ダブルクォーテーションについてはエスケープされているかどうかで判定できる可能性がある。
    • ダブルクォーテーションで囲まれていないフィールドの途中にエスケープされていないダブルクォーテーションが出現した場合はどのように解釈するか。
    • 行頭が「#」の場合どうするか。RFC4180では行頭が「#」の場合については決められていないが,K3フォーマットではこの行はレコードとして解釈せずにコメント行または制御行として扱う。
    • 文字コードはどうするか。RFC4180ではUS-ASCIIがデフォルトだが,これだと日本語が扱えないのでデフォルトを変えるべきではないか。
これらを考慮する場合に重要なのが「リーズナブル」という考え方です。これは具体的には以下のような考え方です。
  1. なるべく少ないコードでより多くの要求を満たす実装方法を選択する。
  2. 重要ではない小さな要求のために大量のコードが必要になる実装方法は避ける。
  3. 何かに対応するために大量のコードが必要になったら「この実装方法には何か根本的な問題があるのではないか?」と考える。
  4. 実現不可能なもの,実現が困難なものはどこかで諦める。その場合はユーザが納得できる代替案を探す。
以上を踏まえて,CSVパーサを実現するためにRFC4180に対して以下のように追加修正を行います。
  1. CSVパーサの実装において,CSV以外のデータを受け取った場合の処理については特に考慮しません。この問題は,CSVパーサが呼ばれる以前に,たとえばファイル名の拡張子でエラー判定するなどして解決するものとします。
  2. フィールドに含まれる空白のうち,フィールドの先頭と末尾の空白は無視(削除)できることにします。これは特に数値を数値データとして取り込む場合に必要です。
  3. MIMEタイプのパラメータは読み込んだCSVデータの中で判断できないため,パーサでは対応しません。
  4. 行頭に「#」がある行はコメント行とします。ヘッダ行はこのコメント行で実現するものとします。対応すべき制御コマンドもないので「#」1つでもコメント行とします。
  5. フィールド内にエスケープされたダブルクォーテーションが含まれる場合の動作は次のようにします。
    • エスケープされたダブルクォーテーションの前後にデータがある場合は,フィールド全体を囲むダブルクォーテーションはあってもなくても良い。
    • フィールド内のデータがエスケープされたダブルクォーテーションだけの場合,全体を囲むダブルクォーテーションが必須。(つまり連続する4つのダブルクォーテーションが必要。)
    • 逆にフィールド内のデータがダブルクォーテーションの1ペアだけだった場合,パーサはこれをダブルクォーテーションで囲んだNULLフィールドとして扱う。
  6. レコードの区切りは改行(またはファイル末尾)ですが,ダブルクォーテーションで囲んだフィールドに改行が入っていることがあるため,改行がレコードの区切りとは限りません。
  7. フィールド数は固定であることを期待しません。また空行もありうるものとします。空行はスキップします。フィールドが多いときは余りは無視します。フィールドが足りないときはNULLフィールドで補うものとします。
  8. ダブルクォーテーションを2つ続けることによりダブルクォーテーションをエスケープする仕様のため,ダブルクォーテーションは1フィールド及び1レコード内で必ず偶数になります。
  9. 文字コードはUS-ASCIIではなく「Windows-31J/MS932」とします。これはExcelで開けるCSVの文字コードに合わせています。
【実装の考察】
処理手順としては,入力したテキストデータを先頭から1レコードづつ切り出し,このレコードをフィールドに分割する,これを入力テキストの最後まで繰り返す,という流れになりますが,処理手順を検討した結果,以下の流れで処理を行うことにします。
  1. レコードの確定
    入力テキストを1行づつ取り出し,ダブルクォーテーションのペアをトレースしてレコードの末尾を確定し,1レコードを確定する。
    • 取り出した1行分の入力テキストに含まれるダブルクォーテーションの数がゼロまたは偶数ならば,その1行を1レコードとして確定する。
    • 取り出した1行分の入力テキストに含まれるダブルクォーテーションの数が奇数ならば,ダブルクォーテーションで囲まれたフィールドの途中と見なして次の1行分の入力テキストを取り出し前の行と連結する。これをダブルクォーテーションが偶数になるまで(または入力テキスト末尾になるまで)繰り返す。
  2. フィールドの分割
    レコードに対して,最初にレコード全体をカンマで分割した後で,ダブルクォーテーションをヒントに
    • 文字列フィールドのカンマによる分割の再結合
    • 文字列フィールドを囲むダブルクォーテーションの削除
    • ダブルクォーテーションが2つ連続した場合のエスケープ処理
    を行う。
  3. 1と2を入力テキスト末尾まで繰り返す。
以上のような処理になりますが,ソースコードを簡潔にするために上記「レコードの確定」と「レコードのフィールドへの分割」を別メソッドにして実装することにします。

このメソッドを実装する前に,前回の記事で作った「簡易CSVパーサ」を,上記メソッドを呼び出すように書き換えたコードを先に作ってしまいます。

【簡易パーサの修正】
「レコードの確定」「レコードのフィールドへの分割」を行うメソッドを以下のように規定します。
/**
 * BufferedReaderから1レコード分のテキストを取り出す。
 * @param reader 行データを取り出すBufferedReader。
 * @return 1レコード分のテキスト。
 * @throws IOException 入出力エラー
 */
private String buildRecord (BufferedReader reader);

/**
 * レコードデータsrcを分割してフィールドの配列にする。
 * @param src レコードデータ。
 * @param dest レコードデータからフィールドの配列を取り出してリストに加える。
 */
private void splitRecord (String src, LinkedList dest);

このメソッドを使って前回の記事の「簡易CSVパーサプログラム」を書き換えると次のようになります。
注:行頭が「#」の行はコメント行と見なしてスキップするコードも追加しています。
//------------------------------------------------------------------
/**
 * CSVファイルの読み込み。
 * @param stream 入力ストリーム。FileInputStream,ByteArrayInputStreamなど。
 */
public void read (
  InputStream stream)
{
  LinkedList     columns = new LinkedList();
  InputStreamReader reader = null;
  BufferedReader   buff = null;
  try {
    reader = new InputStreamReader(stream, "MS932");
    buff = new BufferedReader(reader);
    String record;
    int   lineNum = 0;

    while ((record = buildRecord(buff)) != null) {
      lineNum++;
      if (record.length() <= 0)
        continue;
      if (record.startsWith("#"))
        continue;
      splitRecord(record, columns);
      if (0 < columns.size()) {
        readColumns(columns, lineNum);
        columns.clear();
      }
    }
  }
  catch (Exception ex) {
    ex.printStackTrace();
  }
  finally {
    columns.clear();
    try {
      if (buff != null) {
        buff.close();
      }
      if (reader != null) {
        reader.close();
      }
      stream.close();
    }
    catch (IOException ex) {
      ex.printStackTrace();
    }
  }
}

//------------------------------------------------------------------
/**
 * 読み込んだ1レコード分のデータをDBに取り込む。
 * 実際の処理は派生クラスで実装。
 * @param columns 1レコードをフィールドに分割した文字列リスト。
 * @param lineNum CSVの行番号(エラーが発生したときの行番号記録用)。
 */
abstract protected void readColumns (LinkedList columns, int lineNum);


今回はここまで。
次回は,「レコードの確定」と「レコードのフィールドへの分割」を行う「buildRecord」と「splitRecord」両メソッドの実装を行い,CSVパーサを完成させます。

CSVパーサを作る(その3) - RFC4180対応 後編→

■関連情報
CSVの仕様:RFC4180日本語訳)(Wikipedia
桐ver.8活用ガイド(K3フォーマットとCSVの違いについての解説あり)
Microsoftコードページ932(Wikipedia
WebObjects:CSVレスポンスの実装

■関連書籍をAmazonで検索:[Java]
Effective Java 第2版 (The Java Series)



にほんブログ村 IT技術ブログへ にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキングへ ←この記事が役に立ったという方はクリックお願いします。
▼CSVパーサを作る[その1][その2][その3]
SOHO家具オンラインショップ Garageデル株式会社コンピューターミュージックモニター

| | トラックバック (0)

Java:CSVパーサを作る(その1) - 簡易パーサ

データベースExcelなどでデータをインポート・エクスポートする際に使用されるファイルフォーマットとしてCSVがあります。
CSVを単にカンマでフィールドを区切り,改行でレコードを区切ったテキストデータと見るならば,JavaでCSVファイルをインポートしてデータベースに読み込ませるなどの処理を行うCSVパーサを作るには,BufferedReaderクラスのreadLineメソッドでレコードを取り出し,Stringクラスのsplitメソッドでフィールドに分割すれば,それでCSVパーサができあがります。
この場合の具体的なプログラムを以下に例示します。

■処理の概要
このCSVパーサでは,以下の処理をファイルの最後まで繰り返し実行します。

  1. 読み込んだCSVデータから1行を1レコードとして取り出します。
  2. 1レコードをフィールドに分割してStringクラスインスタンスのリストを作ります。
  3. 1レコード分のフィールドのリストをレコード処理メソッド(「readColumns」)に渡します。
CSVパーサクラス及びreadColumnsメソッドはabstractとし,派生クラスでreadColumnsメソッドの処理を実装するものとします。派生クラスのreadColumnsメソッドでは,CSVパーサで生成した1レコード分のフィールドのリストをデータベースへ登録する処理などを実装します。
CSVファイルの文字コードは「Windows-31J/MS932」とします。これはExcelで開けるCSVの文字コードに合わせています。

簡易CSVパーサプログラム

//------------------------------------------------------------------
/**
 * CSVファイルの読み込み。
 * @param stream 入力ストリーム。FileInputStream,ByteArrayInputStreamなど。
 */
public void read (
  InputStream stream)
{
  InputStreamReader reader = null;
  BufferedReader   buff = null;
  try {
    reader = new InputStreamReader(stream, "MS932");
    buff = new BufferedReader(reader);
    String  record;
    String[] columns;
    int   lineNum = 0;

    while ((record = buff.readLine()) != null) {
      lineNum++;
      if (record.length() <= 0)
        continue;
      columns = record.split(",");
      if (0 < columns.length) {
        readColumns(columns, lineNum);
      }
    }
  }
  catch (Exception ex) {
    ex.printStackTrace();
  }
  finally {
    try {
      if (buff != null) {
        buff.close();
      }
      if (reader != null) {
        reader.close();
      }
      stream.close();
    }
    catch (IOException ex) {
      ex.printStackTrace();
    }
  }
}

//------------------------------------------------------------------
/**
 * 読み込んだ1レコード分のデータをDBに取り込む。
 * 実際の処理は派生クラスで実装。
 * @param columns 1レコードをフィールドに分割した文字列リスト。
 * @param lineNum CSVの行番号(エラーが発生したときの行番号記録用)。
 */
abstract protected void readColumns (String[] columns, int lineNum);


次回は,この簡易パーサを拡張して「RFC4180」に対応したCSVパーサへの拡張を試みます。

CSVパーサを作る(その2) - RFC4180対応 前編→

■関連情報
CSVの仕様:RFC4180日本語訳)(Wikipedia
Microsoftコードページ932(Wikipedia
WebObjects:CSVレスポンスの実装

■関連書籍をAmazonで検索:[Java]
Java謎+落とし穴徹底解明



にほんブログ村 IT技術ブログへ にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキングへ ←この記事が役に立ったという方はクリックお願いします。
▼CSVパーサを作る[その1][その2][その3]
ブックオフオンラインSony Style(ソニースタイル)ioPLAZA【アイ・オー・データ直販サイト】

| | トラックバック (0)

WebObjects:CSVレスポンスの実装

Webアプリケーションにおいて,データベースの内容をCSVデータにしてユーザにダウンロードさせたい場合があるかと思います。ここではWebObjectsの環境で作成するWebアプリでの実装例として,ボタンクリックに対するWebブラウザへのレスポンスをCSVデータとすることで,WebブラウザでCSVデータをダウンロードさせる実装例を示します。

【処理の概要】
WebObjectsのアクションメソッドの戻り値としてコンポーネントクラスのインスタンスを返すと,WebObjectsがそのクラスのappendToResponseメソッドを呼び出してWebブラウザへのレスポンスを作成します。この仕組みを使ってCSVデータを返すために,CSVデータを返すためのコンポーネントクラスを作り,メンバ変数にCSVデータセットしてappendToResponseメソッドが呼ばれればCSVレスポンスが作られるようにします。

【実装手順】
1)コンポーネントの作成
CSVデータを返すコンポーネントを作成します。
これは通常のHTMLを返すコンポーネントの新規作成と同じで,Xcodeの「新規ファイル」ダイアログから「WebObjects」→「Component」を選んで「次へ」ボタンで進み,ターゲットを「Application Server」にしてファイル名にコンポーネント名を入力して完了ボタンで作成を実行します。
ここでは仮に,コンポーネント名を「CSVExport」と指定することにします。

2)CSVデータを保持するメンバ変数と初期化メソッドの作成
CSVExport.javaにて,CSVデータとファイル名を保持するメンバ変数と,これに値を設定する初期化メソッドを実装します。

private String mFileName;
private String mCSVData;

public void init (
  String fileName,
  String csvData)
{
  mFileName = fileName;
  mCSVData = csvData;
}

3)appendToResponseのオーバーライド
CSVExport.javaにて「appendToResponse」メソッドを定義すると,Application.javaやSession.javaで定義したappendToResponseメソッドがオーバーライドされます。CSVデータを返す場合のappendToResponseメソッドは以下のようになります。このメソッドでは,出力するCSVファイルをExcelで開くことができるように,文字コードを「Windows-31J/MS932」に指定しています。
public void appendToResponse (
  WOResponse res,
  WOContext cont)
{
  res.setContextEncoding("MS932");
  super.appendToResponse(res, cont);
  if (mFileName != null && mCSVData != null) {
    res.setHeader("public", "Cache-Control");
    res.setHeader("public", "Pragma");
    res.setHeader("text/csv;charset=Windows-31J", "Content-Type");
    res.setHeader("attachment;filename=\""+mFileName+".csv\"", "Content-Disposition");
    res.setHeader(new Long(mCSVData.length()).toString(), "Content-Length");
    res.setContent(mCSVData);
    res.setStatus(res.HTTP_STATUS_OK);
  }
}

上記コードのうち,
res.setHeader("public", "Cache-Control");
res.setHeader("public", "Pragma");
IEのダウンロード問題に対処したものです。
以上でCSVExportコンポーネントの実装は完了です。

4)アクションメソッドからの呼び出し
Webアプリ上でボタンを押すと,CSVExportコンポーネントによりCSVファイルがダウンロードされるようにします。
WebアプリのCSVダウンロードボタンを配置したページのコンポーネントクラスにおいて,ボタンを押したときに呼ばれるアクションメソッドを用意して,その戻り値としてCSVExportクラスインスタンスを返すようにします。アクションメソッドは以下のようになります。
public CSVExport exportAction ()
{
  CSVExport nextPage = null;
  String fileName = buildFileName(); // ファイル名生成.
  String csvData = buildCSVData();  // CSVデータ生成.
  if (csvData != null && 0 < csvData.length()) {
    nextPage = (CSVExport)pageWithName("CSVExport");
    nextPage.init(fileName, csvData);
  }
  return nextPage;
}
このアクションメソッドをWebページのWOSubmitButtonなどにバインドすることにより,ユーザのボタン押下でCSVデータをWebブラウザからダウンロードすることができます。

5)CSVデータ生成メソッドの実装の概要
CSVデータの作成についてはJavaの「StringBuffer」クラスで組み立てるのが一般的かと思います。
CSVのデータフォーマットの仕様についてはRFC4180日本語訳)を参照してください。
この実装方法で巨大なCSVファイルを扱う場合,最後にtoStringメソッドでStringインスタンスを生成するときに,文字列データに必要なメモリサイズが倍になってしまうので,メモリ不足に注意しましょう。WebObjectsのメモリ不足対策はこちら
private String buildCSVData ()
{
  StringBuffer buff = new StringBuffer();
  buff.append("カラム1-1");
  buff.append(",");    // カンマ挿入.
  buff.append("\"");    // ダブルクォーテーション(二重引用符)挿入.
  buff.append("カラム1-2");
  buff.append("\"");
  buff.append("\r\n");   // 改行(CRLF)挿入.
  buff.append("カラム2-1");
  buff.append(",");
  buff.append("\"");
  buff.append("カラム2-2");
  buff.append("\"");
  buff.append("\r\n");

  return buff.toString();
}

※CSVの「数値/数値」をExcelで日付にしないために
ここで実装したしたCSVレスポンスのデータをユーザがダウンロードしてExcelで開いて閲覧・編集する場合,フィールドデータの形式が「数値/数値」または「数値-数値」で,数値が日付として解釈できる範囲内の場合,Excelはこれを勝手に日付データとして取り込んでしまいます。
これに対する対処法として,CSVレスポンスデータを組み立てるときにフィールドデータの先頭に半角スペースを入れる,というのが最も手っ取り早い対処法かと思います。先頭に半角スペースを入れた「 数値/数値」のデータをExcelに読み込ませると,Excelはこのデータの先頭の半角スペースを削除して日付ではなく文字列としてデータを取り込みます。
参考:Excel で文字列または数値が意図しない表示形式に変換される(Microsoftサポートオンライン)

■関連情報
CSVの仕様:RFC4180日本語訳
Java:Shift_JISのエイリアスの変更について
Microsoftサポートオンライン:IEのダウンロードの問題
Java:CSVパーサを作る[その1][その2][その3]
WebObjects:[日本語技術マニュアル][開発と運用について][API Reference(javadoc)][ADC Tools][サポート][製品紹介]

■関連書籍をAmazonで検索:[WebObjects 和書 洋書]
コーディングの掟 現場でよく見る不可解なJavaコードを一掃せよ!

■Apple Store:Windows/Solaris版[WebObjects 5.2]

にほんブログ村 IT技術ブログへ にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキングへ ←この記事が役に立ったという方はクリックお願いします。
Mac ソフトのことなら act2.comMac ソフトのことなら act2.comMac ソフトのことなら act2.com

| | トラックバック (0)

Java:MySQLに金額を格納する

データベースで金額を格納するのに適したデータ型として,MySQLでは「DECIMAL」型があります。FLOATやDOUBLEのような浮動小数点数の型では,小数点以下の値は近似値となりますが,DECIMALでは小数点以下の値も含めて正確に保存するために,MySQL5.0.3以前では数値を文字列に,5.0.3以降ではバイナリデータに変換して保存しています。「DEC」「NUMERIC」「FIXED」はDECIMALの別名です。
DECIMALフィールドの宣言は「DECIMAL(M,D)」です。Mはフィールド全体の桁数,DはMのうち小数点以下の桁数(スケール)を表します。DECIMALの最大桁数(DECIMAL(M,D)のMの値)は,MySQL5.0.3より前は1~254桁まで,5.0.6からは1~65桁までです。また,小数点以下の桁数(DECIMAL(M,D)のDの値)は0~30桁までです。(出典1)(出典2

MySQLにJavaプログラムからJDBCアダプタ(MySQL Connector/J)経由でアクセスする場合,データベースのDECIMAL型のフィールドは,JDBCによりJavaの「BigDecimalクラスインスタンスに置き換えられます。
BigDecimalクラスでは,BigDecimalどうしでの四則演算のほか,小数点以下の桁数操作や丸め処理を行うことができます。BigDecimalで指定できる丸め処理は以下になります。

パラメータ名称説明
ROUND_UP切り上げ計算結果に端数があれば,端数を切り捨てて最小桁を+1(負の値のときは-1)します。
ROUND_DOWN切り捨て計算結果に端数があれば,端数を単に切り捨てます。
ROUND_CEILING正の無限大計算結果が正の値のときは切り上げ,負の値のときは切り捨てを行います。その結果,誤差は必ず正の方向に現れます。この指定は負の方向に誤差が出てはならない場合に指定します。
ROUND_FLOOR負の無限大計算結果が正の値のときは切り捨て,負の値のときは切り上げを行います。その結果,誤差は必ず負の方向に現れます。この指定は正の方向に誤差が出てはならない場合に指定します。
ROUND_HALF_UP四捨五入計算結果の端数が0.5より小さいときは切り捨て,0.5より大きいときは切り上げ,0.5のときは切り上げを行います。
注:連続して計算を行う場合には,四捨五入のみを指定して計算すると,計算結果の累積誤差が大きくなる欠点があります。そのため五捨六入と交互またはランダムに切り替えて誤差を抑えるなどの工夫が必要です。
ROUND_HALF_DOWN五捨六入計算結果の端数が0.5より小さいときは切り捨て,0.5より大きいときは切り上げ,0.5のときは切り捨てを行います。
ROUND_HALF_EVEN最近接偶数への丸め計算結果の端数が0.5より小さいときは切り捨て,0.5より大きいときは切り上げ,0.5のときは結果が偶数になるように切り上げまたは切り捨てを行います。
具体的には「0.5→0(切捨),1.5→2(切上),2.5→2(切捨),3.5→4(切上),4.5→4(切捨),5.5→6(切上)…」といった値になります。
連続して計算を行う場合に,累積誤差が出にくい丸め処理です。
ROUND_UNNECESSARY丸め無し計算結果で端数が生じる場合はArithmeticException例外をスローします。

●BigDecimalでの数値の比較
BigDecimal同士で数値を比較する場合,equalsメソッドでは数値としては同じでもスケールが違う値,たとえば「1」「1.0」「1.00」は別の値として判定されてしまいます。
これらを同じ値として比較判定する場合はcompareToメソッドを使います。

●BigDecimalでのゼロ値の判定
BigDecimalに設定された値がゼロかどうかを判定する場合,予めゼロ値を設定したBigDecimalとcompareToメソッドで比較判定する方法のほかに,BigDecimalのsignumメソッドが0を返すかどうかでも判定できます。signumメソッドは値が負の場合は-1,ゼロの場合は0,正の場合は1を返します。
予めゼロ値が設定されているBigDecimalを使いたい場合は,Java1.5以降で「BigDecimal.ZERO」が定義されています。

■関連情報
MySQL:5.0.3以降のDECIMAL データ タイプの変更(MySQL5.1:22章:精密計算
MySQLの数値型:[4.1][5.1]
MySQL Connector/J:JDBCで値を受け渡すときのJavaの型:[5.0][5.1]
端数処理:(Wikipedia

■関連書籍をAmazonで検索:[MySQL][Java][JDBC]
データベース・リファクタリング



にほんブログ村 IT技術ブログへ にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキングへ ←この記事が役に立ったという方はクリックお願いします。
ioPLAZA【アイ・オー・データ直販サイト】デポのオフィスチェアー特集(大)

| | トラックバック (0)

JavaMail:メール送信でOP25B対応

【概要】
昨今のスパムメール対策により,Javaプログラムからのメール送信が困難な場合がでてきています。送信先がLAN内だったり,LAN内のメールサーバ(MTA)をリレーしたり,ISPが提供するメールサーバをリレーしてメール送信するなら問題ありませんが,JavaプログラムからISPのユーザ回線を経由してインターネットの向こう側に設置しているメールサーバでメールをリレーしようとすると,最近各ISPが導入を進めているOutbound Port 25 Blocking(OP25B)によりメール送信が阻止されてしまうことがあります。
Outbound Port 25 Blockingとは,一般ユーザのパソコンから送信用メールサーバをリレーせずに直接送信先のメールサーバへメールを送りつけるのを禁止するもので,ISPのユーザ回線から外部の25番ポート(SMTP)へ接続するのを阻止することで実現しています。これにより25番ポートからインターネットへメール送信できるのはISPのメールサーバや固定IPを取得したホストに限られますが,メールをリレーする送信用メールサーバがISPの外側にいる場合にも接続が阻止される弊害が出ています。
この対策としては,ISPの外側にいるメールサーバがメール送信のリレーを受け付けるときは25番以外のポート番号を使用すればよいということになります。この代替ポートとして「サブミッションポート」と呼ばれる587番ポートを使うようメールサーバを設定することができます。このとき587番ポートが正規のアカウントによるメール送信のみに使われるように,587番ポートはSASL認証必須に設定します。メールサーバ側のサブミッションポート対応についてはこちらの記事の後半にて,Mac OS X Serverで稼動するPostfixの設定方法を解説しています。

【JavaMailによる実装例】
この記事では,JavaプログラムからJavaMailを使ってメール送信するときに,メールサーバに25番ではなく587番ポートに接続して,SASL認証を行った上でメール送信するコードを例示します。なお,JavaMailを使用するには,事前にJavaMailとJAFをダウンロードして,クラスパスで指定されているディレクトリにJavaMailの「mail.jar」とJAFの「activation.jar」をインストールしておく必要があります。(JAFはJava SE 6に含まれるようになったので,Java SE 6ではJAFのインストールは不要です。)
JavaMailダウンロード:[JavaMail][JAF]
クラスパスの設定:[概要][Windows][Solaris][Mac OS X]
下記のコードではSASLの認証方法として「AUTH LOGIN」で認証できることを確認しています。認証方法「CRAM-MD5」については,JavaMailではSMTPで未サポートのようです(出典)。

下記のコードは25番ポートへ認証なしにメールを送信する場合と以下の点が異なります。
1)接続先SMTPサーバのポート番号を587番に指定:
→プロパティ「mail.smtp.port」を「587」に設定。

2)SASL認証(SMTP Auth)を試みるよう指定:
→プロパティ「mail.smtp.auth」を「true」に設定。

3)SASL認証で使用する認証情報クラスを用意する:
→Authenticatorクラスを継承し,getPasswordAuthenticationメソッドを実装したクラス(PassAuther)を用意する。

4)Session取得時に認証情報クラスインスタンスで初期化:
→Session.getDefaultInstance()メソッドの第二引数にPassAutherのインスタンスを渡す。

【WebObjectsでJavaMailを使う場合の注意】
WebObjectsアプリケーションではデフォルトで「Session」というクラスが作られるため,JavaMailのSessionクラスとクラス名のバッティングが起こります。そのためJavaMailのSessionクラスを指定する場合に「javax.mail.Session」とフルパスでクラス名を指定するなどの対応が必要です。

JavaMailを使って587番ポートからSASL認証してメール送信を行う実装例:

import java.util.*;
import javax.mail.*;
import javax.mail.internet.*;
/**
 * SendSASLMail:メール送信クラス.
 */
public class SendSASLMail
{
  /**
   * PassAuther:認証情報クラス.
   */
  private class PassAuther extends Authenticator
  {
    private String mUserName;
    private String mPassword;
    //------------------------------------------------------------------
    /**
     * PassAutherクラスのコンストラクタ.
     * @param userName SASL認証で使うユーザ名.
     * @param passwd  同パスワード.
     */
    public PassAuther (
      String userName,
      String passwd)
    {
      super();
      mUserName = userName;
      mPassword = passwd;
    }
    //------------------------------------------------------------------
    /**
     * Authenticatorの派生クラスで必須のメソッド.
     */
    public PasswordAuthentication getPasswordAuthentication ()
    {
      return new PasswordAuthentication(mUserName, mPassword);
    }
  }

  private String mHostName;
  private String mUserName;
  private String mPassword;
  //------------------------------------------------------------------
  /**
   * SendSASLMailクラスのコンストラクタ.
   * @param host   送信用ホスト名.
   * @param userName SASL認証用ユーザ名.
   * @param passwd  SASL認証用パスワード.
   */
  public SendSASLMail (
    String host,
    String userName,
    String passwd)
  {
    mHostName = host;
    mUserName = userName;
    mPassword = passwd;
  }

  //------------------------------------------------------------------
  /**
   * メール送信実行メソッド.
   * @param from   差出人.
   * @param toList  宛先リスト.
   * @param ccList  CCリスト.
   * @param bccList BCCリスト.
   * @param subject Subject.
   * @param message メール本文.
   * @throws MessagingException
   * @throws AuthenticationFailedException
   * @throws SendFailedException
   */
  public void send (
    Address  from,
    Address[] toList,
    Address[] ccList,
    Address[] bccList,
    String   subject,
    String   message)
    throws MessagingException, AuthenticationFailedException, SendFailedException
  {
    if (from != null && toList != null && 0 < toList.length) {
      Properties props = new Properties();
      props.setProperty("mail.transport.protocol", "smtp");
      props.setProperty("mail.smtp.host", mHostName);
      props.setProperty("mail.smtp.port", "587");
      props.setProperty("mail.host", mHostName);
      props.setProperty("mail.smtp.connectiontimeout", "60000");
      props.setProperty("mail.smtp.timeout", "60000");
      props.setProperty("mail.smtp.auth", "true");

      Authenticator auther = new PassAuther(mUserName, mPassword);
      Session session = Session.getDefaultInstance(props, auther);

      // session.setDebug(true); // デバッグ情報を表示する.
      MimeMessage mmsg = new MimeMessage(session);

      mmsg.setFrom(from);
      mmsg.setRecipients(Message.RecipientType.TO, toList);
      if (ccList != null && 0 < ccList.length) {
        mmsg.setRecipients(Message.RecipientType.CC, ccList);
      }
      if (bccList != null && 0 < bccList.length) {
        mmsg.setRecipients(Message.RecipientType.BCC, bccList);
      }
      if (subject != null && 0 < subject.length()) {
        mmsg.setSubject(subject, "iso-2022-jp");
      }
      mmsg.setText(message, "iso-2022-jp");
      mmsg.setSentDate(new Date());

      Transport.send(mmsg);
    }
  }

  //------------------------------------------------------------------
  /**
   * 動作確認用mainメソッド.
   * @param args
   */
  public static void main(String[] args)
  {
    SendSASLMail sender = new SendSASLMail("mail.example.com", "username", "password");
    try {
      Address from = new InternetAddress("address@example.com", "差出人", "iso-2022-jp");
      Address to = new InternetAddress("addr1@example.com", "宛先1(TO)", "iso-2022-jp");
      Address cc = new InternetAddress("addr2@example.com", "宛先2(CC)", "iso-2022-jp");

      Address[] toList = new Address[] {to};
      Address[] ccList = new Address[] {cc};

      String subject = "テストメール";
      String message = "JavaMailからのメールです。";

      sender.send(from, toList, ccList, null, subject, message);
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }
  }
}


■関連情報
ISPによるOP25B 実施状況((財)日本データ通信協会・迷惑メール相談センター)
メール送信サンプル:[JavaMail(SMTP)編(JavaでHelloWorld)][JavaMailでメール送信アプリを作る(@IT Java Tips)]
JavaMail完全解説
Sun:[JavaMail]
API Reference(javadoc):[JavaMail][J2EE 1.3.1]
ダウンロード:[JavaMail][JAF]
WebObjects:WOMailDeliveryで日本語メール送信

■関連書籍をAmazonで検索:[JavaMail][Java]
J2EEパターン 第2版



にほんブログ村 IT技術ブログへ にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキングへ ←この記事が役に立ったという方はクリックお願いします。

| | トラックバック (0)

Java:ログインパスワードを暗号化する

Webアプリケーションなどでは,アクセス時にパスワードによるログイン認証を行う場合があります。
ここではその具体的な実装のうち,データベースに格納するログインパスワードの暗号化について述べてみたいと思います。

【概要】
複数のユーザが個別にユーザ名とパスワードを設定する場合,そのアカウント情報はデータベースに保存するようになると思います。
このときパスワードをそのままデータベースに保存してしまうと,パスワードはデータベースを閲覧できるすべての人に知られてしまいます。また,Base64変換や元に戻せる暗号にした場合でも,元に戻せる技術をもった人にパスワードを知られてしまう可能性があります。そのため,パスワードを保存する場合は不可逆暗号にするのが一般的かと思います。
不可逆暗号とは元に戻せない暗号です。不可逆暗号を使ってパスワード認証を行うには,事前に暗号化したパスワードをデータベースに保存しておき,ログイン時にユーザが入力したパスワードを暗号化してみて,データベースに保存されている暗号化されたパスワードと同一になるかどうかで正しいパスワードか判断することができます。不可逆のメリットとしては,元に戻せないのでパスワードの漏洩が防げると同時に,データベースを閲覧できる人にパスワード窃盗の嫌疑がかかるのを防ぐことができます。デメリットとしては,元に戻せないのでユーザがパスワードを忘れてしまったらパスワードを登録し直すしか手がないことです。

パスワードだけを暗号化した場合,同じパスワードの人がいた場合に同じ暗号データがデータベースに登録されてしまい,データベースを閲覧した人に同じパスワードであることがわかってしまう可能性があります。この対策としては(ユニークな)ユーザ名とパスワードを加えて暗号化すれば良いかと思います。

【Javaによる不可逆暗号の実装】
Javaにはメッセージダイジェストを作成するクラスがありますので,これを使って不可逆暗号を作ることができます。具体例として下記のようなコードになります。
なお,このコードではユーザ名がNULLであることを想定していません。なぜならユーザ名はアカウントを特定するユニークキーなので,必須であるべきだからです。ユーザがユーザ名を入力しなかった場合は,このメソッドを呼ぶ前にエラーではじくべきです。
このコードでは,メッセージダイジェストをテキストとして扱うために,sun.misc.Base64Encodingを使用してBase64変換しています。また,Base64Encodingの出力結果は末尾にプラットフォーム依存の改行コードが入るので,これを削除するためにStringクラスのtrimメソッドを使用しています。

import java.security.*;
import sun.misc.*;

/**
* パスワードを不可逆暗号化する.
*/
public class PassDigest
{
  //------------------------------------------------------------------
  /**
   * ユーザ名とパスワードでメッセージダイジェストを作成しBASE64エンコードして返す.
   * @param userName ユーザ名.
   * @param password パスワード.
   * @return 生成したダイジェスト.
   * @throws NoSuchAlgorithmException Java実行環境にSHA-1が実装されていない.
   */
  public static String build (
    String userName,
    String password)
    throws NoSuchAlgorithmException
  {
    String passDigest = null;
    if (password != null && 0 < password.length()) {
      MessageDigest md = MessageDigest.getInstance("SHA-1");
      md.update(userName.getBytes());
      md.update(password.getBytes());
      byte[] digest = md.digest();

      BASE64Encoder encoder = new BASE64Encoder();
      String b64digest = encoder.encodeBuffer(digest);
      passDigest = b64digest.trim();
    }
    return passDigest;
  }
}


■関連情報
Sun:Java暗号化アーキテクチャ API の仕様およびリファレンス
クラスMessageDigest(Javadoc)

■関連書籍をAmazonで検索:[Java]
Javaで作って学ぶ暗号技術 - RSA,AES,SHAの基礎からSSLまで



にほんブログ村 IT技術ブログへ にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキングへ ←この記事が役に立ったという方はクリックお願いします。
サンワサプライ直営【サンワダイレクト】ioPLAZA【アイ・オー・データ直販サイト】120*60

| | トラックバック (0)

WebObjects:メモリ不足エラー対応

【問題】
WebObjectsで開発したWebアプリケーションが稼動中にメモリ不足でメモリ割り当てできなくなったとき,OutOfMemoryError例外が発生します。

【対応策】
WebObjectsで開発するWebアプリはJavaアプリケーションなので,対応策は基本的にJavaアプリケーションと同じです。

割り当てメモリと残メモリの調査
Webアプリケーションには,起動時にJava VMからヒープメモリが割り当てられます。
割り当てられたヒープメモリのサイズは以下のコードで調べることができます。このコードはたとえばmainメソッドの先頭に埋め込むと良いでしょう。

割り当てメモリサイズを表示するJavaプログラムコード
long total = Runtime.getRuntime().totalMemory();
System.out.println("Total Memory = " + Long.toString(total) + "bytes");

特定の時点での残りのヒープメモリは以下のコードで調べることができます。
このコードでは回収できるメモリはできるだけ回収してから残メモリサイズをカウントするため,事前にgcを呼ぶようにしています。
このコードはOutOfMemoryError例外が発生する処理を行う直前の残メモリを調べるときなどに使えるでしょう。
残メモリサイズを表示するJavaプログラムコード
Runtime.getRuntime().gc();
long free = Runtime.getRuntime().freeMemory();
System.out.println("Free Memory = " + Long.toString(free) + "bytes");
※メモリ不足エラーが起きたときは,原因がメモリリークではないかを十分に確認してください。Java VMは参照されなくなったメモリを自動的に回収してくれますが,参照されたままのメモリは回収してくれません。

Javaでの割り当てメモリ拡張方法
Javaアプリケーションのメモリ不足に対する最も手っ取り早い対処方法が,アプリケーション割り当てメモリを増やすことです。
Javaでは,アプリを起動するjavaコマンド([Win版][Solaris版])へのオプション指定で割り当てメモリのサイズを指定することができます。

指定できるオプション指定:
-Xmxサイズ値:割り当てメモリの最大値を指定
-Xmsサイズ値:割り当てメモリの初期値を指定

サイズ値は,-Xmxは2MBより大きい1024の倍数,-Xmsは1MBより大きい1024の倍数でなければなりません。
各サイズ値にキロバイトを指定する場合はkまたはK,メガバイトを指定する場合はmまたはMを付けます。

-Xmxで指定できる値の上限は,OS環境やJavaVMのバージョンにより異なるようです。-Xmxに上限を超える値を指定した場合は「Could not reserve enough space for object heap」というエラーメッセージが返ります。

コマンドラインから起動する場合のオプション指定例:最大値128MB,初期値64MB,起動クラス:EntryClass
java -Xmx128m -Xms64m EntryClass

WebObjectsでメモリサイズを指定するには?
オプション「-Xmxサイズ値」及び「-Xmsサイズ値」をWebObjectsで運用するWebアプリに適用するには以下のようにします。
運用環境で,すでに実稼動しているWebアプリに設定する。
JavaMonitorで以下の設定を行います。
1.JavaMonitorを起動し,Applicationsタブの画面から設定するアプリケーションの「Config」ボタンを押します。
2.「Configuring Application "アプリケーション名"」の画面が表示され,「New Instance Defaults」が開いている状態になります。
3.「New Instance Defaults」の「Additional Arguments:」テキストボックスにオプション「-Xmxサイズ値 -Xmsサイズ値」を入力(追記)して「Push」「Update for New Instances Only」ボタンを押します。
4.アプリケーションのDetail View画面に移動し,アプリケーションを再起動します。

開発版(動作確認用)に設定する。
1.Xcodeにて,プロジェクト名のターゲットをダブルクリックで開きます。
2.「設定」ポップアップで「Development」を選びます。
3.ウインドウの左側リストから「設定」→「シンプルビュー」→「詳細設定ビュー」を開きます。
4.「ビルド設定」が開くので,この中の「JVM_OPTIONS」に「-Xmxサイズ値 -Xmsサイズ値」を入力(追記)します。
5.ウインドウを閉じ,ターゲットをクリーニングしてビルドします。

開発環境でビルドする運用版に設定する。
1.Xcodeにて,プロジェクト名のターゲットをダブルクリックで開きます。
2.ウインドウの左側リストから「設定」→「シンプルビュー」→「詳細設定ビュー」を開きます。
3.「ビルド設定」が開くので,この中の「JVM_OPTIONS」に「-Xmxサイズ値 -Xmsサイズ値」を入力(追記)します。
4.以上の操作をすべてのターゲットのすべての「設定」ポップアップで行います。
5.ウインドウを閉じ,ターゲットをクリーニングしてビルドします。

※上記で指定した内容が,ビルドした運用版Webアプリのパッケージに含まれる各プラットフォーム用クラスパスファイル(例:MacOS用のクラスパスファイルは「Contents/MacOS/MacOSClassPath.txt」)の「# JVMOptions」行に反映されます。

■関連情報
OutOfMemoryError対応事例(@IT:Java Solution)
WebObjects旧版のJavaメモリリーク問題
WebObjects:[日本語技術マニュアル][開発と運用について][API Reference(javadoc)][ADC Tools][サポート][製品紹介]

■関連書籍をAmazonで検索:[Java][WebObjects 和書 洋書]
Webアプリケーション・サーバー 設計・構築ノウハウ

■Apple Store:Windows/Solaris版[WebObjects 5.2]

にほんブログ村 IT技術ブログへ にほんブログ村 IT技術ブログ プログラム・プログラマへ にほんブログ村 IT技術ブログ ネットワーク・SEへ 人気ブログランキングへ ←この記事が役に立ったという方はクリックお願いします。


Mac ソフトのことなら act2.comioPLAZA【アイ・オー・データ直販サイト】ioPLAZA【アイ・オー・データ直販サイト】ioPLAZA【アイ・オー・データ直販サイト】ioPLAZA【アイ・オー・データ直販サイト】

| | トラックバック (0)

MySQL:4.1日本語問題

MySQLは4.0から4.1のバージョンアップで日本語を含む文字コードの扱いが大きく変わっており,それが原因でデータベースで日本語テキストを読み書きするときに文字化けなどが発生するケースがあります。ここではその問題について,当方でわかっている範囲で簡単にまとめてみました。

この問題について,詳しくはMySQLユーザ会FAQページなどを参照してください。

MySQLの文字コード処理は何が変わったのか

MySQL4.1以前は,データベースに格納するテキストデータの文字コードを指定することはできましたが,MySQLがその指定に従って何らかの処理(文字コード変換など)をすることはなく,指定した文字コードは単なるラベルに過ぎなかったようです。
MySQL4.1以降では,指定した文字コードに基づいて次のような処理を行うようになりました。

データベースのスキーマ定義で指定された文字コードとクライアントツールで指定された文字コードが異なる場合,mysqldデーモンとクライアントとの間でデータ転送を行うときに,指定された文字コード間で文字コード変換を行います。
また,このときの文字コード変換は,一度Unicode(UCS-2)に変換してから目的の文字コードに再度変換するという手順になります。

それがどのような影響をもたらしているのか

1)日本語文字コードを指定して作成したデータベースのテキストフィールドに,コマンドラインツール「mysql」から文字コード指定なしでテキストデータを入力したとき,mysqlコマンドの文字コードのデフォルトが「latin1」なので,入力する日本語テキストに対して「latin1→日本語文字コード」の変換を行ってしまい,日本語テキストが壊れます。

2)mysqldデーモンで指定される文字コードのデフォルトが「latin1」なので,データベースを作成するときに文字コードを明示しないと,データベースやテキストフィールドの文字コードはlatin1となります。この状態でmysqlコマンドから日本語文字コードを指定してデータベースに日本語テキストを入力すると「日本語文字コード→latin1」の文字コード変換が起こりますが,latin1には日本語が含まれないため,日本語の文字がすべて「?」に置き換えられてデータベースに書き込まれます。

3)文字コード変換について,Unicodeと非Unicodeを変換する変換テーブルに標準がないため,文字コード変換を行うと一部の文字が意図せぬコードに変換されてしまう可能性があります。詳細はこちらとかこちらとかこちらを参照。なお,文字コード変換は一旦Unicode(UCS-2)に変換してから目的の文字コードに変換するため,非Unicodeから非Unicodeへの変換でもこの問題が発生します。

4)文字コードのデフォルトを変えずにデータベースをlatin1で作成しクライアントからlatin1で読み書きした場合,文字コードが同じなので文字コード変換は起こらずに日本語テキストをデータベースに保存・読み出しができます。ただし,コマンドラインツール「mysqldump」は文字コードのデフォルトが「UTF-8」のため,文字コードをデフォルトのままデータベースの内容をバックアップすると「latin1→UTF-8」の文字コード変換が起きてしまい,日本語テキストが壊れます。

5)JDBCアダプタを介してJavaアプリからデータベースを読み書きした場合,テキストデータはJavaのStringクラスで読み書きを行いますが,Stringクラスのテキストデータは内部でUCS-2で保持するので,データベースアクセスの際にテキストデータの文字コードとUCS-2との間でコード変換が発生するはずです。このときデータベースの文字コードがlatin1なら日本語テキストが壊れ,Unicode系でない場合はUnicodeと非Unicodeの不整合の問題が発生すると思われます。

どうすればいいのか

以下のような設定を行えば,問題は避けられるはずです。
1)MySQLのデータベースに設定する文字コード,及びMySQLに入出力するテキストデータの文字コードを1つに統一して,文字コード変換が起きないようにします。
2)JDBCアダプタからMySQLにアクセスする場合は,MySQLで使用するすべての文字コードにUnicode系を指定して,JavaのStringクラス(UCS-2)との間でUnicode系以外への文字コード変換が起きないようにします。

MySQLでデフォルトの文字コードを設定するには,設定ファイル「my.cnf」(Windowsの場合は「my.ini」)に文字コードの定義を追加します。
MySQLの設定ファイルについてはこちらを参照:[4.1][5.1]
ここで指定できる文字コードのリストはこちらを参照:[4.1][5.1]
Microsoftコードページ932「cp932」はMySQL4.1.12及び5.0.3以降で指定できます。

デフォルトの文字コードの変更については,ソースファイルのリコンパイルによる対応もありますが,mysqldumpについてはソースファイル上での指定にかかわらずUTF-8でリコンパイルされてしまうので,mysqldumpのデフォルト文字コードの変更についてはmy.cnfファイルで対応するしかありません。

my.cnfファイルの[mysqld]セクションで指定できるパラメータに「skip-character-set-client-handshake」があります。このパラメータはクライアントの文字コード設定をサーバの設定で上書きして文字コード変換を行わないようにするようです。この指定はMySQL4.1.15及びMySQL5.0.13以降で使えます。
また,文字コードに「binary」を指定してもMySQL4.1以前同様文字コード変換を行わなくなるようです。但し,データベースやテキストフィールドでbinary型を指定した場合は,テキストの検索や文字列長を指定して行う処理などで不都合が生じるようです。そのため,binaryの指定はクライアントツールに限ったほうがよいようです。

文字コードにUTF-8を指定したmy.cnfファイルの設定例:

[client]
default-character-set = utf8

[mysqld]
default-character-set = utf8
skip-character-set-client-handshake

[mysqldump]
default-character-set = utf8

[mysql]
default-character-set = utf8

注意:my.cnfファイルの設定を更新したら,MySQLを再起動する必要があります。

JDBCアダプタ(MySQL Connector/J)からMySQLにアクセスする場合は,JDBCアダプタがデータベースのクライアントとして動作するときの文字コードを指定しておきます。JDBCアダプタ文字コードはJDBC URLで指定します。
文字コードにUTF-8を指定する場合の設定例(文字コード以外の指定は省いています):
jdbc:mysql://ホスト名/データベース名?useUnicode=true&characterEncoding=UTF8
JDBC URLでのUTF-8設定について,詳細はこちらこちらを参照。

■関連情報
Microsoftコードページ932(Wikipedia
MySQLユーザ会/FAQページ

■関連書籍をAmazonで検索:[MySQL][JDBC]
MySQL 徹底入門 第2版

にほんブログ村 IT技術ブログへ にほんブログ村 IT技術ブログ プログラム・プログラマへ にほんブログ村 IT技術ブログ ネットワーク・SEへ 人気ブログランキングへ ←この記事が役に立ったという方はクリックお願いします。
東急ハンズのネット通販「ハンズネット」120×60サイズ宿・ホテル予約ならじゃらんnet

| | トラックバック (0)

WebObjects:MySQLのためのEOModelの設定

WebObjectsの運用環境で稼動するWebアプリケーションからデータベースにアクセスするためには,データベースの情報を定義するEOModelファイルが必要です。このEOModelファイルはEnterprise Objects Framework(EOF)からJDBCアダプタを介してデータベースにアクセスするためのもので,データベースへのアクセスに必要なJDBCの定義を設定しておく必要があります。
当方では,データベースはもっぱらMySQLを使っていますが,その場合にEOModelファイルに設定しているJDBCの定義をサンプルとして以下に公開します。
MySQLのバージョンは5.0.xで,JDBCアダプタはMySQL Connector/J 5.0.xを使用しています。JDBCアダプタは,Mac OS Xの環境では「/Library/Java/Extensions/」にインストールしています。(参考

JDBC Connectionの設定:

名称設定値
JDBC URL:jdbc:mysql://ホスト名/データベース名?capitalizeTypeNames=true&useUnicode=true&
characterEncoding=UTF8&tinyInt1isBit=false&autoReconnect=true&maxReconnects=3
UserName:MySQL上に作成した,Webアプリからアクセスするデータベースのユーザ名。
Password:同パスワード。
Driver:当方では空白で接続できていますが,「com.mysql.jdbc.Driver」を指定する例もあるようです。
Plugin:空白でOKです。
注:JDBC URLはレイアウトの関係で途中改行を入れていますが,実際は1行です。

JDBC URLで設定した各パラメータについての解説

ホスト名
MySQLが稼動しているホスト名またはIPアドレスを指定します。
WebObjects運用環境とMySQLが同じホストで稼動しているなら,ホスト名は「localhost」または「127.0.0.1」になります。

データベース名
MySQL上に作成した,Webアプリからアクセスするデータベース名を指定します。

capitalizeTypeNames=true
タイプ名を大文字にします。
ドキュメントによると,WebObjectsから呼ぶときはこれを有効にしておくことになっているらしいです。

useUnicode=true
Unicode系の文字エンコーディングを使用することを宣言します。
MySQL 5.0.xではデフォルトがtrueです。

characterEncoding=UTF8
文字エンコーディングをUTF-8に指定します。
MySQLが5.0になって,データベースごと,テキストフィールドごとに文字コードを指定できるようになりましたが,その指定を行った場合はここにはUTF-8を指定することになっているようです。→こちらの最後を参照。
ここで指定した文字コードがデータベースに書き込まれるのは,データベースの文字コードに「binary」とか指定したときに有効なのではないかと思われます。
useUnicodeとcharacterEncodingについての詳細はこちらを参照。

tinyInt1isBit=false
TINYINT(1)を8bit値として扱います。詳細はこちらを参照。
MySQLの旧バージョンにアクセスするプログラムとソースコードを共通化するために,MySQL5.0.xでもTINYINT(1)は8bit値として扱うようにしています。

autoReconnect=true
データベースへクエリを送信する前にデータベースとの接続を確認し,接続が切れていれば再接続を試みます。
ただし,再接続を“安全に”行うことはできず,autoReconnectの機能は将来バージョンで廃止になるようです。
接続が切れる例としては,mysqldはデフォルトでアイドル状態が8時間続いた接続を閉じてしまいます。
この接続が切れるまでのアイドル時間は,「my.cnf」ファイルの[mysqld]セクションで定義する「wait_timeout」(または「interactive_timeout」)パラメータにより指定されます。

maxReconnects=3
autoReconnect=trueのときに再接続を試みる最大の回数です。
注:このパラメータは,MySQL Connector/Jの5.0のドキュメントには記載がありますが,5.1のドキュメントからはなくなっています。

■関連情報
WebObjects:JDBC URL を指定してデータベースサーバの文字エンコードを選択する方法
MySQL 5.1:MySQL Connector/J よくある問題と解決法
MySQL Connector/Jに対してJDBC URLで指定できるパラメータ:[5.0][5.1]
Enterprise Objects Frameworkの解説(Wikipedia)
WebObjects:Javaライブラリの利用方法
WebObjects:[日本語技術マニュアル][開発と運用について][API Reference(javadoc)][ADC Tools][サポート][製品紹介]

■関連書籍をAmazonで検索:[MySQL][WebObjects 和書 洋書][JDBC]
実践 JDBC―Javaデータベースプログラミング術

■Apple Store:Windows/Solaris版[WebObjects 5.2]

にほんブログ村 IT技術ブログへ にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキングへ ←この記事が役に立ったという方はクリックお願いします。


Mac ソフトのことなら act2.comioPLAZA【アイ・オー・データ直販サイト】

| | トラックバック (0)

WebObjects:日本語の基本

WebObjectsで開発を行うときの日本語に関する注意点です。開発環境はMac OS Xを想定しています。

開発環境における注意点

●プロジェクトファイルを保存するフォルダはフルパスで日本語を含むフォルダには置かないようにします。
そうしないとWebObjectsBuilderやJavaコンパイラがファイルを開くことができません。
(/Usersの別名の「ユーザ」など,フォルダ名が別名表示のものはOK)

●開発を始める前に開発環境の文字エンコーディングを揃えます。
ProjectBuilder及びXcodeの文字コードは,日本語環境では「日本語MacOS」(シフトJIS)がデフォルトですが,WebObjectsBuilderの初期値は英文系なので,この設定のまま使用するとWebObjectsBuilderが裏でJavaのソースを書き換えたときに,文字エンコーディングの不一致でソースコードに含まれる日本語が全滅してしまいます。
このようなことにならないために,事前にWebObjectsBuilderの文字エンコーディング設定(「Preferneces」→「General」→「Default Encoding」)をProjectBuilder/Xcodeの文字エンコーディング設定(「環境設定」→「テキスト編集」→「デフォルトのファイルエンコーディング」)に合わせておく必要があります。

ProjectBuilder/Xcodeのデフォルトエンコーディングは,新規コンポーネント及び新規ファイルで適用されます。
「新規プロジェクト」で作られたJavaソースファイル(ClassisグループのApplication.java,Session.java,DirectAction.javaと,WebComponentsグループ→MainのMain.java)の文字エンコーディングは,環境設定にかかわらず「Mac OS Roman」になっています。
この文字エンコーディングを変更してデフォルトの文字エンコーディングに揃えるには,ProjectBuilder/Xcodeで各ソースファイルを開き,「形式」メニュー→「ファイルエンコーディング」で文字エンコーディングを指定し,ダイアログで変換を指定して保存します。

また,既存のソースファイルをプロジェクトに追加して利用する場合も,利用するファイルの文字エンコーディングとデフォルトの文字エンコーディングを揃える必要があります。
これを揃える場合も,各ソースファイルを開いて「形式」メニュー→「ファイルエンコーディング」でデフォルトと同じ文字エンコーディングを指定して変換・保存します。

日本語のためのコーディング

WebアプリがHTMLで使用する文字コードの指定
WebObjectsで開発するWebアプリについて,各コンポーネントを表示したりテキストデータを入出力したりするときに日本語を使用する場合は,使用する日本語文字コードを指定する処理をソースコードに追加する必要があります。
ちなみに下記の例ではUTF-8を指定していますが,他の文字コードを指定することもできます(例:"Windows-31J","EUC-JP","ISO-2022-JP")。なお,ここで指定する文字コードは,サーバで稼動するWebアプリとクライアントのWebブラウザとの間でテキストをやりとりするときの文字コードであり,Webアプリの内部処理やデータベースで使用する文字コードとは無関係です。

注:シフトJISの文字エンコーディングは「Shift_JIS」ではなく「Windows-31J」を使いましょう。詳細はこちら

1)Application.javaのApplicationクラスに以下のメソッドを追加します。

// 各コンポーネントにおけるクライアントからのリクエストのデフォルト処理
public void takeValuesFromRequest(WORequest req, WOContext cont)
{
  req.setDefaultFormValueEncoding("UTF8");
  req.setFormValueEncodingDetectionEnabled(true);
  super.takeValuesFromRequest(req, cont);
}

// 各コンポーネントにおけるクライアントへのレスポンスのデフォルト処理
public void appendToResponse(WOResponse res, WOContext cont)
{
  res.setContentEncoding("UTF8");
  res.setHeader("text/html;charset=utf-8", "Content-Type");
  super.appendToResponse(res, cont);
}

2)Applicationクラスのコンストラクタに以下のコードを追加します。
WOMessage.setDefaultEncoding("UTF8");

3)コンポーネントのレスポンスにHTML表示以外のもの(CSVやPDFのダウンロードなど)がある場合,3-A)または3-B)どちらかの作業が必要です。(CSVレスポンスの具体的な実装例はこちら

3-A)コンポーネントクラスにappendToResponseメソッドを追加すると,追加したメソッドによりApplicationクラスのappendToResponseメソッドがオーバーライドされます。
コンポーネントクラスでappendToResponseメソッドをオーバーライドし,このメソッドにて
res.setHeader("text/html;charset=utf-8", "Content-Type");
の内容を,そのコンポーネントのレスポンスに合わせて適宜書き換えることで,HTML以外のレスポンスに対応します。

3-B)ApplicationクラスのappendToResponseメソッドから
res.setHeader("text/html;charset=utf-8", "Content-Type");
の行を削除すると,コンポーネントのレスポンスからContent-Typeの指定行が消えます。
この状態で,各コンポーネントのソースファイルの中から「コンポーネント名.html」ファイルを開き,<head>タグの中で,
<META HTTP-EQUIV="Content-type" CONTENT="text/html; charset=utf-8">
を追加すると,この指定行がコンポーネントのレスポンスに追加されます。
この指定行の追加をすべてのコンポーネントで行った上で,この指定行の内容をコンポーネントごとに適宜書き換えることで,HTML以外のレスポンスに対応します。

テキストデータの改行コードをWeb画面上で改行させる
テキストデータをWebObjectsBuilder上でWOStringにバインドして表示させると,テキストデータに改行が含まれているにもかかわらず改行されずに表示されてしまいます。
これは,改行(CRLF)は無視して表示するHTMLの仕様によるものです。画面上で改行するには「<br>タグ」が必要です。WOStringでは記号のエスケープ処理は行いますが,改行の<br>タグへの変換は行わないので,この変換を行うコードが必要になります。WebObjectsのクラスライブラリを使って「改行→<br>タグ変換」を行うのは以下のようなコードになります。
str; // 改行を含む文字列がセットされているものとします。
str = WOMessage.stringByEscapingHTMLAttributeValue(str);
NSArray array = NSArray.componentsSeparatedByString(str, "&#13;&#10;");
str = array.componentsJoinedByString("<br/>");
上記コードのうち「WOMessage.stringByEscapingHTMLAttributeValue」は,文字列に含まれる記号のうち,HTMLでエスケープが必要なものをエスケープします。
「NSArray.componentsSeparatedByString」は,第一引数の文字列(str)を第二引数の文字列(改行コード)で分割します。上記の例では改行コード(CRLF)を16進表記で指定しています。
「array.componentsJoinedByString」では,分割した文字列を,間に「<br/>」を挟んでつなげています。

以上のコードで得たstrをWOStringにバインドします。このときWOStringのアトリビュート「escapeHTML」はOFFに指定しておきます。

■関連情報
WebObjects:Web ブラウザの文字エンコードを設定する方法
Java:Shift_JISのエイリアスの変更について
Javaで指定できる文字エンコーディング[1.3][1.4][1.5]
IANA:charsetで指定できる文字セット
WebObjects:[日本語技術マニュアル][開発と運用について][API Reference(javadoc)][ADC Tools][サポート][製品紹介]

■関連書籍をAmazonで検索:[WebObjects 和書 洋書]
WebObjectsアプリケーション開発ガイド

■Apple Store:Windows/Solaris版[WebObjects 5.2]

にほんブログ村 IT技術ブログへ にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキングへ ←この記事が役に立ったという方はクリックお願いします。
デポのオフィスチェアー特集(大)Mac ソフトのことなら act2.comioPLAZA【アイ・オー・データ直販サイト】

| | トラックバック (0)

MySQL:TINYINT(1)の値

【問題】
MySQL数値型TINYINTは8bitの値で-128~127の値を格納できるはずですが,JDBCアダプタにMySQL Connector/Jを使用してJavaアプリケーションからMySQLデータベースのTINYINT(1)のフィールドに値を書き込むと,実際に書き込まれる値は0か1にしかなりません。
また,このフィールドの値をNumber型やその派生クラスの型で受け取ろうとすると,ClassCastException例外が発生します。

【対応策】
JDBCアダプタのデフォルトでは,TINYINT(1)はBIT(1)とみなされます。そのためこの値の設定・取得はNumber型やその派生クラスではなくBoolean型になります。
TINYINTに-128~127を格納するには,TINYINT(4)と桁数指定することで対処できます。また,TINYINT(1)のまま-128~127の値を格納したい場合は,JDBCアダプタの設定としてJDBC URLに「tinyInt1isBit=false」のエントリを加えます。

例:「tinyInt1isBit=false」を含めたJDBC URL
jdbc:mysql://ホスト名/データベース名?useUnicode=true&characterEncoding=UTF8&tinyInt1isBit=false

■追記:
「tinyInt1isBit=false」は,TINYINT(1)で-128~127の値が指定可能だった旧版(5.0以前)のMySQLで構築したデータベースのデータを,スキーマ定義を変更することなく新版で使用するためのもののようです。そのため最新版のMySQLでデータベーススキーマ定義を1から作る場合はこの設定は避けるべきと思われます。

以上とは逆に,旧版のMySQLにおいて,TINYINT(1)をBooleanとして扱う設定もあります。
「transformedBitIsBoolean=true」という指定で,これはConnector/J 3.1.9以降で指定可能です。
Connector/J 3.1.xはMySQL4.1以降が対象になります。(出典
この指定はMySQL5.0以降では指定する意味がありません。

■関連情報
MySQLの数値型:[4.1][5.1]
MySQL Connector/Jに対してJDBC URLで指定できるパラメータ:[5.0][5.1]
JDBCで値を受け渡すときのJavaの型:[5.0][5.1]

■関連書籍をAmazonで検索:[MySQL][JDBC]
MySQL 徹底入門 第2版

にほんブログ村 IT技術ブログへ にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキングへ ←この記事が役に立ったという方はクリックお願いします。


デル株式会社上新電機 パソコン買取サービス

| | トラックバック (0)

Javadocで日本語APIリファレンスを作る

Javaに付属のコマンドツールに「javadoc」というのがあります。
このコマンドにより,JavaのソースファイルからHTML形式のAPIリファレンスを自動生成することができます。
Sunが公開しているJava組み込みクラスのAPIリファレンスは,Javadocで生成したAPIリファレンスの一例です。

Javadocはデフォルトで英語のソースファイルから英語のドキュメントを生成しようとします。
Javadocで日本語のドキュメントを生成するには,Javadocのコマンド実行時に以下のオプション指定を行います。

1)Javadocが生成するメッセージを英語から日本語にしたい。
→Javadocのコマンドオプションで,
「-locale ja」
と指定することで,ドキュメントのナビゲーションバーなどのメッセージが日本語になります。このメッセージの文字コードは,下記4)のオプション指定で決まります。
このロケールについての解説は「java.util.Locale」を参照。また,ここで指定できる言語コードのリストはこちらを参照。

2)生成されるHTMLファイルのヘッダで文字コード指定を行いたい。
→Javadocのコマンドオプションで,
「-charset "文字セット名"」(注:文字セット名はダブルクォーテーションで囲む)
を指定することで,ドキュメントのHTMLヘッダに

<meta http-equiv="Content-Type" content="text/html; charset=文字セット名">
の行が付加されます。
ここで指定できる日本語の文字セット名には「Shift_JIS」「euc-jp」「iso-2022-jp」「utf-8」「Windows-31J」があります。
ここで指定する文字セット名は下記4)の文字エンコーディング名と合わせる必要があります。HTMLヘッダにこの行を付加しない場合はブラウザのデフォルト値が適用されます。
ここで指定できる文字セット名のリストはこちらを参照。

3)Javaソースファイルの文字コードを指定したい。
→Javadocのコマンドオプションで,
「-encoding 文字エンコーディング名」
を指定します。これと下記4)との組み合わせで,ソースファイル内の文字列をHTMLに出力するときの文字コード変換の内容が決まります。
指定可能な文字エンコーディング:[Java 1.4][Java 1.5]

4)出力するHTMLファイルの文字コードを指定したい。
→Javadocのコマンドオプションで,
「-docencoding 文字エンコーディング名」
を指定します。これにより,上記1)の日本語メッセージの文字コードが決まります。また,上記3)との組み合わせで,ソースファイル内の文字列をHTMLに出力するときの文字コード変換の内容が決まります。
指定可能な文字エンコーディング:[Java 1.4][Java 1.5]

コマンド実行例:UTF-8のソースファイルからSJISのドキュメントを生成する。
javadoc -locale ja -charset "Shift_JIS" -encoding UTF-8 -docencoding SJIS -d docs -private *.java
(「-d docs」は出力ディレクトリに「docs」を指定,「-private」はprivateなクラスやメソッドのリファレンスも出力)

■関連情報
Javadocの基本的な使い方(JavaでHelloWorld)
Sun:Javadocのドキュメント[Solaris版][Windows版]
Java:Shift_JISのエイリアスの変更について

■関連書籍をAmazonで検索:[Java]
エンジニアのためのJavadoc再入門講座 現場で使えるAPI仕様書の作り方



にほんブログ村 IT技術ブログへ にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキングへ ←この記事が役に立ったという方はクリックお願いします。
Sony Style(ソニースタイル)Sony Style(ソニースタイル)Sony Style(ソニースタイル)

| | トラックバック (0)

WebObjects:WOMailDeliveryで日本語メール送信

【問題】
WOMailDeliveryは,WebObjectsのフレームワークに含まれるメール送信クラスです。
WOMailDeliveryクラスには文字コード変換の機能が含まれておらず,送信するメール本文の日本語文字列をISO-2022-JPに変換することができません。そのため,メールの本文が日本語だと文字化けして送信されてしまいます。

【対応策】
WOMailDeliveryのメール送信クラスにメール本文を渡す前に,JavaStringクラスのテキストエンコーディング機能を使ってメール本文をISO-2022-JPに変換し,変換結果を格納したStringクラスのインスタンスを作ってこれをWOMailDeliveryのメール送信クラスに渡すことで,日本語メールを送ることができます。
日本語の本文が入ったStringからISO-2022-JPに文字コード変換したStringを作るには以下のようにします:

String message = new String("こんにちは。\n"); // メール本文.

// 本文をISO-2022-JPに文字コード変換したバイト列にする.
byte[] bytes = message.getBytes("ISO2022JP");

// バイト列でStringクラスインスタンスを作る.
// ISO-8859-1は「北米ラテン文字」で,ISO-2022-JPのバイト列を変換せずに取り込むために指定.
String sendMessage = new String(bytes, "ISO8859_1");
あとはこのsendMessageをWOMailDeliveryのメール送信メソッド(WOMailDelivery.sharedInstance().composePlainTextEmail())に渡せばOKです。

■追記
WOMailDeliveryは,指定されたメールサーバ(MTA)のポート25番にSASL認証なしでメールを送信しようとします。そのため,昨今のスパム対策などによりWOMailDeliveryが利用できない場合があります。そのような場合はJavaMailが使えます。(JavaMailによるメール送信の実装例はこちら

■関連情報
WOMailDeliveryの基本的な使い方:Sending E-mail from a WebObjects Application
ISO8859-1(Wikipedia
WebObjects:[日本語技術マニュアル][開発と運用について][API Reference(javadoc)][ADC Tools][サポート][製品紹介]

■関連書籍をAmazonで検索:[WebObjects 和書 洋書]
メール送信システムの作り方大全―Perl/PHP/JavaMail/Windowsそれぞれの場合

■Apple Store:Windows/Solaris版[WebObjects 5.2]

にほんブログ村 IT技術ブログへ にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキングへ ←この記事が役に立ったという方はクリックお願いします。


宿・ホテル予約ならじゃらんnetオフィスの収納家具

| | トラックバック (0)

WebObjects:Javaソースコードに日本語を埋め込む

【問題】
WebObjectsで開発したWebアプリケーションで日本語を表示しようとしたときに,日本語文字列をJavaソースコードに埋め込んで,

String str = new String("こんにちは");
このように書いて,この文字列データをWebブラウザで表示させると文字化けすることがあります。

【原因】
Javaコンパイラはデフォルトでソースファイルの文字コードを英文として解釈しようとします。
JavaのStringクラスは文字列データをUnicode(UCS-2)で保持するため,コンパイル時に文字列データの文字コード変換を行いますが,このときソースファイルがたとえばShift_JISで書かれていた場合,コンパイル時の文字コード変換が「英文→UCS2」ではなく「Shift_JIS→UCS-2」である必要があることをJavaコンパイラに知らせる必要があります。逆にコンパイラに「Shift_JIS→UCS-2」の変換を指定した場合,すべてのソースファイルの日本語文字コードをShift_JISに揃える必要があります。

【対応策】
この問題に対処するには次の2つの対処を行います。
1)開発環境のJavaコンパイラに,オプション指定で日本語文字コードを指定する。
2)ソースファイルで使用する日本語文字コードを1)で指定したものに統一する。

Mac OS XのWebObjects開発環境では以下の手順でこれに対応します。
1)Javaコンパイラに日本語文字コードを指定する。
・ProjectBuilder(Mac OS X 10.2.xまで)の場合
「ターゲット」タブを選んで,各ターゲットの「Javaコンパイラ設定」を開くと「ソースファイルのエンコーディング」というポップアップメニューがありますので,これをソースコードの文字コードと同じものにします。そのためにはProjectBuilderの「ProjectBuilder」メニュー→「環境設定」ダイアログの「テキスト編集」タブ→「デフォルトのエンコーディング」に設定してある文字コードと同じものにしてください。この操作はすべてのターゲットで行ってください。

・Xcode(Mac OS X 10.3以降)の場合
ウインドウの左枠の中に「ターゲット」があり,この中には通常プロジェクト名,「Application Server」,「Web Server」の3つのターゲットがあります。この各ターゲット名をダブルクリックすると,ターゲットの設定ダイアログが表示されます。この中の「Javaコンパイラの設定」を選んで表示される設定パネルに「ソースファイルのエンコーディング」がありますので,ここでソースファイルの文字コードを指定します。この操作はすべてのターゲットのすべての「設定」ポップアップで行ってください。

注意:開発の初期の段階で,すべてのターゲットと設定で文字コードを指定しておくことは重要です。
開発版の設定の文字コードだけ変更しても,開発環境で動作テストするときは正常に動作しますが,そのまま開発を続けると,運用版をビルドして実機で稼動させるときになって文字化けが再発することになります。変更が必要だとわかっているうちに忘れずに変更しておきましょう。

以上の操作を行うことで,Javaコンパイラはソースコードが指定した文字コードで書かれているものとして処理するようになります。

2)ソースファイルの文字コードの設定
ProjectBuilder/Xcodeにおいて新規ファイルを作成すると「環境設定」→「テキスト編集」→「デフォルトのファイルエンコーディング」で指定した文字コードが適用されます。そのためこの値も1)で指定したものに合わせます。ただし「新規プロジェクト」で作成されるソースファイル(「Application.java」「Session.java」「DirectAction.java」)の文字コードは設定にかかわらず「Mac OS Roman」なので,これらの文字コードは後で手動で変更する必要があります。

各ソースファイルの文字コードはProjectBuilder/Xcodeが認識しており,ソースコードの編集で日本語入力が行われると,認識されている文字コードで日本語文字列がソースコードに挿入されます。特定のソースファイルの文字コードがどのように認識されているかは,ソースファイルをプロジェクトから開いて「形式」メニュー→「ファイルエンコーディング」で確認することができます。ここで認識されている文字コードと,1)で指定した文字コードが食い違っていた場合,文字化けを起こす原因になります。
この食い違いを正すには,ProjectBuilder/Xcodeの「形式」メニュー→「ファイルエンコーディング」で正しい文字コードを指定します。すると「再解釈」または「変換」を指定するダイアログが表示されるので,開発環境の文字コードの認識の間違いを正す場合は「再解釈」を,別の文字コードに変換する場合は「変換」を選びます。これによりソースファイルの文字コードとコンパイラが認識する文字コードを合わせることができます。

注意:文字コードの認識が間違った状態で「変換」を行うと日本語文字列データが壊れます。壊れたまま上書き保存してしまわないようにしましょう。

3)WebObjectsBuilderの環境設定
WebObjectsBuilderはソースファイルへのメソッドの追加などを行いますが,このときWebObjectsBuilderの環境設定で指定された文字コードと実際のソースファイルの文字コードが異なる場合,ソースファイルの日本語文字データが壊れます。そのためWebObjectsBuilderの環境設定でも1)と同じ文字コードを設定しておく必要があります。
この設定は,「WebObjects Builder」メニュー→「Preferences...」で環境設定ダイアログを開いて「General」タブの「Default encoding」ポップアップで行います。

■関連情報
WebObjects:[日本語技術マニュアル][API Reference(javadoc)][ADC Tools][サポート][製品紹介]

■関連書籍をAmazonで検索:[Java][WebObjects 和書 洋書]
■Apple Store:Windows/Solaris版[WebObjects 5.2]

にほんブログ村 IT技術ブログへ にほんブログ村 IT技術ブログ プログラム・プログラマへ 人気ブログランキングへ ←この記事が役に立ったという方はクリックお願いします。


Mac ソフトのことなら act2.comMac ソフトのことなら act2.comMac ソフトのことなら act2.com

| | トラックバック (0)