Quantcast
Channel: Salesforce –ぱーくん plus idea
Viewing all 62 articles
Browse latest View live

SalesforceのVisualforce Emailテンプレートで、コンポーネントを使用しpicklistの翻訳を取得する

$
0
0

Visualforce形式のEmailテンプレートでは、通常のEmailテンプレートで出来ないような複雑なことも出来ますが、(グローバル)ピックリストはコード値で定義しておき、その表示値を翻訳している場合に、 そのピックリストの翻訳値をメールの本文に埋め込むのは、「コンポーネント」を使う必要がありました。

コンポーネントを使うと、さらに複雑なビジネスロジックをコンポーネントで使用するクラスに持たせることができるので、SOQL文を使ったレコードの取得や加工なども行うことができます。 特に、翻訳されたラベルを取得するのは、SOQL文でtoLabel()関数を使う方法が簡単なので、コンポーネント内でSOQL文を発行してラベルを取得した結果を返却するメソッドを定義しておけば、表題のようなことができます。

そのやり方のメモです。

 

Apexクラスを定義

初めに、コンポーネントで使用するApexクラスを定義します。

この中では、コンポーネントを通してEmailテンプレートと値をやり取りするための変数「myId」を定義し、setter/getterを定義します。

また、「myId」を使って何らかのロジックを行う関数を定義しますが、それを変数「labelvalue」で返すために、関数名をのget<変数名>(この例ではgetLabelvalue)とします。

public class My_TemplateCtrl {
    public Id myId {get;set;}
    public String getLabelvalue() {
        T_Shiryo_Seikyu__c ss = [select Id, toLabel(Shiryo_Todofuken__c) FROM T_Shiryo_Seikyu__c where Id =: shiryoseikyuId ];
        return ss.Shiryo_Todofuken__c;
    }
}

 

コンポーネントを定義

次に、上記のApexクラスを使ったコンポーネントを定義します。 コンポーネント名は、「My_Template_Component」としました。後程、Emailテンプレート内で、この名前でコンポーネントを使用します。

このコンポーネントで定義しているのは、外部に公開するプロパティ「passedId」で、それを、My_TemplateCtrlの変数「myId」にアサイン(assign to)する宣言を行います。

<apex:component controller="My_TemplateCtrl" access="global">
    <apex:attribute name="passedId" assignTo="{!myId}" type="Id" description="my id for variable pass" />
    {!labelvalue}
</apex:component>

 

コンポーネントをEmailテンプレート内で使用

上記2つを定義すれば、それをEmailテンプレート内で使用できるようになります。定義したコンポーネントを使用する場合は、下記のように />のように記述しましょう。

なお、下記の例では、公開されたプロパティ「passedId」に「{!relatedTo.Id}」を渡しています。(※relatedToについては下に解説)

 <c:My_Template_Component passedId="{!relatedTo.Id}" />

なお、上記のrelatedToですが、テンプレートの最初に以下のような定義をしている場合は、カスタムオブジェクト「My_Custom__c」ということになりますね。


SalesforceのProcessBuilderで前後の値の違いを判定して値更新する

$
0
0

ProcessBuilderは、カスタムオブジェクトの値に一定の変更があった場合に、ステータス更新を行うなど、値の自動更新と同じようなことがGUIの画面で設定できるため、Salesforceの中でも非常に便利な機能だと思います。

その中で、「ある項目の値が○○から▲▲に変わった場合に、そのオブジェクトのステータス項目を更新する」みたいなことを行う場合は、変更前の値と変更後の値を取得しなければいけませんが、「変更前の値」って、どうとればいいんだろう?と思って、試行錯誤した結果をメモしておきます。

 

ProcessBuilderで値が変わったことを判断するには?

本題に入る前に、よく使うTipsとして、「値が変わったことを判断のトリガーとして、ステータス更新する」場合にはどうするかですが、下記のようにします。

更新

 

アクションの実行条件を「式の評価がtrueになる」に設定して、以下のような数式を用います。

AND( ISCHANGED([Account].Mail_Send_Flg__c) ,[Account].Mail_Send_Flg__c ==true)

ISCHANGEDが、値が変更されたかどうかを判定する数式になるので、値の変更があり、項目値が「true」になった場合にアクションが実行されることになるというわけです。

 

ProcessBuilderで変更前の値を取得するには?

上記の例では、変更前の値は何でもOkでした。とにかく、前後で値が変更されたら、アクションが実行されるので、変更履歴などを残すのに便利な条件式です。

では、前の値が、「02」で、それから「01」に変わった場合にのみメールを送りたいような条件はどうすればいいでしょうか。

これを実現するためには、以下の数式を使えます。Accoutに、ピックリストの項目「Update_Status__c」があると仮定しましょう。

AND( ISPICKVAL(PRIORVALUE([Account].Update_Status__c), ’02’) ,ISPICKVAL([Account].Update_Status__c, ’01’))

PRIORVALUEが、変更前の値を取得できる数式になるので、それを用いて「その値が02だったら」という条件が書けます。

ちなみに、ピックリスト項目の項目値の判定は、「==」ではなく「ISPICKVAL」を用いないと、「数式は無効です: 項目取引先責任者は選択リスト項目です。選択リスト項目は、特定の関数でのみサポートされます。」というエラーになってしまいます。

SalesforceのTestケースでメール送信がされたか確認する

$
0
0

以前、Salesforceで、メール送信がちゃんと行われたかどうかを確認する方法をご紹介しました。

運用時は、実際の送信結果をそれで確認できるのですが、テストの場合は、どうでしょうか。

「@isTest」を用いたテスト時は、実際にはメールが送信されません。そうなると、トリガーによるメール送信などをワークフローやプロセスビルダーで記述していると、確認の方法がないので困ってしまいますね。

そんな場合は、デバッグログで確認することができます。

ワークフロー内で条件によってメールを送信する/しないを分けている場合は、メールを送信した場合、以下のような「WF_EMAIL_SENT」を含んだログが追加されています。

11:07:36.144 |WF_RULE_EVAL_BEGIN|ワークフロー
11:07:36.144 |WF_CRITERIA_BEGIN|[]|<ルール名>xx1p0000000Axxx|xxQp00000001xxx|ON_ALL_CHANGES|0
11:07:36.144 |WF_FORMULA|Formula:ENCODED:[treatNullAsNull]true|Values:
11:07:36.144 |WF_CRITERIA_END|true
11:07:36.144 |WF_SPOOL_ACTION_BEGIN|ワークフロー
11:07:36.144 |WF_ACTION| フロートリガ: 1;
11:07:36.144 |WF_RULE_EVAL_END
11:07:36.144 |WF_FLOW_ACTION_BEGIN|xxLp00000004xxx
11:07:36.879 |FLOW_CREATE_INTERVIEW_BEGIN|x0Dp0000000Cxxx|x00p00000000xxx|x01p0000000Axxx
11:07:36.879 |FLOW_CREATE_INTERVIEW_END|x819a37b3a4e6f5dc3a2a23031815a351729c3-xxxx|メール配信
11:07:36.880 |FLOW_START_INTERVIEWS_BEGIN|1
11:07:36.880 |FLOW_START_INTERVIEW_BEGIN|x819a37b3a4e6f5dc3a2a23031815a351729c3-xxxx|メール配信
11:07:36.880 |FLOW_START_INTERVIEW_END|x819a37b3a4e6f5dc3a2a23031815a351729c3-xxxx|メール配信
11:07:36.880 |WF_EMAIL_SENT|Reference:.<メールアラート名>|Recipients:<メール送信先> |CcEmails: 
11:07:36.880 |FLOW_START_INTERVIEWS_END|1
11:07:36.144 |WF_FLOW_ACTION_END|x9Lp0000000xxxxx
11:07:36.144 |WF_ACTIONS_END| フロートリガ: 1;

逆に、メール送信がされていない場合は、この行は表示されていません。

【2017年最新版】Force.com IDEプラグイン(Eclipse)のセットアップの方法と使い方

$
0
0

最新のForce.com IDE Plug-Inのインストール方法をメモしておきます。

以前書いたときは7年前で、情報も古くなっているので。。

基本的には
https://developer.salesforce.com/docs/atlas.en-us.eclipse.meta/eclipse/ide_install.htm
の情報を参照するのが良いでしょう。

比較的新しいSalesforceAPIのバージョン(Ver36以上)でIDEを使うには、JDK8とEclipseのバージョン4.5(Mars)以上が必要です。

今回私は、Eclipseの4.6(Neon)上で環境の構築を行いました。

 

Eclipseの用意とプラグインのインストール手順

まずは、日本語版のEclipse4.6をダウンロードして解凍し、起動します。

open

 

その後、上部メニューで、「ヘルプ」→「新規ソフトウェアのインストール」をクリックすると「リポジトリーの追加」ダイアログが開きますので、

open

 

名前 Force.com IDE など適当なもの
ロケーション https://developer.salesforce.com/media/force-ide/eclipse45

を入力して「OK」を押します。

このURLはSpring’16 (Force.com IDE v36.0)のプラグインのリポジトリ・ロケーションです。

それ以前のバージョンの場合は、「http://media.developerforce.com/force-ide/eclipse42」を指定します。

後は「次へ」を押していき、ライセンスに同意すれば、インストールが完了します。

最後に再起動を求められると思いますので、再起動してインストールプロセスの完了です。

 

接続できないエラーで失敗する場合 ↓ ↓ ↓

※途中でエラーが発生する場合は、プロキシーの設定が有効になっているかを確認してみましょう。 設定の方法は、この記事(Eclipseを利用して、SalesForceの開発を行う②(セットアップ後につまづきやすいポイント))の「① Eclipseに設定しているproxyが効いていないようだ」を参考にしてください。

 

使い方の第一歩

簡単に使い方をご紹介しておきましょう。

通常のプロジェクトではなく、「新規」→「その他」から、下記のように「Force.com」→「Force.com Project」を選択します。

open

 

すると、どのSalesforce組織に接続するのかのダイアログが表示されます。ここで、プロジェクト名とユーザ名とパスワードを入力します。最後に、「本番/Sandbox」を選択して、どちらのURLに接続するのかを指定します。

open

 

最後に選択するのは、どのソース(コンポーネント)をプロジェクトとして管理するのかを取捨選択するダイアログです。Salesforceで開発できる対象はほとんどここで選択できますが、あまりにたくさん選択しすぎるとプロジェクト自体が重くなってしまうので、一番上を選択することが多いです。

open

 

以上で、最初のFroce.comプロジェクトを作成することができました。後は、いろいろと使っていじってみることをお勧めします。

複雑なSalesforceの入力チェックをApexトリガで行う方法とサンプル

$
0
0

Salesforceの入力チェックは、複数項目の相関チェックや正規表現など、ある程度までなら入力規則でプログラムレスにできます。

しかし、それよりも複雑な場合、たとえば、チェックの際に、関連するレコードの状態を判断するためにSOQL文を発行しないといけないとか、外部のロジックをWebAPIで呼び出すなどは、入力規則だけでは対処できないため、Apexコードを書く必要がありますね。

具体的な手順は、以下の通りです。

 

APexトリガで入力チェックを行う手順

入力チェックを行うオブジェクトにトリガを定義

入力チェックを行うのは、多くの場合、オブジェクトを保存する直前になりますから、Apexトリガを作成して、insertやupdateの時に、

入力された値をチェックすればよいですよね。

そのため、対象となるオブジェクトにトリガを作成します。

trigger SampleTrigger on Account (after delete, after insert, after undelete,  after update, before delete, before insert, before update) {
    
    if(Trigger.isInsert || Trigger.isUpdate){
        if(Trigger.isBefore){
            SampleTriggerHandler.checkInputVariables(trigger.new, trigger.OldMap);
        
        }
    
    }
    
    
}

 

トリガ内または呼ばれるクラス内で、入力チェックを行う

トリガを定義したら、そのクラスか、そのクラスから呼ばれるクラスで入力のチェックを行います。

ここでのサンプルは、トリガ・ハンドラという別のクラスを呼び出しています。

public class SampleTriggerHandler {
    
    public static void checkInputVariables(List<Account> triggerNew, Map<Id, Account> triggerOldMap){
        System.debug('***checkInputVariables Called***');
        
        for(Account newRecord : triggerNew){

      //InsertとUpdateのケース
	    if(triggerOldMap==null){
                //insert時
                System.debug('***checkInputVariables insert時***');                
            }else{
                Account oldRecord = triggerOldMap.get(newRecord.Id);
                //update時
                System.debug('***checkInputVariables update時***');        
            }
        
            
            //項目チェック
            Integer singleErrorCnt = 0;

            if(String.isEmpty(newRecord.Language__c)){
                singleErrorCnt++;
                newRecord.Language__c.addError('言語は必須です。');
            }
                       
            //デフォルト値セット
            if(newRecord.Country__c==null){
                newRecord.Country__c = 'Japan';
            }
            
            //正規表現でチェック
            Boolean regTest= Pattern.matches('^([0-1][0-9]|[2][0-3]):[0-5][0-9]$', newRecord.Jikan__c);
            if(!regTest){
                singleErrorCnt++;
                newRecord.Jikan__c.addError('時間は「HH:MM」の形式で入力してください。');
            } 

            if(singleErrorCnt>0){
                return;
            }           
            
            //マスタ1(Master1__c)からSOQLで検索してデフォルト値をセット
            if(newRecord.Some__c!=null){
                Master1__c ms1 = [select Id, Name, Master1_Name__c from Master1__c where Id =:newRecord.Some__c limit 1];
                newRecord.Some2__c  = ms1.Name;
                newRecord.Some3__c  = ms1.Master1_Name__c;
            }
      
        }
    }
}

少し、無駄なソースも入っていますが、なるべく汎用的に、コピペで使えるようにと思ってのことです。

解説すると、

解説すると、はじめに「InsertとUpdateのケース」があります。
トリガでは、OldとNewの2つのリストが渡ってきます。リスト形式とMap形式が使えますが、私はよくNewをリストで、OldをMap形式で渡します。
それは、Newをリストの拡張For文のループで回し、それに対応するOldの値をMapでNewのIdをキーに取得するやり方が簡単だからです。
で、Oldがnullであれば、新規作成時(Insert)だと判断できます。
「Salesforceのトリガでのoldとnewの使い方」も参考に。

また、それ以降は入力チェックです。
Newに入っている値が、画面から入力された値なので、それに対して、チェックを行っています。
また、必要であれば、値を上書きしてあげると「デフォルト値セット」などが出来ます。

エラーメッセージのセットの仕方
入力チェックエラーの場合、メッセージを返す必要がありますよね。
その場合には、「newRecord.Language__c.addError(‘言語は必須です。’);」のように、項目に対してメッセージをセットします。
その後、returnすれば、レコードは保存されずに、入力画面に戻り、セットしたエラーメッセージが表示されるというわけです。

Visualforce(メールテンプレート)で数値や日時項目の表示フォーマットを指定する方法(日本時間時差修正含む)

$
0
0

Visualforce形式のメールテンプレートを作成しているときに、いくつか表示のフォーマットで苦労したので、メモしておこうと思います。

特に、差込項目としてそのままだと、なかなか思ったように表示させることができませんでした。

 

日時(Datetime)項目が欧米式のフォーマットで表示されてしまう

日時(Datetime)項目をそのまま表示すると、「Sun Jan 01 05:51:00 GMT 2017」のようにGMTで、しかも欧米式のフォーマットで表示されてしまいます。

「2017/01/01 14:51:22」のように表示するにはどうしたらいいでしょう?

Visualforceでは、「outputtext」を使うと、フォーマットを指定できます。

(※下記では、ColDatetime__cが日時項目だとします)

さらに、時間の加算も出来るので、標準時+9時間だと、(9/24)で日本時間にすることができます。

<!-- 日本時間での日時表示 -->
<apex:outputtext value="{0, date, yyyy/MM/dd HH:mm:ss}">
    <apex:param value="{!relatedTo.ColDatetime__c+(9/24)}"></apex:param>
</apex:outputtext>

 

整数の数値項目が小数点付きで表示されてしまう

数値項目は、「5.0」のように、小数点付きで表示されてしまいます。

オブジェクトの項目の定義で、小数点以下の桁数を0桁に変更しても変わりません。

そのような場合は、

<!-- 数値のカンマ区切り -->
<apex:outputtext value="{0, number, ###,###}">
	<apex:param value="{!relatedTo.Number__c}"></apex:param>
</apex:outputtext>

<!-- 数値の0埋め表示 -->
<apex:outputtext value="{0, number, 000000}">
	<apex:param value="{!relatedTo.Number__c}"></apex:param>
</apex:outputtext>

 

なお、もっと複雑なことをしたい場合は、コンポーネントを使うと簡単に実装できます。

やり方は「SalesforceのVisualforce Emailテンプレートで、コンポーネントを使用しpicklistの翻訳を取得する」を参考にしてください。

Visualforce Emailテンプレートで使用できる関数と、カスタム・オブジェクトの詳細画面URLを差込む方法

$
0
0

Salesfroceからユーザにメールを送る場合に、よく要望に上がるのが、「オブジェクトの詳細画面のURLをメールに差し込んで欲しい」というものです。

たとえば、あるオブジェクトに更新があった場合に、ユーザにメールを送るとして、そのメールに変更のあったオブジェクトへのリンクがあれば、そのリンクをクリックすれば、すぐに詳細画面で確認したり、編集したりすることができますよね。

(詳細画面を見るには、もちろんSalesforceへログインすることが前提です。)

どんなURLを埋め込めばいいのか

良く知られているように、Salesforceでは、あるオブジェクトのURLは、

「https://ns31.salesforce.com/オブジェクトId」

のように表現されます。

オブジェクトIdの部分がオブジェクトごとに異なるのは当然ですが、「ns31.salesforce.com」の部分も割り当てられたSFDCインスタンスによって異なり、場合によっては本番環境とSandbox環境でも異なります。

そのため、直にインスタンス名を書いてしまうと、環境が変わったときに、クリックしても表示できないURLになってしまいますから、動的にホスト名(インスタンス名)を取得しないといけません。

そのためには、VisualforceページやVisualforceEmailテンプレートで使用できる「グローバル変数」を使うことができます。

「グローバル変数」は、環境に依存する組織のデータが格納されている変数で、API呼び出しのホスト名や組織の言語など、様々な値を取得できます。

オブジェクトの詳細画面のURLを差し込むには?

この場合、

{!(LEFT($Api.Enterprise_Server_URL_300, FIND('/services',$Api.Enterprise_Server_URL_300))+relatedTo.Id)}

のように「$Api」が使えます。

「{!$Api.Enterprise_Server_URL_300}」には「https://ns31.salesforce.com/services/Soap/c/30.0/00Dp0000000Cnjc」のようにAPIをコールするURLが入っていますので、LEFT関数で「/services」より左側を取ってくるわけです。

なお、Enterprise_Server_URL_XXXの300はAPI のバージョンを示します。たとえば、{!$Api.Enterprise_Server_URL_260} は、バージョン26.0のAPI のエンドポイントを表しています。

(SalesforceではAPIバージョンによって、エンドポイントが異なります。)

以下に、VisualforceページやVisualforceEmailテンプレートで使用できるグローバル変数と数式の演算子と関数のページを載せておきます。

使用できるグローバル変数

https://help.salesforce.com/articleView?id=dev_understanding_global_variables.htm&language=ja&type=0

使用できる数式の演算子と関数

https://help.salesforce.com/articleView?id=customize_functions.htm&type=0

Salesforceの主従関係に関する様々な制限事項(主従関係のリストにオブジェクトが出ず、作成できないときに)

$
0
0

Salesforceでは、オブジェクトとオブジェクトの関係性を定義でき、その種類には「参照関係」と「主従関係」があります。

主従関係を使うと、積み上げ集計を利用できたり、親のオブジェクトを消すと子のオブジェクトも消えるような、いわゆる「カスケード削除」のようなことも出来るので大変便利な反面、その適用にはいくつか制限があります。

いざ主従関係を設定しようと思ったら、その制限に引っかかって設定できず、設計変更を余儀なくされた。。ということがないよう、その制限をいくつかメモしておきます。

※自分がそういうことがあったので、備忘的な感じです。。。。

 

1つのオブジェクトで定義できる主従関係は2つまで

あるオブジェクトで定義できる主従関係の数は2つまでです。

それ以上は定義できません。

そのため、2つの主従関係を作成した時点で、

主従関係追加

 

上記のような表示になり、主従関係を追加で定義できなくなります。

また、あるオブジェクトの親になっているオブジェクトは、「ひとつだけ」親の主従関係を定義できます。

まだひとつしか主従関係の項目を作っていないのに作成できない場合は、そのオブジェクトが他のオブジェクトの親になっていないかを確認してください。

 

主従関係を作れないオブジェクトもある

リードとユーザオブジェクトは他のオブジェクトの親になれません。つまり、この2つのオブジェクトを親にした主従関係は作成できません。

リードで積み上げ集計できないことになりますね。。

リードはSalesforceでは顧客になる前の一時的なレコードで、「取引の開始」でコンバートされてしまうので、あまり情報を蓄積しないことになっているんでしょうか。。。?

 

「参照関係」を途中で「主従関係」に変換できるが、条件がある

はじめ「参照関係」で項目を作成して、途中で「主従関係」に変更したくなることがあるかもしれません。

その場合、変更するには条件があります。

 

・そのオブジェクトにレコードがないこと

 あんまり無いと思いますが、そのオブジェクトのレコードが1件もない場合にはすんなり追加できます。

 

・そのオブジェクトの変更しようとしている参照項目に全部値が入っていること

 主従関係の項目はNullであってはいけません。そのため、既存レコードの該当項目が空のレコードがあると、

「新規の主従関係の作成、または既存の参照関係の主従関係への変更はできません。既存のレコードがリレーションに違反する可能性があります。」

というエラーになって変更できません。

そのときにはまず、当該参照項目にすべて値を入れてから、それを主従関係にします

新規に主従関係を作りたいときは、はじめに参照関係で作成してから、値を埋めた後、主従関係に「データ型の変更」で変換しましょう。

主従関係変更

 


Salesforceでログインできなくなった時に確認すること

$
0
0

Salesfroceでログインできない、またはログインできなくなる理由は様々ですが、いくつか挙げておこうと思います。

 

①ログインIPが制限されている

Salesforceでは、ログインできる時間帯と、ログイン元のIPを制限することが出来ます。

特に、ログインIPアドレスの制限は私もよく設定する項目です。

他の記事「セキュリティトークンのリセットとパスフレーズの設定について」でもご紹介しましたが、ログインIPアドレスの制限として

「開始 IP アドレス」・・・0.0.0.0

「終了 IP アドレス」・・・255.255.255.255

を設定するとセキュリティトークンなしでもツールからログインできるので、開発時には重宝するテクニックです。

(本番ではこの設定は行わないことをお勧めしますが。)

login IP

 

 

②ドメインでlogin.salesforce.comのログインが禁止されている

私が以前はまったのがこのパターンです。

Salesforceでは「私のドメイン」といってドメインを独自に設定することが出来ますが、システム管理者であれば

ログインアドレスも変更することができます。

これは、ログイン画面のアドレスを独自のものにすることでブランドイメージを前面に出せる利点があり、同時に

login.salesforce.comからのログイン禁止することができます。

login IP domain

 

この設定がされていると、login.salesforce.comから普通にログインしようと思ってもログインエラーになります。

「パスワードが間違っているのかな?」と思ってパスワードを設定しなおしても、パスワードリセット・メールからログインしたときは入れるものの、一度ログアウトして再度入りなおそうとするとエラーになるので、少し原因が分かりにくいです。

 

原因を発見するには

もし、システム管理者ではログインできるか、②のようにパスワードリセットすれば入れる場合は、「ログイン履歴」を見ると、ログインできない原因が分かるかもしれません。

login IPreason

 

私の②の場合、ログイン履歴のエラー状況に、「制限されているドメイン」とあったので、ドメインに関する設定だと原因を絞り込むことができました。

セキュリティを考慮した、可逆的暗号化の相互運用の仕組みを実現する方法(Salesforce編)

$
0
0

システムのセキュリティについて考慮されることが多くなってきた昨今、SalesforceやJavaで構築するシステムでも、情報を暗号化して運用することが求められるようになっています。

特に、パスワードは暗号化して保持したり、さらにはインターネット上でやり取りする情報についても暗号化して送信し、受け取り側で復号することシーンもあることでしょう。

今回は、2回にわたって、JavaやSalesforceで可逆的な暗号化を実現する方法をご紹介します。

初回の今回は、Salesforceでの可逆暗号化のしくみ、特にApexコードでの実現方法を見てみることにします。

ゴールは、

  • あるキーワードを元に、PrivateKeyを生成する。
  • そのキーを元に、テキストを暗号化。
  • その暗号化されたテキストを別なところに送る。(PrivateKeyは別途送っておく)
  • 受け取った別なところで、PrivateKeyを元に、テキストを複合化。

 

ということにします。

ただし、PrivateKeyを送っておいても、Salesforce上に保持してApexで読み込むのは手間なので、生成したPrivateKeyをBase64でテキストにして別途送ることにします。

こうすることで、読み込み部分こコーディングがかなりシンプルになります。

さて、Apexで利用できる暗号化のクラスには「Crypto Class」というものがあります。

詳しいAPI仕様書は下記ページを確認してみてください。

https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_restful_crypto.htm

 

暗号化する

早速暗号化するコードを書いてみましょう。


Blob MyPrivateKey = Crypto.generateAesKey(128);
System.debug(MyPrivateKey.size());
System.assertEquals(16, MyPrivateKey.size());

String b64formattedKeyStr=EncodingUtil.base64Encode(MyPrivateKey);

system.debug('★This is b64formattedKeyStr= '+ b64formattedKeyStr);

Blob blobData = Blob.valueOf('sample Text');
Blob encryptedData = Crypto.encryptWithManagedIV('AES128',MyPrivateKey,blobData);
String EncryptedDataString = EncodingUtil.base64Encode(encryptedData);

system.debug('★This is encrypted data String= '+ EncryptedDataString);

解説すると、

Blob MyPrivateKey = Crypto.generateAesKey(128);
Blob型のPrivateKeyを作ります。SalesforceのCryptoで利用できる暗号化方式は、AES128,AES192,AES256ですが、引数が128なので、AES128で秘密鍵ができます。
これは、16Byteです。(このバイト数は後で出てきますが結構重要です。)
ただし、この鍵はgenerateAesKeyが呼ばれるたびに変わるので、ここで生成した鍵を保持しておく必要があります。
Blob型だと扱いにくいので、次のやり方でテキスト型にしてしまいます。

String b64formattedKeyStr=EncodingUtil.base64Encode(MyPrivateKey);
EncodingUtilのbase64Encodeメソッドで生成したPrivateKeyをBase64化します。これにより、テキストで簡単に鍵をやり取りできます。
このコードの下にログを仕込んでありますが、上記コードを動かすと、
xx:xx:xx:xxx USER_DEBUG [x]|DEBUG|★This is b64formattedKeyStr= XX71Fq***6p/99dWGfOpXX==
のようにログが吐かれると思います。
この「XX71Fq***6p/99dWGfOpXX==」がBase64化されたテキスト型の秘密鍵なので、これを別なシステムに送って、そこでBlob型へ逆Base64化すれば元の鍵になるというわけです。

Blob encryptedData = Crypto.encryptWithManagedIV(‘AES128’,MyPrivateKey,blobData);
実際に生成した秘密鍵を使ってテキストを暗号化している箇所になります。
Cryptoクラスにはもうひとつ「encrypt」というメソッドがありますが、そっちは初期化ベクトルをメソッドに与える必要があります。
encryptWithManagedIVはそれをSalesforceで与えてくれるので、少し引数がシンプルになります。

String EncryptedDataString = EncodingUtil.base64Encode(encryptedData);
秘密鍵を使って暗号化されたテキストもBase64化してみます。すると、直下のログで以下のように出力されると思います。(下記は適当にマスクしています。)
15:41:18:086 USER_DEBUG [11]|DEBUG|★This is encrypted data String= sKXityHtqPxWFw***B0GXGXS***lXr6ewX1QAXcdS8=

 

復号する


Blob DecMyPrivateKey = EncodingUtil.base64Decode('Base64化されたPrivateKey文字列');

Blob DecEncryptedData = EncodingUtil.base64Decode('sKWityHtqPxWFwcgtB0GWGIScoxdlXr6ewZ1QAPcdS8=');
Blob decryptedData = Crypto.decryptWithManagedIV('AES128',DecMyPrivateKey,DecEncryptedData);

String decryptedDataString = EncodingUtil.base64Encode(decryptedData);

system.debug('★★This is decrypted Data ='+ decryptedDataString + '&' +decryptedData.toString());

 

  • 上記で暗号化された文字列を、別のSalesforce組織で復号化する
  • 他の言語、システムで暗号化された文字列をSalesforce組織で復号化する

 

といったことを実現します。

基本的には暗号化の時を逆のことをするだけです。

秘密鍵として、「Base64化されたPrivateKey文字列」つまり「XX71Fq***6p/99dWGfOpXX==」を指定するところがキモですね。

他の言語で暗号化されたテキストであっても、暗号化された方式が同じであれば問題なく複合できます。

他の言語での暗号化の例として、Javaを記事にしましたので、ご参照ください。

セキュリティを考慮した、可逆的暗号化の相互運用の仕組みを実現する方法(JavaのAES編)

$
0
0

前回はSalesforceでの暗号化、複合化の仕組みをご紹介しました。

今回はJavaでの仕組みの回となります。

また、Base64化した暗号文字列をやり取りすることで、相互に可逆的な暗号化テキストのやり取りが行えるところまで試してみましょう。

 

JavaによるAES暗号化

はじめに暗号化を行います。

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

import java.util.Arrays;

public class MyAESEncrypt {

    private static final String characterEncoding = "UTF-8";
    private static final String cipherTransformation = "AES/CBC/PKCS5Padding";
    private static final String aesEncryptionAlgorithm = "AES";
    
    private static final String ivInitial = "secrettoencrypts";
    private static final String keyString = "1234567890abcdef";
    
    public static byte[] encryptBase64EncodedWithManagedIV(String clearText) throws Exception {
        byte[] bClearText = clearText.getBytes(characterEncoding);
        return encrypt(bClearText);
    }
    
    
    public static byte[] encrypt(byte[] bClearText) throws Exception{
        Cipher cipher = Cipher.getInstance(cipherTransformation);
        SecretKeySpec secretKeySpecy = new SecretKeySpec(keyString.getBytes(characterEncoding), aesEncryptionAlgorithm);
        
        byte[] initialVector = Arrays.copyOfRange(ivInitial.getBytes(characterEncoding),0,16);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(initialVector);
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpecy, ivParameterSpec);

        byte[] trimmedClearText2 = cipher.doFinal(bClearText);

        return trimmedClearText2;
    }
    


    public static void main(String args[]) throws Exception{
        
    	byte[] clearText = encryptBase64EncodedWithManagedIV( "This is new Crypton");       
        
        byte[] encryptBytesBase64 = Base64.encodeBase64(clearText, false);

        System.out.println("encryptedValue11=" + new String(encryptBytesBase64));

    }


}

Cipher cipher = Cipher.getInstance(cipherTransformation);
最初にCipherクラスをインスタンス化します。引数のcipherTransformationは「AES/CBC/PKCS5Padding」ですね。最初のAES は暗号化の方式、次のCBCはモードです。モードは簡単に言うと、処理方法みたいなものです。最後のPKCS5Paddingはパディング方式になります。詳しくはJavaのこのページを参照してください。
https://docs.oracle.com/javase/jp/8/docs/api/javax/crypto/Cipher.html

SecretKeySpec secretKeySpecy = new SecretKeySpec(keyString.getBytes(characterEncoding), aesEncryptionAlgorithm);
このコードで秘密鍵を生成します。keyStringに指定された文字列をもとに生成するので、この文字列が漏洩しないように気を付けましょう。

IvParameterSpec ivParameterSpec = new IvParameterSpec(initialVector);
このコードで初期化ベクトルを生成します。初期化ベクトルは、同じ鍵を使った暗号化でも、毎回違った結果を出すための引数のようなものだと考えると良いと思います。

cipher.init(Cipher.ENCRYPT_MODE, secretKeySpecy, ivParameterSpec);
上記までに生成した秘密鍵と初期化ベクトルをもとに、Cipherクラスをinitします。これで準備ができましたので、あとは、cipher.doFinalで実際にテキストを暗号化します。

 

複合

では、複合の方も行ってみましょう。


import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

import java.util.Arrays;

public class MyAESDecrypt {

    private static final String characterEncoding = "UTF-8";
    private static final String cipherTransformation = "AES/CBC/PKCS5Padding";
    private static final String aesEncryptionAlgorithm = "AES";

    private static final String ivInitial = "secrettoencrypts";
    private static final String keyString = "1234567890abcdef";
    
    public static byte[] decryptBase64EncodedWithManagedIV(String encryptedText) throws Exception {
        byte[] cipherText = Base64.decodeBase64(encryptedText.getBytes());
        return decryptWithManagedIV(cipherText);
    }

    public static byte[] decryptWithManagedIV(byte[] cipherText) throws Exception{
        byte[] initialVector = Arrays.copyOfRange(ivInitial.getBytes(),0,16);
        byte[] trimmedCipherText = Arrays.copyOfRange(cipherText,0,cipherText.length); 
        return decrypt(trimmedCipherText, initialVector);
    }

    public static byte[] decrypt(byte[] cipherText, byte[] initialVector) throws Exception{
        Cipher cipher = Cipher.getInstance(cipherTransformation);
        SecretKeySpec secretKeySpecy = new SecretKeySpec(keyString.getBytes(), aesEncryptionAlgorithm);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(initialVector);
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpecy, ivParameterSpec);
        cipherText = cipher.doFinal(cipherText);
        return cipherText;
    }

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

        byte[] clearText = decryptBase64EncodedWithManagedIV("qMtiTAjz9zINB8l7ni2My***OAhdFjoiRNWYMU2AbRQ=");
        System.out.println("ClearText:" + new String(clearText,characterEncoding));
    }


}

復号もやっていることは暗号化の逆なので、ほとんど変わらないですね。暗号化のところで出力したBase64を最後に施した文字列を受け取って復号するクラスになります。

 

下は、前回のSalesforceで生成したBase64の暗号化文字列を復号するコードになります。初期化ベクトルの関係でしょうか、最初の16Byteを削らないと正常に複合できませんでした。この理由についてはまた、別途調査しようと思いますが。。

 

package test;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

import java.util.Arrays;

public class MyAESDecrypt2 {

    private static final String characterEncoding = "UTF-8";
    private static final String cipherTransformation = "AES/CBC/PKCS5Padding";
    private static final String aesEncryptionAlgorithm = "AES";

    
    
    public static byte[] decryptBase64EncodedWithManagedIV(String encryptedText, String key) throws Exception {
        byte[] cipherText = Base64.decodeBase64(encryptedText.getBytes());
        byte[] keyBytes = Base64.decodeBase64(key.getBytes());
        return decryptWithManagedIV(cipherText, keyBytes);
    }

    public static byte[] decryptWithManagedIV(byte[] cipherText, byte[] key) throws Exception{
        byte[] initialVector = Arrays.copyOfRange(cipherText,0,16);
        //ここで16byte削ったものを戻す。なぜなら初期化ベクターが16byteだから。
        byte[] trimmedCipherText = Arrays.copyOfRange(cipherText,16,cipherText.length); 
        return decrypt(trimmedCipherText, key, initialVector);
    }

    public static byte[] decrypt(byte[] cipherText, byte[] key, byte[] initialVector) throws Exception{
        Cipher cipher = Cipher.getInstance(cipherTransformation);
        SecretKeySpec secretKeySpecy = new SecretKeySpec(key, aesEncryptionAlgorithm);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(initialVector);
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpecy, ivParameterSpec);
        cipherText = cipher.doFinal(cipherText);
        return cipherText;
    }

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

        byte[] clearText = decryptBase64EncodedWithManagedIV("SR1cArIMJBNYEKK1uw***3yiLL2xy00i2tlBp5hGAp8=", "Y65DvnhcZ**X2Z**56vJ2g==");
        System.out.println("ClearText:" + new String(clearText,characterEncoding));
    }


}

 

Salesforceで大量のデータをいっきに更新するお手軽な方法

$
0
0

Salesforceで大量のデータをいっきに更新したり、条件に合う特定のデータの値を書き換えたいときに使えるTipsです。

大量データの更新を何度もやる場合は、以前書いた「ガバナ制限を回避した大量データの一括更新にも便利 Batch Apexの使い方」のようにApexバッチ化しておくと便利ですが、1回しかやらない場合や、気軽にさくっと更新したい場合は、こちらの方法が便利だと思います。

 

開発者コンソールを開く

やり方は、まず、開発者コンソールを開いて、Debug->Open Execute Anonymous Window を開きます。

jsonify

 

 

一括更新のコードをAnonymous Windowに入力

Anonymous Window を開いたら、下記のようなコードを入力し、右下の「Execute」ボタンを押しましょう。

jsonify

 

String query = 'select id from Contact where LastName = \'SEI\'';
List contacts= Database.query(query);
for (Contact cont : contacts) {
    cont.FirstName = 'mei01';
	System.debug('★★★cont : ' + cont.Id + ','+ cont);
}
update contacts;

 

解説

少し解説すると、はじめに更新対象のレコードをクエリで抽出します。

その後、その抽出したリストの項目を書き換えて、updateメソッドを発行するだけです。

リストに入っているオブジェクトをまとめて更新するので、結構パフォーマンスも早いですよ~。

SOQLによる検索結果をエクセル出力するVisualForce画面の作り方(出力対象の選択を標準画面で行うやり方付き)

$
0
0

Salesforceでよく要望に上がるのに、かゆいところに手が届かないのがレポート機能。特に、日本のビジネスシーンでは帳票のようなレポートが好んで使われることもあり、せっかくためたデータを、有効に活用するには、レポート機能を充実させたい!という要望は多いようです。

多少不便であってもSalesforceのレポート機能でできることで業務を行っていくことも時には有効ですが、ここでは、比較的簡単に自由なレポートを行える方法をご紹介します。

 

今回やることの概要

初めに、今回やることを大まかに見てみます。

  • 標準のリストビュー画面で出力対象のレコードを選択し、カスタムで作成したボタンを押す。
  • 選択したレコードの情報をもとに、Apexの中でSOQLを発行し、必要なオブジェクトのリストを生成。
  • 生成したオブジェクトのリストをもとに、VisualForce画面でプレビューさせる。
  • プレビュー画面で「出力」ボタンを押して、エクセルにその内容を出力する。

 

少し汎用的な作りにするために、対象のレコード選択の画面には標準の画面を利用していますが、完全にVisualForceの画面を出発点にしても良いと思います。エクセルにエクスポートする部分は変わりませんので。

VisualForceでは、画面の「contentType」をエクセルにすると、画面に表示したほぼそのままをエクセル出力できます。

 

具体的なソース

今回は簡単なサンプルとして、取引先責任者のリストビューでレコードを選んで、カスタムボタンでプレビュー画面に行き、取引先と一緒の一覧を表示することにします。構造を分かりやすくするためにシンプルな構成にしましたが、原理さえわかれば、もっと複雑なSOQLを書いて、様々な情報を取得、加工して出力することもできます。

PageController

初めに、PageControllerクラスを作ります。プレビュー画面もエクセル出力画面も、出力形式が違う以外はやることは同じなので、同じクラスを使いまわせます。

public class ContactListPageController{

    public List selectedContactList{get;set;}
    public List exportObjectList{get;set;}
    List ssId{get;set;}


    public ContactListPageController(ApexPages.StandardSetController controller) {
    
        //選択したレコードを取得
        ssId = (List)controller.getSelected();

        selectedContactList=[SELECT Id, IsDeleted, Name, Email, Title, Account.Name FROM Contact where Id IN :ssId order by Name ];

        system.debug('★SELECT='+ssId);

        exportObjectList= new List();
        ContactMeisaiInfo cInfo;

        for(Contact oneCont : selectedContactList){
            
            cInfo = new ContactMeisaiInfo();
            cInfo.AccountName =oneCont.Account.Name;
            cInfo.ConactId =oneCont.Id;
            cInfo.ConactName =oneCont.Name;            

            exportObjectList.add(cInfo);

        }
    }

    public PageReference exportAction() {
        
        system.debug('★exportAction');
        
        //return null;
        
        return Page.VFPreviewContact_Export;

    }


   

    public PageReference back() {
        String rtnPage= ApexPages.currentPage().getParameters().get('retURL');
        PageReference cancel = new PageReference(rtnPage);
        return cancel;
    }

    public class ContactMeisaiInfo{
    
        public String ConactId {get;set;}
        public String ConactName {get;set;}
        public String AccountName {get;set;}
        
        public ContactMeisaiInfo(){

        }
    
    }
    


}

 

プレビュー用クラス

次に作るのは、プレビュー用のクラスです。(タグが含まれているので表現しづらいので、ソースファイルは添付しました。ほしい方はこのリンクからどうぞ。)

listview

 

エクセル出力用クラス

最後にに作るのは、エクセル出力用のクラスです。上記のプレビュー画面で「出力」を押すことで「exportAction」が呼ばれることでエクセルに出力されます。charsetは「MS932」または「CP932」を出力すると文字化けが少ないです。(まれに対応していない文字コードが含まれると文字化けします。。。いい解決方法があれば、ぜひ教えてください。私は、仕方なく対象の文字列をexportAction内などで置換しています。。)

listview

 

カスタムボタンの作成と、リストビューへの表示

材料のソースがそろったところで、取引先責任者の一覧画面に表示するカスタムボタンを作成しましょう。

カスタムボタンまたはカスタムリンクのところから、新規ボタン作成を選びます。「表示の種類」を「リストボタン」にすると、「内容のソース」にVisualForce画面を選べるようになるので、上記で作成したプレビューのVisualForce画面を選びます。

listview

 

「保存」ボタンを押すとカスタムボタンが作成されますので、それを表示しないといけないですね。表示するには、取引先責任者のオブジェクトのカスタマイズから「検索レイアウト -> 取引先責任者 リストビュー」に行って、上記で作成したカスタムボタンを表示するように選択して「保存」します。

 

listview

 

 

 

Salesforceの標準ページ(リストビュー or 詳細)からVisualforceページへ遷移させる方法と設定

$
0
0

Salesforceでは、標準オブジェクトやカスタムオブジェクトのリストビューまたは詳細画面から自分で作成したVisualForce画面に

遷移させることができ、この際に、リストビューで選択した複数レコードのIDを引き継いだり、詳細画面で表示されているオブジェクトのIDを取得したりすることができます。

このような場合、Salaesforce管理画面からカスタムのボタンを作成し、自分で作ったVisualForce画面を遷移先として選択することになるのですが、

「さっき作成したVisualForce画面が出ない!」ということがたまにあります。

こういう画面で、「コンテンツ」のところに何も出てこない場合ですね。

コンテンツ一覧

 

よくよく確認すれば理由があるのですが、忘れるとまた調査することになりそうなので、メモしておきます。

 

表示されるVisualForce画面は、コンストラクタの引数の型が決める

結論を先に言うと、ボタンから呼ばれる画面は、Visualforce画面で使用している拡張コントローラーのコンストラクタの引数の型が決定しています。

具体的には

リストビューから呼ばれるボタン(リストボタン)・・・ApexPages.StandardSetController

詳細画面から呼ばれるボタン(詳細ページボタン)・・・ApexPages.StandardController

です。

リストビューの場合は複数のオブジェクトのSetを渡せるようになっているとイメージすればいいですね。

 

リストビューページから呼ばれる場合

VisualForceページのヘッダ部分
<apex:page standardController="Account" extensions="myDetailControllerExtension" recordSetVar="accounts">

 

コントローラーのコンストラクタ部分
public myDetailControllerExtension(ApexPages.StandardController stdController) {
        List<Account> ssId = (List<Account>)controller.getSelected();
}

 

詳細ページから呼ばれる場合

VisualForceページのヘッダ部分
<apex:page standardController="Account" extensions="myControllerExtension">

 

コントローラーのコンストラクタ部分
public myControllerExtension(ApexPages.StandardSetController stdController) {
        this.acct = (Account)stdController.getRecord();
}

Salesforceで入力チェックをカスタマイズする方法のまとめと「正規表現」を使うTips

$
0
0

Salesforceでは「入力規則」を使って入力時のチェックを行うことができますが、少し複雑なチェックを行う場合は「正規表現」を使えると便利ですよね。

もちろん、Salesforceでできます。

正規表現を使えば、

  • 郵便番号や電話番号の形式
  • 時刻の形式
  • もっと複雑な特定の形式

 

でも自由に記述が可能です。

※基本的な正規表現については、【図解でわかりやすく】正規表現のまとめ その①基本メタキャラクタ編の記事を参照するとわかりやすいと思います。

 

入力規則でチェックする

Salesforceでは、各項目に対する入力チェックをカスタマイズすることができます。

通常は、管理コンソールからの入力規則でプログラムレスに定義することが可能です。

共通する定義の仕方は、下の画像のように、Salesforceの管理コンソールのオブジェクトの定義の「入力規則」に行き、

①入力規則の定義名を入力する。
適当な名前を入力しましょう。

②どんな条件の時にエラーとするのかを定義する。
少し紛らわしいですが、この条件でtrueになったときに、入力エラーとしてはじかれます。

③入力チェックエラー・メッセージを定義
入力チェックのエラーになった場合に、どのようなメッセージを表示するのかを定義します。
 また、表示する場所も、複数項目のエラーをまとめて画面上部に表示するのか、それぞれの項目の横に表示するのかを選べます。

正規表現

 

 

入力規則に使えるチェック表現Tipsまとめ(適宜追加予定)

ある一定のパターンにマッチしない文字を正規表現で防ぐ

正規表現には、REGEX関数を使うと便利です。下記は、『「Name1__c」項目が空欄で無い場合は、「99:99」の形で入力されること』というチェックになります。

AND(
NOT( ISBLANK(Name1__c),
NOT(REGEX( Name1__c, "[0-9]{2}:[0-9]{2}"))
)

 

なお、もっと複雑なチェックや、複数項目の関連チェックの場合は、Apexトリガでチェックすると楽です。

やり方は記事「複雑なSalesforceの入力チェックをApexトリガで行う方法とサンプル」を参照してみてください。

 


Salesforceのエクセル/CSV出力で文字化けするときは、文字コードをUTF-8にすればよい(BOM付き)

$
0
0

SFDCからファイル出力を行うには、エクセル形式が便利です。

以前、「SOQLによる検索結果をエクセル出力するVisualForce画面の作り方(出力対象の選択を標準画面で行うやり方付き)」という記事を書いたのですが、そのときは、文字コードの指定がMS932だったので、内部でUTF8で文字列情報を持っているSalesforceと際が生じ、「・」などの文字が「?」となってしまっていました。

しばらく解決策を知らなかったのですが、先日、一気に2つ解決方法が分かったのでメモしておきます。

 

方法① エクセル出力の文字コード指定を「UTF-8」にする

先入観から、エクセル出力時の文字コードはShift-JISしか無理だと思っていましたが、「UTF-8」という指定をすればそのまま文字化けせずに出力できました。。。

以前の記事でご紹介したソースのcontentTypeの部分を以下のように設定すればOKです。

contentType="application/vnd.ms-excel#ExportList.xls; charset=UTF-8"

 

これで、「・」なども文字化けせずにエクセル出力できます。

方法② UTF-8のcsv出力にする

エクセル出力だけでなく、csvで出力することも可能です。

エクセルで出力できればよいという意見もあるでしょうが、エクセルが入っている環境ばかりではないので、ソリューションのひとつとして記載しておきます。

なお、そのまま文字コードUTF-8のcsvで出力すると、「BOMなし」になり、ダブルクリックしてエクセルで開いたときに文字化けしてしまいます。これを防ぐには「BOM付き」のUTF-8で出力する必要があるので、ちょっとしたテクニックをご紹介します。

ソースコードはこちらから。

 

UTF-8のcsvで出力するVisualForceページは以下のように。

contentType="application/vnd.ms-excel#filename.csv;charset=UTF-8;"
を使います。全体では以下のようになります。

export

 

2つばかり、重要なポイントを記載します。

  • ヘッダ行は「apex:page」タグの直後に書く
    通常のVisualForceページのように「apex:page」タグの下から書くと、ファイル出力したときに1行目に空行ができてしまいます。
    1行目からヘッダ行を出力するには上記のようにしましょう。
  • BOMを挿入する
    Controller側に下記のような「utf8Bom」というメソッドを追加し、VisualForceのヘッダを書く前に呼びます。

 

public String utf8Bom {get {return '\uFEFF';} private set;}

 

PentahoでのSalesforce Insert,Update,Upsertの使いどころ

$
0
0

Pentahoでは、Salesforceのデータを挿入したり、更新したりするときに、「Salesforce Insert」「Salesforce Update」「Salesforce Upsert」という箱を使います。

どんなときにどの箱を使えばいいのか、戸惑うときがあるので、ちょっとまとめてみました。

pentaho

 

 

「Salesforce Insert」の使いどころ

pentaho insert

 

すべて新規のレコードとして作成されますので、SalesforceIdはフィールドにマッピングしません。

作成時にレコードにセットしておきたい項目のみマッピングします。

なので、入力データにIdがあったとしても、SalesforceIdにマッピングされていないので、すべて新規レコードとなります。

 

「Salesforce Update」

pentaho update

 

SalesforceIdをキーとして更新しますので、フィールドにIdをマッピングします。

そのため、入力データのId項目の値が不正だったり、空だと、更新時にエラーになります。

 

「Salesforce Upsert」

pentaho update

 

上記のダイアログのように、「比較フィールド更新」の項目に指定した項目をキーにレコードを更新します。

ここには、ユニークな「外部キー」を指定します。

SalesforceIdも指定できますが、Upsertの場合、Idがnullの場合、Insertしてくれず、INVALID_ID_FIELDになってしまいます。

つまり、IdをキーによろしくUpsertしてくれないということです。

そのため、SalesforceIdでレコードの挿入/更新を行う場合は、下記のように前段でId項目のフィルターを行い、Idがnullの場合はSalesforce Insert、Idが存在している場合はSalesforce Updateを呼ぶように分岐した方が良いと思います。

pentaho update

 

SalesforceのデータをSOQLを書いて自由にエクセルにダウンロードする方法

$
0
0

Salesforceのデータをエクセルやcsvに出力するにはいくつか方法がありますが、DataLoaderや開発者コンソール、WorkbenchなどのWebAPIを実装したツール等を使う必要があったりします。

開発中であったり、使用者が開発者であったりする場合は、比較的自由度の高いその方法を取ることも考えられますが、要件が「営業やマーケティングチームのユーザが毎日の業務のために、Salesforceにある最新のデータを取得したい」といったものである場合は、もう少し、使いやすく、用意しやすい方法がよいですよね。

ひとつのソリューションは、「レポート」にして、実行結果をエクセルやcsv形式で出力するというものです。

ただし、この方法は、少し複雑な結合条件のクエリだと、なかなか定義しづらいという欠点もあります。

それを解消するため、「SFDCのデータをエクセルで参照、加工する」というソリューションもあります。

なかなか良いアイディアですよね。

調べてみるといくつかあるので、興味のある方はサーチしてみてください。

さて、それらのソリューションの多くは、無料ではないので、自作してみました。

同じアプローチを考えていて、自分でコーディングしたいという方の助けになるかもしれませんので、サンプルソースも掲載しておきます。

 

エクセルでSalesforceのデータを参照する方法

やりたいことは、こうです。

エクセルのマクロで、httpリクエストをgetしたり、postしたりすることができます。

また、Salesforceは、WebAPIを公開していますので、WebAPI経由でデータの取得が比較的容易にできます。通常はJavaなどのプログラミング言語を使ってバッチなどを作成するのですが、エクセル・マクロでそれが行えれば、コンパイルも必要ありませんし、少し修正したりするのも簡単だというわけです。

今回のマクロは、エクセルのセルに、ログインURLとログインID、パスワードを書き込むことで、それを使用して、ログインし、そこで取得したトークンを利用してクエリを投げるシンプルなものにします。画面のイメージはちょうど下のような感じです。

Excel

 

 

サンプルソース

実際のサンプルソースです。

Public gLoginURL As String
Public gUserName As String
Public gPassword As String
Public gAccessToken As String
Public gServerURL As String
Public gServiceURL As String

Sub btnLogin_Click()



    gLoginURL = Worksheets("Setting").Range("cLoginURL").Value
    gUserName = Worksheets("Setting").Range("cUserName").Value
        gPassword = Worksheets("Setting").Range("cPassword").Value
    'MsgBox gLoginURL

    Dim targetURL, loginSendData

    targetURL = gLoginURL & "/services/Soap/u/40.0"

    loginSendData = ""


    '   POSTで飛ばします
    Dim httpReq
    Set httpReq = CreateObject("Microsoft.XMLHTTP")
    httpReq.Open "POST", targetURL, False
    Call httpReq.setRequestHeader("Content-Type", "text/xml;charset=utf-8")
    Call httpReq.setRequestHeader("SOAPAction", "login")
    
    Dim postData As Variant
    postData = "" + gUserName + "" + gPassword + ""
    
    Call httpReq.Send(postData)
    
    
    Dim respData As String
 
    If httpReq.Status = 200 Then
        respData = httpReq.responseText
    End If
    MsgBox respData
    
    
    Dim XDoc As Object, root As Object
     
    Set XDoc = CreateObject("MSXML2.DOMDocument")
    XDoc.async = False: XDoc.validateOnParse = False
    XDoc.LoadXML (respData)
    Set root = XDoc.DocumentElement
    
    Set accessTokenField = XDoc.SelectNodes("//loginResponse/result/sessionId")
    gAccessToken = accessTokenField(0).Text
    
    Set serverUrlField = XDoc.SelectNodes("//loginResponse/result/serverUrl")
    gServerURL = serverUrlField(0).Text
    
     
    Set XDoc = Nothing
    
    'MsgBox gServerURL
    
    
    Dim regEx, Match, Matches As Object   ' 変数を作成します。
    Set regEx = CreateObject("VBScript.RegExp")   ' 正規表現を作成します。
    regEx.Pattern = "https://([A-Za-z0-9_\-\.]*)/services"   ' パターンを設定します。
    regEx.IgnoreCase = True   ' 大文字と小文字を区別しないように設定します。
    regEx.Global = True   ' 文字列全体を検索するように設定します。
    Set Matches = regEx.Execute(gServerURL)   ' 検索を実行します。
    For Each Match In Matches   ' Matches コレクションに対して繰り返し処理を行います。
        RetStr = ""
        RetStr = RetStr & Match.Value
    Next
    
    gServiceURL = RetStr
   
    'MsgBox gServiceURL
   

End Sub




Sub serviceQuery_Click()

    
    Dim restTargetURL
    restTargetURL = gServiceURL & "/data/v40.0/query/?q=select%20Id%20From%20Account"
    MsgBox restTargetURL
    MsgBox gAccessToken
    
    '   GETで飛ばします
    Set httpObj = CreateObject("Microsoft.XMLHTTP")
    httpObj.Open "GET", restTargetURL, False
    Call httpObj.setRequestHeader("Content-Type", "application/json; charset=UTF-8")
    Call httpObj.setRequestHeader("Authorization", "Bearer " + gAccessToken)
    Call httpObj.setRequestHeader("Accept", "application/xml")
    httpObj.Send (sendData)
    
    MsgBox httpObj.responseText

End Sub

Google Apps ScriptのXmlServiceでXMLをparseする(或いはSalesforceにGASから接続する方法)

$
0
0

Google Apps Script(GAS)では、httpのPOSTやGETを行うことができます。

ということは、GASからSalesforceにログインして、データを取ってきて、GoogleSpreadSheetで表示することも出来そうだなーと思って、ログイン処理からコーディングし始めたのですが、思ったよりも手こずりました。

特に、ログイン後、返却されるXMLをパースしてログイン用のトークンを抽出するところが、難しかったです。

RSSやWebサービスの戻りの解析など、XML文書のパースはいたるところで使いそうですが、きちんとまとめておかないと、そのたびにつまづきそうなので、ここにメモっておきます。

 

UrlFetchAppを使って、ログインURLへデータをPOSTする

source

 

はじめに、UrlFetchApp.fetch()を使って、POSTを行います。

fetchの引数のoptionsにはヘッダやメソッド、実際のデータなどをセットします。

データには上記のようなXML文書を生成して、セットします。「username」と「password」は自分の環境に合わせて適宜変更してください。

 

XML文書から、XmlServiceを使ってparseし、文字列抽出する

さて、上記のようにloginし、成功すると、以下のようなXML文書が返ってきます。

このXMLから目的の文字列を抽出するのがひと苦労なんですよね。。

xml doc

 

今回は例として、データ取得の際にヘッダに埋め込むのが必要な「sessionId」を抽出してみたいと思います。

多分、他のXML文書で、なかなか目的の値を抽出できない人も同じように考えればOK かと。

キモは名前空間(NameSpace)です。

基本的には、

var xmlDoc = XmlService.parse(response.getContentText());
var rootDoc = xmlDoc.getRootElement();

でドキュメント全体の構造を取得したら、getChild() またはgetChildren()で目的の要素を抽出します。

このときに重要なのは、

  • Valid(妥当)なXML文書であること
  • Rootから順に要素をたどって目的の要素まを取得すること
  • 要素をgetChild() またはgetChildren()するときは、適切な名前空間(NameSpace)を引数に与えること

 

です。

順に確認していきましょう。

 

Valid(妥当)なXML文書であること

GoogleAppsScriptのXML文書パースでは、XML文書がValidであることが必要です。Validとは、DTDにきちんと基づいているということですね。

 

Rootから順に要素をたどって目的の要素まを取得すること

var entries = rootDoc.getChild('Body', nsSoapenv).getChild('loginResponse', nsDefault)

のように、Rootから順に要素をたどって目的の要素まで行きます。

その要素が1つの定義ならgetChild()、リスト形式で複数ならgetChildren()を使います。

 

要素をgetChild() またはgetChildren()するときは、適切な名前空間(NameSpace)を引数に与えること

上記のgetChild() には、第二引数で名前空間が渡されていることが分かります。

名前空間は、下の画像を例にすると、

の「soapenv」の部分です。では、soapenv名前空間の定義はどこにあるのかというと、XML文書の上の方を確認するとありますね。

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns="urn:partner.soap.sforce.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

の部分です。

xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"

ですね。

つまり、に囲まれた部分をgetChild()するには、第二引数に

var nsSoapenv = XmlService.getNamespace("soapenv", "http://schemas.xmlsoap.org/soap/envelope/");

のnsSoapenvを与える必要があります。

これを適切に与えないと、いくらやっても、getChild()/getChildren()の戻りが「null」になってしまいます。

以下のXML文書でいうと、それより下のタグには、名前空間がないですね。なので、デフォルトの(名前なし)の名前空間になります。

var entries = rootDoc.getChild('Body', nsSoapenv).getChild('loginResponse', nsDefault).getChild('result', nsDefault).getChild('sessionId', nsDefault);

上記のように、そこまで、順番にたどっていけばOKです。

いかがでしたでしょうか。

GoogleAppsScriptでのXML文書のパースは、結構頻出しますので、やり方を理解しておくと、いろいろなシーンに応用がきくと思いますよ。

ソースはこちら。

Dataloaderでsalesforceの「ファイル」を一括アップロードする方法

$
0
0

SalesforceもLightningに変わってきて、ファイルも、以前の「ドキュメント」ではなく「ファイル」を使うことが多くなってきました。

このファイル、自分でアップロードして自分で確認するには特に問題ないのですが、自分があげたファイルを他の人に見てもらう場合には、「権限」を付与してあげる必要があります。

で、1つ2つならいいですが、ファイルを大量にアップする場合には、どうしたらいいでしょうか?

今回は、そのやり方をご紹介します。

 

Dataloaderを使って、ローカルPCにあるがファイルを一括で、権限付きでアップロードする方法

「ファイル」のSFDCオブジェクトは「ContentVersion」です。

なので、基本的にはそのオブジェクトに対してDataloaderでローカルファイルをアップロードできます。

また、「ファイル」には「ライブラリ」といわれる、権限の管理とセットになった概念があります。

見た目上は「ファイル」配下に作成したフォルダのようで、フォルダ同様、その中に入れたファイルはそのライブラリに設定した権限を引き継ぎます。

 

 

そのため、アップロードファイル全部に適切な権限を付与した状態にするには、

 

  • ライブラリを作成
  • そのライブラリに権限を付与
  • そのライブラリに対してファイルをアップロード

 

という手順を踏むと良いです。


これを、Dataloaderで行うには、「ContentVersion オブジェクト」の項目にある「FirstPublishLocationId」にライブラリのオブジェクトIDを指定してあげます。

 

ライブラリのオブジェクトIDの調べ方

ライブラリは「ContentWorkspace」オブジェクトですので、SFDCの開発者コンソールから以下のようなSOQLを発行すると、IDが取得できます。(以前の記事に書いたWorkbenchを使うと便利です)

 

SELECT Id,Name,CreatedDate,Description FROM ContentWorkspace

 

取得したオブジェクトIdを「FirstPublishLocationId」にセットしたCSVファイルの用意

 

"TITLE","DESCRIPTION","VERSIONDATA","PATHONCLIENT","FirstPublishLocationId"
"AAAA","This is AAAA","D:\sample\AAAA.png","D:\sample\AAAA.png","058p00000002zzzXXX"

 

このように用意したCSVファイルをDataloaderで読み込んで、insertすれば、ファイルをアップロードできます。

Viewing all 62 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>