Beberapa hari ini saya banyak belajar mengenai JAX-RS pada Java EE 7, selama masa latihan saya menemukan sesuatu yang cukup penting untuk saya catat agar tidak lupa dikemudian hari.

Global Exception Mapper

Pada dasarnya JAX-RS secara otomatis mengenali Bean Validation, sehingga anotasi semacam @Valid, @NotNull, @Size, dll. dapat dikenali secara otomatis dan apabila request yang dikirimkan tidak memenuhi Bean Validation maka akan secara otomatis pula dikirimkan response yang menyatakan bahwa request tersebut gagal.

Permasalahannya adalah response yang dikirimkan masih secara default, sehingga kurang informatif bagi client. Untungnya kita dapat secara mudah mengoverride response dari Bean Validation ini. Caranya cukup mudah, kita terlebih dahulu deklarasikan class yang mengimplements ExceptionMapper dari JAX-RS.

@Provider
public class ConstraintViolationExceptionMapper implements ExceptionMapper<ConstraintViolationException> {

    @Override
    public Response toResponse(final ConstraintViolationException exception) {
        JsonObject response = Json.createObjectBuilder().add("message", "failure").build();

        final String message = exception.getConstraintViolations().stream()
                .map(v -> v.getPropertyPath() + ": " + v.getMessage())
                .collect(Collectors.joining(", "));

        return Response.status(Response.Status.BAD_REQUEST).entity(response).header("X-Validation-Error", message).build();
    }

}

Bagi pengguna Java 7 dapat mengganti fungsi stream dengan syntax seperti berikut

    for (ConstraintViolation violation : e.getConstraintViolations()) {
        StringBuilder message = new StringBuilder();
        if (violation.getPropertyPath() != null) {
            message.append(violation.gerPropertyPath().toString())
            .append(": ")
            .append(violation.getMessage())
            .append(", ");
        }
    }

Deklarasi class diatas menggunakan annotations @Provider dari JAX-RS yang mana secara otomatis konfigurasi kita akan dibaca oleh JAX-RS. Apabila kita tidak menggunakan annotations tersebut, maka kita perlu melakukan konfigurasi secara manual pada class JAXRSConfiguration (Class yang memiliki annotations @ApplicationPath). Selanjutnya saya membuat pesan pada response body secara sederhana yaitu failure, namun saya menyematkan pesan error mengapa request tersebut tidak memenuhi Bean Validation.

Constrain Violation Mapper

Saya baru menyadari bahwa kita dapat membuat Bean Validation kita sendiri, bahkan kita bisa memasukan anotasi @Inject untuk menyematkan code lain dalam operasi Bean Validation kita sendiri. Berikut adalah langkah sederhana dalam membuat Bean Validation. Langkah pertama adalah membuat Custom Constrain Violation Annotations dimana anotasi yang kita buat nantinya dapat digunakan seperti kita menggunakan CDI seperti biasa.

Sebagai contoh saya membuat Bean Validation untuk mengecek apakah produk yang dipesan masih tersedia stocknya, saya membuat anotasi dengan nama @OutOfStock yang mana saya cukup menyematkan anotasi tersebut pada variable parameter untuk setiap request yang akan mengakses resource product. Sedangkan untuk implementasi logicnya sendiri akan diimplementasi pada class dengan nama StockValidator.

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@NotNull
@Constraint(validatedBy = StockValidator.class)
@Documented
public @interface OutOfStock {

    String message() default "Stock is not available";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}

Berikut untuk implementasi business logic dari pengecekan stock suatu product.

// ConstraintValidator consist <name_of_annotations> and <Entity / Value> that need to check
public class StockValidator implements ConstraintValidator<OutOfStock, Product> {

    @Inject
    Stock stock

    @Override
    public void initialize(Contexts constraintAnnotation) {
        // nothing to do
    }

    @Override
    public boolean isValid(Product value, ConstraintValidatorContext context) {
        return this.stock.isStockAvailable(value); // return boolean value
    }
}

Demikian kita telah memiliki Custom Bean Validation, dan sebagai contoh pada request method untuk melakuka order suatu product kita cukup mendeklarasikan anotasi yang sudah kita buat untuk dapat melakukan pengecekan stock masih tersedia atau tidak.

    @POST
    public Response orderProduct(@Valid @OutOfStock Product product) {
        // do something here
    }

Add GZIP Content-Type

Terkadang response JSON yang kita kirimkan terlalu besar dan hal ini tentu akan memakan cukup banyak pita bandwidth internet serta size dari response itu sendiri untungnya JSON merupakan file text sehingga kita dapat mengkompresi ukuran dari response JSON kita. Pada JAX-RS kita dapat memanfaatkan content-accept pada header untuk menerima algoritma kompresi GZIP.

Berikut saya akan mencontohkan membangun Interface Name Binding pada JAX-RS untuk mengkompresi response JSON. Pada dasarnya kita membuat anotasi sendiri seperti pada Constrain Violation, namun bedanya adalah anotasi yang kita gunakan mem binding sebuah class konfigurasi.

@NameBinding
@Retention(RetentionPolicy.RUNTIME)
public @interface Compress {

}

Selanjutnya kita membuat class konfigurasi yang nantinya akan di binding oleh anotasi @Compress yang telah kita buat diatas.

@Provider
@Compress // don't forget to initialize your annotations
public class GZIPWriterInterceptor implements WriterInterceptor {

    @Override
    public void aroundWriteTo(WriterInterceptorContext context)
                    throws IOException, WebApplicationException {

    	MultivaluedMap<String,Object> headers = context.getHeaders();
    	headers.add("Content-Encoding", "gzip");

        final OutputStream outputStream = context.getOutputStream();
        context.setOutputStream(new GZIPOutputStream(outputStream));
        context.proceed();
    }
}

Dengan demikian kita hanya perlu menambahkan anotasi kita pada setiap method JAX-RS kita

    @GET
    @Compress
    public Respone getAllProducts() {
        //do something here
    }

References: