Flex

C# で作る AIR Native Extension


Adobe AIR3 から ANE という ネイティブ拡張機能が実装されたのでWindowsで試してみました。

全部で4ページにまとめます。

今回で終わりです。


開発環境
 Windows XP SP3
 Visual Studio 2010 Express Edition( C# / VC++ )
 FlashBuilder4.6( FlexSDK4.6 )


ane-lab(AIR Native Extension is Lab codes) - Google Project Hosting にC#で作ったサンプルをコミットさせて頂きましたので、参考にして下さい。
間違ってるかもしれないので、決して鵜呑みにしないように。


4.FlashBuilderでAIRアプリを作るよ!最後だよ!


FlashBuilder4.6を使用してFlexデスクトップアプリケーション(AIR)を仕上げましょう。
先回作成したANEファイルから、ネイティブな処理を呼び出せるようになります。


Flexプロジェクトにライブラリプロジェクトを参照させ、ANEファイルをlibsディレクトリにコピーします。


air-swc-append.png


air-ane-append.png


ANEファイルをコピーしたら、Flexプロジェクトのビルドパスにある「ネイティブエクステンション」にANEファイルを追加します。



air-ane-append-project.png




ANEファイルを追加すると、ApplicationList-app.xmlファイルに

<extensions>
        <extensionID>com.chocbanana.win.ApplicationListExtension</extensionID>
    </extensions>
</application>

という記述が追加されます。

ここまで終ったらActionScriptのコーディングです。
以下参照。


[ ApplicationList.mxml ]
-------------------------------
<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
                       xmlns:s="library://ns.adobe.com/flex/spark"
                       xmlns:mx="library://ns.adobe.com/flex/mx">
   
    <fx:Script>
        <![CDATA[
            import com.chocbanana.win.ApplicationListExtension;
           
            import mx.collections.ArrayCollection;
           
           
            private var ane:ApplicationListExtension = new ApplicationListExtension();
           
            [Bindable]
            private var ar:ArrayCollection = new ArrayCollection();
           
            protected function btn_applicationList_clickHandler(event:MouseEvent):void
            {
                // TODO Auto-generated method stub
                var _ar:Array = ane.getApplicationList();
                ar.removeAll();
               
                for(var i:int=0; i<_ar.length; i++){
                    var appName:String = _ar[i] as String;
                    ar.addItem(appName);
                }
            }
        ]]>
    </fx:Script>
   
    <fx:Declarations>
        <!-- 非ビジュアルエレメント (サービス、値オブジェクトなど) をここに配置 -->
    </fx:Declarations>
    <s:Panel x="0" y="0" width="100%" height="100%" fontWeight="bold"
             title="Show Installed Applications">
        <s:VGroup x="0" y="0" width="100%" height="100%" horizontalAlign="center"
                  verticalAlign="middle">
            <s:List id="lst_ApplicationList" width="80%" height="80%" dataProvider="{ar}"
                    fontWeight="normal"/>
            <s:Button id="btn_applicationList" label="アプリケーション一覧の取得"
                      click="btn_applicationList_clickHandler(event)" fontWeight="bold"/>
        </s:VGroup>
    </s:Panel>
</s:WindowedApplication>
[ EOF ]
-------------------------------

Flexプロジェクトをビルドして下さい。
証明書は最初に作成したと思うので、もう既にありますね?


ではリリースビルドです。
リリースビルドのエクスポートをクリックして、[ 署名済みネイティブインストーラー ] を選択して下さい。
証明書のパスワードを入力して、[ 終了 ] をクリックすると、ネイティブなインストーラー(EXEファイル)が生成されます。



air-releasebuild.png


air-releasebuild02.png


インストーラーが作成されたら、それをダブルクリックして、インストールして下さい。

1番最初 に作成したC#のDLLはこのインストーラーの中や、ANEファイルの中には含まれていませんの で、アプリケーションを実行すると、異常終了します。

インストールされたアプリケーションは、C:\ProgramFiles\ApplicationListにありますので、そのディレクトリを開いて、 C#のDLLのみを手動でコピーします。


air-dll-copy.png


再度、AIRアプリケーションを実行してみましょう。
無事に実行が終ると、インストールされているアプリケーションの一覧が表示されると思います。


air-native-application.png




お疲れ様でした。


Appendix - インストーラーについて -

ANEを利用したAIRアプリケーションでは、今現在FlashBuilderにてC#のDLLを含めてインストーラー(配布形式)にまとめることが出来 ません。
で、これをどうしたら良いか…という点なのですが、AIRアプリケーションにはサイレントインストールさせるためのインストールオ プションがあると思います。

で、このインストールオプションと、Windowsのもう1つのインストール形式である、MSIパッケージを組み合わせれば、全てを同梱した形で配布出来 るようになると思います。

これを行うには、SharpDevelopとWixを利用すると、msiパッケージを作成出来るので、そちらで(XMLを書いて)msiパッケージとして 1まとめにし、配布する…というのが妥当だと思います。

あ、高価なインストーラー作成アプリを使うなら、この項目は必要ないです。
参考程度に留めておいて下さい。

C# で作る AIR Native Extension


Adobe AIR3 から ANE という ネイティブ拡張機能が実装されたのでWindowsで試してみました。

全部で4ページにまとめます。

今回はその3ページ目、あと一息です。


開発環境
 Windows XP SP3
 Visual Studio 2010 Express Edition( C# / VC++ )
 FlashBuilder4.6( FlexSDK4.6 )


ane-lab(AIR Native Extension is Lab codes) - Google Project Hosting にC#で作ったサンプルをコミットさせて頂きましたので、参考にして下さい。
間違ってるかもしれないので、決して鵜呑みにしないように。



3.FlashBuilder でSWCとAIRアプリを作るよ!


FlashBuilder4.6使用して、Flexライブラリ(SWC)とFlexデスクトップアプリケーション(AIR)を作成していきます。
2つ同時に作業した方が分かりやすいかもしれません。

これで、SWCからadtコマンドでANEファイルを生成し、ネイティブな処理を呼び出せるようになります。


ではまずAIRアプリのレイアウト及び、証明書の作成から。
先に証明書を作成しておかないと、SWCを作るときにメンドイのです。

画面レイアウトと、証明書が出来たらSWC(Flexライブラリ)を作ります。


air-app-layout.png




air-app-syomeisyo.png




Flexライブラリプロジェクトを作りましょう。

プロジェクトのプロパティ設定で、[ Flexライブラリコンパイラー ] -> [ Aodbe AIRライブラリを含める ] のチェックがONになっていることを確認して下さい。

binディレクトリに VC++で作成したDLLと、先程作ったFlexアプリの証明書を入れておきます。

次に、srcディレクトリに ASのパッケージと、assetsディレクトリ。assetsディレクトリ内にdescriptor.xmlを作成します。

内容は以下の通りで。

[ assets/descriptor.xml ]
------------------------
<extension xmlns="http://ns.adobe.com/air/extension/3.1">
    <id>com.chocbanana.win.ApplicationListExtension</id>
    <versionNumber>1.0</versionNumber>
    <platforms>
        <platform name="Windows-x86">
            <applicationDeployment>
                <nativeLibrary>libApplicationList.dll</nativeLibrary>
                <initializer>initializer</initializer>
                <finalizer>finalizer</finalizer>
            </applicationDeployment>
        </platform>
    </platforms>
</extension>
[ EOF ]
------------------------

このXMLファイルが書けたら、binディレクトリにコピーしておいて下さい。



[ com.chocbanana.win.ApplicationListExtension.as ]
------------------------
package com.chocbanana.win{
   
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.IEventDispatcher;
    import flash.events.StatusEvent;
    import flash.external.ExtensionContext;
   
    public class ApplicationListExtension extends EventDispatcher {

        private var _ExtensionContext:ExtensionContext;
       
        public function ApplicationListExtension(target:IEventDispatcher=null) {
            //TODO: implement function
            super(target);
            _ExtensionContext = ExtensionContext.createExtensionContext("com.chocbanana.win.ApplicationListExtension", null);
        }

        public function dispose():void{
            _ExtensionContext.dispose();
        }
        public function getApplicationList():Array {
            return _ExtensionContext.call("GetApplicationList") as Array;
        }
    }
}
[ EOF ]
------------------------

ASファイル内で、FREのContextを作成し、callメソッドで、DLLの関数を呼びます。
戻り値はFREObectで指定した戻り値の型ですね。

で、これのビルドを行うとSWCファイルが出来ると思います。

SWCファイルが出来たら、それをそのまま選択して、右クリック、コピーして貼り付けして下さい。
名前の拡張子をzipにします。

そのzipファイルをダブルクリックして、解凍すると、中にlibrary.swfファイルがあるので、それもbinディレクトリに含めます。
解凍したzipファイルと、フォルダは要らないので、削除しておいて下さい。



air-swc-package.png




ここから、ADTコマンドを使って、ANEファイルを作ります。
FlashBuilderの [ 実行 ] -> [ 外部ツール ] -> [ 外部ツールの構成 ] を選択して、以下の様に。


air-adt-command.png





[ ADTコマンドの引数(改行は挟まず、スペースで) ]
------------------------
-package -storetype pkcs12 -keystore test.p12 -target ane libApplicationList_Extension.ane descriptor.xml -swc  libApplicationList_Extension.swc -platform Windows-x86 library.swf  libApplicationList.dll
------------------------

実行すると証明書のパスワードを聞いてくるので、入力してEnter。
libApplicationList_Extension.aneファイルがbinディレクトリに生成されます。

生成されたANEファイルをコピーして、AIRアプリのlibsディレクトリにコピーして下さい。

ひとまずはココまで。
次回はAIRアプリの仕上げです。

C# で作る AIR Native Extension


Adobe AIR3 から ANE という ネイティブ拡張機能が実装されたのでWindowsで試してみました。

全部で4ページにまとめます。

今回はその2ページ目です。


開発環境
 Windows XP SP3
 Visual Studio 2010 Express Edition( C# / VC++ )
 FlashBuilder4.6( FlexSDK4.6 )


ane-lab(AIR Native Extension is Lab codes) - Google Project Hosting にC#で作ったサンプルをコミットさせて頂きましたので、参考にして下さい。
間違ってるかもしれないので、決して鵜呑みにしないように。



2.VC ++でDLL作るよ!


Visual Studio 2010 Express Edition VC++ を使用して、AIRライブラリ(SWC)からコールするためのDLLを作成していきます。
VC++でCLRライブラリプロジェクトを選択してください。



vc-project.png




先回作成したC#のDLLとtlbファイル、及びAIR SDKに同梱されているヘッダーファイルとlibファイルをプロジェクトに追加します。



cs-dll.png




air-lib.png




project-files.png




で、ヘッダーファイル2つとメインのCPPファイルを書きます。


[ Stdafx.h ]
------------------------------
// stdafx.h : 標準のシステム インクルード ファイルのインクルード ファイル、または
// 参照回数が多く、かつあまり変更されない、プロジェクト専用のインクルード ファイル
// を記述します。

#pragma once
#include "FlashRuntimeExtensions.h";

[ EOF ]
------------------------------


[ libApplicationList.h ]
------------------------------
// libApplicationList.h
#pragma once
#include <stdlib.h>
#include <wchar.h>
#include <windows.h>

#using <mscorlib.dll>
#using "ApplicationList_CS.dll"

using namespace System;
using namespace System::Runtime::InteropServices;
using namespace System::Collections;
using namespace ApplicationList_CS;

namespace libApplicationList {

    public ref class Class1
    {
        // TODO: このクラスの、ユーザーのメソッドをここに追加してください。
    };
}
[ EOF ]
------------------------------

[ libApplicationList.cpp ]
------------------------------
// これは メイン DLL ファイルです。

#include "stdafx.h"
#include "libApplicationList.h"


extern "C" __declspec(dllexport) void initializer(void** extData, FREContextInitializer* ctxInitializer, FREContextFinalizer* ctxFinalizer);
extern "C" __declspec(dllexport) void finalizer(void** extData);

void ConvertToUni(String^ _str, char* szBuf, const int bufLen)
{
    IntPtr ptr = Marshal::StringToHGlobalUni(_str);
    const char* str = static_cast<const char*>(ptr.ToPointer());
    WideCharToMultiByte(CP_UTF8, 0, (wchar_t*)str, -1, szBuf, bufLen, NULL, NULL);
    Marshal::FreeHGlobal(ptr);
}

FREObject GetApplicationList(FREContext ctx, void* funcData, uint32_t argc, FREObject argv[]){
    FREObject resultObj;
    FREObject elementObj;

    ApplicationList_CS::AppList_CS^ cs = gcnew ApplicationList_CS::AppList_CS();
    ArrayList^ list = gcnew ArrayList();
    list = cs->getApplicationNames();

    // Application Name buffer
    const int bufLen = 1024;
    char szBuf[bufLen];

    const int count = list->Count;

    FRENewObject((const uint8_t*)"Array", 0, NULL, &resultObj, NULL);
    FRESetArrayLength(resultObj, count);

    for(int i=0; i<count; i++){
        String^ str = (String^)list[i];
        ConvertToUni(str, szBuf, bufLen);
        FRENewObjectFromUTF8(strlen(szBuf)+1, (const uint8_t *)szBuf, &elementObj);
        FRESetArrayElementAt(resultObj, i, elementObj);
    }

    return resultObj;
}
void contextFinalizer(FREContext ctx)
{
    return;
}

void contextInitializer(void* extData, const uint8_t* ctxType, FREContext ctx, uint32_t* numFunctions, const FRENamedFunction** functions)
{
    *numFunctions = 1;
    FRENamedFunction*  func= (FRENamedFunction*)malloc(sizeof(FRENamedFunction) * (*numFunctions));

    func[0].name = (const uint8_t*) "GetApplicationList";
    func[0].functionData = NULL;
    func[0].function = &GetApplicationList;

    *functions = func;
}

void initializer(void** extData, FREContextInitializer* ctxInitializer, FREContextFinalizer* ctxFinalizer)
{
    *ctxInitializer = &contextInitializer;
    *ctxFinalizer = &contextFinalizer;
}

void finalizer(void** extData)
{
   
}
[ EOF ]
------------------------------

ポイントは

contextInitializerのFRENamedFunctionでActionScriptからCallされる名前を定義しているのと、ネイ ティブの処理として

Marshal::StringToHGlobalUni(_str);
WideCharToMultiByte(CP_UTF8, 0, (wchar_t*)str, -1, szBuf, bufLen, NULL, NULL);

を使用している点でしょう。

WindowsではOS内部の値はUnicodeで格納されているため、UnicodeからUTF8に変換しないと、AIRに持っていったときに文字化け して表示されます。
ですので、WMIやレジストリなどから”マルチバイト文字”を引っ張ってくるときは要注意です。

あとは、ArrayList^ listに格納された文字列を1つ1つ分解して、FREObjectの構造体に突っ込む作業です。
全部突っ込んだらreturnで戻して終わりです。

AIR側に渡すために各パラメータを細かく分解してから、それぞれ格納し直す作業がメンドイですね…。


では、次回はFlashBuilderでFlexライブラリプロジェクト(SWC)とFlexプロジェクト(デスクトップ AIRアプリ)を作ります。

C# で作る AIR Native Extension



ApplicationList.png


Adobe AIR3 から ANE という ネイティブ拡張機能が実装されたのでWindowsで試してみました。

全部で4ページにまとめます。

今回はその1ページ目です。


開発環境
 Windows XP SP3
 Visual Studio 2010 Express Edition( C# / VC++ )
 FlashBuilder4.6( FlexSDK4.6 )


まぁ、アレです、各プラットフォーム固有の機能が使えたりするというヤツです。
Windowsならね、わざわざAIRでやらなくてもね、C#で全部出来るからね…、あんまり意味無いかもしれませんけどね。 orz
フロントエンドをAIRでまとめて、各プラットフォームごとにANEで固有機能を実装して…という具合にアプリを作るなら、ANE はアリです。

が、結構かなりシビアにキツイです。キツイっス…。orz

レジストリにアクセスしたり、WMI(Windows Management Instrumentation)を使ってハードウェア・ソフト ウェアの情報を取得したり、C#のAPIに丸投げしたり出来ま す。はい。

ane-lab(AIR Native Extension is Lab codes) - Google Project Hosting にC#で作ったサンプルをコミットさせて頂きましたので、参考にして下さい。
間違ってるかもしれないので、決して鵜呑みにしないように。


で、作り方。


作るのは4つ。

C#クラスライブラリ(C#のDLL)
VC++のCLRのクラスライブラリ(VC++ CLR/DLL)
Flexライブラリプロジェクト(SWC)
Flexプロジェクト(デスクトップ AIRアプリ)

です。



1.C#でDLL作るよ!

template.png

最初にキモを抑えて起きましょう。
C#で作ったDLLをVC++から参照して、VC++で作るDLLの中でFlashRuntimeExtensionsを組み込むことにより、AIRアプ リに通知することが出来るようになります。

えー???ですよね。C#からVC++のDLLをImportする…のではなく、その逆です。VC++からC#のDLLを参照しま す。
イヤーな予感がします。はい。


キモはコレ!
VC++から参照出来るように、「アセンブリをCOM参照可能にする」のチェックと、「COM相互運用機能の登録」のチェックをONにしておきます。
これでVC++からCOM経由でC#のクラスやそのメンバを参照出来るようになります。

CS_Setting01.png
CS_Setting02.png

ここで1つ注意。

COM 参照可能にする、とかCOM相互運用機能の登録とありますが、これはWindowsのCOMという機能を利用/経由(.NET/C#がCOMにラップされ る)して、VC++から.NET Objectを操作出来るようにする(マネージドからアンマネージにアクセスする)…というものです。

詳しくは「COM ・ RCW ・ 参照カウント 」などのキーワードで調べてください。

C#(.NET)にはGC(ガベージコレクション)という機能があります。が、COMはMSのドキュメントによると、「COM 自身は、オブジェクトが不要になったと判断しても、メモリから自動的にオブジェクトを削除するわけではありません。不要なオブジェクトの削除は、オブジェ クトのプログラマが行います。プログラマは、参照カウントに基づいて、オブジェクトを削除できるかどうかを判断します。」とあります。(http://msdn.microsoft.com/ja-jp/library/4947zb56%28v=vs.100%29.aspx)


完全にGCと相反していますね…。C#でどうやんのよ、それ…。

えー、通常C#で開発をする場合、変数にスコープがあり、このスコープから抜けるとGCの対象になる(実際にGCが何時行われるかはプログラマからは分か りませんが)…のですが、VC++から参照させるために、C#のライブラリをCOM参照させる必要があるため、C#側のインスタン スを明示的に解放してやる必要があります。

MSのドキュメントにも「すべてのア ウトポ インタパ ラメータを明 示的にNULLに設定する必要があります。」 と記載されています。(http://msdn.microsoft.com/ja-jp/library/ms686638.aspx)& rdquo;すばらしいGoogle翻訳”。

えー、ですので、C#側でも不必要な変数にはNULLを入れてやって下さい。
そうしないと、AIRアプリのプロセスが正常に終了しない場合があります。

あと、string[] とかは使わない方が良いです。使うなら、List<string>とかCollection.Genericを使いましょ う。



では、サンプルです。
OS内にインストールされているアプリケーション一覧を表示します。
これで、えろげとかもバッチリだね!(ぉ

プロジェクト名は ” ApplicationList_CS ” です。


[ App.cs ]
----------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Management;
using Microsoft.Win32;

namespace ApplicationList_CS {
    class App {
        //HotFix情報
        private const string root = "root\\CIMV2";
        private const string query = "SELECT * FROM Win32_QuickFixEngineering WHERE ServicePackInEffect <> '' AND InstalledBy <> ''";

        public ArrayList getApplicationNames() {
            List<string> hotfix = new List<string>();
            List<string> app = new List<string>();

            ManagementObjectSearcher searcher = new ManagementObjectSearcher(root, query);

            try {
                foreach (ManagementObject queryObj in searcher.Get()) {
                    hotfix.Add((string)queryObj["HotFixID"]);
                }
            } catch (Exception e) {
                Console.WriteLine(e.StackTrace);
            } finally {
                searcher = null;
            }
            RegistryKey regKey = null;
            RegistryKey _subRegKey = null;
            try {
                //アプリケーション情報
                string reg = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\";
                string regValue = "DisplayName";
                regKey = Registry.LocalMachine.OpenSubKey(reg);
                string[] subkey = regKey.GetSubKeyNames();

                foreach (string keyName in subkey) {

                    _subRegKey = Registry.LocalMachine.OpenSubKey(reg + keyName);
                    string value = (string)_subRegKey.GetValue(regValue);

                    if (value != null) {
                        for (int j = 0; j < hotfix.Count; j++) {
                            if (value.Contains(hotfix[j].ToString())) {
                                ;
                            } else if (value.Contains("KB")) {
                                ;
                            } else {
                                app.Add(value);
                                break;
                            }
                        }
                    }
                    //value = null;
                }
                app.TrimExcess();

                reg = null;
                regValue = null;
                subkey = null;
            } catch (NullReferenceException e) {
                Console.WriteLine(e.StackTrace);
            } finally {
                regKey.Close();
                _subRegKey.Close();
            }

            app.Sort();

            for (int i = 0; i < (app.Count - 1); i++) {
                try {
                    if (app[i].Equals(app[i + 1])) {
                        app.RemoveAt(i);
                    }
                } catch (Exception ex) {
                    Console.WriteLine(ex.Message);
                }
            }
            app.TrimExcess();
            hotfix = null;
            return new ArrayList(app);
        }
    }
}
[ EOF ]
----------------------------------------



[ AppList_CS.cs ]
----------------------------------------
using System;
using System.Collections;

namespace ApplicationList_CS {
    public class AppList_CS {

        private App app = new App();
       
        public ArrayList getApplicationNames() {
            return app.getApplicationNames();
        }
    }
}
[ EOF ]
----------------------------------------


やっちゃダメなダメ・コードも記載しておきます。絶対やっちゃダメだよw
配列多すぎ。orz


[ アプリケーション名・バージョン・HotFix情報を取得する変なC#コード ]
----------------------------------------
private void setApplicationReg() {
            string[] val1 = null;
            string[] val2 = null;
            string[] val3 = null;

            ArrayList application;
            ArrayList applicationVersion;
            ArrayList hotfix;

            application = new ArrayList(); // application
            applicationVersion = new ArrayList(); // applicationVersion
            hotfix = new ArrayList(); // HotFix

            try {
                //HotFix情報
                string root = "root\\CIMV2";
                string query = "SELECT * FROM Win32_QuickFixEngineering WHERE ServicePackInEffect <> '' AND InstalledBy <> ''";
                ManagementObjectSearcher searcher = new ManagementObjectSearcher(root, query);
                int cnt = 0;
                foreach (ManagementObject queryObj in searcher.Get()) { cnt++; }
                val1 = new string[cnt];
                int i=0;
                foreach (ManagementObject queryObj in searcher.Get()) {
                    val1[i] = (string)queryObj["HotFixID"];
                    i++;
                }
            }catch(Exception e){
                string error = e.StackTrace;
                //Console.WriteLine(e.StackTrace);
            }
            try {
                //アプリケーション情報
                string reg = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\";
                string regValue = "DisplayName";
                RegistryKey regKey = Registry.LocalMachine.OpenSubKey(reg);
                string[] subkey = regKey.GetSubKeyNames();
                int max = subkey.Length;
                regKey.Close();

                //アプリケーションの総数
                val2 = new string[max];
                val3 = new string[max];
                RegistryKey _subRegKey = null;
                int i = 0;
                foreach (string keyName in subkey) {
                    _subRegKey = Registry.LocalMachine.OpenSubKey(reg + keyName);
                    if ((string)_subRegKey.GetValue(regValue) != null) {
                        val2[i] = (string)_subRegKey.GetValue("DisplayName");
                        val3[i] = (string)_subRegKey.GetValue("DisplayVersion");
                        i++;
                    }
                    _subRegKey.Close();
                }
            } catch (NullReferenceException e) {
                string error = e.StackTrace;
                //Console.WriteLine(e.StackTrace);
            }
            try {
                for (int i = 0; i < val2.Length; i++) {
                    if(val2[i] != null){
                        for(int j = 0; j < val1.Length; j++){
                            if (val2[i].Contains(val1[j])) {
                                hotfix.Add(val2[i]);
                                val2[i] = null;
                                val3[i] = null;
                                break;
                            }else if(val2[i].Contains("KB")){
                                hotfix.Add(val2[i]);
                                val2[i] = null;
                                val3[i] = null;
                                break;
                            }
                        }
                    }
                }
                foreach(string _val in val2){
                    if( _val == null){
                        ;
                    } else {
                        application.Add(_val);
                    }
                }
                foreach (string _val in val3) {
                    if (_val == null) {
                        ;
                    } else {
                        applicationVersion.Add(_val);
                    }
                }
                application.TrimToSize();
                applicationVersion.TrimToSize();
                hotfix.TrimToSize();

                application.Sort();
                for (int i = 0; i < application.Count; i++ ) {
                    try {
                        if (application[i].Equals(application[i + 1])) {
                            application.RemoveAt(i);
                        }
                    }catch(Exception){
                   
                    }
                }
                application.TrimToSize();

                this.application = new string[application.Count];
                this.applicationVersion = new string[applicationVersion.Count];
                this.hotfix = new string[hotfix.Count];

                for (int i = 0; i < application.Count; i++) { this.application[i] = (string)application[i]; }
                for (int i = 0; i < applicationVersion.Count; i++) { this.applicationVersion[i] = (string)applicationVersion[i]; }
                for (int i = 0; i < hotfix.Count; i++) { this.hotfix[i] = (string)hotfix[i]; }
            } catch (NullReferenceException ne) {
                Console.WriteLine(ne.StackTrace);
            }
        }
----------------------------------------



もうちょっとだけ続くんじゃ。


ダメじゃない方のコードをコンパイルすると、DLLとtlbファイルが出来ますので、その2つをVC++のプロジェクトで使用します。

で、結局このDLLってどうなるのっ?ってことなのですが、「Visual Studioコマンドプロンプト」というのがあります。
このDOS窓で、oleviewと入力すると、COMを使用しているアプリケーションやコンポーネントが見れたりします。

oleview.png


えー、2ページ目に続きますが、今度はVC++を使って、AIRアプリとを繋ぐDLLを作成していきます。

【 BlazeDS + SpringFramework + MyBatis + HSQLDBの連携 】



Spring Framework3.0.5 - DI(Dependency Injection)コンテナ
MyBatis3.0.6 - O/R Mapper
HSQLDB2.2.5 - Java製RDB



Spring2.0.x + iBatis2.x + HSQLDB1.8.xを Spring3.0.5 + MyBatis3.0.6 + HSQLDB2.2.5で置き換えてみました。
こちらはAnnotationベースでの実装となります。

DBのScirpt, FlexのMXML・ASは変わりませんので、XMLベースで実装したドキュメントを参考にして下さい。
MyBatisを使用して、Annotationベースの実装を2種類(Annotation指定のものと、 SelectBuilder/SqlBuilderを使用したもの)試してみました。
XML書かなくていいので、こちらの方が便利ではあるけれど、ソース追いかけるのがヤヤコシイかな?



[ web.xml ]
---
<?xml version="1.0"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <display-name>Spring BlazeDS</display-name>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>


    <!-- Filter config with GZIP -->
    <filter>
        <filter-name>CompressingFilter</filter-name>
        <filter-class>
            com.planetj.servlet.filter.compression.CompressingFilter
        </filter-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>false</param-value>
        </init-param>
        <init-param>
            <param-name>statsEnabled</param-name>
            <param-value>false</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>CompressingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
   
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
   
    <servlet>
        <servlet-name>MessageBrokerServlet</servlet-name>
        <servlet-class>flex.messaging.MessageBrokerServlet</servlet-class>
        <init-param>
            <param-name>services.configuration.file</param-name>
            <param-value>/WEB-INF/flex/services-config.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet>
        <servlet-name>HttpContextUtilServlet</servlet-name>
        <servlet-class>com.chocbanana.http.HttpContextUtilServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>


    <servlet-mapping>
        <servlet-name>MessageBrokerServlet</servlet-name>
        <url-pattern>/messagebroker/*</url-pattern>
    </servlet-mapping>
</web-app>
[ EOF ]
---

[ HttpContextUtilServlet.java ]
---
package com.chocbanana.http;

import java.io.Serializable;

import javax.servlet.ServletException;

import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;


public class HttpContextUtilServlet extends javax.servlet.http.HttpServlet implements Serializable {

    private static WebApplicationContext ctx;
    /**
     *
     */
    private static final long serialVersionUID = 1L;

    @Override
    public void destroy() {
        // TODO Auto-generated method stub
        try{
            //dao.shutdown();
        }catch(Exception e){
            e.printStackTrace();
        }
        super.destroy();
    }

    @Override
    public synchronized void init() throws ServletException {
        // TODO Auto-generated method stub
        super.init();
        try{
            if(ctx == null) ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(this.getServletContext());
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    public synchronized static WebApplicationContext getContext(){
        return ctx;
    }
}
[ EOF ]
---



[ applicationContext.xml ]
---
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/tx
   http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
   http://www.springframework.org/schema/context
   http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <!-- jdbc.propertiesを認識させる propertyConfigurer -->
    <bean id="propertyConfigurer"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>
                    classpath:/com/chocbanana/dao/database.properties
                </value>
            </list>
        </property>
    </bean>

    <!-- jdbc.propertiesの設定値をdataSourceにインジェクション -->
    <bean id="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${db.driver}" />
        <property name="url" value="${db.url}" />
        <property name="username" value="${db.user}" />
        <property name="password" value="${db.pass}" />
    </bean>

    <!-- Dao Mapperのサービスクラス・パッケージをコンポーネントスキャンだぜ! -->
    <context:component-scan base-package="com.chocbanana.dao.service"/>
     
    <!-- enable autowire -->
    <context:annotation-config />
   
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
    </bean>
   
    <!-- トランザクション制御のインターセプターを構成 -->
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
   
    <!-- 実装クラスのTransactionalアノテーションでジョイントポイントの認識をさせる  -->
    <!-- @transaction-managerのデフォルトは"transactionManager"だが念のため指定  -->
    <!-- @proxy-target-classをtureにしておく。CGLIBでプロキシーを作る方が多少速いらしい -->
    <!-- -->
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />
   

    <!-- アノテーションベースのMapper -->
    <!--
     - コンポーネントスキャンでスキャンだぜ。Injectionされるだぜー。
     - MyBatisのDAO Mapperです。
    -->
     <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.chocbanana.dao.mapper" />
    </bean>
</beans>
[ EOF ]
---


[ database.properties ]
---
#Wed Apr 01 22:43:23 JST 2009
db.pass=
#db.url=jdbc\:hsqldb\:file:/Users/hoehoe/Documents/workspace/WEB-INF/resource/db
db.url=jdbc\:hsqldb\:file:C:/Documents and Settings/hoehoe/My Documents/EclipseProject/SpringBlazeDS/WEB-INF/resource/db
db.driver=org.hsqldb.jdbc.JDBCDriver
db.user=sa
[ EOF ]
---


[ DaoMapper.java ]
---
package com.chocbanana.dao.mapper;

import java.util.ArrayList;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.InsertProvider;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.SelectProvider;
import org.springframework.dao.DataAccessException;

import com.chocbanana.bean.PostMessage;

public interface DaoMapper {

    /* Annotation base */
    @Select("SELECT TOP 1000 * FROM MESSAGE ORDER BY ID DESC")
    ArrayList<PostMessage> getMessages() throws DataAccessException;

    @Insert("INSERT INTO MESSAGE (NAME, MAIL, MESSAGE, DAY) VALUES (#{name}, #{mail}, #{message}, #{day})")
    int putMessages(PostMessage Message) throws DataAccessException;
   
    /* SelectBuilder and SqlBuilder base  */
    @SelectProvider(type=com.chocbanana.dao.provider.SelectProvider.class, method="selectMessage")
    ArrayList<PostMessage> selectMessages() throws DataAccessException;
   
    @SelectProvider(type=com.chocbanana.dao.provider.SelectProvider.class, method="selectOne")
    PostMessage selectOne(PostMessage message) throws DataAccessException;

    @InsertProvider(type=com.chocbanana.dao.provider.SqlProvider.class, method="insertMessage")
    int insertMessages(PostMessage messge) throws DataAccessException;
   
}
[ EOF ]
---

[ SelectProvider.java ]
---
package com.chocbanana.dao.provider;

import static org.apache.ibatis.jdbc.SelectBuilder.BEGIN;
import static org.apache.ibatis.jdbc.SelectBuilder.FROM;
import static org.apache.ibatis.jdbc.SelectBuilder.SELECT;
import static org.apache.ibatis.jdbc.SelectBuilder.SQL;
import static org.apache.ibatis.jdbc.SelectBuilder.ORDER_BY;
import static org.apache.ibatis.jdbc.SelectBuilder.WHERE;

import com.chocbanana.bean.PostMessage;

public class SelectProvider {
     public static String selectMessage() {
         BEGIN();
         SELECT("TOP 1000 * ");
         FROM("MESSAGE");
         ORDER_BY("ID DESC");
         return SQL();
     }

     public static String selectOne(PostMessage message) {
         BEGIN();
         SELECT("*");
         FROM("MESSAGE");
         WHERE("ID like #{id}");
         return SQL();
     }
}
[ EOF ]
---


[ SqlProvider.java ]
---
package com.chocbanana.dao.provider;

import static org.apache.ibatis.jdbc.SelectBuilder.BEGIN;
import static org.apache.ibatis.jdbc.SqlBuilder.INSERT_INTO;
import static org.apache.ibatis.jdbc.SqlBuilder.VALUES;
import static org.apache.ibatis.jdbc.SqlBuilder.SQL;

import com.chocbanana.bean.PostMessage;

public class SqlProvider {
     public static String insertMessage(PostMessage message){
         BEGIN();
         INSERT_INTO("MESSAGE");
         VALUES("NAME, MAIL, MESSAGE, DAY", "#{name}, #{mail}, #{message}, #{day}");
         return SQL();
     }
}
[ EOF ]
---


[ FooService.java ]
---
package com.chocbanana.dao.service;

import java.util.ArrayList;
import org.springframework.dao.DataAccessException;
import org.springframework.transaction.annotation.Transactional;

import com.chocbanana.bean.PostMessage;

@Transactional
public interface FooService {
    public ArrayList<PostMessage> getMessages() throws DataAccessException;
    public int putMessages(PostMessage Message) throws DataAccessException;
    public ArrayList<PostMessage> selectMessages() throws DataAccessException;
    public PostMessage selectOne(PostMessage message) throws DataAccessException;
    public int insertMessages(PostMessage messge) throws DataAccessException;
}

[ EOF ]
---

[ FooServiceImpl.java ]
---
package com.chocbanana.dao.service;

import java.util.ArrayList;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Service;

import com.chocbanana.bean.PostMessage;
import com.chocbanana.dao.mapper.DaoMapper;

@Service("fooService")
public class FooServiceImpl implements FooService {
   
    @Autowired
    private DaoMapper daoMapper;
   
    public void setDaoMapper(DaoMapper daoMapper){
        this.daoMapper = daoMapper;
    }
   
    public ArrayList<PostMessage> getMessages() throws DataAccessException{
        return this.daoMapper.getMessages();
    }
    public int putMessages(PostMessage Message) throws DataAccessException{
        return this.daoMapper.putMessages(Message);       
    }
    public ArrayList<PostMessage> selectMessages() throws DataAccessException{
        return this.daoMapper.selectMessages();
    }
    public PostMessage selectOne(PostMessage message) throws DataAccessException{
        return this.daoMapper.selectOne(message);
    }
    public int insertMessages(PostMessage messge) throws DataAccessException{
        return this.daoMapper.insertMessages(messge);
    }
}
[ EOF ]
---



[ FlexService.java ]
---
package com.chocbanana.ws;

import java.text.SimpleDateFormat;
import java.util.ArrayList;

import javax.annotation.Resource;

import com.chocbanana.bean.*;
import com.chocbanana.dao.Db2DaoInterface;
import com.chocbanana.dao.service.FooService;
import com.chocbanana.http.HttpContextUtilServlet;

@SuppressWarnings("serial")
public class FlexService implements java.io.Serializable{

    @Resource
    private static FooService service;

    private static final String BEAN_TYPE="fooService";

    public FlexService(){
        service = (FooService) HttpContextUtilServlet.getContext().getBean(BEAN_TYPE);
    }
    public ArrayList<ResultMessage> getMessages(){
       
        ArrayList<ResultMessage> result = new ArrayList<ResultMessage>();
        try{
            ArrayList<PostMessage> list;
            list = service.selectMessages(); /* service.getMessages(); */

            java.util.Iterator<PostMessage> it = list.iterator();
            ResultMessage resultMsg = null;

            while(it.hasNext()){
                PostMessage _msg = it.next();
                resultMsg = new ResultMessage();
                resultMsg.setMessage(
                        _msg.getId() +  " " +
                        _msg.getName() + " " +
                        _msg.getMail() + " " +
                        _msg.getDay() + " " + "\n\n" +
                        _msg.getMessage());
                result.add(resultMsg);
            }
        }catch(Exception e){
            e.printStackTrace();
        }
        return result;
       
    }
    public int putMessages(PostMessage message) {
        int ret=0;
        String day = "";
        SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd hh:mm:ss");
        day = format.format(new java.util.Date());
        message.setDay(day);
       
        if("".equals(message.getMessage())){
            return 0;
        }
       
        try{
            ret = service.insertMessages(message); /* service.putMessages(message); */
        }catch(Exception e){
            e.printStackTrace();
        }
            return ret;
    }
}
[ EOF ]
---

【 BlazeDS + SpringFramework + MyBatis + HSQLDBの連携 】



Spring Framework3.0.5 - DI(Dependency Injection)コンテナ
MyBatis3.0.6 - O/R Mapper
HSQLDB2.2.5 - Java製RDB



Spring2.0.x + iBatis2.x + HSQLDB1.8.xを Spring3.0.5 + MyBatis3.0.6 + HSQLDB2.2.5で置き換えてみました。
こちらはXMLベースでの実装となります。

packageの階層に注意してください。



[ db.script -抜粋- ]
---
CREATE CACHED TABLE MESSAGE(
    ID INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 1) NOT NULL PRIMARY KEY,
    NAME VARCHAR(255) NOT NULL,
    MAIL VARCHAR(255) NOT NULL,
    MESSAGE VARCHAR(65535) NOT NULL,
    DAY VARCHAR(255) NOT NULL
    );
[ EOF ]
---



[ web.xml ]
---
<?xml version="1.0"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <display-name>Spring BlazeDS</display-name>

    <!-- Contextに渡すXML -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext-xml.xml</param-value>
    </context-param>


    <!-- Filter config with GZIP -->
    <filter>
        <filter-name>CompressingFilter</filter-name>
        <filter-class>
            com.planetj.servlet.filter.compression.CompressingFilter
        </filter-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>false</param-value>
        </init-param>
        <init-param>
            <param-name>statsEnabled</param-name>
            <param-value>false</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>CompressingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
   
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
   
    <servlet>
        <servlet-name>MessageBrokerServlet</servlet-name>
        <servlet-class>flex.messaging.MessageBrokerServlet</servlet-class>
        <init-param>
            <param-name>services.configuration.file</param-name>
            <param-value>/WEB-INF/flex/services-config.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet>
        <servlet-name>HttpContextUtilServlet</servlet-name>
        <servlet-class>com.chocbanana.http.HttpContextUtilServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>


    <servlet-mapping>
        <servlet-name>MessageBrokerServlet</servlet-name>
        <url-pattern>/messagebroker/*</url-pattern>
    </servlet-mapping>
</web-app>
[EOF]
---


[ HttpContextUtilServlet.java ]
---
package com.chocbanana.http;

import java.io.Serializable;

import javax.servlet.ServletException;

import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;


public class HttpContextUtilServlet extends javax.servlet.http.HttpServlet implements Serializable {

    private static WebApplicationContext ctx;
    /**
     *
     */
    private static final long serialVersionUID = 1L;

    @Override
    public void destroy() {
        // TODO Auto-generated method stub
        try{
            //dao.shutdown();
        }catch(Exception e){
            e.printStackTrace();
        }
        super.destroy();
    }

    @Override
    public synchronized void init() throws ServletException {
        // TODO Auto-generated method stub
        super.init();
        try{
            if(ctx == null) ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(this.getServletContext());
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    public synchronized static WebApplicationContext getContext(){
        return ctx;
    }
}
[EOF]
---


[ applicationContext.xml ]
---
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/tx
   http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

    <!-- jdbc.propertiesを認識させる propertyConfigurer -->
    <bean id="propertyConfigurer"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>
                    classpath:/com/chocbanana/dao/database.properties
                </value>
            </list>
        </property>
    </bean>

    <!-- jdbc.propertiesの設定値をdataSourceにインジェクション -->
    <bean id="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${db.driver}" />
        <property name="url" value="${db.url}" />
        <property name="username" value="${db.user}" />
        <property name="password" value="${db.pass}" />
    </bean>

    <!-- MyBatisのフロントになるsqmMapClientを構成。プロパティ値にMapper.xmlのパスを指定するだけ -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="mapperLocations" value="classpath:/com/chocbanana/dao/Mapper.xml" />
    </bean>
   
    <!-- トランザクション制御のインターセプターを構成 -->
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
   
    <!-- 実装クラスのTransactionalアノテーションでジョイントポイントの認識をさせる  -->
    <!-- @transaction-managerのデフォルトは"transactionManager"だが念のため指定  -->
    <!-- @proxy-target-classをtureにしておく。CGLIBでプロキシーを作る方が多少速いらしい -->
    <!-- -->
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />
   
    <!-- XMLベースのDAO -->
    <bean id="daoXml" class="com.chocbanana.dao.Db2Dao">
        <property name="sqlSessionFactory" ref="sqlSessionFactory" />
    </bean>
</beans>
[EOF]
---

[ database.properties ]
---
#Wed Apr 01 22:43:23 JST 2009
db.pass=
#db.url=jdbc\:hsqldb\:file:/Users/hoehoe/Documents/workspace/WEB-INF/resource/db
db.url=jdbc\:hsqldb\:file:C:/Documents and Settings/hoehoe/My Documents/EclipseProject/SpringBlazeDS/WEB-INF/resource/db
db.driver=org.hsqldb.jdbc.JDBCDriver
db.user=sa
[EOF]
---




[ Db2DaoInterface.java ]
---
/*
 * Created on 2005/06/19
 *
 * TODO To change the template for this generated file go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
package com.chocbanana.dao;

import java.util.ArrayList;

import org.springframework.dao.DataAccessException;
import com.chocbanana.bean.PostMessage;
/**
 * @author hoehoe
 *
 * TODO To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
public interface Db2DaoInterface {
   
    ArrayList<PostMessage> getMessages() throws DataAccessException;
    int putMessages(PostMessage Message) throws DataAccessException;
   
    void shutdown() throws DataAccessException;
   
}
[EOF]
---

[ Db2Dao.java ]
---
/*
 * Created on 2005/06/18
 *
 * TODO To change the template for this generated file go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
package com.chocbanana.dao;

import java.sql.SQLException;
import java.util.ArrayList;

import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.springframework.dao.DataAccessException;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.chocbanana.bean.PostMessage;

/**
 * @author hoehoe
 *
 * TODO To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
public class Db2Dao extends SqlSessionDaoSupport implements Db2DaoInterface{
    @SuppressWarnings("unchecked")
    @Transactional(readOnly=true, propagation=Propagation.REQUIRED)
    public ArrayList<PostMessage> getMessages() throws DataAccessException{
        return (ArrayList<PostMessage>) getSqlSession().selectList("com.chocbanana.dao.Mapper.getMessage");
    }
   
    @Transactional(readOnly=false, rollbackFor=SQLException.class)
    public int putMessages(PostMessage message) throws DataAccessException{
        return getSqlSession().insert("com.chocbanana.dao.Mapper.putMessage", message);
    }

    public void shutdown() throws DataAccessException{
        getSqlSession().selectOne("shutdown");
    }
}
[EOF]
---


[ /com/chocbanana/dao/Mapper.xml ]
---
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
    Copyright 2010-2011 The myBatis Team

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
-->

<!--
    version: $Id: UserMapper.xml 2444 2010-09-15 07:38:37Z simone.tripodi $
-->
<mapper namespace="com.chocbanana.dao.Mapper">
    <select id="getMessage"  resultType="com.chocbanana.bean.PostMessage">
        SELECT TOP 1000 * FROM MESSAGE ORDER BY ID DESC
    </select>

    <insert id="putMessage" parameterType="com.chocbanana.bean.PostMessage" >
        INSERT INTO MESSAGE (NAME, MAIL, MESSAGE, DAY) VALUES (#{name}, #{mail}, #{message}, #{day})
    </insert>

    <!-- database shutdown -->
    <sql id="shutdown">shutdown</sql>
</mapper>
[EOF]
---



[ FlexService.java ]
---
package com.chocbanana.ws;

import java.text.SimpleDateFormat;
import java.util.ArrayList;

import com.chocbanana.bean.*;
import com.chocbanana.dao.Db2DaoInterface;
import com.chocbanana.http.HttpContextUtilServlet;

@SuppressWarnings("serial")
public class FlexService implements java.io.Serializable{
   
    private static Db2DaoInterface dao;
    private static final String BEAN_TYPE = "daoXml";

    public FlexService(){
        dao = (Db2DaoInterface) HttpContextUtilServlet.getContext().getBean(BEAN_TYPE);
    }
    public ArrayList<ResultMessage> getMessages(){
       
        ArrayList<ResultMessage> result = new ArrayList<ResultMessage>();
        try{
            ArrayList<PostMessage> list = dao.getMessages();
            java.util.Iterator<PostMessage> it = list.iterator();
            ResultMessage resultMsg = null;

            while(it.hasNext()){
                PostMessage _msg = it.next();
                resultMsg = new ResultMessage();
                resultMsg.setMessage(
                        _msg.getId() +  " " +
                        _msg.getName() + " " +
                        _msg.getMail() + " " +
                        _msg.getDay() + " " + "\n\n" +
                        _msg.getMessage());
                result.add(resultMsg);
            }
        }catch(Exception e){
            e.printStackTrace();
        }
        return result;
       
    }
    public int putMessages(PostMessage message) {
        int ret=0;
        String day = "";
        SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd hh:mm:ss");
        day = format.format(new java.util.Date());
        message.setDay(day);
       
        if("".equals(message.getMessage())){
            return 0;
        }
       
        try{
            ret = dao.putMessages(message);
        }catch(Exception e){
            e.printStackTrace();
        }
            return ret;
    }
}
---


- Flexのソースコード一覧 -

[ SpringBlazeDS.mxml ]
---
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark"
               xmlns:mx="library://ns.adobe.com/flex/mx"
               minWidth="950" minHeight="500" viewSourceURL="srcview/index.html">

    <fx:Script source="SpringBlazeDS.as"/>
    <fx:Declarations>
        <!-- Place non-visual elements (e.g., services, value objects) here -->
        <s:RemoteObject id="srv" endpoint="{'http://{server.name}:{server.port}/SpringBlazeDS/messagebroker/amf'}" destination="flexService" showBusyCursor="true">
            <s:method name="getMessages" result="resultMessage(event)" fault="faultMessage(event)"/>
        </s:RemoteObject>
    </fx:Declarations>

    <s:Panel x="0" y="0" width="100%" height="100%" title="SpringBlazeDS">
        <s:HGroup x="10" y="10" width="100%" verticalAlign="middle">
            <s:Spacer width="20" height="10"/>
            <s:Button id="btn_post" label="Post" click="btn_post_clickHandler(event)"
                      fontWeight="bold"/>
            <s:Spacer width="15" height="10"/>
            <s:Button id="btn_reload" label="Reload" click="btn_reload_clickHandler(event)"
                      fontWeight="bold"/>
        </s:HGroup>
        <s:Spacer x="10" y="39" width="20" height="10"/>
        <s:HGroup x="10" y="56" width="100%" height="85%">
            <s:DataGrid width="100%" height="100%" dataProvider="{srv.getMessages.lastResult}" creationComplete="d1_creationCompleteHandler(event)">
                <s:columns>
                    <s:ArrayList>
                        <s:GridColumn dataField="message" headerText="" />
                    </s:ArrayList>
                </s:columns>
            </s:DataGrid>
            <s:Spacer width="10" height="10"/>
        </s:HGroup>
       
    </s:Panel>
</s:Application>
[ EOF ]
---


[ SpringBlazeDS.as ]
---

import flash.events.MouseEvent;

import mx.controls.Alert;
import mx.events.FlexEvent;
import mx.managers.PopUpManager;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;

protected function btn_post_clickHandler(event:MouseEvent):void
{
    // TODO Auto-generated method stub
    var post:Post = Post(PopUpManager.createPopUp(this, Post, true));
    post.addEventListener(FlexEvent.REMOVE, closePopUp);
    PopUpManager.centerPopUp(post);
}
private function closePopUp(e:FlexEvent):void{
    srv.getMessages();
}

public function resultUser(event:ResultEvent):void{

}
protected function btn_reload_clickHandler(event:MouseEvent):void
{
    // TODO Auto-generated method stub
    srv.getMessages();
}

protected function d1_creationCompleteHandler(event:FlexEvent):void
{
    // TODO Auto-generated method stub
    srv.getMessages();
}

public function resultMessage(event:ResultEvent):void{

}

public function faultMessage(event:FaultEvent):void{
    Alert.show(event.toString());
}
[ EOF ]
---


[ Post.mxml ]
---
<?xml version="1.0" encoding="utf-8"?>
<s:TitleWindow xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark"
               xmlns:mx="library://ns.adobe.com/flex/mx"
               width="800" height="400" creationComplete="t1_creationCompleteHandler(event)" close="t1_closeHandler(event)" title="PostMessage">
   
    <fx:Script>
        <![CDATA[
            import com.chocbanana.bean.PostMessge;
           
            import mx.controls.Alert;
            import mx.events.CloseEvent;
            import mx.events.FlexEvent;
            import mx.managers.PopUpManager;
            import mx.rpc.events.FaultEvent;
            import mx.rpc.events.ResultEvent;
           
            protected function btn_cancel_clickHandler(event:MouseEvent):void
            {
                // TODO Auto-generated method stub
                txt_message.text = "";
            }
           
            protected function btn_post_clickHandler(event:MouseEvent):void
            {
                // TODO Auto-generated method stub
                var post:PostMessge = new PostMessge();
                post.name = txt_name.text;
                if(post.name == ""){
                    post.name = "以下、名無しに変わりましてfxug-nagoyaがお送りします";
                }
                post.mail = "sage";
                post.message = txt_message.text;
                if(post.message == ""){
                    Alert.show("本文が空です。");
                    return;
                }
                srv.putMessages(post);
            }
           
            protected function t1_closeHandler(event:CloseEvent):void
            {
                // TODO Auto-generated method stub
                PopUpManager.removePopUp(this);
            }
           
            protected function t1_creationCompleteHandler(event:FlexEvent):void
            {
                // TODO Auto-generated method stub
                txt_message.setFocus();
            }
           
            protected function method1_resultHandler(event:ResultEvent):void
            {
                // TODO Auto-generated method stub
                PopUpManager.removePopUp(this);
               
            }
           
            protected function method1_faultHandler(event:FaultEvent):void
            {
                // TODO Auto-generated method stub
                Alert.show(event.message.toString());
            }
           
        ]]>
    </fx:Script>
   
    <fx:Declarations>
        <!-- Place non-visual elements (e.g., services, value objects) here -->
        <s:RemoteObject id="srv" endpoint="{'http://{server.name}:{server.port}/SpringBlazeDS/messagebroker/amf'}" destination="flexService" showBusyCursor="true">
            <s:method name="putMessages" result="method1_resultHandler(event)" fault="method1_faultHandler(event)" />
        </s:RemoteObject>
    </fx:Declarations>
    <s:VGroup x="10" y="10" width="780" height="346">
        <s:HGroup verticalAlign="middle">
            <s:Label text="Name: "/>
            <s:TextInput id="txt_name" width="320" text="以下、名無しに変わりましてfxug-nagoyaがお送りします"/>
        </s:HGroup>
        <s:VGroup width="770" verticalAlign="bottom">
            <s:Spacer height="5" width="5" />
            <s:Label text="Message: "/>
            <s:TextArea id="txt_message" width="100%" height="200"/>
        </s:VGroup>
        <s:Spacer width="20" height="10"/>
        <s:HGroup width="100%" horizontalAlign="right" verticalAlign="middle">
            <s:Button id="btn_cancel" label="Cancel" click="btn_cancel_clickHandler(event)"/>
            <s:Spacer width="20" height="10"/>
            <s:Button id="btn_post" label="Post" click="btn_post_clickHandler(event)"
                      fontWeight="bold"/>
            <s:Spacer width="20" height="10"/>
        </s:HGroup>
    </s:VGroup>

</s:TitleWindow>

[ EOF ]
---



社内から帳票作成とその情報を一括管理出来るように…という要望を頂いたので、AdobeAIRで実装してみました。


サンプルアプリのソースFxUG名古屋勉強会資料_20110709.pdf を置いておきます。


必要となるJavaのライブラリ一式。
----------------------
aopalliance-1.0.jar
asm-2.2.3.jar
backport-util-concurrent.jar
cglib-nodep-2.1_3.jar
commons-beanutils.jar
commons-collections-3.2.1.jar
commons-dbcp-1.4.jar
commons-digester.jar
commons-fileupload-1.2.1.jar
commons-io-1.3.2.jar
commons-logging-1.1.1.jar
commons-pool-1.5.4.jar
cxf-2.2.12.jar
FastInfoset-1.2.7.jar
flex-messaging-common.jar
flex-messaging-core.jar
flex-messaging-remoting.jar
freemarker-2.3.16.jar
geronimo-annotation_1.0_spec-1.1.1.jar
geronimo-servlet_2.5_spec-1.2.jar
geronimo-stax-api_1.0_spec-1.0.1.jar
geronimo-ws-metadata_2.0_spec-1.1.2.jar
ibatis-2.3.4.726.jar
iText-2.1.7.jar
iTextAsian.jar
jasperreports-4.0.1.jar
jasperreports-extensions-3.5.3.jar
jasperreports-fonts-4.0.1.jar
javassist.jar
jaxb-api-2.1.jar
jaxb-impl-2.1.13.jar
jcommon-1.0.16.jar
jdt-compiler-3.1.1.jar
log4j-1.2.16.jar
log4j.dtd
neethi-2.0.4.jar
ognl-3.0.jar
opensaml-1.1.jar
pjl-comp-filter-1.7.jar
poi-3.7-20101029.jar
postgresql-8.3-603.jdbc4.jar
saaj-api-1.3.jar
saaj-impl-1.3.2.jar
serializer-2.7.1.jar
spring-aop.jar
spring-beans.jar
spring-context-support.jar
spring-context.jar
spring-core.jar
spring-jdbc.jar
spring-jms.jar
spring-orm.jar
spring-test.jar
spring-tx.jar
spring-web.jar
spring-webmvc-struts.jar
spring.jar
sql-map-2.dtd
sql-map-config-2.dtd
struts2-convention-plugin-2.2.1.jar
struts2-core-2.2.1.jar
wsdl4j-1.6.2.jar
wss4j-1.5.8.jar
wstx-asl-3.2.9.jar
xalan-2.7.1.jar
xml-resolver-1.2.jar
XmlSchema-1.4.5.jar
xmlsec-1.4.3.jar
xwork-core-2.2.1.jar
----------------------



PDFとして作成された帳票を表示。
pict0003.jpg


iReportでテンプレートを作成し、データを流し込んで、PDFを作成します。

pict0005.jpg


※データは架空のものです。
ご参考まで。

ApacheとResinとStruts2とFlex(BlazeDS)の連携

メモしときます。

Project名はHello。
HelloWorld ! を表示します。


[ resin.conf ]
----------
# web-appのRoot設定
<web-app id="/" root-directory='C:\Documents and Settings\hoehoe\My Documents\EclipseProject\Hello'/>
----------


[ WEB-INF/lib/ ]
----------
aopalliance-1.0.jar
asm-2.2.3.jar
backport-util-concurrent.jar
cglib-nodep-2.1_3.jar
commons-beanutils.jar
commons-dbcp-1.4.jar
commons-digester.jar
commons-fileupload-1.2.1.jar
commons-io-1.3.2.jar
commons-logging-1.1.1.jar
commons-pool-1.5.4.jar
cxf-2.2.9.jar
dir.txt
FastInfoset-1.2.7.jar
flex-messaging-common.jar
flex-messaging-core.jar
flex-messaging-remoting.jar
freemarker-2.3.16.jar
geronimo-annotation_1.0_spec-1.1.1.jar
geronimo-servlet_2.5_spec-1.2.jar
geronimo-stax-api_1.0_spec-1.0.1.jar
geronimo-ws-metadata_2.0_spec-1.1.2.jar
ibatis-2.3.4.726.jar
javassist.jar
jaxb-api-2.1.jar
jaxb-impl-2.1.13.jar
jcommon-1.0.16.jar
log4j-1.2.16.jar
log4j.dtd
neethi-2.0.4.jar
ognl-3.0.jar
opensaml-1.1.jar
pjl-comp-filter-1.7.jar
postgresql-8.3-603.jdbc4.jar
saaj-api-1.3.jar
saaj-impl-1.3.2.jar
serializer-2.7.1.jar
spring-aop.jar
spring-beans.jar
spring-context-support.jar
spring-context.jar
spring-core.jar
spring-jdbc.jar
spring-jms.jar
spring-orm.jar
spring-test.jar
spring-tx.jar
spring-web.jar
sql-map-2.dtd
sql-map-config-2.dtd
struts2-convention-plugin-2.2.1.jar
struts2-core-2.2.1.jar
wsdl4j-1.6.2.jar
wss4j-1.5.8.jar
wstx-asl-3.2.9.jar
xalan-2.7.1.jar
xml-resolver-1.2.jar
XmlSchema-1.4.5.jar
xmlsec-1.4.3.jar
xwork-core-2.2.1.jar
----------




[ web.xml ]
----------
<?xml version="1.0"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
                      http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <!-- Flex(BlazeDSもFilter経由。先にBlazeDSのFilterを記述しておくこと) -->
    <filter>
        <filter-name>flex</filter-name>
        <filter-class>hello.filter.FlexFilter</filter-class>
        <init-param>
            <param-name>services.configuration.file</param-name>
            <param-value>/WEB-INF/flex/services-config.xml</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>flex</filter-name>
        <url-pattern>/messagebroker/*</url-pattern>
    </filter-mapping>


    <filter>
        <filter-name>struts2</filter-name>
        <filter-class>
            org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
        </filter-class>
        <init-param>
            <param-name>actionPackages</param-name>
            <param-value>hello.action</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/struts2/*</url-pattern>
    </filter-mapping>
</web-app>
[EOF]
----------


Struts2の配置 アノテーションを使用するのでstruts.xmlなどは無いです。

[ hello.action.HelloAction ]
----------
package hello.action;

import org.apache.struts2.convention.annotation.Namespace;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.Results;

import com.opensymphony.xwork2.ActionSupport;

/**
 * 相変わらず標準出力に欧米の挨拶をかますだけのアクション。
 */
@Namespace("/struts2")
@Results({
    @Result(name="success", location="hello.jsp"),
    @Result(name="failure", location="error.jsp")
})
public class HelloAction extends ActionSupport {
//public class SayHelloAction {
    private static final long serialVersionUID = 1L;

    /**
     * アクション実行メソッド。
     */
    public String execute() {
        // アクション実行の結果を文字列で返す。
        return ActionSupport.SUCCESS;
    }
    public String getMessage(){
        return "Hello World!!";
    }
}
[ EOF ]
----------


[ /WEB-INF/content/struts2/hello.jsp ]
----------
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<s:property value="message"/>
</body>
</html>
[ EOF ]
----------


[ hello.filter.FlexFilter ]
----------
package hello.filter;

import java.io.IOException;
import java.util.Enumeration;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.mock.web.MockServletConfig;

import flex.messaging.MessageBrokerServlet;

/**
 * The MessageBrokerServlet has a lower priority than Filters, regardless of the
 * url-pattern we use. For that reason I created a filter that directly extends
 * the MessageBrokerServlet but behaves as a filter. This might be quite dodgy
 * but that's the only way I found to integrate struts 2 and Adobe Flex/BlazeDS.
 * Anyone who knows a simpler way please email me at info at spltech.co.uk
 *
 * @author Armindo Cachada
 *
 */
public class FlexFilter extends MessageBrokerServlet implements Filter {

    /**
     *
     */
    private static final long serialVersionUID = 1L;

    public void destroy() {
        super.destroy();
    }

    /**
     * If this method is called the parent service method of the
     * MessageBrokerServlet is called, which does whatever BlazeDS needs to do
     * to communicate with the flex client. Note Here that any subsequent filter
     * will not be called because I am not invoking filterChain.doFilter/ That
     * is on purpose, because if it does, the normal struts 2 action mapping
     * mechanism will be called.
     *
     */
    public void doFilter(ServletRequest servletrequest,
            ServletResponse servletresponse, FilterChain filterchain)
            throws IOException, ServletException {
        this.service((HttpServletRequest) servletrequest,
                (HttpServletResponse) servletresponse);
    }

    /**
     * Note the use here of MockServletConfig. This utility class is available
     * in the spring framework. It is meant to be used for testing but I am
     * actually giving it a real purpose :)
     */
    public void init(FilterConfig filterconfig) throws ServletException {
        System.out.println("filter called");
        MockServletConfig servletConfig = new MockServletConfig(filterconfig
                .getServletContext());
        Enumeration filterParameters = filterconfig.getInitParameterNames();

        while (filterParameters.hasMoreElements()) {
            String filterParameter = (String) filterParameters.nextElement();
            System.out.println("Found parameter: " + filterParameter);
            String value = filterconfig.getInitParameter(filterParameter);
            servletConfig.addInitParameter(filterParameter, value);

        }
        super.init(servletConfig);
    }

}
[ EOF ]
----------


[ /WEB-INF/flex/services-config.xml ]
----------
<?xml version="1.0" encoding="UTF-8"?>
<services-config>

    <services>
        <!-- service要素には何でもいいからIDが要る模様 -->
        <service id="remoting-service"
            class="flex.messaging.services.RemotingService">
            <adapters>
                <adapter-definition id="java-object"
                    class="flex.messaging.services.remoting.adapters.JavaAdapter"
                    default="true" />
            </adapters>
            <default-channels>
                <channel ref="my-amf" />
                <channel ref="my-secure-amf"/>
            </default-channels>

            <!-- destination要素のIDが mx:RemoteObjectの destination プロパティに対応する -->
            <!-- リモート呼び出しを可能にしたい Beanの数だけ destination要素を記述する -->
            <destination id="fxService">
                <properties>
                    <!-- リモート呼び出ししたいJava Beansのクラス名 -->
                    <source>hello.ws.FlexService</source>
                    <!-- 注:この Beanはステートレスである -->
                </properties>
            </destination>
        </service>
    </services>

    <channels>
        <channel-definition id="my-amf"
            class="mx.messaging.channels.AMFChannel">
            <!--
                ここで構成されたエンドポイントURLを mx:RemoteObjectの
                endpoint プロパティにセットする
                {}内は実行時に自動で置き換えられるのでこの記述のままで良い。
            -->
            <endpoint
                url="http://{server.name}:{server.port}/{context.root}/messagebroker/amf"
                class="flex.messaging.endpoints.AMFEndpoint" />
            <properties>
                <add-no-cache-headers>false</add-no-cache-headers>
            </properties>
        </channel-definition>

        <!-- SSL用チャンネルの定義 -->
        <channel-definition id="my-secure-amf" class="mx.messaging.channels.SecureAMFChannel">
            <endpoint url="https://{server.name}:{server.port}/{context.root}/messagebroker/amfsecure" class="flex.messaging.endpoints.SecureAMFEndpoint"/>
            <properties>
                <add-no-cache-headers>false</add-no-cache-headers>
            </properties>
        </channel-definition>
    </channels>
</services-config>
[ EOF ]
----------


[ hello.ws.FlexService ]
----------
package hello.ws;

@SuppressWarnings("serial")
public class FlexService implements java.io.Serializable{
        
    public FlexService(){
    }
    public String sayHello(){
        return "Hello World !";
    }
}
[ EOF ]
----------



Apache
[ httpd.conf -Apache- ]
----------
#
# mod_caucho Resin Configuration
#
#Include conf/extra/httpd-proxy.conf
LoadModule caucho_module /usr/local/apache/modules/mod_caucho.so

ResinConfigServer 127.0.0.1 6800
CauchoConfigCacheDirectory /tmp
CauchoStatus yes
<Location /struts2/*>
  SetHandler caucho-request
</Location>
[ EOF ]
----------


Flex
[ Hello.mxml ]
----------
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" backgroundColor="white" creationComplete="init();">
<mx:Script source="Hello.as"/>
    <mx:RemoteObject id="srv" endpoint="{lbl_endPoint.text}" destination="fxService" showBusyCursor="true">
        <mx:method name="sayHello" result="resultHello(event);" fault="faultHello(event);"/>
    </mx:RemoteObject>
    <mx:Button x="65" y="59" label="click" id="btn_message" click="btn_message_click();"/>
    <mx:Text x="38" y="33" id="txt_message" text="{srv.sayHello.lastResult}"/>
    <mx:Label x="84" y="114" id="lbl_endPoint"/>
</mx:Application>
[ EOF ]
----------

[ Hello.as ]
----------
// ActionScript file
import hello.EndPoint;

import mx.controls.Alert;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;

private function init():void{
    lbl_endPoint.text = EndPoint.getEndPointRevers();
}
private function btn_message_click():void{
    srv.sayHello();
}

private function resultHello(event:ResultEvent):void{
    Alert.show(event.message.toString());
}
private function faultHello(event:FaultEvent):void{
    Alert.show(event.message.toString());
}
[ EOF ]
----------


[ hello.EndPoint.as ]
----------
package hello {

    import flash.external.ExternalInterface;
   
    import mx.messaging.ChannelSet;
    import mx.messaging.channels.AMFChannel;
    import mx.rpc.remoting.mxml.RemoteObject;

    public class EndPoint {
       
        private static var remote:RemoteObject;
       
        public function EndPoint() {
            //TODO: implement function
        }
        public static function getEndPointRevers():String{
            var endPointURL:String = ExternalInterface.call("getFQDN");
            endPointURL = endPointURL + "messagebroker/amf";
            return endPointURL;
        }
        public static function getRemoteObject():RemoteObject{
            //var remote:RemoteObject = new RemoteObject();
            if(remote == null){
                remote = new RemoteObject();
                var cs:ChannelSet = new ChannelSet();
                var ac:AMFChannel = new AMFChannel("my-amf", EndPoint.getEndPointRevers());
                cs.addChannel(ac);
                remote.destination = "flexService";
                remote.channelSet = cs;
            }
            return remote;
        }
    }
}
[ EOF ]
----------

[ index.template.html(JavaScript) ]
----------
<script language="JavaScript" type="text/javascript">
<!--
//------------------------------------------------------------------------------
// MyScript
function setTitle(){
    if(location.href.indexOf("#",0) == -1){
        document.title = "IntraMaster";
    }else{
        document.title = "IntraMaster";
    }
}
function getURL(){
    return (document.URL);
}
function getPort(){
    var protocol = location.protocol;
    var port = location.port;
   
    if(protocol == "http:"){
        if(port == ""){
            port = 80;
        }else{
       
        }
    }else if(protocol == "https:"){
        if(port == ""){
            port = 443;
        }else{
       
        }       
    }
   
    return port;
}
function getFQDN(){
    var fqdn = "";
    var hostname = location.hostname;
    var protocol = location.protocol;
    var port = location.port;
    var href= location.href;
   
    var a;
    var b;
   
    var lastIndex = href.lastIndexOf("/");
    href = href.substring(0, lastIndex);
   
    if(href.indexOf("~") == -1){
        if(port == ""){
            fqdn = protocol + "//" + hostname + "/"
        }else{
            fqdn = protocol + "//" + hostname + ":" + port + "/"
        }
    }else{
        a = href.split("//")[1];
        b = a.split("/")[1];
        if(port == ""){
            fqdn = protocol + "//" + hostname + "/" + b;
        }else{
            fqdn = protocol + "//" + hostname + ":" + port + "/" + b;
        }
    }
    //fqdn = "http://127.0.0.1:8081/messagebroker/amf";
    return fqdn;
}

function getUserAgent(){
    var ua = navigator.userAgent;
    var strOS;
   
    if(ua.indexOf("Win") >= 0){ strOS = "Win"; }
    else if(ua.indexOf("Mac") >= 0){ strOS = "Mac"; }
    else if(ua.indexOf("Linux") >= 0){ strOS = "Linux"; }
    else{ strOS = ""; }

    return strOS;
}

//------------------------------------------------------------------------------
//-->
</script>
[ EOF ]
----------
 
Tomcatと連携する場合はApacheのコンパイル時に ./configure --enable-proxy --enable-proxy-ajp を有効にし、
/usr/local/apache2/conf/extra/httpd-proxy.conf を新規に作成後、以下を記述保存します。

以下の例では、/struts2/* ( /struts2以下のすべてのリクエスト ) に対して ProxyPass を設定しています。

<Location /struts2/>
ProxyPass ajp://localhost:8009/
</Location>

Flexアプリを利用する場合、IEのバージョンによって指定するオプションが違ったのでmemoしておきます。

えー、これくらい統一して下さいな、M$さん...。


[ IE6 の場合 ]
SSL2.0/3.0を使用するのcheckをONに。
TLS 1.0を使用するのcheckをONに。
暗号化されたページをディスクに保存しないのcheckをOFFに。

ie6-internetoption.jpg

[ IE8 の場合 ]
SSL2.0/3.0を使用するのcheckをONに。
TLS 1.0を使用するのcheckをONに。
暗号化されたページをディスクに保存しないのcheckをONに。

ie8-internetoption.jpg

あと、無効なサイトの証明書について警告するのcheckをON/OFFにすると、証明書の確認ダイアログを抑制できたりします(IE6)。

IE8の場合は証明書のアドレスが不一致について警告するのcheckになります。
以上。
IE6 + mod_deflate(GZip圧縮) + Flex/FlashでFlashPlayerの無応答(もしくは動作不良)に対する解決策です。

以下、http://www.fxug.net/modules/xhnewbb/viewtopic.php?viewmode=flat&topic_id=3510&forum=16 に投稿した内容と重複します。

(■追記 - 2010/02/27 - この内容を編集しました。FxUGに投稿した内容と多少前後します。)


GZip compression + Flash Player + IE6 = Nightmare


先に参考にさせて頂いたURLをペタっと。

http://www.google.com/search?hl=ja&safe=off&rlz=1B3GGGL_jaJP336JP336&num=100&q=add-no-cache-headers&btnG=%E6%A4%9C%E7%B4%A2&lr=lang_ja&aq=&oq=
http://www.riaservice.com/?p=526
http://help.adobe.com/ja_JP/LiveCycleDataServicesES/3.0/Developing/WSc3ff6d0ea77859461172e0811f00f6e876-7fd6.html
http://www.adobe.com/support/documentation/jp/flex/2/releasenotes_flex2_fds.html



[ エラー内容 ]

デバッグ版FlashPlayer ver.10.0.32.18, IE6-SP1, Windows2000 Professionalでキャプチャしたエラー画面です。

flexError.jpg



[ 解決策 ]

結果としてはHTTPS + IEでの解決策と同様の手順になりました。
BlazeDS側にある WEB-INF\flex\services-config.xml を編集します。


    <channels>
        <channel-definition id="my-amf"
            class="mx.messaging.channels.AMFChannel">
            <!--
                ここで構成されたエンドポイントURLを mx:RemoteObjectのendpoint プロパティにセットする
                {}内は実行時に自動で置き換えられるのでこの記述のままで良い。
            -->
            <endpoint
                url="http://{server.name}:{server.port}/{context.root}/messagebroker/amf"
                class="flex.messaging.endpoints.AMFEndpoint" />
        </channel-definition>

の中に

<properties>
    <add-no-cache-headers>false</add-no-cache-headers>
</properties>

を記述するだけです。これで、mod_deflateやpjl-comp-filterなどによるGZip圧縮を有効にしても、ちゃんと動作するようにな ると思います。
下記の様に(endpoint以下に)properties部分を加えてWebコンテナを再起動して下さい。

    <channels>
        <channel-definition id="my-amf"
            class="mx.messaging.channels.AMFChannel">
            <!--
                ここで構成されたエンドポイントURLを mx:RemoteObjectの
                endpoint プロパティにセットする
                {}内は実行時に自動で置き換えられるのでこの記述のままで良い。
            -->
            <endpoint
                url="http://{server.name}:{server.port}/{context.root}/messagebroker/amf"
                class="flex.messaging.endpoints.AMFEndpoint" />
            <properties>
                <add-no-cache-headers>false</add-no-cache-headers>
            </properties>
        </channel-definition>

IEオプションのHTTP1.1を有効にして、URLにアクセスした時に動作すればOKかと。

結局パケットをキャプチャしてもダメだったので、デバッグ版FlashPlayerをIE6にインストールしてエラーメッセージを読みました。
エラーメッセージの内容は「NetConnection.Call.Failed: HTTP: Status 200」でしたので、それをキーワードにググったところIE + HTTPSの解決策と同じであることが判明致しましたので、ご報告とさせて頂きます。


以上。ご参考までに。

[ services-config.xml ]
-------
<?xml version="1.0" encoding="UTF-8"?>
<services-config>

    <services>
        <!-- service要素には何でもいいからIDが要る模様 -->
        <service id="remoting-service"
            class="flex.messaging.services.RemotingService">
            <adapters>
                <adapter-definition id="java-object"
                    class="flex.messaging.services.remoting.adapters.JavaAdapter"
                    default="true" />
            </adapters>
            <default-channels>
                <channel ref="my-amf" />
                <channel ref="my-secure-amf"/>
            </default-channels>

            <!-- destination要素のIDが mx:RemoteObjectの destination プロパティに対応する -->
            <!-- リモート呼び出しを可能にしたい Beanの数だけ destination要素を記述する -->
            <destination id="flexService">
                <properties>
                    <!-- リモート呼び出ししたいJava Beansのクラス名 -->
                    <source>com.chocbanana.ws.FlexService</source>
                    <!-- 注:この Beanはステートレスである -->
                </properties>
            </destination>
        </service>
    </services>

    <channels>
        <channel-definition id="my-amf"
            class="mx.messaging.channels.AMFChannel">
            <!--
                ここで構成されたエンドポイントURLを mx:RemoteObjectの
                endpoint プロパティにセットする
                {}内は実行時に自動で置き換えられるのでこの記述のままで良い。
            -->
            <endpoint
                url="http://{server.name}:{server.port}/{context.root}/messagebroker/amf"
                class="flex.messaging.endpoints.AMFEndpoint" />
            <properties>
                <add-no-cache-headers>false</add-no-cache-headers>
            </properties>
        </channel-definition>

        <!-- SSL用チャンネルの定義 -->
        <channel-definition id="my-secure-amf" class="mx.messaging.channels.SecureAMFChannel">
            <endpoint url="https://{server.name}:{server.port}/{context.root}/messagebroker/amfsecure" class="flex.messaging.endpoints.SecureAMFEndpoint"/>
            <properties>
                <add-no-cache-headers>false</add-no-cache-headers>
            </properties>
        </channel-definition>
    </channels>
</services-config>
[ EOF ]
-------



それでもエラーが出る場合は、↓の画像を参考にIEオプションも見直して下さい。

ieOption.jpg

このチェックが付いていると、上手く動かないんだと思います。
「暗号化されたページをディスクに保存しない」のチェックをOFFにして、再度確かめてください。


以下のURLが公式の回答ですね。
http://support.microsoft.com/kb/312496/ja
http://support.microsoft.com/kb/871205/ja


M$的には、
「HTTP 圧縮を使用している Web サーバーから返送されたデータの最初の 2,048 バイトが Internet Explorer で失われる」
「この問題は、コンピュータで Apache HTTP Server を実行していると発生する可能性が高くなります」
「Apache HTTP Server は、任意の種類のファイルでチャンク エンコードを使用できるため」
「IIS は、デフォルトではチャンク エンコードを使用しません」

だそうで、勘弁して下さい、M$さん。ホント。仕様だと回避策探すのが大変です。


MSのドキュメントでは

----------------------
この問題は、WININET と URLMON の間で通知が失われた場合に発生します。
通知が失われると、圧縮解除に必要なエンコードされたチャンクが失われます。
エンコードされたチャンクが失われると、gzip 圧縮解除プログラムが失敗します。

注)
WININET (Wininet.dll) は、HTTP データを受信するコンポーネントです。
URLMON (urlmon.dll) は、gzip 圧縮解除プログラムを実装するコンポーネントです。
----------------------

とあります。
で、IEでのGZipの解凍は urlmon.dll がやっているらしいので、バージョンを比較してみました。

urlmon_dll_img.jpg

エラーが出たのは 6.0.2800 のDLLを持ったWindows2000だけでした。
6.0.2900以上のバージョンのIE6ではエラーメッセージが出ませんでしたので、おそらくは多分、
urlmon.dllのバージョンによる不具合だと思われます。



■ 2010/02/27 - Windows2000 IE6-SP1のDLLをデバッグしてみました。 -

デバッグ対象であるDLLは WININET.DLLです。
URLMON.DLLはインストールされているDLLを使用しました。

んで、これのログを採ってみると...。

-----------------
21:59:49.054 0000024c:<app> 001 HttpQueryInfoA(0xcc000c, HTTP_QUERY_CONTENT_LENGTH (0x5), 0x12d774 [""], 0x12e9e8 [4168], 0x0 [0])
21:59:49.054 0000024c:<app> 001   *lpdwBufferLength = 3
21:59:49.054 0000024c:<app> 001   Query data:
21:59:49.054 0000024c:<app> 001   3 (0x3) bytes @ 0x12d774
21:59:49.054 0000024c:<app> 001   0012d774  36 37 35                                          675
21:59:49.054 0000024c:<app> 001 HttpQueryInfoA() returning TRUE

21:59:49.054 0000024c:<app> 001 HttpQueryInfoA(0xcc000c, HTTP_QUERY_CONTENT_DISPOSITION (0x2f), 0x12d774 [""], 0x12e9e8 [4168], 0x0 [0])
21:59:49.054 0000024c:<app> 001   HttpQueryInfoA() returning 12150 [ERROR_HTTP_HEADER_NOT_FOUND]
21:59:49.054 0000024c:<app> 001 HttpQueryInfoA() returning FALSE
-----------------

というエラーが延々と...。出ます...。orz
ここで、HttpQueryInfoA() returning 12150 [ERROR_HTTP_HEADER_NOT_FOUND] と HttpQueryInfoA() returning FALSE が出ているのが確認できます。

# 本来のログはもっともっと長いです。テキストで5MB近くになりました。追っかけるだけで死ねる...。orz

えー、つまりHTTPヘッダがぶっ千切られてますね。

これにブチ当たった場合、Flexのサーバー側を設定し、IEオプションを見直し、DLLのバージョンを確認して、出来ればDLLのデバッグを行って確認 して下さい。

...まぁIE6だけ...でしょうから。今後は良いと思うんですけどね...。
debug_log.txt


FlexのRemoteObjectのendpointプロパティの動的切り替えのサンプルです。
ActionScriptだとポート番号が0になったり挙動不審だったので、JavaScriptで実装しなおしました。
ExternalInterface.call() で呼んでるだけです。


[ index.template.html ]
------------------
<script language="JavaScript" type="text/javascript">
<!--
// -----------------------------------------------------------------------------
// Globals
// Major version of Flash required
var requiredMajorVersion = ${version_major};
// Minor version of Flash required
var requiredMinorVersion = ${version_minor};
// Minor version of Flash required
var requiredRevision = ${version_revision};
// -----------------------------------------------------------------------------

// MyScript
function getFQDN(){
    var fqdn = "";
    var hostname = location.hostname;
    var protocol = location.protocol;
    var port = location.port;
    var href= location.href;
   
    var a;
    var b;
   
    var lastIndex = href.lastIndexOf("/");
    href = href.substring(0, lastIndex);
   
    // URLから ~ が 見つからなかったら
    if(href.indexOf("~") == -1){
        if(port == ""){
            fqdn = protocol + "//" + hostname + "/"
        }else{
            fqdn = protocol + "//" + hostname + ":" + port + "/"
        }
    }else{
        a = href.split("//")[1];
        b = a.split("/")[1];
        if(port == ""){
            fqdn = protocol + "//" + hostname + "/" + b;
        }else{
            fqdn = protocol + "//" + hostname + ":" + port + "/" + b;
        }
    }
    return fqdn;
}
// -->
</script>
...省略...
------------------


[ EndPoint.as ]
------------------
package com.chocbanana.flex.controller {

    import mx.core.Application;
    import mx.utils.URLUtil;
    
    public class EndPoint {
        public function EndPoint() {
            //TODO: implement function
        }
        public static function getEndPointRevers():String{
            var endPointURL:String = ExternalInterface.call("getFQDN");
            //Alert.show(endPointURL);
            endPointURL = endPointURL + "/messagebroker/amf";
            return endPointURL;
        }
    }
}
[ EOF ]
------------------

一旦ダミーのLabelへURLを貼り付けて、<mx:RemoteObject id="login" endpoint="{lbl_endPoint.text}" /> とすればおk。

[ Splash.mxml ]
------------------
<?xml version="1.0" encoding="utf-8"?>
<mx:TitleWindow xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" verticalAlign="middle" horizontalAlign="center" creationComplete="onCreate();" showCloseButton="false" title="Login" horizontalScrollPolicy="off" width="960" height="520" activate="onActive();">
    <mx:Script>
        <![CDATA[
            import mx.managers.PopUpManager;
            import com.chocbanana.flex.controller.EndPoint;

            public function setEndPoint():void{
                lbl_endPoint.text = com.chocbanana.flex.controller.EndPoint.getEndPointRevers();
            }

        ]]>
    </mx:Script>
<mx:RemoteObject id="login" endpoint="{lbl_endPoint.text}" destination="flexService" showBusyCursor="true">
    <mx:method name="login" result="isLoginResult(event)"/>
</mx:RemoteObject>

<mx:Label id="lbl_endPoint" creationComplete="setEndPoint();" width="5" height="5" visible="false"/>
</mx:TitleWindow>
[ EOF ]
------------------

いちいちダミーのコンポーネントを貼らないとダメな点がなければなぁ...


[ 参考URL ]
http://livedocs.adobe.com/flex/3_jp/html/help.html?content=deep_linking_2.html

ディープリンクを使用しない場合は、

プロジェクト内のViewStackなどのhistoryManagementEnabledをfalseにし、

[ html-template ] フォルダ内にある
history.css
history.js
historyFrame.html
を含めず、

さらに、FlexBuilderのコンパイラ設定を以下のようにする。

プロジェクト/プロパティを選択。
「Flex コンパイラ」オプションを選択。
「ブラウザのナビゲーションとの統合を有効にする」オプションを外す。


FlexBuilderで生成したHTMLにアクセスすると、URLの末尾に#が付いてページのTitleが#だけになってしまうので、それの回避方法を 紹介します。
FlexからJavaScriptを呼び出すため、HTMLのページにJavaScriptを定義する。

[ IntraMaster.html ]
---------------------
<script language="JavaScript" type="text/javascript">
<!--
...省略...
//------------------------------------------------------------------------------
// MyScript
function setTitle(){
    if(location.href.indexOf("#",0) == -1){
        //window.alert("no #");
    }else{
        document.title = "IntraMaster";
        //alert("yes #");
    }
}
setTitle();
//------------------------------------------------------------------------------
// -->
...省略...
</script>
[ EOF ]
---------------------
あとは定義したJavaScriptをActionScriptから(ExternalInterfaceを使用して)任意のタイミングで呼び出すだけ。
今回はFlex側のWindowがActiveになった時点で onActive();を呼び出しています。

[ LoginWindow.mxml ]
--------------------------
<mx:Script>
    <![CDATA[
        private function onActive():void{
            if(flash.external.ExternalInterface.available){
                flash.external.ExternalInterface.call("setTitle");
            }
        }
    ]]>
</mx:Script>
...省略...
[ EOF ]
--------------------------
以上。

手始めにJ2EEコンテナへの展開方法を以下に示します。
XMLを2つ書いて、リモートから呼び出したいJavaのClassを作成します。

[ web.xml ]
----------------
<?xml version="1.0" encoding="UTF-8"?>

<!--
    - START SNIPPET: webxml
-->
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>   
    <servlet>
        <servlet-name>MessageBrokerServlet</servlet-name>
        <servlet-class>flex.messaging.MessageBrokerServlet</servlet-class>
        <init-param>
            <param-name>services.configuration.file</param-name>
            <param-value>/WEB-INF/flex/services-config.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>MessageBrokerServlet</servlet-name>
        <url-pattern>/messagebroker/*</url-pattern>
    </servlet-mapping>
</web-app>
[ EOF ]
----------------

[ WEB-INF\flex\services-config.xml ]
----------------
<?xml version="1.0" encoding="UTF-8"?>
<services-config>

    <services>
        <!-- service要素には何でもいいからIDが要る模様 -->
        <service id="remoting-service" class="flex.messaging.services.RemotingService">
            <adapters>
                <adapter-definition id="java-object"
                    class="flex.messaging.services.remoting.adapters.JavaAdapter"
                    default="true" />
            </adapters>
            <default-channels>
                 <!-- HTTP用 -->
                <channel ref="my-amf" />

                <!-- HTTPS用 -->
                <channel ref="my-secure-amf"/>
            </default-channels>

            <!-- destination要素のIDが mx:RemoteObjectの destination プロパティに対応する -->
            <destination id="flexService">
                <properties>
                    <!-- リモート呼び出ししたいJava Beansのクラス名 -->
                   <!-- リモート呼び出しを可能にしたい Beanの数だけ destination要素を記述する -->
                    <!-- 注:この Beanはステートレスである -->
                    <source>com.chocbanana.ws.FlexService</source>
                </properties>
            </destination>
        </service>

    </services>

    <channels>
        <channel-definition id="my-amf"
            class="mx.messaging.channels.AMFChannel">
            <!--
                ここで構成されたエンドポイントURLを mx:RemoteObjectの
                endpoint プロパティにセットする
                {}内は実行時に自動で置き換えられるのでこの記述のままで良い。
            -->
            <endpoint
                url="http://{server.name}:{server.port}/{context.root}/messagebroker/amf"
                class="flex.messaging.endpoints.AMFEndpoint" />
        </channel-definition>

        <!-- SSL用チャンネルの定義 -->
        <channel-definition id="my-secure-amf" class="mx.messaging.channels.SecureAMFChannel">
            <endpoint url="https://{server.name}:{server.port}/{context.root}/messagebroker/amfsecure" class="flex.messaging.endpoints.SecureAMFEndpoint"/>
            <properties>
                <add-no-cache-headers>false</add-no-cache-headers>
            </properties>
        </channel-definition>
    </channels>
</services-config>
[ EOF ]
----------------

   ...あとは普通にMVCでコーディングしていくだけ。

[ com.chocbanana.ws.FlexService ]
-------------
package com.chocbanana.ws;

import java.io.FileOutputStream;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class FlexService{
   
    private String dbDriver = null;
    private Db2Interface dao = null;
    private Properties prop = null;
    private static final String contextFile = "com/chocbanana/db/dataSource.xml";
    private static final String contextBean = "db";
   
    public FlexService(){
        prop = new Properties();
        InputStream in = FlexService.class.getResourceAsStream("/com/chocbanana/db/database.properties");
        try{
            prop.load(in);
            dbDriver = prop.getProperty("db.driver");
        }catch(java.io.IOException ioe){
            ioe.printStackTrace();
        }
    }
     ・・・省略・・・
}
[ EOF ]
-------------

 MVCのViewに当たるFlex側で、RemoteObjectを設定すればOK.
MXML内にRemoteObjectを定義して、ActionScriptで呼び出すだけ。

[ App.mxml ]
<mx:Script source="App.as"/>
<mx:RemoteObject id="srv" destination="flexService" endpoint="{'http://{server.name}/messagebroker/amf'}" />

[ App.as ]
--------------
var role:LoginRole = srv.getLoginUser.lastResult as LoginRole;
srv.getLoginUser(uname);
[ EOF ]
--------------

などなど。JavaBeanを戻り値として得ることも可能。

このページの先頭へ