Wednesday, December 19, 2012

.NETからAlfresco API につなぐ(その1)

こんにちは、杉本です。

いつもはこっちで書いてますが、勉強会の報告なのでこちらで書かせてもらいます。こちらでははじめまして。

ぼくは普段 .NET さんとお仕事をしているので、最近 Alfresco をぜんぜん触っていないのですが、今回は Alfresco とちょっと連携するようなコードを書いてみることにしました。

Alfresco は文書管理を行うための巨大な仕組みで、内部には RDB をつかっているわけですが、 API を通すことで Alfresco そのものを文書データベース、として扱うのも良いよね、と思っています。「文書管理の仕組み」を導入するとなるとなんだか大袈裟な気がしますが、別システムでファイルを保存するストアを文書管理 DB にする、というような視点で別システムのサブシステムとして見ることもできるんじゃないでしょうか。

AlfrescoのAPIといえば、普通は WebScript API を使いますね。ところが今回はあえてのSOAPで繋いでみます。


ちなみに、 Alfresco の Wiki をみると、Web Services という項目のところには、

Please note that this API has been superceded by CMIS (specifically, the CMIS SOAP binding). It's use is no longer recommended.

とあって、Web Service はもう使われなくなっていくことが書かれています(そもそも SOAP が……)

ただ、今回はちょっと簡単に実験したかったので、すでに消滅しつつある SOAP でやってみることにしました。続編として他のアクセス方法でもやっていきたいと思います。

環境としては手もとにあるものをそのままつかったので、Visual Studio 2012 で、.NET Framework 4.5 の環境下でやっています。たいしたことはしてないので、おそらく2010、4.0 の組み合わせでもいけると思います。

.NET では通信フレームワークとして WCF というのがあります。今回SOAPでお気軽にやってしまったのにはこれがあって、Web Service だと WDSL が型情報を持っているので戻ってくるオブジェクトのクラスを自動生成してくれるんですよね。 JSON からクラスを推測してくれるライブラリもあるにはありますが、標準で出来る以上たいへんお手軽なので、今回はWeb Serviceをつかって実験したのでした。

さて、WDSLの場所はこれです。

http://<servername:PORT>/alfresco/api/<サービス名>Service?wsdl
サービス名には Repository とか Content とかが入ります。

まずおもむろに Visual Studio 2012 で新規ソリューションとプロジェクトを作成して、「サービス参照の追加」を行います。

サービス参照追加画面がでるので、 URL を指定して、「探索」するとサービスのところにサービス一覧が出るので、それを選択、 OK をおせば完了です。簡単だ!(名前空間は適当に)


つづいて、 WCF で通信する際の設定です。app.config右クリックから[WCF構成の編集]を選べば UI で設定できます(app.configを直接編集しても良いです)昔はこんなのなかったんじゃなかったかなあ。便利だなあ。

[エンドポイント]でサービスを選択、[全般]タブ、項目 Binding が basicHttpBinding (デフォルトのまま)であることを確認します。

[バインド]でサービスを選択[セキュリティ]タブ、[(全般)]の下の項目 mode を TransportWithMessageCredential にセットします。

[MessageSecurityプロパティ]の下の項目 MessageClientCredentialType を UserName にセットします。

AuthenticationService はセキュリティ設定不要、 HTTP で接続します。
それ以外はセキュリティ設定が必要、HTTPSで接続します。

app.configは

    
        
            
                
                
                  
                    
                  
                
              
                
                  
                
              
            
        
        
            
            
            
        
    

こんな感じになりました。

次にコードを書いていきますが、今回はローカルに立てたサーバに繋ぐので SSL とかの処理をバイパスすることにします。


        private bool OnRemoteCertificateValidationCallback(Object sender, X509Certificate certificate, X509Chain chain,  SslPolicyErrors sslPolicyErrors) {
            return true; 
        }

こんなコードを書き、
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(OnRemoteCertificateValidationCallback);
こんな風に呼べばOKです。

今回はリポジトリサービス、コンテンツサービスを使ってリポジトリやコンテンツにアクセスしてみました。コードを全部載せてしまいます。


using System;
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Windows;
using AuthenticationService = WpfApplication1.Alfresco.AuthenticationService;
using ContentService = WpfApplication1.Alfresco.ContentService;
using RepositoryService = WpfApplication1.Alresco.RepositoryService;

namespace WpfApplication1 {
    public partial class App : Application {
        private void Application_Startup(object sender, StartupEventArgs e) {
            ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(OnRemoteCertificateValidationCallback);

            var authClient = new AuthenticationService.AuthenticationServiceSoapPortClient();
            var ticket = authClient.startSession("takuma.sugimoto", "takuma");


            var repoClient = new RepositoryService.RepositoryServiceSoapPortClient();
            repoClient.ClientCredentials.UserName.UserName = ticket.username;
            repoClient.ClientCredentials.UserName.Password = ticket.ticket;
            var store = repoClient.getStores().FirstOrDefault(p => p.address == "SpacesStore");
            var query = new RepositoryService.Query() {
                statement = "ID:\"workspace://SpacesStore/3763112e-e36f-44de-9992-ab122350bc21\"",
                language = "lucene",
            };
            var result = repoClient.query(new RepositoryService.QueryConfiguration(), store, query, true);
            var resultNode = result.resultSet.rows.FirstOrDefault();
            var path = resultNode.columns.FirstOrDefault(p => p.name == "{http://www.alfresco.org/model/content/1.0}path").value;
            var name = resultNode.columns.FirstOrDefault(p => p.name == "{http://www.alfresco.org/model/content/1.0}name").value;

            var contentClient = new ContentService.ContentServiceSoapPortClient();
            contentClient.ClientCredentials.UserName.UserName = ticket.username;
            contentClient.ClientCredentials.UserName.Password = ticket.ticket;
            var reference = new ContentService.Reference(){
                store =  new ContentService.Store(){scheme = "workspace",address ="SpacesStore"},
                uuid = resultNode.node.id,
            };
            var predicate = new ContentService.Predicate() {Items = new ContentService.Reference[] { reference }};
            var contents = contentClient.read(predicate, "{http://www.alfresco.org/model/content/1.0}content");
            
            var content = contents.FirstOrDefault();
            var url = content.url + "?ticket=" + ticket.ticket;
            
            var req = WebRequest.Create(url);
            using (var rsp = req.GetResponse()) {
                var stm = rsp.GetResponseStream();
                if (stm != null) {
                    using (stm)
                    using (var reader = new StreamReader(stm))
                    using (var writer = File.OpenWrite(@"D:\Users\takuma.sugimoto\Desktop\temp\" + name)) {
                        var count = 0;
                        var buffer = new byte[4096];
                        do {
                            count = stm.Read(buffer, 0, buffer.Length);
                            writer.Write(buffer, 0, count);
                        } while (count != 0);

                    }
                }

            }
        }

        private bool OnRemoteCertificateValidationCallback(Object sender, X509Certificate certificate, X509Chain chain,  SslPolicyErrors sslPolicyErrors) {
            return true; 
        }
    }
}

ID を直接していしてダウンロードしているというサンプルのためのサンプルコードと言った内容ですが、ご容赦ください。ここにポイントがいくつかあります。

  • WCF で自動生成した場合、サービスごとに型が出来てしまうので、おなじリポジトリを指すクラスが別のものになってしまう。
  • ファイル実体のダウンロード用 Web Service はないので、認証済みのトークンをダウンロード URL につけたURLを直にたたいてダウンロードしてくる

こんなところです。最初の問題は自分で型を作ることも出来るのでそれで解決しますが、それをやるのであれば Web Service でやりはじめた意味があまりないので、自分で型クラスを作るのは次回以降、Web Scriptの API と連携するところで試すことにしましょう。

後者も WebScript であれば専用の API があるようです。ただ、このダウンロード URL を直に叩くというやり方も簡単ではあるので、やりかたとして頭に入れておくと良さそうです。

ハイ、そんなわけで、わざわざフェードアウトしつつある技術を使って Alfresco にアクセスしてみました。技術自体は古くてもかなり簡単にアクセスできるように作られていることが分かってもらえたかと思います。今回はここまで。(いつになるかわかりませんが)次回は Web Script の API と CMIS の API で接続することを試してみたいと思います。