bokusui について

ソフトウェアハウスでのPG・SEから始まり、10年近く勤めた金融系企業の社内SEを数年前にやめ、フリーランス時代を経たのち法人成りしました。システム開発の全工程をこじんまりとやり続けています。

AI・業務自動化展を見てきた

 今やってる仕事のテーマ的に、業務自動化みたいな開発案件が多いので、何かいいものがあればという事で、幕張でやってるAI・業務自動化展に行ってきました。

 メッセの会場は、他テーマの展示とかも含まれているので結構な人でしたが、対象の展示範囲は狭く、今話題?のRPAから自動梱包マシーンまであり、対象は様々。AIというテーマではチャットとかボット、自動化というテーマではRPA系が多かったでしょうか。RPA系の中でも操作手順(シナリオ)の登録UIがいい感じの展示もありましたが、画面操作を自動化するという機能があればRPAって事になっているような感じなので、各展示の違いは解りませんでした。
 ちゃんと見たのは、昔DBツールとしてよく使ったObjectBrowserが、これからはAIを活用して画面イメージから設計書を自動作成するみたいなプレゼンくらい。これだと使用者がシステム開発関係者に限定されるので、何にAIが活用されどんなアウトプットが期待出来るか使用ケースが解りやすい感じです。

 結局、AI活用として今のところ実用性があるのは、画像認識と音声認識くらいですかね。それもGoogleとかMicroSoftとかが用意しているAPIを使用して何かするレベル以上にはまだまだな感じ。AIとかRPAとか言葉だけが先行して、その定義も曖昧なまま。。
 RPAとされているものが実現できるものって、ネイティブアプリを対象にさえしなければ、これまでエクセルマクロとかでちょいと1時間で作ったものと特に変わらないような。。

ZendFramework1でREST APIを試してみた

既存の業務アプリ内にある機能を外部から利用出来たらなあ・・・のような要件があり、RestAPIで実装を検討しました。対象のアプリは、今となっては・・・ですが、ZendFramework1が使われています。

こちらにいろいろ書いていますが、Zend_Rest_Controllerを使えばいいっぽい。

開発環境はEclipse + php + XAMPP
XAMPP内のphp/pearの中に、ダウンロードしたZendFramework1.12.20のZIPからlibrary内のZendを配置、XAMPP内のapache/conf/extra/httpd-xammp.confに該当プロジェクトのAliasをセットして環境ができました。

    Alias /testapp "C:/xampp/htdocs/TestAPP/html/"
    
        AllowOverride All
         Require all granted
    

テスト用のEclipceプロジェクトはこんな感じで最少構成。

.htaccess でindex.phpに仕向けます。

RewriteEngine on
RewriteBase /testapp/
RewriteRule !\.(js|ico|gif|jpg|png|css)$ index.php

index.phpでdispatchします。

<?php
require_once 'Zend/Controller/Front.php';

$controller = Zend_Controller_Front::getInstance();
$controller->setControllerDirectory('../application/controllers');
$controller->setParam('noViewRenderer', true);

try {
	$controller->dispatch();
} catch (Exception $e) {
}

ControllerにZend_Rest_Controllerとして必要なメソッドを追加していきます。

<?php
require_once 'Zend/Rest/Controller.php';

class TestController extends Zend_Rest_Controller  {

    public function indexAction() {
		$this->getResponse()->appendBody("hello world " . $_REQUEST[param1]);
    }

    public function headAction(){
//TODO
    }
    public function getAction() {
//TODO
    }
    public function postAction() {
//TODO
    }
    public function putAction() {
//TODO
    }
    public function deleteAction() {
//TODO
    }
}

で、ブラウザから動作確認

URLをいろいろ意識すると難しそうですが、これなら簡単ですね。

Windows10でHTAがエラーになる

 HTA&VBSの構成で社内利用向けに作ったツール(社内端末はwin7)について、今後のWindows10導入へ向けて稼働確認をしたところ、エラーメッセージが出て動作しませんでした。

で、ネットでいろいろ調べたところ、
https://msdn.microsoft.com/ja-jp/library/cc817574.aspx
等を見ながら、

<meta http-equiv="x-ua-compatible" content="IE=9"/>

を入れてみたところ、エラーは無くなったのですが、画面レイアウトが崩れる結果に・・・
 社内ではこれからの新しい端末はWin10に、既存は当面Win7という方針なので、ツールとしてはどちらも同じように動作して欲しいところです。

 で、上記metaタグを外して結局何がエラーなの?と調べていくと、WMIのExecQueryを使った箇所がWin10ではエラーとなる事が判明。どうもセキュリティがらみの変更っぽいですが、詳細はよくわかりませんでした。。

 今回は別命令で置き換えが可能だったので、それに置き換えて問題無く動作するようになりましたが、画面だけは、フォントサイズの影響があったので微調整しておきました。
 そろそろHTAを止めたいのですが、Windowsマシン前提で簡易な画面ツールを作るとなると、まだ代替え手段が見つかりません。。

Apache CommonsのDateUtilsに機能を拡張する

 よくあるケースと思いますが、メジャーなライブラリにもうちょっと機能が欲しい時、そのライブラリを拡張して共通ライブラリとして使いまわしたりしますよね?
筆者がJavaを使う場合は、だいぶ前からApache CommonsのDateUtilsを拡張し、String・Dateの相互変換メソッドとかを追加していろんな開発案件で使いまわしてましたが、今回、文字列から日時型に変換するけど、どんな文字列パターンになるかが不明確、、という要件があったので、拡張していたクラスに機能を追加し、一般的に日時として使われる文字列からのDate変換機能を追加してみました。
※2021.04 長文からの日時文字列取得で問題があったので修正しています。

package jp.esoro.common.utils;

import java.text.Normalizer;
import java.text.Normalizer.Form;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.time.DateFormatUtils;

/**
 * 日付処理クラス
 * */
public class DateUtils extends org.apache.commons.lang3.time.DateUtils {

	/**
	 * 日付表示パターン(YYYY/MM/DD hh:mm)
	 */
	public static final String FORMAT_YYYY_MM_DD_HH_MM = "yyyy/MM/dd HH:mm";

	public static final String FORMAT_YYYY_MM_DD = "yyyy/MM/dd";
	public static final String FORMAT_YYYYMMDD = "yyyyMMdd";
	public static final String FORMAT_YYMMDD = "yyMMdd";
	public static final String FORMAT_JPN_YYYY_MM_DD = "yyyy年MM月dd日";

	public static final String FORMAT_YYYY_MM_DD_HH_MM_SS = "yyyy/MM/dd HH:mm:ss";

	public static final String FORMAT_MYSQL_DATE = "yyyy-MM-dd";
	public static final String FORMAT_MYSQL_DATETIME = "yyyy-MM-dd HH:mm:ss";
	public static final String FORMAT_YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
	public static final String FORMAT_YYYYMMDDHHMMSSmi = "yyyyMMddHHmmssSSS";
	public static final String FORMAT_YYMMDDHHMMSS = "yyMMddHHmmss";
	public static final String FORMAT_YYMMDDHHMMSSmi = "yyMMddHHmmssSSS";

	/**
	 * 日付(Calendar)を文字列に編集する
	 * @param date
	 * @param pattern
	 * @return
	 */
	public static String format(Calendar date, String pattern) {
		if (date == null) {
			return null;
		}
		return DateFormatUtils.format(date, pattern);
	}

	/**
	 * 日付(Date)を文字列に編集する
	 * @param date
	 * @param pattern
	 * @return
	 */
	public static String format(Date date, String pattern) {
		if (date == null) {
			return null;
		}
		Calendar cal = Calendar.getInstance();
		cal.setTime(date);
		return format(cal, pattern);
	}

	/**
	 * システム日付を取得する
	 * @return Date
	 */
	public static Date getDateTime() {
		return Calendar.getInstance().getTime();
	}

	/**
	 * 文字列をDate型へ変換する
	 * @param value(yyyy_mm_dd)
	 * @return
	 */
	public static Date stringToDate(String value) {
	    return stringToDate(value , FORMAT_YYYY_MM_DD);
	}

	/**
	 * 文字列をDate型へ変換する
	 * @param value
	 * @param pattern 
	 * @return
	 */
	public static Date stringToDate(String value ,String pattern) {
	    SimpleDateFormat sdf = new SimpleDateFormat(pattern);
	    if (value == null) {
	    	return null;
	    }
	    try {
	        return sdf.parse(value);
	    } catch (ParseException e) {
	        return null;
	    }
	}
//ここまで以前から、以降を追加
	/**
	 * 日時文字列をDate変換する
	 * @param str Date変換したい文字列
	 * @return Date 取得出来ない場合はnull
	 * @see あらかじめ文字列パターンが判明している場合は、stringToDateを使用してください)
	 */
	public static Date extractDate(String str) {
		final String[] regexs = {"([0-9]{2,4})(/|-|¥¥.|年)([01]?[0-9])(/|-|¥¥.|月)([0123]?[0-9])(日|日¥¥s+|¥¥s|_)([0-9]{1,2})(:|-|¥¥.|時)([0-9]{1,2})(:|-|¥¥.|分)([0-9]{1,2})",
				"([0-9]{2,4})(/|-|¥¥.|年)([01]?[0-9])(/|-|¥¥.|月)([0123]?[0-9])(日|日¥¥s+|¥¥s|_)([0-9]{1,2})(:|-|¥¥.|時)([0-9]{1,2})",
				"([0-9]{2,4})(/|-|¥¥.|年)([01]?[0-9])(/|-|¥¥.|月)([0123]?[0-9])(日|日¥¥s+|¥¥s|_)([0-9]{1,2})(:|-|¥¥.|時)",
				"([0-9]{2,4})(/|-|¥¥.|年)([01]?[0-9])(/|-|¥¥.|月)([0123]?[0-9])",
				};
		Date ret = null;
		String normStr = Normalizer.normalize(str, Form.NFKC);
		try{
			String chkStr = StringUtils.removePattern(normStr, "am|AM|Am|pm|PM|Pm|午前|午後");
			
			for(String regex : regexs){
				Pattern pattern = Pattern.compile(regex, Pattern.DOTALL|Pattern.CASE_INSENSITIVE);
				Matcher matcher = pattern.matcher(chkStr);
				
				if(matcher.find()){
					if(matcher.groupCount() >= 5){
						boolean isPm = false;
						if(normStr.indexOf(matcher.group()) < 0 && matcher.groupCount() >= 7){
							if( normStr.matches(".*"+matcher.group(1)+matcher.group(2)+matcher.group(3)+matcher.group(4)+matcher.group(5)+matcher.group(6)+"(pm|PM|pm|午後).*")){
								isPm = true;
							}
						}
						
						if(matcher.group(1).length() == 2){
							ret = DateUtils.setYears(getDateTime(), Integer.valueOf("20" + matcher.group(1)));
						}
						else{
							ret = DateUtils.setYears(getDateTime(), Integer.valueOf(matcher.group(1)));
						}
						ret = DateUtils.setDays(ret, 1);
						//Month is 0 -11
						ret = DateUtils.setMonths(ret, Integer.valueOf(matcher.group(3))-1);
						ret = DateUtils.setDays(ret, Integer.valueOf(matcher.group(5)));
						
						if(matcher.groupCount() >= 7){
							int hour = Integer.valueOf(matcher.group(7));
							ret = DateUtils.setHours(ret, hour + (hour<12 && isPm?12:0));
						}
						else{
							ret = DateUtils.setHours(ret, 0);
						}
						if(matcher.groupCount() >= 9){
							ret = DateUtils.setMinutes(ret, Integer.valueOf(matcher.group(9)));
						}
						else{
							ret = DateUtils.setMinutes(ret, 0);
						}
						if(matcher.groupCount() == 11){
							ret = DateUtils.setSeconds(ret, Integer.valueOf(matcher.group(11)));
						}
						else{
							ret = DateUtils.setSeconds(ret, 0);
						}
					}
					return ret;
				}
			}
			if(ret == null){
				SimpleDateFormat sdf1 = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy",Locale.ENGLISH);
		        ret = sdf1.parse(chkStr);
			}
		}catch(Exception e){
	        return null;
		}
		return ret;
	}
}

で、テストです。

package test;

import jp.esoro.common.utils.DateUtils;

public class Datetest {
	static String[] dateStrs = {
			"20170131",
			"2017-9-2 15:00:00",
			"2017/9/2 15:25:00",
			"2017年9月2日8時34分51秒",
			"2017年1月2日8時32分",
			"2017年12月31日午後11時集合",
			"2017年12月2日8時",
			"2017.12.2 PM8:00",
			"2017.12.3 AM8:25",
			"2017.01.31",
			"2017.1.32"	};

	public static void main(String[] args) {
		for(String str : dateStrs){
			System.out.println(str + " is " + DateUtils.format(DateUtils.extractDate(str),DateUtils.FORMAT_YYYY_MM_DD_HH_MM_SS));
		}
	}
}

テスト結果
20170131 is null
2017-9-2 15:00:00 is 2017/09/02 15:00:00
2017/9/2 15:25:00 is 2017/09/02 15:25:00
2017年9月2日8時34分51秒 is 2017/09/02 08:34:51
2017年1月2日8時32分 is 2017/01/02 08:32:00
2017年12月31日午後11時集合 is 2017/12/31 23:00:00
2017年12月2日8時 is 2017/12/02 08:00:00
2017.12.2 PM8:00 is 2017/12/02 20:00:00
2017.12.3 AM8:25 is 2017/12/03 08:25:00
2017.01.31 is 2017/01/31 00:00:00
2017.1.32 is null

YYYYMMDDってのは、ここでは対象外です。だって、ただの数字の羅列は文字として日付とは言えないし。

2年たってバグってた事が判明。1日に戻すのを追加しました。。

カテゴリー: Java

法人住民税の中間納付を忘れてました。。

 そろそろ法人2期目の申告をする時期なんですが、いろいろ書類を整理していた所、
「法人県民税・事業税・地方特別税の予告申告について」
という黄色い書類が出てきて、あれ?中間納付って3月にしたよなあ?と銀行の通帳とかを調べたら、やっぱりどこにも該当が無い。。
 国税の中間申告だけ対処していて、県民地方税の納付がされていない事が判明。。
 今月中に決算申告しなきゃいけないので、ヤバいと思い、管轄の県税事務所に電話した所、とりあえず、そのまま申告して下さいとの事。
 この辺の納付って一元化して欲しいなあ。って、国税管轄の地方法人税もあり、県民地方税とか解りにくいし。。

redmineでたまにInternalErrorが出るようになった。

 筆者が通っているユーザーさんでは、redmineに独自開発含めプラグインを10個くらい入れて使っているのですが、何時からかInternalErrorが出るようになり、その調査を依頼されました。
 状況的にはチケットの一覧や詳細を表示する時、たまにInternalErrorとなる事があり、ただ再表示すれば普通に表示されるという何とも解りにくい状況。。
 redmineのログには下記InternalError発生時の内容が出力されています。

Completed 500 Internal Server Error in 6169.9ms

ActionView::Template::Error (undefined method `deep_symbolize_keys' for #<String:0x007f34da039088>):
    50: <p id="start_date_area">
    51:   <%= f.text_field(:start_date, :size => 10, :disabled => !@issue.leaf?,
    52:                    :required => @issue.required_attribute?('start_date')) %>
    53:   <%= calendar_for('issue_start_date') if @issue.leaf? %>
    54: </p>
    55: <% end %>
    56: 
  lib/redmine/i18n.rb:131:in `store_translations'
  lib/redmine/i18n.rb:152:in `init_translations'
  lib/redmine/i18n.rb:166:in `lookup'
  lib/redmine/i18n.rb:30:in `l'
  app/helpers/application_helper.rb:1180:in `block in include_calendar_headers_tags'
  app/helpers/application_helper.rb:1167:in `include_calendar_headers_tags'
  app/helpers/application_helper.rb:1159:in `calendar_for'
以下省略

i18nが関係しているようですが、たまにしか起きないし、それ以外に問題箇所を特定できそうな出力は読み取れません。
いろいろ試行錯誤してみますが原因は不明。。で、国際化対応が絡んでいるという事で、ログインユーザーの言語設定を英語に変えてみた所、相変わらずInternalErrorがたまに出るものの、ログの出力内容が変わりました。

Completed 500 Internal Server Error in 2011.0ms

NoMethodError (undefined method `deep_symbolize_keys' for #<String:0x007f8f44c7d8c0>):
  lib/redmine/i18n.rb:131:in `store_translations'
  lib/redmine/i18n.rb:152:in `init_translations'
  lib/redmine/i18n.rb:166:in `lookup'
  lib/redmine/i18n.rb:27:in `l'
  app/models/issue_query.rb:172:in `initialize_available_filters'
以下省略

で、エラーが出始めた時期とかを考慮すると、独自プラグインを入れた後なので、そのソースを眺めていると、en.ymlに不備を発見。直して入れてみると問題が解消したようです。

weblogic管理画面のパスワードを忘れた。。

 2年前くらいからOEPEを入れて開発に使っているwindows端末で、webアプリをweblogicで動かしてテストしたいなと思い、weblogicの管理画面からwarファイルをデプロイしようとした所、管理画面のログインパスワードを完全に忘れていました。。
デフォルトユーザーはweblogicだったのは覚えてましたが、心当たりがあるパスワードを入れても全くダメ。で、どうやってリセット出来るか?をググっても何も解らず、困っていた所、下記を発見。
https://docs.oracle.com/cd/E28613_01/web.1211/b65928/weblogicserver.htm#i1013039
開発端末なので既存のドメインにログイン出来なくなっても、ドメインを新規で作ればとりあえず動かせると言う事で。。本番環境では致命的ですけど。

ManagedBeanが非推奨になっていた

JavaEEでJSF2とJAX-RSを使った簡単な業務Webアプリを作っていたんですが、それまで作っていた開発環境のままWebLogicでローカルテストしてある程度動作するようになった後に、さすがにこの規模のAPにWebLogicを用意するのは無理!という事になったので、前にちょっと別件で使ってみたglashfish実装のpayaramicroで動くように変更してみました。
 まず、普通にweblogicで動かしていたwarをpayaraにデプロイすると下記エラーで失敗します・・

[[FATAL] No injection source found for a parameter of type public javax.ws.rs.core.Response

 正直あまり意識してなかったのですが、、やっぱりweblogicになると内包しているライブラリに依存しやすく、ちゃんと内部を理解していないと何が作用して動作しているかが解りにくいです。。
 エラーを見る限り、jax-rs関連でエラーになっている模様。
いろいろライブラリを置き換えていくと、既にManagedBeanが非推奨になっているようだ。。
結果pom.xmlは下記になり

<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.faces/javax.faces-api -->
<dependency>
    <groupId>javax.faces</groupId>
    <artifactId>javax.faces-api</artifactId>
    <version>2.3</version>
    <scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.enterprise/cdi-api -->
<dependency>
    <groupId>javax.enterprise</groupId>
    <artifactId>cdi-api</artifactId>
    <version>2.0</version>
    <scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.glassfish.jersey.containers/jersey-container-servlet -->
<dependency>
    <groupId>org.glassfish.jersey.containers</groupId>
    <artifactId>jersey-container-servlet</artifactId>
    <version>2.25.1</version>
</dependency>

@ManagedBeanを@Namedに置き換え、SessionScopedをjavax.enterprise.contextに変えたりして、ようやくpayaraで動くようになりました。

 で、とりあえず動かすことはできたとは言え、今やWebアプリを積極的にJavaEEで実装する理由が少なくなっている気がします。Wabアプリならphpとかruby on Railsでの大規模サイトの構築事例がたくさんあります。速度的なアドバンテージも今となってはキャッシュにより殆ど感じられないですし、その実装対象のシステムに何等かの制約やレガシーな理由が絡んでないと敢えてJavaEEを選択する理由は何だろう?JavaEEがいまいち普及しないのは結局の所、各実装はベンダー次第となっている事が結果的にアプリケーションサーバの実装次第となってしまい、Javaとして最大の魅力である(と筆者は思っている)OSを超えた一貫性というものが、各APサーバが内包しているライブラリがバラバラである事により、消え失せてしまっているような気がします。

JavaMailで送信日時が取得できないケースがある

 メールを受信する業務用ツールをJavaMailで実装して、かれこれ一年近く普通に使っていたのですが、新しい要件に沿った機能を追加してテストしていたところ、メールの送信日時を取得
javax.mail.Message.getSentDate()
でnullを想定しておらずエラーに。。
これまで何も問題がなかったのですが、どうもメールによっては取れないケースがある模様。
メールをemlファイルにして中身をエディタで見たところ、

問題無いケース Date: Fri, 23 Jun 2017 11:23:10 +0900
問題有るケース Date: Tue Jun 6 12:31:19 2017

 で、試しにemlファイルのDateヘッダを問題無いケースに書き換えてやってみたところ、普通にJavaMailで送信日時が取れた、、という事は?

 細かくは調べてませんが、問題となるメールはDate書式がMIMEの仕様に沿っていないようです。
仕様に沿っていないのか、仕様が明確でないのか、もしかして、仕様が明確になる前からあるSMTPサーバなのか・・?

 OutLook等の一般的なメーラーで見る限り、普通に送信日時として表示されます。
こういう場合、メールを受信する側からすれば、細かい箇所が仕様に沿っていなくても、問題無く処理できるものを用意しなくてはなりません。
とはいえ、なかなか事前に想定するのは難しく、メールみたいに昔からあるものは特に注意しなければいけませんね。。

カテゴリー: Java

消費税の届け出に関する書類が来た

 先日、税務署から「消費税の届出のお尋ね」という書類が来ていました。
あれ?確か、起業から2年は免税じゃなかったっけ?と思いながらも、書類を読んでも「課税期間」という言葉がピンとこないので、税務署の方に電話してみました。なお、筆者の法人はあと2か月で2期目終了となります。
 税務署の電話口では、企業名を言うとすぐに過去の申告内容が検索できるようになってるらしく、前期の売上が1千万超えているから提出が必要ですとの事。ただ、2年間免税なのはその通りで問題無く、課税期間=3期目が始まる今のタイミングで「消費税課税事業者届出書」を提出する必要があるようです。

 もうちょっと調べていくと、消費税には、「原則課税」と中小事業者向けの特例「簡易課税」の2つの計算方法あり、「簡易課税」を選択する場合は、こちらも事前に「消費税簡易課税制度選択届出書」という書類を提出しておく必要があるとの事。
 となると、どちらの計算方法を選択すべきかという事になるのですが、簡易課税を選択すると業種によって「みなし仕入れ率」が決まっており、筆者のような業種だとサービス業に入るので50%で、売上額*消費税率*みなし仕入れ率で、おおよそ消費税率に対し半分が消費税納付額になる模様。一方の「原則課税」は、仕入れ関係の帳簿や請求書等を確実に保存する必要がある、と届いた書類に記載されているので、その辺りの管理が厄介そう。

 計算や帳簿管理が簡単だし、仕入が少ない業種は簡易課税の方がお得になりそうなので、こちらの届出も一緒に提出することにします。