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