mike-neckのブログ

Java or Groovy or Swift or Golang

Speedment ORMのメモ

Stream APIを使ったORM。


概要

  • MavenプラグインでDBスキーマからモデルのクラス/レポジトリー(に該当する)クラスを吐き出す
  • Applicatoinクラスからレポジトリー(に該当する)クラスを取り出す
  • レポジトリーにStream APIを通じてクエリーを投げる

インストール

DBスキーマからモデルを出力するツールはgradleに対応していないのでmavenを用いる(まあ、gradle用にツールを作ってもよいのだが…)。なお、ツールで作られるコードは src/main/generated に出力する。

pluginの方

<build>
  <plugins>
    <plugin>
      <groupId>com.speedment</groupId>
      <artifactId>speedment-maven-plugin</artifactId>
      <version>3.0.3</version>
      <dependencies>
        <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>5.1.40</version>
        </dependency>
      </dependencies>
    </plugin>
    <plugin>
      <groupId>org.codehaus.mojo</groupId>
      <artifactId>build-helper-maven-plugin</artifactId>
      <version>1.7</version>
      <executions>
        <execution>
          <id>add-source</id>
          <phase>generate-sources</phase>
          <goals>
            <goal>add-source</goal>
          </goals>
          <configuration>
            <sources>
              <source>src/main/generated</source>
            </sources>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

dependenciesの方

<dependencies>
  <dependency>
    <groupId>com.speedment</groupId>
    <artifactId>runtime</artifactId>
    <version>3.0.3</version>
    <type>pom</type>
  </dependency>
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.40</version>
  </dependency>
</dependencies>

サンプルのスキーマ

この記事におけるDBのスキーマは次のとおり

f:id:mike_neck:20170305230003p:plain

CREATE TABLE student
(
    id BIGINT PRIMARY KEY NOT NULL AUTO_INCREMENT,
    last_name VARCHAR(10) NOT NULL,
    first_name VARCHAR(10) NOT NULL,
    admission_year INT NOT NULL
)
  ENGINE = InnoDB CHARACTER SET utf8mb4;

CREATE TABLE book_categories
(
    id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
    name VARCHAR(20) NOT NULL
)
  ENGINE = InnoDB CHARACTER SET utf8mb4;
CREATE UNIQUE INDEX book_categories_name_uindex ON book_categories (name);

CREATE TABLE books
(
    id BIGINT PRIMARY KEY NOT NULL AUTO_INCREMENT,
    title VARCHAR(60) NOT NULL,
    category INT NOT NULL,
    CONSTRAINT books_book_categories_id_fk FOREIGN KEY (category) REFERENCES book_categories (id) ON UPDATE CASCADE
)
  ENGINE = InnoDB CHARACTER SET utf8mb4;
CREATE INDEX books_title_index ON books (title);

CREATE TABLE book_lending
(
  id BIGINT PRIMARY KEY NOT NULL AUTO_INCREMENT,
  student_id BIGINT NOT NULL,
  book_id BIGINT NOT NULL,
  rent_date DATE NOT NULL,
  return_date DATE NOT NULL,
  CONSTRAINT book_lending_student_id_fk FOREIGN KEY (student_id) REFERENCES student (id) ON UPDATE CASCADE,
  CONSTRAINT book_lending_books_id_fk FOREIGN KEY (book_id) REFERENCES books (id)
)ENGINE = InnoDB CHARACTER SET utf8mb4;

CREATE TABLE book_back
(
  id BIGINT PRIMARY KEY NOT NULL,
  return_date DATE NOT NULL,
  CONSTRAINT book_back_book_lending_id_fk FOREIGN KEY (id) REFERENCES book_lending (id)
)ENGINE = InnoDB CHARACTER SET utf8mb4;

また、サンプル用に次のデータを使う

INSERT INTO book_categories (id, name) VALUES
  (1, '文学'), (2, '数学'), (3, '物理');

INSERT INTO books (id, title, category) VALUES
    ( 1, '異邦人', 1) # 1
  , ( 2, 'モンテ・クリスト伯', 1) # 2
  , ( 3, '線形代数入門', 2) # 3
  , ( 4, '解析入門', 2) # 4
  , ( 5, 'δ-ε法', 2) # 5
  , ( 6, 'フーリエ解析', 2) # 6
  , ( 7, '統計学入門', 2) # 7
  , ( 8, '力学', 3) # 8
  , ( 9, '熱力学', 3) # 9
  , (10, '電磁気学', 3) # 10
  , (11, '量子論', 3) # 11
;

INSERT INTO student (id, last_name, first_name, admission_year) VALUES
    (1, '竹村', '央人', 2015)  # 1
  , (2, '土井', '正一', 2016)  # 2
  , (3, '高瀬', '彩世', 2017)  # 3
  , (4, '武田', '麻未', 2017)  # 4
  , (5, '松本', '麻郁', 2015)  # 5
;

INSERT INTO book_lending (id, student_id, book_id, rent_date, return_date) VALUES
    (1, 1,  4, '2017-01-13', '2017-01-20') # 1
  , (2, 1,  5, '2017-01-13', '2017-01-20') # 2
  , (3, 1,  6, '2017-01-13', '2017-01-20') # 3
  , (4, 2, 10, '2017-01-15', '2017-01-22') # 4
  , (5, 3,  1, '2017-01-16', '2017-01-23') # 5
  , (6, 5,  3, '2017-01-17', '2017-01-24') # 6
  , (7, 5,  8, '2017-01-17', '2017-01-24') # 7
  , (8, 1,  3, '2017-01-23', '2017-01-30') # 8
;

INSERT INTO book_back (id, return_date) VALUES
    (1, '2017-01-18')
  , (2, '2017-01-18')
  , (3, '2017-01-18')
  , (5, '2017-01-23')
  , (6, '2017-01-22')
  , (8, '2017-01-24')
;

クラスの出力

mavenspeedment:tool というgoalが追加されているので実行するとJavaFX製のツールが起動するので、DBのスキーマを読み取るなどする。

最終的に次のようなクラスを出力される。

src/main/generated/com/example/book/books/Books.java
src/main/generated/com/example/book/books/BooksImpl.java
src/main/generated/com/example/book/books/BooksManager.java
src/main/generated/com/example/book/books/BooksManagerImpl.java
src/main/generated/com/example/book/books/BooksSqlAdapter.java
src/main/generated/com/example/book/books/generated/GeneratedBooks.java
src/main/generated/com/example/book/books/generated/GeneratedBooksImpl.java
src/main/generated/com/example/book/books/generated/GeneratedBooksManager.java
src/main/generated/com/example/book/books/generated/GeneratedBooksManagerImpl.java
src/main/generated/com/example/book/books/generated/GeneratedBooksSqlAdapter.java
...
src/main/generated/com/example/book/generated/GeneratedSpeedmentSampleApplication.java
src/main/generated/com/example/book/generated/GeneratedSpeedmentSampleApplicationBuilder.java
src/main/generated/com/example/book/generated/GeneratedSpeedmentSampleApplicationImpl.java
src/main/generated/com/example/book/generated/GeneratedSpeedmentSampleMetadata.java
src/main/generated/com/example/book/SpeedmentSampleApplication.java
src/main/generated/com/example/book/SpeedmentSampleApplicationBuilder.java
src/main/generated/com/example/book/SpeedmentSampleApplicationImpl.java
...

一つのテーブルにつき次のクラスが作成される

  • テーブルモデルのインターフェース x 2
  • テーブルモデルの実装クラス x 2
  • Managerインターフェース(レポジトリー相当) x 2
  • Manager実装クラス x 2
  • SqlAdapterクラス x 2

それに加えて、4つのアプリケーションクラスも生成される

  • Applicationインターフェース
  • Application実装クラス
  • ApplicationBuilderクラス
  • Metadataクラス

また、スキーマ定義をjsonにして出力しているのでチームで共有する場合はこのjsonからクラスを生成できるように speedment:generate というgoalもある。


利用方法

ApplicationBuilderクラスにDBの接続情報(これはjsonに書き出されているものも利用できる)、ログの設定を入力してApplicationクラスを取得する。

SpeedmentSampleApplication app = new SpeedmentSampleApplicationBuilder()
  .withPassword("speedment")
  .withLogging(ApplicationBuilder.LogType.STREAM)
  .withLogging(ApplicationBuilder.LogType.REMOVE)
  .withLogging(ApplicationBuilder.LogType.UPDATE)
  .withLogging(ApplicationBuilder.LogType.PERSIST)
  .build();

このApplicationクラスからManagerクラスを取得して、Stream APIを使ってデータにアクセスする

単純なクエリー

まず単純な例として student テーブルから admission_year2015 のレコードを取得する。

@Test
void simpleQuery() {
  app.getOrThrow(StudentManager.class)
      .stream()
      .filter(Student.ADMISSION_YEAR.equal(2015))
      .sorted(Student.ID.comparatorNullFieldsLast())
      .collect(toList())
      .forEach(System.out::println);
}

実行すると次のように表示される。

2017-03-05T14:52:17.839Z INFO  [main] (#APPLICATION_BUILDER) - 
   ____                   _                     _     
  / ___'_ __  __  __   __| |_ __ __    __ _ __ | |    
  \___ | '_ |/  \/  \ / _  | '_ \ _ \ /  \ '_ \| |_   
   ___)| |_)| '_/ '_/| (_| | | | | | | '_/ | | |  _|  
  |____| .__|\__\\__\ \____|_| |_| |_|\__\_| |_| '_   
=======|_|======================================\__|==
   :: Speedment by Speedment, Inc.:: (v3.0.3) 

2017-03-05T14:52:17.862Z INFO  [main] (#APPLICATION_BUILDER) - Speedment (Open Source) version 3.0.3 by Speedment, Inc. Specification version 3.0
2017-03-05T14:52:17.880Z INFO  [main] (#APPLICATION_BUILDER) - Java Platform API Specification 1.8 by Oracle Corporation. Implementation Oracle Corporation 1.8.0_111 by Oracle Corporation
2017-03-05T14:52:18.571Z INFO  [main] (#APPLICATION_BUILDER) - MySQL, 5.7.17, MySQL Connector Java mysql-connector-java-5.1.40 ( Revision: 402933ef52cad9aa82624e80acbea46e3a701ce6 ), JDBC version 4.0
2017-03-05T14:52:18.631Z DEBUG [main] (#STREAM) - SELECT `id`,`last_name`,`first_name`,`admission_year` FROM `speedment-sample`.`student` WHERE (`speedment-sample`.`student`.`admission_year` = ?), values:[2015]
StudentImpl { id = 1, lastName = 竹村, firstName = 央人, admissionYear = 2015 }
StudentImpl { id = 5, lastName = 松本, firstName = 麻郁, admissionYear = 2015 }

Stream APIにて filter メソッドで呼び出された箇所が WHERE 句となってSQLが発行されるようだ。

1:Nのjoin

1:Nの関係にあるテーブルをJOINする場合は、 collect メソッドにて Collectors#groupingBy を用いる。

@Test
void joinQuery(){
  final BooksManager bm=app.getOrThrow(BooksManager.class);
  final BookCategoriesManager bcm=app.getOrThrow(BookCategoriesManager.class);
  final Map<BookCategories, List<Books>>booksGroupingByCategory=bm.stream()
      .filter(Books.CATEGORY.equal(2))
      .collect(groupingBy(bcm.finderBy(Books.CATEGORY)));
  booksGroupingByCategory.forEach((k,v) -> {
    System.out.println(String.format("category: %s", k));
    System.out.println(String.format("books: %s", v));
  });
}

実行すると次のように表示される。

2017-03-05T15:01:28.117Z INFO  [main] (#APPLICATION_BUILDER) - 
   ____                   _                     _     
  / ___'_ __  __  __   __| |_ __ __    __ _ __ | |    
  \___ | '_ |/  \/  \ / _  | '_ \ _ \ /  \ '_ \| |_   
   ___)| |_)| '_/ '_/| (_| | | | | | | '_/ | | |  _|  
  |____| .__|\__\\__\ \____|_| |_| |_|\__\_| |_| '_   
=======|_|======================================\__|==
   :: Speedment by Speedment, Inc.:: (v3.0.3) 

2017-03-05T15:01:28.130Z INFO  [main] (#APPLICATION_BUILDER) - Speedment (Open Source) version 3.0.3 by Speedment, Inc. Specification version 3.0
2017-03-05T15:01:28.132Z INFO  [main] (#APPLICATION_BUILDER) - Java Platform API Specification 1.8 by Oracle Corporation. Implementation Oracle Corporation 1.8.0_111 by Oracle Corporation
2017-03-05T15:01:28.963Z INFO  [main] (#APPLICATION_BUILDER) - MySQL, 5.7.17, MySQL Connector Java mysql-connector-java-5.1.40 ( Revision: 402933ef52cad9aa82624e80acbea46e3a701ce6 ), JDBC version 4.0
2017-03-05T15:01:29.040Z DEBUG [main] (#STREAM) - SELECT `id`,`title`,`category` FROM `speedment-sample`.`books` WHERE (`speedment-sample`.`books`.`category` = ?), values:[2]
2017-03-05T15:01:29.107Z DEBUG [main] (#STREAM) - SELECT `id`,`name` FROM `speedment-sample`.`book_categories` WHERE (`speedment-sample`.`book_categories`.`id` = ?), values:[2]
2017-03-05T15:01:29.146Z DEBUG [main] (#STREAM) - SELECT `id`,`name` FROM `speedment-sample`.`book_categories` WHERE (`speedment-sample`.`book_categories`.`id` = ?), values:[2]
2017-03-05T15:01:29.153Z DEBUG [main] (#STREAM) - SELECT `id`,`name` FROM `speedment-sample`.`book_categories` WHERE (`speedment-sample`.`book_categories`.`id` = ?), values:[2]
2017-03-05T15:01:29.159Z DEBUG [main] (#STREAM) - SELECT `id`,`name` FROM `speedment-sample`.`book_categories` WHERE (`speedment-sample`.`book_categories`.`id` = ?), values:[2]
2017-03-05T15:01:29.167Z DEBUG [main] (#STREAM) - SELECT `id`,`name` FROM `speedment-sample`.`book_categories` WHERE (`speedment-sample`.`book_categories`.`id` = ?), values:[2]
category: BookCategoriesImpl { id = 2, name = 数学 }
books: [BooksImpl { id = 3, title = 線形代数入門, category = 2 }, BooksImpl { id = 4, title = 解析入門, category = 2 }, BooksImpl { id = 5, title = δ-ε法, category = 2 }, BooksImpl { id = 6, title = フーリエ解析, category = 2 }, BooksImpl { id = 7, title = 統計学入門, category = 2 }]

JOINの結果だが、残念なことにJOINをしているわけではなく、駆動表から取得して結合する表を取得するパターンのようだ…

Kotlin ts2kt の使い方メモ

KotlinがDefinitelyTypedに対応したと聞いたので早速試してみた


概要

  • ちょっと試しただけのメモ
  • TypeScriptの型定義情報をKotlinの型定義情報に変換するKotlin公式のツール
  • d.tsが型の定義を頑張ってくれていれば、Kotlinで楽できる
  • ファイル名をそのまま出力のファイル名に使うので、複数のライブラリを使う場合は工夫が必要
  • 複数のファイルにまたがって定義されている情報がまだ取り扱えないので、どうしたものか…

インストール

npmでインストールするだけ

npm install ts2kt -g

使い方

ts2kt コマンドに変換する型定義ファイルを渡すだけ。オプションはディレクトリーを指定する d だけ。

例:axiosのktファイルを取得する

$ npm init -y
$ npm install axios --save
$ mkdir kt
$ ts2kt -d kt node_modules/axios/index.d.ts
Converting node_modules/axios/index.d.ts
Save declarations:
        kt/index.kt

これで、 kt/index.kt が出来上がる

残念なところ

いくつかサポートされていない機能がある

  • import
  • typeof
  • never

特に import が対応していないために、複数のファイルにまたがるd.tsがある場合、型情報が正確に出ていないっぽい(よくわかってない)

あと、バグなのか、それとも型定義が間違えているのかわからんけど、落ちてしまうd.tsもあった。

$ npm install @types/whatwg-fetch
$ ts2kt -d fetch node_modules/\@types/whatwg-fetch/index.d.ts
Converting node_modules/@types/whatwg-fetch/index.d.ts
ts2kt: "TypeQuery" kind unsupported yet here! (node_modules/@types/whatwg-fetch/index.d.ts:11:19 to 11:39)
ts2kt: "TupleType" kind unsupported yet here! (node_modules/@types/whatwg-fetch/index.d.ts:24:33 to 24:49)
/usr/local/lib/node_modules/ts2kt/node_modules/typescript/lib/typescript.js:5738
        return identifier.length >= 3 && identifier.charCodeAt(0) === 95 /* _ */ && identifier.charCodeAt(1) === 95 /* _ */ && identifier.charCodeAt(2) === 95 /* _ */ ? identifier.substr(1) : identifier;
                         ^

TypeError: Cannot read property 'length' of undefined
    at Object.unescapeIdentifier (/usr/local/lib/node_modules/ts2kt/node_modules/typescript/lib/typescript.js:5738:26)
    at TsClassToKt.TsClassifierToKt.visitMethodDeclaration_7vsj57$ (/usr/local/lib/node_modules/ts2kt/ts2kt.js:969:35)
    at visitNode (/usr/local/lib/node_modules/ts2kt/ts2kt.js:3939:15)
    at TsClassToKt.visitClassDeclaration_4wvyue$ (/usr/local/lib/node_modules/ts2kt/ts2kt.js:1024:7)
    at TypeScriptToKotlin.visitClassDeclaration_4wvyue$ (/usr/local/lib/node_modules/ts2kt/ts2kt.js:1393:16)
    at visitNode (/usr/local/lib/node_modules/ts2kt/ts2kt.js:3921:15)
    at /usr/local/lib/node_modules/ts2kt/ts2kt.js:3897:7
    at visitEachNode (/usr/local/lib/node_modules/ts2kt/node_modules/typescript/lib/typescript.js:8343:30)
    at Object.forEachChild (/usr/local/lib/node_modules/ts2kt/node_modules/typescript/lib/typescript.js:8503:24)
    at forEachChild_0 (/usr/local/lib/node_modules/ts2kt/ts2kt.js:3901:24)

ArtifactoryのDockerイメージとgradleでのartifactのデプロイのメモ

少々こまったことがあったので、Artifactoryを調べたので、そのメモ。なお、数年前も同じことを調べたという記憶があるのだが、メモを残していなかったのであらたに調べることになった…


書かれていること

  • ArtifactoryのイメージをビルドするDockerfile
  • プロジェクトの成果物をpublishする場合のビルドファイル
  • Artifactoryにpublishしたjarファイルをプロジェクトで利用する場合のビルドファイル

ArtifactoryのイメージをビルドするDockerfile

作りたいArtifactoryのDockerイメージは次のようなもの

  • なるべく小さいイメージにしたい
  • データベースにデフォルトで有効になっているderbyではなく、mysqlを利用する
  • ポート番号をデフォルトの8081は使いたくないので5000にする
  • データベースのスキーマ/ユーザー/パスワードは環境変数によって設定可能にする

Artifactoryではデータベースの接続設定はファイルから読み込む形になっているが、これを起動時の環境変数により修正するためDockerfileだけではなく(もちろんDockerfileで完結させることはできる)、別途シェル(entry-point.sh)を用意した。

Dockerfile

FROM openjdk:8u111-jre-alpine

ENV ARTIFACTORY_VERSION 5.1.0
ENV ARTIFACTORY_HOME    /artifactory-oss-${ARTIFACTORY_VERSION}

ENV ARTIFACTORY_PORT    5000

ADD entry-point.sh /entry-point.sh

RUN apk --no-cache add curl bash && \
  curl -SL "https://bintray.com/jfrog/artifactory/download_file?file_path=jfrog-artifactory-oss-${ARTIFACTORY_VERSION}.zip" > artifactory.zip && \
  unzip -q artifactory.zip && \
  rm artifactory.zip && \
  curl -SL "https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-5.1.40.zip" > mysql-connector.zip && \
  unzip -q mysql-connector.zip && \
  rm mysql-connector.zip && \
  apk --no-cache del curl && \
  mv mysql-connector-java-5.1.40/mysql-connector-java-5.1.40-bin.jar ${ARTIFACTORY_HOME}/tomcat/lib/ && \
  rm -rf mysql-connector-java-5.1.40/ && \
  mkdir ${ARTIFACTORY_HOME}/data && \
  mv ${ARTIFACTORY_HOME}/tomcat/conf/server.xml ${ARTIFACTORY_HOME}/tomcat/conf/tmp.xml && \
  cat ${ARTIFACTORY_HOME}/tomcat/conf/tmp.xml | \
    sed -e "s/8081/${ARTIFACTORY_PORT}/g" > \
    ${ARTIFACTORY_HOME}/tomcat/conf/server.xml && \
  rm ${ARTIFACTORY_HOME}/tomcat/conf/tmp.xml && \
  chmod +x /entry-point.sh

VOLUME ["${ARTIFACTORY_HOME}/data"]
EXPOSE ${ARTIFACTORY_PORT}

CMD     ["/entry-point.sh"]

entry-point.sh

#!/bin/bash

set -eu

if [ ! -v ARTIFACTORY_HOME ]; then
    echo ARTIFACTORY_HOME is not set
    exit 1
fi

if [ ! -v DB_ADDRESS ]; then
  DB_ADDRESS=db
fi

if [ ! -v DB_PORT ]; then
    DB_PORT=3306
fi

provider_types=("filesystem" "fullDb" "cachedFS")

if [ ! -v PROVIDER_TYPE ]; then
  PROVIDER_TYPE=filesystem
elif [[ ! ${provider_types} =~ (^|[[:space:]])${PROVIDER_TYPE}([[:space:]]|$) ]]; then
  PROVIDER_TYPE=filesystem
fi

if [ ! -v PROVIDER_SIZE ]; then
    PROVIDER_SIZE=1GB
fi

cat << EOF > ${ARTIFACTORY_HOME}/etc/db.properties
type=mysql
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://${DB_ADDRESS}:${DB_PORT}/${DB_NAME}?characterEncoding=UTF-8&elideSetAutoCommits=true
username=${DB_USER}
password=${DB_PASSWORD}
binary.provider.type=${PROVIDER_TYPE}
binary.provider.cache.maxSize=${PROVIDER_SIZE}
EOF

${ARTIFACTORY_HOME}/bin/artifactory.sh

あとは次のような docker-compose.yml を作って起動すればよい

version: '2'
services:
  db:
    image: mysql:5.7
    environment:
      - MYSQL_DATABASE=artifactory
      - MYSQL_USER=user
      - MYSQL_PASSWORD=password
      - MYSQL_RANDOM_ROOT_PASSWORD=yes
    ports:
      - 3306:3306
  artifactory:
    build: ./artifactory
    environment:
      - DB_NAME=artifactory
      - DB_USER=user
      - DB_PASSWORD=password
    volumes:
      - ./artifactory/data:/artifactory-oss-5.1.0/data
    depends_on:
      - db
    ports:
      - 5000:5000

そうすると、ローカルで実行した場合は http://localhost:5000/artifactory にアクセスすると Artifactory を開くことができる。なお、デフォルトのユーザーとパスワードがadmin/password。とりあえず、デフォルトのユーザーは使えなくして、別のユーザーを作成する。


プロジェクトの成果物をpublishする

次のようにレポジトリーにあげたい

  • Artifactoryのkey gradle-dev-local
  • あげるファイル
    • pom
    • 成果物のjar
    • ソースのjar
    • javadocのjar

build.gradle は次のとおり

buildscript {
  repositories {
    jcenter()
  }
  dependencies {
    classpath "org.jfrog.buildinfo:build-info-extractor-gradle:4+"
  }
}

apply plugin: 'java'
apply plugin: 'maven-publish'
apply plugin: "com.jfrog.artifactory"

group = 'com.example'
version = '0.1'

repositories {
  mavenCentral()
}

dependencies {
  compileOnly     'org.jetbrains:annotations:15.0'
  compileOnly     'org.projectlombok:lombok:1.16.14'
  compile         'javax.inject:javax.inject:1'
}

task sourceJar(type: Jar) {
  from sourceSets.main.allSource
  classifier = 'sources'
}

task javadocJar(type: Jar) {
  from tasks.javadoc
  classifier = 'javadoc'
}

artifactory {
  contextUrl = 'http://localhost:5000/artifactory'
  publish {
    repository {
      repoKey = 'gradle-dev-local'
      username = "user"
      password = "password"
      maven = true
    }
    defaults {
      publications('javaProject')
      publishArtifacts = true
      publishPom = true
    }
  }
  resolve {
    repository {
      repoKey = 'gradle-dev'
      username = "user"
      password = "password"
      maven = true
    }
  }
}

publishing {
  publications {
    javaProject(MavenPublication) {
      from components.java
      artifacts = [sourceJar, javadocJar, jar]
    }
  }
}

これによって、artifactoryPublish タスクによって Artifactoryにpublishできるようになる。なお、ポイントとしては次のとおり

  • ArtifactoryにてRepository BrowserのSet Me Upからビルドファイルの雛形を吐き出してくれるが、 maven-publish の記述がないので、そのままでは何もできない。またusername/password用にGStringで何かやってくれているプロパティファイルを吐き出してくれるが、残念ながらプロパティファイルはそのまま文字列になるので何にもならない。
  • maven-publish プラグインにてpublishの準備が整えられたartifactを artifactoryのpublicationsに指定する(上記の javaProject と書いてあるやつ)

なお、こちらのページを参考にした、というかほとんどコピーした…

kinjouj.github.io


Artifactoryにpublishしたjarファイルをプロジェクトで利用する

次のようなビルドファイルを用いる

repositories {
  maven {
    url "http://localhost:5000/artifactory/gradle-dev-local"
  }
}

dependencies {
  compile 'com.example:sample-java-project:0.1'
}

以上

eclipseがメインのIDEに指定されているプロジェクトでIntelliJを使って開発する

職場のプロジェクトがメインで指定するIDEeclipseですが、IntelliJ IDEAが大好きなのでIntelliJ IDEAで仕事しています。

するとどうしても問題が出てきます。

  • importの順番が異なるために無駄なdiffが出てくる
  • コードスタイルが異なるのでレビューしづらい
  • インスペクションのレベルが違うのでレビューしづらい/チェックしづらい

importの順番

importの順番は気合で直します(嘘)

eclipseからコードスタイルフォーマットを出力してIntelliJで取り込んでもImportの順番は出力できないようです。

したがって下のQiitaの記事を参考に Code Style > Java > Impors > Import Layout にてImportの順番をeclipseのそれに合わせます。

qiita.com

しかし、時折ですが、eclipseで書いたコードのimport順がおかしな場合もあります。そのようなイレギュラーケースは気合で直していたりします。

コードスタイル

上記の通りeclipseからコードフォーマット設定をxmlで出力して、取り込むことで対応します。しかしeclipseのデフォルト設定で開発している場合などはxmlの情報が少なくなり、かなり変なスタイルが取り込まれることもあったり、そもそもコードスタイルをxmlで出力していないということもあります(.settings/org.eclipse.jdt.core.prefs で管理している場合など)。

この場合はeclipseのフォーマッタをターミナルから起動して修正します。

$ ~/path/to/Eclipse.app/Contents/MacOS/eclipse -application org.eclipse.jdt.core.JavaCodeFormatter -verbose -config ~/path/to/setting/org.eclipse.jdt.core.prefs ~/path/to/project/src/main/java/com/example/Foo.java

なお、以前はこれをgit statusと組み合わせてgit commitのフックで実行していましたが、IntelliJのchangelistが壊れるのでやめて、次のスクリプトを利用してpushの前に実行するようにしています。

#!/usr/bin/env bash

project=~/path/to/project
eclipse=~/path/to/Eclipse.app/Contents/MacOS/eclipse

cd ${project}

head=`git log --oneline --decorate | grep HEAD | awk '{printf $1}'`
last=`git log --oneline -decorate` | grep origin | awk '{printf $1}'`
java_files=`git diff --name-only ${head} ${last} | awk -v dir=${project} '{print dir"/"$1}' awk -F\| 'system("test -f " $1)==0 { print $1 }'`

${eclipse} -application org.eclipse.jdt.core.JavaCodeFormatter -verbose -config ~/path/to/setting/org.eclipse.jdt.core.prefs ${java_files}

git add ${java_files}
git commit -m 'apply eclipse formatter'

コードインスペクション

これが一番困っています。eclipseIntelliJで警告になるレベルが異なる(IntelliJでは設定可能)ようです。下手に警告がちらつくと重要な警告を発見できなかったり、レビューに集中できなかったりと弊害が大きいです。

いろいろと方法を考えたのですが、とりあえずecjでコンパイルするのが一番よさそうです。

apply plugin: 'java'
configurations {
  eclipse
}
dependencies {
  eclipse 'org.eclipse.jdt.core.compiler:ecj:4.4'
  // 他省略
}

task ecj(type: JavaExec) {
    main = 'org.eclipse.jdt.internal.compiler.batch.Main'
    classpath configurations.eclipse.asPath
    args '-encoding', 'utf8'
    args '-source', '8'
    args '-d', "$buildDir/ecj"
    args '-target', '8'
    args '-cp', configurations.compileClasspath.asPath
    args sourceSets.main.allJava
}

このようなタスクを記述した ecj.gradle ファイルを作って、 ./gradlew -b ecj.gradle ecj を起動することで警告を探してeclipseに合わせたコードを記述します。

出力例

15. WARNING in /path/to/project/src/main/java/org/mikeneck/util/Last.java (at line 143)
        .<Last<T>>map(Candidate::new)
                      ^^^^^^^^^^^^^^
Type safety: The constructor Candidate(Object) belongs to the raw type Candidate. References to generic type Candidate<T> should be parameterized
----------
16. WARNING in /path/to/project/src/main/java/org/mikeneck/util/Last.java (at line 147)
        @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Unsupported @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
----------

例えば、上の出力例ではIntelliJでは Optional をフィールドまたはパラメーターに利用すると警告が出てくるので @SuppressWarnings("OptionalUsedAsFieldOrParameterType") をつけますが、eclipseでは OptionalUsedAsFieldOrParameterType に対応していないので警告になっています(Optional をフィールドまたはパラメーターに使っても警告が出ないのだろうか…詳しい人教えて…)。またeclipseでは推論可能な型パラメーターを省略すると警告が出てしまうようです(Foo<T> のコンストラクタを Foo::new と書くと警告が出るが、 Foo<T>::new と書くと警告が出ない。一方IntelliJでは推論可能な型パラメーターの省略を推奨している)。おそらく言語仕様で曖昧に解釈できる箇所をecjでは安全側に倒しているというところでしょうか…。

以上