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.platform.query.api.Aggregate;
037import org.nuxeo.ecm.platform.query.api.Bucket;
038
039/**
040 * Base class to convert {@link List} as json.
041 * <p>
042 * It follow the classic Nuxeo list format :
043 *
044 * <pre>
045 * {
046 *   "entity-type": "GIVEN_ENTITY_TYPE",
047 *                                  <-- pagination info if available are here.
048 *   "entries": [
049 *     {...}, <-- A {@link Writer} must be able to manage this format.
050 *     {...},
051 *     ...
052 *     {...}
053 *   ]
054 * }
055 * </pre>
056 * <p>
057 * This list generates pagination information if the list is a {@link Paginable}.
058 * </p>
059 * <p>
060 * This reader delegates the marshalling of entries to the {@link MarshallerRegistry}. A Json {@link Writer} compatible
061 * with the required type must be registered.
062 * </p>
063 *
064 * @param <EntityType> The type of the element of this list.
065 * @since 7.2
066 */
067public abstract class DefaultListJsonWriter<EntityType> extends AbstractJsonWriter<List<EntityType>> {
068
069    /**
070     * The "entity-type" of the list.
071     */
072    private final String entityType;
073
074    /**
075     * The Java type of the element of this list.
076     */
077    private final Class<EntityType> elClazz;
078
079    /**
080     * The generic type of the element of this list.
081     */
082    private final Type elGenericType;
083
084    /**
085     * Use this constructor if the element of the list are not based on Java generic type.
086     *
087     * @param entityType The list "entity-type".
088     * @param elClazz The class of the element of the list.
089     */
090    public DefaultListJsonWriter(String entityType, Class<EntityType> elClazz) {
091        super();
092        this.entityType = entityType;
093        this.elClazz = elClazz;
094        this.elGenericType = elClazz;
095    }
096
097    /**
098     * Use this constructor if the element of the list are based on Java generic type.
099     *
100     * @param entityType The list "entity-type".
101     * @param elClazz The class of the element of the list.
102     * @param elGenericType The generic type of the list (you can use {@link TypeUtils#parameterize(Class, Type...) to
103     *            generate it}
104     */
105    public DefaultListJsonWriter(String entityType, Class<EntityType> elClazz, Type elGenericType) {
106        super();
107        this.entityType = entityType;
108        this.elClazz = elClazz;
109        this.elGenericType = elGenericType;
110    }
111
112    @Override
113    public void write(List<EntityType> list, JsonGenerator jg) throws IOException {
114        jg.writeStartObject();
115        jg.writeStringField(ENTITY_FIELD_NAME, entityType);
116        writePaginationInfos(list, jg);
117        Writer<EntityType> documentWriter = registry.getWriter(ctx, elClazz, elGenericType, APPLICATION_JSON_TYPE);
118        jg.writeArrayFieldStart("entries");
119        for (EntityType entity : list) {
120            documentWriter.write(entity, elClazz, elClazz, APPLICATION_JSON_TYPE, new OutputStreamWithJsonWriter(jg));
121        }
122        jg.writeEndArray();
123        extend(list, jg);
124        jg.writeEndObject();
125    }
126
127    private void writePaginationInfos(List<EntityType> list, JsonGenerator jg) throws IOException {
128        if (list instanceof Paginable) {
129            Paginable<?> paginable = (Paginable<?>) list;
130            jg.writeBooleanField("isPaginable", true);
131            jg.writeNumberField("resultsCount", paginable.getResultsCount());
132            jg.writeNumberField("pageSize", paginable.getPageSize());
133            jg.writeNumberField("maxPageSize", paginable.getMaxPageSize());
134            jg.writeNumberField("currentPageSize", paginable.getCurrentPageSize());
135            jg.writeNumberField("currentPageIndex", paginable.getCurrentPageIndex());
136            jg.writeNumberField("numberOfPages", paginable.getNumberOfPages());
137            jg.writeBooleanField("isPreviousPageAvailable", paginable.isPreviousPageAvailable());
138            jg.writeBooleanField("isNextPageAvailable", paginable.isNextPageAvailable());
139            jg.writeBooleanField("isLastPageAvailable", paginable.isLastPageAvailable());
140            jg.writeBooleanField("isSortable", paginable.isSortable());
141            jg.writeBooleanField("hasError", paginable.hasError());
142            jg.writeStringField("errorMessage", paginable.getErrorMessage());
143            // compat fields
144            if (paginable instanceof DocumentModelList) {
145                jg.writeNumberField("totalSize", ((DocumentModelList) paginable).totalSize());
146            }
147            jg.writeNumberField("pageIndex", paginable.getCurrentPageIndex());
148            jg.writeNumberField("pageCount", paginable.getNumberOfPages());
149            if (paginable.hasAggregateSupport()) {
150                Map<String, Aggregate<? extends Bucket>> aggregates = paginable.getAggregates();
151                if (aggregates != null && !paginable.getAggregates().isEmpty()) {
152                    jg.writeObjectField("aggregations", paginable.getAggregates());
153                }
154            }
155        }
156    }
157
158    /**
159     * Override this method to write additional information in the list.
160     *
161     * @param list The list to marshal.
162     * @param jg The {@link JsonGenerator} which point inside the list object at the end of standard properties.
163     * @since 7.2
164     */
165    protected void extend(List<EntityType> list, JsonGenerator jg) throws IOException {
166    }
167
168}