2012-12-08

Force.com : 大規模ポータルユーザにレコードのアクセス権を設定したい


大規模ポータルユーザにレコードのアクセス権を設定したい



以前書いた記事( カスタマポータルを利用したい )の続編的何か。
あとで、もうちょっと補足する ... かも。



○ カスタムオブジェクト(Foo)の作成

  1. [設定]→[アプリケーションの設定]→[作成]→[オブジェクト]よりオブジェクトを作成する
    • 表示ラベル: Foo
    • API参照名: Foo__c
    • カスタマポータルで利用可: ON
  2. [カスタム項目 & リレーション]より項目を作成する
    • 項目の表示ラベル: 取引先
    • API参照名: Account
    • データ型: 参照関係(取引先)
  3. [トリガ]よりトリガを作成する
    • FooTrigger
      trigger FooTrigger on Foo__c (before insert) {
          if(Trigger.isBefore) {
              User user = [Select Id, UserType, IsPortalEnabled, AccountId From User Where Id = :Userinfo.getUserId()];
              if(user.IsPortalEnabled) {
                  for(Foo__c f : Trigger.new) {
                      f.Account__c = user.AccountId;
                  }
              }
          }
      }
      



○ 共有ルールの設定

  1. [設定]→[管理者設定]→[セキュリティのコントロール]→[共有設定]より[共有設定]画面を表示する
  2. [共有設定]画面の[デフォルトの共有設定・組織の共有設定]欄の[編集]ボタンを押下する
  3. 下記のオブジェクト及びレコードのアクセス権を設定したいオブジェクトに対して、アクセスを「非公開」にし、[保存]ボタンを押下する
    • 取引先、契約、および納入商品
    • 商談 ※ 取引先のアクセス権と連動
    • ケース ※ 取引先のアクセス権と連動
    • Foo(上記で作成したオブジェクト)



○ プロファイル(「HV-TEST1」)の作成

  1. [設定]→[管理者設定]→[ユーザの管理]→[プロファイル]より[プロファイル]画面を表示する
  2. プロファイル一覧より[ユーザライセンス:High Volume Customer Portal]のプロファイル名を押下する
  3. [コピー]ボタンを押下する
  4. [プロファイル名]を「HV-TEST1」として[保存]ボタンを押下する
  5. [プロファイル名:HV-TEST1]の[カスタムオブジェクト権限]の[Foo](上記で作成したオブジェクト)にアクセス権限を与える



○ カスタマポータル(「カスタマポータル」)の設定

※ カスタマポータルの利用は以前書いた記事( カスタマポータルを利用したい )か他を参照
  1. [設定]→[アプリケーション設定]→[カスタマイズ]→[カスタマポータル]→[設定]より[カスタマポータル設定]画面を表示する
  2. [大規模ポータルユーザの共有設定]より[新規]ボタンを押下する
  3. 各項目を設定し、[保存]ボタンを押下する
    • 名前: カスタマポータル
    • プロファイルの選択: HV-TEST1(上記で作成したプロファイル)
    • オブジェクトの選択: Foo(上記で作成したオブジェクト)
  4. 作成した共有セット名のリンクを押下する
  5. [アクセス権を許可]から[オブジェクト:Foo(上記で作成したオブジェクト)]の[編集]リンクを押下する
  6. [Fooの共有設定]画面が表示されるので、値を設定し[保存]ボタンを押下する
    • アクセス権を付与: 大規模ポータルユーザのアカウントが次に一致するすべてのレコード「取引先」(上記で作成したオブジェクトの項目)
    • アクセス権を選択: 参照のみ or 参照・更新



○ カスタマポータルユーザ

■ 作成
  • [取引先]から[取引先責任者]を作成する
■ 有効化
  1. [取引先責任者]詳細画面の[ポータルの使用]選択リストから[カスタマポータルユーザの有効化]ボタンを選択する
  2. [新規ユーザ]画面からユーザを作成する
    • ユーザライセンス: High Volume Customer Portal
    • プロファイル: HV-TEST1(上記で作成したプロファイル)
  3. 作成完了後[新規ユーザ]のメールアドレスに[ユーザ名]と[パスワード]が送信される



○ その他

※ あとで



◇ 環境

  • Salesforce - Developer Edition - API バージョン 25.0





※ この記事は Force.com Advent Calendar に参加しています :-)。


2012-07-26

Force.com : VF 画面から CSV ファイルをアップロードし、内容を画面に表示したい (ファイル文字コード Shift_JIS 編)


VF 画面から CSV ファイルをアップロードし、内容を画面に表示したい (※ ファイル文字コード Shift_JIS)



● あらすじ

前回、
なる記事を書いてみた。
しかし、CSV ファイルが、UTF-8 or 1byte文字のみなファイルではなく、Shift_JIS なファイルである必要が出ていたために今回に至るのであった。
※ 1byte 文字のみ含まれるファイルであれば前回の記述で OK なのである



○ 考察

問題は、下記。

public class ImportController {

    public Attachment d { get; set; }

        :

    public PageReference read() {

        :

        Blob data = d.Body;
        String body = data.toString();

        :

        return null;
    }

UTF-8 以外の 2byte 文字が来るとエラーが発生する。
しかし、Blob の値が UTF-8 以外の文字コードのものを String にする術がわからない ;-(。
Google 先生に聞くと、このサイト を教えてくれた!ので、試す。

public class ImportController {

    public Attachment d { get; set; }

        :

    public PageReference read() {

        :

        // ※ コピペさせていただいてます ※ //
        Blob data = d.Body;
        String hex = EncodingUtil.convertToHex(data);
        String result = '';
        for (Integer i = 0, size = hex.length(); i < size; i = i + 2) {
            result = result + '%' + hex.substring(i, i + 2);
        }
        String body = EncodingUtil.urlDecode(result, 'Windows-31J');

        :

        return null;
    }

... 130KB くらいのファイルで試すと(体感的に)遅い。場合によってはセッションタイムアウトが発生する。
for 文のところがかなり問題なのである。たぶん、String#substring() 。

要は 16 進数文字列の 16 進数の頭に % を付加すればよいのだろ。文字列置換と正規表現を組み合わせればどうにかなるんじゃないか!?。

public class ImportController {

    public Attachment d { get; set; }

        :

    public PageReference read() {

        :

        Blob data = d.Body;
        String hex = EncodingUtil.convertToHex(data);
        String result = hex.replaceAll('[[0-9][a-fA-F]]{2}', '%$0');
        String body = EncodingUtil.urlDecode(result, 'Windows-31J');

        :

        return null;
    }

再び、130KB くらいのファイルで試すと。なんとか、(体感的に)早くなったのでもうこれで良しとする。
( ... しかし、変態的過ぎる。

※ 追記: 2012-07-26 19:00 くらい
160KB 超えのファイルをアップロードしたところ、
System.LimitException: Regex too complicated
なる例外が ... 正規表現のヒープサイズを超えたよう。
もう、心折れそう ;-(。

※ 追記: 2012-07-27 15:00 くらい
もういいよ。
正規表現で置換する 16 進数文字列のサイズを分割してやればいいんじゃないか(やけくそ ;-(。

public class ImportController {

    public Attachment d { get; set; }

        :

    public PageReference read() {

        :

        Blob data = d.Body;
        String hex = EncodingUtil.convertToHex(data);

        String result = '%';
        Integer hexSize = 300000;    // ※ この数字は適当です ※ //
        Integer qu = hex.length() / hexSize;
        Integer i = 0;
        while(true) {
            if(i >= qu) {
                if(i == qu) {
                    result += hex.substring(i*hexSize).replaceAll('[[0-9][a-fA-F]]{2}', '$0%');
                    result = result.substring(0, result.length()-1);
                }
                break ;
            } else {
                result += hex.substring(i*hexSize, (i+1)*hexSize).replaceAll('[[0-9][a-fA-F]]{2}', '$0%');
            }
            i++;
        }
        String body = EncodingUtil.urlDecode(result, 'Windows-31J');

        :

        return null;
    }

340KB くらいのファイルで試す。正規表現の例外は発生しなくなった。
因みに、分割サイズは適当。「500000」だと例外が発生した。「300000」だと例外は発生しなかった。その間は試していない。ヒープサイズの最大は 6MB なのはわかったのだけれども、その割り振りがわからないでいる。
String#substring() かむばっく。なので、残すは 5MB くらいのファイルに耐えれるか否かである。... より、レスポンスとファイルサイズで折り合いつけよう(もう、半ばどうでもよくなってきた。

それより最初、ビューステートのエラーが発生していたので VF 画面に javascript でファイルサイズのチェックを行っていたのだけれども、今は、135KB のファイルサイズチェックなくても大丈夫っぽ(?)。5MB 超えると勝手に「アップロードできません」なエラーメッセージ出してくれる。ビューステート回避のためにコード書き換えた記憶はあるのだけれども、どう書き換えたのかまではもう覚えていない ;-)。取っ払っておこう。そして、そのうち調べてまとめておこう(希望的観測 :-)。

※ 追記: 2012-07-30 19:00 くらい
3MB くらいのファイルをアップロードしたところ、
System.StringException: String length exceeds maximum: 6000000
なる例外が発生 ... ;-(。
「result += ...」あたりで発生しているらしい。
google 先生に聞いたら、たぶん理由はこんな感じ?
ただ、これが攻略できてもまた
System.LimitException: Regex too complicated
が発生するね。
List lines = body.split('\r\n');
のとこで。てか、UTF-8 版で 5MB 弱のファイルで試したら発生した。

あと、ユーザの設定で「Apex 警告メールの送信」のチェックボックスを ON にしている場合、画面上はエラー画面に遷移はしないけれども「Apex governor limit warning」なメールがやってくる。(16 進数文字列を URL パラメータに正規表現で変換しているところか body.split('\r\n') なところでだろ)。
ログを見ると、
Maximum heap size: 3184485 out of 6000000 ******* CLOSE TO LIMIT
だって。


要は「VF 画面から CSV ファイルをアップロードし、内容をごにょごにょする」なんて思わないが吉!


※ この件はホントの仕様に合わせてみて、そこで折り合いつけることにしたのでここまでとする :-)。



○ 要件

  • CSV のみアップロードを可能とする
  • アップロードするファイルの文字コードは Shift_JIS とする
  • アップロードするファイルの改行コードは \r\n とする



○ Apex クラス : ImportController

■ Shift_JIS 用
public class ImportController {

    public Attachment d { get; set; }

    public String filename { get; set; }

    public List<List<String>> rows { get; set; }

    public ImportController() {
        if(d == null) {
            d = new Attachment();
        }
    }

    public PageReference read() {
        if(d == null || d.Name == null) {
            return null;
        }

        filename = d.Name;

        // Shift_JIS
        Blob data = d.Body;
        String hex = EncodingUtil.convertToHex(data);
        String result = '%';
        Integer hexSize = 300000;
        Integer qu = hex.length() / hexSize;
        Integer i = 0;
        while(true) {
            if(i >= qu) {
                if(i == qu) {
                    result += hex.substring(i*hexSize).replaceAll('[[0-9][a-fA-F]]{2}', '$0%');
                    result = result.substring(0, result.length()-1);
                }
                break ;
            } else {
                result += hex.substring(i*hexSize, (i+1)*hexSize).replaceAll('[[0-9][a-fA-F]]{2}', '$0%');
            }
            i++;
        }
        String body = EncodingUtil.urlDecode(result, 'Windows-31J');

        List<String> lines = body.split('\r\n');
        rows = new List<List<String>>();
        for(String line : lines) {
            if(line.startsWith('"')) line = line.substring(1);
            if(line.endsWith('"')) line = line.substring(0, line.length()-1);
            rows.add(line.split('","', -1));
        }

        return null;
    }
}



○ Visualforce : Import

<apex:page controller="ImportController">
<script type="text/javascript">
    function getExtention(fileName) {
        if (!fileName) {
            return "";
        }
        var fileTypes = fileName.split(".");
        if (fileTypes.length === 0) {
            return "";
        }
        return fileTypes[fileTypes.length - 1];
    }
    function isCsvFile(file) {
        var extention = getExtention(file.name);
        if (extention.toLowerCase() === "csv") {
            return true;
        } else {
            alert("CSVファイルを設定してください.");
            return false;
        }
    }
    function getFile(frm) {
        return frm.elements[2].files[0];
    }
</script>

    <apex:messages />
    <apex:form >
        <apex:pageBlock >
            <apex:pageBlockButtons >
                <apex:commandButton value="read" action="{!read}" onclick="return (isCsvFile(getFile(this.form)));" />
            </apex:pageBlockButtons>
            <apex:pageBlockSection >
                <apex:outputPanel >
                    <apex:outputText value="file: " /><apex:inputFile value="{!d.body}" filename="{!d.name}" />
                </apex:outputPanel>
            </apex:pageBlockSection>
        </apex:pageBlock>
        <apex:pageBlock rendered="{!NOT(ISNULL(rows))}">
            <apex:outputText value="{!filename}" />
            <table>
                <apex:repeat value="{!rows}" var="row">
                <tr>
                    <apex:repeat value="{!row}" var="col">
                    <td style="width:80px;"><apex:outputText value="{!col}" /></td>
                    </apex:repeat>
                </tr>
                </apex:repeat>
            </table>
        </apex:pageBlock>
    </apex:form>
</apex:page>



◇ 環境

  • Salesforce - Developer Edition - API バージョン 25.0



◇ 参考


2012-07-25

Force.com : VF 画面から CSV ファイルをアップロードし、内容を画面に表示したい (ファイル文字コード UTF-8 編)


VF 画面から CSV ファイルをアップロードし、内容を画面に表示したい (※ ファイル文字コード UTF-8)



● あらすじ

とある仕様で VF 画面から CSV ファイルをアップロードして、内容をオブジェクトに insert する必要が出たため。
因みに、内容を画面に表示させる必要はない。
(最近、そこまでやるならもう Force.com じゃなくてもいいじゃないか、とか思い始めている ... ;-(。



○ 要件

  • CSV のみアップロードを可能とする
  • アップロードするファイルの文字コードは UTF-8 とする
  • アップロードするファイルの改行コードは \r\n とする



○ Apex クラス : ImportController

■ UTF-8 用
public class ImportController {

    public Attachment d { get; set; }

    public String filename { get; set; }

    public List<List<String>> rows { get; set; }

    public ImportController() {
        if(d == null) {
            d = new Attachment();
        }
    }

    public PageReference read() {
        if(d == null || d.Name == null) {
            return null;
        }

        filename = d.Name;

        // UTF-8 用(1byte 文字のみのであれば可) //
        Blob data = d.Body;
        String body = data.toString();

        List<String> lines = body.split('\r\n');    // ※ System.LimitException: Regex too complicated 発生の可能性あり ※ //
        rows = new List<List<String>>();
        for(String line : lines) {
            if(line.startsWith('"')) line = line.substring(1);
            if(line.endsWith('"')) line = line.substring(0, line.length()-1);
            rows.add(line.split('","', -1));
        }

        return null;
    }
}

※ Attachment ではなく Document にする場合
public class ImportController {

    public Document d { get; set; }

        :

    public ImportController() {
        if(d == null) {
            d = new Document();
            d.folderid = UserInfo.getUserId();
        }
    }

        :
}
且つ、プロファイルにてドキュメントのオブジェクト権限が「参照」「作成」以上であること



○ Visualforce : Import

<apex:page controller="ImportController">
<script type="text/javascript">
    function getExtention(fileName) {
        if (!fileName) {
            return "";
        }
        var fileTypes = fileName.split(".");
        if (fileTypes.length === 0) {
            return "";
        }
        return fileTypes[fileTypes.length - 1];
    }
    function isCsvFile(file) {
        var extention = getExtention(file.name);
        if (extention.toLowerCase() === "csv") {
            return true;
        } else {
            alert("CSVファイルを設定してください.");
            return false;
        }
    }
    function getFile(frm) {
        return frm.elements[2].files[0];
    }
</script>

    <apex:messages />
    <apex:form >
        <apex:pageBlock >
            <apex:pageBlockButtons >
                <apex:commandButton value="read" action="{!read}" onclick="return (isCsvFile(getFile(this.form)));" />
            </apex:pageBlockButtons>
            <apex:pageBlockSection >
                <apex:outputPanel >
                    <apex:outputText value="file: " /><apex:inputFile value="{!d.body}" filename="{!d.name}" />
                </apex:outputPanel>
            </apex:pageBlockSection>
        </apex:pageBlock>
        <apex:pageBlock rendered="{!NOT(ISNULL(rows))}">
            <apex:outputText value="{!filename}" />
            <table>
                <apex:repeat value="{!rows}" var="row">
                <tr>
                    <apex:repeat value="{!row}" var="col">
                    <td style="width:80px;"><apex:outputText value="{!col}" /></td>
                    </apex:repeat>
                </tr>
                </apex:repeat>
            </table>
        </apex:pageBlock>
    </apex:form>
</apex:page>



◇ 環境

  • Salesforce - Developer Edition - API バージョン 25.0



◇ 参考


2012-07-03

Force.com : カスタマポータルを利用したい


※ 一旦保存



カスタマポータルを利用したい



○ カスタマポータル

■ 設定
・アプリケーションの設定
 ├  :
 ├ カスタマイズ
 :└ カスタマポータル
   └ 設定 → カスタマポータル設定

■ 有効化
  1. [カスタマポータル設定]画面より[編集]ボタンを押下する
  2. [カスタマーポータルの有効化]チェックボックスを ON にして[保存]ボタンを押下する
  3. 必要に応じて[ポータル]を新規作成する(デフォルトで 1 つ作成されている)
  4. [ポータル]欄より[ログインを有効にする]が ON になっている[ポータル]リンクを押下する
  5. [割り当てられたプロファイル]欄の[プロファイルの編集]ボタンを押下し、割り当てるプロファイルを選択する
※ メモ
  • ウィザード画面が表示されるけど、無視してもたぶん OK
  • 複数のポータルに同じプロファイルを設定することは可能。但し、ログイン URL でポータル ID を省略した場合最初に作成されたポータルのログイン画面が表示される
  • ログイン URL: https://{salesforce_instance}/secur/login_portal.jsp?orgId={組織 ID}&portalId={ポータル ID}

■ 無効化
  1. [カスタマポータル設定]画面の[ポータル]欄より無効化したいポータルの[編集]アクションを押下する
  2. [ログインを有効にする]チェックボックスを OFF にして[保存]ボタンを押下する
※ メモ
  • 削除はできない
  • 各ポータルのログインを無効化することでユーザはカスタマポータルにログインできなくなる



○ カスタマポータルプロファイル

ユーザライセンス ロール 共有設定
High Volume Customer Portal (なし) [カスタマポータル設定]の[大規模ポータルユーザの共有設定]にて可
※ あとで
Customer Portal Manager Custom
  • エグゼクティブ
  • マネージャ
  • ユーザ
[セキュリティの設定]→[共有設定]等で可能
※ あとで



○ カスタマポータルユーザ

  • [取引先]から[取引先責任者]を作成する
■ 有効化
  1. [取引先責任者]詳細画面の[ポータルの使用]選択リストから[カスタマポータルユーザの有効化]ボタンを選択する
  2. [新規ユーザ]画面からユーザを作成する
  3. 作成完了後[新規ユーザ]のメールアドレスに[ユーザ名]と[パスワード]が送信される

■ 無効化
  1. [取引先責任者]詳細画面の[ポータルの使用]選択リストから[カスタマポータルユーザの無効化]ボタンを選択する
※ メモ
  • カスタマポータルユーザを有効化している取引先責任者は削除できない (無効化すればできる)
  • カスタマポータルユーザを有効化→無効化→有効化した場合、その取引先責任者の[ユーザID]は異なる
  • カスタマポータルユーザを無効化した場合、その取引先責任者の[ユーザID]を所有者などに設定しようとするとエラーになる (無効化以前に設定されているレコードは設定されたままとなる)

■ システム管理者によるログイン・ログアウト
  1. [取引先責任者]詳細画面の[ポータルの使用]選択リストから[ポータルユーザとしてログイン]ボタンを選択する
  2. カスタマポータルの[ホーム]画面が表示される
  3. 右上に表示されている[ログアウト]リンクよりログアウトする



----- 下記メモ -----



○ 共有ルールの設定

  1. [設定]→[管理者設定]→[セキュリティのコントロール]→[共有設定]より[共有設定]画面を表示する
  2. [共有設定]画面の[デフォルトの共有設定・組織の共有設定]欄の[編集]ボタンを押下する
  3. 下記のオブジェクト及びレコードのアクセス権を設定したいオブジェクトに対して、アクセスを「非公開」にし、[保存]ボタンを押下する
    • 取引先、契約、および納入商品
    • 商談 ※ 取引先のアクセス権と連動
    • ケース ※ 取引先のアクセス権と連動



◇ 環境

  • Salesforce - Developer Edition - API バージョン 25.0

2012-05-08

Force.com : Apex で文字列を他の型に変換したい


Apex で文字列を他の型に変換したい



○ String → Date

■ yyyyMMdd
String syyyyMMdd = '20120508';
String y = syyyyMMdd.substring(0, 4);
String m = syyyyMMdd.substring(4, 6);
String d = syyyyMMdd.substring(6, 8);
Date yyyyMMdd = Date.valueOf(y + '-' + m + '-' + d);

System.debug('##### ' + yyyyMMdd);    // ##### 2012-05-08 00:00:00

■ yyyyMMdd
String syyyyMMdd = '20120508';
String y = syyyyMMdd.substring(0, 4);
String m = syyyyMMdd.substring(4, 6);
String d = syyyyMMdd.substring(6, 8);
Date yyyyMMdd = Date.newInstance(Integer.valueOf(y), Integer.valueOf(m), Integer.valueOf(d));

System.debug('##### ' + yyyyMMdd);    // ##### 2012-05-08 00:00:00



○ String → Integer

■ 整数
String sinteger = '123';
Integer i = Integer.valueOf(sinteger);

System.debug('##### ' + i);    // ##### 123

■ 小数
String sinteger = '123.00000';
Integer i = Integer.valueOf(sinteger);

System.debug('##### ' + i);    // ##### 123

■ 小数 (※ Exception 発生)
String sinteger = '123.45678';
Integer i = Integer.valueOf(sinteger);

// EXCEPTION_THROWN: System.TypeException: Invalid integer: 123.45678

System.debug('##### ' + i);

■ null (※ Exception 発生)
String sinteger = null;
Integer i = Integer.valueOf(sinteger);

//  EXCEPTION_THROWN: System.NullPointerException: Argument 1 cannot be null

System.debug('##### ' + i);

■ 空文字 (※ Exception 発生)
String sinteger = '';
Integer i = Integer.valueOf(sinteger);

//  EXCEPTION_THROWN: |System.TypeException: Invalid integer: 

System.debug('##### ' + i);



○ String → Double

■ 整数
String sdouble = '123';
Double d = Double.valueOf(sdouble);
System.debug('##### ' + d);    // ##### 123.0

■ 小数
String sdouble = '123.45678';
Double d = Double.valueOf(sdouble);
System.debug('##### ' + d);    // ##### 123.45678

■ null (※ Exception 発生)
String sdouble = null;
Double d = Double.valueOf(sdouble);

// EXCEPTION_THROWN: System.NullPointerException: Argument 1 cannot be null

System.debug('##### ' + d);

■ 空文字 (※ Exception 発生)
String sdouble = '';
Double d = Double.valueOf(sdouble);

// EXCEPTION_THROWN: System.TypeException: Invalid double: 

System.debug('##### ' + d);



◇ 参考


2012-04-10

Windowsバッチファイル : テキストファイルの文字コードを変換したい


Windowsバッチファイルでテキストファイルの文字コードを変換したい



○ テキストファイルの文字コードを変換したい

  • 文字コードセット:UTF-8 のファイル(utf8.txt)を用意する
  • バッチ実行ディレクトリに「unicode.txt」「sjis.txt」が出力される
@ECHO OFF

REM 文字コードセット:UTF-8 → Unicode
START /MIN /WAIT CMD /C CHCP 65001 ^& ^( SET /P x=""^<nul ^& CMD /U /C TYPE utf8.txt ^) ^>unicode.txt

REM 文字コードセット:Unicode → Shuft-JIS
START /MIN /WAIT CMD /C CHCP 932 ^& CMD /C TYPE unicode.txt ^>sjis.txt
※ [SET /P="(文字列)"<nul]の"(文字列)"がなんなのかは知らない


■ START
START ["タイトル"] [/D パス] [/I] [/MIN] [/MAX] [/SEPARATE | /SHARED]
 [/LOW | /NORMAL | /HIGH | /REALTIME | /ABOVENORMAL | /BELOWNORMAL]
 [/NODE ] [/AFFINITY <16 進数の関係マスク>] [/WAIT] [/B]
 [コマンド/プログラム] [パラメーター]
  • 指定されたプログラムまたはコマンドを実行するためにウィンドウを開き
  • オプション
    • /MIN : ウィンドウを最小化した状態で起動する
    • /WAIT : アプリケーションを起動し、それが終了するまで待機する
    • ※ その他は下記を参照
      help start
      


■ CMD
CMD [/A | /U] [/Q] [/D] [/E:ON | /E:OFF] [/F:ON | /F:OFF] [/V:ON | /V:OFF] [[/S] [/C | /K] 文字列]
  • Windows コマンド インタープリターの新しいインスタンスを開始する
  • オプション
    • /C : "文字列" に指定されたコマンドを実行した後、終了する
    • /U : 内部コマンドの出力結果を Unicode でパイプまたはファイルに出力する
    • ※ その他は下記で参照
      help cmd
      


■ CHCP
CHCP [nnn]
  • 現在のコード ページ番号を表示または設定する
  • オプション
    • nnn : コードページ番号
    • ※ その他は下記で参照
      help chcp
      


■ SET
SET [変数名=[文字列]]
  • cmd.exe 環境変数を表示、設定、または削除する
  • オプション
    • SET /P="文字列"<NUl :改行しないで文字列を出力する
    • ※ その他は下記で参照
      help set
      



◇ 環境

Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.




◇ 参考


Force.com : 起動 - Data Loader CLIq


CLIp の起動



◆ 起動

  1. $ApexDataLoader_HOME/cliq/cliq.bat をクリックする
  2. バッチが実行され、以下の画面が表示されれば起動成功





◇ 環境

  • Salesforce - Enterprise Edition - API バージョン 24.0
  • Apex Data Loader 23
  • CLIp Release 2.1.1

Force.com : 起動(sandbox 環境) - Data Loader CLIq


sandbox 環境で CLIp の起動



◆ cliq.properties の修正

  1. $ApexDataLoader_HOME/cliq/cliq.properties を修正する
    # Use https://test.salesforce.com/services/Soap/u/ for Sandbox instances
    # NOTE: Make sure the version is equal or lower than your version of Data Loader.
    #
    # For example:
    # sfdc.endpoint=https://test.salesforce.com/services/Soap/u/21.0
    
       ↓↓↓
    # Use https://test.salesforce.com/services/Soap/u/ for Sandbox instances
    # NOTE: Make sure the version is equal or lower than your version of Data Loader.
    #
    # For example:
    sfdc.endpoint=https://test.salesforce.com/services/Soap/u/23.0
    
    ※ [Apex Data Loader 23]を使用しているため[YOUR DATA LOADER VERSION]を「23.0」としている



◆ sandbox 環境で起動

  1. $ApexDataLoader_HOME/cliq/cliq.bat をクリックする
  2. バッチが実行され、以下の画面が表示されれば起動成功


  3. 次の画面(Ex. Export)へ進むと、上記に sandbox の URL が表示されていることを確認(「https://test.sa ...」)


    ※ 以前のバージョンに URL が表示される個所なんてなかった気がする



◇ 環境

  • Salesforce - Enterprise Edition - API バージョン 24.0
  • Apex Data Loader 23
  • CLIp Release 2.1.1




2012-04-04

Force.com : Apex でオブジェクト及び項目の情報を取得する


Apex でオブジェクト及び項目の情報を取得する



○ すべてのオブジェクト及び項目の情報を取得する

Map<String, Schema.SObjectType> smap = Schema.getGlobalDescribe();
for (String objKey : smap.keySet()) {

    // Object
    System.debug('##### key: object API reference name: ' + objKey);

    Schema.SObjectType sobj = smap.get(objKey);
    Schema.DescribeSObjectResult sr = sobj.getDescribe();

    System.debug('##### object is Custom: ' + sr.isCustom());
    System.debug('##### object label: ' + sr.getLabel());
    System.debug('##### object API reference name: ' + sr.getName());

    // field
    Map<String, Schema.SObjectField> fmap = sr.fields.getMap();
    for (String fieldKey : fmap.keySet()) {

        System.debug('***** key: field API reference name: ' + fieldKey);

        Schema.SObjectField f = fmap.get(fieldKey);
        Schema.DescribeFieldResult fr = f.getDescribe();

        System.debug('***** field is Custom: ' + fr.isCustom());
        System.debug('***** field label: ' + fr.getLabel());
        System.debug('***** field API reference name: ' + fr.getName());
        System.debug('***** field type: ' + fr.getType());

        if(fr.getController() != null) {
            System.debug('***** field is controller');
            System.debug('***** controller'
                        + ' label: ' + fr.getController().getDescribe().getLabel());
                        + ' name: ' + fr.getController().getDescribe().getName());
        }

        if(fr.getReferenceTo().isEmpty()) {
            System.debug('***** field is reference to');
            List<Schema.sObjectType> olist = fr.getReferenceTo();
            for(Schema.sObjectType o : olist) {
                System.debug('***** reference to'
                                + ' label:' + o.getDescribe().getLabel() 
                                + ' API reference name:' + o.getDescribe().getName());
            }
        }

        if(!fr.getPicklistValues().isEmpty()) {
            System.debug('***** field is picklist');
            List<Schema.PicklistEntry> plist = fr.getPicklistValues();
            for(Schema.PicklistEntry e : plist) {
                System.debug('***** picklist'
                                + ' label:' + e.getLabel() 
                                + ' value:' + e.getValue());
            }
        }
    }
}



○ オブジェクト

■ カスタムオブジェクトかの判定 (ex. 取引先)
Account.sObjectType.getDescribe().isCustom()
  • true: カスタムオブジェクト
  • false: 標準オブジェクト
■ 表示ラベル (ex. 取引先)
Account.sObjectType.getDescribe().getLabel()
■ API 参照名 (ex. 取引先)
Account.sObjectType.getDescribe().getName()



○ 項目

■ カスタムオブジェクトかの判定 (ex. 取引先.取引先名)
Account.Name.getDescribe().isCustom()
  • true: カスタム項目
  • false: 標準項目
■ 表示ラベル (ex. 取引先.取引先名)
Account.Name.getDescribe().getLabel()
■ API 参照名 (ex. 取引先.取引先名)
Account.Name.getDescribe().getName()
■ データ型 (ex. 取引先.取引先名)
Account.Name.getDescribe().getType()
■ 制御項目 (ex. 取引先.種別)
Account.Type.getDescribe().getController()
  • 制御項目の設定がなければ null
  • 制御項目の設定があれば getDescribe() で制御側項目の詳細の取得が可能
■ 参照関係 (ex. 取引先.最終更新者)
Account.LastModifiedById.getDescribe().getReferenceTo()
  • 参照関係でなければ空
  • 参照関係であれば getDescribe で参照先オブジェクトの詳細の取得が可能
■ 選択リスト (ex. 取引先.種別)
Account.Type.getDescribe().getPicklistValues()
  • 選択リストでなければ空
  • 選択リストであれば値の Schema.PicklistEntry からラベルと値が取得可能



◆ メモ

  • 詳細、その他は参考先を参照のこと



◇ 環境

  • Salesforce - Developer Edition - API バージョン 24.0



◇ 参考


2012-04-02

Force.com : Apex で数値を文字列に変換したい


Apex で数値を文字列に変換したい



○ Integer#format()

Integer i = 12344567890;
System.debug(i.format());
■ 実行結果
1,234,567,890
Integer Methods の説明より
Returns the integer as a string using the locale of the context user
→ 戻り値はユーザのロケールを使用して整数値を文字列とする



◆ メモ

  • てっきり、Java の
    java.lang.Integer.toString(int i) 
    みたいなものだと思っていたら全く違っていた
  • 他も同じっぽい
    • Long#format()
    • Double#format()
    • Decimal#format()



○ String.valueOf(Integer i)

Integer i = 12344567890;
System.debug(String.valueOf(i));
// or
System.debug('' + i);
■ 実行結果
1234567890



◆ メモ

  • Java だと
    java.lang.String.valueOf(int i)
    
    public static String valueOf(int i) {
        return Integer.toString(i);
    }
    
    使うより、
    java.lang.Integer.toString(int i) 
    
    public static String toString(int i) {
        if (i == Integer.MIN_VALUE)
            return "-2147483648";
        int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
        char[] buf = new char[size];
        getChars(i, size, buf);
        return new String(0, size, buf);
    }
    
    使ったほうがメソッドの呼び出し回数が減るから後者を使っていたのだけれども迂闊だった
    (たぶん今も、前者のメソッドで後者のメソッドを呼び出していたはず)
  • 時折 Force.com Apex Code Developer's Guide に目を通そう!



◇ 環境

  • Salesforce - Developer Edition - API バージョン 24.0



◇ 参考


Force.com : Apex で数値がある桁数に満たない場合に左(上位桁)を 0 埋めしたい


Apex で数値がある桁数に満たない場合に左(上位桁)を 0 埋めしたい



○ 数値がある桁数に満たない場合に左(上位桁)を 0 埋めしたい

/**
 * パッディング:左ゼロ埋め
 * @param n 数値
 * @param len 桁数(数値の桁を含める)
 * @return 
 */
public String paddingLeftZero(Integer n, Integer len) {
    Integer nlen = (Math.floor(Math.log10(Double.valueOf(n))).intValue() + 1);
    if(nlen >= len) return String.valueOf(n);
    String s = '';
    for(Integer i=0, l=len-nlen; i<l; i++) {
        s += '0';
    }
    return s + String.valueOf(n);
}



◆ メモ

  • 桁数を超える数値はどうでもいいことにする
  • 数値の桁数を求めるところは
    Integer nlen = n.format().length();
    
    でもよかったのかもしれないと思いつつ、なんとなくこれは習慣



◇ 環境

  • Salesforce - Developer Edition - API バージョン 24.0

2012-03-29

Force.com : Apex で文字列に含まれる半角カタカナを全角カタカナに置換したい


Apex で文字列に含まれる半角カタカナを全角カタカナに置換したい



したくない!
所詮は、置換したい文字の対応 Map を作成してそれで置換しているに過ぎない :-)。



○ 文字列に含まれる半角カタカナを全角カタカナに置換したい

  • 半角カタカナの範囲は「FF61-FF9F」とする
  • 該当する濁点を含む文字が存在しない場合は[全角カタカナ + 全角濁点]とする
  • 該当する半濁点を含む文字が存在しない場合は[全角カタカナ + 全角半濁点]とする
■ 実装クラス
public class StringUtility {

    /** インスタンス */
    private static final StringUtility instance = new StringUtility();

    /** 16 進数表記 → 10 進数表記置換 Map */
    private Map<String, Integer> hexadecimalToDecimalMap;

    /** 半角カナ・置換 Map */
    private Map<String, String> replaceMap;
    /** 半角カナ・置換 Map Unicode */
    private Map<String, String> replaceHexadecimalMap;

    /** 濁点・置換 Map */
    private Map<String, String> replaceDakutenMap;
    /** 濁点・置換 Map Unicode */
    private Map<String, String> replaceDakutenHexadecimalMap;

    /** 半濁点・置換 Map */
    private Map<String, String> replaceHandakutenMap;
    /** 半濁点・置換 Map Unicode */
    private Map<String, String> replaceHandakutenHexadecimalMap;

    /**
     * コンストラクタ
     */
    private StringUtility() {

        // 16 進数表記 → 10 進数表記置換 Map 作成
        hexadecimalToDecimalMap = new Map<String, Integer>();
        hexadecimalToDecimalMap.put('0', 0);
        hexadecimalToDecimalMap.put('1', 1);
        hexadecimalToDecimalMap.put('2', 2);
        hexadecimalToDecimalMap.put('3', 3);
        hexadecimalToDecimalMap.put('4', 4);
        hexadecimalToDecimalMap.put('5', 5);
        hexadecimalToDecimalMap.put('6', 6);
        hexadecimalToDecimalMap.put('7', 7);
        hexadecimalToDecimalMap.put('8', 8);
        hexadecimalToDecimalMap.put('9', 9);
        hexadecimalToDecimalMap.put('A', 10);
        hexadecimalToDecimalMap.put('B', 11);
        hexadecimalToDecimalMap.put('C', 12);
        hexadecimalToDecimalMap.put('D', 13);
        hexadecimalToDecimalMap.put('E', 14);
        hexadecimalToDecimalMap.put('F', 15);

        // 半濁点・置換 Map 作成
        replaceHandakutenHexadecimalMap = new Map<String, String>();
        replaceHandakutenHexadecimalMap.put('FF8A,FF9F', '30D1');    // パ
        replaceHandakutenHexadecimalMap.put('FF8B,FF9F', '30D4');
        replaceHandakutenHexadecimalMap.put('FF8C,FF9F', '30D7');
        replaceHandakutenHexadecimalMap.put('FF8D,FF9F', '30DA');
        replaceHandakutenHexadecimalMap.put('FF8E,FF9F', '30DD');
        replaceHandakutenMap = new Map<String, String>();
        for(String key : replaceHandakutenHexadecimalMap.keySet()) {
            replaceHandakutenMap.put(unicodeToString(key.split(',', 0)), unicodeToString(replaceHandakutenHexadecimalMap.get(key).split(',', 0)));
        }

        // 濁点・置換 Map 作成
        replaceDakutenHexadecimalMap = new Map<String, String>();
        replaceDakutenHexadecimalMap.put('FF76,FF9E', '30AC');    // ガ
        replaceDakutenHexadecimalMap.put('FF77,FF9E', '30AE');
        replaceDakutenHexadecimalMap.put('FF78,FF9E', '30B0');
        replaceDakutenHexadecimalMap.put('FF79,FF9E', '30B2');
        replaceDakutenHexadecimalMap.put('FF7A,FF9E', '30B4');
        replaceDakutenHexadecimalMap.put('FF7B,FF9E', '30B6');    // ザ
        replaceDakutenHexadecimalMap.put('FF7C,FF9E', '30B8');
        replaceDakutenHexadecimalMap.put('FF7D,FF9E', '30BA');
        replaceDakutenHexadecimalMap.put('FF7E,FF9E', '30BC');
        replaceDakutenHexadecimalMap.put('FF7F,FF9E', '30BE');
        replaceDakutenHexadecimalMap.put('FF80,FF9E', '30C0');    // ダ
        replaceDakutenHexadecimalMap.put('FF81,FF9E', '30C2');
        replaceDakutenHexadecimalMap.put('FF82,FF9E', '30C5');
        replaceDakutenHexadecimalMap.put('FF83,FF9E', '30C7');
        replaceDakutenHexadecimalMap.put('FF84,FF9E', '30C9');
        replaceDakutenHexadecimalMap.put('FF8A,FF9E', '30D0');    // バ
        replaceDakutenHexadecimalMap.put('FF8B,FF9E', '30D4');
        replaceDakutenHexadecimalMap.put('FF8C,FF9E', '30D6');
        replaceDakutenHexadecimalMap.put('FF8D,FF9E', '30D9');
        replaceDakutenHexadecimalMap.put('FF8E,FF9E', '30DC');
        replaceDakutenHexadecimalMap.put('FF73,FF9E', '30F4');    // ヴ
        replaceDakutenMap = new Map<String, String>();
        for(String key : replaceDakutenHexadecimalMap.keySet()) {
            replaceDakutenMap.put(unicodeToString(key.split(',', 0)), unicodeToString(replaceDakutenHexadecimalMap.get(key).split(',', 0)));
        }

        // 半角カナ・置換 Map 作成
        replaceHexadecimalMap = new Map<String, String>();
        replaceHexadecimalMap.put('FF61', '3002');    // Halfwidth CJK punctuation
        replaceHexadecimalMap.put('FF62', '300C');
        replaceHexadecimalMap.put('FF63', '300D');
        replaceHexadecimalMap.put('FF64', '3001');
        replaceHexadecimalMap.put('FF65', '30FB');    // Halfwidth Katakana variants
        replaceHexadecimalMap.put('FF66', '30F2');
        replaceHexadecimalMap.put('FF67', '30A1');
        replaceHexadecimalMap.put('FF68', '30A3');
        replaceHexadecimalMap.put('FF69', '30A5');
        replaceHexadecimalMap.put('FF6A', '30A7');
        replaceHexadecimalMap.put('FF6B', '30A9');
        replaceHexadecimalMap.put('FF6C', '30E3');
        replaceHexadecimalMap.put('FF6D', '30E5');
        replaceHexadecimalMap.put('FF6E', '30E7');
        replaceHexadecimalMap.put('FF6F', '30C3');
        replaceHexadecimalMap.put('FF70', '30FC');
        replaceHexadecimalMap.put('FF71', '30A2');
        replaceHexadecimalMap.put('FF72', '30A4');
        replaceHexadecimalMap.put('FF73', '30A6');
        replaceHexadecimalMap.put('FF74', '30A8');
        replaceHexadecimalMap.put('FF75', '30AA');
        replaceHexadecimalMap.put('FF76', '30AB');
        replaceHexadecimalMap.put('FF77', '30AD');
        replaceHexadecimalMap.put('FF78', '30AF');
        replaceHexadecimalMap.put('FF79', '30B1');
        replaceHexadecimalMap.put('FF7A', '30B3');
        replaceHexadecimalMap.put('FF7B', '30B5');
        replaceHexadecimalMap.put('FF7C', '30B7');
        replaceHexadecimalMap.put('FF7D', '30B9');
        replaceHexadecimalMap.put('FF7E', '30BB');
        replaceHexadecimalMap.put('FF7F', '30BD');
        replaceHexadecimalMap.put('FF80', '30BF');
        replaceHexadecimalMap.put('FF81', '30C1');
        replaceHexadecimalMap.put('FF82', '30C4');
        replaceHexadecimalMap.put('FF83', '30C6');
        replaceHexadecimalMap.put('FF84', '30C8');
        replaceHexadecimalMap.put('FF85', '30CA');
        replaceHexadecimalMap.put('FF86', '30CB');
        replaceHexadecimalMap.put('FF87', '30CC');
        replaceHexadecimalMap.put('FF88', '30CD');
        replaceHexadecimalMap.put('FF89', '30CE');
        replaceHexadecimalMap.put('FF8A', '30CF');
        replaceHexadecimalMap.put('FF8B', '30D2');
        replaceHexadecimalMap.put('FF8C', '30D5');
        replaceHexadecimalMap.put('FF8D', '30D8');
        replaceHexadecimalMap.put('FF8E', '30DB');
        replaceHexadecimalMap.put('FF8F', '30DE');
        replaceHexadecimalMap.put('FF90', '30DF');
        replaceHexadecimalMap.put('FF91', '30E0');
        replaceHexadecimalMap.put('FF92', '30E1');
        replaceHexadecimalMap.put('FF93', '30E2');
        replaceHexadecimalMap.put('FF94', '30E4');
        replaceHexadecimalMap.put('FF95', '30E6');
        replaceHexadecimalMap.put('FF96', '30E8');
        replaceHexadecimalMap.put('FF97', '30E9');
        replaceHexadecimalMap.put('FF98', '30EA');
        replaceHexadecimalMap.put('FF99', '30EB');
        replaceHexadecimalMap.put('FF9A', '30EC');
        replaceHexadecimalMap.put('FF9B', '30ED');
        replaceHexadecimalMap.put('FF9C', '30EF');
        replaceHexadecimalMap.put('FF9D', '30F3');
        replaceHexadecimalMap.put('FF9E', '309B');
        replaceHexadecimalMap.put('FF9F', '309C');
        replaceMap = new Map<String, String>();
        for(String key : replaceHexadecimalMap.keySet()) {
            replaceMap.put(unicodeToString(key.split(',', 0)), unicodeToString(replaceHexadecimalMap.get(key).split(',', 0)));
        }
    }

    /**
     * Unicode を文字列に変換する
     * @param unicodes unicode 配列
     * @return 文字列
     */
    public String unicodeToString(String[] unicodes) {
        List<Integer> chars = new List<Integer>();
        for(String unicode : unicodes) {
            Integer c = 0;
            for(Double i=0, len=unicode.length(); i<len; i++) {
                c += (Math.pow(16, (len - i - 1)).intValue() * hexadecimalToDecimalMap.get(unicode.substring(i.intValue(), i.intValue()+1)));
            }
            chars.add(c);
        }
        return String.fromCharArray(chars);
    }

    /**
     * 置換が必要か判定する
     * ※ 半角カナ(FF61-FF9F)が含まれている場合置換を必要とする
     * @param s 文字列
     * @return true: 必要/false: 不必要
     */
    public Boolean isReplace(String s) {
        return !Pattern.matches('^[^\\uFF61-\\uFF9F]{1,}$', s);
    }

    /**
     * 置換
     * @param s 文字列
     * @return 置換文字列
     */
    public String replace(String s) {
        if(s == null || s == '') return s;

        // 半濁点
        if(!isReplace(s)) return s;
        for(String key : replaceHandakutenMap.keySet()) {
            s = s.replaceAll(key, replaceHandakutenMap.get(key));
        }

        // 濁点
        if(!isReplace(s)) return s;
        for(String key : replaceDakutenMap.keySet()) {
            s = s.replaceAll(key, replaceDakutenMap.get(key));
        }

        // 半角カナ
        if(!isReplace(s)) return s;
        for(String key : replaceMap.keySet()) {
            s = s.replaceAll(key, replaceMap.get(key));
        }

        return s;
    }

    /**
     * インスタンス取得
     * @return インスタンス
     */
    public static StringUtility getInstance() {
        return StringUtility.instance;
    }
}

■ 実行
// インスタンス取得
StringUtility sutility = StringUtility.getInstance();

// 置換する文字列作成(半角「パガア」)
String[] codes = new String[]{'FF8A','FF9F', 'FF76', 'FF9E', 'FF71'};
String str = sutility.unicodeToString(codes);

// 置換
String restr = sutility.replace(str);



◆ メモ

  • Apex クラスは UTF-8
  • Apex クラスで半角カタカナを記述しないが吉、ぽい
  • 置換 Map を修正した場合は isReplace() メソッドの修正も忘れないこと



◇ 環境

  • Salesforce - Developer Edition - API バージョン 24.0



◇ 関連記事


文字コード : Unicode


◆ Unicode


2012-03-28

Force.com : Apex で Unicode を文字列に変換したい


Apex で Unicode(\u0000)を文字列に変換したい



○ Unicode(\u0000)を文字列に変換したい

/**
 * Unicode を文字列に変換する
 * @param hexadecimal Unicode
 * @return 文字列
 */
static String unicodeToString(String unicode) {
    Map<String, Integer> hexadecimalToDecimalMap = new Map<String, Integer>();
    hexadecimalToDecimalMap.put('0', 0);
    hexadecimalToDecimalMap.put('1', 1);
    hexadecimalToDecimalMap.put('2', 2);
    hexadecimalToDecimalMap.put('3', 3);
    hexadecimalToDecimalMap.put('4', 4);
    hexadecimalToDecimalMap.put('5', 5);
    hexadecimalToDecimalMap.put('6', 6);
    hexadecimalToDecimalMap.put('7', 7);
    hexadecimalToDecimalMap.put('8', 8);
    hexadecimalToDecimalMap.put('9', 9);
    hexadecimalToDecimalMap.put('A', 10);
    hexadecimalToDecimalMap.put('B', 11);
    hexadecimalToDecimalMap.put('C', 12);
    hexadecimalToDecimalMap.put('D', 13);
    hexadecimalToDecimalMap.put('E', 14);
    hexadecimalToDecimalMap.put('F', 15);

    Integer c = 0;
    for(Double i=2, len=unicode.length(); i<len; i++) {
        c += (Math.pow(16, (len - i - 1)).intValue() * hexadecimalToDecimalMap.get(unicode.substring(i.intValue(), i.intValue()+1)));
    }
    List<Integer> chars = new List<Integer>();
    chars.add(c);
    return String.fromCharArray(chars);
}



◆ メモ




◇ 環境

  • Salesforce - Developer Edition - API バージョン 24.0



◇ 参考


Force.com : Apex で文字列が byte 数以下か判定したい


Apex で文字列が byte 数以下か判定したい



そもそも salesforce 内でシステムが完了していたら byte 数など気にする必要は、よほどのことがない限りないだろうと思う。



○ 文字列が byte 数以下か判定したい

/**
 * Byte 数以下か判定を行う
 * @param bytes byte 数
 * @param s 文字列
 * @return true: 以下/false: より上
 */
static Boolean isByteLessThan(Integer bytes, String s) {
    if(bytes < 0) return false;
    if(s == null || s == '') return true;

    Integer zenkakuLength = s.replaceAll('[\\uFF61-\\uFF9F\\u0020-\\u007E]', '').length();
    Integer hankakuLength = s.length() - zenkakuLength;
    return bytes >= zenkakuLength * 2 + hankakuLength;
}



◆ メモ

  • 文字列は長さしか取得できない
  • force.com 上で Apex コードは UTF-8 で処理されているらしいので、半角文字のコードは Unicode (半角カタカナをハードコーディングしたらシステムエラーが発生した)
  • 正規表現は Pattern (Java 2 Platform SE 5.0) を参照



◇ 環境

  • Salesforce - Developer Edition - API バージョン 24.0



◇ 参考


2012-03-14

Windows バッチファイル : テキストファイルにある文字列を置換したい


Windows バッチファイルでテキストファイルにある文字列を置換したい
  • sample_input.txt に「"foo"」という文字列(「"」も文字列)が出現したら「"boo"」という文字列に置換する
  • sample_output.txt に出力する (既にファイルが存在する場合は上書きする)



○ テキストファイルにある「"foo"」という文字列を「"boo"」という文字列に置換したい

■ ローカル変数版
@echo off

set ofilename=sample_output.txt

type nul >%ofilename%

setlocal ENABLEDELAYEDEXPANSION
for /f "delims=" %%A in (sample_input.txt) do (
    set line=%%A
    echo !line:"foo"="boo"!>>%ofilename%
)
endlocal

■ ラベル版
@echo off

set ofilename=sample_output.txt

type nul >%ofilename%

for /f "delims=" %%A in (sample_input.txt) do (
    set line=%%A
    call :REPLACE
)

GOTO :END

:REPLACE
    echo %line:"foo"="boo"%>>%ofilename%

:END



◇ 環境

Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

2012-03-10

Force.com : Apex で公開グループを削除したい


やっぱり、 Apex で公開グループを作成したら
Apex で公開グループを削除したい



○ 公開グループを削除したい


■ Trigger (API version 12.0)
trigger AccountTrigger on Account (after insert, after delete) {
    if(Trigger.isInsert) {
        
        Account[] accounts = Trigger.new;
    
        List<Group> groups = new List<Group>();
        for(Account account : accounts) {
            Group g = new Group();
            g.Type = 'Regular';                    // タイプ:公開グループ
            g.Name = account.Name;                 // グループ名
            groups.add(g);
        }
        insert groups;

    } else if(Trigger.isDelete) {

        Account[] accounts = Trigger.old;

        // ※ 公開グループでレコードの共有を行っている場合は、その共有を削除する必要がある ※

        Set<String> names= new Set<String>();
        for(Account account : accounts) {
            names.add(account.Name);
        }
        List<Group> groups = [SELECT Id FROM Group WHERE Name IN : names];
        if(groups.size() > 0) {
            delete groups;
        }

    }
}
※ Group の delete は API Version 12.0 以前で可能 ... ぽい!?
※ 公開グループでレコードの共有を行っている場合は、その共有を削除する必要がある
※ 公開グループにメンバーが存在しても関係なし(多分、主従関係なんだろう@適当)



◆ メモ

で Group の delete に関する記述が違う気がする ...

「Apex で公開グループを削除したい」なんで思わないが吉!



◇ 環境

  • Salesforce - Developer Edition - API バージョン 24.0



◇ 参考


Force.com : Apex で公開グループを作成したい


Apex で公開グループを作成したい



○ 公開グループを作成したい

  • 取引先(Account)作成の際に、公開グループは下記の通り作成する
    • グループ名: 取引先名
    • API 参照名: 'g_' + 取引先ID

■ Trigger (API version 24.0)
trigger AccountTrigger on Account (after insert) {
    Account[] accounts = Trigger.new;

    List<Group> groups = new List<Group>();
    for(Account account : accounts) {
        Group g = new Group();
        g.Type = 'Regular';                    // タイプ:公開グループ
        g.Name = account.Name;                 // グループ名
        g.DeveloperName = 'g_' + account.Id;   // API 参照名
        groups.add(g);
    }
    insert groups;
}
※ Group#DeveloperName は API version 24.0 以降より可能



○ 公開グループを作成し、メンバーに追加したい

  • メンバーは追加した取引先の所有者とする

■ Trigger (API version 24.0)
trigger AccountTrigger on Account (after insert) {
    Account[] accounts = Trigger.new;

    // Group 作成
    List<Group> groups = new List<Group>();
    for(Account account : accounts) {
        Group g = new Group();
        g.Type = 'Regular';                    // タイプ:公開グループ
        g.Name = account.Name;                 // グループ名
        g.DeveloperName = 'g_' + account.Id;   // API 参照名
        groups.add(g);
    }
    insert groups;

    // GroupMember 作成
    List<GroupMember> members = new List<GroupMember>();
    for(Account account : accounts) {
        GroupMember member = new GroupMember();
        for(Group g : groups){
            if(g.Name == account.Name) {
                member.GroupId = g.Id;
                break ;
            }
        }
        member.UserOrGroupId = account.OwnerId;
        members.add(member);
    }
    GroupMemberOperations.insertGroupMember(members);

}

■ ApexClass (API version 14.0)
public class GroupMemberOperations {

    public static void insertGroupMember(GroupMember member) {
        insert member;
    }

    public static void insertGroupMember(List<GroupMembe> members) {
        insert members;
    }

}
※ GroupMember の DML 操作は insert/update に限り、API Version 14.0 以前で可能



◇ 環境

  • Salesforce - Developer Edition - API バージョン 24.0



◇ 参考


2012-03-08

Force.com : sObject#clone()


sObject clone(Boolean opt_preserve_id
                     , Boolean opt_IsDeepClone
                     , Boolean opt_preserve_readonly_timestamps
                     , Boolean opt_preserve_autonumber)



○ パラメータ

  • opt_preserve_id
    • true: ID を引き継ぐ
    • false: ID はクリアされる
  • opt_IsDeepClone
    • true: 深いコピー(クローン化されたオブジェクトに変更を加えても元のオブジェクトに影響しない)
    • false: 浅いコピー
  • opt_preserve_readonly_timestamps
    • true: CreatedById, CreatedDate, LastModifiedById, LastModifiedDate を引き継ぐ
    • false: CreatedById, CreatedDate, LastModifiedById, LastModifiedDate はクリアされる
  • opt_preserve_autonumber
    • true: 自動採番を引き継ぐ
    • false: 自動採番はクリアされる



○ 例

※ シチュエーションはフィーリングで
  • Visualforce で[取引先]と[取引先責任者]をともに新規登録する画面を作成するとか
  • その際、[取引先責任者]を登録時に入力規則、Trigger 等でエラーが発生する可能性があるとか
  • エラー発生時は、元の画面に戻って再入力を促すとか
  • ※ この場合[取引先]はロールバックされ、insert はなかったことにされるのだけれども、オブジェクトには登録後に設定された ID がそのまま設定されたままになるのです
public Account account {get; set;}
public List<Contact> contacts {get; set}

public PageReference save() {

    Savepoint sp = Database.setSavepoint();

    try {
        insert account;
    } catch(DmlException e) {
        for(Integer i=0; i<e.getNumDml(); i++) {
            if(StatusCode.FIELD_CUSTOM_VALIDATION_EXCEPTION == e.getDmlType(i)) {
                ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, e.getDmlMessage(i), ''));
            }
        }
        Database.rollback(sp);
           return null;
    }

    for(Contact contact : contacts) {
        contact.accountid = account.Id;
    }
    try {
        insert contacts;
    } catch(DmlException e) {
        for(Integer i=0; i<e.getNumDml(); i++) {
            if(StatusCode.FIELD_CUSTOM_VALIDATION_EXCEPTION == e.getDmlType(i)) {
                ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, e.getDmlMessage(i), ''));
            }
        }
        account = account.clone(false, true, true, true);        // <<- ココ
        Database.rollback(sp);
        return null;
    }

    ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.INFO, 'OK', ''));

    return null;
}



◇ 環境

  • Salesforce - Enterprise Edition - API バージョン 24.0



◇ 参考


2012-03-07

Windows バッチファイル : 二重ループの内側のループを抜けたい


Windows バッチファイルで二重ループの内側のループを抜けたい
  • csv ファイルを読み込む
  • コンソールに出力する
  • 一項目一行に表示する
  • 項目に特定の文字(「foo」)が出現したら、同行のそれ以降の項目は表示しない



○ CSV ファイルを読み込んでいる際に、特定の文字が出現したら同行のそれ以降の項目は表示しない

@echo off

setlocal ENABLEDELAYEDEXPANSION
FOR /F "delims=" %%A IN (sample.csv) DO (
    set x=0
    FOR %%a IN (%%A) DO (
        IF %%a EQU "foo" (
            set x=1
        )
        IF "!x!" EQU "0" (
            echo %%a
        )
    )
)
endlocal
単にフラグ立てて、処理を回避しているだけ。



◇ 環境

Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

Windows バッチファイル : 任意の項目数の CSV ファイルの内容を読み込みたい


Windows バッチファイルで任意の項目数の CSV ファイルの内容を読み込みたい。
  • csv ファイルを読み込む
  • コンソールに出力する
  • 一項目一行に表示する



以前、 にてファイルの内容を読み込んでみたけど、for 文は 26 個までのトークンしか指定できない(help for)ぽいので他にないか探してみた。



○ CSV ファイルの内容を読み込みたい

@echo off

FOR /F "delims=" %%A IN (sample.csv) DO (
    echo +line: %%A
    FOR %%a IN (%%A) DO (
        echo -item: %%a
    )
)

Ex. カンマ区切り
■ sample.csv
"a","b","c","d","e"
"あ","い","う","え","お"
■ 実行結果
C:\>sample.bat
+line: "a","b","c","d","e"
-item: "a"
-item: "b"
-item: "c"
-item: "d"
-item: "e"
+line: "あ","い","う","え","お"
-item: "あ"
-item: "い"
-item: "う"
-item: "え"
-item: "お"
※ ダブルクォートつきの文字が %%a に設定されるぽい

Ex. タブ区切り
■ sample.csv
"a" "b" "c" "d" "e"
"あ" "い" "う" "え" "お"
■ 実行結果
C:\>sample.bat
+line: "a" "b" "c" "d" "e"
-item: "a"
-item: "b"
-item: "c"
-item: "d"
-item: "e"
+line: "あ" "い" "う" "え" "お"
-item: "あ"
-item: "い"
-item: "う"
-item: "え"
-item: "お"
※ ダブルクォートつきの文字が %%a に設定されるぽい



◇ 環境

Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

2012-02-18

Force.com : Apex でカスタマポータルユーザを作成したい


Apex でカスタマポータルユーザを作成したい



○ カスタマポータルユーザ作成
    // 取引先作成
    Account account = new Account(Name='取引先名');
    insert account;

    // プロファイル取得
    Profile cpprofile = [select id from Profile where name='Customer Portal Manager Custom'];

    // カスタマポータルユーザ作成

    // Worker(デフォルト)
    Contact contact = new Contact(email='test_cp@test.co.jp', firstname='foo', lastname='boo', accountid=account.id);
    insert contact;
    User cp = new User(alias = 'standt', email=contact.Email,
        emailencodingkey='UTF-8', lastname=contact.LastName, languagelocalekey='en_US',
        localesidkey='en_US', profileid = cpprofile.Id,
        timezonesidkey='America/Los_Angeles', username='test_cp@test.co.jp',
        contactId=contact.Id);
    insert cp;

    // Manager
    Contact contactManager = new Contact(email='test_cp_m@test.co.jp', firstname='foo', lastname='boo', accountid=account.id);
    insert contactManager;
    User cpManager = new User(alias = 'standt', email=contactManager.Email,
        emailencodingkey='UTF-8', lastname=contactManager.LastName, languagelocalekey='en_US',
        localesidkey='en_US', profileid = cpprofile.Id,
        timezonesidkey='America/Los_Angeles', username='test_cp_m@test.co.jp',
        contactId=contactManager.Id, PortalRole='Manager');
    insert cpManager;

    // Executive
    Contact contactExecutive = new Contact(email='test_cp_ex@test.co.jp', firstname='foo', lastname='boo', accountid=account.id);
    insert contactExecutive;
    User cpExecutive = new User(alias = 'standt', email=contactExecutive.Email,
        emailencodingkey='UTF-8', lastname=contactExecutive.LastName, languagelocalekey='en_US',
        localesidkey='en_US', profileid = cpprofile.Id,
        timezonesidkey='America/Los_Angeles', username='test_cp_ex@test.co.jp',
        contactId=contactExecutive.Id, PortalRole='Executive');
    insert cpExecutive;



◆ メモ
■ 注意
  • User オブジェクトの DML 操作を行うと
    System.DmlException: Insert failed. First exception on row 0; first error: MIXED_DML_OPERATION,非設定オブジェクトを更新した後の設定オブジェクト上の DML 操作 (またはその逆) は、許可されていません...
    等というエラーが発生する場合がある。(Ex. ユーザ insert 後に PortalRole の update を行う)
    その詳細は ここ を参照。仕方ない、仕様なのだから。



◇ 環境

  • Salesforce - Enterprise Edition - API バージョン 24.0



◇ 参考