]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.cms/src/org/argeo/cms/integration/JcrReadServlet.java
Clean dependencies files.
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / integration / JcrReadServlet.java
1 package org.argeo.cms.integration;
2
3 import java.io.IOException;
4 import java.io.UnsupportedEncodingException;
5 import java.net.URLDecoder;
6 import java.nio.charset.StandardCharsets;
7 import java.util.ArrayList;
8 import java.util.LinkedHashMap;
9 import java.util.List;
10 import java.util.Map;
11 import java.util.TreeMap;
12
13 import javax.jcr.Node;
14 import javax.jcr.NodeIterator;
15 import javax.jcr.Property;
16 import javax.jcr.PropertyIterator;
17 import javax.jcr.PropertyType;
18 import javax.jcr.Repository;
19 import javax.jcr.RepositoryException;
20 import javax.jcr.Session;
21 import javax.jcr.Value;
22 import javax.jcr.nodetype.NodeType;
23 import javax.servlet.ServletException;
24 import javax.servlet.http.HttpServlet;
25 import javax.servlet.http.HttpServletRequest;
26 import javax.servlet.http.HttpServletResponse;
27
28 import org.apache.commons.io.IOUtils;
29 import org.apache.commons.logging.Log;
30 import org.apache.commons.logging.LogFactory;
31 import org.apache.jackrabbit.api.JackrabbitNode;
32 import org.apache.jackrabbit.api.JackrabbitValue;
33 import org.argeo.jcr.JcrUtils;
34
35 import com.fasterxml.jackson.core.JsonGenerator;
36 import com.fasterxml.jackson.databind.ObjectMapper;
37
38 /** Access a JCR repository via web services. */
39 public class JcrReadServlet extends HttpServlet {
40 private static final long serialVersionUID = 6536175260540484539L;
41 private final static Log log = LogFactory.getLog(JcrReadServlet.class);
42
43 protected final static String ACCEPT_HTTP_HEADER = "Accept";
44 protected final static String CONTENT_DISPOSITION_HTTP_HEADER = "Content-Disposition";
45
46 protected final static String OCTET_STREAM_CONTENT_TYPE = "application/octet-stream";
47 protected final static String XML_CONTENT_TYPE = "application/xml";
48 protected final static String JSON_CONTENT_TYPE = "application/json";
49
50 private final static String PARAM_VERBOSE = "verbose";
51 private final static String PARAM_DEPTH = "depth";
52
53 protected final static String JCR_NODES = "jcr:nodes";
54 // cf. javax.jcr.Property
55 protected final static String JCR_PATH = "path";
56 protected final static String JCR_NAME = "name";
57
58 protected final static String _JCR = "_jcr";
59 protected final static String JCR_PREFIX = "jcr:";
60 protected final static String REP_PREFIX = "rep:";
61
62 private Repository repository;
63 private Integer maxDepth = 8;
64
65 private ObjectMapper objectMapper = new ObjectMapper();
66
67 @Override
68 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
69 if (log.isTraceEnabled())
70 log.trace("Data service: " + req.getPathInfo());
71
72 String dataWorkspace = getWorkspace(req);
73 String jcrPath = getJcrPath(req);
74
75 boolean verbose = req.getParameter(PARAM_VERBOSE) != null && !req.getParameter(PARAM_VERBOSE).equals("false");
76 int depth = 1;
77 if (req.getParameter(PARAM_DEPTH) != null) {
78 depth = Integer.parseInt(req.getParameter(PARAM_DEPTH));
79 if (depth > maxDepth)
80 throw new RuntimeException("Depth " + depth + " is higher than maximum " + maxDepth);
81 }
82
83 Session session = null;
84 try {
85 // authentication
86 session = openJcrSession(req, resp, getRepository(), dataWorkspace);
87 if (!session.itemExists(jcrPath))
88 throw new RuntimeException("JCR node " + jcrPath + " does not exist");
89 Node node = session.getNode(jcrPath);
90
91 List<String> acceptHeader = readAcceptHeader(req);
92 if (!acceptHeader.isEmpty() && node.isNodeType(NodeType.NT_FILE)) {
93 resp.setContentType(OCTET_STREAM_CONTENT_TYPE);
94 resp.addHeader(CONTENT_DISPOSITION_HTTP_HEADER, "attachment; filename='" + node.getName() + "'");
95 IOUtils.copy(JcrUtils.getFileAsStream(node), resp.getOutputStream());
96 resp.flushBuffer();
97 } else {
98 if (!acceptHeader.isEmpty() && acceptHeader.get(0).equals(XML_CONTENT_TYPE)) {
99 // TODO Use req.startAsync(); ?
100 resp.setContentType(XML_CONTENT_TYPE);
101 session.exportSystemView(node.getPath(), resp.getOutputStream(), false, depth <= 1);
102 return;
103 }
104 if (!acceptHeader.isEmpty() && !acceptHeader.contains(JSON_CONTENT_TYPE)) {
105 if (log.isTraceEnabled())
106 log.warn("Content type " + acceptHeader + " in Accept header is not supported. Supported: "
107 + JSON_CONTENT_TYPE + " (default), " + XML_CONTENT_TYPE);
108 }
109 resp.setContentType(JSON_CONTENT_TYPE);
110 JsonGenerator jsonGenerator = getObjectMapper().getFactory().createGenerator(resp.getWriter());
111 jsonGenerator.writeStartObject();
112 writeNodeChildren(node, jsonGenerator, depth, verbose);
113 writeNodeProperties(node, jsonGenerator, verbose);
114 jsonGenerator.writeEndObject();
115 jsonGenerator.flush();
116 }
117 } catch (Exception e) {
118 new CmsExceptionsChain(e).writeAsJson(getObjectMapper(), resp);
119 } finally {
120 JcrUtils.logoutQuietly(session);
121 }
122 }
123
124 protected Session openJcrSession(HttpServletRequest req, HttpServletResponse resp, Repository repository,
125 String workspace) throws RepositoryException {
126 return workspace != null ? repository.login(workspace) : repository.login();
127 }
128
129 protected String getWorkspace(HttpServletRequest req) {
130 String path = req.getPathInfo();
131 try {
132 path = URLDecoder.decode(path, StandardCharsets.UTF_8.name());
133 } catch (UnsupportedEncodingException e) {
134 throw new IllegalArgumentException(e);
135 }
136 String[] pathTokens = path.split("/");
137 return pathTokens[1];
138 }
139
140 protected String getJcrPath(HttpServletRequest req) {
141 String path = req.getPathInfo();
142 try {
143 path = URLDecoder.decode(path, StandardCharsets.UTF_8.name());
144 } catch (UnsupportedEncodingException e) {
145 throw new IllegalArgumentException(e);
146 }
147 String[] pathTokens = path.split("/");
148 String domain = pathTokens[1];
149 String jcrPath = path.substring(domain.length() + 1);
150 return jcrPath;
151 }
152
153 protected List<String> readAcceptHeader(HttpServletRequest req) {
154 List<String> lst = new ArrayList<>();
155 String acceptHeader = req.getHeader(ACCEPT_HTTP_HEADER);
156 if (acceptHeader == null)
157 return lst;
158 // Enumeration<String> acceptHeader = req.getHeaders(ACCEPT_HTTP_HEADER);
159 // while (acceptHeader.hasMoreElements()) {
160 String[] arr = acceptHeader.split("\\.");
161 for (int i = 0; i < arr.length; i++) {
162 String str = arr[i].trim();
163 if (!"".equals(str))
164 lst.add(str);
165 }
166 // }
167 return lst;
168 }
169
170 protected void writeNodeProperties(Node node, JsonGenerator jsonGenerator, boolean verbose)
171 throws RepositoryException, IOException {
172 String jcrPath = node.getPath();
173 Map<String, Map<String, Property>> namespaces = new TreeMap<>();
174
175 PropertyIterator pit = node.getProperties();
176 properties: while (pit.hasNext()) {
177 Property property = pit.nextProperty();
178
179 final String propertyName = property.getName();
180 int columnIndex = propertyName.indexOf(':');
181 if (columnIndex > 0) {
182 // mark prefix with a '_' before the name of the object, according to JSON
183 // conventions to indicate a special value
184 String prefix = "_" + propertyName.substring(0, columnIndex);
185 String unqualifiedName = propertyName.substring(columnIndex + 1);
186 if (!namespaces.containsKey(prefix))
187 namespaces.put(prefix, new LinkedHashMap<String, Property>());
188 Map<String, Property> map = namespaces.get(prefix);
189 assert !map.containsKey(unqualifiedName);
190 map.put(unqualifiedName, property);
191 continue properties;
192 }
193
194 if (property.getType() == PropertyType.BINARY) {
195 if (!(node instanceof JackrabbitNode)) {
196 continue properties;// skip
197 }
198 }
199
200 writeProperty(propertyName, property, jsonGenerator);
201 }
202
203 for (String prefix : namespaces.keySet()) {
204 Map<String, Property> map = namespaces.get(prefix);
205 jsonGenerator.writeFieldName(prefix);
206 jsonGenerator.writeStartObject();
207 if (_JCR.equals(prefix)) {
208 jsonGenerator.writeStringField(JCR_NAME, node.getName());
209 jsonGenerator.writeStringField(JCR_PATH, jcrPath);
210 }
211 properties: for (String unqualifiedName : map.keySet()) {
212 Property property = map.get(unqualifiedName);
213 if (property.getType() == PropertyType.BINARY) {
214 if (!(node instanceof JackrabbitNode)) {
215 continue properties;// skip
216 }
217 }
218 writeProperty(unqualifiedName, property, jsonGenerator);
219 }
220 jsonGenerator.writeEndObject();
221 }
222 }
223
224 protected void writeProperty(String fieldName, Property property, JsonGenerator jsonGenerator)
225 throws RepositoryException, IOException {
226 if (!property.isMultiple()) {
227 jsonGenerator.writeFieldName(fieldName);
228 writePropertyValue(property.getType(), property.getValue(), jsonGenerator);
229 } else {
230 jsonGenerator.writeFieldName(fieldName);
231 jsonGenerator.writeStartArray();
232 Value[] values = property.getValues();
233 for (Value value : values) {
234 writePropertyValue(property.getType(), value, jsonGenerator);
235 }
236 jsonGenerator.writeEndArray();
237 }
238 }
239
240 protected void writePropertyValue(int type, Value value, JsonGenerator jsonGenerator)
241 throws RepositoryException, IOException {
242 if (type == PropertyType.DOUBLE)
243 jsonGenerator.writeNumber(value.getDouble());
244 else if (type == PropertyType.LONG)
245 jsonGenerator.writeNumber(value.getLong());
246 else if (type == PropertyType.BINARY) {
247 if (value instanceof JackrabbitValue) {
248 String contentIdentity = ((JackrabbitValue) value).getContentIdentity();
249 jsonGenerator.writeString("SHA256:" + contentIdentity);
250 } else {
251 // TODO write Base64 ?
252 jsonGenerator.writeNull();
253 }
254 } else
255 jsonGenerator.writeString(value.getString());
256 }
257
258 protected void writeNodeChildren(Node node, JsonGenerator jsonGenerator, int depth, boolean verbose)
259 throws RepositoryException, IOException {
260 if (!node.hasNodes())
261 return;
262 if (depth <= 0)
263 return;
264 NodeIterator nit;
265
266 nit = node.getNodes();
267 children: while (nit.hasNext()) {
268 Node child = nit.nextNode();
269 if (!verbose && child.getName().startsWith(REP_PREFIX)) {
270 continue children;// skip Jackrabbit auth metadata
271 }
272
273 jsonGenerator.writeFieldName(child.getName());
274 jsonGenerator.writeStartObject();
275 writeNodeChildren(child, jsonGenerator, depth - 1, verbose);
276 writeNodeProperties(child, jsonGenerator, verbose);
277 jsonGenerator.writeEndObject();
278 }
279 }
280
281 public void setRepository(Repository repository) {
282 this.repository = repository;
283 }
284
285 public void setMaxDepth(Integer maxDepth) {
286 this.maxDepth = maxDepth;
287 }
288
289 protected Repository getRepository() {
290 return repository;
291 }
292
293 protected ObjectMapper getObjectMapper() {
294 return objectMapper;
295 }
296
297 }