From 1ffd6c22683f3c03e4c1193ac6bc33c77e155d2b Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Fri, 24 Jan 2020 10:46:40 +0100 Subject: [PATCH] Make JCR JSON format easier to use. --- .../cms/integration/CmsExceptionsChain.java | 6 + .../{JcrServlet.java => JcrReadServlet.java} | 194 +++++++++++------- .../cms/integration/JcrWriteServlet.java | 54 +++++ 3 files changed, 184 insertions(+), 70 deletions(-) rename org.argeo.cms/src/org/argeo/cms/integration/{JcrServlet.java => JcrReadServlet.java} (52%) create mode 100644 org.argeo.cms/src/org/argeo/cms/integration/JcrWriteServlet.java diff --git a/org.argeo.cms/src/org/argeo/cms/integration/CmsExceptionsChain.java b/org.argeo.cms/src/org/argeo/cms/integration/CmsExceptionsChain.java index d7a51b74a..a659a7e9c 100644 --- a/org.argeo.cms/src/org/argeo/cms/integration/CmsExceptionsChain.java +++ b/org.argeo.cms/src/org/argeo/cms/integration/CmsExceptionsChain.java @@ -5,6 +5,8 @@ import java.io.Writer; import javax.servlet.http.HttpServletResponse; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.argeo.util.ExceptionsChain; import com.fasterxml.jackson.core.JsonGenerator; @@ -13,12 +15,16 @@ import com.fasterxml.jackson.databind.ObjectMapper; /** Serialisable wrapper of a {@link Throwable}. */ public class CmsExceptionsChain extends ExceptionsChain { + public final static Log log = LogFactory.getLog(CmsExceptionsChain.class); + public CmsExceptionsChain() { super(); } public CmsExceptionsChain(Throwable exception) { super(exception); + if (log.isDebugEnabled()) + log.error("Exception chain", exception); } public String toJsonString(ObjectMapper objectMapper) { diff --git a/org.argeo.cms/src/org/argeo/cms/integration/JcrServlet.java b/org.argeo.cms/src/org/argeo/cms/integration/JcrReadServlet.java similarity index 52% rename from org.argeo.cms/src/org/argeo/cms/integration/JcrServlet.java rename to org.argeo.cms/src/org/argeo/cms/integration/JcrReadServlet.java index a1113261f..7ef1795d0 100644 --- a/org.argeo.cms/src/org/argeo/cms/integration/JcrServlet.java +++ b/org.argeo.cms/src/org/argeo/cms/integration/JcrReadServlet.java @@ -1,6 +1,9 @@ package org.argeo.cms.integration; import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.TreeMap; import javax.jcr.Node; import javax.jcr.NodeIterator; @@ -17,7 +20,6 @@ import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -29,17 +31,22 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.ObjectMapper; /** Access a JCR repository via web services. */ -public class JcrServlet extends HttpServlet { +public class JcrReadServlet extends HttpServlet { private static final long serialVersionUID = 6536175260540484539L; - private final static Log log = LogFactory.getLog(JcrServlet.class); + private final static Log log = LogFactory.getLog(JcrReadServlet.class); private final static String PARAM_VERBOSE = "verbose"; private final static String PARAM_DEPTH = "depth"; public final static String JCR_NODES = "jcr:nodes"; - public final static String JCR_PATH = "jcr:path"; - public final static String JCR_NAME = "jcr:name"; + // cf. javax.jcr.Property + public final static String JCR_PATH = "path"; + public final static String JCR_NAME = "name"; +// public final static String JCR_ID = "id"; + final static String JCR_ = "jcr_"; + final static String JCR_PREFIX = "jcr:"; + final static String REP_PREFIX = "rep:"; private Repository repository; private Integer maxDepth = 8; @@ -65,7 +72,7 @@ public class JcrServlet extends HttpServlet { Session session = null; try { // authentication - session = openJcrSession(req, resp, repository, dataWorkspace); + session = openJcrSession(req, resp, getRepository(), dataWorkspace); if (!session.itemExists(jcrPath)) throw new RuntimeException("JCR node " + jcrPath + " does not exist"); Node node = session.getNode(jcrPath); @@ -76,47 +83,15 @@ public class JcrServlet extends HttpServlet { resp.flushBuffer(); } else { resp.setContentType("application/json"); - JsonGenerator jsonGenerator = objectMapper.getFactory().createGenerator(resp.getWriter()); + JsonGenerator jsonGenerator = getObjectMapper().getFactory().createGenerator(resp.getWriter()); jsonGenerator.writeStartObject(); - writeNodeProperties(node, jsonGenerator, verbose); writeNodeChildren(node, jsonGenerator, depth, verbose); + writeNodeProperties(node, jsonGenerator, verbose); jsonGenerator.writeEndObject(); jsonGenerator.flush(); } } catch (Exception e) { - new CmsExceptionsChain(e).writeAsJson(objectMapper, resp); - } finally { - JcrUtils.logoutQuietly(session); - } - } - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - if (log.isTraceEnabled()) - log.trace("Data service: " + req.getPathInfo()); - - String dataWorkspace = getWorkspace(req); - String jcrPath = getJcrPath(req); - - Session session = null; - try { - // authentication - session = openJcrSession(req, resp, repository, dataWorkspace); - if (!session.itemExists(jcrPath)) { - String parentPath = FilenameUtils.getFullPathNoEndSeparator(jcrPath); - String fileName = FilenameUtils.getName(jcrPath); - Node folderNode = JcrUtils.mkfolders(session, parentPath); - byte[] bytes = IOUtils.toByteArray(req.getInputStream()); - JcrUtils.copyBytesAsFile(folderNode, fileName, bytes); - } else { - Node node = session.getNode(jcrPath); - if (!node.isNodeType(NodeType.NT_FILE)) - throw new IllegalArgumentException("Node " + jcrPath + " exists but is not a file"); - byte[] bytes = IOUtils.toByteArray(req.getInputStream()); - JcrUtils.copyBytesAsFile(node.getParent(), node.getName(), bytes); - } - } catch (Exception e) { - new CmsExceptionsChain(e).writeAsJson(objectMapper, resp); + new CmsExceptionsChain(e).writeAsJson(getObjectMapper(), resp); } finally { JcrUtils.logoutQuietly(session); } @@ -143,45 +118,98 @@ public class JcrServlet extends HttpServlet { protected void writeNodeProperties(Node node, JsonGenerator jsonGenerator, boolean verbose) throws RepositoryException, IOException { String jcrPath = node.getPath(); - jsonGenerator.writeStringField(JcrServlet.JCR_NAME, node.getName()); - jsonGenerator.writeStringField(JcrServlet.JCR_PATH, jcrPath); +// jsonGenerator.writeStringField(JCR_NAME, node.getName()); +// jsonGenerator.writeStringField(JCR_PATH, jcrPath); +// // meta data +// if (verbose) { +// jsonGenerator.writeStringField(JCR_ID, node.getIdentifier()); +// } + + Map> namespaces = new TreeMap<>(); PropertyIterator pit = node.getProperties(); properties: while (pit.hasNext()) { Property property = pit.nextProperty(); - if (!verbose) { - if (property.getName().equals("jcr:primaryType") || property.getName().equals("jcr:mixinTypes") - || property.getName().equals("jcr:created") || property.getName().equals("jcr:createdBy") - || property.getName().equals("jcr:lastModified") - || property.getName().equals("jcr:lastModifiedBy")) { - continue properties;// skip - } + final String propertyName = property.getName(); + int columnIndex = propertyName.indexOf(':'); + if (columnIndex > 0) { + String prefix = propertyName.substring(0, columnIndex) + "_"; + String unqualifiedName = propertyName.substring(columnIndex + 1); + if (!namespaces.containsKey(prefix)) + namespaces.put(prefix, new LinkedHashMap()); + Map map = namespaces.get(prefix); + assert !map.containsKey(unqualifiedName); + map.put(unqualifiedName, property); + continue properties; } +// String fieldName = property.getName(); +// switch (fieldName) { +// case "jcr:title": +// case "jcr:description": +// case "jcr:created": +// case "jcr:createdBy": +// case "jcr:lastModified": +// case "jcr:lastModifiedBy": +// fieldName = fieldName.substring(JCR_PREFIX.length()); +// } +// +// if (!verbose) { +//// if (property.getName().equals("jcr:primaryType") || property.getName().equals("jcr:mixinTypes") +//// || property.getName().equals("jcr:created") || property.getName().equals("jcr:createdBy") +//// || property.getName().equals("jcr:lastModified") +//// || property.getName().equals("jcr:lastModifiedBy")) { +//// continue properties;// skip +//// } +// if (fieldName.startsWith(JCR_PREFIX)) +// continue properties; +// } + if (property.getType() == PropertyType.BINARY) { if (!(node instanceof JackrabbitNode)) { continue properties;// skip } } - if (!property.isMultiple()) { - jsonGenerator.writeFieldName(property.getName()); - writePropertyValue(property.getType(), property.getValue(), jsonGenerator); - } else { - jsonGenerator.writeFieldName(property.getName()); - jsonGenerator.writeStartArray(); - Value[] values = property.getValues(); - for (Value value : values) { - writePropertyValue(property.getType(), value, jsonGenerator); + writeProperty(propertyName, property, jsonGenerator); + } + + for (String prefix : namespaces.keySet()) { + Map map = namespaces.get(prefix); + jsonGenerator.writeFieldName(prefix); + jsonGenerator.writeStartObject(); + if (JCR_.equals(prefix)) { + jsonGenerator.writeStringField(JCR_NAME, node.getName()); + jsonGenerator.writeStringField(JCR_PATH, jcrPath); +// jsonGenerator.writeStringField(JCR_ID, node.getIdentifier()); + } + properties: for (String unqualifiedName : map.keySet()) { + Property property = map.get(unqualifiedName); + if (property.getType() == PropertyType.BINARY) { + if (!(node instanceof JackrabbitNode)) { + continue properties;// skip + } } - jsonGenerator.writeEndArray(); + writeProperty(unqualifiedName, property, jsonGenerator); } + jsonGenerator.writeEndObject(); } + } - // meta data - if (verbose) { - jsonGenerator.writeStringField("jcr:identifier", node.getIdentifier()); + protected void writeProperty(String fieldName, Property property, JsonGenerator jsonGenerator) + throws RepositoryException, IOException { + if (!property.isMultiple()) { + jsonGenerator.writeFieldName(fieldName); + writePropertyValue(property.getType(), property.getValue(), jsonGenerator); + } else { + jsonGenerator.writeFieldName(fieldName); + jsonGenerator.writeStartArray(); + Value[] values = property.getValues(); + for (Value value : values) { + writePropertyValue(property.getType(), value, jsonGenerator); + } + jsonGenerator.writeEndArray(); } } @@ -196,6 +224,7 @@ public class JcrServlet extends HttpServlet { String contentIdentity = ((JackrabbitValue) value).getContentIdentity(); jsonGenerator.writeString("SHA256:" + contentIdentity); } else { + // TODO write Base64 ? jsonGenerator.writeNull(); } } else @@ -208,22 +237,39 @@ public class JcrServlet extends HttpServlet { return; if (depth <= 0) return; - NodeIterator nit = node.getNodes(); - jsonGenerator.writeFieldName(JcrServlet.JCR_NODES); - jsonGenerator.writeStartArray(); + NodeIterator nit; + + nit = node.getNodes(); children: while (nit.hasNext()) { Node child = nit.nextNode(); - - if (child.getName().startsWith("rep:")) { + if (!verbose && child.getName().startsWith(REP_PREFIX)) { continue children;// skip Jackrabbit auth metadata } + jsonGenerator.writeFieldName(child.getName()); jsonGenerator.writeStartObject(); - writeNodeProperties(child, jsonGenerator, verbose); writeNodeChildren(child, jsonGenerator, depth - 1, verbose); + writeNodeProperties(child, jsonGenerator, verbose); jsonGenerator.writeEndObject(); } - jsonGenerator.writeEndArray(); + + // old +// nit = node.getNodes(); +// jsonGenerator.writeFieldName(JcrServlet.JCR_NODES); +// jsonGenerator.writeStartArray(); +// children: while (nit.hasNext()) { +// Node child = nit.nextNode(); +// +// if (child.getName().startsWith(REP_PREFIX)) { +// continue children;// skip Jackrabbit auth metadata +// } +// +// jsonGenerator.writeStartObject(); +// writeNodeProperties(child, jsonGenerator, verbose); +// writeNodeChildren(child, jsonGenerator, depth - 1, verbose); +// jsonGenerator.writeEndObject(); +// } +// jsonGenerator.writeEndArray(); } public void setRepository(Repository repository) { @@ -234,4 +280,12 @@ public class JcrServlet extends HttpServlet { this.maxDepth = maxDepth; } + protected Repository getRepository() { + return repository; + } + + protected ObjectMapper getObjectMapper() { + return objectMapper; + } + } diff --git a/org.argeo.cms/src/org/argeo/cms/integration/JcrWriteServlet.java b/org.argeo.cms/src/org/argeo/cms/integration/JcrWriteServlet.java new file mode 100644 index 000000000..d678ccbbe --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/integration/JcrWriteServlet.java @@ -0,0 +1,54 @@ +package org.argeo.cms.integration; + +import java.io.IOException; + +import javax.jcr.Node; +import javax.jcr.Session; +import javax.jcr.nodetype.NodeType; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.jcr.JcrUtils; + +/** Access a JCR repository via web services. */ +public class JcrWriteServlet extends JcrReadServlet { + private static final long serialVersionUID = 17272653843085492L; + private final static Log log = LogFactory.getLog(JcrWriteServlet.class); + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + if (log.isTraceEnabled()) + log.trace("Data service: " + req.getPathInfo()); + + String dataWorkspace = getWorkspace(req); + String jcrPath = getJcrPath(req); + + Session session = null; + try { + // authentication + session = openJcrSession(req, resp, getRepository(), dataWorkspace); + if (!session.itemExists(jcrPath)) { + String parentPath = FilenameUtils.getFullPathNoEndSeparator(jcrPath); + String fileName = FilenameUtils.getName(jcrPath); + Node folderNode = JcrUtils.mkdirs(session, parentPath); + byte[] bytes = IOUtils.toByteArray(req.getInputStream()); + JcrUtils.copyBytesAsFile(folderNode, fileName, bytes); + } else { + Node node = session.getNode(jcrPath); + if (!node.isNodeType(NodeType.NT_FILE)) + throw new IllegalArgumentException("Node " + jcrPath + " exists but is not a file"); + byte[] bytes = IOUtils.toByteArray(req.getInputStream()); + JcrUtils.copyBytesAsFile(node.getParent(), node.getName(), bytes); + } + } catch (Exception e) { + new CmsExceptionsChain(e).writeAsJson(getObjectMapper(), resp); + } finally { + JcrUtils.logoutQuietly(session); + } + } +} -- 2.30.2