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.Collections;
028import java.util.List;
029import java.util.Map;
030import java.util.Map.Entry;
031
032import org.apache.commons.lang3.reflect.TypeUtils;
033import org.nuxeo.ecm.automation.core.util.Paginable;
034import org.nuxeo.ecm.core.api.DocumentModelList;
035import org.nuxeo.ecm.core.api.PartialList;
036import org.nuxeo.ecm.core.io.registry.MarshallerRegistry;
037import org.nuxeo.ecm.core.io.registry.Writer;
038import org.nuxeo.ecm.core.io.registry.context.RenderingContext;
039import org.nuxeo.ecm.platform.query.api.Aggregate;
040import org.nuxeo.ecm.platform.query.api.Bucket;
041import org.nuxeo.ecm.platform.query.api.QuickFilter;
042
043import com.fasterxml.jackson.core.JsonGenerator;
044
045/**
046 * Base class to convert {@link List} as json.
047 * <p>
048 * It follow the classic Nuxeo list format :
049 *
050 * <pre>
051 * {
052 *   "entity-type": "GIVEN_ENTITY_TYPE",
053 *                                  &lt;-- pagination info if available are here.
054 *   "entries": [
055 *     {...}, &lt;-- A {@link Writer} must be able to manage this format.
056 *     {...},
057 *     ...
058 *     {...}
059 *   ]
060 * }
061 * </pre>
062 * <p>
063 * This list generates pagination information if the list is a {@link Paginable}.
064 * <p>
065 * This reader delegates the marshalling of entries to the {@link MarshallerRegistry}. A Json {@link Writer} compatible
066 * with the required type must be registered.
067 *
068 * @param <EntityType> The type of the element of this list.
069 * @since 7.2
070 */
071public abstract class DefaultListJsonWriter<EntityType> extends AbstractJsonWriter<List<EntityType>> {
072
073    /**
074     * The "entity-type" of the list.
075     */
076    private final String entityType;
077
078    /**
079     * The Java type of the element of this list.
080     */
081    private final Class<EntityType> elClazz;
082
083    /**
084     * The generic type of the element of this list.
085     */
086    private final Type elGenericType;
087
088    /**
089     * Use this constructor if the element of the list are not based on Java generic type.
090     *
091     * @param entityType The list "entity-type".
092     * @param elClazz The class of the element of the list.
093     */
094    public DefaultListJsonWriter(String entityType, Class<EntityType> elClazz) {
095        super();
096        this.entityType = entityType;
097        this.elClazz = elClazz;
098        this.elGenericType = elClazz;
099    }
100
101    /**
102     * Use this constructor if the element of the list are based on Java generic type.
103     *
104     * @param entityType The list "entity-type".
105     * @param elClazz The class of the element of the list.
106     * @param elGenericType The generic type of the list (you can use {@link TypeUtils#parameterize(Class, Type...) to
107     *            generate it}
108     */
109    public DefaultListJsonWriter(String entityType, Class<EntityType> elClazz, Type elGenericType) {
110        super();
111        this.entityType = entityType;
112        this.elClazz = elClazz;
113        this.elGenericType = elGenericType;
114    }
115
116    @Override
117    public void write(List<EntityType> list, JsonGenerator jg) throws IOException {
118        jg.writeStartObject();
119        ctx.setParameterValues(RenderingContext.RESPONSE_HEADER_ENTITY_TYPE_KEY, this.entityType);
120        jg.writeStringField(ENTITY_FIELD_NAME, entityType);
121        writePaginationInfos(list, jg);
122        Writer<EntityType> documentWriter = registry.getWriter(ctx, elClazz, elGenericType, APPLICATION_JSON_TYPE);
123        jg.writeArrayFieldStart("entries");
124        for (EntityType entity : list) {
125            documentWriter.write(entity, elClazz, elClazz, APPLICATION_JSON_TYPE, new OutputStreamWithJsonWriter(jg));
126        }
127        jg.writeEndArray();
128        extend(list, jg);
129        jg.writeEndObject();
130    }
131
132    private void writePaginationInfos(List<EntityType> list, JsonGenerator jg) throws IOException {
133        if (list instanceof Paginable) {
134            Paginable<?> paginable = (Paginable<?>) list;
135            jg.writeBooleanField("isPaginable", true);
136            jg.writeNumberField("resultsCount", paginable.getResultsCount());
137            jg.writeNumberField("pageSize", paginable.getPageSize());
138            jg.writeNumberField("maxPageSize", paginable.getMaxPageSize());
139            jg.writeNumberField("resultsCountLimit", paginable.getResultsCountLimit());
140            jg.writeNumberField("currentPageSize", paginable.getCurrentPageSize());
141            jg.writeNumberField("currentPageIndex", paginable.getCurrentPageIndex());
142            jg.writeNumberField("currentPageOffset", paginable.getCurrentPageOffset());
143            jg.writeNumberField("numberOfPages", paginable.getNumberOfPages());
144            jg.writeBooleanField("isPreviousPageAvailable", paginable.isPreviousPageAvailable());
145            jg.writeBooleanField("isNextPageAvailable", paginable.isNextPageAvailable());
146            jg.writeBooleanField("isLastPageAvailable", paginable.isLastPageAvailable());
147            jg.writeBooleanField("isSortable", paginable.isSortable());
148            jg.writeBooleanField("hasError", paginable.hasError());
149            jg.writeStringField("errorMessage", paginable.getErrorMessage());
150            // compat fields
151            if (paginable instanceof DocumentModelList) {
152                jg.writeNumberField("totalSize", ((DocumentModelList) paginable).totalSize());
153            }
154            jg.writeNumberField("pageIndex", paginable.getCurrentPageIndex());
155            jg.writeNumberField("pageCount", paginable.getNumberOfPages());
156            if (paginable.hasAggregateSupport()) {
157                Map<String, Aggregate<? extends Bucket>> aggregates = paginable.getAggregates();
158                if (aggregates != null && !paginable.getAggregates().isEmpty()) {
159                    jg.writeObjectFieldStart("aggregations");
160                    for (Entry<String, Aggregate<? extends Bucket>> e : aggregates.entrySet()) {
161                        writeEntityField(e.getKey(), e.getValue(), jg);
162                    }
163                    jg.writeEndObject();
164                }
165
166            }
167            List<QuickFilter> qfs = paginable.getActiveQuickFilters();
168            if (qfs == null) {
169                qfs = Collections.emptyList();
170            }
171
172            List<QuickFilter> aqfs = paginable.getAvailableQuickFilters();
173            if (aqfs != null && !aqfs.isEmpty()) {
174                jg.writeArrayFieldStart("quickFilters");
175                for (QuickFilter aqf : aqfs) {
176                    jg.writeStartObject();
177                    jg.writeStringField("name", aqf.getName());
178                    jg.writeBooleanField("active", qfs.contains(aqf));
179                    jg.writeEndObject();
180                }
181                jg.writeEndArray();
182            }
183        } else if (list instanceof PartialList) {
184            PartialList<EntityType> partial = (PartialList<EntityType>) list;
185            jg.writeNumberField("totalSize", partial.totalSize());
186        }
187    }
188
189    /**
190     * Override this method to write additional information in the list.
191     *
192     * @param list The list to marshal.
193     * @param jg The {@link JsonGenerator} which point inside the list object at the end of standard properties.
194     * @since 7.2
195     */
196    protected void extend(List<EntityType> list, JsonGenerator jg) throws IOException {
197    }
198
199}