プログラマ38の日記

主にプログラムメモです。

Java: XML中の制御コードについて

XMLでは、制御コードは改行やタブなどの決められたもの以外は許可されていません。(こちらの説明が詳しいです)

なので、XMLで値をセットする際には許可されていない文字コードは除去する必要があります。(除去する方法はこの記事を参考にさせて頂きました)

 

そして、XMLを扱ってデータのやり取りを行うWebServiceでもこのXMLの許可されていない制御コードの対応が必要です。

 

以前JavaWebServiceクライアントについて記載したことがありますが、それぞれについて許可されていない制御コードは次の通りの動きです。

Java ライブラリ XML中の許可されていない制御コード 備考
Axis1.4 エラー リクエスト用のXMLを作成する際に、許可されていない制御コードがあるとエラーとなる。
Axis2 (xmlbeans) エラー リクエスト用のXMLを作成する際に、許可されていない制御コードがあるとエラーとなる。
wsimport エラー リクエスト用のXMLを作成する際に、許可されていない制御コードがあるとエラーとなる。
apache cxf エラー リクエスト用のXMLを作成する際に、許可されていない制御コードがあるとエラーとなる。
wsc(Salesforce専用) 除去する

エラーとならない。
wscの中でXML作成ロジックを書いている。

上記の通りとなり、Salesforce専用のwscは許可されていない制御コードを指定してもエラーとなりません。(wscを利用しているデータローダもエラーとなりません)

 

レガシーから移行したデータや、Web画面から入力されたデータには、垂直タブやシフトイン・シフトアウトなど入っている場合があります。

wsc以外のライブラリではデータの受け渡しで許可されていない制御コードは取り除く処理が必要です。

 

最後に

Salesforceとの連携プログラムを作成するのはwscが便利なのだとあらためて感じました。

 

といっても、wscも癖はあるので用途に応じて使い分けが必要だなと思います。

SalesforceからSalesforceに連携する場合は、wsc以外であれば、取得した値をそのままcreate、upsert、updateに使えばいいのですが、wscだけはint型はintへ、date型はdateへ、datetime型はCalendar、base64型はbinaryへ変換などの処理が必要になります。

 

連携プログラムを作る際の参考になれば幸いです。

Salesforce: 時間型(ベータ)が追加されている件と項目作成マクロの更新の件

Winter18のバージョンアップでカスタム項目の型に、時間型が追加されていますね。

f:id:crmprogrammer38:20171030093621p:plain

まだベータですが、時分を入力する項目を次のように用意することが多いので便利になります。

・時分を入力するテキスト欄を用意

・HH:MMを保証する入力規則を用意

・もしくは時間の選択リスト、分の選択リストを用意

 

Apex Class では、Time Classが時間型に対応するようです。

 

後、以前ご紹介したExcelでカスタム項目を作成するマクロですが、ヘルプテキストと説明項目を追加しました。

crmprogrammer38.hatenablog.com

さすがに、API名、ラベル、型だけしか定義できないのはどうかと思い、改修をしました。 

お試しください。

 

 

Java: 外部ライブラリの定数を参照する時は静的コンパイルに気をつけよう

他のライブラリを参照して開発する時に、参照先の定数を使うときがあります。

例えば、WebServiceWSDLを展開したライブラリで、WebSerivceのendpointは定数として定義されていて、その定数を使いたい時があります。

 

開発しているプログラムでその定数を使う時に注意することのメモです。 

結論としては、Javaの静的コンパイルを正しく理解した上で開発する必要があります。

 

例えば、次のようなライブラリがあります。

[Test2.jar]
public class Test2 {
  
  public static final String str1 = "string1";
  public static final String str2 = "string2";

  public static String getStr2(){
    return str2;
  }
}

そして次のプログラムで上記のTest2.jarを呼び出します。

public class Test1 {

  public static void main(String[] args) throws Exception{
    System.out.println(Test2.str1);
    System.out.println(Test2.getStr2());
  }
}

実行結果は、当たり前ですが
string1
string2
となります。

 

ですが、この後Test2.jarを次のように更新します。

[Test2.jar]
public class Test2 {
  
  public static final String str1 = "update:string1";
  public static final String str2 = "update:string2";

  public static String getStr2(){
    return str2;
  }
}

そして先ほどのTest1を再度コンパイルし直さないで実行すると実行結果は、

string1
update:string2
となります。

これはJavaの静的コンパイルという仕組みで、定数をプログラムで使用するとコンパイル時に定数が文字列として展開されるからになります。

 

参照先のライブラリの定数が変更される可能性がある場合、参照先の定数を使う箇所は次のような対応が考えられます。

①参照先のライブラリが更新されたら、常に参照元コンパイルし直す。

②参照先のライブラリの定数を参照しないよう参照先でgetterを用意してもらい、getterを使用する。

③参照先のライブラリの定数を静的コンパイルされないようリフレクションで定数を取得する。
リフレクションの例  Test2.class.getField("str1").get(null);

 

①は確実です。ちゃんとantでビルドすれば問題ないですが、手間がかかります。(複数のjarが混在しているとなかなか大変になります)

②は参照先のライブラリの開発側に対応を依頼することになります。対応してくれればいいですが、何らかの要因で頼めない場合はこの方法はとれません。

③は最後の手段ですが、①の手段以外で、どうしても静的コンパイルを回避したいならこの方法になります。

最後に

定数って静的コンパイルされるんだよなーというのがわかっていれば事前に対応しておく、もしくは運用を考えておくということができますが、後から問題に直面すると原因特定に時間がかかってしまうものだと思います。

Salesforce: 開発でちょっと便利になる関数のメモ(トリガ用:参照項目をSet<Id>で取得、文字列操作:lenb、leftb、 rightb)

いくつか関数を作ったのでメモです。

 

用意した関数は次の4つです。

  1. トリガでよく使う参照項目のIdのSetを作る
  2. 文字のバイト数(Excelのlenbと同等)
  3. 文字のバイト数(Excelのleftbと同等)
  4. 文字のバイト数(Excelのrightbと同等)

 

1. トリガでよく使う参照項目のIdのSetを作る

トリガで参照項目(参照関係や主従関係)の項目のIdのSetを作り、参照先のオブジェクト情報を取得することは多いですが、その時にいちいちfor文を作るのが手間なので作りました。

    public static Set<Id> idSetOfField(List<Sobject> rows, String idFieldName ){
        
        if(rows == null){
            return null;
        }
        
        Set<Id> retset = new Set<Id>();
        
        for(Sobject row : rows){
            Object val = row.get(idFieldName );
            
            if(val != null){
                retset.add( (Id)val );
            }
        }
        return retset;
    }

文字列で項目名を指定すればIdのSetが取得できます。

使う時は、
    Set<Id> ownerset = SampleUtils.idSetOfField(Trigger.New, 'OwnerId');
などとなります。


2. 文字のバイト数(Excelのlenbと同等)

    public static Integer lenb(String val){
    
        String ex_hankakumoji = '。、・ヲィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン゙';
    
        
        if( val == null ){
            return null;
        }
    
        Integer bytelength = 0;
        for(Integer idx=0; idx< val.length(); idx++ ){
            String charval = val.substring(idx, idx+1);
            
            if( charval.isAsciiPrintable() ){
                bytelength = bytelength +1;
            } else if( ex_hankakumoji.indexof(charval) != -1 ){
                bytelength = bytelength +1;
            } else {
                bytelength = bytelength +2;
            }
        
        }
    
        return bytelength;
    }


3. 文字のバイト数(Excelのleftbと同等)

2のlenbを使っているのでlenbと同じクラス内に定義します。

    public static String  leftb(String val, Integer bytelength){
        
        if(bytelength <= 0 ){
            return null;
        }
        
        if( val == null ){
            return null;
        }
        
        
        Integer bytecount = 0;
        String retstr = '';
        
        for(Integer idx=0; idx < val.length(); idx++ ){
            
            String charstr = val.substring(idx,idx+1);
            
            Integer strlenb = lenb(charstr);
            
            if( bytelength < (bytecount + strlenb) ){
                
                if( strlenb == 2  && (bytecount + strlenb - bytelength) == 1 ){
                    retstr = retstr + ' ';
                }
                
                break;
            } else {
                retstr = retstr + charstr;
                bytecount = bytecount + strlenb;
            }
        }
    
        return retstr;    
    }


4. 文字のバイト数(Excelのrightbと同等)

2のlenbを使っているのでlenbと同じクラス内に定義します。

    public static String  rightb(String val, Integer bytelength){
        
        if(bytelength <= 0 ){
            return null;
        }
        
        if( val == null ){
            return null;
        }
        
        
        Integer bytecount = 0;
        String retstr = '';
        
        for(Integer idx=val.length()-1; idx > -1; idx-- ){
            
            String charstr = val.substring(idx,idx+1);
            
            Integer strlenb = lenb(charstr);
            
            if( bytelength < (bytecount + strlenb) ){
                
                if( strlenb == 2  && (bytecount + strlenb - bytelength) == 1 ){
                    retstr = ' ' + retstr;
                }
                
                break;
            } else {
                retstr = charstr + retstr;
                bytecount = bytecount + strlenb;
            }
        }
    
        return retstr;    
    }

 

Salesforce: メタデータエクスポートツールのバージョンを上げました(APIVer41)

Salesforceのバージョンアップに伴い、コマンドラインで動作するメタデータのエクスポートツールのバージョンを上げました。

 

ダウンロードはこちらです。

 

今回のバージョンアップでは、メタデータで目立って追加されているものはないように思います。(TopicsForObjectsが追加されていたり、Workflowでは、何も指定していないオブジェクトはダウンロードしないようになっていたりしています。)

後、メタデータでは取得できないSobjectは増えています。

 

以前のバージョンは次になります。

 [APIVer40]

crmprogrammer38.hatenablog.com

 

 [APIVer39]

crmprogrammer38.hatenablog.com

 

最後に

以前、CustomFeedFilterでエラーになることが判明してエクスポートツールではワイルドカードで取得するようにしているのですが、今回のバージョンアップでは解消されていませんでした。

crmprogrammer38.hatenablog.com

 

 以前、承認プロセスのメタデータの一覧を取得すると物理名ではなく論理名が帰ってくる不具合があったのですが、しばらくはそのままだったなーとちょっと思い出しました。

 

Java/その他: テキストファイルで、utf8からshift_jis(CP932)への変換時に注意すること

テキストファイルなどutf8だと扱いづらく、shift_jisに変換したい時があります。

欲しいコマンドがみつからない時に、自分で作成するときの注意事項です。

 

結論として、utf8からshift_jisへの変換で、プログラムで工夫することで文字化けを回避できる文字があります。

 

そもそも utf8からshift_jisの変換はshift_jisよりutf8の方が文字が多いため、文字化けは回避できません。ですが、次の文字は次のようにマッピングが可能で文字化けを回避できます。

 

shift_jisで文字化けする文字 shift_jisで文字化けしない文字
表示 文字コード 表示 文字コード
\u2212 \uFF0D
\u301C \uFF5E
¢ \u00A2 \uFFE0
£ \u00A3 \uFFE1
¬ \u00AC \uFFE2
\u2014 \u2015
\u2016 \u2225

※OSのバージョンでは変換しなくても文字化けしない場合があります。

 

Java文字コード変換プログラムを作成する場合に、上の変換を考慮する前と後では次のようになります。

 

[文字の変換を考慮する前]

    String in = "読込むファイル名";
    String out= "書込むファイル名";
    
    String readEncoding = "utf8";
    String writeEncoding= "Windows-31J";
    

    BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(in),readEncoding));
    
    BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(out),writeEncoding));
    

    while(true){
      String line = br.readLine();
      
      if(line ==null){
        break;
      }

      bw.write(line);
      bw.newLine();

    }
    
    br.close();
    bw.close();

上のように工夫することで、shift_jisでも文字化けせずに済みます。

 

特に「〜」は、OracleDBからのデータ取得や、webでのデータ取得の中で、文字化けしてしまう文字コードとなっている時が多いなーと感じています。

JDBC: 接続URLではまったこと(SQLServer , MySQLでメモリエラーが発生する件)

Javaで、SQLServerMySQLのデータ連携プログラムを書くとき注意事項のメモです。(ある程度の件数がある時の場合です。)

 

結論としては jdbcのURLのパラメータをきちんと書かないと、「out of memory」のエラーになる可能性が高くなります。

 

「out of memory」エラーになる書き方

SQLServer
jdbc:sqlserver://[ホスト名]:[ポート番号];databaseName=[データベース名]

MySQL
jdbc:mysql://[ホスト名]:[ポート番号]/[データベース名]

 

「out of memory」エラーにならない書き方

SQLServer
jdbc:sqlserver://[ホスト名]:[ポート番号];databaseName=[データベース名];SELECTMETHOD=cursor

MySQL
jdbc:mysql://[ホスト名]:[ポート番号]/[データベース名]?useCursorFetch=true&defaultFetchSize=1000

 

SQLServerでは、selectmethod=cursor 、 MySQLではuseCursorFetch=trueでかつdefaultFetchSize=XXXを指定しないと取得結果がメモリに展開されてしまい、データがメモリの容量を超えるとエラーになります。

 

SQLServerは、selectmethod=cursorを設定しない場合、デフォルトでselectmethod=direct扱いとなり、一方的にDBから結果が送られてきます。DBからの結果を全てメモリに展開してから次の処理が動くわけではないのがまだましです。

ですが、MySQLでは、useCursorFetch=trueでかつdefaultFetchSize=XXXを指定しないと、全てメモリに展開してから次の処理が動きます。100万件程度を操作するとほぼメモリエラーになると思います。

 

最後に

JavaベースのETLツールもありますので、jdbc urlには充分注意が必要です。

jdbc urlでは、パラメータを指定しない場合は、遅いけどエラーにならない動きを選択して欲しいなと思いました。

 

MySQLである程度のデータを取得する場合、パラメータを指定しないとまずエラーというのはひどい設計だと思います。Oraclejdbcドライバではパラメータを指定しなくてもエラーにならないので安定してるなーと思います。