2015年9月12日土曜日

Glassfish+Derby+JDBCレルムについて(ソースコード解説)

JDBCレルムについてソースコード内容について概要を記述。
下記GitHubのコードの解説である。
https://github.com/Fufuhu/SimpleAuthentication

NetBeans上でMavenのWebプロジェクトで作成している。


Servletとjspだけで構成されたシンプルな構成になっている。
(Webページディレクトリ直下のindex.jspは無視してもらって構いません。)

■画面遷移について

以下のような形で画面遷移を構成しています。


index.jspがログインページLoginServletにてJDBCレルム認証を実行後、
認証を通過すればtoppage.jsp, 失敗すればerror.jspに遷移する。


■index.jsp
ログインページ。ユーザ名とパスワードの入力したのちに、
ログインボタンクリックで、LoginServletを呼び出し、JDBCレルム
認証を実行することができる。

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>ログイン</title>
    </head>
    <body>
        ログイン情報<br/>
        <form method="POST" action="../LoginServlet">
            ユーザ名:<input type="text" name="username"/> <br/>
            パスワード:<input type="password" name="password"/><br/>
            <input type="submit" method="GET" value="ログイン"/>
        </form>
    </body>
</html>



■LoginServlet(一部抜粋)
42行目のrequest.login(username, password);でJDBCレルム認証を実行している。
index.jspで入力されたユーザ名、パスワードの内容に応じてログイン可否を判断している。
loginメソッドで認証ができれば、そのままtoppage.jspへの遷移を続行。(ユーザ認証情報がセッションに含まれるようになる。)できなければServletExceptionがスローされるので、error.jspに遷移する。

package authentication;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 *
 * @author USER
 */
@WebServlet(name = "LoginServlet", urlPatterns = {"/LoginServlet"})
public class LoginServlet extends HttpServlet {

   
    private final String topPage = "/login/toppage.jsp";
    private final String errorPage = "/login/error.jsp";
    /**
     *
     * @param request servlet request
     * @param response servlet response
     * @throws IOException if an I/O error occurs
     */
    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws IOException {
        response.setContentType("text/html;charset=UTF-8");
            String username = request.getParameter("username");
            String password = request.getParameter("password");
           
            ServletContext sc = getServletContext();
           
            try {
                //JDBCレルム認証部分(認証に失敗すると例外をスロー)
                request.login(username, password);
            } catch (ServletException ex) {
                Logger.getLogger(LoginServlet.class.getName()).log(Level.SEVERE, null, ex);
                try {
                    sc.getRequestDispatcher(errorPage).forward(request, response);
                } catch (ServletException ex1) {
                    Logger.getLogger(LoginServlet.class.getName()).log(Level.SEVERE, null, ex1);
                }
            }
            try {
                sc.getRequestDispatcher(topPage).forward(request, response);
            } catch (ServletException ex) {
                Logger.getLogger(LoginServlet.class.getName()).log(Level.SEVERE, null, ex);
            }
    } 
    /**
     * Handles the HTTP <code>POST</code> method.
     *
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }
}

■toppage.jsp
仮のトップページ。ウェルカムメッセージと、
ログアウトのための処理呼び出しが可能なだけ。

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>トップページ</title>
    </head>
    <body>
        <h1>ログインに成功しました。</h1><br/>
        <form action="./LogoutServlet">
            ${param["username"]}様、いらっしゃいませ。<br/>
            <input type="submit" value="ログアウト"/>
        </form>
    </body>
</html>

■LogoutServlet(一部抜粋)
request.getSession().invalidate();でセッション情報を明示的に破棄するとともに
request.logout();でログアウト処理を実行している。

package authentication;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 *
 * @author USER
 */
@WebServlet(name = "LogoutServlet", urlPatterns = {"/LogoutServlet"})
public class LogoutServlet extends HttpServlet {

    private final String indexPage = "./login/index.jsp";
    private final String errorPage = "./login/error.jsp";
    /**
     * Processes requests for both HTTP <code>GET</code> and <code>POST</code>
     * methods.
     *
     * @param request servlet request
     * @param response servlet response
     */
    /**
     * @param request servlet request
     * @param response servlet response
     * @throws java.io.IOException
     */
    protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws IOException
    {
        //セッションを明示的に破棄する。
        request.getSession().invalidate();
        ServletContext sc = getServletContext();
       
        try {
           //認証からのログアウトを行う処理。
            request.logout();
        } catch (ServletException ex) {
            Logger.getLogger(LogoutServlet.class.getName()).log(Level.SEVERE, null, ex);
            try {
                sc.getRequestDispatcher(errorPage).forward(request, response);
            } catch (ServletException ex1) {
                Logger.getLogger(LogoutServlet.class.getName()).log(Level.SEVERE, null, ex1);
            }
        }
        response.sendRedirect(indexPage);      
    }
    /**
     * Handles the HTTP <code>POST</code> method.
     *
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }
}


■error.jsp
ログイン処理時にrequest.login()に失敗するとこちらのページに遷移する。

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>

        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Page</title>
    </head>
    <body>
        <h1>ログイン失敗</h1><br/>
        <a href="./index.jsp">ログインページへ</a>
    </body>
</html>


次の記事では、コードではなく、アクセス制御(web.xml)の記入について説明します。
※生のxmlを書くわけではなく、NetBeansにおんぶにだっこで設定します。

Glassfish+Derby+JDBCレルムについて(Glassfishの設定)

Glassfish側の設定。

以下の項目を作成していきます。

  1. コネクションプール
  2. JNDIリソース
1.についてはもともとあるものを流用してもよいのですが、
個人的に気持ち悪さを感じてしまうのであえて新しく作成することにします。

1. コネクションプールの作成
Glassfishの管理画面にログインします。
ローカルで実行している場合は、
http://localhost:4848にアクセスしてもらえれば管理画面に
アクセスできるはずです。
初期状態では特にパスワードなどは設定していないので
そのまま管理画面にアクセスできるはずです。

アクセス後は、管理画面の「JDBC」→「JDBC Connection Pools」と
選択をしていきます。作成済みのコネクションプールの一覧が
表形式で表示されるので、「New」ボタンをクリックしたのち、下記画面の内容を入力します。


これで以前の記事で作成したデータベースに対する
JDBCのコネクションプールは作成完了です。

一枚目の画像の「Ping」ボタンをクリックして

コネクションプールの名前にAuthenticationPoolと名付けたことを覚えておいて
ください。

2. JNDIリソースの作成

次はJNDIリソースを作成します。
管理画面の「JDBC」→「JDBC Resources」を選択します。
作成済みのJDBCプールのJNDIリソースの一覧が表示されるので、
「New」ボタンをクリックしたのち、下記の内容を入力します。


3. JDBCレルム設定の作成

「Conifgurations」→「Conifgurations」→「Security」→「Realms」を選択したのち、
「New」ボタンをクリックします。

下記画像の設定を投入します。


設定内容は以前の記事にて作成したDerbyのデータベースの設定に
準拠しています。

これでGlassfish側のJDBCレルムの設定は完了です。




2015年9月10日木曜日

Glassfish+Derby+JDBCレルムについて(Derby(JavaDB)の設定)

JDBCレルム認証を実現するためにはまずデータベースの作成が必要です。
ここでは、NetBeansをつかってローカルのJavaDB上にJDBCレルム認証に利用するデータベースを構築する方法を説明します。


NetBeansのサービスタグを選択、JavaDBを右クリックしてデータベースの
作成を選択します。


ここでは、データベース名をAuthenticationSampleとして作成しています。
ユーザ名はAPP, パスワードもユーザ名と同じにしています。
(当然ですが、本番環境での利用の際にはユーザ名、パスワードともに配慮が必要です。)

これで、JavaDB上にAuthenticationSampleデータベースが作成されました。



次は新規の接続先として先ほど作成したAuthenticationSampleに接続します。
下記のような設定を行ってください。


これで、接続先の設定が完了しました。
次にテーブルの作成に移ります。

テーブルの作成には先ほど作成した接続先を右クリックし、
コマンドの実行を選択します。新しい画面が開くので、下記のSQLを入力し、
usertableとgrouptableを作成します。


create table usertable (
username varchar(20) NOT NULL CONSTRAINT USER_PK PRIMARY KEY ,
mailaddress varchar(100) NOT NULL,
password varchar(128) NOT NULL
);


create table grouptable(
username varchar(20) NOT NULL,
groupid varchar(20) NOT NULL,
CONSTRAINT GROUP_PK PRIMARY KEY(username, groupid),
CONSTRAINT USER_FK FOREIGN KEY(username)
REFERENCES usertable(username) ON DELETE
);

これで、JDBCレルム認証用のデータベースが作成できました。
次以降の設定で必要となる内容は以下の通りです。

usertableのパスワード列には生パスワードではなく、SHA-256でハッシュ化した
値を入れないといけません。こちらについてはサンプルコードを
寺田佳央さんが提供してくれています。
https://github.com/yoshioterada/JDBC-Realm-Sample/tree/master/src/main/java/jp/co/oracle/jdbcrealm/SHADigestUtil


以下の情報は次回以降に利用するため、記載しておきます。

JDBC URL: jdbc:derby//localhost:1527/AuthenticationSample
データベース名: AuthenticationSample
データベースのユーザ名/パスワード: APP/APP
ユーザテーブル: usertable
ユーザ名: username
パスワード: password

グループテーブル: grouptable
ユーザ名: username
グループ名: groupid




Glassfish + Derby + JDBCレルムについて

JDBCレルム認証を説明した可能な限りシンプルなコードを作成しました。


https://github.com/Fufuhu/SimpleAuthentication


具体的な説明は後日。

2015年9月7日月曜日

Glassfish + Derby + JDBCレルムについて(導入編)

私の妄想が7割くらいなので、正しい理解なのかは保証しません。
図にしないと理解しづらい低能なので、JDBC、JNDI、JDBCレルムの依存関係はこんな感じ。
(コネクションプールを利用する場合)



  • JDBC Connection Pool ... どこにあるDBに接続するか
  • JDBC Resource(JNDI) ... JNDIとしてJDBC Connection Poolを取り扱えるようにする。
  • JDBCレルム ... JNDIからJDBC Connection Poolへ接続し、特定のテーブルからユーザ名・パスワードとロール名を読み取る。
JDBCレルムでパスワードを取り扱う際には、生パスワードをDBに書き込むことができないのでうっかりさんも安心。寺田さんの記事の例ではSHA-256でハッシュ化してあります。

一応私の方でも実装編を解説しようかと思います。以下の寺田さんの例だとJSFを使っているので、前提知識多めな感じになってしまっているので...私の方ではもっとシンプルにServletとJSPを使って実装しようかと思います。

2015年9月5日土曜日

JSFのfaceletsきめぇな方へ

JSFってエンジニアにはうれしいけど、デザイナにとってはつらいよね。

漢らしい業務臭い画面ってそれはそれで味があっていいけれども、
かわいらしいデザインって欲しいじゃない?

ってことでHTMLっぽくJSFを記述する方法をちょいと調べてみた。

JSF 2.2 でさらに便利になったMarkupを使ってみよう


公式に近い部分だとこれかな??
HTML(5) Friendly Markup in JSF 2.2


これをつかえばcssとかJavaScriptも一緒に使えるかな??

CDIメモ(自分向け)

完全に自分向けメモ。
ぼやっと本読んでたら何やら理解できて来た気がするので……

内容が正しいかはまったくもって保証しません。

■ CDI(Context and Dependency Injection)


    @Named(value = "injectSample")
    public class InjectSample {
        @Inject InjectedSample injected;
        public void sample() {
            injected.execute();
        }
    }


通常であればinjectedはどこかでインスタンス化する
必要がある(new InjectedSample();)が、CDIを利用することで
これをしなくてよくなる。

これによって、
InjectSampleとInjectedSampleの間の依存関係を疎にすることができる。
(その代わりInjectedSampleは引数を受け付けないコンストラクタによって
インスタンス化される)

■ コンテキスト

上記の内容を理解するにはコンテキスト(Context)にるいて理解する必要がある。

CDIにおけるコンテキストとは
スコープアノテーションによって指定されたオブジェクトの存続設定

■スコープアノテーション

CDIビーンに対して設定されるオブジェクトの存続期間


  • @RequestScoped
  • @SessionScoped
  • @ApplicationScoped
  • @ConversationScoped
  • @Dependent

■CDIビーン

管理ビーンの一種。CDIサービスによって管理される。
管理ビーンはコンテナ内で管理されており、
スコープに応じて適切なオブジェクトが自動で選択され、
変数に代入される。

2015年9月1日火曜日

WebSocketのサンプルコード(クライアント編)


WebSocketのクライアント(ブラウザ)側の実装サンプル。

テキストフィールドにメッセージを入れてSubmitボタンをクリックすると
他のクライアントにもメッセージが配信される。そんだけ。


Bootstrapを使ってみたけど、説明するって意味では、
明らかにコード可読性の低下要因でしかないな~......


以下の解説の用語とかは信用しないでください。
jQueryとBootstrap使ってるけれども正しい使い方ではないと思う。
基本的には、

  1. Websocketプロトコルでのアクセス先(wsUri)を指定
  2. Websocketをnewする(websocket)
  3. websocketに対して必要なメソッドを実装(ここではonerror, onopen, onmessage)
  4. クライアント上の操作内容に応じてjQueryでぐりぐりする
(ここでは、submitボタンをクリックするとテキストフィールドに入力したメッセージが 送信される。同時に他のクライアントにもメッセージが配信される。)
<html>
    <head>
        <title>Chatter</title>
        
        
        <link href="css/bootstrap.min.css" rel="stylesheet"></link>
        <script src="./js/jquery-2.1.1.js" type="text/javascript"></script>
        <script type="text/javascript">
            $(function(){
                var wsUri = "ws://" + document.location.host + "/WebSocketChatter/MessageReceiptor";
                var websocket = new WebSocket(wsUri);
                websocket.onerror = function(evt) {
                    
                };
                websocket.onopen = function(evt) {
                    
                };
                websocket.onmessage = function(message) {
                    console.log('Received Text:'+message.data);
                    $('<div class="well row" style="margin-bottom: 2px;">
'+message.data+'</div>
').appendTo('#container_message');
                };
                
                $('#btn_submit').on('click', function(){
                    var text = $('#text_message').val();
                    if(text !== '') {
                        websocket.send(text);                        
                        console.log('Send Text:'+text);
                    }
                });
                
                $('#btn_clear').on('click', function(){
                    $('#text_message').val('');
                });
            });
        </script>
    </head>
    <body>
        <div class="row">
            <div class="col-lg-3">
</div>
<div class="col-lg-3">
                <h1 class="label-primary" style="border-radius: 3px 3px;">
WebSocketChatter</h1>
</div>
</div>
<div class="row">
            <div class="col-lg-3">
</div>
<div class="col-lg-3">
                <h2 class="label-info">
入力項目</h2>
</div>
</div>
<div class="row">
            <div class="col-lg-3">
</div>
<div class="col-lg-3">
                <label for="text_message">Message</label>
                <input class="form-control" id="text_message" placeholder="Text Message" type="text" />
            </div>
</div>
<div class="row">
            <div class="col-lg-3">
</div>
<div class="col-lg-4 btn-toolbar">
                <div class="btn-group">
                    <button class="btn btn-success" id="btn_submit" type="button">Submit    <span class="glyphicon glyphicon-check"></span></button>
                    <button class="btn btn-danger" id="btn_clear" type="button">Clear   <span class="glyphicon glyphicon-remove"></span></button>
                </div>
</div>
</div>
<div class="row">
            <div class="col-lg-3">
</div>
<div class="col-lg-3">
                <h2 class="label-info">
メッセージコンテナ</h2>
<div class="col-lg-12" id="container_message">
                </div>
</div>
</div>
</body>
</html>

2015年8月31日月曜日

WebSocketのサンプルコード(サーバサイド)

GlassFish 4.1 + Java 1.7で記述。
(サポート切れててゴメン。たぶん1.8でも動くよ(祈り)。)

なんか画面の表示下のコード表示が微妙にバグ踏んでるけど、まあいいや。
(勝手に<Session>を</session>で閉じなくていいんだよ......</session>消せないし...)
※102行目の</session></session>は無視おねがいします。

package websocketchatter;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

/**
 * WebSocketのURIを指定。
 * @author USER
 */
@ServerEndpoint("/MessageReceiptor")
public class MessageReceiptor {

    private static Set peers = Collections.synchronizedSet(new HashSet());
    private static File file;
    private static BufferedWriter writer;

  /* いずれかのセッションからメッセージが送信された場合のメソッド */
    @OnMessage
    public String onMessage(String message) {
       for(Session s : peers) {
      /*
             非同期に各セッションにメッセージを送る。
           */
           s.getAsyncRemote().sendText(message);
           try {
               System.out.println(message);
               writer.write(message, 0, message.length());
               writer.newLine();
               writer.flush();
           } catch (IOException ex) {
               Logger.getLogger(MessageReceiptor.class.getName()).log(Level.SEVERE, null, ex);
           }
           System.out.println(message);
       }
        return null;
    }
  /* セッションがクローズされた際に呼び出されるメソッド */
    @OnClose
    public void onClose(Session peer) {
        peers.remove(peer);
    }

    /* セッションがオープンされた際に呼び出されるメソッド */
    @OnOpen
    public void onOpen(Session peer) {
    /*
         この処理は非常に雑なので参考にしないで。
     永続化に際してはJPAとかを使ってRDBに書き込んだりしたほうがいいと思います。
     滅茶苦茶雑な処理です。(こんな書き方はしてはいけません。)
         修正したら正式版を別記事で挙げます。
        */
        if(file == null) {
            Date date = new Date();
            SimpleDateFormat format = new SimpleDateFormat("YYYYMMdd");
            String str = format.format(date);
            file = new File(str+".log");
            try {
                System.out.println("File Path:" + file.getCanonicalPath());
            } catch (IOException ex) {
                Logger.getLogger(MessageReceiptor.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        
        if(writer == null) {
            try {
                writer = new BufferedWriter(new FileWriter(file));
            } catch (IOException ex) {
                Logger.getLogger(MessageReceiptor.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        
        try {
            BufferedReader reader = new BufferedReader(new FileReader(file));
            for(String str = reader.readLine(); str != null; str = reader.readLine()) {
                peer.getAsyncRemote().sendText(str);
            }
        } catch (FileNotFoundException ex) {
            Logger.getLogger(MessageReceiptor.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(MessageReceiptor.class.getName()).log(Level.SEVERE, null, ex);
        }       
        peers.add(peer);
    }    
}


そのうち、クライアント側処理(jQuery + Bootstrapを組み合わせた場合かな~...)
と組み合わせた時の画像か動画でも載せます。

ちな、jQueryとBootstrapの使い方として正しいのかは保証しません。

なので、今後のToDoとしては以下の通り

  1. クライアント側(ブラウザ側実装の提示)
  2. PowerPointスライド(画像)によるサーバサイド処理の内容の表示
  3. @OnOpen処理の屑さ加減の改善(JPAを使ったRDBによる永続化

JFreeChartで横軸の値を縦書きにする

JFreeChartでグラフを作るときに悩ましいのが、
横軸のラベル(って言えばいいのでしょうか?)の管理。
横軸ラベルの文字列が長くなると本来の値が
...に置換されてしまい残念な結果になってしまいます。
一応の対応策としては、ラベルを縦書きにすることで
問題を解消できます。
こんな感じで、修正可能です。
JFreeChart chart = ChartFactory.createBarChart("サンプルグラフ", "横軸", "縦軸", dataset);
chart.setAntiAlias(true);
CategoryPlot plot = chart.getCategoryPlot();
CategoryAxis axis = plot.getDomainAxis();
axis.setCategoryLabelPositions(CategoryLabelPositions.UP_90);

JFreeChartから上記処理に基づいて
CategoryAxisを取得しsetCategoryLabelPositionsメソッドで
向きを変更することができます。

2015年8月28日金曜日

なんだこれ

以下の記事だけやたらと閲覧数が多い。なんでだ?
対した記事ではないと思うんだけど。
所詮はjQueryのことを良く知らない人間が勉強し始めて即日書いたものなのに......


http://ryoma0923.blogspot.jp/2015/01/jquerycss.html


律儀に動画を作成したのがよかったのかな?

ユーザ主導の技術革新(まだ案レベル)

とくにパブリッククラウドに限った話になりますが、
技術革新はユーザ手動で起こるという印象を受けます。

詳細は改めて説明しますね。

ハイプサイクルに踊らされる危険性

所謂Gartnerの発表しているハイプサイクル

https://ja.wikipedia.org/wiki/%E3%83%8F%E3%82%A4%E3%83%97%E3%83%BB%E3%82%B5%E3%82%A4%E3%82%AF%E3%83%AB

  • 黎明期
  • 流行期
  • 幻滅期
  • 回復期
  • 安定期
の5つのフェーズで新しい技術が広がっていったり、途中で消えたりするアレです。
個人的には、発表する意義は大いにあるけれども如何わしいと思うものですね。

これには少なくとも一つだけ技術的にいえることがあります。

流行期から幻滅期に落ち込んだ時にどこまで頑張れるかという問題です。
最近の技術はOSS主導がほとんどです。
安定期にたどり着くまでの技術っていうのは幻滅期にどれだけのビッグベンダーが撤退せずに
踏みとどまったかである程度の趨勢が決まるという印象を受けます。
(根拠は見つけ次第追記しようかと。なければこの記事は撤回。) 

正直なところはビッグベンダー以外はは審美眼とかそういうレベルの話になってしまい、
山師が鉱脈を見つける以上の根拠はなかなか説明しづらいと思います。
(ビッグベンダーにガンガン売り込んでファンを増やすという手はあるかもしれません。)
これまでも技術的に優れていても消えていった技術は山ほどある訳で。
結局のところ安定期にたどり着ける技術というのを確実に見分ける方法は永遠の課題になり続けると思います。

注意したいのは、仮に安定期までたどりついたテクノロジーがあったとしてもそれが流行期に大量に広まったものと同じものであるかという問題です。

最近の例だとハイプサイクルに直接載っているかは知りませんが、
Hadoopがいい例かと思います。

Hadoopのアーリーアダプター?の悩みがHadoopのバージョン間での断絶です。
Hadoopの2.2以降で導入されたYARNについては様々なHadoopのバリエーションを生み出してしまいました。(とはいっても使いたい処理内容別にいろいろな選択肢が用意されたという観点では悪いこととは一刀両断できないわけですが)

Hadoopに限った話でよいのでそこに対するアプローチって何かあるのでしょうかね?

一部のSIerのようにコミッタを輩出してコミュニティをコントロールしようとするなんて力技もあったりするわけですが。

ただ一つ言えることはテクノロジー企業である限りは安定期に入ってからのんびり参入なんてのは基本的には勝ち目は薄いと思います。(先進企業を買収するなんて力技除く)

適材適所の"所"って?(スコープの問題)

たまにはらしくない全体像のお話し。
適度にアルコールも入ったところで。


ものの配置などの選択をする際の基本原則があります。

”適材適所"

小学生でも知っている言葉ですね。

ですがこの言葉の厄介なところは、
適材適"所"の"所"のスコープです。

それを表すためによく使われる言葉が、

"局所最適化"と"全体最適化"。

適材適所の"所"の部分を考えてできるだけ
広い範囲での適材適所を考えられるように
なりたいというのが今のところの目標。

これをどこまで広く考えられるかが、
ここでの問題。

例えば孫正義さんみたいなレベルになると
常人には考えられないスコープで考えてしまうので、
一般人からすると完全に突飛な考えが出てきてしまう
傾向が強いのかなと思ったり。

少なくともPepperなんかは広告塔以上の役割は
すぐには出てこないけれども将来的には
絶対に有用性の出てくる技術かと思います。

そもそも局所最適化を突き詰めることが
できる人もなかなか少ないのもアレなわけですが。
局所最適化はブラックボックスを生み出しやすい
といった問題をはらんでいます。

(エンジニアの方には理解いただけるかもしれませんが、
パフォーマンスチューニングしまくったソースコードは
可読性が下がるってアレです。)

全体最適といってもどこまでを全体とみなすかという
問題はどうしても生じてしまうんですけどね。

企業の一担当者であれば、自身の担当範囲。
(もう少し広く考えてほしいというのはあるかもしれませんが)

企業の管理者であれば、自身の管理範囲内での
全体最適を目指さなければなりません。

政治家だったら自分の国全体における全体最適を
目指す必要があります。

人によって全体最適の全体の定義が変わってしまうのは
仕方ないと思います。おそらく他人からそういった観点で
尊敬される人というのは自身の担うべき範囲を少し超えた範囲まで
カバーして全体最適(に近いもの)を出せる人なのかなと......

なかなか私自身は自身の範囲内での局所最適化に
はまりやすいですしそこまでいたらないことも多いですが、
なるべく広い範囲での全体最適を目指して動きたいですね。


あ~、酒で正気を保っていないだけに取り留めのない文章に
なってしまいましたが、ひとまずこの記事はここまで。


2015年7月15日水曜日

pdfファイルにJFreeChartのグラフを出力

JFreeChartで出力したグラフをiTextを使ってpdfファイルに張り込む。

全体的な流れとしては、

  1. JFreeChartでグラフを生成
  2. 1.からBufferedImageを生成
  3. ChatUtilityを使ってPNG形式でエンコードしたものをbyteの配列として取得
  4. iTextのDocumentクラスをインスタンス化
  5. byteの配列からImageクラスを生成
  6. Imageのpdfファイル上の貼り付け場所を決める
  7. PdfWriterを生成する
  8. Documentをオープンする
  9. PdfContentByteを取得してimageを張り付ける
  10. Documentをクローズする
いつも通り品質は保証しません。


package variouslibraries.javalearning.jfree;

import com.itextpdf.text.BadElementException;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Image;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfWriter;
import java.awt.image.BufferedImage;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.category.DefaultCategoryDataset;


public class JFreeChartToPDF {
    public static void main(String[] args) {
        
 //グラフを作る
 String series1 = "Kyoto";
 String series2 = "Hyogo";
 String series3 = "Osaka";
 String category1 = "Category 1";
 String category2 = "Category 2";
 String category3 = "Category 3";
 String category4 = "Category 4";
 String category5 = "Category 5";
 String category6 = "Category 6";
 DefaultCategoryDataset dataset = new DefaultCategoryDataset();
 dataset.addValue(2.0, series1, category1);
 dataset.addValue(3.0, series1, category2);
 dataset.addValue(4.0, series1, category3);
 dataset.addValue(2.0, series1, category4);
 dataset.addValue(1.0, series1, category5);
 dataset.addValue(3.0, series1, category6);

 dataset.addValue(4.0, series2, category1);
 dataset.addValue(1.0, series2, category2);
 dataset.addValue(2.0, series2, category3);
 dataset.addValue(3.0, series2, category4);
 dataset.addValue(1.0, series2, category5);
 dataset.addValue(2.0, series2, category6);

 dataset.addValue(5.0, series3, category1);
 dataset.addValue(3.0, series3, category2);
 dataset.addValue(2.0, series3, category3);
 dataset.addValue(6.0, series3, category4);
 dataset.addValue(5.0, series3, category5);
 dataset.addValue(7.0, series3, category6);
        
      JFreeChart chart = ChartFactory.createLineChart("Sample Line Chart", "Category", "Value", dataset, PlotOrientation.VERTICAL, true, true, false);
 chart.setAntiAlias(true);   
        
        
        
        //byteの配列を変更する。
        float width = PageSize.A4.getWidth();
        float height = PageSize.A4.getHeight();
        BufferedImage bufImage = chart.createBufferedImage((int)width, (int) (height/2.0));
        byte[] byteArray = null;
        try {
            byteArray = ChartUtilities.encodeAsPNG(bufImage);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        
        //pdfファイルを作成する
        Document document = new Document(PageSize.A4);
        try {
            Image image = Image.getInstance(byteArray);
            image.setAbsolutePosition(0, document.getPageSize().getHeight() - bufImage.getHeight());
            PdfWriter instance = PdfWriter.getInstance(document, new FileOutputStream("PdfSample2.pdf"));
            document.open();
            PdfContentByte pdfContentByte = instance.getDirectContent();
            pdfContentByte.addImage(image);
        } catch (BadElementException | IOException ex) {
            ex.printStackTrace();
        } catch (DocumentException ex) {
            ex.printStackTrace();
        }
        document.close();
    }
}


2015年7月14日火曜日

JavaでXMLを読み込む(DOM版)

PowerShellでXMLを読み込む方法を書いたので、
今度はJavaで読み込む方法も書いてみた。
DOM使って読み込む。

まあ......手順としては理解しやすいけどめんどいね。
以下のファイルを読み込んでみた。


 

    ChildA
    ChildB
    ChildC


コードの全体の流れとしてはこんな感じ。

  1. Fileをインスタンス化
  2. DocumentBuilderFactoryをインスタンス化
  3. DocumentBuilderをインスタンス化
  4. DocumentBuilderを使って1をparseしてDocumentをインスタンス化
  5. 4.からElementにxml文書のルートノードを格納
  6. ElementからNodeListを取得
6はここでは、getElementsByTagIdメソッドを使っている。
getChildNodeでも同じ結果が得られると思って使ってみると、なんか
NodeListのgetLengthメソッドの結果が違う......
これは要調査。

package variouslibraries.javalearning.xml;

import java.io.File;
import java.io.IOException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class XMLReader {
    public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
        File file;
        file = new File("./SampleXML.xml");
        
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        Document document = builder.parse(file);
        
        Element root = document.getDocumentElement();
        System.out.println("Root要素のタグ名:"+root.getNodeName());
        
        System.out.println("子要素を取得して値を表示。");
        NodeList children = root.getElementsByTagName("Child");
        for(int i = 0; i < children.getLength(); i++) {
            Node node = children.item(i);
            System.out.println(node.getTextContent());
        }        
    }
}


実行結果

Root要素のタグ名:Parent
子要素を取得して値を表示。
ChildA
ChildB
ChildC

PowerShellを使ったXMLファイルの読み込み

XMLファイルの読み込みはPowerShellは超得意。

 下記のようなXMLファイルがあった場合、

 ChildA
 ChildB
 ChildC

以下のようなpowershellスクリプトで簡単に要素の取り出しができる。

$xmlFilePath = "C:\Users\USER\OneDrive\PowerShell\XML読み込み\simple.xml"
$target = [xml](Get-Content $xmlFilePath)

if( $target -eq $null ){
    Write-Warning "ファイル読み込み失敗"
} else {
    Write-Warning "同じ名前のノードは配列として読み込まれる"
    Write-Host $target.Parent.Child;
    
    Write-Warning "なので、foreachで要素一つ一つを拾える"
    foreach($child in $target.Parent.Child) {
        Write-Host $child
    }
}


実行結果はこんな感じ。


PS C:\Users\USER\OneDrive\PowerShell\XML読み込み> C:\Users\USER\OneDrive\PowerShell\XML読み込み\XmlLoading.ps1
警告: 同じ名前のノードは配列として読み込まれる
ChildA ChildB ChildC
警告: なので、foreachで要素一つ一つを拾える
ChildA
ChildB
ChildC

2015年7月13日月曜日

iTextを使ってpdfを出力する

ずっと食わず嫌いで触っていなかったので、今回こそ克服のために触る。


iTextってpdfライブラリを使ってHello world的なことをやってみた。


  1. Documentをインスタンス化
  2. PdfWriterインスタンス化時に1をFileOutputStreamと一緒に放り込む
  3. Documentをオープン(document.open());
  4. Documentに要素を追加(document.add());
  5. Documentをクローズ(document.close());

なんか1.でサイズを指定したり、4.で文字列以外も追加できる様子。
(まあ、当たり前ですが)

package variouslibraries.javalearning.log4j.itext;

import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.pdf.PdfWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author USER
 */
public class ITextPdfSample {
    public static void main(String[] args) {
        Document document = new Document();
        try {
            PdfWriter instance = PdfWriter.getInstance(document, new FileOutputStream("PdfSample.pdf"));
            document.open();
            document.add(new Paragraph("PDF Sample!"));
        } catch (FileNotFoundException | DocumentException ex) {
           ex.printStackTrace();
        }
        document.close();
    }
}
</pre>


はい、食わず嫌いしてて申し訳ありませんでした。

出来上がったファイルは以下。
完成品はかわいげもへったくれもないですね。
気が向いたらいろいろ試してみます。

古い記事ですが、
あたりに、いろいろ追加情報があったり、サーブレットからキックして
pdf出力させたい人にはいいかも。

他のライブラリが欲しいって人は
を参照すると幸せになれるかも。

JFreeChartでSVG出力(JFreeSVG利用)

JFreeChartもJFreeSVGもjfree.orgが開発しているんで、
相性はよかろうという安易な考えで書いてみた。

流れとしては、

  1. 適当にJFreeChartのグラフを作る
  2. SVGGraphics2D(キャンバスみたいなもん)を作成
  3. Rectangle2Dを作成
  4. SVGUtilsで出力
というながれ、2,3は出力先のキャンバスとキャンバス上に
どれくらいのサイズで出力するかを設定する感じ。
一先ず動きゃええの精神で作っているので品質は保証しません。


package variouslibraries.javalearning.jfree;

import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.IOException;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.graphics2d.svg.SVGGraphics2D;
import org.jfree.graphics2d.svg.SVGUtils;


public class JFreeChartSVGOutput {

 public static void main(String[] args) {
  // (1)データセットの作成
  String series1 = "First";
  String series2 = "Second";
  String series3 = "Third";
  // カテゴリーの設定
  String category1 = "Category 1";
  String category2 = "Category 2";
  String category3 = "Category 3";
  String category4 = "Category 4";
  String category5 = "Category 5";
  String category6 = "Category 6";
  DefaultCategoryDataset dataset = new DefaultCategoryDataset();
  dataset.addValue(3.0, series1, category1);
  dataset.addValue(2.0, series1, category2);
  dataset.addValue(4.0, series1, category3);
  dataset.addValue(4.0, series1, category4);
  dataset.addValue(4.0, series1, category5);
  dataset.addValue(6.0, series1, category6);

  dataset.addValue(5.0, series2, category1);
  dataset.addValue(3.0, series2, category2);
  dataset.addValue(6.0, series2, category3);
  dataset.addValue(5.0, series2, category4);
  dataset.addValue(6.0, series2, category5);
  dataset.addValue(5.0, series2, category6);

  dataset.addValue(6.0, series3, category1);
  dataset.addValue(7.0, series3, category2);
  dataset.addValue(7.0, series3, category3);
  dataset.addValue(6.0, series3, category4);
  dataset.addValue(5.0, series3, category5);
  dataset.addValue(7.0, series3, category6);

  // (2)JFreeChartオブジェクトの生成
  JFreeChart chart = ChartFactory.createLineChart("Sample Line Chart", "Category", "Value", dataset, PlotOrientation.VERTICAL, true, true, false);
  chart.setAntiAlias(true);
  // (3)グラフの出力

  SVGGraphics2D svg = new SVGGraphics2D(640, 480);
  Rectangle2D rectangle = new Rectangle(640, 480);
  chart.draw(svg, rectangle);

  try {
   SVGUtils.writeToSVG(new File("./SampleLineChart.svg"), svg.getSVGElement());
  } catch (IOException e) {
   e.printStackTrace();
  }
 }

}
出力される画像は以下のような感じ。
実際のファイルだと拡大縮小してもスムーズな線のままです。
(したのファイルはpngにしているのでそうもいきません。)

Inkscapeやらで読み込むと一つ一つの線やら背景やらを
編集できるけど、使い道は知らない。

2015年5月7日木曜日

dateコマンドを使って日付フォーマットを指定

緒戦は自分向けのメモなので適当


前提

export LANG=C していること。

内容


2015年5月7日 午後10時22分45秒を 2015/05/07:22:22:45 と表示する。

date +%Y%m%d:%H:%M:%S

これでO.K.

実行結果

$ date +%Y/%m/%d:%H:%M:%S
2015/05/07:22:23:19

JSONをHTTPリクエストメッセージに載せる

wgetやcurlを使ってJSONメッセージをHTTPリクエストに載せる方法。

POSTメッセージを使えばリクエストボディに情報を含めることができる。
これを用いて、JSONメッセージをHTTPリクエストメッセージに載せる。

1. curl版

curl -v -H "Accept: application/json" -H "Content-type: application/json"
 -X POST -d {JSONデータ} {URI}

認証はまた別の機会に記載する予定。

2015年1月9日金曜日

WebSocketについてしらべる

先日、グラフにAjaxを使って要素を追加するサンプルを作ってみた。


JAX-RSでJSONを出力してみた。


が、こいつをWebSocketでやれないかと。

イメージ的には、グラフのデータがサーバサイドで更新されたら即グラフに反映されるイメージ。
WebSocketについては聞いたことがある程度で実際のコードやら詳細な仕組みやらについては
詳しいわけではないので調査。ちょっと古い資料ではあるけれども
OracleのArun Guptaさん作成の資料があったので参考までに載せておく。



Java API for WebSocket 1.0: Java EE 7 and GlassFish from Arun Gupta

また、きしださんのサンプルもあったのでリンクを張っておく。

WebSocketをネタにJava EE 7正式版を試してみる

強引に実装する方法はなくもなさそうだけれども、あまりスマートな実装には
なりそうにないので、何とかいい方法はないかと調査中。。。

(2015/1/9 23:00 追記) ちょうど想定していたものを見つけた。
http://ameblo.jp/principia-ca/entry-11513826700.html

...と思ったが、もともと想定していた力押し実装だった(泣)

(↑と同時にさらに追記)
http://software.fujitsu.com/jp/technical/interstage/apserver/guide/pdf/WebSocket.pdf
こんな資料発見。目を通しておこう。

(2015/8/31 さらにさらに追記)
ごくごく簡単なアプリを作ってみたので今度追加します。※
※特にきしださんのものと変わんない(+ bootstrapつかってるくらい)のできしださんに怒られたら消します。技術的な側面についてはちゃんと解説したものを載せますね。




2015年1月8日木曜日

JAX-RSでJSONを出力してみた。

ほぼ自分用のメモ。
JAX-RSでJSONを出力してみました。

これを使えば、JAX-RS + AJAXの組み合わせで
ThinServerアーキテクチャライクなことができる...のか?


(手順)

  1. GlassFishでWebアプリケーションプロジェクトを作成。
  2. パターンからのRESTful Webサービスを作成する。
  3. 単純なルート・リソースを作成
  4. パスを指定(今回はSampleを設定)。* MIME-TYPEには application/jsonを指定。
  5. getJSONメソッド内でJSONを作り文字列にして返す。

* http://localhost:8080/アプリケーション名/webresources/パス がアクセスパスになる。
  RESTful Webサービスディレクトリ配下にあるクラス名を右クリックして
 テスト・リソースURIを選択すると、実行できる。(GlassFishをあらかじめ起動しておく必要がある)


以下、サンプルコードです。

SampleResourde.java

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package sample;

import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObjectBuilder;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.PathParam;
import javax.ws.rs.Consumes;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.GET;
import javax.ws.rs.Produces;

/**
 * REST Web Service
 *
 * @author USER
 */
@Path("Sample")
public class SampleResource {

    @Context
    private UriInfo context;

    /**
     * Creates a new instance of SampleResource
     */
    public SampleResource() {
    }

    /**
     * Retrieves representation of an instance of sample.SampleResource
     * @return an instance of java.lang.String
     */
    @GET
    @Produces("application/json")
    public String getJson() {
        String json;
        JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
        JsonObjectBuilder objBuilder = Json.createObjectBuilder();
        
        objBuilder.add("time", "2015/01/23 14:30:25");
        objBuilder.add("value", 45.5);
        arrayBuilder.add(objBuilder);
        
        objBuilder.add("time", "2015/01/23 14:30:25");
        objBuilder.add("value", 45.5);
        arrayBuilder.add(objBuilder);
        
        JsonArray jsonArray = arrayBuilder.build();
        json = jsonArray.toString();
        
        return json;
    }

    /**
     * PUT method for updating or creating an instance of SampleResource
     * @param content representation for the resource
     * @return an HTTP response with content of the updated or created resource.
     */
    @PUT
    @Consumes("application/json")
    public void putJson(String content) {
    }
}

出力はこんな感じになります。
(適宜改行入れてます。)

http://localhost:8080/アプリケーション名/webresources/Sample にアクセス。

[
    {
     "time":"2015/01/23 14:30:25",
     "value":45.5
    },{
     "time":"2015/01/23 14:30:25",
     "value":45.5
    }
]

2015年1月1日木曜日

jQuery+CSSでウィンドウの最小化を実現してみた(+最大化改善)

jQuery+CSSでウィンドウの最小化を実装。
ついでにウィンドウをドラッグしたときに移動できるようにもしてみました。



まともにテストしていない & 例外処理甘いです。
特定の操作組み合わせで変な動作します。


Window.html
<!DOCTYPE html>
<html>
    <head>
        <title>TODO supply a title</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="stylesheet" href="./css/Window.css">
        <script src="./js/vendor/jquery-2.1.1.js"></script>
        <script src="./js/vendor/jquery-ui.js"></script>
        <script src="./js/Window.js"></script>
    </head>
    <body>
        
        <button class="show">ウィンドウを表示</button>
        
        <div class="window">
            <div class="title">
                ウィンドウのタイトル
            </div>
            <button class="minimize">-</button>
            <button class="maximize">□</button>
            <button class="close">×</button>
            <div class="content">
                コンテンツの内容はこんな感じですよ。
                コンテンツの内容はこんな感じですよ。
                コンテンツの内容はこんな感じですよ。
                コンテンツの内容はこんな感じですよ。
                コンテンツの内容はこんな感じですよ。
                コンテンツの内容はこんな感じですよ。
                コンテンツの内容はこんな感じですよ。
                コンテンツの内容はこんな感じですよ。
                コンテンツの内容はこんな感じですよ。
                コンテンツの内容はこんな感じですよ。
                
            </div>
        </div>
    </body>
</html>
Window.css
.window {
    position:absolute;
    width: 260px;
    height: 180px;
    border-style: solid;
    border-width: 2px;
    border-color: rgb(150, 150, 150);
    background: rgb(218, 218, 218);
}

.window > .title {
    position: absolute;
    left: 0px;
    display: inline-block;
    background-color: rgb(0, 0, 230);
    border-color: rgb(0, 0, 180);
    border-width: 2px;
    height: 20px;
    width: 260px;
    margin: 0px;
    color: white;
}

.window > button {
    position: absolute;
    display: inline-block;
    border-width: 2px;
    border-style: solid;
    border-color: rgb(150, 150, 150);
    background-color: rgb(218, 218, 218);
    height: 20px;
    margin: 0px;
}

.window > .minimize {
    right: 40px;
}
.window > .maximize {
    right: 20px;
}
.window > .close {
    right: 0px;
}

.window > .content {
    position: relative;
    display: block;
    overflow: scroll;
    top: 20px;
    width: 260px;
    height: 160px;
}

.show {
    position: relative;
    display: block;
    top: 0px;
    left: 0px;
}
Window.js
$(function(){
    
    var $window = $('.window'),
        $maximize = $window.find('.maximize'),
        $minimize = $window.find('.minimize'),
        $close = $window.find('.close'),
        $title = $window.find('.title'),
        $contents = $window.find('.content'),
        $showButton = $('.show'),
        $baseWindow = $(window);


        /*
         * ドラッグできるようにする。
         */
    jQuery('.window').draggable();
    
    /*
     * (ブラウザ全体の)ウィンドウサイズを取得する。
     */
    var width = $baseWindow.width(),
        height = $baseWindow.height();
    
    
    /*
     * もともと表示されているウィンドウサイズを取得する。
     */
    var originalWidth = $window.width(),
        originalHeight = $window.height();
    
    /*
     * ウィンドウの幅、高さ、位置を記録するための変数を準備
     */
    var windowWidth, windowHeight, windowOffset;     
    
    var duration = 750;
    
    
    /*
     * 初期サイズ合わせ。
     */
    windowWidth = $window.width();
    $title.css({ width: windowWidth+'px' });
    
    /*
     * 閉じるボタン(×)をクリック時の挙動
     */
    $close.on('click', function(){
        $window.css({ display: 'none' });
    });
    
    /*
     * 最大化ボタン(□)をクリックした時の挙動
     */
    $maximize.on('click', function(){
        width = $baseWindow.width();
        height = $baseWindow.height();
        /*
         * miximizedクラスを使って最大化フラグを管理
         */
        $(this).toggleClass('maximized');
        if($(this).hasClass('maximized')){
            /*
             * 最大化フラグがあるときは
             * ブラウザウィンドウの横幅、縦幅まで
             * ウィンドウサイズを拡張、
             * 位置は左上に移動する。
             */
            
            /*
             * 最大化解除の際の位置戻しができるように
             * ウィンドウの位置を事前に記憶しておく。
             */
            windowOffset=$window.offset();
            $window.stop(true).animate({
                top: '0px',
                left: '0px',
                width: width+'px',
                height: height+'px'
            }, {
                duration: duration,
                queue: false
            });
            $title.stop(true).animate({
                width: width+'px'
            },{
                duration: duration,
                queue: false
            });
            $contents.stop(true).animate({
                width: width+'px',
                height: height+'px'
            },{
                duration: duration,
                euque: false
            });
        } else {
            /*
             * ウィンドウの位置を記憶しておく。
             */
            $window.stop(true).animate({
                top: windowOffset.top+'px',
                left: windowOffset.left+'px',
                width: originalWidth+'px',
                height: originalHeight+'px'
            }, {
                duration: duration,
                queue: false
            });
            $title.stop(true).animate({
                width: originalWidth+'px'
            },{
                duration: duration,
                queue: false
            });
            $contents.stop(true).animate({
                width: originalWidth+'px',
                height: originalHeight+'px'
            },{
                duration: duration,
                euque: false
            });
        }
        $contents.css({display:'inline-block'});
    });
    
    /*
     * ウィンドウを表示 ボタンをクリックしたときの挙動
     */
    $showButton.on('click', function(){
        $window.css({ display: 'inline-block' });
    });
    
    

    /*
     * 最小化(-)ボタンをクリックしたときの挙動
     */
    $minimize.on('click', function(){
        var height = $(window).height();
        var titleHeight = $title.height();

        /*
         * minimizedクラスで最小化フラグを管理
         */
        $window.toggleClass('minimized');
        if($window.hasClass('minimized')){
            /*
             * 最小化フラグありの場合は
             * ウィンドウの現在位置を記録した後に
             * コンテンツ非表示、ウィンドウの横幅を
             * 変更した後に画面最下部に移動させる。
             */
            windowOffset= $window.offset();
            windowWidth = $window.width();
            windowHeight = $window.height();
            $contents.css({ display: 'none' });
            $window.animate({
                top: height - 20 +'px',
                left: '0px',
                width: originalWidth+'px',
                height: titleHeight+'px'
            },{
                duration: duration,
                queue: false
            });
            $title.animate({
                width: originalWidth+'px'
            },{
                duration: duration,
                queue: false
            });
        } else {
            /*
             * 最小化フラグを持たない場合は
             * コンテンツを再表示したのち、ウィンドウサイズ、位置を
             * 以前の状態に戻す。
             */
            $contents.css({ display: 'inline-block'});
            $window.animate({
                width: windowWidth+'px',
                height: windowHeight+'px',
                top: windowOffset.top+'px',
                left: windowOffset.left+'px'
            },{
                duration: duration,
                queue: false
            });
            $title.animate({
                width: windowWidth+'px'
            },{
                duration: duration,
                queue: false   
            });
        }
    });
});