001/*
002 * (C) Copyright 2016 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 *     Guillaume Renard <grenard@nuxeo.com>
018 */
019package org.nuxeo.elasticsearch.io.marshallers.json;
020
021import static org.nuxeo.ecm.core.io.registry.MarshallingConstants.FETCH_PROPERTIES;
022import static org.nuxeo.ecm.core.io.registry.MarshallingConstants.MAX_DEPTH_PARAM;
023import static org.nuxeo.ecm.core.io.registry.MarshallingConstants.TRANSLATE_PROPERTIES;
024import static org.nuxeo.ecm.core.io.registry.reflect.Instantiations.SINGLETON;
025import static org.nuxeo.ecm.core.io.registry.reflect.Priorities.REFERENCE;
026
027import java.io.Closeable;
028import java.io.IOException;
029import java.lang.reflect.Type;
030import java.util.List;
031
032import javax.inject.Inject;
033import javax.ws.rs.core.MediaType;
034
035import org.apache.commons.logging.Log;
036import org.apache.commons.logging.LogFactory;
037import org.nuxeo.ecm.core.api.model.Property;
038import org.nuxeo.ecm.core.api.model.impl.DocumentPartImpl;
039import org.nuxeo.ecm.core.api.model.impl.PropertyFactory;
040import org.nuxeo.ecm.core.io.marshallers.json.ExtensibleEntityJsonWriter;
041import org.nuxeo.ecm.core.io.marshallers.json.document.DocumentModelJsonWriter;
042import org.nuxeo.ecm.core.io.registry.reflect.Setup;
043import org.nuxeo.ecm.core.schema.SchemaManager;
044import org.nuxeo.ecm.core.schema.types.Field;
045import org.nuxeo.ecm.core.schema.types.ListType;
046import org.nuxeo.ecm.core.schema.types.Schema;
047import org.nuxeo.ecm.core.schema.utils.DateParser;
048import org.nuxeo.ecm.directory.io.DirectoryEntryJsonWriter;
049import org.nuxeo.ecm.platform.query.api.Aggregate;
050import org.nuxeo.ecm.platform.query.api.Bucket;
051import org.nuxeo.ecm.platform.query.core.BucketRange;
052import org.nuxeo.ecm.platform.query.core.BucketRangeDate;
053import org.nuxeo.elasticsearch.aggregate.SignificantTermAggregate;
054import org.nuxeo.elasticsearch.aggregate.TermAggregate;
055
056import com.fasterxml.jackson.core.JsonGenerationException;
057import com.fasterxml.jackson.core.JsonGenerator;
058
059/**
060 * @since 8.4
061 */
062@SuppressWarnings("rawtypes")
063@Setup(mode = SINGLETON, priority = REFERENCE)
064public class AggregateJsonWriter extends ExtensibleEntityJsonWriter<Aggregate> {
065
066    public static final String ENTITY_TYPE = "aggregate";
067
068    public static final String FETCH_KEY = "key";
069
070    private static final Log log = LogFactory.getLog(AggregateJsonWriter.class);
071
072    @Inject
073    private SchemaManager schemaManager;
074
075    public AggregateJsonWriter() {
076        super(ENTITY_TYPE, Aggregate.class);
077    }
078
079    public AggregateJsonWriter(String entityType, Class<Aggregate> entityClass) {
080        super(entityType, entityClass);
081    }
082
083    @Override
084    public boolean accept(Class<?> clazz, Type genericType, MediaType mediatype) {
085        return true;
086    }
087
088    @SuppressWarnings("unchecked")
089    @Override
090    protected void writeEntityBody(Aggregate agg, JsonGenerator jg) throws IOException {
091        boolean fetch = ctx.getFetched(ENTITY_TYPE).contains(FETCH_KEY);
092        jg.writeObjectField("id", agg.getId());
093        jg.writeObjectField("field", agg.getField());
094        jg.writeObjectField("properties", agg.getProperties());
095        jg.writeObjectField("ranges", agg.getRanges());
096        jg.writeObjectField("selection", agg.getSelection());
097        jg.writeObjectField("type", agg.getType());
098        if (!fetch || !(agg instanceof TermAggregate || agg instanceof SignificantTermAggregate)) {
099            jg.writeObjectField("buckets", agg.getBuckets());
100            jg.writeObjectField("extendedBuckets", agg.getExtendedBuckets());
101        } else {
102            String fieldName = agg.getField();
103            Field field = schemaManager.getField(fieldName);
104            if (field != null) {
105                try (Closeable resource = ctx.wrap()
106                                             .with(FETCH_PROPERTIES + "." + DocumentModelJsonWriter.ENTITY_TYPE,
107                                                     "properties")
108                                             .with(FETCH_PROPERTIES + "." + DirectoryEntryJsonWriter.ENTITY_TYPE,
109                                                     "parent")
110                                             .with(TRANSLATE_PROPERTIES + "." + DirectoryEntryJsonWriter.ENTITY_TYPE,
111                                                     "label")
112                                             .with(MAX_DEPTH_PARAM, "max")
113                                             .open()) {
114
115                    writeBuckets("buckets", agg.getBuckets(), field, jg);
116                    writeBuckets("extendedBuckets", agg.getExtendedBuckets(), field, jg);
117                }
118            } else {
119                log.warn(String.format("Could not resolve field %s for aggregate %s", fieldName, agg.getId()));
120                jg.writeObjectField("buckets", agg.getBuckets());
121                jg.writeObjectField("extendedBuckets", agg.getExtendedBuckets());
122            }
123        }
124    }
125
126    protected void writeBuckets(String fieldName, List<Bucket> buckets, Field field, JsonGenerator jg)
127            throws IOException, JsonGenerationException {
128        // prepare document part in order to use property
129        Schema schema = field.getDeclaringType().getSchema();
130        DocumentPartImpl part = new DocumentPartImpl(schema);
131        // write data
132        jg.writeArrayFieldStart(fieldName);
133        for (Bucket bucket : buckets) {
134            jg.writeStartObject();
135
136            jg.writeObjectField("key", bucket.getKey());
137
138            Property prop = PropertyFactory.createProperty(part, field, Property.NONE);
139            if (prop.isList()) {
140                ListType t = (ListType) prop.getType();
141                t.getField();
142                prop = PropertyFactory.createProperty(part, t.getField(), Property.NONE);
143            }
144            log.debug(String.format("Writing %s for field %s resolved to %s", fieldName, field.getName().toString(),
145                    prop.getName()));
146            prop.setValue(bucket.getKey());
147
148            writeEntityField("fetchedKey", prop, jg);
149            jg.writeNumberField("docCount", bucket.getDocCount());
150            jg.writeEndObject();
151
152            if (bucket instanceof BucketRange) {
153                BucketRange bucketRange = (BucketRange) bucket;
154                jg.writeNumberField("from", bucketRange.getFrom());
155                jg.writeNumberField("to", bucketRange.getTo());
156            }
157
158            if (bucket instanceof BucketRangeDate) {
159                BucketRangeDate bucketRange = (BucketRangeDate) bucket;
160                jg.writeStringField("fromAsDate", DateParser.formatW3CDateTime(bucketRange.getFromAsDate().toDate()));
161                jg.writeStringField("toAsDate", DateParser.formatW3CDateTime(bucketRange.getToAsDate().toDate()));
162            }
163        }
164        jg.writeEndArray();
165    }
166
167}