システム開発を好きになる。

2017年12月16日土曜日

C# で SQL Server 接続時に "SqlCommand.Prepare requires all variable length parameters to have an explicitly set non-zero Size" が発生した場合の対処

12月 16, 2017
オフィス狛 技術部です。

とあるプロジェクト(C#)で、今まで Oracle だけに接続していたのが、SQL Server への接続も追加になりました。
Oracle への接続は「Oracle.ManagedDataAccess.Client」を使っていましたが、
SQL Server への接続は、「System.Data.SqlClient」を使う事になりました。

※本当ならデータベースに依存しないように「System.Data.Common」を使って、
プロバイダファクトリ的に作るべきなのでしょうが、途中から変更するのが大人の事情で難しかったです・・・。

と言う事で、Oracleへの接続クラスをコピーして、使用するクラスなどを変えていざ実行してみると、

    "Message": "An error has occurred.",
    "ExceptionMessage": "SqlCommand.Prepare requires all variable length parameters to have an explicitly set non-zero Size",
    "ExceptionType": "System.ApplicationException",

「可変長パラメータには、必ずゼロ以外のサイズを指定しろ」と言っていますね。
確かに、プログラム的には、
string sql =
        "SELECT login_id" 
  + ",user_name "
  + "FROM authentication_info "
                + "WHERE user_identifier = :user_identifier "
                + "AND hoge_code = :hoge_code ";
〜中略〜
SqlParameter parameter1 = this._cmd.CreateParameter();
parameter1.Value = "123";
parameter1.ParameterName = ":user_identifier";
parameter1.DbType = DbType.String;
SqlParameter parameter2 = this._cmd.CreateParameter();
parameter2.Value = "123";
parameter2.ParameterName = ":hoge_code";
parameter2.DbType = DbType.String;
this._cmd.Parameters.Add(parameter1);
this._cmd.Parameters.Add(parameter2);
となっていて、サイズを指定していません。

「Oracle.ManagedDataAccess.Client」では不要でも、
「System.Data.SqlClient」では必要なんですね。

と言うわけで、プログラムも以下の様に変更します。
string sql =
        "SELECT login_id" 
  + ",user_name "
  + "FROM authentication_info "
                + "WHERE user_identifier = @user_identifier "
                + "AND hoge_code = @hoge_code ";
〜中略〜
SqlParameter parameter1 = this._cmd.CreateParameter();
parameter1.Value = "123";
parameter1.ParameterName = ":user_identifier";
parameter1.DbType = DbType.String;
parameter1.Size = parameter1.Value.ToString().Length;   // 追加
SqlParameter parameter2 = this._cmd.CreateParameter();
parameter2.Value = "123";
parameter2.ParameterName = ":hoge_code";
parameter2.DbType = DbType.String;
parameter2.Size = parameter2.Value.ToString().Length;  // 追加
this._cmd.Parameters.Add(parameter1);
this._cmd.Parameters.Add(parameter2);
これで問題なく実行出来ました。

ちなみに、「Oracle.ManagedDataAccess.Client」でのプリペアドステートメントには「:(コロン)」を使いますが、「System.Data.SqlClient」では「@(アットマーク)」を使います。

今回の話は大した話ではないのですが、
「Oracle.ManagedDataAccess.Client」では問題無かった、と言う部分がハマりどころなので、
わざわざ記事にしてみました。

2017年12月9日土曜日

Android Studio で DexArchiveMergerException: Unable to merge dex が発生した場合の対処

12月 09, 2017
オフィス狛 技術部です。

あるAndroidアプリの改修を行う事になり、Android Studio で gradleの設定を変更しました。
(対応のsdkversionを変更したり、ライブラリの追加など)

いざ、ビルドをすると・・・
Error:Execution failed for task ':app:transformDexArchiveWithExternalLibsDexMergerForDebug'.
> java.lang.RuntimeException: com.android.builder.dexing.DexArchiveMergerException: Unable to merge dex

これだけではちょっと分からないですね・・・。
まずは、エラーの詳細を知る為にビルドの設定を変えましょう。

Preferences...」->「Build, Execution, Deployment」->「Compiler」を選択して、
Command-line Options:」に「--stacktrace」と入力します。

この状態で再度ビルドしてみます。すると・・・
先程より情報が多く表示されました。その中でも注目すべきは、下記の部分です。
Error:com.android.dex.DexException: Multiple dex files define Lorg/apache/http/conn/HttpClientConnectionManager;
これは、Gradleにおける依存関係のエラーで、
dependenciesで指定しているライブラリが別のライブラリから使用されている場合などに出てきます。

結論から言ってしまうと、この時は単純なミスで、dependenciesに同じライブラリを2つ指定していた事がエラーの原因でした。
(記述が離れた場所にあったから気付かなかった・・・)

ただ、実際はそんな単純に解決できない場合もあります。
その場合は、各ライブラリの依存関係を調べてみましょう。

Android Studio のターミナルから下記のコマンドで依存関係を見ます。
./gradlew app:dependencies
この時、
./gradlew: Permission denied
となった場合、「gradlew」に実行権限が付いて可能性があるので、
下記コマンドで実行権限を付けます。
chmod +x gradlew

gradlewの出力は以下の様になります。(抜粋)
+--- com.android.support:appcompat-v7:26.1.0 (*)
+--- com.google.android.gms:play-services-gcm:11.6.2
|    +--- com.google.android.gms:play-services-base:11.6.2
|    |    +--- com.google.android.gms:play-services-basement:11.6.2
|    |    |    +--- com.android.support:support-v4:25.2.0 -> 26.1.0 (*)
|    |    |    \--- com.google.android.gms:play-services-basement-license:11.6.2
|    |    +--- com.google.android.gms:play-services-tasks:11.6.2
|    |    |    +--- com.google.android.gms:play-services-basement:11.6.2 (*)
|    |    |    \--- com.google.android.gms:play-services-tasks-license:11.6.2
|    |    \--- com.google.android.gms:play-services-base-license:11.6.2
|    +--- com.google.android.gms:play-services-basement:11.6.2 (*)
|    +--- com.google.android.gms:play-services-iid:11.6.2
|    |    +--- com.google.android.gms:play-services-base:11.6.2 (*)
|    |    +--- com.google.android.gms:play-services-basement:11.6.2 (*)
|    |    \--- com.google.android.gms:play-services-iid-license:11.6.2
|    \--- com.google.android.gms:play-services-gcm-license:11.6.2
+--- com.android.support:cardview-v7:26.1.0
|    \--- com.android.support:support-annotations:26.1.0
この情報から、各ライブラリの依存関係を確認します。

私の感覚だと、
しばらくメンテしてなかったアプリなどの、
ライブラリのバージョン上げる作業をしている時によく発生する気がします。

メンテは計画的に、ですね。


2017年11月23日木曜日

Oracle Data Provider for .NET (ODP.NET) で ORA-12154 が発生した場合の対処

11月 23, 2017

オフィス狛 技術部です。

弊社では、C# を使ってOracleに接続する際は、
Oracle Data Provider for .NET (ODP.NET) を使用する機会が多いです。
Oracle Clientのインストールが不要で、Oracleに接続出来るのが、やっぱり強みですね。

ただ、Oracle Clientと全く関係ないかと言うと、そうでもないので、トラブルが発生する事があります。

今回、とあるWebサーバーのOracleへの接続設定を変更した際、以下のようなエラーが発生しました。

ORA-12154:TNS: 指定された接続識別子を解決できませんでした

接続情報はプログラム側とWeb.config側で、以下のように設定しています。

[プログラム側]
private OracleConnection _con;
private string _constr = "User Id=komauser;"
           + "Password=komapass;"
           + "Data Source=SRC_TEST";

public void Connect()
{
    try
    {
        this._con = new OracleConnection(this._constr);
        this._con.Open();
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

[Web.config側]
<configuration>
  --(中略)--
  <oracle.manageddataaccess.client>
    <version number="*">
      <dataSources>
        <dataSource alias="SRC_DEV" descriptor="(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=192.168.1.111)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=komadb))) " />
      </dataSources>
    </version>
  </oracle.manageddataaccess.client>
</configuration>

Data Sourceの指定が間違えている」とすぐ気付くかもしません。
ただ、今回はそれに気付かず、数時間悩んでしまいました。

気付かなかった理由として、
今回、変更したのは接続先のIPアドレスだけで、変更前は確かに接続出来ていたからです。
そうなると、もうネットワークとかその辺の設定ミスを疑ってしまいます。

まあ、結局数時間後に気付いたのですが、
では何故変更前はこの状態で接続出来たのか・・・。

実は、ODP.NET(パッケージはOracle.ManagedDataAccess.Client ) が Data Source を判断するのは、優先順位があります。
Oracle Data Provider for .NET, Managed Driver Configuration dataSources Section
によると、最優先は「 Web.config の oracle.manageddataaccess.client タグ」なのですが、
そこに合致しない場合は、「tnsnames.ora」を参照するようです。

と言う訳で、該当のWebサーバーを調べたら・・・「tnsnames.ora」ありました。
そこにしっかりと、
SRC_TEST =
  (DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.1.110)(PORT = 1521))
    )
    (CONNECT_DATA =
      (SERVICE_NAME = komadb)
    )
  )
と記載されていました。

と言う訳で、
ODP.NET で 接続がうまくいかない場合、Data Source の指定を疑ってみましょう、と言う話でした。


2017年5月27日土曜日

AWS EC2 で Amazon Linux を使用する場合に最初にやること

5月 27, 2017

オフィス狛 技術部です。

AWSでEC2インスタンスを立ち上げる度に、設定すべき事を忘れてしまうので、
備忘録の為にブログに残しておこうと思います。

「aws ec2 やる事」でググると、もっと細くやる事を記載している方も居るので、
しっかりやりたい方は、それらを参考にして下さい。

ここに記載するのは、本当に最低限の設定です。

何はともあれ yum update

まずは yum update です。
$ sudo yum update -y

タイムゾーンの変更

Amazon Linux はデフォルトのタイムスタンプが UTC になっているので、日本時間に変更します。
$ date
2017年  5月  20日 土曜日 02:33:43 UTC
$ sudo ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
$ date
2017年  5月  20日 土曜日 11:35:33 JST

このままでは、サーバーの再起動を行うと UTC に戻ってしまうので、設定を変更します。
$ sudo vim /etc/sysconfig/clock

※「ZONE="UTC"」となっている部分を「ZONE="Asia/Tokyo"」に変更します。

ec2-user の 削除

ec2-user はデフォルトのユーザーなので、セキュリティ上、削除しておいた方が良いです。
(もちろん変わりのユーザーも作成します。)

まずは、新規ユーザー(ofkomauser)を追加します。
$ sudo useradd ofkomauser

鍵などをec2-userからコピーし、権限設定を行います。
$ sudo cp -arp /home/ec2-user/.ssh /home/ofkomauser/
$ sudo chown -R ofkomauser /home/ofkomauser/.ssh

新しいユーザーのパスワードを設定します
$ sudo passwd ofkomauser

sudoersファイルを編集し、新しいユーザーで sudo が使えるようにします。
$ sudo visudo -f /etc/sudoers.d/cloud-init
※「ec2-user」の部分をコメント化、もしくは削除し、新しいユーザーを追加します。
#ec2-user ALL = NOPASSWD: ALL
ofkomauser ALL = NOPASSWD: ALL

# User rules for ec2-user
#ec2-user ALL=(ALL) NOPASSWD:ALL

そして、最後に「ec2-user」を削除します。
$ sudo userdel -r ec2-user

以上が、最低限の設定になります。

EC2の用途によっては、もっと細かい設定は必要ですが、
それはまった追って記事にしようと思います。


2017年5月6日土曜日

Codeigniter で他システムとセッション共有する場合に、$_SESSION が消えてしまう

5月 06, 2017
オフィス狛 技術部です。

同じドメイン配下に存在する別システム(Codeigniter ではないPHPを使ったシステム)と、
セッションを共有する・・・つまり、$_SESSION で値をやりとりする事になったのですが、
Codeigniter 側で $_SESSION から値を取ろうとすると、空になっている現象が発生しました。
単純には行かないのですね、やっぱり。

※ちなみに、Codeigniter のバージョンは 3.1.2 です。

1)セッションIDを調べてみる

セッションIDが変わってしまって、違うセッションを見ているのかと思い、
$_COOKIE を調べたところ、session_id は同じでした。
(同じドメインですし、敢えてセッションを変えるような設定もしていないので)
セッションIDが同じで値が取れないのであれば、どこかで値を消している、という事になります。

2)Codeigniter を使用していないPHPシステムで試してみる

Codeigniter を使用していない、Webサーバー上に置いただけのPHPファイルで試してみたところ、
$_SESSION から値が取れる事が確認出来ました。
という事は、Codeigniter でセッションを消している事になります。

3)Codeigniter の Session.php を調べてみる

というわけで、Codeigniter の System/libraries/Session に存在する、
「Session.php」を調べてみました。

105行目付近に、
$class = $this->_ci_load_classes($this->_driver);
という記述があり、このタイミングで driver を変更しているため、
この処理以降に設定した driver のセッションを参照しているのではないかと予想できます。

※実際、この処理の直前では $_SESSION の値は取得できたが、
直後には値が消えていた事を確認しています。

4)対応方法(暫定)

とりあえず、先程の記述の直前で、
// 追加ここから
session_start();
$session = $_SESSION // 値を一時変数に格納
session_write_close();
// 追加ここまで
$class = $this->_ci_load_classes($this->_driver);
と記載します。実は、session_start 自体はもっと後で行うのですが、
ここで一旦スタートさせて、$_SESSION の値を退避しておきます。
(closeも忘れずに)
そして、今度は、元々あった「session_start」(150行目近辺)の後に、
session_start();
//追加ここから
$_SESSION = $session; // 値を一時変数から再設定
// 追加ここまで
と追加する事で、他システムで設定された $_SESSION を利用する事ができます。

5)本当はどう対応するべきなのか

実は、弊社側のシステムは、セッションをDBに保存する設定にしています。
そしておそらく、他システム側は、セッションはファイル保存していると思います。
(弊社保有のシステムでは無いので、実際は分からないですが・・・)
ですので、$_SESSION が消えてしまうのも、当然と言えば当然なのです。
だとするならば、
こちら側の driver を「files」にして、保存パスを共有した場合、うまくいきそうな気がします。

※ちなみに、設定出来る driver は、本家サイト(翻訳版)に記載してありますが、
  • files(デフォルト; ファイルシステムベース)
  • database
  • redis
  • memcached
となっています。(自分でカスタムセッションドライバも作成可能です。)

今回は、テスト環境含め、弊社が触れる環境では無いので、
色々試せないですが、いずれ社内に環境作って試してみようと思います。


2017年4月21日金曜日

Jenkins をアップデートしたら、 HTTP ERROR 503 Service Unavailable が発生

4月 21, 2017
オフィス狛 技術部です。

Jenkinsは、頻繁にアップデートが出るので、本番稼働中だったりするとなかなか反映できません。
今回も、以下のようなアラートが出たので、まずは開発環境からアップデートする事に。

弊社の場合、JenkinsをインストールしているのはAWS EC2なので、一番簡単なアップデートは、
sudo yum update です。
そして、アップデートした後に、接続してみると・・・
HTTP ERROR 503 Service Unavailable」になりました。


・・・・だから、嫌なんですよ、この類のアップデート。と愚痴っても仕方ないです。
いきなり本番環境をアップデートしなかった事をラッキーと思わないと。
(いや、しないですけどね、そんな事)

ログを確認すると、以下の内容でした。
UnsupportedClassVersionError」?
何かが古い?
WARNING: Failed startup of context w.@a38dddf{/,file:/var/cache/jenkins/war/,STARTING}{/var/cache/jenkins/war}
java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:606)
        at org.eclipse.jetty.webapp.IterativeDescriptorProcessor.visit(IterativeDescriptorProcessor.java:85)
 (中略)
        at Main._main(Main.java:264)
        at Main.main(Main.java:112)
Caused by: java.lang.UnsupportedClassVersionError: jenkins/util/SystemProperties : Unsupported major.minor version 52.0
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:803)
        at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
 (中略)
        at org.eclipse.jetty.server.handler.ContextHandler.loadClass(ContextHandler.java:1583)
        at org.eclipse.jetty.webapp.StandardDescriptorProcessor.visitListener(StandardDescriptorProcessor.java:1956)
        ... 25 more

色々と調べてみると、本家に issue がありました。
Jenkins 2.54 causes java.lang.UnsupportedClassVersionError

なるほど、Java 8が必須になったのですね。
確かに、EC2 の Amazon Linux はデフォルト Java 7 です。

と言うわけで、Javaのアップデートをしていきます。

まずは現在のバージョンを改めて確認してみます。
$ java -version
java version "1.7.0_131"
OpenJDK Runtime Environment (amzn-2.6.9.0.71.amzn1-x86_64 u131-b00)
OpenJDK 64-Bit Server VM (build 24.131-b00, mixed mode)
$ yum list installed | grep java
java-1.7.0-openjdk.x86_64             1:1.7.0.131-2.6.9.0.71.amzn1 installed    
javapackages-tools.noarch             0.9.1-1.5.amzn1              installed    
tzdata-java.noarch                    2017b-1.69.amzn1             installed

Java 7 である事が確認出来たので、次は Java 8 にアップデートします。
$sudo yum install java-1.8.0-openjdk-devel

alternatives でバージョンを切り替えます。
$ sudo alternatives --config java

2 プログラムがあり 'java' を提供します。

  選択       コマンド
-----------------------------------------------
*+ 1           /usr/lib/jvm/jre-1.7.0-openjdk.x86_64/bin/java
   2           /usr/lib/jvm/jre-1.8.0-openjdk.x86_64/bin/java

Enter を押して現在の選択 [+] を保持するか、選択番号を入力します:2

念の為、適用されているバージョンを確認します。
$ java -version
openjdk version "1.8.0_121"
OpenJDK Runtime Environment (build 1.8.0_121-b13)
OpenJDK 64-Bit Server VM (build 25.121-b13, mixed mode)

無事にアップデート出来たので、Jenkinsの再起動を行います。
再起動は
http://[ホスト名 or IPアドレス]/safeRestart
に接続する事で、簡単に行う事ができます。

これで無事に接続できました。
アップデートの度にこんな思いするのは、本当辛いです。


2017年4月2日日曜日

Redmine チケットの状態変更をSlackに連携する

4月 02, 2017
オフィス狛 技術部です。

オフィス狛では、社内の連絡に関してメールは使わず、全てSlackでやり取りしています。
ただ、Redmineに関してはデフォルトだとメール通知しか出来ないので、
Redmineの為だけにメールチェックを行う事が必要になってしまいます。
このメールチェックをやめたい、という事で、
Redmineでチケットが登録・編集された際に、Slackへ通知するようにしていきます。

まずは、Slackのチャンネル作成、Slack側のアプリ作成作業を行う必要があるのですが、
この辺の作業は、下記を参照下さい。
AWS EC2 にインストールした GitLab と Slack の連携
という事で、この先は、Slackのチャンネル作成(今回は、「#redmine-notification」というチャンネルを作成)が完了していて、
且つ、Slack側のアプリ作成〜Webhook用のURL取得済みとして、進めて行きます。

1)redmine_slack(プラグイン)のインストール

まずはRedmineのプラグインをインストールします。
$ cd /var/lib/redmine/plugins
$ git clone https://github.com/sciyoshi/redmine-slack.git redmine_slack
$ bundle install
$ rake redmine:plugins:migrate RAILS_ENV=production
$ touch /var/lib/redmine/tmp/restart.txt
ここでは、redmineのホームディレクトリが「/var/lib/redmine」の場合を想定しています。
「restart.txt」を作成しておく事で、apacheの再起動無しで、Redmineの再起動を行う事ができます。
(ファイル作成後に、ブラウザでRedmineに接続した時に再起動が行われます。)

正しくインストールされていると、Redmineの「管理 > プラグイン」から、以下のように確認出来ます。


2)カスタムフィールドの作成

続いて、Redmineの「管理 > カスタムフィールド」で「プロジェクト」を選択し、
新しいカスタムフィールドを作成します。
名称は「Slack Channel」にしておきます。


3)redmine_slack(プラグイン)の設定

Redmineの「管理 > プラグイン」から、Redmine Slackの「設定」を選択します。
以下のような設定画面が出てくるので、
Slack側のアプリを作成した際のWebhook用URLを入力します。
今回は、チケットの更新のみを連携の対象としています。(「Post Issue Updates?」にチェックを付けている)


そして、先ほどカスタムフィールドの名前を「Slack Channel」にした意味がここで分かります。
redmine_slackは、デフォルトだと、全プロジェクトで共通な一つのチャンネルのみ連携が出来ます。

その連携先チャンネルをプロジェクト単位で決めたい場合は、
カスタムフィールドで「Slack Channel」を作成する必要があります。

ちなみに、プラグイン設定の方の「Slack Channel」は必須項目で、
指定無しには出来ないので、「-(半角ハイフン)」を設定しています。

4)Redmine各プロジェクトごとの設定

プロジェクトの「設定 > 情報」を表示すると、追加したカスタムフィールドが出てくるので、
ここに、Slackのチャンネル(今回は、「#redmine-notification」)を入力します。


これで、このプロジェクトのチケットが変更された際、Slackへの連携が可能になります。


備考:連携の際に気を付ける事

弊社の場合、Git(GitLab)のリポジトリにPushした際にSlackへ通知が飛び、
同時に、リポジトリへの変更がRedmineが参照しているリポジトリへ連携されるようになっています。
つまり、コミットコメントなどで、Redmineのチケットの状態を変更するような記載(「fixes #xxxx」とか)をすると、 Slackの通知が2回飛ぶ事になってしまい、これが開発繁忙期になると、通知がピーピー鳴りまくるので、 通知が必要なプロジェクトを見極める必要があります。

弊社では、お客様とのやりとり(バグトラッキング)で、通知は漏れなく欲しい場合、
そして、そのプロジェクトでGitのリポジトリを参照していない場合のみ、
今回のような設定を行なっています。