Category: Java EE

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

PBKDF2 with Java


references

a general method

Following is a general method for generating PBKDF2 key in standard JDK.

byte[] pbkdf2(final char[] password, final byte[] salt,
              final int iterationCount, final int keyLength) {

    try {
        final SecretKeyFactory secretKeyFactory =
            SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        final KeySpec keySpec = new PBEKeySpec(
            password, salt, iterationCount, keyLength);
        try {
            final SecretKey secretKey =
                secretKeyFactory.generateSecret(keySpec);
            return secretKey.getEncoded();
        } catch (InvalidKeySpecException ikse) {
            throw new RuntimeException(ikse);
        }
    } catch (NoSuchAlgorithmException nsae) {
        throw new RuntimeException(nsae);
    }
}

And here comes a simplified version for 7.

byte[] pbkdf2(final char[] password, final byte[] salt,
              final int iterationCount, final int keyLength) {

    try {
        return SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
            .generateSecret(new PBEKeySpec(password, salt, iterationCount,
                                           keyLength))
            .getEncoded();
    } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
        throw new RuntimeException(e);
    }
}

password in byte[]

Maybe the password is already in a hashed or encoded form of byte[]. We can convert it to char[] like this.

char[] cassword(final byte[] bassword) {
    final char[] cassword = new char[bassword.length];
    for (int i = 0; i < cassword.length; i++) {
        cassword[i] = (char) (bassword[i] & 0xFF);
    }
    return cassword;
}

Basic RESTful Webapp


Here comes a simple web application for demonstrating the RESTFul web APIs.

Source

svn

$ svn co http://jinahya.googlecode.com/svn/trunk\
> /com.googlecode.jinahya.test/basic-restful-webapp
...
Checked out revision n.

$

mvn

$ cd basic-restful-webapp
$ mvn clean package embedded-glassfish:run
...
Hit ENTER to redeploy, X to exit

Now you can access to resources under http://localhost:58080/basic-restful-webapp/.

Domain Objects

Item.java

@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(namespace = "http://jinahya.googlecode.com/test")
@XmlType(propOrder = {"name", "stock"})
public class Item implements Serializable {


    private static final long serialVersionUID = -3104855615755133457L;


    private static final AtomicLong ATOMIC_ID = new AtomicLong();


    public static Item newInstance(final String name, final int stock) {

        final Item instance = new Item();

        instance.name = name;
        instance.stock = stock;

        return instance;
    }


    public static Item putCreatedAtAndId(final Item item) {

        item.createdAt = new Date();
        item.id = ATOMIC_ID.getAndIncrement();

        return item;
    }


    protected Item() {
        super();
    }


    @XmlAttribute
    public Date getCreatedAt() {
        return createdAt;
    }


    @XmlAttribute
    public Date getUpdatedAt() {
        return updatedAt;
    }


    @XmlAttribute
    public Long getId() {
        return id;
    }


    private Date createdAt;


    protected Date updatedAt;


    private Long id;


    @XmlElement(required = true, nillable = true)
    protected String name;


    @XmlElement(required = true, nillable = true)
    protected int stock;


}

Items.java

@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(namespace = "http://jinahya.googlecode.com/test")
public class Items implements Serializable {


    private static final long serialVersionUID = 5775071328874654134L;


    public Collection<Item> getItems() {

        if (items == null) {
            items = new ArrayList<>();
        }

        return items;
    }


    @XmlElement(name = "item")
    private Collection<Item> items;


}

Business Facade

ItemTable.java

public class ItemFacade {


    private static final long serialVersionUID = 5775071328874654134L;


    private static class InstanceHolder {


        private static final ItemFacade INSTANCE = new ItemFacade();


        private InstanceHolder() {
            super();
        }


    }


    public static ItemFacade getInstance() {
        return InstanceHolder.INSTANCE;
    }


    private ItemFacade() {
        super();
    }


    public void insert(final Item... items) {

        for (Item item : items) {
            Item.putCreatedAtAndId(item);
            tuples.put(item.getId(), item);
        }
    }


    public Item select(final long id) {

        return tuples.get(id);
    }


    public Collection<Item> selectAll() {
        return tuples.values();
    }


    public boolean update(final long id, final Item newItem) {

        if (!tuples.containsKey(id)) {
            return false;
        }

        final Item oldItem = tuples.get(id);
        if (oldItem == null) {
            return false;
        }

        oldItem.updatedAt = new Date();
        oldItem.name = newItem.name;
        oldItem.stock = newItem.stock;

        return true;
    }


    public Item delete(final long id) {
        return tuples.remove(id);
    }


    public void deleteAll() {
        tuples.clear();
    }


    private final Map<Long, Item> tuples = new HashMap<>();


}

Webservice Resource

ItemsResource.java

@Path("/items")
public class ItemsResource {


    @GET
    @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public Items read() {

        final Items items = new Items();

        items.getItems().addAll(ItemFacade.getInstance().selectAll());

        return items;
    }


    @DELETE
    public void delete() {

        ItemFacade.getInstance().deleteAll();
    }


    @POST
    @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public Response createItem(@Context final UriInfo info,
                               final Item item) {

        ItemFacade.getInstance().insert(item);

        UriBuilder builder = info.getAbsolutePathBuilder();
        builder = builder.path(Long.toString(item.getId()));

        final URI uri = builder.build();

        return Response.created(uri).build();
    }


    @Path("/{id: \\d+}")
    @GET
    @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public Item readItem(@PathParam("id") final long id) {

        return ItemFacade.getInstance().select(id);
    }


    @Path("/{id: \\d+}")
    @PUT
    @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    public Response updateItem(@PathParam("id") final long id,
                               final Item newItem) {

        final boolean updated = ItemFacade.getInstance().update(id, newItem);

        if (!updated) {
            return Response.status(Status.NOT_FOUND).build();
        }

        return Response.status(Status.NO_CONTENT).build();
    }


    @Path("/{id: \\d+}")
    @DELETE
    public void deleteItem(@PathParam("id") final long id) {

        ItemFacade.getInstance().delete(id);
    }


}
resource POST GET PUT DELETE
/items CREATE
/items/{id: \\d+} READ UPDATE DELETE

Demonstration

CREATE

$ cat src/test/resources/item.xml
<item xmlns="http://jinahya.googlecode.com/test">
  <name>xml</name>
  <stock>30</stock>
</item>

$ curl -i \
> -X POST \
> http://localhost:58080/items \
> -H "Content-Type: application/xml" \
> --data "@src/test/resources/item.xml"
HTTP/1.1 201 Created
X-Powered-By: ...
Server: ...
Location: http://localhost:58080/items/0
Content-Length: 0
Date: ...

$

READ

$ curl -s \
> http://localhost:58080/items/0 \
> -H "Accept: application/xml" \
> | xmllint --format -
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:item xmlns:ns2="http://jinahya.googlecode.com/test"
          createdAt="2013-03-29T17:43:12.413+09:00"
          id="0">
  <name>xml</name>
  <stock>30</stock>
</ns2:item>

$

UPDATE

$ cat src/test/resources/item.json
{
    "name":"json",
    "stock":"40"
}

$ curl -i \
> -X PUT \
> http://localhost:58080/items/0 \
> -H "Content-Type: application/json" \
> --data "@src/test/resources/item.json"
HTTP/1.1 204 No Content
X-Powered-By: ...
Server: ...
Date: ...


$ curl -s \
> http://localhost:58080/items/0 \
> -H "Accept: application/json" \
> | python -m json.tool
{
    "@createdAt": "2013-03-29T17:43:12.413+09:00",
    "@id": "0",
    "@updatedAt": "2013-03-29T17:48:23.238+09:00",
    "name": "json",
    "stock": "40"
}

$

DELETE

$ curl -i \
> -X DELETE \
> http://localhost:58080/items/0
HTTP/1.1 204 No Content
X-Powered-By: ...
Server: ...
Date: ...

$

/schema.xsd

$ curl -s http://localhost:58080/schema.xsd | xmllint --format -
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema xmlns:tns="http://jinahya.googlecode.com/test"
           xmlns:xs="http://www.w3.org/2001/XMLSchema"
           attributeFormDefault="unqualified"
           elementFormDefault="qualified"
           version="1.0"
           targetNamespace="http://jinahya.googlecode.com/test">
  <xs:element name="imageFormat" type="tns:imageFormat"/>
  <xs:element name="imageFormats" type="tns:imageFormats"/>
  <xs:element name="imageSuffix" type="tns:imageSuffix"/>
  <xs:element name="imageSuffixes" type="tns:imageSuffixes"/>
  <xs:element name="item" type="tns:item"/>
  <xs:element name="items" type="tns:items"/>
  <xs:complexType name="imageFormat">
    <xs:simpleContent>
      <xs:extension base="xs:string">
        <xs:attribute name="canRead" type="xs:boolean" use="required"/>
        <xs:attribute name="canWrite" type="xs:boolean" use="required"/>
      </xs:extension>
    </xs:simpleContent>
  </xs:complexType>
  <xs:complexType name="imageFormats">
    <xs:sequence>
      <xs:any processContents="lax" namespace="##other" minOccurs="0" maxOccurs="unbounded"/>
    </xs:sequence>
    <xs:attribute name="empty" type="xs:boolean"/>
  </xs:complexType>
  <xs:complexType name="imageSuffix">
    <xs:simpleContent>
      <xs:extension base="xs:string">
        <xs:attribute name="canRead" type="xs:boolean" use="required"/>
        <xs:attribute name="canWrite" type="xs:boolean" use="required"/>
      </xs:extension>
    </xs:simpleContent>
  </xs:complexType>
  <xs:complexType name="imageSuffixes">
    <xs:sequence>
      <xs:any processContents="lax" namespace="##other" minOccurs="0" maxOccurs="unbounded"/>
    </xs:sequence>
    <xs:attribute name="empty" type="xs:boolean"/>
  </xs:complexType>
  <xs:complexType name="item">
    <xs:sequence>
      <xs:element name="name" type="xs:string"/>
      <xs:element name="stock" type="xs:int"/>
    </xs:sequence>
    <xs:attribute name="createdAt" type="xs:dateTime"/>
    <xs:attribute name="id" type="xs:long"/>
    <xs:attribute name="updatedAt" type="xs:dateTime"/>
  </xs:complexType>
  <xs:complexType name="items">
    <xs:sequence>
      <xs:element ref="tns:item" minOccurs="0" maxOccurs="unbounded"/>
    </xs:sequence>
    <xs:attribute name="empty" type="xs:boolean"/>
  </xs:complexType>
</xs:schema>

/imageFormats

POST GET PUT DELETE
/imageFormats Read All
/imageTypes/{name} Read Single
$ curl -s -H "Accept: application/xml" http://localhost:58080/imageFormats
<imageFormats xmlns="http://jinahya.googlecode.com/test">
  <imageFormat canRead="true" canWrite="true">jpg</imageFormat>
  <imageFormat canRead="true" canWrite="true">bmp</imageFormat>
  <imageFormat canRead="true" canWrite="true">BMP</imageFormat>
  <imageFormat canRead="true" canWrite="true">JPG</imageFormat>
  <imageFormat canRead="true" canWrite="true">wbmp</imageFormat>
  <imageFormat canRead="true" canWrite="true">jpeg</imageFormat>
  <imageFormat canRead="true" canWrite="true">png</imageFormat>
  <imageFormat canRead="true" canWrite="true">PNG</imageFormat>
  <imageFormat canRead="true" canWrite="true">JPEG</imageFormat>
  <imageFormat canRead="true" canWrite="true">WBMP</imageFormat>
  <imageFormat canRead="true" canWrite="true">GIF</imageFormat>
  <imageFormat canRead="true" canWrite="true">gif</imageFormat>
</imageFormats>
$ curl -s -H "Accept: application/json" http://localhost:58080/imageFormats
{
    "imageFormat": [
        {
            "$": "bmp",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "BMP",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "jpg",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "JPG",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "wbmp",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "jpeg",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "png",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "PNG",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "JPEG",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "WBMP",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "GIF",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "gif",
            "@canRead": "true",
            "@canWrite": "true"
        }
    ]
}
$ curl -s -H "Accept: application/xml" http://localhost:58080/imageFormats/png
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<imageFormat xmlns="http://jinahya.googlecode.com/test"
             canRead="true" canWrite="true">png</imageFormat>
$ curl -s -H "Accept: application/json" http://localhost:58080/imageFormats/jpeg
{
    "$": "jpeg",
    "@canRead": "true",
    "@canWrite": "true"
}

/imageTypes

POST GET PUT DELETE
/imageTypes Read All
/imageTypes/{name} Read Single
$ curl -s -H "Accept: application/xml" http://localhost:58080/imageTypes
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<imageTypes xmlns="http://jinahya.googlecode.com/test">
  <imageType canRead="true" canWrite="true">image/png</imageType>
  <imageType canRead="true" canWrite="true">image/jpeg</imageType>
  <imageType canRead="true" canWrite="true">image/x-png</imageType>
  <imageType canRead="true" canWrite="true">image/vnd.wap.wbmp</imageType>
  <imageType canRead="true" canWrite="true">image/bmp</imageType>
  <imageType canRead="true" canWrite="true">image/gif</imageType>
</imageTypes>
$ curl -s -H "Accept: application/json" http://localhost:58080/imageTypes
{
    "imageType": [
        {
            "$": "image/png",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "image/jpeg",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "image/x-png",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "image/vnd.wap.wbmp",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "image/bmp",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "image/gif",
            "@canRead": "true",
            "@canWrite": "true"
        }
    ]
}
$ curl -s -H "Accept: application/xml" http://localhost:58080/imageTypes/image/png
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<imageType xmlns="http://jinahya.googlecode.com/test"
           canRead="true" canWrite="true">image/png</imageType>
$ curl -s -H "Accept: application/json" http://localhost:58080/imageTypes/image/jpeg
{
    "$": "image/jpeg",
    "@canRead": "true",
    "@canWrite": "true"
}

/imageSuffixes

POST GET PUT DELETE
/imageTypes Read All
/imageTypes/{name} Read Single
$ curl -H "Accept: application/xml" http://localhost:58080/imageSuffixes
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<imageSuffixes xmlns="http://jinahya.googlecode.com/test">
  <imageSuffix canRead="true" canWrite="true">bmp</imageSuffix>
  <imageSuffix canRead="true" canWrite="true">jpg</imageSuffix>
  <imageSuffix canRead="true" canWrite="true">jpeg</imageSuffix>
  <imageSuffix canRead="true" canWrite="true">wbmp</imageSuffix>
  <imageSuffix canRead="true" canWrite="true">png</imageSuffix>
  <imageSuffix canRead="true" canWrite="true">gif</imageSuffix>
</imageSuffixes>
$ curl -H "Accept: application/json" http://localhost:58080/imageSuffixes
{
    "imageSuffix": [
        {
            "$": "bmp",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "jpg",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "wbmp",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "jpeg",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "png",
            "@canRead": "true",
            "@canWrite": "true"
        },
        {
            "$": "gif",
            "@canRead": "true",
            "@canWrite": "true"
        }
    ]
}
$ curl -H "Accept: application/xml" http://localhost:58080/imageSuffixes/png
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<imageSuffix xmlns="http://jinahya.googlecode.com/test"
             canRead="true" canWrite="true">png</imageSuffix>
$ curl -s -H "Accept: application/json" http://localhost:58080/imageSuffixes/jpeg
{
    "$": "jpeg",
    "@canRead": "true",
    "@canWrite": "true"
}

Serving XML Schema with JAX-RS


update

@GET
@Path("/some.xsd")
@Produces({MediaType.APPLICATION_XML})
public Response readSomeXsd() throws JAXBException, IOException {

    final JAXBContext context; // get some

    return Response.ok((StreamingOutput) output -> {
        context.generateSchema(new SchemaOutputResolver() {
            @Override
            public Result createOutput(final String namespaceUri,
                                       final String suggestedFileName)
                throws IOException {
                final Result result = new StreamResult(output);
                result.setSystemId(uriInfo.getAbsolutePath().toString());
                return result;
            }
        });
    }).build();
}

@Context
private transient UriInfo uriInfo;

You can serve the XML Schema for your domain objects.

public class SchemaStreamingOutput implements StreamingOutput {

    public SchemaStreamingOutput(final JAXBContext context) {
        super();

        if (context == null) {
            throw new NullPointerException("null context");
        }

        this.context = context;
    }

    @Override
    public void write(final OutputStream output) throws IOException {

        context.generateSchema(new SchemaOutputResolver() {

            @Override
            public Result createOutput(final String namespaceUri,
                                       final String suggestedFileName)
                throws IOException {

                return new StreamResult(output) {

                    @Override
                    public String getSystemId() { // nasty
                        return suggestedFileName;
                    }
                };
            }
        });
    }

    private final JAXBContext context;
}

Now you can serve your XML Schema like this.

@GET
@Path("/items.xsd")
@Produces({MediaType.APPLICATION_XML})
public Response readXsd() throws JAXBException {

    final JAXBContext context = ...;

    return Response.ok(new SchemaStreamingOutput(context)).build();
}

WS-Security UsernameToken with Password Derived Key


@Override
public boolean handleMessage(final SOAPMessageContext context) {

    final boolean outbound =
        (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);

    if (outbound) { // out-going

        final SOAPEnvelope envelope;
        try {
            envelope = context.getMessage().getSOAPPart().getEnvelope();
        } catch (SOAPException soape) {
            soape.printStackTrace(System.err);
            return false;
        }

        final Document document = ((Node) envelope).getOwnerDocument();

        final WSSecHeader header = new WSSecHeader();
        try {
            header.insertSecurityHeader(document);
        } catch (WSSecurityException wsse) {
            wsse.printStackTrace(System.err);
            return false;
        }

        final WSSecUsernameToken usernameToken = new WSSecUsernameToken();
        usernameToken.setUserInfo(USERNAME, PASSWORD);
        usernameToken.addDerivedKey(false, null, 8192);
        usernameToken.prepare(document);

        final byte[] derivedKey;
        try {
            derivedKey = usernameToken.getDerivedKey();
        } catch (WSSecurityException wsse) {
            wsse.printStackTrace(System.err);
            return false;
        }

        final WSSecDKEncrypt encrypt = new WSSecDKEncrypt();
        encrypt.setExternalKey(derivedKey, usernameToken.getId());
        encrypt.setSymmetricEncAlgorithm(WSConstants.AES_256);
        encrypt.setCustomValueType(
            WSConstants.WSS_USERNAME_TOKEN_VALUE_TYPE);
        try {
            encrypt.build(document, header);
        } catch (WSSecurityException wsse) {
            wsse.printStackTrace(System.err);
            return true;
        } catch (ConversationException ce) {
            ce.printStackTrace(System.err);
            return false;
        }

        usernameToken.prependToHeader(header);

        return true;

    } else { // in-coming
        return true;
    }
}

@MatrixParam with Jersey


With, currently the newest (3.1.2.2), GlassFish, @MatrixParam doesn’t work as expected.

http://.../path1/path2;value={value} does work,
http://.../path1;value={value}/path2 does not work.

And it’s still open.

One of workarounds is, as stated at Inheritance of duplicate @MatrixParam, using PathSegment along with @PathParam.

@GET
@Path("/{path1: path1}/path2")
public Resource read(
    @PathParam("path1") final PathSegment path1) {

    assert path1 != null;
    assert path1.getPath().equals("path1");

    final MultivaluedMap<String, String> matrixParameters = path1.getMatrixParameters();
    final List<String> values = matrixParameters.get("value");
    final String value = matrixParameters.getFirst("value");

    // ...
}