001/* 002 * (C) Copyright 2015-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 * Nicolas Chapurlat <nchapurlat@nuxeo.com> 018 * Ronan DANIELLOU <rdaniellou@nuxeo.com> 019 */ 020 021package org.nuxeo.ecm.core.io.marshallers.json.document; 022 023import static org.nuxeo.ecm.core.io.marshallers.json.document.DocumentModelJsonWriter.ENTITY_TYPE; 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.ByteArrayOutputStream; 028import java.io.IOException; 029import java.util.Set; 030 031import org.apache.commons.logging.Log; 032import org.apache.commons.logging.LogFactory; 033import org.nuxeo.ecm.core.api.Blob; 034import org.nuxeo.ecm.core.api.DocumentModel; 035import org.nuxeo.ecm.core.api.model.Property; 036import org.nuxeo.ecm.core.api.model.impl.ArrayProperty; 037import org.nuxeo.ecm.core.api.model.impl.ListProperty; 038import org.nuxeo.ecm.core.api.model.impl.primitives.BlobProperty; 039import org.nuxeo.ecm.core.io.download.DownloadService; 040import org.nuxeo.ecm.core.io.marshallers.json.AbstractJsonWriter; 041import org.nuxeo.ecm.core.io.registry.MarshallingException; 042import org.nuxeo.ecm.core.io.registry.reflect.Setup; 043import org.nuxeo.ecm.core.schema.types.ListType; 044import org.nuxeo.ecm.core.schema.types.SimpleType; 045import org.nuxeo.ecm.core.schema.types.Type; 046import org.nuxeo.ecm.core.schema.types.primitives.BinaryType; 047import org.nuxeo.ecm.core.schema.types.primitives.BooleanType; 048import org.nuxeo.ecm.core.schema.types.primitives.DoubleType; 049import org.nuxeo.ecm.core.schema.types.primitives.IntegerType; 050import org.nuxeo.ecm.core.schema.types.primitives.LongType; 051import org.nuxeo.ecm.core.schema.types.resolver.ObjectResolver; 052import org.nuxeo.runtime.api.Framework; 053 054import com.fasterxml.jackson.core.JsonGenerator; 055 056/** 057 * Convert {@link Property} to Json. 058 * <p> 059 * Format is: 060 * 061 * <pre> 062 * "stringPropertyValue" <-- for string property, each property may be fetched if a resolver is associated with that property and if a parameter fetch.document=propXPath is present, in this case, an object will be marshalled as value 063 * or 064 * true|false <- for boolean property 065 * or 066 * 123 <- for int property 067 * ... 068 * { <- for complex property 069 * "subProperty": ..., 070 * ... 071 * }, 072 * [ ... ] <- for list property 073 * } 074 * </pre> 075 * </p> 076 * 077 * @since 7.2 078 */ 079@Setup(mode = SINGLETON, priority = REFERENCE) 080public class DocumentPropertyJsonWriter extends AbstractJsonWriter<Property> { 081 082 private static final Log log = LogFactory.getLog(DocumentPropertyJsonWriter.class); 083 084 @Override 085 public void write(Property prop, JsonGenerator jg) throws IOException { 086 writeProperty(jg, prop); 087 jg.flush(); 088 } 089 090 protected void writeProperty(JsonGenerator jg, Property prop) throws IOException { 091 if (prop.isScalar()) { 092 writeScalarProperty(jg, prop); 093 } else if (prop.isList()) { 094 writeListProperty(jg, prop); 095 } else if (prop instanceof BlobProperty) { // a blob 096 writeBlobProperty(jg, prop); 097 } else if (prop.isComplex()) { 098 writeComplexProperty(jg, prop); 099 } else if (prop.isPhantom()) { 100 jg.writeNull(); 101 } 102 } 103 104 protected void writeScalarProperty(JsonGenerator jg, Property prop) throws IOException { 105 Type type = prop.getType(); 106 Object value = prop.getValue(); 107 if (!fetchProperty(jg, prop.getType().getObjectResolver(), value, prop.getXPath())) { 108 writeScalarPropertyValue(jg, ((SimpleType) type).getPrimitiveType(), value); 109 } 110 } 111 112 private void writeScalarPropertyValue(JsonGenerator jg, Type type, Object value) throws IOException { 113 if (value == null) { 114 jg.writeNull(); 115 } else if (type instanceof BooleanType) { 116 jg.writeBoolean((Boolean) value); 117 } else if (type instanceof LongType) { 118 jg.writeNumber(((Number) value).longValue()); // value may be a DeltaLong 119 } else if (type instanceof DoubleType) { 120 jg.writeNumber((Double) value); 121 } else if (type instanceof IntegerType) { 122 jg.writeNumber((Integer) value); 123 } else if (type instanceof BinaryType) { 124 jg.writeBinary((byte[]) value); 125 } else { 126 jg.writeString(type.encode(value)); 127 } 128 } 129 130 protected boolean fetchProperty(JsonGenerator jg, ObjectResolver resolver, Object value, String path) 131 throws IOException { 132 if (value == null) { 133 return false; 134 } 135 boolean fetched = false; 136 if (resolver != null) { 137 String genericPropertyPath = path.replaceAll("/[0-9]*/", "/*/"); 138 Set<String> fetchElements = ctx.getFetched(ENTITY_TYPE); 139 boolean fetch = false; 140 for (String fetchElement : fetchElements) { 141 if ("properties".equals(fetchElement) || path.startsWith(fetchElement) 142 || genericPropertyPath.startsWith(fetchElement)) { 143 fetch = true; 144 break; 145 } 146 } 147 if (fetch) { 148 Object object = resolver.fetch(value); 149 if (object != null) { 150 try { 151 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 152 writeEntity(object, baos); 153 jg.writeRawValue(baos.toString()); 154 fetched = true; 155 } catch (MarshallingException e) { 156 log.error("Unable to marshall as json the entity referenced by the property " + path, e); 157 } 158 } 159 } 160 } 161 return fetched; 162 } 163 164 protected void writeListProperty(JsonGenerator jg, Property prop) throws IOException { 165 jg.writeStartArray(); 166 if (prop instanceof ArrayProperty) { 167 Object[] ar = (Object[]) prop.getValue(); 168 if (ar == null) { 169 jg.writeEndArray(); 170 return; 171 } 172 Type itemType = ((ListType) prop.getType()).getFieldType(); 173 ObjectResolver resolver = itemType.getObjectResolver(); 174 String path = prop.getXPath(); 175 for (Object o : ar) { 176 if (!fetchProperty(jg, resolver, o, path)) { 177 writeScalarPropertyValue(jg, ((SimpleType) itemType).getPrimitiveType(), o); 178 } 179 } 180 } else { 181 ListProperty listp = (ListProperty) prop; 182 for (Property p : listp.getChildren()) { 183 writeProperty(jg, p); 184 } 185 } 186 jg.writeEndArray(); 187 } 188 189 protected void writeComplexProperty(JsonGenerator jg, Property prop) throws IOException { 190 jg.writeStartObject(); 191 for (Property p : prop.getChildren()) { 192 jg.writeFieldName(p.getName()); 193 writeProperty(jg, p); 194 } 195 jg.writeEndObject(); 196 } 197 198 protected void writeBlobProperty(JsonGenerator jg, Property prop) throws IOException { 199 Blob blob = (Blob) prop.getValue(); 200 if (blob == null) { 201 jg.writeNull(); 202 return; 203 } 204 jg.writeStartObject(); 205 String v = blob.getFilename(); 206 if (v == null) { 207 jg.writeNullField("name"); 208 } else { 209 jg.writeStringField("name", v); 210 } 211 v = blob.getMimeType(); 212 if (v == null) { 213 jg.writeNullField("mime-type"); 214 } else { 215 jg.writeStringField("mime-type", v); 216 } 217 v = blob.getEncoding(); 218 if (v == null) { 219 jg.writeNullField("encoding"); 220 } else { 221 jg.writeStringField("encoding", v); 222 } 223 v = blob.getDigestAlgorithm(); 224 if (v == null) { 225 jg.writeNullField("digestAlgorithm"); 226 } else { 227 jg.writeStringField("digestAlgorithm", v); 228 } 229 v = blob.getDigest(); 230 if (v == null) { 231 jg.writeNullField("digest"); 232 } else { 233 jg.writeStringField("digest", v); 234 } 235 jg.writeStringField("length", Long.toString(blob.getLength())); 236 237 String blobUrl = getBlobUrl(prop); 238 if (blobUrl == null) { 239 blobUrl = ""; 240 } 241 jg.writeStringField("data", blobUrl); 242 jg.writeEndObject(); 243 } 244 245 /** 246 * Gets the full URL of where a blob can be downloaded. 247 * 248 * @since 7.2 249 */ 250 private String getBlobUrl(Property prop) { 251 DocumentModel doc = ctx.getParameter(ENTITY_TYPE); 252 if (doc == null) { 253 return ""; 254 } 255 DownloadService downloadService = Framework.getService(DownloadService.class); 256 257 String xpath = prop.getXPath(); 258 // if no prefix, use schema name as prefix: 259 if (!xpath.contains(":")) { 260 xpath = prop.getSchema().getName() + ":" + xpath; 261 } 262 263 String filename = ((Blob) prop.getValue()).getFilename(); 264 return ctx.getBaseUrl() + downloadService.getDownloadUrl(doc, xpath, filename); 265 } 266 267}