Tagged: maven

Basic Java EE with Apache Maven


some-parent

다른 모듈들에서 사용할 dependency/plugin 등을 정리하는 모듈이다. java-ee-apiderby, h2 등의 메모리 DB, 그리고, eclipselink, hibernate-core, openejb-core 등이 정의되었다.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>some.group</groupId>
  <artifactId>some-parent</artifactId>
  <version>0.1.0-SNAPSHOT</version>
  <packaging>pom</packaging>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <version.org.apache.tomee>7.0.2</version.org.apache.tomee>
    <version.org.eclipse.persistence>2.6.4</version.org.eclipse.persistence>
    <version.org.glassfish.jersey>2.25</version.org.glassfish.jersey>
    <version.org.glassfish.jersey.security>${version.org.glassfish.jersey}</version.org.glassfish.jersey.security>
    <version.org.glassfish.jersey.test-framework>${version.org.glassfish.jersey}</version.org.glassfish.jersey.test-framework>
    <version.org.wildfly.swarm>2017.1.1</version.org.wildfly.swarm>
  </properties>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.1.8</version>
      </dependency>
      <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>1.4.193</version>
      </dependency>
      <dependency>
        <groupId>javax</groupId>
        <artifactId>javaee-api</artifactId>
        <version>7.0</version>
      </dependency>
      <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.5</version>
      </dependency>
      <dependency>
        <groupId>org.apache.derby</groupId>
        <artifactId>derby</artifactId>
        <version>10.13.1.1</version>
      </dependency>
      <dependency>
        <groupId>org.apache.tomee</groupId>
        <artifactId>openejb-core</artifactId>
        <version>${version.org.apache.tomee}</version>
      </dependency>
      <dependency>
        <groupId>org.eclipse.persistence</groupId>
        <artifactId>eclipselink</artifactId>
        <version>${version.org.eclipse.persistence}</version>
      </dependency>
      <dependency>
        <groupId>org.gavaghan</groupId>
        <artifactId>geodesy</artifactId>
        <version>1.1.3</version>
      </dependency>
      <dependency>
        <groupId>org.glassfish</groupId>
        <artifactId>javax.el</artifactId>
        <version>3.0.1-b08</version>
      </dependency>
      <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>5.2.6.Final</version>
      </dependency>
      <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>5.4.0.CR1</version>
      </dependency>
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.22</version>
      </dependency>
      <dependency>
        <groupId>org.testng</groupId>
        <artifactId>testng</artifactId>
        <version>6.10</version>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-failsafe-plugin</artifactId>
          <version>2.19.1</version>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-javadoc-plugin</artifactId>
          <version>2.10.4</version>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-pmd-plugin</artifactId>
          <version>3.7</version>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-project-info-reports-plugin</artifactId>
          <version>2.9</version>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.19.1</version>
        </plugin>
        <plugin>
          <groupId>org.bsc.maven</groupId>
          <artifactId>maven-processor-plugin</artifactId>
          <version>3.2.0</version>
        </plugin>
        <plugin>
          <groupId>org.codehaus.mojo</groupId>
          <artifactId>cobertura-maven-plugin</artifactId>
          <version>2.7</version>
        </plugin>
        <plugin>
          <groupId>org.jacoco</groupId>
          <artifactId>jacoco-maven-plugin</artifactId>
          <version>0.7.8</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

some-entities

Persistence-Unit 에 해당하는 모듈이다. 중요한 점으로 이 묘듈은 persistence.xml 파일을 포함하지 않는다는 것이다. persistence.xml 파일은 최종 application 에서 정의한다.

javaee-api

우선적으로 필요한 의존성은 javaee-api이다. provided scope로 선언한다.

  ...
  <groupId>some</groupId>
  <artifactId>some-entities</artifactId>
  <version>0.1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  ...

  <dependencies>
    <dependency>
      <groupId>javax</groupId>
      <artifactId>javaee-api</artifactId>
      <scope>provided</scope>
    </dependency>
  </dependencies>

  ...

</project>

BaseEntity.java

필요에 따라서 다른 entity들이 상속할 수 있는 기본클래스를 선언할 수도 있다.

@MappedSuperclass
public abstract class BaseEntity implements Serializable {

    // ...

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = COLUMN_NAME_ID, nullable = false, updatable = false)
    @XmlAttribute
    private Long id;

    // ...
}

metamodel generation

JPA Metamodel 들을 생성한다. maven-processor-pluginhibernate-jpamodelgen을 사용한다.

  ...
  <build>
    ...
    <plugins>
      ...
      <plugin>
        <groupId>org.bsc.maven</groupId>
        <artifactId>maven-processor-plugin</artifactId>
        <executions>
          <execution>
            <id>generate-metamodels</id>
            <goals>
              <goal>process</goal>
            </goals>
            <phase>generate-sources</phase>
            <configuration>
              <processors>
                <processor>org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor</processor>
              </processors>
            </configuration>
          </execution>
        </executions>
        <dependencies>
          <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-jpamodelgen</artifactId>
            <version>5.2.6.Final</version>
          </dependency>
        </dependencies>
      </plugin>
      ...
    </plugins>
    ...
  </build>
  ...

src/test/resources/META-INF/persistence.xml

단위시험을 위해 in-memory database를 이용하는 persistence.xml 파일을 준비하자.

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1"
             xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
  <persistence-unit name="somePU" transaction-type="RESOURCE_LOCAL">
    <provider>${provider}</provider>
    <class>com.mycompany.some.Some</class>
    ...
    <validation-mode>CALLBACK</validation-mode>
    <properties>
      <property name="javax.persistence.jdbc.driver" value="${javax.persistence.jdbc.driver}"/>
      <property name="javax.persistence.jdbc.url" value="${javax.persistence.jdbc.url}"/>
      <property name="javax.persistence.schema-generation.database.action" value="create"/>
    </properties>
  </persistence-unit>
</persistence>

validation-modeCALLBACK으로 설정했으므로 validation framework을 추가해 줘야 한다.

    ...
    <dependency>
      <groupId>org.glassfish</groupId>
      <artifactId>javax.el</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-validator</artifactId>
      <scope>test</scope>
    </dependency>
    ...

자 이제 h2, derby 등의 database와 eclipselinke, hibernate-core등의 JPA provider 등을 선택적으로 사용할 수 있는 profile 들을 작성한다.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  ...
  <profiles>
    <profile>
      <id>derby</id>
      <activation>
        <activeByDefault>true</activeByDefault>
      </activation>
      <properties>
        <javax.persistence.jdbc.driver>org.apache.derby.jdbc.EmbeddedDriver</javax.persistence.jdbc.driver>
        <javax.persistence.jdbc.url>jdbc:derby:memory:someDB;create=true</javax.persistence.jdbc.url>
      </properties>
      <dependencies>
        <dependency>
          <groupId>org.apache.derby</groupId>
          <artifactId>derby</artifactId>
          <scope>test</scope>
        </dependency>
      </dependencies>
    </profile>
    <profile>
      <id>h2</id>
      <properties>
        <javax.persistence.jdbc.driver>org.h2.Driver</javax.persistence.jdbc.driver>
        <javax.persistence.jdbc.url>jdbc:h2:mem:someDB</javax.persistence.jdbc.url>
      </properties>
      <dependencies>
        <dependency>
          <groupId>com.h2database</groupId>
          <artifactId>h2</artifactId>
          <scope>test</scope>
        </dependency>
      </dependencies>
    </profile>
    <profile>
      <id>eclipselink</id>
      <activation>
        <activeByDefault>true</activeByDefault>
      </activation>
      <properties>
        <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
      </properties>
      <dependencies>
        <dependency>
          <groupId>org.eclipse.persistence</groupId>
          <artifactId>eclipselink</artifactId>
          <scope>test</scope>
        </dependency>
      </dependencies>
    </profile>
    <profile>
      <id>hibernate</id>
      <properties>
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
      </properties>
      <dependencies>
        <dependency>
          <groupId>org.hibernate</groupId>
          <artifactId>hibernate-core</artifactId>
          <scope>test</scope>
        </dependency>
      </dependencies>
    </profile>
  </profiles>
</project>

기본적으로 derbyeclipselinkactiveByDefault 로 설정하였다. 다음과 같이 profile을 바꿔가면서 시험할 수 있다.

$ mvn -Ph2,eclipselink test
$ mvn -Pderby,hibernate test

some-services

some-resources

how to exclude generated jpa metamodels from being javadoc-ed


<plugins>
  <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-javadoc-plugin</artifactId>
    <configuration>
      <sourceFileExcludes>
        <exclude>**/**_.java</exclude>
      </sourceFileExcludes>
    </configuration>
  </plugin>
</plugins>

using transitive-dependency’s version


problem

다음과 같은 dependency-tree 가 있다고 하자.

A <- B <- C

C 에서 Aversion 값을 사용하고자 할 때 어떻게 하면 될까?

solution

add direct (redundant) dependency

// C/pom.xml
<properties>
  <a.version>x.y.z</a.version>
</properties>
<dependencies>
  <dependency>
    <artifactId>A</artifactId>
    <version>${a.vresion}</version>
    <scope>compile</scope>
  </dependency>
  <dependency>
    <artifactId>B</artifactId>
    <version>x.y.z</version>
    <scope>compile</scope>
  </dependency>
</dependencies>

Now we can use the value of ${a.version} in other files.

A's version is ${a.version}.

enforce dependency convergence

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-enforcer-plugin</artifactId>
  <executions>
    <execution>
      <id>enforce</id>
      <configuration>
        <rules>
           <dependencyConvergence/>
        </rules>
      </configuration>
      <goals>
        <goal>enforce</goal>
      </goals>
    </execution>
  </executions>
</plugin>

profiling different implementations for testing


I’m writing a small library which used JAX-RS API.

<dependency>
  <groupId>javax.ws.rs</groupId>
    <artifactId>javax.ws.rs-api</artifactId>
    <version>2.0.1</version>
    <scope>provided</scope>
</dependency>

Profiles can help for testing the code against each implementation.

<profile>
  <id>jersey</id>
  <activation>
    <activeByDefault>true</activeByDefault>
  </activation>
  <dependencies>
    <dependency>
      <groupId>org.glassfish.jersey.core</groupId>
      <artifactId>jersey-client</artifactId>
      <version>2.23.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</profile>    
<profile>
  <id>cxf</id>
  <dependencies>
    <dependency>
      <groupId>org.apache.cxf</groupId>
      <artifactId>cxf-rt-rs-client</artifactId>
      <version>3.1.6</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</profile>    
<profile>
  <id>resteasy</id>
  <dependencies>
    <dependency>
      <groupId>org.jboss.resteasy</groupId>
      <artifactId>resteasy-client</artifactId>
      <version>3.0.17.Final</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</profile>

Test against each implementation like this.

$ mvn -Pjersey verify
$ mvn -Pcxf verify
$ mvn -Presteasy verify

lombok with maven


disclaimer

I don’t use lombok. And I won’t use it as long as I don’t have to.

references

sources

https://github.com/jinahya/lombok-with-maven-example

lombok

lombok을 provided scope 로 추가하자.

<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.16.8</version>
  <scope>provided</scope>
</dependency>

클래스 파일 하나를 다음과 같이 추가하였다.

public class Person {

    @lombok.Getter
    @lombok.Setter
    private String name;

    @lombok.Getter
    @lombok.Setter
    private int age;
}

컴파일 후 클래스파일을 살펴보면 getter와 setter가 생성되어 있음을 확인할 수 있다.

$ mvn clean compile
...
$ javap target/classes/com/github/jinahya/example/Person.class
Compiled from “Person.java”
public class com.github.jinahya.example.Person {
  public com.github.jinahya.example.Person();
  public java.lang.String getName();
  public void setName(java.lang.String);
  public int getAge();
  public void setAge(int);
}
$

delombok

pom.xml에 다음과 같이 plugin을 추가하자.

<plugin>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok-maven-plugin</artifactId>
  <version>1.16.8.0</version>
  <executions>
    <execution>
      <phase>generate-sources</phase>
      <goals>
        <goal>delombok</goal>
      </goals>
    </execution>
  </executions>
</plugin>

src/main/lombok폴더에 다음과 같은 파일을 추가하였다.

public class Group {

    @lombok.Getter(lazy = true)
    private final List<Person> people = new ArrayList<>();
}

빌드 후 Group.class 파일을 de-compile 해 보면 다음과 같은 내용을 확인할 수 있다.

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

public class Group
{
  private final AtomicReference<Object> people = new AtomicReference();

  public List<Person> getPeople()
  {
    Object value = this.people.get();
    if (value == null) {
      synchronized (this.people)
      {
        value = this.people.get();
        if (value == null)
        {
          List<Person> actualValue = new ArrayList();
          value = actualValue == null ? this.people : actualValue;
          this.people.set(value);
        }
      }
    }
    return (List)(value == this.people ? null : value);
  }
}

maven-release-plugin and git submodules


References

Problem

src/main/java 아래에 특정 폴더가 submodule 로 처리된 프로젝트를 평소와 같이 릴리즈 하였다.

$ git checkout -b release/test
$ mvn release:prepare release:perform
...
[INFO] Nothing to compile - all classes are up to date
...
$ 

target/checkout 폴더에서 submodule이 init 되지 않은 채 그대로 처리된다.

Solution

위 링크에서 제시된 해결책을 사용하여 해결하였다.

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>exec-maven-plugin</artifactId>
  <inherited>false</inherited> <!-- only execute these in the parent -->
  <executions>
    <execution>
      <id>git submodule update</id>
      <phase>initialize</phase>
      <configuration>
        <executable>git</executable>
        <arguments>
          <argument>submodule</argument>
          <argument>update</argument>
          <argument>--init</argument>
          <argument>--recursive</argument>
        </arguments>
      </configuration>
      <goals>
        <goal>exec</goal>
      </goals>
    </execution>
  </executions>
</plugin>

Analyzing with SonarQube in OS X


Qube](http://www.sonarqube.org/)를 별도의 서버에 설치하지 않고도 사용하는 방법이다.

Abstract

Installing SonarQube via Homebrew

Homebrew를 이용하여 sonarsonar-runner를 설치하자.

$ brew install sonar sonar-runner
...ass...ignorant...
$

Starting SonarQube

SonarQube를 실행하자.

$ sonar start
Starting SonarQube...
Started SonarQube.
$

http://localhost:9000 에서 SonarQube가 동작하는 것을 확인할 수 있다.

Analyzing Project with SonarQube

Apache Maven 프로젝트를 분석해보자.

$ ls -F
pom.xml     src/        target/
$ mvn org.sonarsource.scanner.maven:sonar-maven-plugin:3.0.1:sonar
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 9.426 s
[INFO] Finished at: 2016-03-16T14:18:42+09:00
[INFO] Final Memory: 16M/382M
[INFO] ------------------------------------------------------------------------
$

다시 http://localhost:9000 에 가 보면 분석된 결과를 확인할 수 있다.

Stopping SonarQube

분석작업이 끝나면 SonarQube를 종료하자.

$ sonar stop
Stopping SonarQube...
Stopped SonarQube.
$

Swagger-Core JAX-RS Project Setup (KOR)


본 문서는 Swagger Core JAX RS Project Setup 1.5.X을 적용하면서 얻은 경험을 기술합니다.
원본의 내용과는 다른 부분이 있습니다.

references

dependency

swagger-jersey2-jaxrs가 아닌 swagger-jarx를 추가한다.

<dependency>
  <groupId>io.swagger</groupId>
  <!--artifactId>swagger-jersey2-jaxrs</artifactId-->
  <artifactId>swagger-jaxrs</artifactId>
  <version>1.5.4</version>
  <scope>compile</scope>
</dependency>

WorldApplication

이 부분은 어쩔 수 없이 모든 리소스 클래스와 swagger와 관련된 클래스 두개를 추가했다.

@ApplicationPath("/api")
public class WorldApplication extends Application {
    @Override
    public Set<Class<?>> getClasses() {
        final Set<Class<?>> classes = new HashSet();
        // add my own resource classes here
        classes.add(ApiListingResource.class);
        classes.add(SwaggerSerializers.class);
        return classes;
    }
}

BeanConfiguration

Target environment를 구분하기 위해 다음과 같이 json파일을 하나 생성하고 Jackson을 통해 읽어드린다.

{
  "basePath": "${swagger.config.basePath}",
  "description": "description",
  "host": "${swagger.config.host}",
  "resourcePackage": "com.github.jinahya.mysql.world.ws.rs",
  "scan": true,
  "schemes": [${swagger.config.schemes}],
  "version": "${api.version}",
  "info": {
    "title": "MySQL World Database API"
  }
}
public WorldApplication() {
    super();
//        BeanConfig beanConfig = new BeanConfig();
//        beanConfig.setTitle("world");
//        beanConfig.setVersion("1.0.0");
//        beanConfig.setSchemes(new String[]{"http"});
//        beanConfig.setHost("localhost:8080");
//        beanConfig.setBasePath("world/api");
//        beanConfig.setResourcePackage("com.github.jinahya.mysql.world.ws.rs");
//        beanConfig.setScan(true);
    final ObjectMapper mapper = new ObjectMapper();
    try {
        mapper.readValue(
            getClass().getResource("/bean-config.json"), BeanConfig.class);
    } catch (final IOException ioe) {
        ioe.printStackTrace(System.err);
    }
}

Swagger-UI

swagger-uisrc/main/webapp 아래에 복사하고 다음과 같이 filtering를 준비한다.

<!DOCTYPE html>
<html>
  <head>
    <script type="text/javascript">
      $(function () {
        var url = window.location.search.match(/url=([^&]+)/);
        if (url && url.length > 1) {
          url = decodeURIComponent(url[1]);
        } else {
          url = "${app.contextUrl}/swagger.json";
        }
    </script>
  </head>
</html>

Hiding dependencies by Shading and ProGuarding


Let’s say your library depends on commons-lang and you don’t want them exposed.

make it optional

<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-lang3</artifactId>
  <scope>compile</scope>
  <optional>true</optional>
</dependency>

shade it

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-shade-plugin</artifactId>
  <version>2.4.2</version>
  <executions>
    <execution>
      <phase>package</phase>
      <goals>
        <goal>shade</goal>
      </goals>
      <configuration>
        <relocations>
          <relocation>
            <pattern>org.apache.commons.lang3</pattern>
            <shadedPattern>your.main.commons.lang3</shadedPattern>
          </relocation>
        </relocations>
      </configuration>
    </execution>
  </executions>
</plugin>

proguard it

<plugin>
  <groupId>com.github.wvengen</groupId>
  <artifactId>proguard-maven-plugin</artifactId>
  <executions>
    <execution>
      <id>proguard</id>
      <phase>package</phase>
      <goals>
        <goal>proguard</goal>
      </goals>
    </execution>
  </executions>
  <configuration>
    <addMavenDescriptor>true</addMavenDescriptor>
    <includeDependency>flase</includeDependency>
    <options>
      <option>
        -dontobfuscate
        -dontoptimize
        -keepattributes *Annotation*
        -keepattributes Signature
        -keepattributes InnerClasses
        -keep class your.main.* { *; }
      </option>
    </options>
    <libs>
      <lib>${java.bootstrap.classes}</lib>
      <lib>${java.cryptographic.extension.classes}</lib>
      <lib>${java.secure.socket.extension.classes}</lib>
    </libs>
  </configuration>
  <dependencies>
    <dependency>
      <groupId>net.sf.proguard</groupId>
      <artifactId>proguard-base</artifactId>
      <version>5.2.1</version>
      <scope>runtime</scope>
    </dependency>
  </dependencies>
</plugin>