Category: java

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

Advertisements

Installing the StartCom CA Certificate into the local JDK


references

problem

I just found that my local JDK doesn’t like the StartSSL™‘s certificate on remote server.
He kept failing with following error while deploying site.

# Transfer error: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

solution

Download Tool Box/StartCom CA Certificates/StartCom Root CA (PEM encoded) which is named ‘ca.pem’.
Then execute following script.

KEYSTORE=$JAVA_HOME/jre/lib/security/cacerts
$JAVA_HOME/bin/keytool -import \
  -alias StartCom-Root-CA \
  -file ca.pem \
  -keystore "$KEYSTORE"

When asked for password? changeit.

derby properties with maven-surefire-plugin


It seems that derby log files generated on the root.

$ ls -l
...
-rw-r--r-- 1 onacit Users 13K 3월  29 18:11 pom.xml
drwxr-xr-x 1 onacit Users   0 5월  23  2012 src/

$ mvn clean test
...

$ ls -l
...
-rw-r--r-- 1 onacit Users 660 4월   1 12:28 derby.log
-rw-r--r-- 1 onacit Users 13K 3월  29 18:11 pom.xml
drwxr-xr-x 1 onacit Users   0 5월  23  2012 src/

$

Nasty, huh?

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <configuration>
    <systemProperties>
      <property>
        <name>derby.stream.error.file</name>
        <value>target/derby.log</value>
      </property>
      <property>
        <name>derby.locks.monitor</name>
        <value>true</value>
      </property>
      <property>
        <name>derby.locks.deadlockTrace</name>
        <value>true</value>
      </property>
      <property>
        <name>derby.language.logStatementText</name>
        <value>true</value>
      </property>
    </systemProperties>
  </configuration>
</plugin>
$ ls -l
...
-rw-r--r-- 1 onacit Users 13K 3월  29 18:11 pom.xml
drwxr-xr-x 1 onacit Users   0 5월  23  2012 src/

$ mvn clean test
...

$ ls -l
...
-rw-r--r-- 1 onacit Users 13K 3월  29 18:11 pom.xml
drwxr-xr-x 1 onacit Users   0 5월  23  2012 src/

$ ls -l target/
...
drwxr-xr-x 1 onacit Users   0 4월   1 12:39 classes/
-rw-r--r-- 1 onacit Users 34K 4월   1 12:40 derby.log
drwxr-xr-x 1 onacit Users   0 4월   1 12:39 generated-sources/
drwxr-xr-x 1 onacit Users   0 4월   1 12:39 generated-test-sources/
drwxr-xr-x 1 onacit Users   0 4월   1 12:40 surefire-reports/
drwxr-xr-x 1 onacit Users   0 4월   1 12:39 test-classes/

$

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();
}

Hexadecimal Encoding/Decoding with Java


Representing numbers in hexadecimal form, at least in programming, might still be a very difficult problem. There are plenty of googled results for how we do it in any languages.

DatatypeConverter from JAXB

First and foremost way is definitely using one of existing classes and methods in JDK.

Here comes OpenJDK’s implemention of those method.

public byte[] parseHexBinary(String s) {
    final int len = s.length();

    // "111" is not a valid hex encoding.
    if (len % 2 != 0) {
        throw new IllegalArgumentException("hexBinary needs to be even-length: " + s);
    }

    byte[] out = new byte[len / 2];

    for (int i = 0; i < len; i += 2) {
        int h = hexToBin(s.charAt(i));
        int l = hexToBin(s.charAt(i + 1));
        if (h == -1 || l == -1) {
            throw new IllegalArgumentException("contains illegal character for hexBinary: " + s);
        }

        out[i / 2] = (byte) (h * 16 + l);
    }

    return out;
}

private static int hexToBin(char ch) {
    if ('0' <= ch && ch <= '9') {
        return ch - '0';
    }
    if ('A' <= ch && ch <= 'F') {
        return ch - 'A' + 10;
    }
    if ('a' <= ch && ch <= 'f') {
        return ch - 'a' + 10;
    }
    return -1;
}

private static final char[] hexCode = "0123456789ABCDEF".toCharArray();

public String printHexBinary(byte[] data) {
    StringBuilder r = new StringBuilder(data.length * 2);
    for (byte b : data) {
        r.append(hexCode[(b >> 4) & 0xF]);
        r.append(hexCode[(b & 0xF)]);
    }
    return r.toString();
}

half

number (4bit) character (8bit)
0x0 ( 0) 0x30 ('0')
0x1 ( 1) 0x31 ('1')
0x2 ( 2) 0x32 ('2')
0x3 ( 3) 0x33 ('3')
0x4 ( 4) 0x34 ('4')
0x5 ( 5) 0x35 ('5')
0x6 ( 6) 0x36 ('6')
0x7 ( 7) 0x37 ('7')
0x8 ( 8) 0x38 ('8')
0x9 ( 9) 0x39 ('9')
0xA (10) 0x41 ('A')
0xB (11) 0x42 ('B')
0xC (12) 0x43 ('C')
0xA (13) 0x44 ('D')
0xE (14) 0x45 ('E')
0xF (15) 0x46 ('F')
protected static int encodeHalf(final int decoded) {

    if (decoded < 0x0A) {
        return decoded + 0x30;
    } else {
        return decoded + 0x37;
    }
}


protected static int decodeHalf(final int encoded) {

    if (encoded < 0x41) {
        return encoded - 0x30;
    } else {
        return encoded - 0x37;
    }
}

single

number (8bit) character (16bit)
0x00 (  0) 0x30 0x30 ('00')
.... (  .) .... .... ('..')
0x09 (  9) 0x30 0x39 ('09')
0x0A ( 10) 0x30 0x41 ('0A')
.... ( ..) .... .... ('..')
0x0F ( 15) 0x30 0x46 ('0F')
0x10 ( 16) 0x31 0x30 ('10')
.... ( ..) .... .... ('..')
0x63 ( 99) 0x36 0x33 ('63')
0x64 (100) 0x36 0x34 ('64')
0xC7 (199) 0x43 0x37 ('C7')
0xC8 (200) 0x43 0x38 ('C8')
0xC9 (201) 0x43 0x39 ('C9')
.... (...) .... .... ('..')
0xFF (255) 0x46 0x46 ('FF')
protected static byte[] encodeSingle(final int decoded) {

    final byte[] encoded = new byte[2];

    encoded[0] = (byte) encodeHalf(decoded >> 4);
    encoded[1] = (byte) encodeHalf(decoded & 0x0F);

    return encoded;
}


protected static int decodeSingle(final byte[] encoded) {

    final int high = decodeHalf(encoded[0] & 0xFF);
    final int low = decodeHalf(encoded[1] & 0xFF);

    return (high << 4 | low);
}

multiple

protected static void encodeSingle(final int decoded, final byte[] encoded, final int offset);

protected static int decodeSingle(final byte[] encoded, final int offset);

protected static byte[] encodeMultiple(final byte[] decoded);

protected static byte[] decodeMultiple(final byte[] encoded);

sources

$ svn co http://jinahya.googlecode.com/svn/trunk/com.googlecode.jinahya/hex-codec/ hex-codec