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