Category: Java EE
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
PBKDF2 with Java
references
- Secure Password Storage – Lots of don’ts, a few dos, and a concrete Java SE example
- Wikipedia
- Java™ Cryptography Architecture Standard Algorithm Name Documentation
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"); // ... }
secure the glassfish admin
Configurations -> server-config -> Security -> Realms -> admin-realm -> Manage Users