Tagged: maven
Basic Java EE with Apache Maven
some-parent
다른 모듈들에서 사용할 dependency/plugin 등을 정리하는 모듈이다. java-ee-api
와 derby
, 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-plugin
과 hibernate-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-mode
를 CALLBACK
으로 설정했으므로 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>
기본적으로 derby
와 eclipselink
를 activeByDefault
로 설정하였다. 다음과 같이 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
에서 A
의 version
값을 사용하고자 할 때 어떻게 하면 될까?
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를 이용하여 sonar
와 sonar-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-ui를 src/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>
archiving all artifacts belong to a specific groupId from local repository
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>