HtmlJsonProvider.java

/*
 * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.jersey.media.htmljson;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.client.WebTarget;
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 org.openide.util.lookup.ServiceProvider;
import org.openide.util.lookup.ServiceProviders;

import net.java.html.BrwsrCtx;
import net.java.html.json.Model;
import net.java.html.json.Models;
import net.java.html.json.Property;

/**
 * Implementation of Jersey's message body reader and writer that
 * can handle reading and writing of JSON models generated by {@link Model}
 * annotation provided by
 * <a target="_blank" href="http://bck2brwsr.apidesign.org/javadoc/net.java.html.json/">net.java.html.json</a>
 * library. Include
 * this JAR in your project and you can then use your
 * model classes as Jersey's entities.
 * <p>
 * <pre>
 * {@link Model @Model}(className="Query", properties={
 *   {@link Property @Property}(name="items", type=Item.<b>class</b>, array=true)
 * })
 * <b>class</b> QueryImpl {
 *
 *   {@link Model @Model}(className="Item", properties={
 *     {@link Property @Property}(name="id", type=String.<b>class</b>),
 *     {@link Property @Property}(name="kind", type=Kind.<b>class</b>)
 *   })
 *   <b>class</b> ItemImpl {
 *   }
 *
 *   <b>enum</b> Kind {
 *     GOOD, BAD
 *   }
 *
 *   <b>public static</b> List{@code <Item>} doQuery() {
 *     {@link WebTarget} target = ...;
 *     Query q = target.request(MediaType.APPLICATION_JSON).get().readEntity(Query.<b>class</b>);
 *     return q.getItems();
 *   }
 * }
 * </pre>
 *
 * @author Jaroslav Tulach (jtulach at netbeans.org)
 */
@ServiceProviders({
        @ServiceProvider(service = MessageBodyWriter.class),
        @ServiceProvider(service = MessageBodyReader.class)
})
public final class HtmlJsonProvider implements MessageBodyWriter<Object>, MessageBodyReader<Object> {

    @Override
    public boolean isWriteable(Class clazz, Type type, Annotation[] antns, MediaType mt) {
        if (!mt.isCompatible(MediaType.APPLICATION_JSON_TYPE)) {
            return false;
        }
        if (clazz.isArray()) {
            return Models.isModel(clazz.getComponentType());
        }
        if (java.util.List.class.isAssignableFrom(clazz)) {
            if (type instanceof ParameterizedType) {
                ParameterizedType pt = (ParameterizedType) type;
                Type[] args = pt.getActualTypeArguments();
                if (args.length == 1 && args[0] instanceof Class) {
                    return Models.isModel((Class<?>) args[0]);
                }
            }
        }
        return Models.isModel(clazz);
    }

    @Override
    public long getSize(Object t, Class type, Type type1, Annotation[] antns, MediaType mt) {
        return -1;
    }

    @Override
    public void writeTo(Object t, Class type, Type type1, Annotation[] antns, MediaType mt, MultivaluedMap mm, OutputStream out)
            throws IOException, WebApplicationException {
        dump(t, out);
    }

    private void dump(Object t, OutputStream out) throws IOException {
        if (t instanceof Object[]) {
            Object[] arr = (Object[]) t;
            out.write('[');
            for (int i = 0; i < arr.length; i++) {
                if (i > 0) {
                    out.write(',');
                }
                dump(arr[i], out);
            }
            out.write(']');
        } else {
            out.write(t.toString().getBytes("UTF-8"));
        }
    }

    @Override
    public boolean isReadable(Class<?> type, Type type1, Annotation[] antns, MediaType mt) {
        return isWriteable(type, type1, antns, mt);
    }

    @Override
    public Object readFrom(Class<Object> clazz,
                           Type type, Annotation[] antns, MediaType mt,
                           MultivaluedMap<String, String> mm,
                           InputStream in) throws IOException, WebApplicationException {
        BrwsrCtx def = BrwsrCtx.findDefault(HtmlJsonProvider.class);
        if (clazz.isArray()) {
            List<Object> res = new ArrayList<>();
            final Class<?> cmp = clazz.getComponentType();
            Models.parse(def, cmp, in, res);
            Object[] arr = (Object[]) Array.newInstance(cmp, res.size());
            return res.toArray(arr);
        }
        if (clazz.isAssignableFrom(java.util.List.class)
                && type instanceof ParameterizedType
                && ((ParameterizedType) type).getActualTypeArguments().length == 1
                && ((ParameterizedType) type).getActualTypeArguments()[0] instanceof Class) {
            List<Object> res = new ArrayList<>();
            final Class<?> cmp = (Class<?>) ((ParameterizedType) type).getActualTypeArguments()[0];
            Models.parse(def, cmp, in, res);
            return res;
        }
        return Models.parse(def, clazz, in);
    }
}