001/*
002 * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl-2.1.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Nicolas Chapurlat <nchapurlat@nuxeo.com>
016 */
017
018package org.nuxeo.ecm.core.io.marshallers.json;
019
020import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE;
021import static org.nuxeo.ecm.core.io.registry.MarshallingConstants.ENTITY_FIELD_NAME;
022
023import java.io.IOException;
024import java.lang.reflect.Type;
025import java.util.List;
026import java.util.Map;
027
028import org.apache.commons.lang3.reflect.TypeUtils;
029import org.codehaus.jackson.JsonGenerator;
030import org.nuxeo.ecm.automation.core.util.Paginable;
031import org.nuxeo.ecm.core.api.DocumentModelList;
032import org.nuxeo.ecm.core.io.registry.MarshallerRegistry;
033import org.nuxeo.ecm.core.io.registry.Writer;
034import org.nuxeo.ecm.platform.query.api.Aggregate;
035import org.nuxeo.ecm.platform.query.api.Bucket;
036
037/**
038 * Base class to convert {@link List} as json.
039 * <p>
040 * It follow the classic Nuxeo list format :
041 *
042 * <pre>
043 * {
044 *   "entity-type": "GIVEN_ENTITY_TYPE",
045 *                                  <-- pagination info if available are here.
046 *   "entries": [
047 *     {...}, <-- A {@link Writer} must be able to manage this format.
048 *     {...},
049 *     ...
050 *     {...}
051 *   ]
052 * }
053 * </pre>
054 * <p>
055 * This list generates pagination information if the list is a {@link Paginable}.
056 * </p>
057 * <p>
058 * This reader delegates the marshalling of entries to the {@link MarshallerRegistry}. A Json {@link Writer} compatible
059 * with the required type must be registered.
060 * </p>
061 *
062 * @param <EntityType> The type of the element of this list.
063 * @since 7.2
064 */
065public abstract class DefaultListJsonWriter<EntityType> extends AbstractJsonWriter<List<EntityType>> {
066
067    /**
068     * The "entity-type" of the list.
069     */
070    private final String entityType;
071
072    /**
073     * The Java type of the element of this list.
074     */
075    private final Class<EntityType> elClazz;
076
077    /**
078     * The generic type of the element of this list.
079     */
080    private final Type elGenericType;
081
082    /**
083     * Use this constructor if the element of the list are not based on Java generic type.
084     *
085     * @param entityType The list "entity-type".
086     * @param elClazz The class of the element of the list.
087     */
088    public DefaultListJsonWriter(String entityType, Class<EntityType> elClazz) {
089        super();
090        this.entityType = entityType;
091        this.elClazz = elClazz;
092        this.elGenericType = elClazz;
093    }
094
095    /**
096     * Use this constructor if the element of the list are based on Java generic type.
097     *
098     * @param entityType The list "entity-type".
099     * @param elClazz The class of the element of the list.
100     * @param elGenericType The generic type of the list (you can use {@link TypeUtils#parameterize(Class, Type...) to
101     *            generate it}
102     */
103    public DefaultListJsonWriter(String entityType, Class<EntityType> elClazz, Type elGenericType) {
104        super();
105        this.entityType = entityType;
106        this.elClazz = elClazz;
107        this.elGenericType = elGenericType;
108    }
109
110    @Override
111    public void write(List<EntityType> list, JsonGenerator jg) throws IOException {
112        jg.writeStartObject();
113        jg.writeStringField(ENTITY_FIELD_NAME, entityType);
114        writePaginationInfos(list, jg);
115        Writer<EntityType> documentWriter = registry.getWriter(ctx, elClazz, elGenericType, APPLICATION_JSON_TYPE);
116        jg.writeArrayFieldStart("entries");
117        for (EntityType entity : list) {
118            documentWriter.write(entity, elClazz, elClazz, APPLICATION_JSON_TYPE, new OutputStreamWithJsonWriter(jg));
119        }
120        jg.writeEndArray();
121        extend(list, jg);
122        jg.writeEndObject();
123    }
124
125    private void writePaginationInfos(List<EntityType> list, JsonGenerator jg) throws IOException {
126        if (list instanceof Paginable) {
127            Paginable<?> paginable = (Paginable<?>) list;
128            jg.writeBooleanField("isPaginable", true);
129            jg.writeNumberField("resultsCount", paginable.getResultsCount());
130            jg.writeNumberField("pageSize", paginable.getPageSize());
131            jg.writeNumberField("maxPageSize", paginable.getMaxPageSize());
132            jg.writeNumberField("currentPageSize", paginable.getCurrentPageSize());
133            jg.writeNumberField("currentPageIndex", paginable.getCurrentPageIndex());
134            jg.writeNumberField("numberOfPages", paginable.getNumberOfPages());
135            jg.writeBooleanField("isPreviousPageAvailable", paginable.isPreviousPageAvailable());
136            jg.writeBooleanField("isNextPageAvailable", paginable.isNextPageAvailable());
137            jg.writeBooleanField("isLastPageAvailable", paginable.isLastPageAvailable());
138            jg.writeBooleanField("isSortable", paginable.isSortable());
139            jg.writeBooleanField("hasError", paginable.hasError());
140            jg.writeStringField("errorMessage", paginable.getErrorMessage());
141            // compat fields
142            if (paginable instanceof DocumentModelList) {
143                jg.writeNumberField("totalSize", ((DocumentModelList) paginable).totalSize());
144            }
145            jg.writeNumberField("pageIndex", paginable.getCurrentPageIndex());
146            jg.writeNumberField("pageCount", paginable.getNumberOfPages());
147            if (paginable.hasAggregateSupport()) {
148                Map<String, Aggregate<? extends Bucket>> aggregates = paginable.getAggregates();
149                if (aggregates != null && !paginable.getAggregates().isEmpty()) {
150                    jg.writeObjectField("aggregations", paginable.getAggregates());
151                }
152            }
153        }
154    }
155
156    /**
157     * Override this method to write additional information in the list.
158     *
159     * @param list The list to marshal.
160     * @param jg The {@link JsonGenerator} which point inside the list object at the end of standard properties.
161     * @since 7.2
162     */
163    protected void extend(List<EntityType> list, JsonGenerator jg) throws IOException {
164    }
165
166}