2017年12月31日日曜日

Java : interfaceのprivateメソッド

Java9からinterfaceにprivateメソッドを記述することができるようになった。



privateメソッドのため子クラスから参照することは出来ない。interfaceに実装したdefaultメソッドやstaticメソッドすることを想定した機能だと思う。使うときにはこのように実装する。普通に継承すればいいだけ。



abstractクラスとinterfaceの違いがなくなってしまったような気がする。

違いとして考えられるのはinterfaceはいくらメソッドを書けるといってもinterfaceなので、いくつでもimplementsすることが出来る。



つまりinterfaceだとabstractとは異なり多重継承のようなことが出来てしまう。これはdefaultメソッドを使えるようになったJava8のときから出来る。Java9になってprivateメソッドも実装できるようになったのでこの機能がより強化された形になったと思う。

多重継承というとあまりいいイメージがない。C++で開発を行うときにも内部的な規約で禁止していたりと、何かと嫌われている感じがする。ただ、うまくやればより柔軟な設計が可能になるわけで、使う、使わないの選択はプログラマが行うほうがいいと思う。

2017年12月30日土曜日

Java : List / Map / Set のファクトリメソッド

Java9から List / Map / Set にファクトリメソッドが追加されている。

Java8までだと、Listを使うにはインスタンスを作成し、その後に add する必要があった。


これがJava9になると、このように書くことができるようになった。



このコードはJava8だとコンパイルエラーになってしまう。

Listなどのコレクションクラスを使う場合にいちいち実装クラス(ArrayListやLinkedList)を指定してやらないといけないところが面倒だと感じていたので、この面倒さが解消されるのが嬉しい。Listの使用方法によって、実装クラスを使い分けたほうが速度面で優位なのかもしれないけれど、そこまでシビアに処理能力の差を求められることも少ないんじゃないだろうか。もちろん、作っているプログラムによるわけだけれど。

空のListを作りたい場合は、「List list = List.of();」とする。



Setでも同様にコードを書くことができる。



Setは要素の重複が許されないので、下記のように書いてしまうと実行時にIllegalArgumentExceptionが発生してしまうので注意が必要となる。



最後にMapも試してみる。



Mapでは「Key1, Value1, Key2, Value2 ・・・・」というようにKeyとValueを繰り返し書いていくことになる。注意しないといけないのは、Mapの場合は10個までしか指定できないこと。なので、下記のように書くとコンパイルエラーとなる。



11個以上指定する場合は Map.ofEntries メソッドを使う。



2017年12月27日水曜日

Java : JShell

Java9から導入されたJavaのREPL環境であるJShellで少し遊んでみたので、やったことをまとめておく。以下、動作環境はWindows8.1、Windows PowerShell。

まず、JShellのディレクトリだが、JDKインストールフォルダ配下のbinディレクトリ。私の環境だと「C:\Program Files\java\jdk-9.0.1\bin」となっている。


JShell上でプログラムを実行してみる。


変数も使える。作った変数は /vars とコマンドを打つと確認できる。


メソッドの作り方。1行でも書くことはできるが、行の途中で改行するとJShellの方で勝手に判断してくれる。作ったメソッドは /methods とコマンドを打つと確認できる。


クラスも作ったりできるのだけれど、書くのが大変なのでこの辺りで・・・。ちなみに、/help とコマンドを打つとヘルプを見られる。


ヘルプの全文は以下の通り。色々な機能を持っていることが分かる。

|  Java言語の式、文または宣言を入力します。
|  または、次のコマンドのいずれかを入力します:
|  /list [<name or id>|-all|-start]
|       入力したソースをリストします
|  /edit <name or id>
|       名前またはIDで参照されるソース・エントリを編集します
|  /drop <name or id>
|       名前またはIDで参照されるソース・エントリを削除します
|  /save [-all|-history|-start] <file>
|       ファイルにスニペット・ソースを保存します。
|  /open <file>
|       ソースの入力としてファイルを開きます
|  /vars [<name or id>|-all|-start]
|       宣言された変数およびその値をリストします
|  /methods [<name or id>|-all|-start]
|       宣言されたメソッドおよびその署名をリストします
|  /types [<name or id>|-all|-start]
|       宣言された型をリストします
|  /imports
|       インポートされたアイテムをリストします
|  /exit
|       exit jshell
|  /env [-class-path <path>] [-module-path <path>] [-add-modules <modules>] ...
|       評価コンテキストを表示または変更します
|  /reset [-class-path <path>] [-module-path <path>] [-add-modules <modules>]...
|       reset jshell
|  /reload [-restore] [-quiet] [-class-path <path>] [-module-path <path>]...
|       リセットして関連する履歴をリプレイします -- 現在または以前(-restore)
|  /history
|       入力した内容の履歴
|  /help [<command>|<subject>]
|       jshellに関する情報を取得します
|  /set editor|start|feedback|mode|prompt|truncation|format ...
|       jshell構成情報を設定します
|  /? [<command>|<subject>]
|       jshellに関する情報を取得します
|  /!
|       最後のスニペットを再実行します
|  /<id>
|       IDでスニペットを再実行します
|  /-<n>
|       n回前のスニペットを再実行します
|
|  詳細は、'/help'の後にコマンドまたはサブジェクトの名前を続けて
|  入力してください。
|  たとえば、'/help /list'または'/help intro'などです。サブジェクト:
|
|  intro
|       jshellツールの概要
|  shortcuts
|       スニペットおよびコマンドの補完、情報アクセス、および自動コード生成
|       のキーストロークの説明
|  context
|       /env /reloadおよび/resetの評価コンテキスト・オプション



以前、同僚がJShellを作る意味(存在意義)が分からないと言っていたのだけれど、ちょっと動作を確認したいときなどには便利。今まではクラスをいちいち作成してなど、ちょっと面倒だった。

APIを呼び出して結果を得ることも簡単にできる。手軽に試せるのが何よりの利点だろうと思った。

また、これからJavaを学習する人にとっては、クラスやなんだといちいち書かなくていいので、学習の敷居も下げてくれるものだと思う。RubyやPythonなどにはREPL環境があるわけで、ある環境とない環境のどちらを使いたいかと言われれば、断然ある環境なわけだし、他の言語に対抗するうえでも必要なものなんじゃないだろうか。

2017年12月17日日曜日

Java : MapとList を使ってJSONを作成するクラス

JavaでMapとListを使ってJSON文字列を返却するクラスを作成した。Jackson等のライブラリを使えば楽なのかもしれないけれど、仕事によっては外部ライブラリを自由に使うことができなかったりすることもあるので、そうしたときには使えると思う。

ソース


使用例



出力結果


ちょっと使いづらい気もするけれど実用に耐え無いわけじゃないし、これで良しとしておこう。

2017年12月1日金曜日

jQuery : Ajaxの基本的なやり方

Ajaxをうまく使えば、ブラウザアプリであっても操作性の優れたアプリを作ることが出来る。サブミットしなくてもサーバと通信が出来るだけで、UIの幅がかなり広がる。この記事では、jQueryを用いたAjaxの基本的なやり方を説明していく。

Ajaxプログラム


上記のプログラムはAjaxを利用してサーバと通信を行うことができる。

  • url : 通信を行うURLを指定する
  • type : POSTかGETを指定する
  • data : リクエストパラメータの指定を行う。key : value の組み合わせで指定を行い、カンマ区切りで複数が指定できる
  • timeout : タイムアウトまでの時間をミリ秒で指定する
  • beforeSend : サーバと通信を行う前に行う処理を記述する
  • done : サーバとの通信が成功したときの処理を記述する
  • fail : サーバとの通信が失敗したときの処理を記述する
  • always : サーバとの通信の成否に関わらず行う処理を記述する


Ajaxによる処理は非同期処理となるので、そこはプログラム設計をするときに考慮にいれる必要があるが、asyncというプロパティもありこれのtrue/falseを切り替えることで同期処理とすることも可能だ。

  • async : true  ->  非同期処理
  • async : false  ->  同期処理


同期処理を行うAjaxプログラム

2017年11月28日火曜日

javascript : jsonオブジェクトの空の判定方法

Object.keys().lengthを使うことでjsonオブジェクトが空であるかどうかの判定を行える。

2017年11月23日木曜日

Salesforce : with sharing と without sharing

SalesforceのApexクラスを作成するときにwith sharingとwithout sharingというキーワードをクラスにつけることができる。実のところ、これが何を意味するかは調べもせずに使っていた。

ちょっと気になってはいたので今回調べてみた。

この2つのキーワードはクラスに共有ルールをクラスに適用するかどうかを設定するものらしい。

with sharingキーワードを指定すると、現在のユーザの共有ルールが適用されたうえでApexコードが実行されるようになる。通常、Apexコードはシステムコンテキストで実行されるため、共有ルールを指定したい場合は必ずwith sharingを指定する。



without sharingは逆で、共有ルールを適用したいことを明示的に示すものとなる。



注意したいのはクラスで指定しているキーワードのほうの共有ルールが適用されるということだ。例えば、without sharingを指定したクラスからwith sharingを指定したクラスを呼び出すと、with sharingを指定したクラスのメソッドには共有ルールが適用される。


2017年11月19日日曜日

Visualforce : apex:outputLinkタグで勘違い

apex:outputLinkタグでちょっとした勘違いをしていた。valueにリンク先を指定すればリンクを画面上に表示してくれるわけだけど、リンク先がSalesforce上の画面だったときに毎度URLFORを指定していたのだ。



このソースの5行目にURLFORを使っているところが問題の箇所。
これで正常に動作はするので問題ないといえば問題ないのだけど、相対パスを指定できるからURLFORは不要だということについ先日気がついた。



5行目を変更した。

<apex:outputLink value="{!URLFOR('/' + a.Id)}">などと書く必要はまったくなくて、<apex:outputLink value="/{!a.Id}">これだけで同じ処理が実現できる。なんとなく損をした気分だ。

2017年11月14日火曜日

Java : DBの数値項目にNULLをセット

JavaでPreparedStatementを使うとき数値型のカラムにNULLをセットするときには、PreparedStatement.setNull(int parameterIndex, int sqlType)を使う。sqlTypeのほうにはjava.sql.Typesで定義される型を指定する。下記のサンプルソースではjava.sql.Types.NULLを使っている。

String型であればPreparedStatement.setString(int parameterIndex, String x)の第2引数にNULLが許容されるのであまり気にする必要はないがPreparedStatement.setLong(int parameterIndex, long x)やPreparedStatement.setInt(int parameterIndex, long x)の場合、第2引数にNULLが許容されないので、NULLをセットしたい場合は回避するようなプログラムを書く必要がある。

2017年11月12日日曜日

Java : サーブレットのユニットテスト

サーブレットのユニットテストを行うときに困るのが、HttpServletRequestとHttpServletResponseがインターフェースなのでインスタンスを作成できないところ。自前でテスト用のモッククラスを作成するのだと非常に面倒である。

そこで、利用したいのがspring-test-5.0.1.RELEASE.jar

MockHttpServletRequestクラス、MockHttpServletResponseクラスを使うとHttpServletRequest、HttpServletResponseを簡単に用意できる。これでサーブレットのdoPostやdoGetメソッドのユニットテストのソースを楽に書ける。



インスタンス作成してsetParameterなどのメソッドを使ってデータを渡すだけで、モッククラスが使える。そのあとで、サーブレットのdoPostやdoGetにパラメータを渡してやる。

これだけだと、実行時エラーが出てしまうので以下のjarをクラスパスに配置してやる必要がある。

ひとつ目が、spring-core-5.0.1.RELEASE.jar。リンク先へ飛んで、spring-framework-5.0.1.RELEASE-dist.zipをクリックしてzipファイルを解凍すると中にあるのが確認できる。

ふたつ目が、commons-logging-1.2.jar。こちらもリンク先へ飛んでcommons-logging-1.2-bin.zipをクリックしてzipファイルを解凍すると中にあるのが確認できる。


2017年11月8日水曜日

Java : sqliteに接続してデータを取得する

sqliteに接続してデータを取得するには、Connection、Statement、DriverManagerなどを使う。

sqliteのJDBCドライバがビルドパスに存在していることが前提となるので、まだ無いようだったらダウンロードする必要がある。JDBCドライバは使用しているPCにインストールされているsqliteのバージョンとそろえないといけない。もし、バージョンが分からないようなら、以下のコマンドでsqliteのバージョンを確認しよう。
sqlite3 -version

ConnectionとSatementは使用後にcloseする必要があるので注意する。

2017年11月7日火曜日

jQuery : URLを指定してformのサブミット

<form>タグをsubmitするときに、クリックされたボタンによって遷移先のURLを変えたい場合がある。そんなときは、jQueryを使うと簡単に実現できる。


$('#form1').attr('action', 'http://btn1.com');
この部分(3行目と8行目)でURLを指定している。

ユーザーの入力に応じて動作を変えたり、ひとつの<form>タグ中に複数のボタンを置いて、それぞれ別の遷移先を指定したいときなどに便利。

2017年11月6日月曜日

フロントコントローラ

Webサイトが複雑化してくると、認証、承認、セッション管理や共通のログ出力などあらゆるリクエストで同じ処理を行う必要が出てくることがある。こうした振る舞いがあちこちに散らばってしまったら、非常にメンテナンスのし難いプログラムになってしまう。認証の処理を変えようとしたら、あらゆる画面のプログラムを書き換えないといけないなんてゾッとする。

こうしたときには、フロントコントローラパターンを用いてコントローラを実装すればよいと思う。

フロントコントローラパターンを使うと、あらゆるリクエストを、ひとつのコントローラが受け付けるため共通の処理を一か所に組み込むことが出来る。以下のソースはJavaによるフロントコントローラパターンの例。


2017年11月5日日曜日

Java : Streamを使った配列の検索でNullPoiterExceptionが出てしまう

先日、Streamを利用した配列の検索について書いたのだが、色々とやっているうちにNullPointerExceptionが発生するようになってしまった。どういうケースで発生するするかというと、2回検索を行ったときだ。


上記の14行目でNullPointerExceptionが発生する。

仕方がないのでasListで一旦Listに変換してから、List.containsを使うことで検索を行う。


これで複数回検索してもExceptionが発生しないのだけど、Streamでやる方法はないのだろうか。やり方にこだわりがあるわけではないのだが、分からないので少し気持ち悪い。

2017年11月3日金曜日

Java : 配列の要素の検索を行う

配列の要素を検索する方法。Listであればcontainsメソッドがあるので簡単だけれど、配列だと少しだけ手順が必要になる。


StreamクラスのanyMatchメソッドを使うことで配列の要素検索を行える。

2017年11月2日木曜日

Java : propertiesファイルが文字化けする

メッセージをpropertiesファイルに外だししたら、ブラウザに表示したときに文字化けしてしまった。propertiesファイルもブラウザもUTF-8なので大丈夫かと思っていたらそうでもなかったようだ。デバックして確認したらJavaプログラムでpropertiesファイルからメッセージを取得したタイミングで文字化けしていた。

ResourceBundle rb = ResourceBundle.getBundle("hoge");
System.out.println(rb.toString());

上記のプログラムだと文字化けしてしまう。

調べてみると、ResourceBundle.Controlクラスを継承したクラスを作ることで対処可能なことが分かった。

public class ResourceBundleUtf8Control extends ResourceBundle.Control {

     private static final String SUFFIX = "properties";
     private static final String ENCODE = "UTF-8";
     
     @Override
     public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
         throws IllegalAccessException, InstantiationException, IOException {

         String bundleName = toBundleName(baseName, locale);
         String resourceName = toResourceName(bundleName, SUFFIX);
         
         try (InputStream is = loader.getResourceAsStream(resourceName);
             InputStreamReader isr = new InputStreamReader(is, ENCODE);
             BufferedReader reader = new BufferedReader(isr)) {
             return new PropertyResourceBundle(reader);
         }
     }
}

このクラスを作って、下記のようにpropertiesファイルを読み込むと文字化けしない。

ResourceBundle rb = ResourceBundle.getBundle("hoge", new ResourceBundleUtf8Control());
System.out.println(rb.toString());

参考URL(というかほとんどそのまんまなんだけど・・・)
https://ja.stackoverflow.com/questions/27787/eclipse%E3%81%A7application-properties%E3%81%AE%E5%80%A4%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B%E3%81%A8%E6%96%87%E5%AD%97%E5%8C%96%E3%81%91%E3%81%99%E3%82%8B

2017年11月1日水曜日

jsp:useBeanでGenericを使う

<%@ page import="java.util.*" %>
<jsp:useBean id="hoge" scope="request" class="java.util.ArrayList<String>" />

上記のようにjsp:useBeadでArrayList<String>を使おうとしたら下記のようなエラーが出た。
useBeanのクラス属性 [java.util.ArrayList<String>] の値が無効です
Genericの型指定のところでエラーが出てしまっているようなのだが、使えないと不便だし非常に困る。対応策としては、class属性だけでなくtype属性も指定すること。

<%@ page import="java.util.*" %>
<jsp:useBean id="hoge" scope="request" class="java.util.ArrayList" type="java.util.ArrayList<String>" />

このように書き換えたらエラーが出なくなった。

2017年10月31日火曜日

jQuery : formのサブミット

jQueryでformのサブミットをすると、サブミット前に送信データの加工やバリデーションチェックといった処理を行える。

jQuery
$(function() {
     $('#btn').click(function() {
          // 色々な処理を行う。
          // ・・・
          // ・・・
          $('#form1').submit();  // サブミット
     });
});

HTML
<html>
     <head>
          <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
          <title>hogehoge</title>
     </head>
     <body>
          <form id="form1">
               <input type="button" id="btn" value="ボタン" />
          </form>
     </body>
</html>

ボタンがクリックされたタイミングでサブミットを行うようにしている。
こうすると、サブミットを実行する前に処理を行うことができる。

2017年10月30日月曜日

Java : Mapの並び順

JavaにはMapにはHashMap、LinkedHashMap、TreeMapの3種類があるのだけれど、それぞれ役割が異なっているので場面によって使い分ける必要がある。

HashMap : 順番は保証されない
LinkedHashMap : putした順番が保障される
TreeMap : Keyの昇順の順番になる

MapにKey/Valueをputしたあとに、ループで処理したいといった要件があるときは、HashMapを使ってしまうと出力の順番が保障されないので困ってしまう。

Map<String, String> map = new HashMap<String, String>();
map.put("01", "北海道");
map.put("02", "青森県");
map.put("03", "岩手県");
map.put("04", "宮城県");
map.put("05", "秋田県");
map.put("06", "山形県");
map.put("07", "福島県");
map.put("08", "茨城県");
map.put("09", "栃木県");
map.put("10", "群馬県");
map.put("11", "埼玉県");
map.put("12", "千葉県");
map.put("13", "東京都");
map.put("14", "神奈川県");
map.put("15", "新潟県");
map.put("16", "富山県");
map.put("17", "石川県");
map.put("18", "福井県");
map.put("19", "山梨県");
map.put("20", "長野県");
map.put("21", "岐阜県");
map.put("22", "静岡県");
map.put("23", "愛知県");
map.put("24", "三重県");
map.put("25", "滋賀県");
map.put("26", "京都府");
map.put("27", "大阪府");
map.put("28", "兵庫県");
map.put("29", "奈良県");
map.put("30", "和歌山県");
map.put("31", "鳥取県");
map.put("32", "島根県");
map.put("33", "岡山県");
map.put("34", "広島県");
map.put("35", "山口県");
map.put("36", "徳島県");
map.put("37", "香川県");
map.put("38", "愛媛県");
map.put("39", "高知県");
map.put("40", "福岡県");
map.put("41", "佐賀県");
map.put("42", "長崎県");
map.put("43", "熊本県");
map.put("44", "大分県");
map.put("45", "宮崎県");
map.put("46", "鹿児島県");
map.put("47", "沖縄県");
for (Map.Entry<String, String> m : map.entrySet()) {
    System.out.println("KEY : " + m.getKey() + "    VALUE : " + m.getValue());
}

上記のプログラムの実行結果を見てみると、HashMapではバラバラな順番で出力されていることが分かる。
KEY : 22    VALUE : 静岡県
KEY : 44    VALUE : 大分県
KEY : 01    VALUE : 北海道
KEY : 23    VALUE : 愛知県
KEY : 45    VALUE : 宮崎県
KEY : 02    VALUE : 青森県
KEY : 24    VALUE : 三重県
KEY : 46    VALUE : 鹿児島県
KEY : 03    VALUE : 岩手県
KEY : 25    VALUE : 滋賀県
KEY : 47    VALUE : 沖縄県
KEY : 04    VALUE : 宮城県
KEY : 26    VALUE : 京都府
KEY : 05    VALUE : 秋田県
KEY : 27    VALUE : 大阪府
KEY : 06    VALUE : 山形県
KEY : 28    VALUE : 兵庫県
KEY : 07    VALUE : 福島県
KEY : 29    VALUE : 奈良県
KEY : 08    VALUE : 茨城県
KEY : 09    VALUE : 栃木県
KEY : 30    VALUE : 和歌山県
KEY : 31    VALUE : 鳥取県
KEY : 10    VALUE : 群馬県
KEY : 32    VALUE : 島根県
KEY : 11    VALUE : 埼玉県
KEY : 33    VALUE : 岡山県
KEY : 12    VALUE : 千葉県
KEY : 34    VALUE : 広島県
KEY : 13    VALUE : 東京都
KEY : 35    VALUE : 山口県
KEY : 14    VALUE : 神奈川県
KEY : 36    VALUE : 徳島県
KEY : 15    VALUE : 新潟県
KEY : 37    VALUE : 香川県
KEY : 16    VALUE : 富山県
KEY : 38    VALUE : 愛媛県
KEY : 17    VALUE : 石川県
KEY : 39    VALUE : 高知県
KEY : 18    VALUE : 福井県
KEY : 19    VALUE : 山梨県
KEY : 40    VALUE : 福岡県
KEY : 41    VALUE : 佐賀県
KEY : 20    VALUE : 長野県
KEY : 42    VALUE : 長崎県
KEY : 21    VALUE : 岐阜県
KEY : 43    VALUE : 熊本県

これがLinkedHashMapだと、putした順番に並ぶ。

Map<String, String> map = new LinkedHashMap<String, String>();
map.put("10", "群馬県");
map.put("11", "埼玉県");
map.put("12", "千葉県");
map.put("13", "東京都");
map.put("14", "神奈川県");
map.put("15", "新潟県");
map.put("16", "富山県");
map.put("17", "石川県");
map.put("18", "福井県");
map.put("19", "山梨県");
map.put("01", "北海道");
map.put("02", "青森県");
map.put("03", "岩手県");
map.put("04", "宮城県");
map.put("05", "秋田県");
map.put("06", "山形県");
map.put("07", "福島県");
map.put("08", "茨城県");
map.put("09", "栃木県");
map.put("40", "福岡県");
map.put("41", "佐賀県");
map.put("42", "長崎県");
map.put("43", "熊本県");
map.put("44", "大分県");
map.put("45", "宮崎県");
map.put("46", "鹿児島県");
map.put("47", "沖縄県");
map.put("20", "長野県");
map.put("21", "岐阜県");
map.put("22", "静岡県");
map.put("23", "愛知県");
map.put("24", "三重県");
map.put("25", "滋賀県");
map.put("26", "京都府");
map.put("27", "大阪府");
map.put("28", "兵庫県");
map.put("29", "奈良県");
map.put("30", "和歌山県");
map.put("31", "鳥取県");
map.put("32", "島根県");
map.put("33", "岡山県");
map.put("34", "広島県");
map.put("35", "山口県");
map.put("36", "徳島県");
map.put("37", "香川県");
map.put("38", "愛媛県");
map.put("39", "高知県");
for (Map.Entry<String, String> m : map.entrySet()) {
    System.out.println("KEY : " + m.getKey() + "    VALUE : " + m.getValue());
}


出力結果を確認すると、ちゃんとputした順番に並んでいる。
KEY : 10    VALUE : 群馬県
KEY : 11    VALUE : 埼玉県
KEY : 12    VALUE : 千葉県
KEY : 13    VALUE : 東京都
KEY : 14    VALUE : 神奈川県
KEY : 15    VALUE : 新潟県
KEY : 16    VALUE : 富山県
KEY : 17    VALUE : 石川県
KEY : 18    VALUE : 福井県
KEY : 19    VALUE : 山梨県
KEY : 01    VALUE : 北海道
KEY : 02    VALUE : 青森県
KEY : 03    VALUE : 岩手県
KEY : 04    VALUE : 宮城県
KEY : 05    VALUE : 秋田県
KEY : 06    VALUE : 山形県
KEY : 07    VALUE : 福島県
KEY : 08    VALUE : 茨城県
KEY : 09    VALUE : 栃木県
KEY : 40    VALUE : 福岡県
KEY : 41    VALUE : 佐賀県
KEY : 42    VALUE : 長崎県
KEY : 43    VALUE : 熊本県
KEY : 44    VALUE : 大分県
KEY : 45    VALUE : 宮崎県
KEY : 46    VALUE : 鹿児島県
KEY : 47    VALUE : 沖縄県
KEY : 20    VALUE : 長野県
KEY : 21    VALUE : 岐阜県
KEY : 22    VALUE : 静岡県
KEY : 23    VALUE : 愛知県
KEY : 24    VALUE : 三重県
KEY : 25    VALUE : 滋賀県
KEY : 26    VALUE : 京都府
KEY : 27    VALUE : 大阪府
KEY : 28    VALUE : 兵庫県
KEY : 29    VALUE : 奈良県
KEY : 30    VALUE : 和歌山県
KEY : 31    VALUE : 鳥取県
KEY : 32    VALUE : 島根県
KEY : 33    VALUE : 岡山県
KEY : 34    VALUE : 広島県
KEY : 35    VALUE : 山口県
KEY : 36    VALUE : 徳島県
KEY : 37    VALUE : 香川県
KEY : 38    VALUE : 愛媛県
KEY : 39    VALUE : 高知県

これをTreeMapに変えるとKEY順に並べ直してくれる。

Map<String, String> map = new TreeMap<String, String>();
map.put("10", "群馬県");
map.put("11", "埼玉県");
map.put("12", "千葉県");
map.put("13", "東京都");
map.put("14", "神奈川県");
map.put("15", "新潟県");
map.put("16", "富山県");
map.put("17", "石川県");
map.put("18", "福井県");
map.put("19", "山梨県");
map.put("01", "北海道");
map.put("02", "青森県");
map.put("03", "岩手県");
map.put("04", "宮城県");
map.put("05", "秋田県");
map.put("06", "山形県");
map.put("07", "福島県");
map.put("08", "茨城県");
map.put("09", "栃木県");
map.put("40", "福岡県");
map.put("41", "佐賀県");
map.put("42", "長崎県");
map.put("43", "熊本県");
map.put("44", "大分県");
map.put("45", "宮崎県");
map.put("46", "鹿児島県");
map.put("47", "沖縄県");
map.put("20", "長野県");
map.put("21", "岐阜県");
map.put("22", "静岡県");
map.put("23", "愛知県");
map.put("24", "三重県");
map.put("25", "滋賀県");
map.put("26", "京都府");
map.put("27", "大阪府");
map.put("28", "兵庫県");
map.put("29", "奈良県");
map.put("30", "和歌山県");
map.put("31", "鳥取県");
map.put("32", "島根県");
map.put("33", "岡山県");
map.put("34", "広島県");
map.put("35", "山口県");
map.put("36", "徳島県");
map.put("37", "香川県");
map.put("38", "愛媛県");
map.put("39", "高知県");
for (Map.Entry<String, String> m : map.entrySet()) {
    System.out.println("KEY : " + m.getKey() + "    VALUE : " + m.getValue());
}


出力結果を確認するとKEYの昇順に並んでいることが分かる。
KEY : 01    VALUE : 北海道
KEY : 02    VALUE : 青森県
KEY : 03    VALUE : 岩手県
KEY : 04    VALUE : 宮城県
KEY : 05    VALUE : 秋田県
KEY : 06    VALUE : 山形県
KEY : 07    VALUE : 福島県
KEY : 08    VALUE : 茨城県
KEY : 09    VALUE : 栃木県
KEY : 10    VALUE : 群馬県
KEY : 11    VALUE : 埼玉県
KEY : 12    VALUE : 千葉県
KEY : 13    VALUE : 東京都
KEY : 14    VALUE : 神奈川県
KEY : 15    VALUE : 新潟県
KEY : 16    VALUE : 富山県
KEY : 17    VALUE : 石川県
KEY : 18    VALUE : 福井県
KEY : 19    VALUE : 山梨県
KEY : 20    VALUE : 長野県
KEY : 21    VALUE : 岐阜県
KEY : 22    VALUE : 静岡県
KEY : 23    VALUE : 愛知県
KEY : 24    VALUE : 三重県
KEY : 25    VALUE : 滋賀県
KEY : 26    VALUE : 京都府
KEY : 27    VALUE : 大阪府
KEY : 28    VALUE : 兵庫県
KEY : 29    VALUE : 奈良県
KEY : 30    VALUE : 和歌山県
KEY : 31    VALUE : 鳥取県
KEY : 32    VALUE : 島根県
KEY : 33    VALUE : 岡山県
KEY : 34    VALUE : 広島県
KEY : 35    VALUE : 山口県
KEY : 36    VALUE : 徳島県
KEY : 37    VALUE : 香川県
KEY : 38    VALUE : 愛媛県
KEY : 39    VALUE : 高知県
KEY : 40    VALUE : 福岡県
KEY : 41    VALUE : 佐賀県
KEY : 42    VALUE : 長崎県
KEY : 43    VALUE : 熊本県
KEY : 44    VALUE : 大分県
KEY : 45    VALUE : 宮崎県
KEY : 46    VALUE : 鹿児島県
KEY : 47    VALUE : 沖縄県

2017年10月18日水曜日

Bootstrap 4.x におけるパンくずリスト

Bootstrap v4.0.0-betaにしたらパンくずリストがうまく表示できなくて困った。

HTML
<ol class="breadcrumb">
    <li><a href="#">アイテム1</a></li>
    <li><a href="#">アイテム2</a></li>
    <li class="active">アイテム3</li>
</ol>

Bootstrap 3.xではこれで良かったと思うのだけれど表示が下記のようになってしまい、これではパンくずリストとは言えない。


Bootstrap 4.xではパンくずリストの書き方が変わってしまったようなので修正する。

修正後HTML
<ol class="breadcrumb">
    <li class="breadcrumb-item"><a href="#">アイテム1</a></li>
    <li class="breadcrumb-item"><a href="#">アイテム2</a></li>
    <li class="breadcrumb-item active">アイテム3</li>
</ol>

liタグのクラスに breadcrumb-item を指定することでパンくずリストになる。


ちなみにnavタグを利用してもパンくずリストを作れるようになっている。

navタグを利用した場合のHTML
<nav class="breadcrumb">
    <a class="breadcrumb-item" href="#">アイテム1</a>
    <a class="breadcrumb-item" href="#">アイテム2</a>
    <span class="breadcrumb-item active">アイテム3</span>
</nav>


2017年10月17日火曜日

jQuery : loadを使ってHTMLをパーツ読み込む

jQueryのloadを使うことで、パラメータに指定したHTMLを読み込んでDOMに挿入することが出来る。例えば、すべての画面で共通で利用するヘッダなどをパーツとして切り出しておき、それを読み込むといったことを行える。

HTML
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>hoge</title>

        <script type="text/javascript" src="../js/jquery-3.2.1.min.js"></script>
        <script type="text/javascript" src="../js/load.js"></script>
    </head>
    <body>
        <!-- ここにHTMLが読み込まれる -->
        <div id="header"></div>
    </body>
</html>

loadで読み込まれるHTML
<div>
    hogehogehoge
</div>

load.js
$(function(){
    // HTMLをid=headerのところに挿入する
    $("#header").load("読み込みたいHTMLのパス(例: ../parts/header.html");
})


ローカルで試してみたら、実は動かなかった。loadはAjaxを使った処理になるようなのだが、Google Chromeはセキュリティの関係でローカルファイルへのAjax要求を許可していない。ということで、これをやるためだけにTomcatを起動して・・・など、色々と面倒なことをやってようやく動いた。HTML・CSS・javascriptでモックをお手軽に作ろうと思っただけなのに。

2017年10月16日月曜日

Bootstrap dropdown require Popper.js (https://popper.js.org)

Bootstrap v4.0 betaを使おうとしたら下記のようなエラーが出てしまった。

Bootstrap dropdown require Popper.js (https://popper.js.org)

ソース
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>hoge</title>

    <link rel="stylesheet" type="text/css" href="../css/bootstrap.min.css"></link>

    <script type="text/javascript" src="../js/jquery-3.2.1.min.js"></script>
    <script type="text/javascript" src="../js/bootstrap.min.js"></script>
</head>


何やらよく分からないが、Popper.jsというのが必要ということらしい。popper.js自体はエラーに記載されているURLへ行けばダウンロードできる。
https://popper.js.org


このPopper.jsは何なのかというと、ツールチップ用のjsのらしい。日本語の情報があまりなかったが、下記のリンクを見つけた。
http://design-develop.net/web-design/popper.html

私は使ったことがないので具体的なところはよく分からないが、ツールチップの制御が簡単に行えて、しかもかゆいところに手が届くようなものになっているようだ。

ともあれ、popper.jsを読み込むように書き換えたらエラーは出なくなった。

修正後のソース
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>hoge</title>

    <link rel="stylesheet" type="text/css" href="../css/bootstrap.min.css"></link>

    <script type="text/javascript" src="../js/jquery-3.2.1.min.js"></script>
    <script type="text/javascript" src="../js/popper.js"></script>
    <script type="text/javascript" src="../js/bootstrap.min.js"></script>
</head>


よくよく調べるとbootstrap v4.0への移行ガイドにpopper.jsに依存するとのことが書かれているのを見つけた。
http://cccabinet.jpn.org/bootstrap4/getting-started/introduction


2017年9月28日木曜日

Ruby : 文字列のエンコード変換

■概要

Rubyにおける文字列のエンコード変換方法

■準備

特になし

■確認環境

実行端末:Windows 8.1
バージョン:ruby 2.4.2

■コード

str_utf8 = str.encode('utf-8', 'CP932')

■まとめ

文字列のエンコード変換には encode を使う。
str.encode(dst_encoding, src_encoding)
dst_encoding : 変換後のエンコード
src_encoding : 返還前のエンコード

ちなみに、WindowsのエンコードはShift_JISと言われているけれど実は違って、 CP932 というShift_JISを拡張したエンコード。なのでWindowsはShift_JISで定義されていない文字も扱うことができる。これ知らずに、昔ひどい目にあったことがある。

2017年9月27日水曜日

Ruby : Win32OLEを利用したExcelの操作

■概要

RubyのプログラムでExcelの以下の操作を試してみる。
Excelファイルの新規作成
Excelファイルを開く
Excelファイルのセルへの書き込み
Excelファイルのセルや範囲指定してのコピー&ペースト
Excelの行・列の削除

RubyでExcel操作を行うライブラリはいくつかあるようだが、ここではWin32OLEを利用する。

■Win32OLEについて

Windows上でのみ使用可能。
xls、xlsx、両方の形式に対応している。

■準備

特になし

■確認環境

実行端末:Windows 8.1
バージョン:ruby 2.4.2

■コード

Excelファイルの新規作成

require 'win32ole'

#Excel VBA定数のロード
module Excel; end

#Excelの初期化
excel = WIN32OLE.new('Excel.Application')
excel.visible = true #Excelの表示/非表示設定
excel.displayAlerts = true #アラートメッセージを表示/非表示設定
WIN32OLE.const_load(excel, Excel)

#Excelブックの新規作成
book = excel.workbooks.add

#保存
book.saveAs('C:\\workspace\\ruby\\work.xlsx')

#Excelを閉じる
book.close

excel.quit()

既存Excelファイルを開く

require 'win32ole'

#Excel VBA定数のロード
module Excel; end

#Excelの初期化
excel = WIN32OLE.new('Excel.Application')
excel.visible = true #Excelの表示/非表示設定
excel.displayAlerts = true #アラートメッセージを表示/非表示設定
WIN32OLE.const_load(excel, Excel)

#Excelブックを開く
book = excel.Workbooks.Open('C:\\workspace\\ruby\\work.xlsx')

#Excelを閉じる
book.close

excel.quit()

Excelのセルへ文字を書き込む

require 'win32ole'

#Excel VBA定数のロード
module Excel; end

#Excelの初期化
excel = WIN32OLE.new('Excel.Application')
excel.visible = true #Excelの表示/非表示設定
excel.displayAlerts = true #アラートメッセージを表示/非表示設定
WIN32OLE.const_load(excel, Excel)

#Excelブックを開く
book = excel.Workbooks.Open('C:\\workspace\\ruby\\work.xlsx')

#Excelの一番左端のシートを取得
sheet = book.worksheets(1)

#セルへ書き込み
sheet.rows[1].columns[1].value = 'abcdefg'

#開いたブックを上書き
book.save

#Excelを閉じる
book.close

excel.quit()

Excelのセルや範囲指定してコピー&ペースト

require 'win32ole'

#Excel VBA定数のロード
module Excel; end

#Excelの初期化
excel = WIN32OLE.new('Excel.Application')
excel.visible = true #Excelの表示/非表示設定
excel.displayAlerts = true #アラートメッセージを表示/非表示設定
WIN32OLE.const_load(excel, Excel)

#Excelブックを開く
book = excel.Workbooks.Open('C:\\workspace\\ruby\\work.xlsx')

#Excelの一番左端のシートを取得
sheet = book.worksheets(1)

#セルのコピー
sheet.rows[1].columns[1].copy

#ペースト
sheet.rows[2].columns[1].select
book.ActiveSheet.paste

#範囲指定でコピー
sheet.range('A1:A2').copy

#ペースト
sheet.rows[1].columns[2].select
book.ActiveSheet.paste

#開いたブックを上書き
book.save

#Excelを閉じる
book.close

excel.quit()

Excelの行・列の削除

require 'win32ole'

#Excel VBA定数のロード
module Excel; end

#Excelの初期化
excel = WIN32OLE.new('Excel.Application')
excel.visible = true #Excelの表示/非表示設定
excel.displayAlerts = true #アラートメッセージを表示/非表示設定
WIN32OLE.const_load(excel, Excel)

#Excelブックを開く
book = excel.Workbooks.Open('C:\\workspace\\ruby\\work.xlsx')

#Excelの一番左端のシートを取得
sheet = book.worksheets(1)

#行削除
sheet.Range('A1').EntireRow.Delete

#列削除
sheet.Range('A1').EntireColumn.Delete

#開いたブックを上書き
book.save

#Excelを閉じる
book.close

excel.quit()

■まとめ

Win32OLEを利用することでExcel操作がRubyから行える。
マクロ付きブックのxlsm形式は未確認だが、動作に問題はないと思う。

2017年9月25日月曜日

Ruby : 分数の計算

■概要

Rubyにおける分数の計算方法

■準備

特になし

■確認環境

実行端末:Windows 8.1
バージョン:ruby 2.4.2

■コード

#分数の加算( 1/2 + 1/3 )
puts Rational(1, 2) + Rational(1, 3)
#分数の減算( 7/6 - 2/3 )
puts Rational(7, 6) - Rational(2, 3)
#分数の乗算( 2/3 * 5/9 )
puts Rational(2, 3) * Rational(5, 9)
#分数の除算( 6/13 / 2/5 )
puts Rational(6, 13) / Rational(2, 5)

■実行結果

5/6
1/2
10/27
15/13

■まとめ

Rational(a, b)を使うことで簡単に分数の計算を行うことができる。
約分も自動でやってくれる。

2017年9月24日日曜日

Ruby:リテラル式展開

■概要

Rubyにおけるリテラル式展開について
リテラルとは 1 のような数値や 'Hello World!' のような文字列のようにRubyのプログラムの中に直接書き込むことのできる値のことを指す。Rubyではダブルクォーテーションで囲われた文字列のなかに、式の内容を埋め込むことができる。

■準備

特になし

■確認環境

実行端末:Windows 8.1
バージョン:ruby 2.4.2

■コード

str = 'Hello World!'
$str = 'Hello Ruby!'
@str = 'Hello'

#式展開
puts("#{str}")
puts('#{str}')
puts("#str")
puts('#str')

#$ではじまる変数の式展開
puts("#$str")
puts('#$str')

#@ではじまる変数の式展開
puts("#@str")
puts('#@str')

#式展開のなかではRubyの式をそのまま書くことができる
puts("#{str + ' ' + $str + ' ' + @str}")

■実行結果

Hello World!
#{str}
#str
#str
Hello Ruby!
#$str
Hello
#@str
Hello World! Hello Ruby! Hello

■まとめ

ダブルクォーテーションで囲われた文字列のなかに以下のような形式で記述すると式の内容を埋め込むことが出来ている。
#{式}
実行結果を見るとシングルクォーテーションでは式の内容が展開されていないことが分かる。また、変数が$または@ではじまるばあいは{}を省略することできる。
#$変数
#@変数

便利なのは式展開のなかではRubyの式をそのまま書くことができることだと思う。

2017年9月23日土曜日

Rubyでユーザーのキーボード入力を対話的に受け取る

■概要

ユーザーのキーボード入力を対話的に受け取って処理を行うスクリプトの作成

■準備

特になし

■確認環境

実行端末:Windows 8.1
バージョン:ruby 2.4.2

■コード

class InteractClass

    def initialize

        puts <<-EOT
----------------------------------------
表示させたい言葉の番号を入力してEnterを押してください
何もせずに終了する場合は 9 を入力してEnterを押してください
----------------------------------------
1. おはよう
2. こんにちは
3. こんばんは
4. foo
5. bar
6. Hello World!
----------------------------------------
        EOT

        interact

    end

    def interact

        case gets.chomp
            when '1'
                puts 'おはよう'
            when '2'
                puts 'こんにちは'
            when '3'
                puts 'こんばんは'
            when '4'
                puts 'foo'
            when '5'
                puts 'bar'
            when '6'
                puts 'Hello World!'
            when '9'
                exit 1
            else
                puts <<-EOT
----------------------------------------
【エラー】
1~6, 9 のいずれかを入力してください
----------------------------------------
                EOT
                ic = InteractClass.new()
        end
    end
end

ic = InteractClass.new()

■まとめ


gets.chompの部分でユーザーの入力を受け取っている。

2017年9月22日金曜日

RubyでSCPを利用

概要

RubyでSCPを利用してファイルのアップロード、ダウンロードを行う

準備

「RubyでSSH接続」を実施して動作することを確認する。
以下のコマンドを実行してSCPのライブラリを取得する。
gem install net-scp

確認環境

実行端末:Windows 8.1
バージョン:ruby 2.4.2

コード

アップロード

require 'net/ssh'
require 'net/scp'

Net::SSH.start('<IP Address>', '<User>', :password => '<Password>') do |ssh|
    #ファイルアップロード
    remort_path = '<Remort File Pass>'
    local_path = '<Local File Pass>'
    ssh.scp.upload! local_path, remort_path
end

ダウンロード

require 'net/ssh'
require 'net/scp'

Net::SSH.start('<IP Address>', '<User>', :password => '<Password>') do |ssh|
    #ファイルダウンロード
    remort_path = '<Remort File Pass>'
    local_path = '<Local File Pass>'
    ssh.scp.download! remort_path, local_path
end

まとめ

ssh.scp.upload、ssh.scp.downloadでscpのアップロード、ダウンロードの実行を行っている。ssh.exex!('<コマンド>')を利用してscpコマンドを発行することもできると思う(検証していない)が、それより手軽に実現できると思う。

2017年9月21日木曜日

RubyでSSH接続を試してみる

概要

Rubyで他の端末へSSH接続して、コマンド発行を行う。

準備

以下のコマンドをコマンドラインで実行してSSHのライブラリを取得する
gem install net-ssh

確認環境

実行端末:Windows 8.1
バージョン:ruby 2.4.2

コード

require 'net/ssh'

Net::SSH.start('<IP Address>', '<User>', :password => '<Password>') do |ssh|
    print(ssh.exec!('ls'))
end

説明

これだけでSSH接続ができる。コマンド実行も ssh.exex!('<コマンド>') だけで簡単に行える。

2017年8月11日金曜日

SalesforceでAjaxを使ってみる3

昨日調べたところによると、<apex:commandButton>タグでAjaxを実現ができるようなので、これが一番簡単そうだし深堀してみることにした。

https://developer.salesforce.com/docs/atlas.ja-jp.pages.meta/pages/pages_compref_commandButton.htm

このリンク先を見てみると、「Ajax」という単語がところどころに出てくる。「action」の属性を見てみると、
サーバに対する AJAX 要求によって呼び出される action メソッド。このメソッドを参照するには、差し込み項目の構文を使用します。
というように書かれている。actionはよく使っていたが、Ajax要求とは知らなかったので少し驚いた。

<apex:page Controller="TestController">
    <apex:form >
        <apex:outputText >ファイルを選択してください:</apex:outputText>
     <apex:inputFile value="{!uploadFile}"/>
        <br/>
        <apex:commandButton value="アップロード" action="{!upload}"/>
    </apex:form>
</apex:page>

action="{!upload}"の部分が該当箇所で、TestControllerクラスに実装されたuploadメソッドを呼び出している。

2017年8月10日木曜日

SalesforceでAjaxを使ってみる2

Ajaxはブラウザと非同期にサーバーと通信を行うことができて、うまく使えばけっこう便利なWebアプリの開発を行えるようになるだろう。ページ遷移を伴わない動的なWebアプリは、きっとユーザのうけもいいものになるに違いない。

AjaxはjQueryなどのフレームワークを利用すると簡単に記述することができ、尚且つブラウザごとの互換性もあまり心配しなくてよくなる(はず)。SalesforceにはAJAX Toolkitというのがあり、これを利用することでAjaxを実現できるようだ。

また、Visualforceの<apex:commandButton>タグのrerender属性を指定すると、メソッドの呼び出しをsubmitではなく、Ajaxで処理してくれるらしい。あとは、メソッドに@RemotoActionアノテーションをつけることや、jQueryを組み込む方法などがある。

まだ実際のソースコードを作ったわけではないが、それぞれ長所、短所ともにあるようなので、使いどころを覚えていく必要がありそうだ。色々な方法があるようなのでひとつずつ試していこうと思う。

2017年8月9日水曜日

SalesforceでAjaxを使ってみる

最近は仕事のほうが忙しくて、まあ暇しているのに比べれば何よりなのだけれど、あまり学習の時間が取れていない。細々とは続けているものの、なかなかブログに書くほどの分量は貯まらないし、日々気力は落ちていくしで悪循環だった。

というわけで、5分10分くらいのことで、まとまり切らなくてもちょっとずつ書いていこうと思う。

SalesfroceでAjaxを使ってみたいと思った。いまの仕事(Salesforceではない)でAjaxを結構使う機会があったので、じゃあSalesforceでAjaxを実現するには何をしたらいいのかということを調べた。

このサイトが参考になりそうだ。

https://developer.salesforce.com/docs/atlas.en-us.ajax.meta/ajax/sforce_api_ajax_introducing.htm

2017年7月21日金曜日

メンタルヘルスケアの記事を読んで

http://itpro.nikkeibp.co.jp/atcl/column/16/081800177/061500031/?ST=spleaf

この話はとても分かりやすかった。

メンタルヘルスケアについての話なんだけれど、ある日突然パタッといったりするもんだよということが書かれている。

頑張って働くというのは大変なようで、案外簡単だったりする。仕事だとやらないと、なんていう義務感も働くのでなおさら。

いまは残業にたいして厳しいのでそうでもないけれど、私が仕事を始めた頃はそうでもなかった。プログラマという職業を選んだということもあるかもしれないけれど。

まだ大丈夫なんて自分に言い聞かせはじめたら、かなり危ない兆候で、本当は休まないといけない。まだ大丈夫なんて、通常の状態じゃ思ったりしない。

趣味が楽しくなくなったり、休日に起きれなくなったりと、体からシグナルは意外とたくさん出ていたりする。

「大丈夫?」なんて声をかけたところで「大丈夫」と大抵は答えが返ってくる。普段と言動が変わってしまった人の「大丈夫」なんてまったく信用ならない。

冒頭の記事にもあるようにストレスとはうまく付き合わないといけないわけだけど、これって周囲の協力が必要と思う。

セルフケアが重要なのは当然だけど、ストレスの感じかたは人それぞれで、一概に決めつけられない。精神的に強い人というのはあまり見たことなくて、特定の状況において強いとか、得意なところが人によってちがう。

お互いがお互いをという関係になってくると思うので、あるときは助けるし、別のときは助けてもらえるというのがいい関係だと思う。

2017年7月18日火曜日

Salesforce : ContentDocumentに新しいバージョンをアップロード

ContentVersionのinsertをしてやればApexでContentDocumentに新しいファイルのアップロードを行える。では、すでにアップロードされているファイルの新しいバージョンをアップロードするにはどうすればいいのだろうか。

ContentVersionはContentDocumentIdというフィールドを持っている。これはContentVersionが紐づいているContentDocumentのIDを保持するフィールドだ。なので、insertしてやるときに、新しいバージョンをアップロードしたいContentDocumentのIDをこのフィール―ドにセットしてやればいい。

ContentVersion cv = new ContentVersion();
cv.Title = 'アップロードテスト';
cv.VersionData = this.uploadFile;
cv.PathOnClient = 'test.jpg';
cv.isMajorVersion = true;

// 新しいバージョンを作成するContentDocumentを指定
cv.ContentDocumentId = '06910000009UasWAAS';

insert cv;

2017年7月13日木曜日

Salesforce : ContentDocument?


昨日ContentVersionへのアップロードに成功したのだけれど、このページのURLが気にかかっている。

*****/sObject/ContentDocument/home

ContentVersionにアップロードすると、ContentDocumentに格納されているようなのだ。ContentVersionについて調べているときに、ちらほらと見かけたのを見て見ぬふりをしていたが、真面目に取り組みあわないといけないようだ。

ちょっと調べた感じだとこんなイメージ。


なのだけれど、ContentDocumentHistoryなるオブジェクトも存在していて、VersionとHistoryってなにが違うのかがよく分からない。

2017年7月12日水曜日

Salesforce : ContentVersionはどこに?

昨日ContentVersionのinsertに成功したと思しき結果が得られた。成功したと言いきれないのは、insertした結果のファイルがどこに行ってしまうのかを知らないからだ。

ContentVersionなんだから何かのバージョン管理してるものなんだろうなあ、とか考えてみると気になることはあるのだけれど、とりあえず、insertされたファイルの行方を探そうと思う。

アプリケーションランチャーを開いてみると、「ファイル」という名前の項目がいた。


これじゃないだろうか?


思った通り、アップロードしたファイルを見つけることが出来た。

2017年7月11日火曜日

Salesforce : Apex : transientキーワード

ビューステートにはガバナ制限があるので、128KB以上のデータを扱えない。それ以上のデータを扱うには、このガバナ制限を回避する必要がある。その方法としてtransientキーワードを使う。

transientキーワードを使うとビューステートとして送信できなくなるのだが、逆に考えるとビューステートのガバナ制限は気にしなくていいとも言える。

こんなふうに書く。

public transient Blob uploadFile { get; set; }

さっそく、昨日失敗したファイルアップロードを試してみる。

Visualforce
<apex:page Controller="TestController">
    <apex:form >
        <apex:outputText >ファイルを選択してください:</apex:outputText>
     <apex:inputFile value="{!uploadFile}"/>
        <br/>
        <apex:commandButton value="アップロード" action="{!upload}"/>
    </apex:form>
</apex:page>

Apex
public class TestController {
 /* アップロードするファイル */
    public transient Blob uploadFile { get; set; }
    
    /* アップロード処理 */
    public void upload() {
        ContentVersion cv = new ContentVersion();
        cv.Title = 'アップロードテスト';
        cv.VersionData = uploadFile;
        cv.PathOnClient = 'test.jpg';
        cv.isMajorVersion = true;
        insert cv;
    }
}

エラーは発生しなかったので、うまくいったような気がする。ひとつ問題があって、ContentVersionにinsertしたファイルはどこに行けば確認できるのかが分からない。

ちょっとしたことのはずなのに課題はなくなってくれないなあ。

2017年7月10日月曜日

Salesforce : ビューステートのガバナ制限を回避したい

ビューステートには128KBというガバナ制限がある。けれども、ファイルアップロードするようなVisualforceを作成したら、その程度のガバナ制限はすぐに超えてしまう。どうにかしたい。

どうにか出来ないかなと思って考えたのが次のこと。

実はapex:formタグの外にファイルアップロードを書けばいいんじゃないか

というわけでさっそく試してみた。

もともと、このように書いていたVisualforceを
<apex:page Controller="TestController">
    <apex:form >
        <apex:outputText >ファイルを選択してください:</apex:outputText>
        <apex:inputFile value="{!uploadFile}"/>
        <br/>
        <apex:commandButton value="アップロード" action="{!upload}"/>
    </apex:form>
</apex:page>

このように書き直した。
<apex:page Controller="TestController">
    <apex:form >
        <apex:outputText >ファイルを選択してください:</apex:outputText>
        <apex:inputFile value="{!uploadFile}"/>
    </apex:form>
    <apex:commandButton value="アップロード" action="{!upload}"/>
</apex:page>

そしたら、エラーになってしまった。

<apex:commandButton> (<apex:page> の下にある) は <apex:form></apex:form> タグ間に存在する必要があります。

そういえば、apex:commandButtonタグはapex:formタグに囲われた領域にいないといけないんだった。例えばこんな感じ。

<apex:page Controller="TestController">
    <apex:outputText >ファイルを選択してください:</apex:outputText>
    <apex:inputFile value="{!uploadFile}"/>
    <apex:form >
        <apex:commandButton value="アップロード" action="{!upload}"/>
    </apex:form>
</apex:page>

これだと、apex:commandButtonタグはapex:formタグの内側にあるからOK。ちなみにapex:outputTextタグとapex:inputFileタグは外側にあってもエラーにはならない。

で、どうしたら回避できるんだろう。

2017年7月9日日曜日

Salesforce : ビューステート

ファイルをアップロードするプログラムを書いたのだけれど、エラーとなってしまった。

ビューステートの最大表示サイズの制限 (135 KB) を超えています。このページのビューステートのサイズは、465.039 KB でした

何かのサイズ制限に引っかかったようなのだが、何のことなのかよく分からない。取りあえず、「ビューステート」という用語が理解できれば解決するだろうと辺りをつけて調べてみる。

どうやらVisualfoceでは画面遷移のときの情報を自動的に保持してくれるようになっていて、この保持してくれるやつのことをビューステートと呼ぶらしい。

これにはガバナ制限があって、128KBしか保持できないとのことのようだ。エラーメッセージでは135KBになっているけれど、バージョンの違いのせいだろうか?

アップロードするファイルのサイズは、400KBを超えていたのでこのガバナ制限に引っかかっているよというのがこのエラーの内容。

でも、このくらいのサイズのファイルをアップロードしたくなるときはあると思うのだけれど。諦めるしかないのだろうか?

2017年7月8日土曜日

HTML : CSS : HTMLのテーブルに縦スクロール

昨日はtableに横スクロールをつけたので、今日は立てスクロール。

<html>
    <head>
        <title>スクロール</title>
        <style type="text/css">
            .scroll{
                overflow-x:scroll;
                overflow-y:scroll;
            }
        </style>
    </head>
    <body>
        <div class="scroll" style="width:500px;height:500px;">
            <table class="t" frame="border" rules="all" style="width:1000px;">
                <thead>
                    <th>ヘッダヘッダヘッダヘッダヘッダ</th>
                    <th>ヘッダヘッダヘッダヘッダヘッダ</th>
                    <th>ヘッダヘッダヘッダヘッダヘッダ</th>
                    <th>ヘッダヘッダヘッダヘッダヘッダ</th>
                    <th>ヘッダヘッダヘッダヘッダヘッダ</th>
                </thead>
                <tbody>
                 <tr>
                        <td>あああああああああああああああ</td>
                        <td>いいいいいいいいいいいいいいい</td>
                        <td>ううううううううううううううう</td>
                        <td>えええええええええええええええ</td>
                        <td>おおおおおおおおおおおおおおお</td>
                    </tr>
                    <tr>
                        <td>あああああああああああああああ</td>
                        <td>いいいいいいいいいいいいいいい</td>
                        <td>ううううううううううううううう</td>
                        <td>えええええええええええええええ</td>
                        <td>おおおおおおおおおおおおおおお</td>
                    </tr>
                    <tr>
                        <td>あああああああああああああああ</td>
                        <td>いいいいいいいいいいいいいいい</td>
                        <td>ううううううううううううううう</td>
                        <td>えええええええええええええええ</td>
                        <td>おおおおおおおおおおおおおおお</td>
                    </tr>
                    <tr>
                        <td>あああああああああああああああ</td>
                        <td>いいいいいいいいいいいいいいい</td>
                        <td>ううううううううううううううう</td>
                        <td>えええええええええええええええ</td>
                        <td>おおおおおおおおおおおおおおお</td>
                    </tr>
                    <tr>
                        <td>あああああああああああああああ</td>
                        <td>いいいいいいいいいいいいいいい</td>
                        <td>ううううううううううううううう</td>
                        <td>えええええええええええええええ</td>
                        <td>おおおおおおおおおおおおおおお</td>
                    </tr>
                    <tr>
                        <td>あああああああああああああああ</td>
                        <td>いいいいいいいいいいいいいいい</td>
                        <td>ううううううううううううううう</td>
                        <td>えええええええええええええええ</td>
                        <td>おおおおおおおおおおおおおおお</td>
                    </tr>
                    <tr>
                        <td>あああああああああああああああ</td>
                        <td>いいいいいいいいいいいいいいい</td>
                        <td>ううううううううううううううう</td>
                        <td>えええええええええええええええ</td>
                        <td>おおおおおおおおおおおおおおお</td>
                    </tr>
                    <tr>
                        <td>あああああああああああああああ</td>
                        <td>いいいいいいいいいいいいいいい</td>
                        <td>ううううううううううううううう</td>
                        <td>えええええええええええええええ</td>
                        <td>おおおおおおおおおおおおおおお</td>
                    </tr>
                    <tr>
                        <td>あああああああああああああああ</td>
                        <td>いいいいいいいいいいいいいいい</td>
                        <td>ううううううううううううううう</td>
                        <td>えええええええええええええええ</td>
                        <td>おおおおおおおおおおおおおおお</td>
                    </tr>
                    <tr>
                        <td>あああああああああああああああ</td>
                        <td>いいいいいいいいいいいいいいい</td>
                        <td>ううううううううううううううう</td>
                        <td>えええええええええええええええ</td>
                        <td>おおおおおおおおおおおおおおお</td>
                    </tr>
                    <tr>
                        <td>あああああああああああああああ</td>
                        <td>いいいいいいいいいいいいいいい</td>
                        <td>ううううううううううううううう</td>
                        <td>えええええええええええええええ</td>
                        <td>おおおおおおおおおおおおおおお</td>
                    </tr>
                </tbody>
            </table>
        </div>
    </body>
</html>

単純にこう書いてもできるのだけれど、これだと少しだけ具合が悪い。ヘッダーも一緒にスクロールされてしまうからだ。tableタグの外側にあるdivタグでスクロールさせているので当然と言えば当然。

ヘッダーがスクロールされてしまうと実用性がない。なので、tbodyタグだけスクロールするように修正する。

<html>
    <head>
        <title>スクロール</title>
        <style type="text/css">
            .scroll{
                overflow-x:scroll;
            }
            .scroll-thead, .scroll-tbody {
                display:block;
            }
            .scroll-tbody {
                overflow-y:scroll;
                height:100px;
            }
        </style>
    </head>
    <body>
        <div class="scroll" style="width:500px;">
            <table class="t" frame="border" rules="all" style="width:1000px;">
                <thead class="scroll-thead">
                    <tr>
                        <th>ヘッダヘッダヘッダヘッダヘッダ</th>
                        <th>ヘッダヘッダヘッダヘッダヘッダ</th>
                        <th>ヘッダヘッダヘッダヘッダヘッダ</th>
                        <th>ヘッダヘッダヘッダヘッダヘッダ</th>
                        <th>ヘッダヘッダヘッダヘッダヘッダ</th>
                    </tr>
                </thead>
                <tbody class="scroll-tbody">
                 <tr>
                        <td>あああああああああああああああ</td>
                        <td>いいいいいいいいいいいいいいい</td>
                        <td>ううううううううううううううう</td>
                        <td>えええええええええええええええ</td>
                        <td>おおおおおおおおおおおおおおお</td>
                    </tr>
                    <tr>
                        <td>あああああああああああああああ</td>
                        <td>いいいいいいいいいいいいいいい</td>
                        <td>ううううううううううううううう</td>
                        <td>えええええええええええええええ</td>
                        <td>おおおおおおおおおおおおおおお</td>
                    </tr>
                    <tr>
                        <td>あああああああああああああああ</td>
                        <td>いいいいいいいいいいいいいいい</td>
                        <td>ううううううううううううううう</td>
                        <td>えええええええええええええええ</td>
                        <td>おおおおおおおおおおおおおおお</td>
                    </tr>
                    <tr>
                        <td>あああああああああああああああ</td>
                        <td>いいいいいいいいいいいいいいい</td>
                        <td>ううううううううううううううう</td>
                        <td>えええええええええええええええ</td>
                        <td>おおおおおおおおおおおおおおお</td>
                    </tr>
                    <tr>
                        <td>あああああああああああああああ</td>
                        <td>いいいいいいいいいいいいいいい</td>
                        <td>ううううううううううううううう</td>
                        <td>えええええええええええええええ</td>
                        <td>おおおおおおおおおおおおおおお</td>
                    </tr>
                    <tr>
                        <td>あああああああああああああああ</td>
                        <td>いいいいいいいいいいいいいいい</td>
                        <td>ううううううううううううううう</td>
                        <td>えええええええええええええええ</td>
                        <td>おおおおおおおおおおおおおおお</td>
                    </tr>
                    <tr>
                        <td>あああああああああああああああ</td>
                        <td>いいいいいいいいいいいいいいい</td>
                        <td>ううううううううううううううう</td>
                        <td>えええええええええええええええ</td>
                        <td>おおおおおおおおおおおおおおお</td>
                    </tr>
                    <tr>
                        <td>あああああああああああああああ</td>
                        <td>いいいいいいいいいいいいいいい</td>
                        <td>ううううううううううううううう</td>
                        <td>えええええええええええええええ</td>
                        <td>おおおおおおおおおおおおおおお</td>
                    </tr>
                    <tr>
                        <td>あああああああああああああああ</td>
                        <td>いいいいいいいいいいいいいいい</td>
                        <td>ううううううううううううううう</td>
                        <td>えええええええええええええええ</td>
                        <td>おおおおおおおおおおおおおおお</td>
                    </tr>
                    <tr>
                        <td>あああああああああああああああ</td>
                        <td>いいいいいいいいいいいいいいい</td>
                        <td>ううううううううううううううう</td>
                        <td>えええええええええええええええ</td>
                        <td>おおおおおおおおおおおおおおお</td>
                    </tr>
                    <tr>
                        <td>あああああああああああああああ</td>
                        <td>いいいいいいいいいいいいいいい</td>
                        <td>ううううううううううううううう</td>
                        <td>えええええええええええええええ</td>
                        <td>おおおおおおおおおおおおおおお</td>
                    </tr>
                </tbody>
            </table>
        </div>
    </body>
</html>

tbodyタグにoverflow-y:scrollをスタイルで指定してあげる。theadとtbodyにdisplay:block。これで、tblodyだけのスクロールの準備ができた。

あとは、tbodyで縦幅を指定してあげれば、tbodyの表示要素が指定した以上の縦幅を持っているときにスクロールできるようになる。

2017年7月7日金曜日

HTML : CSS: HTMLのtableに横スクロール

HTMLのテーブルに横スクロールをつけたい。

簡単に出来るかと思ったら意外なほど時間がかかった。毎日ブログを更新すると決めているので急いで書かないと間に合わない。

<html>
    <head>
        <title>スクロール</title>
        <style type="text/css">
            .scroll{
                overflow-x:scroll;
            }
        </style>
    </head>
    <body>
        <div class="scroll" style="width:500px;">
            <table class="t" frame="border" rules="all" style="width:1000px;">
                <thead>
                    <th>ヘッダヘッダヘッダヘッダヘッダ</th>
                    <th>ヘッダヘッダヘッダヘッダヘッダ</th>
                    <th>ヘッダヘッダヘッダヘッダヘッダ</th>
                    <th>ヘッダヘッダヘッダヘッダヘッダ</th>
                    <th>ヘッダヘッダヘッダヘッダヘッダ</th>
                </thead>
                <tbody>
                    <td>あああああああああああああああ</td>
                    <td>いいいいいいいいいいいいいいい</td>
                    <td>ううううううううううううううう</td>
                    <td>えええええええええええええええ</td>
                    <td>おおおおおおおおおおおおおおお</td>
                </tbody>
            </table>
        </div>
    </body>
</html>

こんな感じでtableタグのひとつ上の階層をdivタグで囲ってあげて、そこにoverflow-x:scrollをスタイルで指定する。これだけで横スクロールするテーブルの出来上がり。


何に嵌っていたかというと、最初こんなソースを書いてしまった。

<html>
    <head>
        <title>スクロール</title>
        <style type="text/css">
            .scroll{
                overflow-x:scroll;
            }
        </style>
    </head>
    <body>
        <div class="scroll">
            <table class="t" frame="border" rules="all">
                <thead>
                    <th>ヘッダヘッダヘッダヘッダヘッダ</th>
                    <th>ヘッダヘッダヘッダヘッダヘッダ</th>
                    <th>ヘッダヘッダヘッダヘッダヘッダ</th>
                    <th>ヘッダヘッダヘッダヘッダヘッダ</th>
                    <th>ヘッダヘッダヘッダヘッダヘッダ</th>
                </thead>
                <tbody>
                    <td>あああああああああああああああ</td>
                    <td>いいいいいいいいいいいいいいい</td>
                    <td>ううううううううううううううう</td>
                    <td>えええええええええええええええ</td>
                    <td>おおおおおおおおおおおおおおお</td>
                </tbody>
            </table>
        </div>
    </body>
</html>

何が違うかというとdivタグとtableタグのところにstyleでwidthを指定していないこと。


スクロールバーは出ているけれども、スクロールしたいという要望には堪えられなさそうだ。tableより外側にいるdivタグのほうが狭くないとスクロールできない。当たり前。

2017年7月3日月曜日

Salesforce : ContentVersionってなんだ

Einstein Visionはなかなか進まないので、とっかかりになりそうなところから進めていこうと思う。

public List<Vision.Prediction> getCallVisionContent() {
 // Get a new token
 String access_token = getAccessToken();

 // Make a prediction for an image stored in Salesforce
 // by passing the file as blob which is then converted to base64 string
 ContentVersion contents = [SELECT Title,VersionData FROM ContentVersion where Id = '06828000005b65hAAA' LIMIT 1];
 return Vision.predictBlob(contents.VersionData, access_token, 'GeneralImageClassifier');
}

アップロードされたファイルをEinsteinに渡しているのがこのメソッドなんでけれど、実を言うと"ContentVersion"が何なのかよく分かっていない。ひとつひとつ知識を身につけていきたい。

調べてみると"ContentVersion"はSalesforceのファイルストレージの種類のひとつらしい。Salesforceのファイルストレージの種類は以下の4つがある。


  • 添付ファイル:Attachment
  • ドキュメント:Document
  • コンテンツ:ContentVersion
  • ファイル:FeedItem

各ファイルストレージごとに少しずつ役割やアップロードできるファイル容量に差があるみたい。ここが詳しかった。
https://base.terrasky.co.jp/articles/GhMtp

明日はContentVersionにアップロードしてみよう。

2017年6月30日金曜日

Salesforce : Apex : SOQL : クエリの結果の受け取りかた

ApexでSOQLクエリを発行してた下記のように結果を受け取る。

// sObjectで結果を受け取る
Account a = [SELECT Id FROM Account];

// sObjectのリストで結果を受け取る
List<Account> li = [SELECT Id FROM Account];

どちらでもいいのだけれど、「sObjectで結果を受け取る」のほうの場合でクエリの結果が複数レコード取得出来てしまうとエラーとなってしまう。なので、使用できる箇所は限定的だ。

逆に言えば、クエリの結果が1件であることが保証できるのであればリストでクエリの結果を受け取る必要はないと言える。

ところが昨日、複数レコードデータが取得できるはずのないクエリでエラーとなってしまった。

これが問題のクエリ
ContentVersion content = [SELECT Title,VersionData FROM ContentVersion where Id = '06841000000LkfCAAS' LIMIT 1];

"Limit 1"を指定しているし、そもそもIDを条件句に入れているのだから、複数レコード取得できるはずがない。
List has no rows for assignment to SObject
昨日は勘違いしていたが、冷静になってこのメッセージを読み返すとクエリの結果がゼロ件だからエラーになっているようである。

これ、私にとっては新事実なのだけれど。

というわけで試してみる。

Account a = [SELECT Id FROM Account WHERE Name = '???'];

データを取得できない条件を指定してSOQLクエリを発行し結果を受け取る。
List has no rows for assignment to SObject
同じメッセージが得られたので、ゼロ件だとエラーになるみたいだ。

てっきり、ゼロ件でもエラーとはならないと思い込んでいた。もし本当なら「sObjectで結果を受け取る」なんてプログラムを書くメリットがまったくないと言える。

100%確実に、データが1件のときというの保証するのは難しい。"LIMIT 1"とすれば

Account a = [SELECT Id FROM Account WHERE Name = '???' LIMIT 1];

クエリは最大でも1件しかデータを返さない。

けれど、絶対に1件取得できる保証ができないケースのほうが多い気がする。とりあえず、必ずListを使って受け取るようにしようと思う。

2017年6月29日木曜日

Einstein Vision : 画像認識をアップロードファイルでやりたい4

見えないふりをしていたのだけれど、実はクイックスタート: Einstein Visionで使うプログラムには最初からコメントアウトされている部分がある。昨日、うまくアップロードした画像を認識できなかったので、この部分を少し調べてみたい。何か解決のヒントが得られるかもしれない。

<!--  <apex:pageBlock > -->
<!--      <apex:repeat value="{!callVisionContent}" var="prediction"> -->
<!--          <apex:outputText value="{!prediction.label}" />:<apex:outputText value="{!prediction.probability}" /><br/> -->
<!--    </apex:repeat> -->
<!--  </apex:pageBlock> -->

コメントアウトされているVisualforceの箇所が上記の部分。いかにも、そのまま動きそうな雰囲気を出している。こいつのコメントアウトをはずす。

こいつはVisionControllerクラスのcallVisionContentメソッドを呼び出している。

public List<Vision.Prediction> getCallVisionContent() {
 // Get a new token
 String access_token = getAccessToken();

 // Make a prediction for an image stored in Salesforce
 // by passing the file as blob which is then converted to base64 string
 ContentVersion content = [SELECT Title,VersionData FROM ContentVersion where Id = '06841000000LkfCAAS' LIMIT 1];
 return Vision.predictBlob(content.VersionData, access_token, 'GeneralImageClassifier');
}

よくみると、こいつはVisionクラスのpredictBlobメソッドを呼び出している。私が昨日作ったソースではエラーになったがこいつはどうだろうか。

エラーになってしまった。

気になるのはエラーの内容で、"List has no rows for assignment to SObject"。これはクエリの結果が複数件なのに、単一(リストではなく)オブジェクトで受け取ろうとしたときに発生するエラー(だったと思う)。

確かにプログラムはクエリの結果をリストではなく単一のオブジェクトで受け取っている。だが、クエリを見てみると、

ContentVersion content = [SELECT Title,VersionData FROM ContentVersion where Id = '06841000000LkfCAAS' LIMIT 1];

"LIMIT 1"となっているため、クエリの結果は複数件にはならないはず。謎のエラーである。

謎が増えてしまったわけだが、このあたりで今日は終わり。


1回目:
http://nodding-off-programmer.blogspot.jp/2017/06/einstein-vision_26.html
2回目:
http://nodding-off-programmer.blogspot.jp/2017/06/einstein-vision_27.html
3回目:
http://nodding-off-programmer.blogspot.jp/2017/06/einstein-vision_28.html
4回目(今回):

2017年6月28日水曜日

Einstein Vision : 画像認識をアップロードファイルでやりたい3

ファイルアップロードできるようになったので、あとはそのデータをEinsteinに渡してあげるだけ。"predictBlob"というメソッドがいるので、そいつに渡してやる。

認識させるのは私の好きな花、準絶滅危惧種のチョウジソウ。


と思ったのだけれど、残念ながら失敗。


メソッドが用意されているから簡単にできると思ったのだけれど、うまくいかなかった。原因はよく分からない。もう少し調べる必要がありそうだ。

1回目:
http://nodding-off-programmer.blogspot.jp/2017/06/einstein-vision_26.html
2回目:
http://nodding-off-programmer.blogspot.jp/2017/06/einstein-vision_27.html
3回目(今回):

2017年6月27日火曜日

Einstein Vision : 画像認識をアップロードファイルでやりたい2

画像認識をアップロードファイルでやるには、ローカルのファイルをアップロードできるようにしないといけない。ファイルアップロードには<apex:inputFile>を使う。

<apex:inputFile value="{!uploadFile}"/>

こんな感じ。

"ファイルを選択"をクリックするとファイル選択ダイアログが表示されるので、ファイルを選ぶだけ。

valueは必ず指定する。valueに指定するのはBlob型。

こんな感じ。

public Blob uploadFile { get; set; }

今日はここまで。

1回目:
http://nodding-off-programmer.blogspot.jp/2017/06/einstein-vision_26.html

2回目(今回):

3回目:
http://nodding-off-programmer.blogspot.jp/2017/06/einstein-vision_28.html

2017年6月26日月曜日

Einstein Vision : 画像認識をアップロードファイルでやりたい

サンプルのEinstein Visionを動かすプログラムだと、画像ファイルをアップロードして画像認識は行えない。リンク先(こんな感じ)を指定すると画像を認識するという仕組みになっている。

画像ファイルをアップロードできるようにしたとしても、ファイルを認識させるやり方を調べないといけない。

と思っていたのだけど、ちゃんとメソッドが用意されていた。

public static List<Prediction> predictUrl(String url, String access_token, String model) {
    return predictInternal(url, access_token, model, false);
}

public static List<Prediction> predictBase64(String base64String, String access_token, String model) {
    return predictInternal(base64String, access_token, model, true);
}

public static List<Prediction> predictBlob(blob fileBlob, String access_token, String model) {
    return predictInternal(EncodingUtil.base64Encode(fileBlob), access_token, model, true);
}

Visionクラスに画像認識のメソッドがまとまっているだけれど、その中の"predictBlob"というメソッドがファイルを指定できるものになっている。ちなみに、"predictUrl"がリンクを指定するメソッド。

内部のソースをもっときちんと読めば色々とできるかもしれない。

1回目(今回):

2回目:
http://nodding-off-programmer.blogspot.jp/2017/06/einstein-vision_27.html
3回目:
http://nodding-off-programmer.blogspot.jp/2017/06/einstein-vision_28.html

2017年6月25日日曜日

Einstein Vision : 画像を任意のものを選択できるようにする

この前Einstein Visionのページをタブ表示するようにした。普通にタブを作っただけだから何の工夫もないけど。
Einstein Vision をタブ表示

画像認識を使いやすくするには、せめて自分で好きな画像を選べるようにしたい。

<apex:image url="http://metamind.io/images/generalimage.jpg">
</apex:image>

Einstein VisionのVisualforceは固定値で画像を指定しているので、これを変更することで好きな画像を選べるようになる。さらに、画像のリンクを入力できる入力ボックスとボタンを配置。

画像を指定できるように修正
<apex:image url="{!imageLink}"/>

入力ボックス&ボタンを配置
<apex:outputText>画像のリンクを入力してください:</apex:outputText>
<apex:inputText value="{!imageLink}"/>
<apex:commandButton value="画像認識"/>

ここまでやったらVisionControllerを修正する。Visualforceのほうで{!imageLink}で画像のリンクアドレスをVisionControllerとやり取りするようにしているので、これに合わせて修正を行う。

追加
public String imageLink { get; set; }

VisionControllerは画像のリンクを固定値で持っている。

return Vision.predictUrl('http://metamind.io/images/generalimage.jpg',access_token,'GeneralImageClassifier');

こいつを先ほど追加した imageLink を参照するように変更

変更
return Vision.predictUrl(imageLink, access_token, 'GeneralImageClassifier');

あとは作ったものを試してみる。

リンクを入力してから画像認識ボタンをクリック。


指定した画像で画像認識できた。

2017年6月23日金曜日

Salesforce : Einstein Vision をタブ表示

TrailheadでEinstein Visionを試してみたものの、プレビュー表示だけでタブを作成したわけではない。画像認識で遊びやすくするための第一歩として、タブを作成した。


これだけだと、何もできないけれど画像を指定できるようにしたりしようと思う。それにしても、画像認識技術をどんなふうに活用すればいいのかというアイデアはまったくない。

Salesforceを使うのは個人よりかは断然、企業だろう。個人での利用は、開発者がDeveloper Editionを使うとか用途が限定されていると思われる。ビジネスにどう使うものなのか、何か事例でもないかなあ。

2017年6月22日木曜日

Salesforce : Einstein Visionで画像認識

クイックスタート: Einstein Visionで作成した画像認識のプログラムで少し遊んでみた。

Trailheadではサンプルとして1枚の画像しか認識してなかったので、画像認識やってるという実感が得られなかったし、プログラマのさがなのか、固定値を表示しても同じことが起こるとか考えてしまう。

プログラムをほとんど読まずに作成してしまったので、実を言うと、どういうプログラムになっているのか分かっていない。もう少し真面目に読み込んで、画像認識を試している実感を得られるようにしたい。

プログラムを読んでいくと、お手軽にいくつかの画像で画像認識を試そうと思ったら2か所ほど手を入れればいいことが分かった。

作成した"Predict"という名前のVisualforceの4~5行目に下記のような記述がある。

<apex:image url="http://metamind.io/images/generalimage.jpg">
</apex:image>

まず、ここで指定しているurlをブラウザに表示させたい画像に変更する。これだと表示画像が変更されるだけなので、Apexのほうにも手を入れて、認識させる画像も変えてあげないといけない。

それが"VisionController"クラス。29行目を変更する。

return Vision.predictUrl('http://metamind.io/images/generalimage.jpg',access_token,'GeneralImageClassifier');

Vision.predicUrlメソッドの第一引数を認識させたい画像に変更することで、Einstein Visionが読み込む画像が変わる。

試してみた結果は下記通り。


この花がデイジーであるかどうかは、残念ながら私には分からないのだが、ちゃんと認識している気がする。

次はこれ。


2羽が写っているからか、自信なさげな結果になっているけどガチョウでたぶんあってるんじゃないだろうか。あひるかな?ガチョウとあひるの違い分からないし、これでOK。

自分で好きな画像を指定できるようにプログラムを改造できそうだ。