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; import javax.jcr.Property; import javax.jcr.PropertyIterator; import javax.jcr.PropertyType; import javax.jcr.Repository; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Value; import javax.jcr.nodetype.NodeType; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.jackrabbit.api.JackrabbitNode; import org.apache.jackrabbit.api.JackrabbitValue; import org.argeo.jcr.JcrUtils; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.ObjectMapper; /** Access a JCR repository via web services. */ public class JcrReadServlet extends HttpServlet { private static final long serialVersionUID = 6536175260540484539L; 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"; // 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; private ObjectMapper objectMapper = new ObjectMapper(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if (log.isTraceEnabled()) log.trace("Data service: " + req.getPathInfo()); String dataWorkspace = getWorkspace(req); String jcrPath = getJcrPath(req); boolean verbose = req.getParameter(PARAM_VERBOSE) != null && !req.getParameter(PARAM_VERBOSE).equals("false"); int depth = 1; if (req.getParameter(PARAM_DEPTH) != null) { depth = Integer.parseInt(req.getParameter(PARAM_DEPTH)); if (depth > maxDepth) throw new RuntimeException("Depth " + depth + " is higher than maximum " + maxDepth); } Session session = null; try { // authentication session = openJcrSession(req, resp, getRepository(), dataWorkspace); if (!session.itemExists(jcrPath)) throw new RuntimeException("JCR node " + jcrPath + " does not exist"); Node node = session.getNode(jcrPath); if (node.isNodeType(NodeType.NT_FILE)) { resp.setContentType("application/octet-stream"); resp.addHeader("Content-Disposition", "attachment; filename='" + node.getName() + "'"); IOUtils.copy(JcrUtils.getFileAsStream(node), resp.getOutputStream()); resp.flushBuffer(); } else { resp.setContentType("application/json"); JsonGenerator jsonGenerator = getObjectMapper().getFactory().createGenerator(resp.getWriter()); jsonGenerator.writeStartObject(); writeNodeChildren(node, jsonGenerator, depth, verbose); writeNodeProperties(node, jsonGenerator, verbose); jsonGenerator.writeEndObject(); jsonGenerator.flush(); } } catch (Exception e) { new CmsExceptionsChain(e).writeAsJson(getObjectMapper(), resp); } finally { JcrUtils.logoutQuietly(session); } } protected Session openJcrSession(HttpServletRequest req, HttpServletResponse resp, Repository repository, String workspace) throws RepositoryException { return workspace != null ? repository.login(workspace) : repository.login(); } /** * To be overridden. * * @return the workspace to use, or null if default should be used. */ protected String getWorkspace(HttpServletRequest req) { return null; } protected String getJcrPath(HttpServletRequest req) { return req.getPathInfo(); } protected void writeNodeProperties(Node node, JsonGenerator jsonGenerator, boolean verbose) throws RepositoryException, IOException { String jcrPath = node.getPath(); // 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(); 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 } } 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 } } writeProperty(unqualifiedName, property, jsonGenerator); } jsonGenerator.writeEndObject(); } } 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(); } } protected void writePropertyValue(int type, Value value, JsonGenerator jsonGenerator) throws RepositoryException, IOException { if (type == PropertyType.DOUBLE) jsonGenerator.writeNumber(value.getDouble()); else if (type == PropertyType.LONG) jsonGenerator.writeNumber(value.getLong()); else if (type == PropertyType.BINARY) { if (value instanceof JackrabbitValue) { String contentIdentity = ((JackrabbitValue) value).getContentIdentity(); jsonGenerator.writeString("SHA256:" + contentIdentity); } else { // TODO write Base64 ? jsonGenerator.writeNull(); } } else jsonGenerator.writeString(value.getString()); } protected void writeNodeChildren(Node node, JsonGenerator jsonGenerator, int depth, boolean verbose) throws RepositoryException, IOException { if (!node.hasNodes()) return; if (depth <= 0) return; NodeIterator nit; nit = node.getNodes(); children: while (nit.hasNext()) { Node child = nit.nextNode(); if (!verbose && child.getName().startsWith(REP_PREFIX)) { continue children;// skip Jackrabbit auth metadata } jsonGenerator.writeFieldName(child.getName()); jsonGenerator.writeStartObject(); writeNodeChildren(child, jsonGenerator, depth - 1, verbose); writeNodeProperties(child, jsonGenerator, verbose); jsonGenerator.writeEndObject(); } // 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) { this.repository = repository; } public void setMaxDepth(Integer maxDepth) { this.maxDepth = maxDepth; } protected Repository getRepository() { return repository; } protected ObjectMapper getObjectMapper() { return objectMapper; } }