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