logo

サイト内検索
ココログ最強検索 by 暴想

最近のトラックバック

無料ブログはココログ

« 2009年8月 | トップページ | 2011年6月 »

2011年5月

RestでForce.comのデータにServletからアクセスするテスト

このページのサンプルコードで動かしてみる。

まずはTomcat環境の構築です!

はじめに概略整理しましょう。
このサンプル例では

  1. Tomcat上のServletからForce.comへアクセス
  2. Http送信にはApacheのHttpClientライブラリを使用
  3. Oauth認証後、Force.com内のデータにアクセス
  4. Json形式のデータをJavaのJsonライブラリを使用して取得

ということをやっていますね。
このページの例はServlet仕様3.0のコードなので、
Tomcat7を使用しろとのこと。
コードを書き直せばそれ以下バージョンでもいいのでしょうが、
せっかくなのでTomcat7を環境構築する。

Tomcat7はこちらからダウンロードする。
今回Windows環境ですのでzipを落とします。

おっとTomcatプラグインのTomcat7対応版を
ここから落として入れておきましょうね。

↓こうして設定できましたでしょうか?

Ws000005

次にTomcatでSSL設定する必要があります。
8443ポートでアクセスできるようにします。

jdkのインストール先にあるkeytoolというコマンドを叩いて、
keyを作成します。

詳細はこちら

keyはデフォルトで%HOMEPATH%の下に.keystoreという名前でできます。
作成先を指定するにはパラメータ"-keystore"を使用。

↓%CATALINA_HOME%\conf\server.xml
<!--
<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
               maxThreads="150" scheme="https" secure="true"
               clientAuth="false" sslProtocol="TLS" keystorePass="changeit"
               keystoreFile="C:/Documents and Settings/ユーザ名/.keystore" />
-->

ここのコネクタ部分のコメントを外してkeystorePassとkeystoreFileを指定。
ここでkeystorePassはデフォルトのchangeitのまま
キーを作成しています。

そしたらTomcatを起動して8443ポートでアクセスしてみよう。

↓chromeでアクセス
Ws000006

とりあえず このまま続行をクリック。

こんな感じのURLに
Ws000007

 

おおっTomcat7から画面ずいぶん変わったなー
Ws000008



次にEclipseのJavaプロジェクトの作成です!

次にサンプルコードをダウンロードして、
EclipseのJavaプロジェクトに入れときます。
サンプルコードは日本チームさん
が用意してくれたものを使用することとする。

ただしindex.htmlは用意されていないので、
もとのソースを使用する。

あといちいちダウンロード面倒なので、
ここ「library1.zip」をダウンロード
ここ「library2.zip」をダウンロード
に必要なライブラリ置いとくので、
クラスパスに通してください。

↓そうするとこんな感じです。

Ws000010

jsonのライブラリはサンプルページのjson.orgからたどって、
必要ファイルをソースのままダウンロードしました。
jarとかになっているのですか?

私は恥ずかしながら知らなかったのですが、servlet3.0仕様からは
web.xmlなくてもアノテーションだけでよいのですね。



続いてForce.comにリモートサイトを登録します。

自分のTomcatの場所をリモートサイトとして登録します。

↓設定 >> アプリケーションの設定 >> 開発 >> リモードアクセス
から自分のTomcatの場所を指定し、
コンシューマ鍵とコンシューマの秘密を
OAuthServletの@WebInitParamアノテーションに指定する。

※ちなみに、設定 >> 管理者設定 >> セキュリティーのコントロール >> リモートサイトの登録
はSalesforce.com から呼び出すことのできる Web アドレスですので、
今回のTomcat側が行うことをSalesforceから行う場合の設定ですので、
別ものですのでよろしくお願いします。

こちらの機能を使ってYahooAPIにRESTでアクセスしたりする、

実践的研修 Force.com研修 アドバンス編はこちらからどうぞ。



サンプルプログラムを動かす

話し戻ってhttps://localhost:8443/RestTest/にアクセスすると。

コンテキスト直下のindex.htmlが開きます。
これはServletの仕様。

index.html

Ws000011

DHMLでリンクを作成していますね。
このリンクをクリックすると
あとはサンプルページの説明通り、
Servletをデバッグすれば、概要をつかむことが可能です。

ただ、DemoRestのshowAccounts()

private void showAccounts(String instanceUrl, String accessToken,
            PrintWriter writer) throws ServletException, IOException {
       
        HttpClient httpclient = new DefaultHttpClient();
        // set the SOQL as a query param
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("q",
                "SELECT Name, Id from Account LIMIT 100"));
        HttpGet get = new HttpGet(instanceUrl + "/services/data/v21.0/query?"
                + URLEncodedUtils.format(params, "UTF-8"));
        // set the token in the header
        get.addHeader("Authorization", "OAuth " + accessToken);
        HttpResponse getResponse = httpclient.execute(get);
        if (getResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
            // Now lets use the standard java json classes to work with the
            // results
            try {
                JSONObject response = new JSONObject(new JSONTokener(
                        EntityUtils.toString(getResponse.getEntity(), "UTF-8")));
                System.out.println("Query response: " + response.toString(2));
                writer.write(response.getLong("totalSize")
                        + " record(s) returned\n\n");
                JSONArray results = response.getJSONArray("records");
                for (int i = 0; i < results.length(); i++) {
                    writer.write(results.getJSONObject(i).getString("Id")
                            + ", " + results.getJSONObject(i).getString("Name")
                            + "\n");
                }
                writer.write("\n");
            } catch (JSONException e) {
                e.printStackTrace();
                throw new ServletException(e);
            }
        }
    }

ここのresponse.getString("totalSize")は
getLong("totalSize")にしないと落ちました。
サンプルではshowAccounts以外にもいろいろやってますが、
今回とりあえず showAccountsのみ試してみました。

↓showAccountsのもどりはこんな感じになります。

Ws000012

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * -
     ネットのパワーを不動産業へ生かす!
     不動産業向け顧客管理・営業支援システム  顧きゃく録!
- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *

ApexのメソッドにSOAPでアクセスしてみるテスト

結論から言うとログイン処理に関しては、
partnerAPIかEnterpriseAPIを使用しなくてはならないみたい。
それかOauth認証でユーザに入力促すのも出来るようです。

ここではpartnerAPIを使用してログイン後、
自身の公開Apexを呼ぶということをやってみました。

ここに公開メソッドの書き方は書いてあります。
これと同じAccountPlanを作成。
自作のHelloWorldクラスも作成してWSDLを作る。

上のサイト通りにApexコードを書くと、
Force.comの 設定 >> 開発 >> Apexクラス の一覧に

Ws000000_2

こんな感じでWSDLというリンクボタンが作成されます。
ここからWSDLファイルをダウンロードしてですね、
今回はJavaでアクセスしますんで、
Axisのクライアント側stubを作成します。

この辺にWSDL2Javaの使い方とかは書いてあります。
ここではenterpriseAPIのstub作成が書かれていますが、
同じ感じでpartnerAPIのstubも作成出来ますんで、
作成しておきましょう。

(enterpriseAPIでももちろん良いのですが、
ここではpartnerAPIを使っているので)

Force.com研修では、
アドバンス編にてpartnerAPI等々
についても詳しくご説明いたしております!

↓そんで出来るとこんな感じです。

Ws000001_2

↓そしてこれらを使用してのクライアントサイドのプログラム

Ws000004

↓結果はこんな感じ
Ws000002

書き忘れたけどstub作るときにUrlのコンストラクタに
渡す引数の定数値は呼び出したいAPIのエンドポイントです。

これ指定しないとエンドポイントがないよ!って怒られる。

具体的に言うとWSDLファイルの

Ws000003

この部分です。

Locatorの使いどころがわからない。

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * -
     ネットのパワーを不動産業へ生かす!
     不動産業向け顧客管理・営業支援システム  顧きゃく録!
- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *

Force.comのDBでselect * from が出来ないか

Force.comのDBをsoqlで読んだ場合、selectの後に指定したフィールド以外の得られたオブジェクトのフィールドにアクセスしようとすると、以下のエラーで落ちます。

SObject row was retrieved via SOQL without querying the requested field

Force.com研修でもこのエラーについては説明していますが、このエラー、何度見ただろうか。
ようは、文字通り、このオブジェクトは元々、そのフィールドをリクエストしていないsoqlで読まれたものだ、と言っている。

ならば、select * ですべてのフィールドを読みたくなるが、Force.comのsoqlではselect * がサポートされていない。
HibernateのようなORマッピングに慣れていると、読まれたオブジェクトはすべてのフィールドが準備万端なのが当然のように思ってしまうが、クラウドのフレームワークは節約志向に出来ているので、そうは行かないということらしい。

仕方が無いので、地道に必要なフィールドを

select id,Name,xxx__c,yyy__c ・・

てな感じで、記述することになる。
しかし、この問題は、実は非常に厄介で、カスタム項目を追加した場合に、表示するVisualforceページだけでなく、いちいちapexコードにも手を入れなければならない事態になる危険性を秘めているのです。

特定の運用環境のみ、カスタム項目を追加したい場合など、カスタム項目の追加や、Visualforceページの修正は比較的容易だが、apexコードの修正が必要となったとたんに、大事になるので、頭が痛いところ。

そこで、カスタム項目をメタデータから動的に読み取ってsoqlを組み立てるサービスメソッドを作ることにしました。

[Apexコード]
        //SOQL発行用にSObjectのカスタムフィールドのカンマ区切りのリストを作る
        public static String getCustomFieldsNames(Schema.SObjecttype tp){
                String res = '';
                Map<String,Schema.sObjectField> fmap = tp.getDescribe().fields.getMap();
                for(String fn:fmap.keySet()){
                        if(fn.endswith('__c')){
                                res = res + fn + ',';
                        }
                }
                return res.substring(0,res.length()-1);
        }
このメソッドは、特定のSobjectのフィールド名のうち、__cで終わるカスタム項目をすべて選択してカンマ区切りにして返しますので、こんな感じに使用してSoqlを発行すると良いです↓

        String wfnms = 'id,name,' +  getCustomFieldsNames(Item__c.SObjecttype);
        Item__c[] itms = Database.query('select ' + wfnms + ' from Item__c where ownerId=\'' + oid + '\'');

あえて、__cで終わるカスタム項目のみにしてあるのは、一部の標準項目をSoqlに指定するとエラーになるものがあるからです。

これで、オブジェクトにカスタム項目を追加してもApexをいじらなくてすむようになります。

実質的なselect * の完成!

「顧きゃく録」開発チームではこの方法を標準としてます。

(必ずすべてのカスタム項目を読むのでsalesforceのクラウド側からはありがたくないかも..)

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * -
     ネットのパワーを不動産業へ生かす!
     不動産業向け顧客管理・営業支援システム  顧きゃく録!
- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *

Apex正規表現はJavaと"大体"同じだよ

大体こんな感じのAPIになっていてJavaと大体同じであると分かりました。微妙に違ってるとこもあるのだけれどね。
@isTest private class RegExTest {     static testMethod void testMatcherMatches() { String regex1 = 'neko inu saru kiji'; String searchStr = 'neko inu saru kiji'; Pattern pt = Pattern.compile(regex1); Matcher matcher = pt.matcher(searchStr); boolean bool = matcher.matches(); system.assertEquals(bool, true); regex1 = 'neko'; searchStr = 'neko inu saru kiji'; pt = Pattern.compile(regex1); matcher = pt.matcher(searchStr); boolean bool2 = matcher.matches(); system.assertEquals(bool, true); } static testMethod void testMatcherFind() { String regex1 = 'inu'; String searchStr = 'neko inu saru kiji'; Pattern pt = Pattern.compile(regex1); Matcher matcher = pt.matcher(searchStr); boolean bool = matcher.find(); system.assertEquals(bool, true); bool = matcher.find(); system.assertEquals(bool, false); matcher.reset(); bool = matcher.find(); system.assertEquals(bool, true); } static testMethod void testMatcherLookAt() { String regex1 = 'inu'; String searchStr = 'neko inu saru kiji'; Pattern pt = Pattern.compile(regex1); Matcher matcher = pt.matcher(searchStr); boolean bool = matcher.lookingAt(); system.assertEquals(bool, false); regex1 = 'neko'; searchStr = 'neko inu saru kiji'; pt = Pattern.compile(regex1); matcher = pt.matcher(searchStr); bool = matcher.lookingAt(); system.assertEquals(bool, true); } static testMethod void testBackRefer() { String regex1 = '((neko).+)(\\2zura)'; String searchStr = 'neko inu saru kiji neko inu nekozura'; Pattern pt = Pattern.compile(regex1); Matcher matcher = pt.matcher(searchStr); matcher.find(); String all = matcher.group(0); String f = matcher.group(1); String b = matcher.group(3); system.assertEquals(all, 'neko inu saru kiji neko inu nekozura'); system.assertEquals(f, 'neko inu saru kiji neko inu '); system.assertEquals(b, 'nekozura'); } } 

Force.com研修はこちらへ

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * -
     ネットのパワーを不動産業へ生かす!
     不動産業向け顧客管理・営業支援システム  顧きゃく録!
- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *

Force.comのSites機能を使用してXMLファイルを出力する

実は「顧きゃく録」ではSites機能を使用して、siteMap.xmlファイルを生成する機能を持っています。

その際に使用したSites機能を使用してXMLファイルを出力するテクニックの例です。

[サンプルプログラム:SampleByXml]

1.仕様
以下のようなユーザーIDを含むリクエストURLに対して、特定のユーザーの所有するカスタムオブジェクトのリストをXMLにて返す。
1.1.リクエストURL
http://サイトのURL/SampleByXml?ownerid=ユーザーのID
例:

http://kdbmiuratest-developer-edition.na11.force.com/SampleByXml?ownerid=005A0000000tlF2

↑実際にこのURLで稼動します。

1.2.レスポンスXML

タグ名:内容

items:カスタムオブジェクトのリスト
 item:個々のオブジェクト
  フィールド名:値

例:

<items>
<item>
<id>a04A00000044Te7IAE</id>
<name>井村屋あんまん</name>
<price__c>120</price__c>
</item>
<item>
<id>a04A00000044TYeIAM</id>
<name>井村屋にくまん</name>
<price__c>120</price__c>
</item>
<item>
<id>a04A00000044TcMIAU</id>
<name>三色団子</name>
<price__c>110</price__c>
</item>
</items>

2.ソースコード

[Visualforceページ]

<apex:page controller="SampleByXml" showHeader="false"
sidebar="false" contentType="text/xml">
    <apex:outputText escape="false" value="{!xml}"/>
</apex:page>

[コントローラークラス]

public with sharing class SampleByXml {

    public String getXml() {
        String oid = system.currentPageReference().getParameters().get('ownerid');
        String wfnms = 'id,name,' +  getCustomFieldsNames(Item__c.SObjecttype);
        Item__c[] itms = Database.query('select ' + wfnms + ' from Item__c where ownerId=\'' + oid + '\'');
        DOM.Document doc = new DOM.Document();
        dom.XmlNode items
            = doc.createRootElement('items', null, null);
        //フィールド名の配列   
        String[] fnms = wfnms.split(',');
        for(Item__c itm:itms){
                dom.XmlNode itemNode
                = items.addChildElement('item', null, null);
                for(String fnm:fnms){
                        dom.XmlNode fldNode
                        = itemNode.addChildElement(fnm, null, null);
                        fldNode.addTextNode(itm.get(fnm) + '');
                }      
        }
        system.debug(doc.toXmlString());
        return doc.toXmlString();
    }
   

        //SOQL発行用にSObjectのカスタムフィールドのカンマ区切りのリストを作る
        public static String getCustomFieldsNames(Schema.SObjecttype tp){
                String res = '';
                Map<String,Schema.sObjectField> fmap = tp.getDescribe().fields.getMap();
                for(String fn:fmap.keySet()){
                        if(fn.endswith('__c')){
                                res = res + fn + ',';
                        }
                }
                return res.substring(0,res.length()-1);
        }

}

3.解説
Visualforceページではapex:outputTextを使用して{!xml}でgetXmlの戻りを出しているだけです。ただしescape="false"を指定します。apex:page タグではヘッダー、サイドバーを無効にして、contentType="text/xml"を指定するところがミソです。
コントローラークラスではリクエストパラメーターからowneridを取り、DBをその条件で読んで、DOM.Documentおよびdom.XmlNodeを使用してXMLに組み上げます。今回は当社の研修で使用したItem__cというカスタムオブジェクトを使用しています。
なお、getCustomFieldsNamesメソッド以下は、任意のカスタムオブジェクトのカスタムフィールド名を入手するために当社で使用しているユーティリティメソッドですので本題と特に関係はありません。
いかかでしょうか?

P.S.getCustomFieldsNamesメソッドに関しては、別記事に書きました。

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * -
     ネットのパワーを不動産業へ生かす!
     不動産業向け顧客管理・営業支援システム  顧きゃく録!
- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *

ブログ「顧きゃく録」技術ノート リニューアル スタート

ブログ「ケーピーの技術ノート」を改め、(株)ケーピーエスの「顧きゃく録」チームの技術ブログとして、再スタートすることにしました。
今後は、主にForce.comの技術情報を中心として掲載していきます。
ケーピーエスで毎月開催している「Force.com研修」での内容や経験を生かしていければと思っております。

よろしく!

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * -
     ネットのパワーを不動産業へ生かす!
     不動産業向け顧客管理・営業支援システム  顧きゃく録!
- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *

« 2009年8月 | トップページ | 2011年6月 »