読者です 読者をやめる 読者になる 読者になる

crmprogrammer38の日記

プログラマのメモ

【Salesforce】カスタムボタンで、WebServiceの戻り値をMsxml2.XMLHTTPでsendする際にはまったこと

カスタムボタンで、Msxml2.XMLHTTPを使って別のシステムと接続する際にはまったことのメモになります。

 

やりたかったことは、

1.カスタムボタンで、文字列を戻すカスタムWebServiceをコール。

2.戻り値で取得した文字列を、Msxml2.XMLHTTPのsendで別システムに送信。

3.送信した結果を画面表示(alert)。

です。

 

どこではまったかというと2のsendです。

なぜだかうまくいかなくて、取得した戻り値をtoString()して文字列にしたらsendできました。

//カスタムWebService
global class SampleWS {
    webservice static String sample1(String param){
        return 'returnvalue:' + param;
    }
}
//カスタムボタンの処理
{!requireScript('/soap/ajax/39.0/connection.js')}
{!requireScript('/soap/ajax/39.0/apex.js')}

var ret = sforce.apex.execute('SampleWS' , 'sample1' , { param : 'パラメータ' } );

var xmlhttp = new ActiveXObject ("Msxml2.XMLHTTP");
xmlhttp.open('POST', 'http://xxxx.xxxx/xxxx/xxxx', false);
xmlhttp.send(ret); ← これだとエラー
xmlhttp.send(ret.toString()); ← これだとうまくいく

理由は明確にはわかりませんでしたがtoString()で動いたのでOKとしました。

 

【Salesforce】コマンドラインのメタデータのエクスポートツールを作りました。

メタデータを使うシーンは多い

1.開発時のバックアップ

2.デプロイ用のモジュール

3.モジュールリリース後の確認

 

上記のような場面で何かと必要となるメタデータですが、「Force.com IDE」や「Force.com Migration Tool」のメタデータダウンロード機能だと、少し使いづらさを感じてツールを作成しました。

下記からダウンロードできます。ソースコードも同梱していますので、気になる方はユーザ、パスワードを悪用したりしていないことを確認いただければと思います。

 

メタデータダウンロードツールはこちら

 

 ツールの概要は次です。

・Java8を利用します。ツールには含めていないので別途インストールが必要です。

・動作確認はWindows7, 8 ,10で実施しています。

 

ツールの使い方はWindowsのバッチファイル「metadatabakup.bat」を編集し、次の環境変数に値を指定後、metadatabakup.bat」を起動してください。

f:id:crmprogrammer38:20170515111120p:plain

環境変数
backup.dir

メタデータファイルを保存するパスのルート。

このパスの下に、「metadata_yyyyMMddHHmmss」のフォルダをツール側で作成してメタデータを出力します。

metadata.include

取得するメタデータを限定する場合に指定します。(任意)

「ApexClass, ApexComponent, ApexPage, ApexTrigger, CustomObject, ・・・」などメタデータAPIのlistMetadataで取得できるタイプで指定してください。「CustomField」など、listMetadataで取得できないタイプは指定できません。

下記のmetadata.excludeと一緒には指定できません。

metadata.exclude

除外するメタデータを限定する場合に指定します。(任意)

指定方法は、上記のmetadata.includeと同様です。

上記のmetadata.includeと一緒には指定できません。

sfdc.url

APIのログインURLを指定します。

https://test.salesforce.com/services/Soap/u/39.0

https://login.salesforce.com/services/Soap/u/39.0

になります。他には、インスタンスURLを直接指定する、マイドメインURLを直接指定するなどでしょうか。

sfdc.username ログインユーザ名
sfdc.password ログインパスワード。セキュリティトークンを使用する場合は、パスワード+セキュリティトークンの文字列となります。
http.proxyHost プロキシホスト。通信時にプロキシを使用する場合指定します。
http.proxyPort プロキシポート番号。通信時にプロキシを使用する場合指定します。
http.proxyUser プロキシユーザ。プロキシでユーザ認証する場合指定します。
http.proxyPassword プロキシパスワード。プロキシでユーザ認証する場合指定します。
http.auth.ntlm.domain NTLMドメイン。ntlmドメインを使用する場合指定します。
JAVACMD

javaのコマンドを指定します。javaは8以上のバージョンが必要です。

パスが通っていれば「java」のままで、パスが通っていない場合フルパスでjavaコマンドまでを指定します。

 

後書き

「Force.com IDE」だと、メタデータの量が多いと、メタデータの選択時点で時間がかかってしまうのと、そもそもGUIのため自動化できません。
「Force.com Migration Tool」は、package.xmlを書くのがハードルが高かったという経緯があり、このツールを作ってみました。
ソースコードもつけてますので、自由に改造して使ってもらえるとうれしいです。

【Salesforce】Java8のバージョンを上げたらSalesforceIDEで通信エラーが発生した件

Salesforceへproxy host, proxy port, prox yuser, proxy passwordを指定して接続しています。
Java8のバージョンを上げたら次の接続エラーが出たので回避策のメモです。

 

[接続エラー内容]
Unable to tunnel through proxy. Proxy returns "HTTP/1.1 407 authenticationrequired"

f:id:crmprogrammer38:20170515103008p:plain

 

 

[回避策]
eclipse.iniに次のシステムプロパティを追加
「-Djdk.http.auth.tunneling.disabledSchemes=」

 

例 

-startup
plugins/org.eclipse.equinox.launcher_1.3.0.v20140415-2008.jar
--launcher.library
plugins/org.eclipse.equinox.launcher.win32.win32.x86_1.1.200.v20150204-1316
-product
org.eclipse.epp.package.java.product
--launcher.defaultAction
openFile
--launcher.XXMaxPermSize
256M
-showsplash
org.eclipse.platform
--launcher.XXMaxPermSize
256m
--launcher.defaultAction
openFile
--launcher.appendVmargs
-vm
javaw.exe
-vmargs
-Dosgi.requiredJavaVersion=1.8
-Xms40m
-Xmx512m
-Djdk.http.auth.tunneling.disabledSchemes=

[経緯]
Java 8 Update 111 (8u111)から、HTTPSトンネリングのBasic認証の無効化がされておりそれが原因でした。
上の回避策のプロパティを指定することで無効化が解除されるようです。
(なので、Java 8 でも Update110まではこのエラーになりません)

プロキシ経由、その際にユーザとパスワードが必要な環境で発生する可能性があります。

 

[所感]
tls1.0無効化でjava8に上げたら、上げたら上げたで別の通信の問題が待ってるという現実でした。通信周りはほんとに色々な問題がありますね。。

【Salesforce】ガバナ制限対策 static変数を使ってSOQLの回数を減らそう

Salesforceでは、1つのApex トランザクションでのSOQLの回数制限があります。

Salesforceのstatic変数は、スコープがトランザクションであることを利用してSOQLの発行回数を次の方法で少なくできます。


1.設定情報のオブジェクトへのSOQLを一度で済ます。
2.同じ検索条件の場合に、マスタデータの検索は一度で済ます。


具体的には次です。

1.設定情報のオブジェクトへのSOQLを一度で済ます。

設定情報のオブジェクトはいくつかありますが、次をピックアップします。
(1)レコードタイプ(RecordType)
(2)公開グループやロール、キュー(Group)
(3)現在のユーザ情報(User)


(1)レコードタイプへのSOQLを1度で済ます場合は次のようになります。

public class RecordTypeUtils {

  private static Map<String, RecordType> recTypeMap = new Map<String, RecordType>();
  
  static {
    List<RecordType> rectypelist = [  Select
                   Id
                  ,Name
                  ,DeveloperName
                  ,SobjectType
                  From
                   RecordType
                  where IsActive = true ];
                  
    for( RecordType rectype : rectypelist ){
      String key = rectype.SobjectType + ':' + rectype.DeveloperName;
      
      recTypeMap.put( key, rectype );
    }
  }
  
  public static RecordType getRecordType(String sobjectType , String developerName ){
  
    return recTypeMap.get( sobjectType + ':' + developerName );
  }
}

 使う時は次のようにします。何度使ってもRecordTypeへのSOQLは1度です。

RecordType rectype = RecordTypeUtils.getRecordType('SampleObject__c' , 'SampleRecordType');

 レコードタイプIdをセットしたい時に利用できます。

 

 (2)公開グループやロール、キューへのSOQLを1度で済ます場合は次のようになります。

public class GroupUtils {

  private static Map<String, Group> groupMap = new Map<String, Group>();
  
  static {
    List<Group> grouplist = [  
                  Select
                     Id
                    ,Name
                    ,DeveloperName
                    ,RelatedId
                    ,Type
                  From
                     Group
                ];
                  
    for( Group grp : grouplist ){
      String key = grp.Type + ':' + grp.DeveloperName;
      
      groupMap.put( key, grp );
    }
  }
  
  public static Group getGroup(String type , String developerName ){
  
    return groupMap.get( type + ':' + developerName );
  }
}

 使う時は次のようにします。何度使ってもGroupへのSOQLは1度です。

Group grp = GroupUtils.getGroup('Queue','SampleQueue');

所有者項目に特定のキューをセットしたい時に利用できます。

 

(3) 現在のユーザ情報へのSOQLを1度で済ます場合は次のようになります。

public class CurrentUserUtils {

  @TestVisible
  private static User curuser;

  public static User getCurrentUser(){
  
    if( curuser == null ){
      curuser = [Select
             Id
            ,Name
            ,UserRole.Name
            ,Profile.Name
            ,Contact.Name
            ,Contact.Account.Name
            ,Manager.Name
            ,Manager.email
          From
             User
          Where Id =:Userinfo.getUserId()];
    }
  
    return curuser;
  }
}

 使う時は次のようにします。何度使用してもUser(現在のユーザ)へのSOQLは1度です。

User currentUser = CurrentUserUtils.getCurrentUser();

 現在のユーザ情報を取得する際に利用できます。Apex内のstatic変数「curuser」にTestVisibleアノテーションをつけた意味は、テストクラスの中でユーザを切り替えてテストする際に、ユーザ切り替え時にnullでクリアできるようにしています。

 

2.同じ検索条件の場合に、マスタデータの検索は一度で済ます。

検索条件とその結果をstatic変数に保持するようにします。

単純なIdでの検索の場合、次のようになります。

public class SOQLResultUtils  {

  private static Map<String, List<Account>> accCache = new Map<String, List<Account>>();
  
  public static List<Account> getAccount(Set<Id> accIdSet){
  
    if( accIdSet == null ){
      accIdSet = new Set<Id>();
    }
  
    String srchstr = String.valueOf( accIdSet );
  
     if( accCache.keySet().contains( srchstr ) ){
       return accCache.get(srchstr );
     } else {
     
      List<Account> result = [Select
                   Id
                  ,Type
                  ,Phone
                  ,Fax
                  ,Industry
                  From
                   Account
                 ] ;
       accCache.put( srchstr , result );
       return result;
     }
  }
}

Contactのトリガで使った時には次のようになります。検索するIdのSetが同じであれば、SOQLは一度ですみます。

Set<Id> accset = new Set<Id>();
for( Contact c : Trigger.New )
if(c.AccountId != null) accset.add(c.AccountId);
}
List<Account> acclist =
SOQLResultUtils.getAccount(accset);

 同じトリガが何回か動いてしまった場合でも、SOQLの回数は増えません。

 

最後に

トリガのforループの中でSOQLを発行している場合のSOQLの回数制限のエラーはわかりやすく対処しやすいと思います。

 

でも、実際は、そんな簡単なエラーではなく、1つの画面に仕様が集中していたり、1つのオブジェクトにトリガが集中していたりで、SOQLの回数がつみあがった結果、SOQLの回数制限のエラーが発生するケースが多いと思います。

性質が悪いのが、SOQLの回数制限のエラーはある程度開発が進んだ後に発覚するため大きくプログラムを改修すると後退テストの影響範囲が大きくなってしまいます。

 

そんな時に上記のやり方なら、プログラムの改修範囲は大きくないのと、同じようなSOQLなら1つにまとめてしまうことで、さらにSOQLの回数を減らせます。

 

【Salesforce】apexのstatic変数と、javaのstatic変数の違いでとまどったこと

私はjavaのプログラムを書くことが多いのですが、javaのstatic変数とapexのstatic変数の違いにとまどいました。

 

javaのstatic変数は、Classで保持する変数で、例えばWebアプリケーションであれば、そのWebアプリケーションの中で1つの値を保持できて、複数のユーザで1つの値を共有できます。

※厳密にはClassloader別ですが、いったんおいておきます。

 

ですが、apexのstatic変数は、トランザクションを開始してから終了するまでが変数のスコープで、トランザクション単位に管理されるため、ユーザ毎の値になります。

 

以下、原文

A static variable is static only within the scope of the Apex transaction. It’s not static across the server or the entire organization. The value of a static variable persists within the context of a single transaction and is reset across transaction boundaries. For example, if an Apex DML request causes a trigger to fire multiple times, the static variables persist across these trigger invocations.

 

 

【Salesforce】メモ & 添付ファイルの非公開のチェックボックスは本人に限定する場合に便利

メモ & 添付ファイルには、非公開のチェックがあります。

f:id:crmprogrammer38:20170414123249p:plain

この非公開にチェックを入れた後には、プロファイル>システム管理者権限の

・すべてのデータの参照

・すべてのデータの編集

の権限を持つ人は参照できますが、それを除くと、本人以外は見えなくなります。

共有ルールや階層を使用したアクセス制御とは無関係にこの制御がかかります。

 

本人というのは、所有者に指定した人になるので、システム管理者や、Apexプログラムなどからその人にだけ見せたいメモや、添付ファイルを作成することができます。

 

確実にその人だけが参照できる情報を登録する際には便利です。

 

下が、メモ & 添付ファイルをApexやAPIで使用する際の項目になります。

 

メモ

API名:Note

API ラベル タイプ
ParentId 参照先 ID reference
Title タイトル string
IsPrivate 非公開 boolean
Body 内容 textarea
OwnerId 所有者 ID reference

 

添付ファイル

API名:Attachment

API ラベル タイプ
ParentId 参照先 ID reference
Name ファイル名 string
IsPrivate 非公開 boolean
ContentType コンテンツタイプ string
Body 本文 base64
OwnerId 所有者 ID reference
Description 説明 textarea

【Salesforce】ページレイアウトの関連リストのレコードに条件をつける。

Salesforceの標準のページレイアウトの関連リストは、参照可能なレコード全て表示されますが、条件に該当するレコードだけ表示したい時があります。

 

例えば、その顧客の仕掛中の商談だけ表示する、または、現在対応中のケースだけ表示するなどの場合だったり、レコードは論理削除する設計としたが、関連リストには論理削除したレコードは表示したくないなどがあると思います。

 

現時点(APIバージョン39)では、ページレイアウトの関連リストに、条件を設定することはできないので次のように工夫することで対応できます。

 

関連リストに条件をつける方法

[手順概要]

参照関係、(もしくは主従関係)の項目に対応する参照関係項目をもう1つ用意します。

そして、表示する条件に合致した時に、トリガで用意した項目へ元の参照関係、および主従関係の項目の値をセットし、条件に合致しない時はnullをセットする処理を行います。

関連リストには用意した参照関係項目を使って表示します。

 

[手順詳細]

 例として、取引先と商談を使います。Salesforceの標準項目として、商談には、「取引先ID」があり、取引先への参照関係項目があります。

 

手順1.商談に、取引先への参照関係項目を作成します。仮に「取引先 ID(完了したものは除く)」項目とします。

 

手順2.商談にApexトリガを作成し、新規作成、更新時に、フェーズが"Closed Won" , "Closed Lost"以外のときに、「取引先ID」の値を、「取引先 ID(完了したものは除く)」にセットし、"Closed Won" , "Closed Lost"の時は、「取引先 ID(完了したものは除く)」にnullをセットします。

 

手順3.取引先のページレイアウトの関連リストに、取引先 ID(完了したものは除く)」の関連リストを表示します。(関連リストの表示ラベルを「商談 (完了したものは除く)」にします)

 

そうすると、次のように完了したもの以外で関連リストを表示できます。

(商談の関連リストは完了を含んでいて、商談(完了したものは除く)の関連リストは完了は除いています)

f:id:crmprogrammer38:20170405145126p:plain