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;
029import java.util.Map.Entry;
030
031import org.apache.commons.lang3.reflect.TypeUtils;
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;
039import org.nuxeo.ecm.platform.query.api.QuickFilter;
040
041import com.fasterxml.jackson.core.JsonGenerator;
042
043/**
044 * Base class to convert {@link List} as json.
045 * <p>
046 * It follow the classic Nuxeo list format :
047 *
048 * <pre>
049 * {
050 *   "entity-type": "GIVEN_ENTITY_TYPE",
051 *                                  <-- pagination info if available are here.
052 *   "entries": [
053 *     {...}, <-- A {@link Writer} must be able to manage this format.
054 *     {...},
055 *     ...
056 *     {...}
057 *   ]
058 * }
059 * </pre>
060 * <p>
061 * This list generates pagination information if the list is a {@link Paginable}.
062 * </p>
063 * <p>
064 * This reader delegates the marshalling of entries to the {@link MarshallerRegistry}. A Json {@link Writer} compatible
065 * with the required type must be registered.
066 * </p>
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            List<QuickFilter> aqfs = paginable.getAvailableQuickFilters();
169            if (aqfs != null && !aqfs.isEmpty()) {
170                jg.writeArrayFieldStart("quickFilters");
171                for (QuickFilter aqf : aqfs) {
172                    jg.writeStartObject();
173                    jg.writeStringField("name", aqf.getName());
174                    jg.writeBooleanField("active", qfs.contains(aqf));
175                    jg.writeEndObject();
176                }
177                jg.writeEndArray();
178            }
179        }
180    }
181
182    /**
183     * Override this method to write additional information in the list.
184     *
185     * @param list The list to marshal.
186     * @param jg The {@link JsonGenerator} which point inside the list object at the end of standard properties.
187     * @since 7.2
188     */
189    protected void extend(List<EntityType> list, JsonGenerator jg) throws IOException {
190    }
191
192}