【SpringMVC】設定ファイルその1

  • src/main/webapp/WEB-INF/web.xml

    Springの共通Bean設定ファイルの定義
    アプリケーションの共通設定を行う。
    root-context.xmlではファンクション層(F層:Service)、データ層(D層:Dao)の定義を行う。

    <listener>
      <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/spring/root-context.xml</param-value>
    </context-param>
    

    SpringMVCのBean設定ファイルの定義
    リクエストとサーブレットクラスのマッピングを行う。
    servlet-context.xmlではプレゼンテーション層(P層:Controller)の定義を行う。

    <servlet>
      <servlet-name>appServlet</servlet-name>
      <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
      <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
      </init-param>
      <load-on-startup>1</load-on-startup>
    </servlet>
    
    <servlet-mapping>
      <servlet-name>appServlet</servlet-name>
      <url-pattern>/</url-pattern>
    </servlet-mapping>
    

    Spring MVCではリクエストはすべて「DispatcherServlet.java」が受け取り、
    どのコントローラーを利用するかは「HandlerMapping.java」に委譲している。
    マッピングする際に設定値として、「servlet-context.xml」が指定されている。

【SpringMVC】プロジェクト作成

  • Spring MVCプロジェクト作成

    「ファイル」-「新規」-「Spring」-「Springプロジェクト」を選択します。

    テンプレートは「Spring MVC Project」を選択します。
    ここで最初はテンプレートが見つからなかったので、インストールした覚えがあります。
    (後で別マシンで検証)

    インストール場所は自動で下記に配置されました。
    「C:\Users\ユーザ名\.m2」

    パッケージ名は最低3階層まで作成しないとエラーになります。
    例:「jp.co.sample」

    作成後のパッケージ構成は下記の通り。

    src/main/java:コントローラーJava配置用
    src/main/resources:Spring用設定ファイル配置用(log4.xmlあり)
    src/test/java:JUnit実施用
    src/test/resources:JUnit実施用
    src/main/webapp/WEB-INF/views:JSP配置用
    src/main/webapp/WEB-INF/classes:プロパティファイル配置用
    
  • JREシステムライブラリー変更
    Eclipseの設定によってはJREシステムライブラリーが1.6になっているので、
    プロジェクトを右クリックして「Javaのビルド・パス」で
    JREシステムライブラリー1.6を除去して、
    JREシステムライブラリー1.7を追加します。

【SpringMVC】導入

  • Spring MVC導入

    普段は軽さ重視で「Eclipse 3.7 Indigo」を利用しているのですが、
    プラグインの追加で「Eclipse 3.7 Indigo」用の
    「Spring Tool Suite」を選択しても
    プロジェクト・エクスプローラーの表示がバグったりしたので、
    「Eclipse 4.3 Kepler」を利用することにしました。

    「Eclipse 4.3 Kepler」を立ち上げたら
    「ヘルプ」-「Eclipse マーケットプレース」を選択。
    「検索」タブで「STS」を入力し、
    「Eclipse 4.3 Kepler」用の「Spring Tool Suite」を選択してインストールします。

    「Spring UAA Integration」はチェックを外してインストールしました。
    規約に同意してEclipseを再起動すれば導入完了です。

【Java】セッションとクッキーについて

セッションとクッキーについてメモ。

  • クッキーについて

    • 保存場所

      C:\Users\ユーザ名\AppData\Local\Microsoft\Windows\Temporary Internet Files
      
    • ファイル名

      Cookie:ユーザ名@サイト名/
      
    • ブラウザメモリに保存されているクッキーの値が最優先
      ファイルに保存されたクッキーの値を変更しても
      ブラウザを閉じない限り、異なるIEで繋いでもブラウザで取得済の
      ブラウザメモリのクッキーの値を使用する。
      ブラウザを閉じて繋ぎなおすと、ファイルに保存されたクッキーの値を使用する。

      ただしクッキーの値の変更はツールを使用しないと有効にならない。
      手書きで変更しても読み取れずに2番目の動作時にクッキーの値がNULLとなる。

  • セッションについて

    • 保存場所
      ブラウザメモリまたはクッキーのファイルに保存。
      キー名はJSESSIONIDであり、クッキーの値が優先される。
    • セッションハイジャック
      セッションIDはサーバーとやり取りしている中でブラウザが自動でサーバーへ送信しているが、
      下記の記述で明示的に直接指定することも可能。

      変更前
      セッションIDはブラウザメモリまたはクッキーの値を自動でサーバーへ送信

      http://z-area.net/コンテキスト名?request1=test1&request2=test2
      

      変更後
      セッションIDはURLの値をサーバーへ送信

      http://z-area.net/コンテキスト名;jsessionid=12345?request1=test1&request2=test2
      

      12345のセッションIDが正規のユーザのものである場合、
      変更後のリクエストは正常に処理される。

      ログイン画面を表示した時点でセッションIDが採番されるので、
      セッションハイジャックを防ぐにはログイン成功後にセッションIDを採番し直す。

  • ツール
    クッキー書き換え:IECookiesView(日本語化可能)
    HTTP情報参照:ieHTTPHeaders

【Java】JavaでXML解析

JavaでXMLを解析します。

  • XMLファイル

    <?xml version="1.0" encoding="UTF-8"?>
    <sample>
    	<location>
    		<area name="area1">
    			<shop type="kanto_shopName1" required="111" />
    		</area>
    		<area name="area2">
    			<shop type="sikoku_shopName1" required="222" />
    			<shop type="sikoku_shopName2" required="333" />
    		</area>
    		<area name = "area3">
    			<shop type="kansai_shopName1" required="444" />
    			<shop type="kansai_shopName2" required="555" />
    			<shop type="kansai_shopName3" required="666" />
    		</area>
    	</location>
    </sample>
    
  • XMLファイル読取

    /*
     * メイン処理を実行します。
     * @param args 引数
     */
    public static void main(String[] args) {
    
        try {
            File fileObject = new File("src/conf/test.xml");
            DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            Document document = docBuilder.parse(fileObject);
            NodeList nodeList = document.getChildNodes();
            scanNodes(nodeList);
            System.out.println("マップサイズ:" + resultMap.size());
    
            for (String key : resultMap.keySet()) {
                List<Z_XmlAnalyzerBean> list = resultMap.get(key);
                System.out.print("全体キーは" + key + "@");
                for (int i=0; i<list.size(); i++) {
                    Z_XmlAnalyzerBean bean = list.get(i);
                    System.out.print(bean.getKey() + ":" + bean.getValue() + ", ");
                }
                System.out.println("");
            }
    
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
  • XMLファイル解析

    /*
     * ノードリストを再帰的に解析します。
     * @param nodeList ノードリスト
     */
    public static void scanNodes(NodeList nodeList) {
    
        for (int i=0; i<nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            if (Node.ELEMENT_NODE == node.getNodeType()) {
                NamedNodeMap nodeMap = node.getAttributes();
                if (nodeMap != null) {
                    for (int j=0; j<nodeMap.getLength(); j++) {
                        if (nodeMap.item(j).getNodeName().equals("name")) {
                            String mapKey = nodeMap.item(j).getNodeValue();
                            List<Z_XmlAnalyzerBean> list = new ArrayList<Z_XmlAnalyzerBean>();
                            resultMap.put(mapKey, list);
                        }
    
                        if (nodeMap.item(j).getNodeName().equals("type")) {
                            String mapValueKey = nodeMap.item(j).getNodeName();
                            String mapValueValue = nodeMap.item(j).getNodeValue();
                            System.out.println(mapValueKey + ":" + mapValueValue);
    
                            Node parentNode = node.getParentNode();
                            NamedNodeMap parentNodeMap = parentNode.getAttributes();
                            String parentZokuseiName = parentNodeMap.item(0).getNodeName();
                            String parentZokuseiValue = parentNodeMap.item(0).getNodeValue();
                            System.out.println("親:" + parentZokuseiName + "," + parentZokuseiValue);
    
                            List<Z_XmlAnalyzerBean> list = resultMap.get(parentZokuseiValue);
                            Z_XmlAnalyzerBean bean = new Z_XmlAnalyzerBean();
                            bean.setKey(mapValueKey);
                            bean.setValue(mapValueValue);
                            list.add(bean);
                        }
                    }
                }
            }
    
            if (node.hasChildNodes()) {
                scanNodes(node.getChildNodes());
            }
        }
    }
    
  • XML要素格納Bean

    public class Z_XmlAnalyzerBean {
    
        private String key = null;
        private String value = null;
    
        public String getKey() {
            return key;
        }
        public void setKey(String key) {
            this.key = key;
        }
        public String getValue() {
            return value;
        }
        public void setValue(String value) {
            this.value = value;
        }
    }
    

【Java】Javaからシェル呼び出し

JavaからWindowsのコマンドプロンプトを呼び出して、
ファイルコピーは実施したことありましたが、
現場の実作業でLinuxのシェルを呼び出して欲しいと言われました。
普通はナイナイと話していたのに実際にあるとは驚きました。

自宅で試してみました。

  • 呼ばれる側

    cat /home/ユーザ名/java/シェル名.sh
    #!/bin/sh
    echo "Hellow World !!"
    cd aaaa
    

    標準出力とエラー出力の両方があった場合を試したかったので、
    わざと存在しないディレクトリに移動しています。
    実行権限をつけておきます。

    chmod -x シェル名
    
  • 呼び出し側

    /**
     * メイン処理
     * @param args 引数
     */
    public static void main(String[] args) {
    
        ArrayList<String> cmd = new ArrayList<String>();
        cmd.add("/home/ユーザ名/java/シェル名.sh");
    
        try {
                ProcessBuilder processBuilder = new ProcessBuilder(cmd);
                processBuilder.redirectErrorStream(true);
                Process process = processBuilder.start();
                InputStream inputStream = process.getInputStream();
                printInputStream(inputStream);
                System.out.println(process.waitFor());
        } catch (Exception e) {
                e.printStackTrace();
        }
    }
    
    /**
     * 入力ストリームの内容を出力する。
     * @param inputStream 入力ストリーム
     * @throws IOException [入出力エラーが発生した場合]
     */
    public static void printInputStream(InputStream inputStream) throws IOException {
            try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));) {
                for (;;) {
                    String line = bufferedReader.readLine();
                    if (line == null) {
                        break;
                }
                    System.out.println(line);
            }
        }
    }
    
  • 実行結果

    javac クラス名.java
    java クラス名
    
    Hellow World !!
    /home/ユーザ名/java/シェル名.sh: line 3: cd: aaa: そのようなファイルやディレクトリはありません
    1
    

    ProcessBuilderクラスのredirectErrorStreamをtrueにしたので、
    標準出力とエラー出力が同時に出力されています。
    ただしこのバッファは1024バイトまでらしいです。
    ⇒echoで1050バイトほど出力するようなシェルにしてみましたが、
     出力できてしまいました。謎です。

    また、異常終了したので戻り値も「1」になっています。

  • 恥ずかしながらはまったこと
    シェル名.shをWindowsで作成していたため、改行コードがCRLFのままでした。
    改行コードがCRLFのままシェルを実行すると、下記エラーが出力されます。

    -bash: ./シェル名.sh: /bin/bash^M: bad interpreter: そのようなファイルやディレクトリはありません
    

    「^M」が出ているのがわかります。
    sedコマンドでLFに置換しました。

    sed -i 's/r//' /home/ユーザ名/java/シェル名.sh
    

【Eclipse】Eclipse再設定

Eclipseで色々いじりすぎて復元できなくなった場合の再設定メモ。

EclipseはIndigoの3.7.2を利用しています。

  • 壊れたEclipse
    【モジュール】
    EclipseのworkplaceディレクトリからSVN管理されていないプロジェクトをコピーしてどこかに保存する。

    【設定】
    「ファイル」-「エクスポート」を選択する。
    「一般」-「設定」を選択し、「次へ」ボタンを押下する。
    全てをエクスポートにチェックを入れ、「宛先設定ファイル」を指定し、「完了」ボタンを押下する。

  • 新しいEclipse
    【モジュール】
    どこかにコピーしておいたプロジェクトを新しいworkplaceに配置。
    「ファイル」-「インポート」を選択する。
    「一般」-「既存プロジェクトをワークスペースへ」を選択し、「次へ」ボタンを押下する。
    ルート・ディレクトリの選択にチェックを入れ、プロジェクトを選択し、「完了」ボタンを押下する。
    SVN管理されているプロジェクトはチェックアウトする。

    【設定】
    「ファイル」-「インポート」を選択する。
    「一般」-「設定」を選択し、「次へ」ボタンを押下する。
    ソース設定ファイルを選択し、「完了」ボタンを押下する。

【Eclipse】リモートデバッグ

ローカル環境でWebアプリケーションサーバを入れ、
その上でデバッグで動かしながら開発することはよくありますが、
他環境で動作しているWebアプリケーションに対してデバッグする方法。

前提条件として、デバッグ可能な状態でWebアプリケーションを起動していること。
Raspberry PiではTomcatが起動しているので、Tomcatを例にします。

  • デバッグモードでTomcat起動
    「$TOMCAT_HOME/bin/catalina.sh」を確認すると下記ようになっています。

    if [ "$1" = "jpda" ] ; then
      if [ -z "$JPDA_TRANSPORT" ]; then
        JPDA_TRANSPORT="dt_socket"
      fi
      if [ -z "$JPDA_ADDRESS" ]; then
        JPDA_ADDRESS="8000"
      fi
      if [ -z "$JPDA_SUSPEND" ]; then
        JPDA_SUSPEND="n"
      fi
      if [ -z "$JPDA_OPTS" ]; then
        JPDA_OPTS="-agentlib:jdwp=transport=$JPDA_TRANSPORT,address=$JPDA_ADDRESS, server=y,suspend=$JPDA_SUSPEND"
      fi
      CATALINA_OPTS="$CATALINA_OPTS $JPDA_OPTS"
      shift
    fi
    

    引数に「jpda」を与えるとデバッグ可能な状態で起動できます。
    自作シェルではそんな引数を受け取れるよう作成していないので、
    「catalina.sh」を直接呼び出します。

    /etc/init.d/tomcat.sh stop
    /tomcat/apache-tomcat-7.0.55/bin/catalina.sh jpda start
    
  • Eclipseの設定

    Eclipseから「実行」-「デバッグの構成」を選択。
    リモートJavaアプリケーションで右クリックして新規を選択。
    接続プロパティーのホスト:z-area.net または Raspberry PiのIP
    接続プロパティーのホスト:8000
    他はデフォルトのまま「適用」ボタン押下。
    「デバッグ」ボタン押下。
    

    ローカルのソースにデバッグポイントを置いて
    Webアプリケーションに接続し、デバッグポイントを通るような操作を行うと
    他環境のWebアプリケーションに対してデバッグできます。

    Eclipseから「デバッグ」ボタン押下してタイムアウトになる場合、
    サーバ側のFWでポート8000を許可する必要があります。

【Eclipse】Eclipseのwarファイル出力

プロジェクトを右クリックし、エクスポートから「Web」-「WARファイル」を選択。
Webプロジェクト名:任意名
宛先:任意の出力ファイル名

画面に従いそのままwarファイルを出力しようとすると
「モジュール名が無効です。」とエラーが出て出力できない・・・。
原因はTomcatプロジェクトになっているからで、
動的Webプロジェクトに変更すれば良いので、手順をメモしておきます。

プロジェクトを右クリックし、プロパティから「プロジェクト・ファセット」を選択。
変な英字リンクをクリックするとチェックボックスがたくさんある画面が開くので、
「Xdoclet付き動的Webプロジェクト」を選択してから下記の通り修正。

Java:1.7 バージョンを変更しました。
JavaScript:1.0 チェックボックスを付けました。
WebDoclet:チェックボックスを外しました。
動的Webモジュール:2.4

上記設定を行い、「適用」ボタン押下。
設定が完了するとプロジェクトに地球儀がついて、warファイル出力が出来るように。

ついでにjarファイルの配置メモ。

Tomcatプロジェクト:「ファイル」-「新規」-「その他」-「Java」-「Tomcatプロジェクト」

【配置jarファイル】
(1) JREシステムライブラリー7.0
(2) eclipseJP-tomcat-7.0-lib
  ⇒jsp-api.jarファイルやel-api.jarファイル
(3) eclipseJP-workspace-プロジェクト名-WEB-INF-lib
  ⇒poiやmysqlのjarファイル

【Javaのビルド・パス】
(1)、(2)、(3)
動的Webプロジェクト:「ファイル」-「新規」-「動的Webプロジェクト」

【配置jarファイル】
(1) JREシステムライブラリー7.0
(2) WebAppライブラリー
  ⇒(4)と(5)のjarファイル、warファイルをエクスポート時にも利用
(3) EARライブラリー
  ⇒配置なし
(4) eclipseJP-tomcat-7.0-lib
  ⇒jsp-api.jarファイルやel-api.jarファイル
(5) eclipseJP-workspace-プロジェクト名-WEB-INF-lib
  ⇒poiやmysqlのjarファイル

【Javaのビルド・パス】
(1)、(2)、(3)

【Java】ApachePOI

自社出勤簿はExcel管理されており、毎月月末に入力するのが面倒なので
Webから退社時刻だけ入力したら自社出勤簿に保存されるようにPOIを使ってみることに。
利用したPOIは「poi-bin-3.10.1-20140818」です。

自社出勤簿はいまだにExcel2003なのでHSSFXXXのオブジェクトを利用します。
両方を扱えるインターフェースもあるようです。

HSSFXXX:Excel2003のファイルフォーマット(.xls)を扱うオブジェクト
XSSFXXX:Excel2007のファイルフォーマット(.xlsx)を扱うオブジェクト
// Excelファイル取得
POIFSFileSystem filein = new POIFSFileSystem(new FileInputStream(String 入力ファイルのフルパス));

// ワークブック取得
HSSFWorkbook wb = new HSSFWorkbook(filein);
// シート取得
HSSFSheet sheet = wb.getSheet(String シート名);
// 行数取得
HSSFRow row = sheet.getRow(int 行数);

// 列取得(出社時刻用)
HSSFCell cellFirst = row.getCell(int 列数);
// 出社時刻設定
cellFirst.setCellValue(double 出社時刻);

// 列取得(退社時刻用)
HSSFCell cellEnd = row.getCell(int 列数);
// 退社時刻設定
cellEnd.setCellValue(double 退社時刻);

// ワークブック強制再計算
wb.setForceFormulaRecalculation(true);
// Excelファイル保存
wb.write(new FileOutputStream(String 出力ファイルのフルパス));

出社時刻、退社時刻をdouble型にしている理由ですが、
自社出勤簿の出社時刻、退社時刻はExcelのセルを確認すると
ユーザ定義で「h:mm」の時刻形式で、当日勤務時間や残業時間は自動計算されるようになっています。

Excelを使って入力した場合、下記の通りの表示となり、
画面表示:9:00 18:00
セル内値:9:00:00 18:00:00
出勤簿計算結果:正常

それと同じ画面表示、セル内値にしたいので、
Excel側にはシリアル値(1900年1月1日を1として算出した数値)を渡す必要があり、
double型をsetCellValueに渡しています。

// 時
double hh = Double.parseDouble("0900".substring(0,2))*(1.0/24.0);
// 分
double mm = Double.parseDouble("0900".substring(2,4))*(1.0/24.0/60.0);

setCellValueは受け取れる型が下記の通り複数あるので、StringとDateで試してみました。
java.lang.String
java.util.Date
java.util.Calendar
double
boolan
RichTextString

【お試し:String】

cellFirst.setCellValue("09:00");
cellEnd.setCellValue("18:00");

画面表示:09:00 18:00
セル内値:09:00 18:00
出勤簿計算結果:正常
⇒画面表示とセル内値がちょっと違う。

【お試し:Date】

SimpleDateFormat hhmmFormat = new SimpleDateFormat("h:mm");
cellFirst.setCellValue(hhmmFormat.parse("09:00"));
cellEnd.setCellValue(hhmmFormat.parse("18:00"));

画面表示:613655:00 613674:00
セル内値:1970/1/1 9:00:00 1970/1/1 18:00:00
出勤簿計算結果:正常
⇒何か色々違う