プログラマ38の日記

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

雑記: Java開発ではまった思い出(JDBCやlog4J)

久しぶりにJavaを使うことがありました。

だいぶ前に、JavaのWeb開発をしていてその時にはまったことをふと思い出しました。

 

WebLogicのDataSourceのConnectionをcloseしておらずエラー

インスタンスを起動してしばらくすると、DBのConnectionが取得できずエラーになる現象が発生していました。調べた結果、Connectionを最後にcloseしていないことが判明。

  Context ctx = null;
  Hashtable ht = new Hashtable();
  ht.put(Context.INITIAL_CONTEXT_FACTORY,"weblogic.jndi.WLInitialContextFactory");
  ht.put(Context.PROVIDER_URL,"t3://hostname:port");
  Connection conn = null;

  try{ 
    ctx = new InitialContext(ht);
    javax.sql.DataSource ds = (javax.sql.DataSource) ctx.lookup ("myDataSource");
    conn = ds.getConnection();
  ・・・・・・ //途中の処理
    conn.close();  //必ず必要

絶対にcloseが必要ならWebLogic側でやってくれてもいいのになーと思いました。

 

WebLogicWebServiceでbooleanの引数にnullを指定されエラー

@WebMethod(operationName = "doSample")  
public Result doSample(
      @WebParam(name = "p1") String  p1, 
      @WebParam(name = "p2") boolean  p2
  ) {

こんなWebServiceを作成しました。

p2にnullを指定されるとWebLogicのエラーが呼び出し元に返ります。nullならfalseに変換してくれないのかと驚きました。

 

Log4jlog4j.propertiesの設定が正しく動かない

 ログが指定したファイルに書き出されず、何がいけないのかずっと調べていましたが、結局別のチームで作成したjarの中に別のlog4j.propertiesが含まれていてそちらが優先されていました。

アプリケーションでは、かなり多くのjarを含んでいて原因を特定するのは大変でした。jarの中身を確認する際に次を使ったのですがとても便利でした。

jd-gui

早くて使いやすいUIです。

 

今となっては懐かしい思い出ですが、当時は余裕がなくて大変だったなー。

Salesforce: 文字化けの制御で注意したいこと

他のシステムとの整合をとるため、Salesforceで入力できる文字を制限をかけたり、変換したい場合があります。

次のような要件があると思います。

  1. Shift_JISで定義されていない文字をエラーとしたい
  2. Shift_JISで定義されていない文字は、全角四角"□"に変換したい

※この例で、Shift_JISはCP932とします。

 

1の要件には次のようなコードが簡単です。

String convertbefore= 'a①bⅰc𠀋ef𠀋g'; //文字化けするか判定する文字

String charSet = 'Windows-31J';
String encodeStr = EncodingUtil.urlEncode(convertbefore,charSet);
String decodeStr = EncodingUtil.urlDecode(encodeStr, charSet);

if( convertbefore == decodeStr ) {
System.debug('文字化けする文字は無い');
} else {
System.debug('文字化けする文字が有る');
}

EncodingUtil.urlEncodeとurlDecodeで、文字化けする文字が「?」になるのを利用して、元の文字と比較することで、文字化けする文字列かどうかは判断できます。

ただ、この場合はどの文字が文字化けするかまではわかりません。

 

2の要件では、文字化けする文字まで特定する必要があります。この時にサロゲートペアの文字の対処が必要です。

サロゲートペアの文字でも、EncodingUtil.urlEncodeとurlDecodeで、文字化けする文字が「?」1文字となるのは変わりません。

String convertbefore= 'a①bⅰc𠀋ef𠀋g';

String charSet = 'Windows-31J';
String encodeStr = EncodingUtil.urlEncode(convertbefore,charSet);
String decodeStr = EncodingUtil.urlDecode(encodeStr, charSet);


Integer beforeidx = 0;
Integer decodeidx = 0;

String convertafter = '';

for( ; decodeidx < decodeStr.length() ;  ){


  String charval_before = '';
  if(    convertbefore.substring(beforeidx,beforeidx+1).codepointat(0) >= 55296
                && convertbefore.substring(beforeidx,beforeidx+1).codepointat(0) <= 56319 ){
    charval_before = convertbefore.substring(beforeidx , beforeidx+2);    
    beforeidx++;
  } else {
    charval_before = convertbefore.substring(beforeidx , beforeidx+1);
  }

  String charval_decode = decodeStr.substring(decodeidx, decodeidx + 1);

  if( charval_before == charval_decode ){
    convertafter += charval_before;
  } else {
    convertafter += '□';
  }

  beforeidx++;
  decodeidx++;
}

System.debug(convertafter);

 1文字ずつ同じかどうか見て、違った文字は文字化けした文字として扱い”□"に置き換えていますが、サロゲートペアの文字は、きちんと上位サロゲートと下位サロゲートで1文字として文字を作る必要があります。

Excel: テンプレートテキスト内の変数を展開してテキスト出力するマクロを作りました

あるテキストの中身を、一部書き換えて別のテキストを作りたい時があります。

 

例えば、Salesforceでロールや、公開グループ、キューなどを作成する場合に、ファイルの名前とファイル内の一部を変更すればデプロイで新規作成ができます。

※ロールや公開グループ、キューであればデータローダで作成できてしまうので、あまりいい例ではありませんでした。デプロイではなく、MetadataAPIのcreateMetadataを使ってコンポーネントを作成する場合で、1つサンプルのコンポーネントを取得して、XMLEncoderでxmlファイルにすれば、そのxmlファイルを元に新規用のxmlが作れます。例えばListViewを大量に複製した場合など便利です。

 

10個程度であれば、1つ1つ手で作ればいいのですが、100個や1000個となると手で作ると時間もかかりますし、作業する手が疲れてしまいます。

 

前置きが長いですが、テンプレートのファイルに差し込む変数を指定して別のファイルに出力するマクロを作成しました。

 

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

 

 使い方

f:id:crmprogrammer38:20170724121049p:plain

  1. テンプレートファイルと展開後ファイルをフルパスで指定します。
    ファイルは全てShift_JISです。(なので、Salesforceメタデータを出力する場合は、xml宣言でutf-8から、Windows-31Jに変更が必要です)
  2. 展開する変数の値を記載します。
  3. 環境変数を展開して保存」ボタンをクリックします。

テンプレートファイルの例

<?xml version="1.0" encoding="Windows-31J"?>
<Role xmlns="http://soap.sforce.com/2006/04/metadata">
    <caseAccessLevel>Edit</caseAccessLevel>
    <contactAccessLevel>Edit</contactAccessLevel>
    <description>%value03%</description>
    <mayForecastManagerShare>false</mayForecastManagerShare>
    <name>%value01%</name>
    <opportunityAccessLevel>Edit</opportunityAccessLevel>
    <parentRole>%value02%</parentRole>
</Role>

 例えば、Salesforceのロールを新規作成するためにxmlファイルを作成する場合はテンプレートファイルを上のようにしておきます。xml宣言では、Windows-31Jに指定します。そして、変数を差し込みたい箇所に%変数名%とします。(VBAの処理内では、プロセスの環境変数を展開しているだけです。)

 

他にも色々テンプレートから変数を展開したい時があると思いますのでお試しください。

Salesforce: Apexで共有オブジェクト(・・Share)にロールを指定する

Salesforceで特定のユーザにデータを参照・編集させる設定は、共有設定を使うか、Apexで共有オブジェクトを操作するかになります。

 

共有オブジェクトに特定のユーザのロールを指定したい時があります。

例えば、代表者(User)を入力すると、その代表者は「参照・編集」、その代表者のロールに所属するユーザは「参照」ができるという仕組みにしたいとします。

その際はApexで共有オブジェクトを操作するのが簡単です。次のようになります。

 

  1. Apexにて、"編集"の「カスタムオブジェクトのアクセス権」で、代表者のユーザIDを共有オブジェクトに登録。
  2. Apexにて、"参照"の「カスタムオブジェクトのアクセス権」で、代表者のユーザのロールに対応するグループIDを共有オブジェクトに登録。

上記2のユーザのロールに対応するグループIDの関係は次のようになります。

f:id:crmprogrammer38:20170721130637p:plain

1つのロールに対してグループは複数あります。データイメージは次の通りです。

f:id:crmprogrammer38:20170721131335p:plain

 種別の内部の値は次の通りで、この中から指定したい種別を選びます。

種別 内部で指定する値
ロール Role
ロール & 内部下位ロール RoleAndSubordinates
ロール、内部 & ポータル下位ロール RoleAndSubordinatesInternal

 

上記を踏まえて、2の代表者のユーザのロールに対応するグループIDを共有オブジェクトへ登録する際には次のようなデータになります。

ラベル 名前 セットする値
参照先 ID ParentId 参照するレコードID
ユーザ / グループ ID UserOrGroupId ユーザのロールに対応するグループID
カスタムオブジェクトのアクセス権 AccessLevel Read
テリトリー割り当て方法 RowCause Manual もしくは 「Apex 共有の理由」の理由名

共有ボタンから手動でも参照させたい人を指定する場合、 「Apex 共有の理由」を作成してその理由名を、テリトリー割り当て方法にセットした方がいいと思います。

というのは、ApexからもManualで登録してしまうと、判断がつかなくなりますので。

Salesforce: ユーザの「承認申請メールを受信する」項目をまとめて更新したい時

結論として、データローダや、ApexClassでまとめて更新する手段が用意されていないようです。少し作業を効率化できるちょっとしたアイデアを書きます。

 

「承認申請メールを受信する」項目をまとめて更新したい時

例えば、そもそも承認申請のメールが不要であったり、別のプログラムで承認申請時のメールを飛ばすので標準のメールは飛ばしたくない場合、ユーザの「 承認申請メールを受信する」 を”受信しない”にします。

f:id:crmprogrammer38:20170720161307p:plain

でも、 「 承認申請メールを受信する」項目は、データローダでは更新できない項目で、100名、1000名のユーザに対してでもユーザの編集画面から1件、1件変更が必要になります。

その作業を少し楽にする方法として次を考えました。

 

1.ユーザのカスタム項目で次の数式を作成します。

HYPERLINK(“/” + Id + “/e?receiveApprovalsEmails=SEND_NONE”, “初期値:受信しない" , "_blank")

2.ビューを作成し、1で作成した数式項目を表示するようにしておきます。

3.上記数式項目のリンクをクリックすると「 承認申請メールを受信する」の初期値で”受信しない”が指定された状態で表示されるので、そのまま保存します。

4.これを対象ユーザ数分繰り返します。

 

結局1件1件ですが、画面のスクロールと選択リストから選ぶ操作が省けます。

 

補足

上記の例では”受信しない”を初期値としていますが次のようにパラメータを変えることでその他の選択肢を初期値にすることができます。

「承認申請メールを受信」の値 パラメータで指定する値
承認者または代理承認者である場合 SEND_ALL
自分が承認者である場合のみ SEND_APPROVER
自分が代理承認者である場合のみ SEND_DELEGATED
受信しない SEND_NONE

SQL: MS-ACCESSで、結合条件で大文字・小文字を区別する

MS-ACCESSの結合は、通常では大文字・小文字の区別がありません。

たまに、大文字・小文字を区別して結合をしたい時があります。
例えば、Salesforceの15桁のSalesforceIDで結合する場合などです。

 

そんな時には、「strcomp(項目1, 項目2, 0) = 0 」を使うことで、大文字・小文字を区別して結合することができます。

 

[大文字・小文字を区別しないで結合するサンプルSQL]

SELECT * FROM sample1 
INNER JOIN sample2 
ON (sample1.field1 = sample2.field1)

 

[大文字・小文字を区別して結合するサンプルSQL]

SELECT * FROM sample1 
INNER JOIN sample2 
ON (sample1.field1 = sample2.field1)
and (strcomp(sample1.field1 , sample2.field1,0)=0)

上記のように通常の結合条件の後に、strcompでバイナリ比較を行うことで大文字・小文字を区別して結合することができます。

 

[補足]

普通に結合条件の結果だけ考えれば、下記のように通常の結合条件は不要でstrcompだけで欲しい結果は得られます。

SELECT * FROM sample1 
INNER JOIN sample2 
ON  (strcomp(sample1.field1 , sample2.field1,0)=0)

 ですが、上記の記述では、結合する項目にインデックスがあっても使ってくれません。(直積の実行プランになります)

思いのほか結合処理に時間がかかってしまうので、面倒でも結合条件とstrcompの比較条件と両方の記載が必要です。

 

strcompでの比較と通常の文字の比較は次のような結果になります。

No 比較の内容 値1 値2 strcomp(値1,値2,0) iif( 値1 = 値2 , 'equal' , 'not equal')
1 同じ値での比較 ABC ABC 0 equal
2 大文字と小文字の比較 ABC abc -1 equal
3 小文字と大文字の比較 abc ABC 1 equal
4 空文字と空文字の比較     0 equal
5 文字とnullの比較 ABC <null> <null> not equal
6 nullと文字の比較 <null> ABC <null> not equal
7 nullとnullの比較 <null> <null> <null> not equal

※nullと空文字の表記が難しかったので、nullは<null>と表現しています。

 

Salesforce: メタデータ「フィード条件(CustomFeedFilter)」エクスポート時にエラーとなる件

エクスポートしようとするとエラーが発生するメタデータコンポーネントがあります。
それは、「フィード条件(CustomFeedFilter)」なのですが、エクスポートしようとすると
"UNKNOWN_EXCEPTION msg: null: Need to specify full name, Name:XXX, Delimiter:."となります。(XXXは、フィード条件の名前です。)

 

他のメタデータコンポーネントと同様に、
PackageTypeMembers.setNameで"CustomFeedFilter" , PackageTypeMembers.setMembersでFilePropertiesのfullNameを指定しているので、プログラムの書き方の問題ではないと思います。

 

2017/7/24 追記

エラーの原因がわかりました。通常のメタデータは、MetadataAPIで取得したメタデータの名前をそのままリクエストすれば、対象のメタデータがダウンロードができますが、「CustomFeedFilter」は、取得したメタデータの名前だけではエラーとなり、取得したメタデータの名前の先頭に"Case."を付与しないといけませんでした。

でも、Case.がつくということは、その他のオブジェクトにもフィード条件が指定できるようになる可能性があり、"Case."を固定値で付与するのではなく、ワイルドカードで指定するように修正を行いました。エクスポートツールはワイルドカードで指定するように変更したものを再アップロードしました。ご利用されている方はあらためてダウンロードお願いします。

 

以下不要となりました。

APIVer39とAPIVer40のメタデータエクスポートツールを作成していますが、CustomFeedFilterのエクスポートでエラーが発生する場合は、除外するメタデータコンポーネントにCustomFeedFilterを指定してご利用下さい。

set metadata.exclude=CustomFeedFilter

 

 

crmprogrammer38.hatenablog.com

 

 

crmprogrammer38.hatenablog.com