2019年9月16日 (月)

Apache Wicketを使ってみる[4] -認証/認可編-

0. Wicketで認証/認可を扱うには

wicket-auth-roles というdependencyが必要。

<dependency>
  <groupId>org.apache.wicket</groupId>
  <artifactId>wicket-auth-roles</artifactId>
  <version>8.5.0</version>
</dependency>

1. Wicketでの認証

通常の WebApplication に認証情報を処理できるような拡張が施された AuthenticatedWebApplication と、認証処理と認証情報のセッションストアへの格納を行う AuthenticatedWebSession で構成される。

二つとも abstract なので、使うには継承したうえで実装しなければならない処理がある。

AuthenticatedWebApplication AuthenticatedWebSession
  • protected Class<? extends WebPage> getSignInPageClass()
    サインインページを構成するクラスを返す
  • protected boolean authenticate(String username, String password)
    ユーザー名とパスワードから認証を行う
  • protected Class<? extends AbstractAuthenticatedWebSession> getWebSessionClass()
    アプリケーションで使うセッションストアのクラスを返す

上記のユーザーガイドからの抜粋をKotlinで書いてみると、こんな感じになる。

  • セッションストアクラス
class BasicAuthenticationSession(request: Request) : AuthenticatedWebSession(request) {
  override fun authenticate(username: String, password: String) : Boolean {
    // ここで認証処理を行う
    // 通常であれば、DBアクセスしてユーザーの有無やパスワード比較とかをする、はず
    return username == password && username == "wicketer"
  }

  override fun getRoles() : Roles {
    // ロールベースのアクセス制御用に、ログイン中ユーザーのロールを返す
    // 通常であれば、セッションストアのユーザー情報からロールを引いたりする、はず
    return Roles()
  }
}
  • アプリケーションクラス
class WicketApplication() : AuthenticatedWebApplication() {
  override fun getHomePage() : Class<out WebPage> {
    // いわゆる「スタートページ」を構成するクラスを返す
    return HomePage::class.java
  }
  override fun getWebSessionClass() : Class<out AbstractAuthenticatedWebSession> {
    // アプリケーションでつかうセッションストアを返す
    return BasicAuthenticationSession::class.java
  }
  override fun getSignInPageClass() : Class<out WebPage> {
    // いわゆる「ログインページ」を構成するクラスを返す
    return SignInPage::class.java
  }
}
  • サインインページ
class SignInPage() : WebPage() {
  private var username: String = ""
  private var password: String = ""

  override fun onInitialize() {
    super.onInitialize()

    // ログインフォームを構成するStatelessForm
    val form: StatelessForm<Unit> = object : StatelessForm<Unit>("form") {
      override fun onSubmit() {
        if (username.length == 0) {
          return
        }

        // 認証処理を呼び出し、認証成功のときは前ページで設定されたリダイレクト先へ遷移する
        val authResult = AuthenticatedWebSession.get().signIn(username, password)
        if (authResult) {
          // ここではそのままリダイレクトしているが、ここでユーザーロールのチェックとかをはさむ、はず
          continueToOriginalDestination()
        }
      }
    }

    // ModelとTextFieldをformに追加する
    form.model = CompoundPropertyModel(this)
    form.add(TextField<String>("username"))
    form.add(PasswordTextField("password"))

    add.(form)
  }
}

サインインページのデフォルト実装はあるけれど、同じくデフォルト実装のパネルと組み合わせて使う想定のようで、自力で書くのと大差ないかも....

2. 途中で認証をはさみたいときは

「トップページとかは普通に見れるようにするが、あるページは会員だけに見せたい」といった場合、途中でユーザー認証を行い、認証に成功したらユーザーがもといたページに戻す、といった処理をやることになる。

そういった場合は、以下の二つの方法をとる、とのこと。[出典]

いずれも、実行時に遷移前の状態をセッションストアに保存したあと、認証に成功した場合認証前の状態に復元してくれるが、認証ページへの遷移直前に RestartResponseAtInterceptPageException が内部的にスローされてページ遷移するので、以後の処理は行われなくなる。

3. Wicketでの認可

いわゆる処理に対する権限チェックである。先ほどの例で出てきた AuthenticatedWebSession#getRoles() も、認可に関する処理の一つ。
「ログインしているか否か」も、認可の範囲に入る模様。

基本処理は IAuthorizationStrategy で規定されている

boolean isActionAuthorized(Component, Action) アクションが許可されているかどうかを返す。
boolean isInstantiationAuthorized(Class<T extends IRequestableComponent>) 指定されたコンポーネントをインスタンス化できるかどうかを返す。
boolean isResourceAuthorized(IResource, PageParameters) リクエストのパラメータが、リソースへのアクセスが許可されているかどうかを返す。

だが、すべてを許可するALLOW_ALLという実装のほか、ページ単位のチェックを行う規定処理がすでに組み込まれている。個人的には、一番しっくりきたのがロールベース戦略として紹介されている MetaDataRoleAuthorizationStrategyAnnotationsRoleAuthorizationStrategy だった。
それぞれ、用途的には以下のような違いがある。

MetaDataRoleAuthorizationStrategy アプリケーション起動時に、あらかじめ使用可能な権限をページごとに列挙しておく
AnnotationsRoleAuthorizationStrategy ページクラスに対し、アノテーションで権限を宣言していく

両者とも一長一短ではあるけど、たとえば、 AdminOperatablePage というページに「サインインしていたら表示可能、スーパーユーザーなら中のFormを操作可能」という権限を設定する場合、

  • MetaDataRoleAuthorizationStrategy のばあい
class WicketApplication() : AuthenticatedWebApplication() {
  override fun init() {
    securitySettings.authorizationStrategy = MetaDataRoleAuthorizationStrategy(this)

    // ログインしていたら "AdminOperatablePage" はインスタンス化を許可
    MetaDataRoleAuthorizationStrategy.authorize(AdminOperatablePage::class.java, "SIGNED_IN")

    // adminForm というコンポーネントは、"ADMIN"なら操作可能にする
    // "ADMIN"でないときはロックされる
    MetaDataRoleAuthorizationStrategy.authorize(adminForm, Action.ENABLE, "ADMIN")
  }
}
  • AnnotationsRoleAuthorizationStrategy のばあい
@AuthorizeInstantiation("SIGNED_IN")
@AuthorizeAction(action = "ENABLE", roles = {"ADMIN"})
class AdminOperatablePage(params: PageParameters) : WebPage(params) {
  // @AuthorizeInstantiation で "SIGNED_IN" を持つ場合にインスタンス化を許可
  // ここの @AuthorizeAction は、ページ全体に効果がある
  ...
}

....といった感じになるはず。

なお、権限は AuthenticatedWebSession#getRoles() で取得されるので、先ほどのセッションストアクラスを書き直してみると、

class BasicAuthenticationSession(request: Request) : AuthenticatedWebSession(request) {

  override fun getRoles() : Roles {
    // ロールは中身がHashSet<String>なので、条件に応じたロール識別文字列をaddしていく
    // @see https://ci.apache.org/projects/wicket/apidocs/8.x/org/apache/wicket/authroles/authorization/strategies/role/Roles.html
    val resultRoles = Roles()

    if (isSignedIn()) {
      // たとえば、サインインしていたら"SIGNED_IN"をおく、とか
      resultRoles.add("SIGNED_IN")
    }
    if (username == "superuser") {
      // スーパーユーザーだったら"ADMIN"を置く、とか
      resultRoles.add("ADMIN")
    }
    // このほか、ユーザーのオブジェクトを保管しているなら、そのオブジェクトにぶら下がっている
    // ロールをaddしていくことになる、はず

    return resultRoles
  }
}

...といった具合になるはず。

4. 認可に失敗したときは

認可に失敗したとき、デフォルトでは

  • ユーザーがサインインしていないときは、既定のサインインページへ遷移させる
  • サインインしているがコンポーネントが認可できない場合は、 UnauthorizedInstantiationException がスローされる

....という挙動だが、カスタム処理にしたい場合は IUnauthorizedComponentInstantiationListener というイベントリスナがあるので、これの中で処理する。

  securitySettings.unauthorizedComponentInstantiationListener = IUnauthorizedComponentInstantiationListener {
    // 認可失敗を処理する "AuthWarningPage" というページをレスポンスにする
    it.setResponsePage(AuthWarningPage::class.java)
  }

ロールの検証を追加するなら、こんな感じになるはず。

  securitySettings.unauthorizedComponentInstantiationListener = object : IRoleCheckingStrategy, IUnauthorizedComponentInstantiationListener {
    override fun onUnauthorizedInstantiation(component: Component?) {
      // 認可失敗を処理する "AuthWarningPage" というページをレスポンスにする
      component!!.setResponsePage(AuthWarningPage::class.java)
    }

    override fun hasAnyRole(roles: Roles): Boolean {
      // 指定ロールが見つかったらtrueを返す処理を書く
      var result = roles.hasRole("ADMIN")
      return result
    }
  }

まぁ、このあたりはデフォルト実装でもどうにかなるレベルなので、やるかどうかはケースバイケースか。

| | コメント (0)

2019年9月14日 (土)

MavenのJettyにDataSourceを設定するのに苦労した話

0. やっぱりデータソースは外に出したいのです

前回のデータアクセスの話では、

val database: Sql2o = Sql2o("jdbc:mysql://localhost:3306/mydb", "mydbadmin", "mydbadmin")

と、ソースコードに直接書いていたけれど、実際の運用を考えるとDBの接続先設定とプロダクションコードは極力分離したいところ。

幸いにも、Mavenで作ったプロジェクトのアーキタイプにはJettyがdependencyに組み込まれていて、test側に開発サーバとしてJettyを起動するときの処理が最初から入っている。
ここに DataSource 設定を書いてやれば、理屈の上ではプロダクションコードからDBの接続先設定を分離できることになる。

で、前置きが長くなってしまったけど、Mavenで依存に入っているJettyにDataSourceを設定するのに結構苦労をしたので、その時の備忘録的なメモを書いてみる。

1. やること(というかやったこと)

やったことをざっくりまとめると、以下のとおり。

  1. pom.xmlに依存とプラグイン設定を追加
  2. Jettyの設定ファイル追加
  3. web.xmlにDataSourceルックアップ設定を追加
  4. DataSourceを使うようデータアクセス部分を変更

1. pom.xmlに依存とプラグイン設定を追加

なにはなくともdependencyに必要なライブラリを入れなければならない。
今回はコネクションプールライブラリを挟む方針で、高性能を謳っていることで話題のHikariCPを使ってみることにする。

<dependencies>
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.17</version>
    <!-- Jettyがtestスコープで動くのでそちらに合わせる -->
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>3.3.1</version>
    <!-- Jettyがtestスコープで動くのでそちらに合わせる -->
    <scope>test</scope>
  </dependency>
</dependencies<

Jettyがtestスコープで動くので、コネクションプールとJDBCドライバもそれに合わせておく。

次に、JettyがHikariCPとconnector-jを扱う設定を読み込めるようにするのだけど、これはMavenプラグインの設定をいじる必要がある。
追加するとこんな感じになるはず。

<plugins>
  <plugin>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-maven-plugin</artifactId>
    <version>${jetty9.version}</version>
    <configuration>
      <-- 追加、testスコープのclasspathを優先する設定 -->
      <useTestScope>true</useTestScope>
      <-- 追加ここまで -->
      <systemProperties>
        <systemProperty>
          <name>maven.project.build.directory.test-classes</name>
          <value>${project.build.directory}/test-classes</value>
        </systemProperty>
      </systemProperties>
      <jettyXml>
        ${project.basedir}/src/test/jetty/jetty.xml,${project.basedir}/src/test/jetty/jetty-ssl.xml,${project.basedir}/src/test/jetty/jetty-http.xml,${project.basedir}/src/test/jetty/jetty-https.xml
      </jettyXml>
      <!-- 以下追加 -->
      <webApp>
        <!-- web.xml を読み込む先を指定する設定 -->
        <descriptor>${project.basedir}/src/main/webapp/WEB-INF/web.xml</descriptor>
        <!-- 「データソースとして定義する内容」を書いたファイルを読み込む設定 -->
        <jettyEnvXml>${project.basedir}/src/test/jetty/jetty-env.xml</jettyEnvXml>
      </webApp>
      <!-- 追加ここまで -->
    </configuration>
  </plugin>
</plugins>

2. Jettyの設定ファイル編集

前述のpom.xml

に追加したjetty-env.xmlを、src/test/jettyに作る。

設定例をググっていると、 Configure の class に org.eclipse.jetty.webapp.WebAppContext を使う例が結構ひっかっかると思うけど、これをやると ClassCastException で設定に失敗するので org.eclipse.jetty.maven.plugin.JettyWebAppContext に変更している。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
<!-- Configure の class は、 Maven プラグインから動くのでこっちを使う -->
<Configure id="wac" class="org.eclipse.jetty.maven.plugin.JettyWebAppContext">
  <New id="myDS" class="org.eclipse.jetty.plus.jndi.Resource">
    <Arg><Ref refid="wac" /></Arg>
    <!-- ここが DataSource の名前になる -->
    <Arg>jdbc/datasource</Arg>
    <Arg>
      <New class="com.zaxxer.hikari.HikariDataSource">
        <Arg>
          <New class="com.zaxxer.hikari.HikariConfig">
            <!-- 以下 setter 部分の意味は JavaDoc とかを参照のこと -->
            <Set name="maximumPoolSize">20</Set>
            <Set name="driverClassName">com.mysql.cj.jdbc.Driver</Set>
            <Set name="jdbcUrl">jdbc:mysql://localhost:3306/mydb</Set>
            <Set name="username">mydbadmin</Set>
            <Set name="password">mydbadmin</Set>
          </New>
        </Arg>
      </New>
    </Arg>
  </New>
</Configure>

3. web.xmlにDataSourceルックアップ設定を追加

下回りは設定できたので、プロダクションコードに関連する箇所をいじって、 DataSource をルックアップできるようにする。

pom.xmlに追加したとおり、/src/main/webapp/WEB-INF/web.xmlに以下を追加。

<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://www.oracle.com/webfolder/technetwork/jsc/xml/ns/javaee/web-app_3_1.xsd"
    version="3.1">

  <!-- "jdbc/datasource" をデータソースとして名付ける設定 -->
  <resource-ref>
    <res-ref-name>jdbc/datasource</res-ref-name>
    <res-type>javax.sql.DataSource</res-type>
    <res-auth>Container</res-auth>
  </resource-ref>

</web-app>

4. DataSourceを使うようデータアクセス部分を変更

ここまでくれば DataSource をルックアップできるようになるので、接続設定を DataSource を使うように変更する。
例によって Kotlin で書いている。

open class DatabaseHelper {
    // init ブロックで初期化されるので空でよい
    //val database: Sql2o = Sql2o("jdbc:mysql://localhost:3306/mydb", "mydbadmin", "mydbadmin")
    val database: Sql2o

    init {
        // InitialContext から "jdbc/datasource" をルックアップする
        val context = InitialContext()
        val datasource = context.lookup("java:comp/env/jdbc/datasource") as DataSource
        // ルックアップした DataSource オブジェクトで Sql2o を初期化
        database = Sql2o(datasource)
    }
}

この状態で mvn package を実行して war を作り、 Tomcat とかのアプリケーションコンテナにデプロイしてもちゃんと動く。ここまで来るのに長かった....。

| | コメント (0)

2019年9月 7日 (土)

Apache Wicketを使ってみる[3] -Model編-

0. Wicketでいう「Model」とは何か

Modelというと、だいたいビジネスロジック層とかデータアクセス層とかの認識でいるわけだが、Wicketにおける「Model」はセッションストアを含むデータアクセスも、Viewへの入出力を含む状態の受け渡しもやる。[出典]
こう書いてしまうと、かなりViewModelっぽい気もする。

1. 基本中の基本

単純なラベル表示の場合、たいていのチュートリアルにはこう書かれているはず。

HTML
<div><span wicket:id="strLabel">Label!!</span></div>
Java
add(new Label("strLabel", "Hello, World!!));

要は「"strLabel"というIDをもつ要素に"Hello, World!!"という文字列を置け」というだけのものだけれど、内部的には

add(new Label("strLabel", new Model<String>("Hello, World!!")));

と、Viewへの出力を行う基本的な実装を持つModelを仲介している、そうな。[出典]

2. 応用編

前述の例は単純なリテラルの出力だけだけど、当然のことながら「Java Beansとのやりとり」も「別のModelが吐いた出力を受けた入力」も、Modelとして処理できる。

全文はhttps://github.com/bitstorm/Wicket-tutorial-examples/tree/master/ModelChainingExampleを参照いただくとして、「ドロップダウンリストの選択値変化に応じてFormのテキストボックスの値を動的に変更する」処理も容易に実現できる。
なお、こちらのコードは例によってKotlinで書いてみた。

class PersonListDetails() : WebPage() {
    /** 連動して値を反映させる先のForm */
    private lateinit var forms: Form<Unit>
    /** ドロップダウンリストの中身(select + option) */
    private lateinit var personList: DropDownChoice<Person%>

    init {
        // selectとformの橋渡しをするModel
        val listModel = Model<Person>
        // selectで選択された値を拾うレンダラー
        personRender = ChoiceRenderer<Person>("fullName")

        // ドロップダウンリストを画面へ割り当てる
        personList = DropDownChoice<Person>("persons", listModel, loadPersons(), personRender)
        // 更新通知を受け取るためのイベントリスナー的なもの
        // これをトリガーに、下で定義しているformにModelが値を反映してくれる
        personsList.add(FormComponentUpdatingBehavior());
        // 画面へコンポーネントを反映
        add(personList)

        // selectの変化を受け取るModelを引数にとるModelをformに張る
        form = Form("form". CompoundPropertyModel<Person>(listModel))
        //   それぞれ、listModelが持っているJava Beansのプロパティに紐づいている
        form.add(TextField("name"));
        form.add(TextField("surname"));
        form.add(TextField("address"));
        form.add(TextField("email"));

        add(form)
    }

    // selectに張る中身のListを作る処理
    fun loadPerson(): List<Person> {...}
}

// 名(name)と姓(surname)を受け取る
data class Person(var name:String, var surname: String): Serializable {
    // 住所
    var address: String
    // メアド
    var email: String
    // 配偶者
    var spouse: Person?
    // 子供
    var children: MutableList<Person>?
}

これをStrutsでやろうと思うと、JSPにhiddenでいっぱい値をねじ込んでおかないとサクッとはできないよな....

ちなみに、前述のチュートリアルを動かすには pom.xmlのパッケージング設定がjarになっているのでwarに変更する 必要があるので注意。

| | コメント (0)

2019年9月 2日 (月)

続:Apache Wicketを使ってみる -データアクセス編-

0. データアクセスをどうするか

Quick Start Wizardで作成したMavenプロジェクトには、当然のことながらデータアクセスに関するライブラリがないので、自力でどうにかするしかない。

とはいえ、Spring Bootのような至れり尽くせりな環境でもないけど、ローレベルなJDBCをスクラッチで書き起こす元気もないので、今回はsql2oというライブラリを使ってみることにする。

1. sql2oとは

sql2o本家、およびGitHubの記述をざっくりまとめると、こんな感じ。

位置づけ的にはDapper.NETRoom Persisitence Libraryに近いかも。

  • 小さく軽量なデータアクセス用ライブラリ
  • JDBCのResultSetをPOJOに変換する機能を提供するが、SQLを生成する機能はない
  • 他の著名なライブラリに比べ、高速に(モノによっては6,7倍高速)動作する

導入はMavenで。執筆時点の最新版は1.6.0だった。
今回はDBMSにMySQLを使ってみることにするので、Connector/Jも一緒に設定しておく。

<dependency>
  <groupId>org.sql2o</groupId>
  <artifactId>sql2o</artifactId>
  <version>1.6.0</version>
</dependency>
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>8.0.17</version>
</dependency>

2. 検索系処理

データベースへの接続は、Sql2oのコンストラクタに接続文字列を渡すことで作ってくれる。
なお、あえてすべてKotlinで書いてみた。

val database: Sql2o = Sql2o("jdbc:mysql://localhost:3306/mydb", "mydbadmin", "mydbadmin")

オープン処理とかは、DAOの基底クラスにプロパティを書いておくと扱いやすいかも。

/** トランザクションなしのConnection */
val connection: Connection
    get() {
        return database.open()
    }
/** トランザクション開始ありのConnection */
val connectionWithTran: Connection
    get() {
database.beginTransaction() }

で、検索系はSQLをそのまま書き、それを Connection#createQuery(String) に渡して Query オブジェクトを作り、 Query#executeAndFetch(Class) を実行すると ResultSet を POJO へバインドしてくれる。

// SQLをそのまま書く。長いSQLの場合はヒアドキュメントを使うと楽かも
val sql = "select todo_id, todo_title, created_at, finished from todo"
// 接続をオープン
val conn = connection
try {
    // use 関数でクエリ実行(Javaでいうところのtry-with-resources)
    conn.use {
        // setAutoDeriveColumnNames はスネークケース→キャメルケースを自動変換する設定
        val query = conn.createQuery(sql).setAutoDeriveColumnNames(true)
        val ret: MutableList<ToDo> = query.executeAndFetch(ToDo::class.java)
    }
} catch (e: Exception) {
    // do something...
}

3. 更新系処理

更新系処理も、検索系処理同様 createQuery(String) でプリペアードステートメントを作り、 Connection#executeUpdate() で実行する。

val sql = "insert into todo (todo_title, created_at, finished) values (:todoTitle, :createdAt, :finished)"
// トランザクション開始ありのConnectionを取得
val tran = connectionWithTran
try {
    tran.use {
        val stmt = tran.createQuery(sql)
                // プリペアードステートメントへバインドするパラメータを追加していく
                .addParameter("todoTitle", todo.todoTitle)
                .addParameter("createdAt", todo.createdAt)
                .addParameter("finished", todo.finished)
        stmt.executeUpdate()
        // use 関数内で commit()を発行
        tran.commit()
    }
} catch (e: Exception) {
    // do something...
    // ロールバックは暗黙的に行われる
}

なお、sql2oには宣言的トランザクションはないので、トランザクション制御は自分でやらなければならない。

4. 最後に

自力でSQLのチューニングをしたほうが手っ取り早い場合なんかには、こういうライブラリのほうがいいかもしれない。

| | コメント (0)

2019年8月15日 (木)

Apache Wicketを使ってみる

0. 事の始まり

ある筋から「Apache Wicketを学習しといて」とのお達しがきたので自分向けメモ。

1. Apache Wicketとは

公式の記載をざっくり集約するとこんなところ。

  • 2000年代半ばのJava Webアプリケーションフレームワーク戦争の、数少ない生き残りの一つ(2004年に世に出た)
  • Apacheソフトウェア財団のもと、Apache 2.0 Licenseで公開されているOSS
  • Ajax対応機能を内包
  • 多言語対応(デフォルトで25言語を内包)
  • ページやコンポーネントはJavaオブジェクトとして扱える
  • 複数タブ/ウィンドウへの対応
  • ページやコンポーネントのテストのサポート
  • CDIやSpring、GuiceなどのDIをサポート
  • JPA、Bean ValidationなどのJava EE 6サポート
  • 最新安定版のWicket 8では、Java SE 8の言語仕様に対応するため、Java 8とServlet API 3.1が必須に

まぁ、最後のやつは、いまどきならよほどのことがない限りクリアできていると思うけど。
※そのよほどの(ry

2. セットアップ

プロジェクト自体はMavenで管理可能。
※言うまでもないが、Maven自体の扱いについては割愛。

MavenのコマンドラインはQuick Start Wizardで作成できる。

このページで生成されたコマンドラインをターミナルに投入すれば、Mavenが必要なものをそろえてくれるんだが、Windows使いで、PowerShellを使うときには注意が必要。

PS > mvn archetype:generate `
>>   "-DarchetypeGroupId=org.apache.wicket" `
>>   "-DarchetypeArtifactId=wicket-archetype-quickstart" `
>>   "-DarchetypeVersion=8.5.0" `
>>   "-DgroupId=com.mycompany" `
>>   "-DartifactId=myproject" `
>>   "-DarchetypeRepository=https://repository.apache.org/" `
>>   "-DinteractiveMode=false"

といった具合にmvnの -D オプションをダブルクォーテーションで囲わないと、「出力ディレクトリがわからない」とMavenに怒られる。
ハイフンで始まる部分がPowerShellのオプションと解釈されてしまうのが、どうも原因なようだ。なお、DOS窓なら問題なし。

Mavenがプロジェクトを生成したら、mvn clean installで必要ライブラリをダウンロードする。このあたりは、ほかのMavenプロジェクトと同じ扱い。

あとはIDEにMavenプロジェクトとしてインポートすれば、開発体制はとりあえず整う。
必要があれば、愛用のIDEにプラグインを入れてやろう。

3. 開発サーバの起動

生成されたpom.xmlをみると、dependencyブロックにJetty 9が含まれているので、

PS > mvn jetty:run

で localhost:8080 でJettyが起動する。

4. ページの構成など

Strutsなんかに代表されるクラシックなJava Webアプリケーションと違い、ViewとなるHTMLと、処理を受け持つJavaクラス(≒バッキングビーン)が1対1対応していて、ともに同じJavaパッケージ階層に置く必要がある。

このあたり、どちらかといえばASP.NETに近いかも。ただし、CSSなどを駆使して描画する枠線などは、独自タグのあるASP.NETと違い、普通にCSSで定義する。以下、GitHubにあるexampleから引用。

<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
<head>
  <title>Wicket Examples - sample panel</title>
</head>
<body>
Everything outside of the <wicket:border> tags will be ignored.
Might be handy as preview code.

  <wicket:border>
    <div style="border: 2px dotted #fc0; width: 400px; padding: 5px;">
      before the border contents <br />
    <wicket:body/>
    <br />after the border contents <br />
    </div>
  </wicket:border>

</body>
</html>

JSPとは違い、HTMLにカスタムタグを埋め込む形式なので、普通にプレビューできるのはいいと思う。

サブミットの処理については、バッキングビーンにViewのformに対応するインナークラスを定義して処理するそうな。
以下、こちらもGitHubにある公式のexampleから引用。

public final class GuestBook extends WicketExamplePage {
  /** A global list of all comments from all users across all sessions */
  private static final List<Comment> commentList = new ArrayList<>();

  /**
   * Constructor that is invoked when page is invoked without a session.
   */
  public GuestBook() {
    // Add comment form
    add(new CommentForm("commentForm"));

    // Add commentListView of existing comments
    add(new PropertyListView<>("comments", commentList) {
      @Override
      public void populateItem(final ListItem listItem) {
        listItem.add(new Label("date"));
        listItem.add(new MultiLineLabel("text"));
      }
    }).setVersioned(false);
  }

  /**
   * A form that allows a user to add a comment.
   *
   * @author Jonathan Locke
   */
  public final class CommentForm extends Form<ValueMap> {
    /**
     * Constructor
     *
     * @param id The name of this component
     */
    public CommentForm(final String id) {
      // Construct form with no validation listener
      super(id, new CompoundPropertyModel<>(new ValueMap()));

      // this is just to make the unit test happy
      setMarkupId("commentForm");

      // Add text entry widget
      add(new TextArea<>("text").setType(String.class));

      // Add simple automated spam prevention measure.
      add(new TextField<>("comment").setType(String.class));
    }

    /**
     * Show the resulting valid edit
     */
    @Override
    public final void onSubmit() {
      ValueMap values = getModelObject();

      // check if the honey pot is filled
      final String _comment = (String) values.get("comment");
      if (_comment != null && !_comment.isBlank()) {
        error("Caught a spammer!!!");
        return;
      }
      // Construct a copy of the edited comment
      Comment comment = new Comment();

      // Set date of comment to add
      comment.setDate(new Date());
      comment.setText((String)values.get("text"));
      commentList.add(0, comment);

      // Clear out the text component
      values.put("text", "");
    }
  }

  /**
   * Clears the comments.
   */
  public static void clear() {
    commentList.clear();
  }
}

5. Ajax対応について

サブミット処理の応用。処理を受け付けるクラスの中で、AjaxFallbackLinkで発動用のリンクと紐づけ、AjaxRequestTargetに処理結果を渡す。

このしくみだと、JavaScriptでイベントハンドラをゴリゴリ書かなくてもAjax対応できるのはいいかもしれない。

6. データアクセス

Wicket自体にはデータアクセス用のコンポーネントはないので、Spring Dataなんかと組み合わせる必要がある。

7. 雑感

今回やったところとしてはこんなところ。

  • Viewがあるページには、普通にHTMLのプレビューがきくので、画面の作成はJSPを使うものより効率よくできそう
  • Ajaxをつかった動きのあるページの作成は、比較的簡単に作れそう

認証とテストについては、また日を改めて挑むことにしよう。

| | コメント (0)

2019年7月20日 (土)

続々:BSoDとの戦いに挑む

収束した....のか?

前回前々回のポストからいくつかやってみた結果の結論から言うと、Version 1903をインストールしたら収まった。ような気がする。

それまでにやったこと

ざっくり書くとこんな感じ。

  1. 余計な仮想スイッチの削除
  2. NICのドライバ更新
  3. Windows 10 Version 1903 (Windows 10 May 2019 Update) のインストール

1. 余計な仮想スイッチの削除

OSはPro版なのでHyper-Vが使えるんだけど、Version 1709から「既定のスイッチ(Default Switch)」と呼ばれる仮想スイッチが自動で作成されるようになった。

既定のスイッチについてはQiitaに解説記事が投稿されているのでそちらを参照いただくとして、過去Hyper-Vゲストにインターネットアクセスさせるために仮想スイッチを作っていたことがあり、そいつと機能的に噛んでいるのでは?ということで既定のスイッチだけ残して残りは削除することに。

ちなみにこれ、Hyper-Vの役割をセットアップしていないと削除できないので、Hyper-Vをセットアップして既定のスイッチ以外を削除して再起動。
これでしばらく様子をみることにしたが、一日置いてBSoD発症。

2. NICのドライバ更新

デバイスマネージャーでNIC(有線、無線とも)を見てみると、Windows Update経由でインストールされたものが使用されていた。

そういえば昔(10年以上前の話だが)、両面自動印刷機能とかが付いたHPのカラーインクジェットプリンタをWindows Updateのドライバで動かそうとしたら、片面印刷だけ、かつカラー不可という非常にイケてない状況になったことがあったので、IntelからNICのドライバをダウンロードしてセットアップ。
同様に様子を見ていたら、作業の3日後にBSoD発症。

3. Windows 10 Version 1903 のインストール

バージョン情報をみると、Version 1903公開から2か月以上たっていたにもかかわらずVersion 1803のままだった。たぶん、1903をダウンロードできるだけのまとまった稼働時間を確保していなかったのだろう。

ということで、更新アシスタントからVersion 1903を手動セットアップすることに。

結果、Intel製NICドライバは見事Windows Update版で上書きされてしまったが、実のところVersion 1903をセットアップして以降BSoDは発症していない。こっちの記事でDocker for Windowsを使っていても、である。

はっきりしないうちに収束した感があるので、モヤモヤするものが若干あるけれど。

| | コメント (0)

DockerにSQL Serverを追い出す件

追い出したい

僕のPCには開発用と称して

  • PostgreSQL 9.6
  • MySQL 8
  • Microsoft SQL Server 2017 Developer

をセットアップしているんだが、ホスト環境にそのままセットアップしているとこういった不満が出てくる(という私見)。

  • アップグレードは簡単にしたい
  • DBMSはアプリと1対1対応させたい
  • 開発していないときは、DBMSのプロセスを下げたい

こんなところか。
ということで、手始めにSQL ServerをDockerに追い出してみることにする。

SQL Server on Docker

いまどきのSQL ServerはLinux上でも動かせるエディションがリリースされているので、Microsoftも公式Dockerイメージを公開している。
今回はホスト環境にデータを永続化しておきたいので、Docker Hubの記載をもとにdocker-compose.ymlをかいてみた。

まだホスト環境でSQL Serverが稼働中なので、ホスト環境からの接続ポートを1433から14330に変更している。

version: '3'

 

services:
# Microsoft SQL Server 2017 Linux (Developer Edition)
db:
image: mcr.microsoft.com/mssql/server:2017-latest
container_name: mssql2017dev
environment:
ACCEPT_EULA: Y
# If you runs in production environment, you should change sa password.
SA_PASSWORD: P@ssw0rd2017
# If you have valid Product ID, set MSSQL_PID valiable.
# See : https://hub.docker.com/_/microsoft-mssql-server
#MSSQL_PID: Express
volumes:
- ./data/data:/var/opt/mssql/data
- ./data/log:/var/opt/mssql/log
ports:
- 14330:1433

docker-compose.ymlを置いているディレクトリで、PowerShellからこう叩くとDocker上にSQL Serverが立ち上がる。

PS> mkdir .\data\data
PS> mkdir .\data\log
PS> docker-compose up -d

 

ツールで接続する

 

立ち上がったら、ホスト環境のsqlcmdで接続して動作確認。

PS> sqlcmd -S localhost,14330 -U sa -P P@ssw0rd2017

注意点としては、ポートの指定がよくあるコロン(:)ではなくカンマ(,)だということ。MSDNにはちゃんと書かれているけれど、これは初見殺しだよなぁ。

接続ができたら、今度はSQL Server Management Studioを使えるようにしてみる。接続ダイアログのサーバ名は、sqlcmdの時同様の書き方でよい。

Connwithssms

これでSQL ServerをDockerに追い出せたので、データ移行をこなせば追い出しが完了する見込み。

 

プロジェクト的なもの

GitHubにプロジェクト的なものを上げてます。
ご参考あれ。

» 続きを読む

| | コメント (0)

2019年6月23日 (日)

続:BSoDとの戦いに挑む

ネイティブモジュールのデバッグに挑む

先日のポストでToDoにしていた、デバッガのセットアップとかをやってみた。

いまどきのデバッガ

Debugging Tools for Windows というやつを使うんだが、こいつにはMicrosoft Store版があって、最新のWindows 10の場合そちらのほうがより近代的なんだそうな。

Windbg Preview in Microsoft Store

ストアアプリなので、導入自体はいたって簡単。ストアからアプリを入れたら、アクセスしやすくするためスタートメニューにピン止めしておく。

一方、ダンプサイズの拡張については、最近のWindowsはやらなくてもそれなりのサイズを確保してくれるらしい。設定自体は以前と同じシステムのプロパティの中の「起動と回復」の中にある。

Boot_and_recovery

デフォルト値が「自動メモリダンプ」になっていて、メモリを16GB搭載しているこのPCでは、だいたい1GB~1.5GB程度のダンプを確保してくれるようになっている。
昔のデフォルト値は「最小メモリダンプ(256KB)」だったので、ダンプから自力でどうにかしようと考えているような人にはいい時代になった、と言える(?)

実際にデバッガにかけてみる

実のところ、よく発生しているBSoD 2種が先日の記事を書いた後で運よく(?)発生したので、ダンプを確保して分析にかけてみることにした。
WinDbgを起動して、確保したダンプを読み込ませると、自動的に簡易分析が始まる。デバッグシンボルの取得は勝手にやってくれるようだ。

0xd1_simple_analysis

画面中の !analyze -v のリンクを押すと分析が始まる。
以下は DRIVER_IRQL_NOT_LESS_OR_EQUAL で BSoD になったやつを分析したときの出力だけど、長いので途中省略。 vmswitch というモジュールがトリガーになったようだ。

0: kd> !analyze -v
*******************************************************************************
*                                                                             *
*                        Bugcheck Analysis                                    *
*                                                                             *
*******************************************************************************

 

DRIVER_IRQL_NOT_LESS_OR_EQUAL (d1)
An attempt was made to access a pageable (or completely invalid) address at an
interrupt request level (IRQL) that is too high.  This is usually
caused by drivers using improper addresses.
If kernel debugger is available get stack backtrace.
Arguments:
Arg1: 0000000000000548, memory referenced
Arg2: 0000000000000002, IRQL
Arg3: 0000000000000000, value 0 = read operation, 1 = write operation
Arg4: fffff808c961972b, address which referenced memory

 

Debugging Details:
------------------
  :
(中略)
  :
FOLLOWUP_IP: 
vmswitch!VmsQueueGroupIsRestrictedQueue+7
fffff808`c961972b 8b9048050000    mov     edx,dword ptr [rax+548h]

 

FAULT_INSTR_CODE:  548908b
SYMBOL_STACK_INDEX:  3
SYMBOL_NAME:  vmswitch!VmsQueueGroupIsRestrictedQueue+7
FOLLOWUP_NAME:  MachineOwner
MODULE_NAME: vmswitch
IMAGE_NAME:  vmswitch.sys
DEBUG_FLR_IMAGE_TIMESTAMP:  62acf328
STACK_COMMAND:  .thread ; .cxr ; kb
BUCKET_ID_FUNC_OFFSET:  7
FAILURE_BUCKET_ID:  AV_vmswitch!VmsQueueGroupIsRestrictedQueue
BUCKET_ID:  AV_vmswitch!VmsQueueGroupIsRestrictedQueue
PRIMARY_PROBLEM_CLASS:  AV_vmswitch!VmsQueueGroupIsRestrictedQueue
  :

そして、もう一つ。 KERNEL_SECURITY_CHECK_FAILURE で BSoD になったやつを分析してみる。

Loading Dump File [C:\work\memdumps\0x139_vmswitch-sys_20190622-03.DMP]
Kernel Bitmap Dump File: Kernel address space is available, User address space may not be available.

 

 

************* Path validation summary **************
Response                         Time (ms)     Location
Deferred                                       srv*
Symbol search path is: srv*
Executable search path is: 
Windows 10 Kernel Version 17134 MP (4 procs) Free x64
Product: WinNt, suite: TerminalServer SingleUserTS
Built by: 17134.1.amd64fre.rs4_release.180410-1804
Machine Name:
Kernel base = 0xfffff803`254b0000 PsLoadedModuleList = 0xfffff803`2585d170
Debug session time: Sat Jun 22 22:09:48.984 2019 (UTC + 9:00)
System Uptime: 0 days 5:39:39.842
Loading Kernel Symbols
.........................................Page 20008bef5 too large to be in the dump file.
Page 2000aadf4 too large to be in the dump file.
......................
................................................................
................................................................
...............................
Loading User Symbols

 

Loading unloaded module list
..............
For analysis of this file, run !analyze -v
nt!KeBugCheckEx:
fffff803`2565aab0 48894c2408      mov     qword ptr [rsp+8],rcx ss:0018:ffffda05`86612940=0000000000000139
2: kd> !analyze -v
*******************************************************************************
*                                                                             *
*                        Bugcheck Analysis                                    *
*                                                                             *
*******************************************************************************

 

KERNEL_SECURITY_CHECK_FAILURE (139)
A kernel component has corrupted a critical data structure.  The corruption
could potentially allow a malicious user to gain control of this machine.
Arguments:
Arg1: 0000000000000003, A LIST_ENTRY has been corrupted (i.e. double remove).
Arg2: ffffda0586612c60, Address of the trap frame for the exception that caused the bugcheck
Arg3: ffffda0586612bb8, Address of the exception record for the exception that caused the bugcheck
Arg4: 0000000000000000, Reserved

 

Debugging Details:
------------------
  :
(中略)
  :
FOLLOWUP_IP: 
vmswitch!VmsQsUpdatePerProcessorPacketStats+2c3
fffff80b`e4a67be3 cd29            int     29h

 

FAULT_INSTR_CODE:  8d4829cd
SYMBOL_STACK_INDEX:  4
SYMBOL_NAME:  vmswitch!VmsQsUpdatePerProcessorPacketStats+2c3
FOLLOWUP_NAME:  MachineOwner
MODULE_NAME: vmswitch
IMAGE_NAME:  vmswitch.sys
DEBUG_FLR_IMAGE_TIMESTAMP:  62acf328
STACK_COMMAND:  .thread ; .cxr ; kb
BUCKET_ID_FUNC_OFFSET:  2c3
FAILURE_BUCKET_ID:  0x139_3_CORRUPT_LIST_ENTRY_vmswitch!VmsQsUpdatePerProcessorPacketStats
BUCKET_ID:  0x139_3_CORRUPT_LIST_ENTRY_vmswitch!VmsQsUpdatePerProcessorPacketStats
PRIMARY_PROBLEM_CLASS:  0x139_3_CORRUPT_LIST_ENTRY_vmswitch!VmsQsUpdatePerProcessorPacketStats

おや? こちらも vmswitch がトリガーになっているようだ。

問題になっているモジュールが特定できたので、次はワークアラウンドの検索かな....。

| | コメント (0)

2019年6月21日 (金)

BSoDとの戦いに挑む

ここ最近ひどい

僕が普段使いしている Panasonic CF-B11 というノート PC なんだけど、ここ最近不定期に BSoD を出してくる。
どうにかしなきゃね、ということで、調べてみることに。

イベントログを読む

Windows という OS はぎりぎりまできっちりイベントログを収集していて、それは BSoD による強制再起動も例外ではない。ということは割と知られていることなので、とりあえず直近の2か月ほどを抜いてみる。

BSoD イベントは、 System イベントログ内に BugCheck というソース名で記録されるので、 Windows PowerShell でとりあえずヘッドラインだけでも出してみる。

PS > Get-EventLog -LogName System -After "2019/05/01" | Where-Object {$_.Source -eq "BugCheck"}

   Index Time          EntryType   Source                 InstanceID Message                                                                            
   ----- ----          ---------   ------                 ---------- -------                                                                            
  157766 6 20 06:47    Error       BugCheck               1073742825 ソース 'BugCheck' のイベント ID '1073742825' の説明が見つかりません。必要なレジストリ情報またはメッセージを表示するメッセージ DL...
  156383 6 17 21:48    Error       BugCheck               1073742825 ソース 'BugCheck' のイベント ID '1073742825' の説明が見つかりません。必要なレジストリ情報またはメッセージを表示するメッセージ DL...
  151168 6 08 16:26    Error       BugCheck               1073742825 ソース 'BugCheck' のイベント ID '1073742825' の説明が見つかりません。必要なレジストリ情報またはメッセージを表示するメッセージ DL...
  148570 6 08 15:11    Error       BugCheck               1073742825 ソース 'BugCheck' のイベント ID '1073742825' の説明が見つかりません。必要なレジストリ情報またはメッセージを表示するメッセージ DL...
  146691 6 05 20:38    Error       BugCheck               1073742825 ソース 'BugCheck' のイベント ID '1073742825' の説明が見つかりません。必要なレジストリ情報またはメッセージを表示するメッセージ DL...
  145460 6 03 22:35    Error       BugCheck               1073742825 ソース 'BugCheck' のイベント ID '1073742825' の説明が見つかりません。必要なレジストリ情報またはメッセージを表示するメッセージ DL...
  144162 6 02 20:54    Error       BugCheck               1073742825 ソース 'BugCheck' のイベント ID '1073742825' の説明が見つかりません。必要なレジストリ情報またはメッセージを表示するメッセージ DL...
  137197 6 01 22:18    Error       BugCheck               1073742825 ソース 'BugCheck' のイベント ID '1073742825' の説明が見つかりません。必要なレジストリ情報またはメッセージを表示するメッセージ DL...
  136141 6 01 07:49    Error       BugCheck               1073742825 ソース 'BugCheck' のイベント ID '1073742825' の説明が見つかりません。必要なレジストリ情報またはメッセージを表示するメッセージ DL...
  133441 5 26 19:51    Error       BugCheck               1073742825 ソース 'BugCheck' のイベント ID '1073742825' の説明が見つかりません。必要なレジストリ情報またはメッセージを表示するメッセージ DL...
  133303 5 26 13:27    Error       BugCheck               1073742825 ソース 'BugCheck' のイベント ID '1073742825' の説明が見つかりません。必要なレジストリ情報またはメッセージを表示するメッセージ DL...
  132988 5 25 21:32    Error       BugCheck               1073742825 ソース 'BugCheck' のイベント ID '1073742825' の説明が見つかりません。必要なレジストリ情報またはメッセージを表示するメッセージ DL...
  132631 5 25 19:19    Error       BugCheck               1073742825 ソース 'BugCheck' のイベント ID '1073742825' の説明が見つかりません。必要なレジストリ情報またはメッセージを表示するメッセージ DL...
  131648 5 24 23:02    Error       BugCheck               1073742825 ソース 'BugCheck' のイベント ID '1073742825' の説明が見つかりません。必要なレジストリ情報またはメッセージを表示するメッセージ DL...
  130682 5 22 22:24    Error       BugCheck               1073742825 ソース 'BugCheck' のイベント ID '1073742825' の説明が見つかりません。必要なレジストリ情報またはメッセージを表示するメッセージ DL...
  129735 5 20 21:04    Error       BugCheck               1073742825 ソース 'BugCheck' のイベント ID '1073742825' の説明が見つかりません。必要なレジストリ情報またはメッセージを表示するメッセージ DL...
  129301 5 19 15:18    Error       BugCheck               1073742825 ソース 'BugCheck' のイベント ID '1073742825' の説明が見つかりません。必要なレジストリ情報またはメッセージを表示するメッセージ DL...
  128834 5 19 11:44    Error       BugCheck               1073742825 ソース 'BugCheck' のイベント ID '1073742825' の説明が見つかりません。必要なレジストリ情報またはメッセージを表示するメッセージ DL...
  128416 5 18 22:42    Error       BugCheck               1073742825 ソース 'BugCheck' のイベント ID '1073742825' の説明が見つかりません。必要なレジストリ情報またはメッセージを表示するメッセージ DL...
  126350 5 13 22:47    Error       BugCheck               1073742825 ソース 'BugCheck' のイベント ID '1073742825' の説明が見つかりません。必要なレジストリ情報またはメッセージを表示するメッセージ DL...
  125399 5 12 10:24    Error       BugCheck               1073742825 ソース 'BugCheck' のイベント ID '1073742825' の説明が見つかりません。必要なレジストリ情報またはメッセージを表示するメッセージ DL...
  125194 5 11 23:39    Error       BugCheck               1073742825 ソース 'BugCheck' のイベント ID '1073742825' の説明が見つかりません。必要なレジストリ情報またはメッセージを表示するメッセージ DL...
  123956 5 08 13:52    Error       BugCheck               1073742825 ソース 'BugCheck' のイベント ID '1073742825' の説明が見つかりません。必要なレジストリ情報またはメッセージを表示するメッセージ DL...
  121016 5 02 23:33    Error       BugCheck               1073742825 ソース 'BugCheck' のイベント ID '1073742825' の説明が見つかりません。必要なレジストリ情報またはメッセージを表示するメッセージ DL...
  120450 5 02 15:58    Error       BugCheck               1073742825 ソース 'BugCheck' のイベント ID '1073742825' の説明が見つかりません。必要なレジストリ情報またはメッセージを表示するメッセージ DL...
  119397 5 01 19:09    Error       BugCheck               1073742825 ソース 'BugCheck' のイベント ID '1073742825' の説明が見つかりません。必要なレジストリ情報またはメッセージを表示するメッセージ DL...

あー、このアウトプットではだめだ、ということで、 Format-List に通してみたところ、大半がこんな感じ。

Index              : 120450
EntryType          : Error
InstanceId         : 1073742825
Message            : ソース 'BugCheck' のイベント ID '1073742825' の説明が見つかりません。必要なレジストリ情報またはメッセージを表示するメッセージ DLL ファイルがローカル コンピューターに存在しない可能性があります。または、これらのデータへのアクセス
                     許可がユーザーに与えられていない可能性があります。次の情報はイベントの一部です:'0x00000139 (0x0000000000000003, 0xfffff801f3072950, 0xfffff801f30728a8, 0x0000000000000000
                     )', 'C:\WINDOWS\MEMORY.DMP', 'b537a1cf-30ea-4911-be63-13624e70de94'
Category           : (0)
CategoryNumber     : 0
ReplacementStrings : {0x00000139 (0x0000000000000003, 0xfffff801f3072950, 0xfffff801f30728a8, 0x0000000000000000), C:\WINDOWS\MEMORY.DMP, b537a1cf-30ea-
                     4911-be63-13624e70de94}
Source             : BugCheck
TimeGenerated      : 2019/05/02 15:58:00
TimeWritten        : 2019/05/02 15:58:00
UserName           : 

ReplacementStrings 行をみると、バグチェックコードが 0x139 だとわかった。先頭の16進数がそれだ。
MSDN の Bug Check Code Reference によれば、

カーネルが重要なデータ構造の破損を検知した
んだそうな。[出展]

加えて、先頭から2番目の16進数は「破損のタイプ」だそう。前述の MSDN の記事では、

A LIST_ENTRY was corrupted (for example, a double remove). For more information, see the following Cause section.

LIST_ENTRY というのは、リスト構造のデータを扱う構造体のことのようだ。

むぅ、構造体そのものではなくて、そいつを操作している処理を特定しないといけないようだ。ここから先の追跡は、デバッガ使ってダンプを眺めてみるしかなさそうだけど、デバッガって Visual Studio のどのコンポーネントに入ってるんだろう?

次の ToDo

  1. デバッガのセットアップ
  2. ダンプの取得サイズを1段階上げる設定に変える

くらいかな....?

| | コメント (0)

2019年4月21日 (日)

MySQL素人がWindows版で3rd partyユーティリティからの接続を使えるようにした話

終わってみればどうということはないけれど

 

正解に行きつくまで、正直なところ相当苦労した。いや、ぼくの MySQL 力がしょぼいせいだけど。

 

何が問題だったのか

 

かいつまんで書くとこんなところ。

 

  • 開発にも使っている自分の Windows PC に、 MySQL Community Edition 8.0 をインストール
  • 起動モードは Windows Service に設定 (Window なので)
  • 同時にインストールできる MySQL Workbench から接続に行くと普通に使える(まぁ、当たり前)
  • 普段は JetBrains DataGrip を使っているので、 DataGrip からローカルホストの MySQL に接続しようとすると「タイムゾーンがおかしい」といって接続できない

 

やったことの備忘録

 

大きくわけて二つ。

 

  1. タイムゾーン設定のインポート
  2. 起動時に参照されるタイムゾーン設定の作成

 

1. タイムゾーン設定のインポート

 

「そう言えば、タイムゾーンって my.cnf とかに書くやつじゃなかったっけ?」というわけで、そのあたりをググってみたところ、

 

Windowsの場合はOSにタイムゾーンの設定が無いため、タイムゾーンのファイルをダウンロードする必要があります。

 

などという記述を発見。
「ええ?そうなん?」と思いつつも、公式の MySQL :: Time zone description tables から、 MySQL 5.7以上むけの POSIX 標準 SQL ファイルをダウンロードして実行。

 

これでタイムゾーンの管理情報は作成できたので、起動してみるがダメ。

 

2. 起動時に参照されるタイムゾーン設定の作成

 

ここでどんなタイムゾーン設定が有効になっているのかを調べてみる。

 

mysql> show variables like '%time_zone%';
+------------------+--------+
| Variable_name | Value |
+------------------+--------+
| system_time_zone | |
| time_zone | SYSTEM |
+------------------+--------+
2 rows in set (0.01 sec)

 

これはどういうことなのか? と再度調べてみたところ、

 

 

変数名 説明
system_time_zone システムのタイムゾーンで、起動時に TZ 環境変数を参照して決定したあとは、変更されることはない
time_zone サーバのカレントタイムゾーンで、権限があれば後から変更できる。
初期値は SYSTEM で、 system_time_zone と同じであることを表す

 

....とのことのようだ。(出展 : http://download.nust.na/pub6/mysql/doc/refman/5.1/ja/time-zone-support.html)

 

なるほど、 my.cnf を編集しなくても環境変数定義で大丈夫なのか。ということで、オフセット値を設定してみることに。
管理者権限のあるDOS窓を起動して、以下を投入。 MySQL はサービス稼働なので、システム環境変数として登録した。

 

> setx /m TZ +09:00

 

この状態で MySQL を再起動して、再びタイムゾーン設定を見てみると、

 

mysql> show variables like '%time_zone%';
+------------------+--------+
| Variable_name | Value |
+------------------+--------+
| system_time_zone | +09 |
| time_zone | SYSTEM |
+------------------+--------+
2 rows in set (0.01 sec)

 

....ということで、無事タイムゾーンが日本に設定されたようだ。

 

この状態で、再び GataGrip から接続を試みると、無事接続できた。

| | コメント (0)

«古いプロジェクトを最新のASで開こうとした話