デジタルチャイルド日記

コンピュータ関連のいろいろメモを残していく子供部屋です。

SpringBatchを触ってみた。入門編

お仕事でバッチ処理を開発することになったので、その予習です。

具体的にどんな形になるのかは不明なので、今回はSpringBatchをいじってみました。

意外とやることが多いため、記事が少々長めになっています。

 

環境

Eclipse Neon (ver 4.6.0)

・Spring boot batch 2.1.3.RELEASE (2019/03/03 最新)

 (※サンプルの為、DBは未使用設定。次回予定!)

 

SpringBatchって

SpringはJavaをやってる人でしたら、1回は聞いたことのあるフレームワークだと思います。SpringBatchはそのフレームワークの仲間で、バッチ処理(一括処理など)に特化したフレームワークになります。

実際、バッチ処理の代表的なものは、夜間に大量のデータをファイルから読み込み、DBに書き込むとか、逆にDBに保存されているものを、一括で外部に連携するためにファイルに出力するとか、一括処理が多いのです。

 

SpringBootBatch

Springの中でもSpringBootと呼ばれるものがあります。これはSpringを扱いやすくする為に、いろいろなライブラリを集めて使いやすくしたものになります。

そのため、逆に不要なものが入り込んだりすることもあり、扱いには注意が必要な部分があります。(アプリがいっぱい入ったメーカー製ノートPCみたいな感じ(笑))

SpringBootBatchは、SpringBatchを扱いやすくする為、いろいろなモジュールをまとめてくれたものと考えればいいでしょう。

今回はこのSpringBootBatchを使うことにします。

 

導入

ライブラリ導入を楽にする為、Eclipseから、[新規]→[Maven プロジェクト]を選択して、Mavenプロジェクトを作成します。(Gradleなどを使って作成しても問題ありません)

今回は、archtypeは「maven-archtype-quickstart」を使っています。

生成されたプロジェクトのpom.xmlに以下のように編集します。

※今回はDBを使用しないようにする為、exclusionを追加しています。これをしておかないと、DB関連の設定がない場合、実行時にエラーとなります。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>jp.digitalchild.sample</groupId>
  <artifactId>batch</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>batch</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-batch</artifactId>
      <version>2.1.3.RELEASE</version>
      <exclusions>
        <exclusion>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-jdbc</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
  </dependencies>
</project>

 ファイルを保存すると自動的にMaven依存ライブラリにいろいろライブラリが追加されます。

これで一旦準備完了です。

 

バッチ起動のプログラムを作る

次にバッチを起動するためのクラスを作成します。といっても、よくあるmainメソッドをもったクラスになります。

EclipseからMavenプロジェクトを作成すると、App.javaというクラスができていると思うので、それを修正してみます。

package jp.digitalchild.sample.batch;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App {

	public static void main(String[] args) {
		SpringApplication.run(App.class, args);
	}
}

このクラスがバッチの起動クラスになります。ここで一度実行してみると、以下のようにコンソールに出力がされるはずです。


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.3.RELEASE)

2019-03-03 11:17:00.070  INFO 30488 --- [           main] jp.digitalchild.sample.batch.App         : Starting App on xxxxxx with PID 999999 (XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX)
2019-03-03 11:17:00.073  INFO 30488 --- [           main] jp.digitalchild.sample.batch.App         : No active profile set, falling back to default profiles: default
2019-03-03 11:17:00.809  INFO 30488 --- [           main] jp.digitalchild.sample.batch.App         : Started App in 1.203 seconds (JVM running for 1.753)

※フォルダ情報などは、変更しています。

起動が確認できたら、次へ行きましょう。

 

ジョブを定義する。

SpringBatchはJOB->STEP->TASKLET or CHUNKの順に実行時の階層を持っています。それぞれを記述する必要があります。

今回はシンプルにTASKLETと呼ばれる実行単位でクラスを作成しました。

1つのクラス内に、JOBとSTEPとTASKLETを記述しています。

package jp.digitalchild.sample.batch;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableBatchProcessing
public class SampleBatchApplication {

    @Autowired
    public JobBuilderFactory jobBuilderFactory;

    @Autowired
    public StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job sampleJob() {
        return this.jobBuilderFactory.get("sampleJob")
                    .start(sampleStep1())
                    .build();
    }

    @Bean
    public Step sampleStep1() {
        return this.stepBuilderFactory.get("sampleStep1")
                                .tasklet(sampleTasklet1())
                                .tasklet(sampleTasklet2())
                                .build();
    }

    private Tasklet sampleTasklet1() {
        System.out.println("TASK1 START");
        return null;
    }

    private Tasklet sampleTasklet2() {
        System.out.println("TASK2 START");
        return null;
    }
}

見たいただければ、予想はできると思うのですが、sampleJob→sampleStep1→sampleTasklet1→sampleTasklet2の順に実行されていきます。sampleJob内でsampleStep1を登録し、sampleStep1内でsampleTasklet1とsampleTasklet2を順番に登録することで、上記のような順で実行がされます。

 

実行してみると、以下のようなコンソールが出力されます。


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.3.RELEASE)

2019-03-03 11:38:05.806  INFO 32532 --- [           main] jp.digitalchild.sample.batch.App         : Starting App on xxxxxx with PID 999999 (XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX)
2019-03-03 11:38:05.809  INFO 32532 --- [           main] jp.digitalchild.sample.batch.App         : No active profile set, falling back to default profiles: default
TASK1 START
TASK2 START
2019-03-03 11:38:06.800  INFO 32532 --- [           main] jp.digitalchild.sample.batch.App         : Started App in 1.424 seconds (JVM running for 2.005)

無事、Tasklet1とTasklet2が順番に実行されたことがわかると思います。

 

最後に

今回は、SpringBatchを導入してみよう程度の為、SpringBatchを使うメリットがよくわかりませんでしたが、CHUNKを使ってみたり、実行制御を使ってみたりすることで、わかりやすいプログラムができるのではないかと考えています。

 

また、Chunkを使った検証が終わったら、記事にしたいと思います。

 

総称型について

先日、総称型を使ってメソッドを作ったんですが、いざ動かすと、ClassCastExceptionが!!

 

なぜだ!!!

 

ということで、総称型について、再度、勉強だ( ^ω^)

 

総称型って

要は汎用的に使える型?ということですかね。通常、javaなどの場合、型を厳密に宣言して、変数などを定義しますが、これだと、型違いの関数をいっぱい作成したりする必要があり、無駄が発生するケースがあるのでしょうね。

その代わりといってはなんですが、汎用的な型を使って、いろいろな型に1つの関数やクラスで対応しようとしているもののように見えます。

 

どうやって使うの?

シンプルな使い方としては、型の代わりにTとか書くらしいです。

たとえば、メソッドの場合、

 

public <T> T method(T param) {

  System.out.println(param);

  return param;

}

のように記述します。

<T>という記述が気になりますが、これがどうやら、型を指定する場所のようです。

 

呼び出し方は、以下のようになります。

 

t.<String>method("文字");

t.<Integer>method(1);

 

文字型と数値型を渡していますが、これで特に問題なく動きます。また、戻り値も<>内で指定した型として扱われます。

その為、以下のように書くとコンパイル時にエラーとなります。(なるんかな・・)

 

t.<String>method(1);

 

これはTがStringなのに、引数としてIntegerが渡っているためです。

実は、この<String>などは省略して、記述することも可能です。

 

t.method(1);

 

引数にTがあり、その値がIntegerなので、自動的に判断するんですかね。

 

どんな時に使うの

実際のところ業務アプリを作っている場合、そうそうお目にかかることはないと思います。たとえば、フレームワークを開発していてアプリ独自のクラスの処理をしないといけないケースなどは、これを使うと便利だと思います。

 

使う場合の問題点

もともとJavaはある程度、型に厳格な言語ですので、不用意に総称型を使うといろいろな弊害が発生します。(今回もそれに引っかかったので)

たとえば、以下のコードだと、コンパイルエラーにはなりませんが、実行時にエラーが発生します。

 

<呼び出し元>

String aaa = new String[10];

t.method2(aaa, 1, new String(""));

t.method2(aaa, 2, new Integer(1));

 

<呼び出し先> 

public <T> void method2(T tlist , int i, T param) {
    System.out.println(param);
    try {

        tlist[i] = param;
    } catch (SecurityException e) {
        e.printStackTrace();
    }
}

 

2回目のmethod2を呼び出し段階で、String[]に数字を入れようとしてるのが分かると思いますが、総称型を使うことで、想定していないところで、このようなことが起きる可能性があります。※総称型を配列に使わないというのは、割と有名な話なのかな?

 

まとめ

総称型は、うまく使えれば、かなり役にたつ素晴らしい技術だと思います。
ただし、使いどころを間違えばあらぬバグを発生させたりするものでもあります。
使いどころに気を付けてうまく使っていきましょう^^

EclipseでMaven Web Project作成

Eclipseで不通にMaven Web Projectを作成すると、あれれ?ビルドエラー・・・

 

困ったときはググるのです。そして、メモを残すのです。

ということで、少々パクリ感がありますが、うまくいかなかったときのメモです。

 

【環境】

Eclipse 4.6 x64  (Preiades All in one)

 

【手順】

  1. パッケージエクスプローラーで右クリック→新規→Maven プロジェクトを選択
  2. デフォルトワークスペースロケーションにチェックをして、次へボタンを押下
  3. アーキタイプの選択で、「maven-archetype-webapp」を選択して、次へボタンを押下

  4. グループIDとアーティファクトIDに文字を入れて(今回は、MavenWebAppSampleとしました)、完了ボタンを押下
  5. 出来上がったプロジェクト内のsrc/main/webapp/WEB-INF/index.jspコンパイルエラーになっています。なぜだww

※環境によっては正しく作られるのか?

 

【解決方法】

index.jspを開いて、エラーを確認すると、
スーパークラス "javax.servlet.http.HttpServlet" が Java ビルド・パスで見つかりませんでした」

つまり、servletのライブラリがないと・・

ならば!

pom.xmlに追加してやればいい!そもそもなんでもともと入ってないんだよ><;

 

ということで追加。

 

    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.4</version>
      <scope>provided</scope>
    </dependency>

 

今回は、とりあえず、2.4を追加しました。

これで保存したらいったんエラーはなくなるはずです。

 

さて、開発を・・・

 

あれ?Javaソースのフォルダはどこだ!

 

ない!どこを見てもない!

 

これも実は追加する必要があります。ただし、eclipse上からソースフォルダを追加しようとすると、すでにソースフォルダだといわれ追加できません。

これは、ソースフォルダは定義されているが、実体がないためです。

ならばということで実体を作ってしまいます。

 

パッケージエクスプローラーのプロジェクトから、右クリック→新規→フォルダーでsrc/main/javaフォルダを作ってしまいましょう。

 

これで一通り準備は完了です。

 

最後にうまく動くか確認です。

 

MavenWebAppSampleプロジェクトを右クリックして、実行→サーバで実行を選択すると、コンソールに文字が表示され、eclipse上でブラウザが起動して、Hello World!が表示されるはずです。

 

これで、Webアプリケーションが作成できました。

 

しかし、いきなりビルドエラーとは・・・(笑)

strutsのFormFileValidator

strutsにはファイルをアップロードするFormFileなるインターフェースが存在する。このFormFileをチェックするvalidatorがないっぽいので、それを作成した時のメモ。

 

独自のvalidatorは以前に記事を書いているのでそちらを参照するとして、具体的にソースなどを記載しておきます。

FormFileChecksクラスを作成。

まずはチェックをするロジックを作成します。作成する内容はファイルの拡張子をチェックすることにします。

注意することは、エラーが発生した場合にFormFileのメンバーをnullでクリアすることです。これを行っていないと、エラーがでたまま、再度、POSTすると異常終了するとかなんとか・・・

※FormFileのプロパティが2つ以上ある場合は、両方ともnullクリアする必要があるのかな・・・それなら、もう少し考えないといけないけど。

あとは、FormFile用のgetValueFormFileメソッドを作成していることぐらいですね。

 

package jp.digitalchild.struts.fw.validator;

import java.lang.reflect.InvocationTargetException;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.validator.Field;
import org.apache.commons.validator.GenericValidator;
import org.apache.commons.validator.Validator;
import org.apache.commons.validator.ValidatorAction;
import org.apache.commons.validator.util.ValidatorUtils;
import org.apache.struts.action.ActionMessages;
import org.apache.struts.upload.FormFile;
import org.apache.struts.validator.Resources;

/**
 * ファイルチェック用のValidator
 *
 */
public class FormFileChecks {

	/**
	 * ファイルの拡張子をチェックする。
	 *
	 * @param bean
	 * @param va
	 * @param field
	 * @param errors
	 * @param v
	 * @param request
	 * @return
	 */
	@SuppressWarnings("deprecation")
	public static boolean validateFileType(Object bean,
		ValidatorAction va,
		Field field,
		ActionMessages errors,
		Validator v,
		HttpServletRequest request) {
        
        // ファイルの取得
		FormFile value = null;
		try {
			value = getValueFormFile(bean, field.getProperty());
		} catch(Exception e){
			return false;
		}
		// 値が正しく取得できた場合は、チェック処理
		if(value != null && value.getFileSize() != 0) {

			String[] fileStrings = value.getFileName().split(Pattern.quote("."));
			String extentioin = fileStrings[fileStrings.length-1];

			// 比較対象拡張子の読み込み
			String fileTypeList = field.getVarValue("FileType");
			if(fileTypeList != null) {
				String[] candidates = fileTypeList.split(",");

				for (int cnt = 0; cnt < candidates.length; cnt++) {
					if (candidates[cnt].equalsIgnoreCase(extentioin)) {
						return true;
					}
				}
			}
			// 1つもマッチしない場合は、エラー処理
			errors.add(field.getKey(),
		       Resources.getActionMessage(request, va,  field));
			value = null;
			return false;
		} else {
			return true;
		}
	}


	/**
	 * ファイルの拡張子をチェックする。(条件付き)
	 *
	 * @param bean
	 * @param va
	 * @param field
	 * @param errors
	 * @param v
	 * @param request
	 * @return
	 */
	public static boolean validateFileTypeIf(Object bean,
		ValidatorAction va,
		Field field,
		ActionMessages errors,
		Validator v,
		HttpServletRequest request) {

		Object form = v.getParameterValue("java.lang.Object");

		FormFile value = null;
		boolean required = false;

		// 値の取得
		try {
			value = getValueFormFile(bean, field.getProperty());
		} catch(Exception e){
			return false;
		}

		// 条件文の構文解析strutsの標準ロジックと同じ)
		int i = 0;
		String fieldJoin = "AND";

		if(!GenericValidator.isBlankOrNull(field.getVarValue("fieldJoin"))) {
			fieldJoin = field.getVarValue("fieldJoin");
		}

		if(fieldJoin.equalsIgnoreCase("AND")) {
			required = true;
		}

		for(; !GenericValidator.isBlankOrNull(field.getVarValue("field[" + i + "]")); ++i) {

			String dependProp = field.getVarValue("field[" + i + "]");
			String dependTest = field.getVarValue("fieldTest[" + i + "]");
			String dependTestValue = field.getVarValue("fieldValue[" + i + "]");
			String dependIndexed = field.getVarValue("fieldIndexed[" + i + "]");

			if(dependIndexed == null) {
				dependIndexed = "false";
			}

			String dependVal = null;
				boolean thisRequired = false;

				if(field.isIndexed() && dependIndexed.equalsIgnoreCase("true")) {
					String key = field.getKey();

					if(key.indexOf("[") > -1 && key.indexOf("]") > -1) {
						String ind = key.substring(0, key.indexOf(".") + 1);

						dependProp = ind + dependProp;
					}
				}

				dependVal = ValidatorUtils.getValueAsString(form, dependProp);

				if(dependTest.equals("NULL")) {
					if(dependVal != null && dependVal.length() > 0) {
						thisRequired = false;
					} else {
						thisRequired = true;
					}
				}

				if(dependTest.equals("NOTNULL")) {
					if(dependVal != null && dependVal.length() > 0) {
						thisRequired = true;
					} else {
						thisRequired = false;
					}
				}

				if(dependTest.equals("EQUAL")) {
					thisRequired = dependTestValue.equalsIgnoreCase(dependVal);
				}

				if(fieldJoin.equalsIgnoreCase("AND")) {
					required = required && thisRequired;
				} else {
					required = required || thisRequired;
				}
		}

		// 条件が成立していれば
		if(required) {
			if(value != null && value.getFileSize() != 0) {

				String[] fileStrings = value.getFileName().split(Pattern.quote("."));
				String extentioin = fileStrings[fileStrings.length-1];

				// 比較対象拡張子の読み込み
				String fileTypeList = field.getVarValue("fileType");
				if(fileTypeList != null) {
					String[] candidates = fileTypeList.split(",");

					for (int cnt = 0; cnt < candidates.length; cnt++) {
						if (candidates[cnt].equalsIgnoreCase(extentioin)) {
							return true;
						}
					}
				}
				// 1つもマッチしない場合は、エラー処理
				errors.add(field.getKey(), Resources.getActionMessage(v, request, va, field));
				value = null;
				return false;
			} else {
				return true;
			}
		} else {
			return true;
		}
	}

	/**
	 * FormFile型でFormから値を取得
	 *
	 * @param bean
	 * @param property
	 * @return
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 * @throws NoSuchMethodException
	 */
	public static FormFile getValueFormFile(Object bean, String property) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
		Object value = null;

		try {
			value = PropertyUtils.getProperty(bean, property);
		} catch (IllegalAccessException e) {
			e.printStackTrace();
			throw e;
		} catch (InvocationTargetException e) {
			e.printStackTrace();
			throw e;
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
			throw e;
		}
		return value == null?null:(value instanceof FormFile?(FormFile)value:null);
	}
}

※条件付きのチェックについても、同様に作成しています。(validateFileTypeIf)

validator-rules.xmlへの追加

 

validator-rules.xmlに以下を追加します。内容についての説明は割愛。

<?xml version="1.0" encoding="UTF-8" ?>

<form-validation>
	<global>
	<-- ここから下追加  -->
	<validator name="fileTypeCheck" classname="jp.digitalchild.struts.fw.validator.FormFileChecks" method="validateFileType" methodparams="java.lang.Object,
	        org.apache.commons.validator.ValidatorAction,
	        org.apache.commons.validator.Field,
	        org.apache.struts.action.ActionMessages,
	        org.apache.commons.validator.Validator,
	        javax.servlet.http.HttpServletRequest" depends="" msg="errors.fileTypeError"></validator>

    <validator name="fileTypeCheckIf" classname="jp.digitalchild.struts.fw.validator.FormFileChecks" method="validateFileTypeIf" methodparams="java.lang.Object,
            org.apache.commons.validator.ValidatorAction,
            org.apache.commons.validator.Field,
            org.apache.struts.action.ActionMessages,
            org.apache.commons.validator.Validator,
            javax.servlet.http.HttpServletRequest" depends="" msg="errors.fileTypeError">%lt;/validator>
	<-- ここまで追加  -->

    </global>
</form-validation>
    

 

validation.xmlへの追加

 

validation.xmlに以下を追加します。内容についての説明は割愛。

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE form-validation PUBLIC
    "-//Apache Software Foundation//
    DTD Commons Validator Rules Configuration 1.1.3//EN"
    "http://jakarta.apache.org/commons/dtds/validator_1_1_3.dtd">

<form-validation>
	<formset>
		<form name="sampleForm">
			<field property="file" depends="fileTypeCheckIf">
        		<!-- ファイルタイプ -->
				<var>
					<var-name>fileType</var-name>
					<var-value>jpg,png,gif</var-value>
				</var>
                <!-- 条件:dependsをfileTypeCheckの場合、不要 -->
				<var>
					<var-name>field[0]</var-name>
					<var-value>checkbox1</var-value>
				</var>
				<var>
					<var-name>fieldTest[0]</var-name>
					<var-value>EQUAL</var-value>
				</var>
                <var>
                    <var-name>fieldValue[0]</var-name>
                    <var-value>1</var-value>
                </var>
			</field>
		</form>
	</formset>
</form-validation>

その他

フォームの作成は、特に記載することはありません。プロパティとして、fileをFormFile型で定義すること、getter,setterを準備すること。

あとは、struts-config.xmlで、sampleFormのFormを定義すること。

この辺りは、strutsの基本なので、ここでは割愛します。

 

以上、簡単なメモでした。

 

Seleniumでテスト

ひと昔前に、Waitrでテストの自動化をしたことあるのですが、最近、ポチポチと手でたたくことが増えており、最近の自動化事情はどうなんだろうということで、Seleniumでのテスト自動化のメモです。

 

Seleniumって。

Seleniumは、実際はSelenium WebDriverといいます。WebDriverとは、ブラウザの情報を外部から読んだり書いたりできる為のドライバーってことですかね。

でもって、それを実装したものが、Selenium WebDriverってことで。(たぶん)

Selenium - Web Browser Automation

 

★なんか、W3CでもWebDriverが策定されているとかなんとか・・・

 

環境準備

Selenium WebDriverはいろいろなプログラム言語をつかって、ブラウザにアクセスすることができますが、今回はJavaでやってみます。(Javaがメインで扱っているから)

Jenkinsなどで、ビルドをしているチームなどだと、Jenkinsビルドが終わったら、テストを起動するなんてことも可能らしいです。

ただ、気楽にテストするならRubyがおすすめですかね。

 

今回のテスト環境

IE11

 

準備するもの
  • Eclipse ... 開発用のIDEです。
  • Java1.8以降 ... 3.1あたりでjava1.8が必要になった?英語はわかりませんw
  • Selenium WebDriver ... 上記Seleniumのサイトからダウンロードしてください。
  • Internet Explorer Driver Server ... IE用のドライバサーバです。

 今回はIE用ということですが、他ブラウザの場合、下の画面の下部にドライバがあるようですので、そちらを参照ください。

 

<WebDriverとDriverServerのダウンロード>

f:id:digitalchild:20180630123833p:plain

 

 ダウンロードされた「IEDriverServer_x64_3.13.0.zip」「selenium-java-3.13.0.zip」をそれぞれ解凍して、eclipseに取り込みます。

f:id:digitalchild:20180630124818p:plain

IEDriverServer_x64_3.13.0.zip

 解凍後、eclipseのプロジェクト内に、IEDriverフォルダ(名前は適当です)を作成して、IEDriverServer.exeをコピーします。

selenium-java-3.13.0.zip

解凍後、eclipseのビルドパスから、すべてのjarファイルを追加します。

★libsフォルダ配下もすべて。

 

IEの事前設定

IEのインターネットオプションから、すべてのゾーンに対して、「保護モード有効にする」にチェックを入れます。さらに詳細設定の中の「拡張保護モードを有効にする」のチェックを外します。 

f:id:digitalchild:20180630134424p:plain

IEを再起動したら、準備完了です。

 

プログラム作成

実際のテストをするプログラムを作成します。

今回は、サンプルなので、mainを使ってますが、実際のテストの場合、JUnitからを使って同様のロジックを作成することになると思います。

 

<ソース>

package jp.digitalchild.sample.serenium;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;

public class WebDriverSample {

  public static void main(String[] args) {

    WebDriver driver;

    // IEDriverServerの起動
    System.setProperty("webdriver.ie.driver", ".\\IEDriver\\IEDriverServer.exe");
    driver = new InternetExplorerDriver();

    // Googleに遷移する。
    driver.get("http://www.google.co.jp");
    
    // タイトルをコンソールに出してみる。
    System.out.println(driver.getTitle());

    // 画面を終了
    //driver.quit();
  }
}

 あとはeclipseから普通に動かしてみると、以下のようなコンソールが出力され、IEが起動して、googleに遷移しているはずです。

<コンソール>

Started InternetExplorerDriver server (64-bit)
3.13.0.0
Listening on port 24287
Only local connections are allowed
情報: Detected dialect: W3C [土 6 30 13:53:26 JST 2018]
Google

以上、とりあえずのメモでした。

 

※補足

上記、プログラムを繰り返し呼び出すことで、サイトへの攻撃のようなことも可能になるので、注意しましょう。

IEの互換表示

IEの互換表示が勝手にIE5とかになって、ボタンが押せなくなるから何とかしてほしいとの要望をいただいたので、そのメモです。

 

IEには互換表示用のモードがあり、開発者としては結構便利なんですけど、エンドユーザさんが使う場合、このモードが想定外になってしまうと、正常にJavaScriptなどが動かくなったりするので結構面倒です。

また、このモード表示はIEの設定などに左右される為、なにも対処していなければ、各クライアントごとに違うモードで起動してしまいます。

 

詳しくはマイクロソフトのページで公開されているのでそちらを参照ください。

 

blogs.msdn.microsoft.com

これをどの環境でも同一のモードで起動するためには、HTML内に互換モードを決定するための<meta>タグを組み込んだり、headerに属性を送り込んだりすることで解決するらしいです。

 

<対処法>

  1. プログラム上でレスポンスヘッダーにX-UA-Compatibleを追加する。
  2. X-UA-CompatibleをHTML内に指定する。
  3. WebサーバでレスポンスヘッダーにX-UA-Compatibleを追加する。

 上から順に影響範囲は小さいのかな。

 プログラム上でレスポンスヘッダーにX-UA-Compatibleを追加する

 サーバ側のプログラムでヘッダーを設定できるなら、以下のようにする方法もありかなと思っています。

※以下は、JavaServletの場合

response.setHeader("X-UA-Compatible", "IE=11");    

この場合は、Servlet単位(つまり、アクション単位)で設定ができるので、もっとも影響範囲は小さいですが、まぁ使うことはないでしょうね。 

 X-UA-CompatibleをHTML内に指定する

以下の<meta>をHTMLのheader内に記述するようにします。いろいろサイトをみていると<meta>を記述する場所が重要なようで、jsファイルなどの読み込み前や、文字コードの指定後に書くといいみたいです。

<meta http-equiv="x-ua-compatible" content="IE=Edge" >
<meta http-equiv="x-ua-compatible" content="IE=EmulateIE11" >    

この方法は、HTML内に記述するので、該当画面を表示する場合はすべてに影響があるますね。

※このEmulateIE11って必要なんだろうか・・・

WebサーバでレスポンスヘッダーにX-UA-Compatibleを追加する。

この方法の場合、そのWebサーバを経由している場合、すべて影響があるかと思われます。

以下はApacheの場合です。

LoadModule headers_module modules/mod_headers.so
<IfModule headers_module>
   Header set X-UA-Compatible: IE=11
</IfModule>

モジュールがない場合は、別途準備しないといけないとかなんとか・・・

 

とりあえず、今回は、2番目の方法で解決しようと思ってます。

 

以上、メモでした。

 

javascriptによる入力文字数の表示

仕事が忙しくてすっかりおさぼりしてましたが、久々のメモです。

 

webで入力チェックする前に画面に今入力している文字数を出してほしいという依頼があったので、そのメモです。

※実際には入力チェックはバイトで行っているので、入力バイト数をだすことにしています。

 

入力中に入力文字を表示するのはいろいろ方法があると思います。

onkeypressを使う方法とか、onpasteを使うとかありますけど、今回はフォーカスが当たっている間だけ、その内容をチェックする方式にしました。

 

では、実際のソースをメモっておきましょう

JavaScript側>

var timer;

/**
 * タイマー処理の開始
 */
function start(obj,count) {
	timer = setInterval(countText.bind(this, obj, count), 200);
}

/**
 * タイマー処理の終了
 */
function stop(obj,count){
	clearInterval(timer); // 繰り返し処理を中止する
}

/**
 * テキスト文字列をカウント
 * 全角を2バイト,半角を1バイト、改行は2バイトとする。
 * ※今回、お仕事で受けた仕様なので、いろいろ問題はありますが、よしとします。
 */
function countText(obj,count) {
	// バイト数をカウント
	inputlength = 0;
	str = escape(obj.value.replace(/\r/g, '').replace(/\n/g, '\r\n'));
	for (i=0;i<str.length;i++,inputlength++) {
		if (str.charAt(i) == "%") {
			if (str.charAt(++i) == "u") {
				i += 3;
				inputlength++;
			}
			i++;
		}
	}

	// inputlength = encodeURIComponent(obj.value).replace(/%[A-F\d]{2}/g, 'U').replace(/\r/g, '').replace(/\n/g, '\r\n').length;
	document.getElementById(count).textContent=inputlength;
}

<HTML側>

<div id="textCount">0</div>
<html:textarea name="helloworld" property="helloWorldTextarea" disabled="disabled" onfocus="javascript:start(this,'textCount')" onblur="javascript:stop(this,'textCount')">
</html:textarea>
<% if(!enabled) { %>
	<html:hidden name="helloworld" property="helloWorldTextarea"></html:hidden>
<% } %>
<script>
countText(document.getElementsByName('helloWorldTextarea')[0],'textCount');
</script>

HTMLがStrutsJSPになっていますが、肝はonfocusイベントとonblurイベント、とは、下についてscriptタグですかね。

 

 普通に、encodeURIComponetを使う方法も考えたのですが、UTF-8の場合、全角が3バイトになったりするので、今回は1文字づつチェックすることに。

<2018/16/18 補足>

ShiftJISでも3バイトになってしまいました。内部的にUTF-16で持ってるからですかね。

 

タイマー形式なので、ペーストした場合でも、特に問題なく反応してくれます。

 

ただ、気になったのが少々重くなることが懸念されるぐらいですかね。

 

今回は管理画面用ということでしたので、ブラウザも限定されるし、環境もそこまで厳しいものではないということだったので、これで満足!