1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.rest.delivery.jcr;
35
36 import info.magnolia.cms.util.DateUtil;
37 import info.magnolia.jcr.util.PropertyUtil;
38
39 import java.io.IOException;
40 import java.io.OutputStream;
41 import java.lang.annotation.Annotation;
42 import java.lang.reflect.Type;
43 import java.util.Set;
44
45 import javax.jcr.Node;
46 import javax.jcr.NodeIterator;
47 import javax.jcr.Property;
48 import javax.jcr.PropertyIterator;
49 import javax.jcr.PropertyType;
50 import javax.jcr.RepositoryException;
51 import javax.jcr.Value;
52 import javax.json.Json;
53 import javax.json.JsonArrayBuilder;
54 import javax.json.stream.JsonGenerator;
55 import javax.ws.rs.Produces;
56 import javax.ws.rs.WebApplicationException;
57 import javax.ws.rs.core.MediaType;
58 import javax.ws.rs.core.MultivaluedMap;
59 import javax.ws.rs.ext.MessageBodyWriter;
60 import javax.ws.rs.ext.Provider;
61
62 import org.apache.commons.codec.binary.Base64;
63 import org.apache.commons.io.IOUtils;
64 import org.apache.jackrabbit.JcrConstants;
65 import org.slf4j.Logger;
66 import org.slf4j.LoggerFactory;
67
68 import com.google.common.collect.Sets;
69
70
71
72
73 @Provider
74 @Produces({ MediaType.APPLICATION_JSON })
75 public class NodeWriter implements MessageBodyWriter<Node> {
76
77 private static final Logger log = LoggerFactory.getLogger(NodeWriter.class);
78
79 private static final String SAME_NAME_SIBLING_PREFIX = "_";
80 private static final String META_PROPERTY_PREFIX = "@";
81 private static final String PATH_PROPERTY = META_PROPERTY_PREFIX + "path";
82 private static final String NAME_PROPERTY = META_PROPERTY_PREFIX + "name";
83 private static final String CHILD_PROPERTY = META_PROPERTY_PREFIX + "nodes";
84 private static final String UUID_PROPERTY = META_PROPERTY_PREFIX + "id";
85 private static final String NODE_TYPE_PROPERTY = META_PROPERTY_PREFIX + "nodeType";
86 private static final Set<String> additionalSpecialProps = Sets.newHashSet(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.JCR_UUID);
87
88 @Override
89 public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
90 return Node.class.isAssignableFrom(type);
91 }
92
93 @Override
94 public long getSize(Node node, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
95
96 return -1;
97 }
98
99 @Override
100 public void writeTo(Node node, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException {
101 JsonGenerator jsonGenerator = entityStream instanceof OutputStreamWrapper ?
102 ((OutputStreamWrapper) entityStream).getJsonGenerator() :
103 Json.createGenerator(entityStream);
104
105 writeNode(jsonGenerator, node, false);
106
107 if (!OutputStreamWrapper.isWrapped(entityStream)) {
108 jsonGenerator.close();
109 }
110 }
111
112
113 void writeNode(JsonGenerator jsonGenerator, Node node, boolean objectKeyNeeded) {
114 try {
115 enterNode(jsonGenerator, node, objectKeyNeeded);
116
117 writeSpecialProperties(jsonGenerator, node);
118
119 PropertyIterator properties = node.getProperties();
120 while (properties.hasNext()) {
121 Property property = properties.nextProperty();
122 if (isAllowedJcrProperty(property.getName())) {
123 writeProperty(jsonGenerator, property, node.hasNode(property.getName()));
124 }
125 }
126
127 NodeIterator children = node.getNodes();
128
129 JsonArrayBuilder childNodeNames = Json.createArrayBuilder();
130 while (children.hasNext()) {
131 Node child = children.nextNode();
132 writeNode(jsonGenerator, child, true);
133 childNodeNames.add(child.getName());
134 }
135 jsonGenerator.write(CHILD_PROPERTY, childNodeNames.build());
136
137 leaveNode(jsonGenerator);
138 } catch (RepositoryException re) {
139 log.error(re.getMessage());
140 }
141 }
142
143 protected boolean isAllowedJcrProperty(String propertyName) {
144 return !additionalSpecialProps.contains(propertyName);
145 }
146
147 private void enterNode(JsonGenerator jsonGenerator, Node node, boolean objectKeyNeeded) throws RepositoryException {
148 if (objectKeyNeeded) {
149 jsonGenerator.writeStartObject(node.getName());
150 } else {
151 jsonGenerator.writeStartObject();
152 }
153 }
154
155 private void leaveNode(JsonGenerator jsonGenerator) {
156 jsonGenerator.writeEnd();
157 }
158
159 private void writeProperty(JsonGenerator jsonGenerator, Property property, boolean shouldAddPrefix) throws RepositoryException {
160 String propertyName = (shouldAddPrefix ? SAME_NAME_SIBLING_PREFIX : "") + property.getName();
161 if (property.isMultiple()) {
162 jsonGenerator.writeStartArray(propertyName);
163 for (Value value : property.getValues()) {
164 jsonGenerator.write(getValueString(value));
165 }
166 jsonGenerator.writeEnd();
167 } else {
168 jsonGenerator.write(propertyName, getValueString(property.getValue()));
169 }
170 }
171
172 private void writeSpecialProperties(JsonGenerator jsonGenerator, Node node) throws RepositoryException {
173 jsonGenerator.write(NAME_PROPERTY, node.getName());
174 jsonGenerator.write(PATH_PROPERTY, node.getPath());
175 jsonGenerator.write(UUID_PROPERTY, node.getIdentifier());
176 jsonGenerator.write(NODE_TYPE_PROPERTY, node.getPrimaryNodeType().getName());
177 }
178
179 private String getValueString(Value value) throws RepositoryException {
180 switch (value.getType()) {
181 case PropertyType.DATE:
182 return DateUtil.format(value.getDate().getTime(), null);
183 case PropertyType.BINARY:
184 return getBinaryString(value);
185 case PropertyType.DECIMAL:
186 return String.valueOf(value.getDecimal());
187 case PropertyType.NAME:
188 case PropertyType.URI:
189 return value.getString();
190 default:
191 return PropertyUtil.getValueString(value);
192 }
193 }
194
195
196
197
198
199
200
201 private String getBinaryString(Value propertyValue) throws RepositoryException {
202
203 byte[] decodedPropertyValue;
204 try {
205 decodedPropertyValue = Base64.encodeBase64(IOUtils.toByteArray(propertyValue.getBinary().getStream()));
206 } catch (IOException e) {
207 throw new RepositoryException(e);
208 }
209 return new String(decodedPropertyValue);
210 }
211 }