2013年4月28日日曜日

AD 情報取得のための Java プログラム

前回は AD 認証を行うサンプルを紹介しましたが、今回は AD から情報を取り出すサンプルを紹介します。AD にはいろいろな情報がありますが、今回はユーザの名前、アカウントID、メールアドレスを抽出するプログラムを紹介します。

import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.AuthenticationException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchResult;
import javax.naming.directory.SearchControls;

/**
 * JNDI の LDAP インタフェースを用いて AD からユーザ情報を取得
 *
 * 引数1:AD 接続先 URL
 * 引数2:AD のドメイン名
 * 引数3:ユーザID(AD が参照できる Administrator 等)
 * 引数4:パスワード
 * 引数5:検索先のルートとなる DN
 * 引数6:抽出する属性のリスト(カンマ区切り)。
 *     userPrincipalNameLocal ⇒ アカウントID(ドメインを含まない)を出力
 */
public class adlist {

    public static void main(String args[]) throws NamingException {

        Hashtable env = new Hashtable();

        // Contextファクトリ
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");

        // 接続先URL
        env.put(Context.PROVIDER_URL,args[0]);

        // セキュリティレベル
        env.put(Context.SECURITY_AUTHENTICATION, "simple");

        // ユーザID+ドメイン
        env.put(Context.SECURITY_PRINCIPAL, args[2] + "@" + args[1]);

        // パスワード
        env.put(Context.SECURITY_CREDENTIALS, args[3]);

try {
           // AD に接続
           InitialDirContext context = new InitialDirContext(env);

// 指定されたルート DN 以下のユーザを検索
  String base = args[4];
  String filter = "CN=*";
  SearchControls controls = new SearchControls();
  controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
  NamingEnumeration answer = context.search(base, filter, controls);

  // 引数の抽出属性リストを分解
  String[] cols = args[5].split(",");
  int colSize = cols.length;

  // 検索結果のループ
  while ( answer.hasMore() ) {
    SearchResult result = (SearchResult)answer.next();
      Attributes attr = result.getAttributes();
      for ( int i = 0; i < colSize; i++ ) {
        if ( cols[i].startsWith("userPrincipalNameLocal") ) {
          // アカウントID(ドメインを含まない)を出力
        String name = attr.get("userPrincipalName").get().toString();
        name = name.substring(0, name.indexOf("@"));
        System.out.print(name);
        } else {
        // 指定された属性を出力
        if ( attr.get(cols[i]) != null ) {
          System.out.print(attr.get(cols[i]).get().toString());
        }
        }
      if ( i != colSize -1 ) {
      System.out.print(",");
        }
       }
      System.out.println();
     }
    } catch (AuthenticationException ex) {
    // 認証エラー
    System.out.println("認証エラー");
  } catch (NamingException ex ) {
    // その他通信エラー等
    ex.printStackTrace();
  }
    }
}

このプログラムは、以下のようにパラメータを渡して利用することができます。


set PARAM=ldap://XXX.XXX.XXX.XXX:389/
set PARAM=%PARAM% mypcad.com
set PARAM=%PARAM% Administrator
set PARAM=%PARAM% "mypassword"
set PARAM=%PARAM% "OU=NAMELESS,DC=my0000ad,DC=mypcad,DC=com"
set PARAM=%PARAM% cn,displayName,userPrincipalNameLocal,mail

java adlist %PARAM%


第5引数は、検索のルートとなる OU の DN を指定します。正しい DN を調べるには、以下のコマンドを利用します。

dsquery user -name NAMELESS

第6引数には、取得したい AD の属性をカンマ区切りで指定します。AD がどのような属性を持つかは、以下のサイトが参考になります。

http://support.microsoft.com/kb/257218/ja

プログラムでは、属性に userPrincipalNameLocal を指定した場合に、ユーザー プリンシパル名(accountid@domain のような情報)からアカウントIDのみを取り出して表示するように工夫しています。

意外と簡単にできますね。


2013年4月16日火曜日

AD認証の Java プログラム

AD認証のJavaプログラムを書く機会があったので、紹介。


import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.AuthenticationException;
import javax.naming.directory.InitialDirContext;

/**
 * JNDI の LDAP インタフェースを用いて AD認証を実行
 *
 * 引数1:AD 接続先 URL
 * 引数2:AD のドメイン名
 * 引数3:ユーザID
 * 引数4:パスワード
 */
public class adauth {

    public static void main(String args[]) {

        Hashtable env = new Hashtable();

        // Contextファクトリ
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");

        // 接続先URL
        env.put(Context.PROVIDER_URL,args[0]);

        // セキュリティレベル
        env.put(Context.SECURITY_AUTHENTICATION, "simple");

        // ユーザID+ドメイン
        env.put(Context.SECURITY_PRINCIPAL, args[2] + "@" + args[1]);

        // パスワード
        env.put(Context.SECURITY_CREDENTIALS, args[3]);

try {
// 認証チェック
InitialDirContext context = new InitialDirContext(env);
System.out.println("OK");
} catch (AuthenticationException ex) {
// 認証エラー
String msg = ex.getExplanation();
if ( msg.indexOf("AcceptSecurityContext") > 0 &&
msg.substring(msg.indexOf("AcceptSecurityContext")).indexOf("775") > 0 ) {
System.out.println("ユーザがロックアウト");
} else {
System.out.println("認証エラー");
}
} catch (NamingException ex ) {
// その他通信エラー等
ex.printStackTrace();
}
    }
}

以下のコマンドで実験できます。


java adauth ldap://XXX.XXX.XXX.XXX:389/ mypcad.com myuserid "mypassword"

一応、AD認証のエラー判定として、通常の認証エラーと、パスワード間違えすぎてロックアウトされているエラーは区別したいのですが、どうがんばってもエラーメッセージからしか判定できないような気がします。こんな実装で良いんかな?誰か良い方法があれば教えてください。





2013年4月14日日曜日

多忙なSEのためのTODO管理


みなさんはTODO管理をどのように実施しているでしょうか。手帳やノートを活用している人も多いと思いますが、最近はスマートフォンでTODO管理ができるアプリも増えていますので、それらを活用している人も多いと思います。

私は iPhone を使っていますが、AppStore にはすごくたくさんのTODO管理アプリがあります。日々多忙なSEにとって、どのようなアプリが最適なのか考察してみたいと思います。

完全に個人的な解釈ですが、TODO管理には2つのパターンがあると考えています。

①それ単独で完結するタスクのTODO管理
②プロジェクト単位にまとめられたタスクのTODO管理

例えば、「作業表作成」や「○○会議資料作成」といった単発のタスクは①、○○プロジェクトに関する「技術調査」、「PJ計画書作成」、「要員調達」といったタスクは②に当てはまります。

もしあなたがPMや上級SEでなく、プロジェクトに組み込まれた1担当者の立場であるなら、①のTODO管理ができていれば十分と思われます。なぜならそういう立場であれば、通常は1つのプロジェクトに専任することが多く、またプロジェクトに関わる様々なタスクは、PMや上級SEが運営するPJ管理の中で指示されることになるからです。

もしあなたがPMや上級SEであり、プロジェクトの様々なタスクを整理して実施するだけでなく、同時に複数のプロジェクトの管理もしなければならない立場であるなら、①と②の両方のTODO管理が必要になるでしょう。

そしてTODO管理のアプリに関しても、①のみの管理のために最適なアプリと、①と②の両方の管理のために最適なアプリがあると思います。

今回は①のみの管理のために使える(と個人的に思っている)iPhoneアプリを紹介したいと思いますが、その前にアプリ選定の前提条件を説明します。
  • すべてのTODO管理を iPhone で行います。最近はデータをクラウドで管理して、プラウザやPCのアプリでも管理できるタイプもありますが、客先常駐のSEであれば、セキュリティ上の制約で勝手にPCにアプリをインストールすることもできず、場合によってはインターネットへの接続すら制限されているところもあるからです。このため、クラウド機能は必須にはしません。
  • 複雑な機能は不要とし、シンプルに最小限の手順でTODO管理が行えるものを選定します。多忙なSEは、TODO管理は期限の設定さえできれば十分で、タグやフォルダ分け、優先順位を複雑に管理して、「自己管理できている感」に浸っている余裕はないからです。
以下、お勧めの順番です。

■Toodledo

Toodledo
Toodledo - To Do List(250円) 
販売元:Toodledo(18MB)

TODO管理アプリとしては一番有名なのではないでしょうか。

初期表示画面です。


「タスクを追加する」ボタンを押して、タスク追加の画面を表示します。


「詳細を編集する」ボタンを押して、タスクの詳細を編集する画面を表示します。



「期日」の箇所をタップして期日の編集画面を表示します。



この画面で「曜日」が表示されているところがポイントです。なぜなら、期日を意識するときに○日までに、ではなく、今週の○曜日までに、で考えることも多いからです。また、「カスタム」をタップすることで、カレンダーから期日を設定することもできます。

この手順でタスクを追加していくと、最終的にこのようなタスクリストができます。これらのタスクを完了させるには、チェックボックスをタップします。



Toodledo はアプリの動作をかなり細かく設定できるところが優れています。お勧めの設定は、スタートページを「ホットリスト」、フィールドのデフォルト値の期日を「今日」、ホットリストのソート順を「期日」「優先度」です。このようにすれば、アプリを起動してタスクの管理が最小限の手順で実行できます。
  • ホットリストからタスクを追加、期限を設定してホットリストに戻るまで5タップ
  • ホットリストからタスクの消し込みは1タップ

■Remember The Milk

Remember The Milk
Remember The Milk(無料) 
販売元:Remember The Milk(11MB)


Remember The Milk (RTM)は、無料アプリとしてはかなり高機能です。それゆえマニアックな部分も多く、ファンも多い半面、使いこなせていない人も多いと聞きます。しかし私は、難しい使い方をするのではなく、単純なTODO管理のみに利用する場合であれば、十分に使いやすいアプリなのではないかと考えています。
※実際には有料のPROアカウントもあります。無料アカウントの制約は、クラウド同期が1日に1回のみ、通知機能は無効、といったところです。


初期画面はこのようなイメージです。左端のメニューは「今週」「今日」「明日」「リスト」「タグ」「場所」「検索」です。「リスト」はフォルダ分けやプロジェクト分類に利用できますが、ここではその様な利用の仕方はしないので、単純に「今週」のメニューだけ利用します。左端のパネルを左にスワイプすると、以下の画面が表示されます。


「追加」のところをタップすると、タスクが入力できます。


タスク名を入力し、「カレンダー」のマークをタップすると、直接期日を入力できます。


ここで指定できる「期日」は1週間以内の日です。それより先の期日を設定したい場合は、後で説明するタスクの編集を行います。このようにタスクを編集して追加していくと、以下のようなリストになります。



タスクを編集するには各タスクをタップし、タスクの詳細画面を表示します。


編集ボタンをタップすると、各項目の編集画面が開きます。


「期日」をタップすると、カレンダーから選択できるようになります。


タスクを完了状態にするには、タスク一覧でタスクを長押しします。



「完了」ボタンが表示されるので、それをタップすると完了状態になります。

RTMは本当に機能が豊富で、公式ブログではいろいろな使い方が紹介されています。しかし多忙なSEには、ここで紹介したシンプルな使い方をお勧めします。使いやすさは Toodledo と比較しても遜色はないと思います。

  • 「今週」リストからタスクを追加、期限を設定して「今週」リストに戻るまで4タップ
  • 「今週」リストからタスクの消し込みは、長押し+1タップ

■Wunderlist

6 Wunderkinder
Wunderlist(無料) 
販売元:6 Wunderkinder(21MB)

Wunderlist も無料アプリとしては人気があるほうだと思います。Wunderlistは様々なリスト(フォルダ)を用意しておき、そこにタスクを追加するのですが、ここでは凝った使い方はしないので、すべてデフォルトの「受信箱」に追加することにします。

右端のパネルを左にスワイプすると、タスク一覧が出てきます。



「アイテムを追加...」をタップして、新規タスクを入力します。



「終了」ボタンをタップでタスクが追加されます。


期限日を設定するには、さらにタスクをタップして編集画面を開きます。


「期限日を設定」をタップして、カレンダーを開き、期限日を設定します。


前述しましたが、ここで曜日が表示されないのは残念です。このようにして追加したタスクリストは以下のようになります。


ここで残念なのは、タスクが期限日でソートされないことです。一往編集により、自分で順序を入れ替えることは可能なのですが。。。期限日でソートをして見たい場合は、右にスワイプして「週」メニューを開く必要があります。




もうひとつ残念なこととして、この「週」からはタスクを追加することができないのです。タスクを追加するには、もう一度「受信箱」に戻る必要があります。
タスクを完了させるには、チェックボックスをタップします。



いくつか不満はありましたが、無料アプリとしては使いやすいと思います。スッキリしたデザインも良いと思います。

  • 「受信箱」からタスクを追加、期限日を設定して「受信箱」に戻るまで6タップ
  • 「週」リストからタスクの消し込みは、1タップ
  • 「受信箱」と「週」リストの切り替えは少し面倒


他にも評価したのはいくつかあるのですが、少しだけ紹介しておきます。

■domo Todo +

実は本当はこのアプリが一番使いやすいと感じています。なによりアプリの設計思想が「できるだけ少ない操作でタスク管理」なので。なんとタスク追加は最小で3タップで完了です!ただ動作が少し不安定、という評価があったため、今回はお勧めからは外しました。しかし、今後の改善次第では一番のお勧めになりそうなアプリではあります。いずれ機会があったらまた評価してみたいと思います。

■Any.Do

シンプルかつ使いやすいインタフェースが特徴です。ただ、iPhone を横向きしてランドスケープモードにしないとカレンダーでの期日設定ができない(私は普段画面向きをロックしている)のと、「近日中」リストに入れたタスクをうっかりタップしてしまうと、勝手に期日が1週間後に更新されてしまう仕様が致命的でした。


次回は、プロジェクト管理ができるTODO管理アプリを紹介したいと思います。




2013年4月13日土曜日

Apache の KeepAliveTimeout の設定

Apache を利用している Web システムが、突然レスポンスが返ってこなくなる、あるいは異常に遅い、こんなトラブルに遭遇した経験はないでしょうか?

・サーバが死んでいるわけではない。
・サーバ負荷が高いわけでもない。
・ブラウザでアクセスしたときは、レスポンスがなかなか返ってこない。

このようなレスポンス遅延の症状が出たとき、考えられる原因はいくつかあるのですが、可能性が高い原因として Apache の KeepAliveTimeout の設定があります。

KeepAliveTimeout は、1つのクライアント(ブラウザ)が、コネクションを占有できる時間を設定します。通常ブラウザが Web ページにアクセスしたときは、HTML ページをロードした後、そこから参照される CSS や画像等のファイルを次々と読み込むことになります。このとき、最初の HTML ページをロードする際に使用していたコネクションを解放せず、その後のファイルの読み込みにもそのコネクションを利用したほうがはるかに効率的です(なぜならネットワーク処理において、コネクションの確立が一番コストのかかる処理だからです)。このため、KeepAliveTimeout の設定は、1ページの読み込みにかかる時間+α程度の時間を設定するのが定石です。

ところが、Apache の KeepAliveTimeout のデフォルトの設定は、バージョン2.0で15秒、バージョン2.2で5秒です。特に古い Apache の設定を引き継いで、15秒に設定しているようなサイトは要注意です。

KeepAliveTimeout が長いということは、ブラウザがページ読み込み後も、無駄にコネクションを占有しているということです。そして当然コネクション数は無限ではありません。コネクション数はMaxClients で設定されますが、デフォルトでは 256 程度です。

もし KeepAliveTimeout が 15 秒で、MaxClients が 256 、その環境に同時に 300 人が接続してきたとします。この場合、まず 256人だけが最初に処理されます。しかし残りの 44 人は、サーバのリソースに余裕があったとしても、15秒間待たされることになります。

レスポンス遅延の原因は他にもいろいろあるのですが、それままたの機会に。。。


2013年3月1日金曜日

Struts1.3+EJB3.0 の lookup 方式およびパフォーマンス

レガシーStruts(Struts1.3)の Action クラスで EJB3.0 を呼び出す場合、インジェクションができないので lookup で呼び出す必要があります。WebLogic11g の場合ですが、以下のように記述できます。


■インタフェース
@Local
@Remote
public interface MyBean

■EJB
@Stateless(mappedName = "MyBean")
public class MyBeanEJB implements MyBean {

■lookup
Context ctx = new InitialContext();
MyBean ejb = (MyBean)ctx.lookup("MyBean#" + MyBean.class.getName());

この記述で、一見正常に動作しているように見えましたが、1つ問題が発生しました。負荷テストを実施した際、この EJB の利用部分でものすごくパフォーマンスが悪いことが判明したのです。EJB 自体はStruts と同じサーバ(ローカル)で稼働しているため、普通にローカル呼び出しになっているのですが、その前段階の lookup が異常に遅いことが判明しました(負荷がかかった状態で、10sec くらいかかってしまう)。

おそらく、lookup だけはリモートから探しに行っているようで、そこが遅いのではないかと考えました。そこで以下のようにコードを修正してみました。

■リモート
@Local
public interface MyBean

■EJB
@Stateless
public class MyBeanEJB implements MyBean {

■lookup
Context ctx = new InitialContext();
MyBean ejb = ctx.lookup ("java:comp/env/ejb/MyBean"/MyBean");


■web.xml
 <ejb-local-ref>
    <ejb-ref-name>ejb/MyBean</ejb-ref-name>
    <ejb-ref-type>Session</ejb-ref-type>
    <local>mypackage.ejb.MyBean</local>
 </ejb-local-ref>

実験してみたところ、同じ lookup の速度が数msとなり、大きく改善しました。こんなところに罠がしかけられていたんですね。。。





2013年2月27日水曜日

jQueryMobile の popup で iframe を表示した際に自動的に閉じる方法

かなり特殊な構成になりますが、jQueryMobile の popup パネルを表示し、そこに iframe で別ページを表示するサイトを作成したときに、少し苦労したところがあるので、記録しておきます。

その popup をあるイベントをトリガーに自動的に閉じる動作をさせたかったのですが、jQueryMobile のマニュアルを参照しながら、以下のコーディングをしました。


  $("#popupPanel").popup("close");

しかしこれでは、うまく動作する端末としない端末がありました(このときは Android では動作したのですが、iPhone では動作しませんでした)。この popup("close") ですが、ある条件のときには、どうやら history.back() の動作をしているようなのです。もちろん通常の popup であれば、history.back() はパネルを閉じるのと同じ効果が出ます。しかし今回作成したのは、内部に iframe で別ページを表示した popup パネルであり、その内部でいくつか遷移が行われている状態です。このため、いくら popup("close") を呼び出しても、iframe 内の履歴が戻るだけで、いっこうにパネルが閉じないのです。

いろいろ調べた結果、以下のおまじないを記述することで、閉じる動作をすることができました。



  $("#popupPanel").popup({history: false});
  $("#popupPanel").popup("close");


jQueryMobile を使いこなすのも、なかなか難しいですね。。。

2013年2月25日月曜日

スマホサイトのiframeの高さ問題

先日OperaがレンダリングエンジンとしてWebkitを採用するというニュースが話題になりました。競争による技術革新が阻害される可能性も指摘されているようですが。。。SEの立場から正直な感想を言わせてもらうと、複数ブラウザの検証とギャップを埋めるコーディングほど無駄な作業はないなーと思うので、レンダリングエンジンが統一されて開発者の負担が軽減されるのであれば、大歓迎です。とは言え、ブラウザによる微妙な違いは、まだ当分悩むことになりそうですが。。。

先日、スマホサイトのiframeの高さがうまく調整できないという問題に直面しました。

<iframe width="100%" ・・・>

として、高さを指定せずに iframe を表示した場合、うまく内部のコンテンツの高さにあわせて高さを調整してくれる端末と、そうでない端末がありました。調べてみると、

iPhone ・・・ OK
Android 2.* ・・・ OK
Android 4.0 ・・・ NG

という結果でした。Android の iframe にはバグも多いという記事も見たことがありますが、こんなものなのでしょうか。iframe 内のコンテンツの高さを計算して高さをそろえる手法もあるようでしたが、内部が別ドメインであるなど、ややこしい問題もあったので、このときはそれらしい高さを iframe に指定することでしのぎました。

だれか、もっと良い解決方法があれば、教えて欲しいです。