GroupSession 開発環境の構築を Visual Studio Code でやってみる・最終回(前編)の続きです。

今回は、前回までで作成した開発環境と実行環境を使って、実際に Hello World! チュートリアルを実装していきます。

 

 

GroupSession: HelloWorld! チュートリアルの実装

ようやく待ちに待った実装です。

GroupSession 本家サイトのチュートリアル は、文書が古いまま更新されていないのが現状です。そのままではビルドそのものにも失敗すると思われるので、これが動作するように現状の GroupSession の環境に合わせて読み替えていきたいと思います。

 

プラグインID

一応、プラグインIDは本家のチュートリアル通り newplugin としておきます。

このプラグインIDあちこちで意味を持ってくるので、注意しておいてください。

 

ディレクトリ構成

プラグインのディレクトリの構成は以下のようになっています。

gsession/ -- プロジェクトルート
|     :
|-- newplugin/   -- (1)
|   `-- images/
|-- WEB-INF/
|   `-- plugin/
|       |    :
|       `-- newplugin/ --(2)
|           |-- jsp/
|           `-- src/
:

このツリーで、src/ のように文字末尾に / が付いているのはディレクトリ(フォルダ)を表します。いわゆるディレクトリの区切り文字ですね。

通常 Windows であればディレクトリの区切り文字としては / ではなくて \ を使用するはずですが、この記事では以降、このディレクトリを表す記号として / を使用します。

各ディレクトリの用途は以下の通りです。

  • (1)… アイコン画像やJavaScriptファイルなどの Webリソースファイル
  • (2)… ソースファイルなど

全ての GroupSession のソースを読み込んだわけではないので確実なことは言えませんが、Webリソースファイル、Java ソースファイルのディレクトリ名は、プラグインIDである必要があります。

上記のツリーで言えば、

  • gsession/newplugin
  • gsession/WEB-INF/plugin/newplugin

になります。

また、サーブレットの設定によって異なりますが、WEB-INF/ 以下のファイルやディレクトリには、Web経由で参照は不可になっており、Web リソースについては、クライアントから参照可能になっていることに注意してください。

ちなみに Java のソースファイルは、gsession/WEB-INF/plugin/newplugin/src 以下に作成することになります。

 

アイコンファイルの準備

必要に応じてアイコンファイルを準備します。

チュートリアルでは、アイコンファイルは

  • 25x25 ピクセルのGIF画像

を用意するとありますが、直近のバージョン(5.x 系)ではメニュー用のアイコンは、50x50ピクセルのPNG画像を使用するようになっているみたいですね。

一応、アイコンファイルが用意されていない場合でも動作に問題はありませんが、自作プラグインとして見た目も気にする方は、

  • 50x50 ピクセルの PNG画像

を用意しましょう。

gsession/
|-- common
|   `-- images
|       `-- pluginimg
|           `-- original
|               `-- menu_icon_newplugin_50.png -- (1)
`-- newplugin
    `-- images
        `-- menu_icon_single.gif  -- (2)

アイコンファイルを配備する際は、50x50ピクセルの PNG画像については、上の図の (1) に、menu_icon_{pluginId}_50.png という命名規則でファイル名をつけます。

今回は、プラグインID が newplugin なので、menu_icon_newplugin_50.png となります。

25x25の GIF画像も用意しておく場合には、上の図の (2) にファイルを配備します。ファイル名は、チュートリアルに倣って、menu_icon_single.gif としておきます。

 

設定ファイルの作成

plugin.xml の作成

プラグイン定義ファイル、plugin.xml を作成します。

ファイルの作成場所は、以下の (1) になります。この時、

  • WEB-INF/plugin/newplugin

というディレクトリは存在していないと思いますので、作成しておきます。

gsession/
|     :
|-- WEB-INF/
|   `-- plugin/
|       |    :
|       `-- newplugin/
|           `-- plugin.xml -- (1)
:

以下は、シンプルなプラグイン定義ファイルの内容になります1

タグ <topmenu-info>...</topmenu-info> で、トップメニューにアイコンを表示するようにし、アイコンをクリックした際のリンク先も定義しています。

<?xml version="1.0" encoding="UTF-8" ?>
<plugin>
  <!-- プラグインID -->
  <id>newplugin</id>

  <!-- 名称  -->
  <name>ニュープラグイン</name>
  <name-eng>New Plugin</name-eng>

  <!-- 説明  -->
  <description>新しいプラグイン</description>

  <!-- トップメニュー トップメニューの情報を記述する 記述しない場合はメニューに表示しない  -->
  <topmenu-info>
    <!-- メニューへの表示/非表示 -->
    <view>true</view>
    <!-- メニューアイコンクリック時に遷移するURL -->
    <url>../newplugin/helloworld.do</url>
  </topmenu-info>

  <!-- ヘルプ情報を設定する。 -->
  <help-info>
    <!-- メニューの表示/非表示 -->
    <view>false</view>
  </help-info>

  <!-- ログ出力を設定する。 -->
  <log-info>
    <!-- ログ出力 対象/非対象 -->
    <output>true</output>
  </log-info>
</plugin>

Visual Studio Code で編集する際の文字コード

この記事では、編集の際の文字コードを UTF-8 にしています。

Windows で Visuas Studio Code を使用すると、文字コードが Shift-JIS になりがちですが、UTF-8 にしておくことをおすすめします。

 

struts-config.xml の作成

次に Struts の定義ファイル struts-config.xml を作成します。作成場所は以下の (1) になります。

gsession/
|     :
|-- WEB-INF/
|   `-- plugin/
|       |    :
|       `-- newplugin/
|           |-- plugin.xml
|           `-- struts_config.xml -- (1)
:

この定義ファイルでは、いわゆるコントローラーの役割をする アクションクラスのマッピングに関する定義と、その際に使用されるフォームクラス(アクションフォームと呼ばれます)に関する定義があります。

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

<!DOCTYPE struts-config PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 1.3//EN"
        "http://struts.apache.org/dtds/struts-config_1_3.dtd">

<struts-config>

<!-- ========== アクションフォームの定義 =================================== -->
    <form-beans>
        <form-bean name="helloworldForm" type="jp.groupsession.v2.newplugin.HelloWorldForm" />
    </form-beans>

<!-- ================================================ Global Exception -->
    <global-exceptions></global-exceptions>

<!-- ========== Global Forward Definitions ============================== -->
  <global-forwards type="org.apache.struts.action.ActionForward">
  </global-forwards>

<!-- ========== アクションマッピングの定義 ============================== -->
    <action-mappings>
       <!-- メイン -->
       <action
            name="helloworldForm"
            path="/newplugin/helloworld"
            scope="request"
            input="/WEB-INF/plugin/newplugin/jsp/helloworld.jsp"
            validate="false"
            type="jp.groupsession.v2.newplugin.HelloWorldAction">
       </action>
    </action-mappings>
</struts-config>

上記の設定に関する内容の詳細は、Struts 1.3.10 に関するドキュメントを・・と言いたいところですが、Struts1 はすでに EOL を迎えており2、現在は Struts2 がメインになっているのですが、その構成は大幅に変わっているみたいです3

なので、簡単に上記の設定を説明しておきます。

アクションフォームとして、helloworldForm という名前で jp.groupsession.v2.newplugin.HelloWorldForm という Java クラスを割り当てていることを定義していることがわかります。

また、アクションマッピングの定義が一つあり、こちらはクライアントが、

  • リクエストパスとして、/newplugin/helloworld.do4

でアクセスした場合の振る舞いが定義されています。

  • アクションクラスとして、jp.groupsession.v2.newplugin.HelloWorldAction を使用し
  • アクションフォームとして、helloworldForm= jp.groupsession.v2.newplugin.HelloWorldForm)を使用し
  • フォームのバリデーションは false、つまりフォームの値の検証は行わない

というように定義されています。

また、パラメータ類は既定として

  • request スコープ

で渡され、アクションが実行された場合の遷移先として、

  • /WEB-INF/plugin/newplugin/jsp/helloworld.jsp

を指定しています。

これらの説明は極めてざっくりです。申し訳ありません。

 

Java ソースの作成

Visual Studio Code にソースパスを追加する

gsession/WEB-INF/plugin/newpluginsrc というディレクトリを追加し、このディレクトリを Javaソースパスに追加します。

ソースパスの追加は、上記の src を右クリックして、Add Folder to Java Source Path をクリックします。

gsession/
|     :
|-- WEB-INF/
|   `-- plugin/
|       |    :
|       `-- newplugin/
|           |-- src/    -- (1)
|           |-- plugin.xml
|           `-- struts_config.xml
:

 

HelloWorldForm.java を作成する

gsession/WEB-INF/plugin/newplugin/src の下に次のようにディレクトリを作成します。

gsession/WEB-INF/plugin/newplugin/src/
`-- jp/
    `-- groupsession/
        `-- v2/
            `-- newplugin/

この場合は、ソースパスよりも下のディレクトリなので、ディレクトリと呼ぶより Javaパッケージ や、単に パッケージ とも呼ばれますね。

続いて、src/jp/groupsession/v2/newplugin に、HelloWorldForm.java を作成します。

GroupSession でのお約束は、全てのフォームクラスは、AbstractGsForm を継承する必要がある ということです。

内容は以下のとおりです。

package jp.groupsession.v2.newplugin;

import jp.groupsession.v2.struts.AbstractGsForm;

public class HelloWorldForm extends AbstractGsForm {

}

見ておわかりのように、今回このアクションフォームは中身がスカスカです。フィールド定義は一つもありません。

 

HelloWorldAction.java を作成する

次に アクションクラスである、HelloWorldAction.java を作成しますが、その前に

  • AbstractNewpluginAction.java

という抽象クラスを定義します。

場所はフォームクラスと同じ、src/jp/groupsession/v2/newplugin になります。

gsession/
|     :
|-- WEB-INF/
|   `-- plugin/
|       |    :
|       `-- newplugin/
|           |-- src/
|           |   `-- jp/groupsession/v2/newplugin/
|           |       |-- AbstractNewpluginAction.java    -- (作成)
|           |       `-- HelloWorldForm.java
|           |-- plugin.xml
|           `-- struts_config.xml
:

こちらは、本家のチュートリアルでは存在しないファイルですが、最近の GroupSession のコーディング習慣に合わせて、プラグイン共通のアクションクラス として作成しています。

GroupSession では、アクションクラスは必ず AbstractGsAction を継承する必要があるので、そちらの抽象クラスを継承します。

package jp.groupsession.v2.newplugin;

import jp.groupsession.v2.struts.AbstractGsAction;

/**
 * <br>[機  能] ニュープラグインで共通的に使用するアクションクラスです
 * <br>[解  説]
 * <br>[備  考]
 *
 * @author ZRD
 */
public abstract class AbstractNewpluginAction extends AbstractGsAction {

    /**プラグインIDを取得します
     * @return String プラグインID
     * @see jp.groupsession.v2.struts.AbstractGsAction#getPluginId()
     */
    @Override
    public String getPluginId() {
        return "newplugin";
    }
}

このプラグイン共通のクラスは、単純に親クラスの getPluginId() メソッドをオーバーライドしているだけのクラスです。

GroupSession の各プラグインは、このようにして getPluginId() メソッドで、プラグインIDを返すように作られています。

次に、HelloWorldAction.java を定義します。こちらは、先程作成したプラグイン共通のアクションクラス AbstractNewpluginAction.java を継承します。

package jp.groupsession.v2.newplugin;

import java.sql.Connection;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

public class HelloWorldAction extends AbstractNewpluginAction {

    @Override
    public ActionForward executeAction(
            ActionMapping mapping,
            ActionForm form,
            HttpServletRequest req,
            HttpServletResponse res,
            Connection con
    ) throws Exception {
        return mapping.getInputForward();
    }
}

このクラスでは、AbstractGsAction で抽象メソッドとして定義されている executeAction メソッドを実装する必要があります。

このメソッドは、ActionForward と呼ばれる、遷移先に関する情報を持ったオブジェクトを返す必要があります。

この例では、mapping.getInputForward() となっており、Struts 設定ファイルの input= で指定した JSP ファイルにフォワードするようになっています。

 

ビューの作成

helloworld.jsp を作成する

gsession/WEB-INF/plugin/newplugin/ の下に jsp ディレクトリを作成し、そこに helloworld.jsp を作成します。

gsession/
|     :
|-- WEB-INF/
|   `-- plugin/
|       |    :
|       `-- newplugin/
|           |-- jsp/
|           |   `-- helloworld.jsp -- (作成)
|           |-- src/
|           |   `-- jp/groupsession/v2/newplugin/
|           |       |-- AbstractNewpluginAction.java
|           |       `-- HelloWorldForm.java
|           |-- plugin.xml
|           `-- struts_config.xml
:

この JSP ファイルは、<html:form ... > タグと <%@ include file="/WEB-INF/plugin/common/jsp/header001.jsp" %><%@ include file="/WEB-INF/plugin/common/jsp/footer001.jsp" %> の間以外はほとんど雛形どおりです。

本家チュートリアルと比べてみると、冒頭の taglib が微妙に異なる程度です。

<%@ page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%>
<%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %>
<%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %>
<%@ taglib uri="http://struts.apache.org/tags-logic" prefix="logic" %>
<%@ taglib uri="http://struts.apache.org/tags-nested" prefix="nested" %>
<%@ taglib uri="/WEB-INF/ctag-css.tld" prefix="theme" %>
<%@ taglib uri="/WEB-INF/ctag-message.tld" prefix="gsmsg" %>
<%@ taglib uri="/WEB-INF/ctag-jsmsg.tld" prefix="gsjsmsg" %>
<%@ page import="jp.groupsession.v2.cmn.GSConst" %>
<!DOCTYPE html>

<html:html>
<head>
<title>[GroupSession] HelloWorld</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel=stylesheet href='../common/css/default.css' type='text/css'>
</head>

<body class="body_03">
<html:form action="/newplugin/helloworld">

<%@ include file="/WEB-INF/plugin/common/jsp/header001.jsp" %>

<table width="100%">
<tr>
<td width="100%" align="center">
<p>Hello World!</p>
</td>
</tr>
</table>

<%@ include file="/WEB-INF/plugin/common/jsp/footer001.jsp" %>

</html:form>
</body>
</html:html>

 

build.xml を作成する

gsession/WEB-INF/plugin/newplugin/build.xml を作成します。

gsession/
|     :
|-- WEB-INF/
|   `-- plugin/
|       |    :
|       `-- newplugin/
|           |-- jsp/
|           |   `-- helloworld.jsp
|           |-- src/
|           |   `-- jp/groupsession/v2/newplugin/
|           |       |-- AbstractNewpluginAction.java
|           |       `-- HelloWorldForm.java
|           |-- build.xml -- (作成)
|           |-- plugin.xml
|           `-- struts_config.xml
:

こちらは、本家のチュートリアルどおりにすると、ほぼ間違いなくビルドに失敗 します。

以下のようにして作成すると、成功する(はず)です。

修正箇所は、一部のディレクトリパスの追加・削除や、コンパイルオプションの変更です。

<?xml version="1.0" encoding="UTF-8"?>
<!--
====================================================
GroupSession newplugin
共通クラスのビルドが完了していること
create JTS
====================================================
-->
<project name="GroupSession newplugin" default="build" basedir=".">
<!--
====================================================
Properties: 変数の定義
====================================================
-->
    <!-- ディレクトリ名の定義 -->
    <property name="SRC.DIR"        value="./src" />
    <property name="PLGIN_CLS.DIR"  value="./classes" />

    <!-- Compile Options -->
    <property name="BLD.TARGET"     value="11" />
    <property name="DEBUG"          value="yes" />
    <property name="DEPRECATION"    value="true" />
    <property name="SRC.ENC"        value="UTF-8" />
    <property name="PROP.ENC"       value="Windows-31J" />
    <property name="ANT.RUNTIME"    value="no" />

    <!-- ClassPath -->
    <path id="app_classpath">

        <!-- OSのクラスパス -->
        <pathelement path="${classpath}" />

        <!-- LIBディレクトリ -->
        <pathelement location="../../../WEB-INF/convert/lib/jsp-api.jar"/>
        <pathelement location="../../../WEB-INF/convert/lib/servlet-api.jar"/>
        <pathelement location="../../../WEB-INF/build/lib/websocket-api.jar"/>

        <!-- LIBディレクトリ -->
        <!-- アプリケーション共通lib -->
        <!-- <fileset dir="../../../../../lib">
            <include name="**/*.jar" />
            <include name="**/*.zip" />
        </fileset> -->
        <fileset dir="../../../WEB-INF/lib">
            <include name="**/*.jar" />
            <include name="**/*.zip" />
        </fileset>
        <!-- アプリケーション 共通クラス -->
        <pathelement location="../../../WEB-INF/classes" />
    </path>
    <!--
====================================================
INIT: 初期化
====================================================
-->
    <target name="init"
        description="init"
    >
        <tstamp>
            <format property="YMDHMS"
                pattern="yyyy.MM.dd hh:mm:ss"
            />
        </tstamp>
        <echo message="Project Name = ${ant.project.name}" />
        <echo message="Project Root = ${basedir}" />
        <echo message="JDK Version = ${ant.java.version}" />
        <echo message="Building Time = ${YMDHMS}" />
        <echo message="" />
    </target>
    <!--
====================================================
BUILD: Classファイルの作成(コンパイル)
====================================================
-->
    <target name="build">
        <mkdir dir="${PLGIN_CLS.DIR}" />
        <javac
            classpathref="app_classpath"
            destdir="${PLGIN_CLS.DIR}"
            source="${BLD.TARGET}"
            target="${BLD.TARGET}"
            debug="${DEBUG}"
            deprecation="${DEPRECATION}"
            encoding="${SRC.ENC}"
            includeAntRuntime = "${ANT.RUNTIME}">
            <src path="${SRC.DIR}" />
            <compilerarg value="-Xlint:unchecked" />
            <include name="**/*.java" />
        </javac>
    </target>
    <!--
====================================================
CLEAN:作成したClassファイルをディレクトリごと削除
====================================================
-->
    <target name="clean">
        <antcall target="init" />
        <delete dir="${PLGIN_CLS.DIR}" />
    </target>
</project>

 

プロジェクトをビルドする

ここまでで、ファイルは以下の通りになっているはずです。

作成したファイル群

作成したファイル群

Visual Studio Code の下部パネルの TERMINAL タブを開いてください。ここまで記事通りにやってきていたとすると、次のような表示になるでしょう。

D:\work\java\gsession>

次のようにして、プラグインのディレクトリに移動します。

cd WEB-INF\plugin\newplugin

DIR コマンドでディレクトリの内容を表示させると、次のようになっているでしょう。

dir

    ディレクトリ: D:\work\java\gsession\WEB-INF\plugin\newplugin

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        2022/12/27     10:42                jsp
d-----        2022/12/27     10:39                src
-a----        2022/12/27     10:43           3509 build.xml
-a----        2022/12/27     10:33            912 plugin.xml
-a----        2022/12/27     10:34           1209 struts_config.xml

PS D:\work\java\gsession\WEB-INF\plugin\newplugin>

ここで、次のようにして ant build を実行します。次のように、

BUILD SUCCESSFUL

と表示されれば、ビルド成功です。

ant build
Buildfile: D:\work\java\gsession\WEB-INF\plugin\newplugin\build.xml

build:
    [mkdir] Created dir: D:\work\java\gsession\WEB-INF\plugin\newplugin\classes
    [javac] Compiling 3 source files to D:\work\java\gsession\WEB-INF\plugin\newplugin\classes

BUILD SUCCESSFUL
Total time: 1 second
PS D:\work\java\gsession\WEB-INF\plugin\newplugin>

 

動作確認

サーバーと同期させる

ここで、Tomcat サーバーを起動させてみて動作を確認してみたいのですが、おそらくサーバーのステーテスは、

  • Publish Required

のようになっていると思います。

これは、現在のソースとサーバの公開フォルダの内容が一致していない場合に表示されます。要するに、先程ビルドした内容をサーバーの公開フォルダと同期させる必要があるということです。

この時は、サーバーを右クリックして Publish Server (Incremental) として、ビルドした内容をサーバーに公開します。

増分を同期させる

増分を同期させる

ステータスが Synchronized となれば、同期は完了です。

 

動作を確認

サーバーを起動させ、動作を確認しましょう。

サーバーを右クリックして Start Server でサーバーを起動します。

起動処理が完了するのを待った後、サーバーを右クリックで Server Actions ... から、Show in Browser で、http://localhost:8080/gsession をクリックすると、GroupSession のログイン画面が現れるので、

  • ユーザー名: admin
  • パスワード: admin

でログインしましょう。

すると、管理者設定画面が現れるので、まずは プラグインマネージャー をクリックしてみます。

GroupSession: 管理者設定画面

GroupSession: 管理者設定画面

プラグインマネージャーは、プラグインの使用・未使用や、アクセス権を変更するための画面ですが、下の方にスクロールしていくと、ニュープラグイン というプラグインが追加されているのがわかると思います。

追加されたニュープラグイン(アイコンを追加していない状態です)

追加されたニュープラグイン(アイコンを追加していない状態です)

 

GroupSession のユーザーを追加

一旦、[戻る] をクリックして、管理者設定画面に戻ります。

ここで、適当なユーザーを追加したいのですが、GroupSession では初めてのユーザーを追加する前にグループを登録しておく必要 があります。なので、グループマネージャー をクリックして、適当なグループを一つ作成しましょう。

ここでは、

  • グループID: TEST
  • グループ名: テストグループ

として、グループを作成したことにします。

次に、ユーザーマネージャー を使って、適当なユーザーを追加します。

ここでは、

  • ユーザID: testuser
  • パスワード: (適当に設定)
  • 氏名: テスト 太郎
  • 氏名(カナ): テスト タロウ
  • 所属グループ: テストグループ

としてユーザーを作成します。

 

作成したプラグインを表示

ユーザーを作成し終わったら、一旦 GS管理者からログアウトして、テスト用ユーザーでログインしましょう。

トップメニュー(表示されない場合は、画面の上端でマウスをホバーすると表示されます)のニュープラグインをクリックします。

ニュープラグインをクリック

ニュープラグインをクリック

次のように表示されれば成功です。

GroupSession: Hello World!

GroupSession: Hello World!

 

まとめ — カンニングのススメ —

えらく長くかかった当シリーズも、一旦ここで終了となります。

GroupSession を自分なりにビルドするに当たって、やはり改めて大事なことがあることを確信しました。

それは、

わからないときはカンニングする

ということです。

特に、Apache Ant でビルドに失敗した際、元のソースに入っている build.xml をカンニングして問題点をピックアップしました。

こういったプログラミングに関することは、資格試験や、学校のテストなどではない限り、カンニングしまくることはとても大事だと思います。

GroupSession はそういう意味で、とてもためになる教材であるな、というのが今回の実感ですね。

さすが、今更 Struts と Ant を使って Web アプリケーションを組んだり、自力で SQLをゴリゴリと書いて、DBアクセッサなどを書いたりすることはないかもしれませんが、より深く知るためにはそういったことを知っておくこともとても重要だと感じさせられます5

それが無償で、結構な規模の Java Web アプリケーションのソースを見れるなんて、本当にありがたい限りです。

日本トータルシステム株式会社さんには感謝の気持ちでいっぱいで、言葉にするのも難しいくらいですね。

もしこの記事を読んで、GroupSession のプラグインをビルドできた方がおられれば幸いですし、さらにより深い部分を知りたければ、目の前にある GroupSession のソースを読み解いていけば、どのような仕組みで動いているのか、ということを学びを得ることができると思います。

それでは、長くなりましたがありがとうございました。

 

 


  1. 後述しますが、他のプラグインの例を参考にしています。 

  2. end-of-life の略。既に、製品のライフサイクルが終了しているということ。更新や、脆弱性に対する対策もなされない状態。 

  3. Struts1 は、EOL後にもいくつかの脆弱性が発見されているのですが、GroupSession は独自に脆弱性対策を施しています。だからといって安全、というではありませんが、インターネットに表向きに公開する場合などは、セキュリティに関する情報に注意する必要があります。 

  4. 実際のパスは http://localhost:8080/gsession/newplugin/helloworld.do になります。 

  5. なぜ、プリペアドステートメント(Prepared Statement)を使用しないといけないのか、とかですね。 


 

 

zaturendo

中小企業社内SE。

0件のコメント

コメントを残す

アバタープレースホルダー

メールアドレスが公開されることはありません。 が付いている欄は必須項目です