001/* 002 * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl-2.1.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Nicolas Chapurlat <nchapurlat@nuxeo.com> 016 */ 017 018package org.nuxeo.ecm.core.io.marshallers.json.document; 019 020import static org.nuxeo.ecm.core.io.marshallers.json.document.DocumentModelJsonWriter.ENTITY_TYPE; 021import static org.nuxeo.ecm.core.io.registry.reflect.Instantiations.SINGLETON; 022import static org.nuxeo.ecm.core.io.registry.reflect.Priorities.REFERENCE; 023 024import java.io.ByteArrayOutputStream; 025import java.io.IOException; 026import java.util.Set; 027 028import org.apache.commons.logging.Log; 029import org.apache.commons.logging.LogFactory; 030import org.codehaus.jackson.JsonGenerator; 031import org.nuxeo.ecm.core.api.Blob; 032import org.nuxeo.ecm.core.api.DocumentModel; 033import org.nuxeo.ecm.core.api.model.Property; 034import org.nuxeo.ecm.core.api.model.impl.ArrayProperty; 035import org.nuxeo.ecm.core.api.model.impl.ListProperty; 036import org.nuxeo.ecm.core.api.model.impl.primitives.BlobProperty; 037import org.nuxeo.ecm.core.io.download.DownloadService; 038import org.nuxeo.ecm.core.io.marshallers.json.AbstractJsonWriter; 039import org.nuxeo.ecm.core.io.registry.MarshallingException; 040import org.nuxeo.ecm.core.io.registry.reflect.Setup; 041import org.nuxeo.ecm.core.schema.types.ComplexTypeImpl; 042import org.nuxeo.ecm.core.schema.types.ListType; 043import org.nuxeo.ecm.core.schema.types.SimpleType; 044import org.nuxeo.ecm.core.schema.types.Type; 045import org.nuxeo.ecm.core.schema.types.primitives.BinaryType; 046import org.nuxeo.ecm.core.schema.types.primitives.BooleanType; 047import org.nuxeo.ecm.core.schema.types.primitives.DoubleType; 048import org.nuxeo.ecm.core.schema.types.primitives.IntegerType; 049import org.nuxeo.ecm.core.schema.types.primitives.LongType; 050import org.nuxeo.ecm.core.schema.types.resolver.ObjectResolver; 051import org.nuxeo.runtime.api.Framework; 052 053/** 054 * Convert {@link Property} to Json. 055 * <p> 056 * Format is: 057 * 058 * <pre> 059 * "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 060 * or 061 * true|false <- for boolean property 062 * or 063 * 123 <- for int property 064 * ... 065 * { <- for complex property 066 * "subProperty": ..., 067 * ... 068 * }, 069 * [ ... ] <- for list property 070 * } 071 * </pre> 072 * 073 * </p> 074 * 075 * @since 7.2 076 */ 077@Setup(mode = SINGLETON, priority = REFERENCE) 078public class DocumentPropertyJsonWriter extends AbstractJsonWriter<Property> { 079 080 private static final Log log = LogFactory.getLog(DocumentPropertyJsonWriter.class); 081 082 @Override 083 public void write(Property prop, JsonGenerator jg) throws IOException { 084 writeProperty(jg, prop); 085 jg.flush(); 086 } 087 088 protected void writeProperty(JsonGenerator jg, Property prop) throws IOException { 089 if (prop.isScalar()) { 090 writeScalarProperty(jg, prop); 091 } else if (prop.isList()) { 092 writeListProperty(jg, prop); 093 } else { 094 if (prop.isPhantom()) { 095 jg.writeNull(); 096 } else if (prop instanceof BlobProperty) { // a blob 097 writeBlobProperty(jg, prop); 098 } else { // a complex property 099 writeComplexProperty(jg, prop); 100 } 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.getPath())) { 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((Long) value); 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 propertyPath = path.replaceFirst("/", ""); 138 String genericPropertyPath = propertyPath.replaceAll("\\[[0-9]*\\]", ""); 139 Set<String> fetchElements = ctx.getFetched(ENTITY_TYPE); 140 boolean fetch = false; 141 for (String fetchElement : fetchElements) { 142 if ("properties".equals(fetchElement) || propertyPath.startsWith(fetchElement) 143 || genericPropertyPath.startsWith(fetchElement)) { 144 fetch = true; 145 break; 146 } 147 } 148 if (fetch) { 149 Object object = resolver.fetch(value); 150 if (object != null) { 151 try { 152 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 153 writeEntity(object, baos); 154 jg.writeRawValue(baos.toString()); 155 fetched = true; 156 } catch (MarshallingException e) { 157 log.error("Unable to marshall as json the entity referenced by the property " + path, e); 158 } 159 } 160 } 161 } 162 return fetched; 163 } 164 165 protected void writeListProperty(JsonGenerator jg, Property prop) throws IOException { 166 jg.writeStartArray(); 167 if (prop instanceof ArrayProperty) { 168 Object[] ar = (Object[]) prop.getValue(); 169 if (ar == null) { 170 jg.writeEndArray(); 171 return; 172 } 173 Type itemType = ((ListType) prop.getType()).getFieldType(); 174 ObjectResolver resolver = itemType.getObjectResolver(); 175 String path = prop.getPath(); 176 for (Object o : ar) { 177 if (!fetchProperty(jg, resolver, o, path)) { 178 writeScalarPropertyValue(jg, ((SimpleType) itemType).getPrimitiveType(), o); 179 } 180 } 181 } else { 182 ListProperty listp = (ListProperty) prop; 183 for (Property p : listp.getChildren()) { 184 writeProperty(jg, p); 185 } 186 } 187 jg.writeEndArray(); 188 } 189 190 protected void writeComplexProperty(JsonGenerator jg, Property prop) throws IOException { 191 jg.writeStartObject(); 192 for (Property p : prop.getChildren()) { 193 jg.writeFieldName(p.getName()); 194 writeProperty(jg, p); 195 } 196 jg.writeEndObject(); 197 } 198 199 protected void writeBlobProperty(JsonGenerator jg, Property prop) throws IOException { 200 Blob blob = (Blob) prop.getValue(); 201 if (blob == null) { 202 jg.writeNull(); 203 return; 204 } 205 jg.writeStartObject(); 206 String v = blob.getFilename(); 207 if (v == null) { 208 jg.writeNullField("name"); 209 } else { 210 jg.writeStringField("name", v); 211 } 212 v = blob.getMimeType(); 213 if (v == null) { 214 jg.writeNullField("mime-type"); 215 } else { 216 jg.writeStringField("mime-type", v); 217 } 218 v = blob.getEncoding(); 219 if (v == null) { 220 jg.writeNullField("encoding"); 221 } else { 222 jg.writeStringField("encoding", v); 223 } 224 v = blob.getDigestAlgorithm(); 225 if (v == null) { 226 jg.writeNullField("digestAlgorithm"); 227 } else { 228 jg.writeStringField("digestAlgorithm", v); 229 } 230 v = blob.getDigest(); 231 if (v == null) { 232 jg.writeNullField("digest"); 233 } else { 234 jg.writeStringField("digest", v); 235 } 236 jg.writeStringField("length", Long.toString(blob.getLength())); 237 238 String blobUrl = getBlobUrl(prop); 239 if (blobUrl == null) { 240 blobUrl = ""; 241 } 242 jg.writeStringField("data", blobUrl); 243 jg.writeEndObject(); 244 } 245 246 /** 247 * Gets the full URL of where a blob can be downloaded. 248 * 249 * @since 7.2 250 */ 251 private String getBlobUrl(Property prop) { 252 DocumentModel doc = ctx.getParameter(ENTITY_TYPE); 253 if (doc == null) { 254 return ""; 255 } 256 DownloadService downloadService = Framework.getService(DownloadService.class); 257 String xpath = prop.getSchema().getName() + ":" + ComplexTypeImpl.canonicalXPath(prop.getPath().substring(1)); 258 String filename = ((Blob) prop.getValue()).getFilename(); 259 return ctx.getBaseUrl() + downloadService.getDownloadUrl(doc, xpath, filename); 260 } 261 262}