001/* 002 * (C) Copyright 2015 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 */ 019 020package org.nuxeo.ecm.core.io.marshallers.json.document; 021 022import static org.nuxeo.ecm.core.io.marshallers.json.document.DocumentModelJsonWriter.ENTITY_TYPE; 023import static org.nuxeo.ecm.core.io.registry.reflect.Instantiations.SINGLETON; 024import static org.nuxeo.ecm.core.io.registry.reflect.Priorities.REFERENCE; 025 026import java.io.ByteArrayOutputStream; 027import java.io.IOException; 028import java.util.Set; 029 030import org.apache.commons.logging.Log; 031import org.apache.commons.logging.LogFactory; 032import org.codehaus.jackson.JsonGenerator; 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.ComplexTypeImpl; 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.getPath())) { 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 propertyPath = path.replaceFirst("/", ""); 140 String genericPropertyPath = propertyPath.replaceAll("\\[[0-9]*\\]", ""); 141 Set<String> fetchElements = ctx.getFetched(ENTITY_TYPE); 142 boolean fetch = false; 143 for (String fetchElement : fetchElements) { 144 if ("properties".equals(fetchElement) || propertyPath.startsWith(fetchElement) 145 || genericPropertyPath.startsWith(fetchElement)) { 146 fetch = true; 147 break; 148 } 149 } 150 if (fetch) { 151 Object object = resolver.fetch(value); 152 if (object != null) { 153 try { 154 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 155 writeEntity(object, baos); 156 jg.writeRawValue(baos.toString()); 157 fetched = true; 158 } catch (MarshallingException e) { 159 log.error("Unable to marshall as json the entity referenced by the property " + path, e); 160 } 161 } 162 } 163 } 164 return fetched; 165 } 166 167 protected void writeListProperty(JsonGenerator jg, Property prop) throws IOException { 168 jg.writeStartArray(); 169 if (prop instanceof ArrayProperty) { 170 Object[] ar = (Object[]) prop.getValue(); 171 if (ar == null) { 172 jg.writeEndArray(); 173 return; 174 } 175 Type itemType = ((ListType) prop.getType()).getFieldType(); 176 ObjectResolver resolver = itemType.getObjectResolver(); 177 String path = prop.getPath(); 178 for (Object o : ar) { 179 if (!fetchProperty(jg, resolver, o, path)) { 180 writeScalarPropertyValue(jg, ((SimpleType) itemType).getPrimitiveType(), o); 181 } 182 } 183 } else { 184 ListProperty listp = (ListProperty) prop; 185 for (Property p : listp.getChildren()) { 186 writeProperty(jg, p); 187 } 188 } 189 jg.writeEndArray(); 190 } 191 192 protected void writeComplexProperty(JsonGenerator jg, Property prop) throws IOException { 193 jg.writeStartObject(); 194 for (Property p : prop.getChildren()) { 195 jg.writeFieldName(p.getName()); 196 writeProperty(jg, p); 197 } 198 jg.writeEndObject(); 199 } 200 201 protected void writeBlobProperty(JsonGenerator jg, Property prop) throws IOException { 202 Blob blob = (Blob) prop.getValue(); 203 if (blob == null) { 204 jg.writeNull(); 205 return; 206 } 207 jg.writeStartObject(); 208 String v = blob.getFilename(); 209 if (v == null) { 210 jg.writeNullField("name"); 211 } else { 212 jg.writeStringField("name", v); 213 } 214 v = blob.getMimeType(); 215 if (v == null) { 216 jg.writeNullField("mime-type"); 217 } else { 218 jg.writeStringField("mime-type", v); 219 } 220 v = blob.getEncoding(); 221 if (v == null) { 222 jg.writeNullField("encoding"); 223 } else { 224 jg.writeStringField("encoding", v); 225 } 226 v = blob.getDigestAlgorithm(); 227 if (v == null) { 228 jg.writeNullField("digestAlgorithm"); 229 } else { 230 jg.writeStringField("digestAlgorithm", v); 231 } 232 v = blob.getDigest(); 233 if (v == null) { 234 jg.writeNullField("digest"); 235 } else { 236 jg.writeStringField("digest", v); 237 } 238 jg.writeStringField("length", Long.toString(blob.getLength())); 239 240 String blobUrl = getBlobUrl(prop); 241 if (blobUrl == null) { 242 blobUrl = ""; 243 } 244 jg.writeStringField("data", blobUrl); 245 jg.writeEndObject(); 246 } 247 248 /** 249 * Gets the full URL of where a blob can be downloaded. 250 * 251 * @since 7.2 252 */ 253 private String getBlobUrl(Property prop) { 254 DocumentModel doc = ctx.getParameter(ENTITY_TYPE); 255 if (doc == null) { 256 return ""; 257 } 258 DownloadService downloadService = Framework.getService(DownloadService.class); 259 String xpath = prop.getSchema().getName() + ":" + ComplexTypeImpl.canonicalXPath(prop.getPath().substring(1)); 260 String filename = ((Blob) prop.getValue()).getFilename(); 261 return ctx.getBaseUrl() + downloadService.getDownloadUrl(doc, xpath, filename); 262 } 263 264}