顯示具有 JAX-RS 標籤的文章。 顯示所有文章
顯示具有 JAX-RS 標籤的文章。 顯示所有文章

2013年1月17日 星期四

Applying Spring Security for HTTP Basic Authentication of Apache CXF

在這裡記錄如何利用Spring Security設定HTTP Basic Authentication來保護Apache CXF RESTful Service。這裡不討論AuthenticationProvider的實作細節。
Spring XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:jaxrs="http://cxf.apache.org/jaxrs"
    xmlns:security="http://www.springframework.org/schema/security"
    xsi:schemaLocation="http://cxf.apache.org/configuration/security http://cxf.apache.org/schemas/configuration/security.xsd
        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <import resource="spring-cornerstone-logic-core.xml" />

    <context:annotation-config />
    <context:component-scan base-package="com.gss.gmo.cao.cornerstone.logic.web" />

    <!-- Spring Security -->
    <security:http auto-config="false" pattern="/services/rest/**" use-expressions="true">
        <security:http-basic />
        <security:intercept-url pattern="/services/rest/maintain*/**" access="hasRole('MAINTENANCE')" />
        <security:intercept-url pattern="/services/rest/**" access="hasRole('LOOKUP')" />
        <security:intercept-url pattern="/**" access="authenticated" />
    </security:http>

    <security:authentication-manager alias="authenticationManager">
        <security:authentication-provider ref="daoAuthenticationProvider" />
    </security:authentication-manager>

    <!-- Apache CXF -->
    <jaxrs:server id="rest" address="/rest">
        <jaxrs:serviceBeans>
            <ref bean="helloWorld" />
            <ref bean="maintainUnitService" />
            <ref bean="roleGroupService" />
            <ref bean="userService" />
            <ref bean="unitService" />
        </jaxrs:serviceBeans>
        <jaxrs:providers>
            <bean class="com.gss.gmo.cao.restserver.provider.GsonMessageBodyProvider" />
            <bean class="com.gss.gmo.cao.restserver.provider.ThrowableMapper" />
            <bean class="com.gss.gmo.cao.restserver.provider.ExceptionMapper" />
            <bean class="com.gss.gmo.cao.restserver.provider.RuntimeExceptionMapper" />
            <bean class="com.gss.gmo.cao.restserver.provider.ValidationExceptionMapper" />
            <bean class="com.gss.gmo.cao.restserver.provider.StaleStateExceptionMapper" />
        </jaxrs:providers>
    </jaxrs:server>

    <!-- Hibernate Validator -->
    <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
    <bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor" />

</beans>

2013年1月10日 星期四

Customize JAX-RS Provider (JSR311)

以下是使用google-gson處理RESTful JSON message的provider,其中日期的部分使用ISO8601格式。
com.gss.gmo.cao.restclient.provider.GsonMessageBodyProvider
package com.gss.gmo.cao.restclient.provider;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Date;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.gss.gmo.cao.gson.ISODateTimeAdapter;

/**
 * MessageBodyWriter and MessageBodyReader for MediaType.APPLICATION_JSON.
 *
 * @author linus_chien
 *
 */
@Provider
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class GsonMessageBodyProvider implements MessageBodyWriter<Object>, MessageBodyReader<Object> {

    /**
     * Gson instance.
     */
    private final Gson gson;

    /**
     * Init Gson object.
     */
    public GsonMessageBodyProvider() {
        gson = new GsonBuilder().registerTypeAdapter(Date.class, new ISODateTimeAdapter()).create();
    }

    @Override
    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return true;
    }

    @Override
    public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders,
            InputStream entityStream) throws IOException, WebApplicationException {
        return gson.fromJson(new InputStreamReader(entityStream, "UTF-8"), genericType);
    }

    @Override
    public long getSize(Object obj, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return -1;
    }

    @Override
    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
        return true;
    }

    @Override
    public void writeTo(Object obj, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders,
            OutputStream entityStream) throws IOException, WebApplicationException {
        entityStream.write(gson.toJson(obj, genericType).getBytes("UTF-8"));
    }
}
com.gss.gmo.cao.gson.ISODateTimeAdapter
package com.gss.gmo.cao.gson;

import org.joda.time.format.ISODateTimeFormat;

/**
 * Format sample: 2012-12-05T10:55:41.063+08:00
 *
 * @author linus_chien
 *
 */
public class ISODateTimeAdapter extends AbstractDateTimeAdapter {

    public ISODateTimeAdapter() {
        super(ISODateTimeFormat.dateTime());
    }
}
com.gss.gmo.cao.gson.AbstractDateTimeAdapter
package com.gss.gmo.cao.gson;

import java.lang.reflect.Type;
import java.util.Date;
import org.joda.time.format.DateTimeFormatter;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;

public abstract class AbstractDateTimeAdapter implements JsonSerializer<Date>, JsonDeserializer<Date> {

    private DateTimeFormatter formatter;

    protected AbstractDateTimeAdapter(DateTimeFormatter formatter) {
        this.formatter = formatter;
    }

    @Override
    public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        return formatter.parseDateTime(json.getAsString()).toDate();
    }

    @Override
    public JsonElement serialize(Date date, Type typeOfSrc, JsonSerializationContext context) {
        return new JsonPrimitive(formatter.print(date.getTime()));
    }
}

使用Apache CXF快速製作Java RESTful Client (JSR311)

使用Apache CXF製作Java RESTful client是很容易的,首先我們看一下org.apache.cxf.jaxrs.client.JAXRSClientFactory,其中有很多名為create的static method,而在官方的說明文件中有特別提到Proxy-based API的使用方式,簡單的說,它可以利用一組符合JSR311規範的interface,反過來generate proxy client instance,讓我們可以不需要自己撰寫任何HTTP client或者是其它RESTful API就做出client端的物件,是不是很方便呢?

以下是一組簡單的test case:
  1. JSR311 interface
  2. 代碼:
    package com.gss.gmo.cao.restclient;

    import java.util.Date;

    import javax.ws.rs.GET;
    import javax.ws.rs.Path;
    import javax.ws.rs.Produces;
    import javax.ws.rs.core.MediaType;

    @Path("test")
    public interface TestResource {
        @GET
        @Path("getDate")
        @Produces(MediaType.APPLICATION_JSON)
        Date getDate();
    }
  3. Implementation class
  4. 代碼:
    package com.gss.gmo.cao.restclient.provider;

    import java.util.Date;
    import javax.ws.rs.GET;
    import javax.ws.rs.Path;
    import org.joda.time.format.ISODateTimeFormat;
    import com.gss.gmo.cao.restclient.TestResource;

    @Path("test")
    public class TestResourceImpl implements TestResource {
        @Override
        @GET
        @Path("getDate")
        public Date getDate() {
            return ISODateTimeFormat.dateTime().parseDateTime("2012-12-14T09:21:00.123+08:00").toDate();
        }
    }
  5. Test case
  6. 代碼:
    package com.gss.gmo.cao.restclient.provider;

    import static junit.framework.Assert.assertEquals;
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.List;
    import org.apache.cxf.jaxrs.client.JAXRSClientFactory;
    import org.joda.time.format.ISODateTimeFormat;
    import org.junit.Before;
    import org.junit.Test;
    import com.gss.gmo.cao.restclient.TestResource;
    import com.sun.jersey.test.framework.AppDescriptor;
    import com.sun.jersey.test.framework.JerseyTest;
    import com.sun.jersey.test.framework.WebAppDescriptor;

    public class ResponseExceptionMapperTest extends JerseyTest {

        private TestResource testResource;

        @Override
        protected AppDescriptor configure() {
            return new WebAppDescriptor.Builder().initParam("com.sun.jersey.config.property.packages", "com.gss.gmo.cao.restclient.provider").build();
        }

        @Override
        @Before
        public void setUp() throws Exception {
            super.setUp();
            List<Object> providers = new ArrayList<Object>();
            providers.add(new GsonMessageBodyProvider());
            testResource = JAXRSClientFactory.create("http://localhost:9998", TestResource.class, providers);
        }

        @Test
        public void testGetDate() {
            Date date = testResource.getDate();
            assertEquals(ISODateTimeFormat.dateTime().parseDateTime("2012-12-14T09:21:00.123+08:00").toDate(), date);
        }
    }
當我們要接上一個外部RESTful Web Services時,只要依照對方提供的API,依樣畫葫蘆改用JSR311做出interface之後,也就等於完成client端的程式了。