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