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 * <-- pagination info if available are here. 054 * "entries": [ 055 * {...}, <-- 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}