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 <[email protected]> 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.ListTypeImpl; 047import org.nuxeo.ecm.core.schema.types.Schema; 048import org.nuxeo.ecm.core.schema.types.SchemaImpl; 049import org.nuxeo.ecm.core.schema.types.primitives.BooleanType; 050import org.nuxeo.ecm.core.schema.types.primitives.StringType; 051import org.nuxeo.ecm.core.schema.utils.DateParser; 052import org.nuxeo.ecm.directory.io.DirectoryEntryJsonWriter; 053import org.nuxeo.ecm.platform.query.api.Aggregate; 054import org.nuxeo.ecm.platform.query.api.Bucket; 055import org.nuxeo.ecm.platform.query.core.BucketRange; 056import org.nuxeo.ecm.platform.query.core.BucketRangeDate; 057import org.nuxeo.elasticsearch.aggregate.SignificantTermAggregate; 058import org.nuxeo.elasticsearch.aggregate.SingleBucketAggregate; 059import org.nuxeo.elasticsearch.aggregate.SingleValueMetricAggregate; 060import org.nuxeo.elasticsearch.aggregate.TermAggregate; 061 062import com.fasterxml.jackson.core.JsonGenerationException; 063import com.fasterxml.jackson.core.JsonGenerator; 064 065/** 066 * @since 8.4 067 */ 068@SuppressWarnings("rawtypes") 069@Setup(mode = SINGLETON, priority = REFERENCE) 070public class AggregateJsonWriter extends ExtensibleEntityJsonWriter<Aggregate> { 071 072 public static final String ENTITY_TYPE = "aggregate"; 073 074 public static final String FETCH_KEY = "key"; 075 076 private static final Log log = LogFactory.getLog(AggregateJsonWriter.class); 077 078 /** Fake schema for system properties usable as a page provider aggregate */ 079 protected static final Schema SYSTEM_SCHEMA = new SchemaImpl("system", null); 080 081 static { 082 SYSTEM_SCHEMA.addField("ecm:mixinType", new ListTypeImpl("system", "", StringType.INSTANCE), null, 0, null); 083 SYSTEM_SCHEMA.addField("ecm:tag", new ListTypeImpl("system", "", StringType.INSTANCE), null, 0, null); 084 SYSTEM_SCHEMA.addField("ecm:primaryType", StringType.INSTANCE, null, 0, null); 085 SYSTEM_SCHEMA.addField("ecm:currentLifeCycleState", StringType.INSTANCE, null, 0, null); 086 SYSTEM_SCHEMA.addField("ecm:versionLabel", StringType.INSTANCE, null, 0, null); 087 SYSTEM_SCHEMA.addField("ecm:isTrashed", BooleanType.INSTANCE, null, 0, null); 088 } 089 090 /** 091 * @since 10.3 092 */ 093 protected Field getSystemField(String name) { 094 Field result = SYSTEM_SCHEMA.getField(name); 095 if (result == null && name.startsWith("ecm:path@level")) { 096 SYSTEM_SCHEMA.addField(name, StringType.INSTANCE, null, 0, null); 097 return SYSTEM_SCHEMA.getField(name); 098 } 099 return result; 100 } 101 102 @Inject 103 private SchemaManager schemaManager; 104 105 public AggregateJsonWriter() { 106 super(ENTITY_TYPE, Aggregate.class); 107 } 108 109 public AggregateJsonWriter(String entityType, Class<Aggregate> entityClass) { 110 super(entityType, entityClass); 111 } 112 113 @Override 114 public boolean accept(Class<?> clazz, Type genericType, MediaType mediatype) { 115 return true; 116 } 117 118 @SuppressWarnings("unchecked") 119 @Override 120 protected void writeEntityBody(Aggregate agg, JsonGenerator jg) throws IOException { 121 boolean fetch = ctx.getFetched(ENTITY_TYPE).contains(FETCH_KEY); 122 String fieldName = agg.getField(); 123 Field field; 124 if (fieldName.startsWith("ecm:")) { 125 field = getSystemField(fieldName); 126 if (field == null) { 127 log.warn(String.format("%s is not a valid field for aggregates", fieldName)); 128 return; 129 } 130 } else { 131 field = schemaManager.getField(fieldName); 132 } 133 jg.writeObjectField("id", agg.getId()); 134 jg.writeObjectField("field", agg.getField()); 135 jg.writeObjectField("properties", agg.getProperties()); 136 jg.writeObjectField("ranges", agg.getRanges()); 137 jg.writeObjectField("selection", agg.getSelection()); 138 jg.writeObjectField("type", agg.getType()); 139 if (agg instanceof SingleValueMetricAggregate) { 140 Double val = ((SingleValueMetricAggregate) agg).getValue(); 141 jg.writeObjectField("value", Double.isFinite(val) ? val : null); 142 } else if (agg instanceof SingleBucketAggregate) { 143 jg.writeObjectField("value", ((SingleBucketAggregate) agg).getDocCount()); 144 } else if (!fetch || !(agg instanceof TermAggregate || agg instanceof SignificantTermAggregate)) { 145 jg.writeObjectField("buckets", agg.getBuckets()); 146 jg.writeObjectField("extendedBuckets", agg.getExtendedBuckets()); 147 } else { 148 if (field != null) { 149 try (Closeable resource = ctx.wrap() 150 .with(FETCH_PROPERTIES + "." + DocumentModelJsonWriter.ENTITY_TYPE, 151 "properties") 152 .with(FETCH_PROPERTIES + "." + DirectoryEntryJsonWriter.ENTITY_TYPE, 153 "parent") 154 .with(TRANSLATE_PROPERTIES + "." + DirectoryEntryJsonWriter.ENTITY_TYPE, 155 "label") 156 .with(MAX_DEPTH_PARAM, "max") 157 .open()) { 158 159 writeBuckets("buckets", agg.getBuckets(), field, jg); 160 writeBuckets("extendedBuckets", agg.getExtendedBuckets(), field, jg); 161 } 162 } else { 163 log.warn(String.format("Could not resolve field %s for aggregate %s", fieldName, agg.getId())); 164 jg.writeObjectField("buckets", agg.getBuckets()); 165 jg.writeObjectField("extendedBuckets", agg.getExtendedBuckets()); 166 } 167 } 168 } 169 170 protected void writeBuckets(String fieldName, List<Bucket> buckets, Field field, JsonGenerator jg) 171 throws IOException, JsonGenerationException { 172 // prepare document part in order to use property 173 Schema schema = field.getDeclaringType().getSchema(); 174 DocumentPartImpl part = new DocumentPartImpl(schema); 175 // write data 176 jg.writeArrayFieldStart(fieldName); 177 for (Bucket bucket : buckets) { 178 jg.writeStartObject(); 179 180 jg.writeObjectField("key", bucket.getKey()); 181 182 Property prop = PropertyFactory.createProperty(part, field, Property.NONE); 183 if (prop.isList()) { 184 ListType t = (ListType) prop.getType(); 185 t.getField(); 186 prop = PropertyFactory.createProperty(part, t.getField(), Property.NONE); 187 } 188 log.debug(String.format("Writing %s for field %s resolved to %s", fieldName, field.getName().toString(), 189 prop.getName())); 190 prop.setValue(bucket.getKey()); 191 192 writeEntityField("fetchedKey", prop, jg); 193 jg.writeNumberField("docCount", bucket.getDocCount()); 194 jg.writeEndObject(); 195 196 if (bucket instanceof BucketRange) { 197 BucketRange bucketRange = (BucketRange) bucket; 198 jg.writeNumberField("from", bucketRange.getFrom()); 199 jg.writeNumberField("to", bucketRange.getTo()); 200 } 201 202 if (bucket instanceof BucketRangeDate) { 203 BucketRangeDate bucketRange = (BucketRangeDate) bucket; 204 jg.writeStringField("fromAsDate", DateParser.formatW3CDateTime(bucketRange.getFromAsDate().toDate())); 205 jg.writeStringField("toAsDate", DateParser.formatW3CDateTime(bucketRange.getToAsDate().toDate())); 206 } 207 } 208 jg.writeEndArray(); 209 } 210 211}