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.ListType; 045import org.nuxeo.ecm.core.schema.types.SimpleType; 046import org.nuxeo.ecm.core.schema.types.Type; 047import org.nuxeo.ecm.core.schema.types.primitives.BinaryType; 048import org.nuxeo.ecm.core.schema.types.primitives.BooleanType; 049import org.nuxeo.ecm.core.schema.types.primitives.DoubleType; 050import org.nuxeo.ecm.core.schema.types.primitives.IntegerType; 051import org.nuxeo.ecm.core.schema.types.primitives.LongType; 052import org.nuxeo.ecm.core.schema.types.resolver.ObjectResolver; 053import org.nuxeo.runtime.api.Framework; 054 055/** 056 * Convert {@link Property} to Json. 057 * <p> 058 * Format is: 059 * 060 * <pre> 061 * "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 062 * or 063 * true|false <- for boolean property 064 * or 065 * 123 <- for int property 066 * ... 067 * { <- for complex property 068 * "subProperty": ..., 069 * ... 070 * }, 071 * [ ... ] <- for list property 072 * } 073 * </pre> 074 * 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 { 096 if (prop.isPhantom()) { 097 jg.writeNull(); 098 } else if (prop instanceof BlobProperty) { // a blob 099 writeBlobProperty(jg, prop); 100 } else { // a complex property 101 writeComplexProperty(jg, prop); 102 } 103 } 104 } 105 106 protected void writeScalarProperty(JsonGenerator jg, Property prop) throws IOException { 107 Type type = prop.getType(); 108 Object value = prop.getValue(); 109 if (!fetchProperty(jg, prop.getType().getObjectResolver(), value, prop.getXPath())) { 110 writeScalarPropertyValue(jg, ((SimpleType) type).getPrimitiveType(), value); 111 } 112 } 113 114 private void writeScalarPropertyValue(JsonGenerator jg, Type type, Object value) throws IOException { 115 if (value == null) { 116 jg.writeNull(); 117 } else if (type instanceof BooleanType) { 118 jg.writeBoolean((Boolean) value); 119 } else if (type instanceof LongType) { 120 jg.writeNumber((Long) value); 121 } else if (type instanceof DoubleType) { 122 jg.writeNumber((Double) value); 123 } else if (type instanceof IntegerType) { 124 jg.writeNumber((Integer) value); 125 } else if (type instanceof BinaryType) { 126 jg.writeBinary((byte[]) value); 127 } else { 128 jg.writeString(type.encode(value)); 129 } 130 } 131 132 protected boolean fetchProperty(JsonGenerator jg, ObjectResolver resolver, Object value, String path) 133 throws IOException { 134 if (value == null) { 135 return false; 136 } 137 boolean fetched = false; 138 if (resolver != null) { 139 String genericPropertyPath = path.replaceAll("/[0-9]*/", "/*/"); 140 Set<String> fetchElements = ctx.getFetched(ENTITY_TYPE); 141 boolean fetch = false; 142 for (String fetchElement : fetchElements) { 143 if ("properties".equals(fetchElement) || path.startsWith(fetchElement) 144 || genericPropertyPath.startsWith(fetchElement)) { 145 fetch = true; 146 break; 147 } 148 } 149 if (fetch) { 150 Object object = resolver.fetch(value); 151 if (object != null) { 152 try { 153 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 154 writeEntity(object, baos); 155 jg.writeRawValue(baos.toString()); 156 fetched = true; 157 } catch (MarshallingException e) { 158 log.error("Unable to marshall as json the entity referenced by the property " + path, e); 159 } 160 } 161 } 162 } 163 return fetched; 164 } 165 166 protected void writeListProperty(JsonGenerator jg, Property prop) throws IOException { 167 jg.writeStartArray(); 168 if (prop instanceof ArrayProperty) { 169 Object[] ar = (Object[]) prop.getValue(); 170 if (ar == null) { 171 jg.writeEndArray(); 172 return; 173 } 174 Type itemType = ((ListType) prop.getType()).getFieldType(); 175 ObjectResolver resolver = itemType.getObjectResolver(); 176 String path = prop.getXPath(); 177 for (Object o : ar) { 178 if (!fetchProperty(jg, resolver, o, path)) { 179 writeScalarPropertyValue(jg, ((SimpleType) itemType).getPrimitiveType(), o); 180 } 181 } 182 } else { 183 ListProperty listp = (ListProperty) prop; 184 for (Property p : listp.getChildren()) { 185 writeProperty(jg, p); 186 } 187 } 188 jg.writeEndArray(); 189 } 190 191 protected void writeComplexProperty(JsonGenerator jg, Property prop) throws IOException { 192 jg.writeStartObject(); 193 for (Property p : prop.getChildren()) { 194 jg.writeFieldName(p.getName()); 195 writeProperty(jg, p); 196 } 197 jg.writeEndObject(); 198 } 199 200 protected void writeBlobProperty(JsonGenerator jg, Property prop) throws IOException { 201 Blob blob = (Blob) prop.getValue(); 202 if (blob == null) { 203 jg.writeNull(); 204 return; 205 } 206 jg.writeStartObject(); 207 String v = blob.getFilename(); 208 if (v == null) { 209 jg.writeNullField("name"); 210 } else { 211 jg.writeStringField("name", v); 212 } 213 v = blob.getMimeType(); 214 if (v == null) { 215 jg.writeNullField("mime-type"); 216 } else { 217 jg.writeStringField("mime-type", v); 218 } 219 v = blob.getEncoding(); 220 if (v == null) { 221 jg.writeNullField("encoding"); 222 } else { 223 jg.writeStringField("encoding", v); 224 } 225 v = blob.getDigestAlgorithm(); 226 if (v == null) { 227 jg.writeNullField("digestAlgorithm"); 228 } else { 229 jg.writeStringField("digestAlgorithm", v); 230 } 231 v = blob.getDigest(); 232 if (v == null) { 233 jg.writeNullField("digest"); 234 } else { 235 jg.writeStringField("digest", v); 236 } 237 jg.writeStringField("length", Long.toString(blob.getLength())); 238 239 String blobUrl = getBlobUrl(prop); 240 if (blobUrl == null) { 241 blobUrl = ""; 242 } 243 jg.writeStringField("data", blobUrl); 244 jg.writeEndObject(); 245 } 246 247 /** 248 * Gets the full URL of where a blob can be downloaded. 249 * 250 * @since 7.2 251 */ 252 private String getBlobUrl(Property prop) { 253 DocumentModel doc = ctx.getParameter(ENTITY_TYPE); 254 if (doc == null) { 255 return ""; 256 } 257 DownloadService downloadService = Framework.getService(DownloadService.class); 258 259 String xpath = prop.getXPath(); 260 // if no prefix, use schema name as prefix: 261 if (!xpath.contains(":")) { 262 xpath = prop.getSchema().getName() + ":" + xpath; 263 } 264 265 String filename = ((Blob) prop.getValue()).getFilename(); 266 return ctx.getBaseUrl() + downloadService.getDownloadUrl(doc, xpath, filename); 267 } 268 269}