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