package org.argeo.maintenance.backup; import java.io.IOException; import java.io.InputStream; import java.io.Writer; import java.util.Arrays; import java.util.Base64; import java.util.Set; import java.util.TreeSet; import javax.jcr.Binary; import javax.jcr.Node; import javax.jcr.RepositoryException; import javax.jcr.Session; import org.apache.commons.io.IOUtils; import org.argeo.jcr.Jcr; import org.argeo.jcr.JcrException; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; /** XML handler serialising a JCR system view. */ public class BackupContentHandler extends DefaultHandler { final static int MAX_DEPTH = 1024; final static String SV_NAMESPACE_URI = "http://www.jcp.org/jcr/sv/1.0"; final static String SV_PREFIX = "sv"; // elements final static String NODE = "node"; final static String PROPERTY = "property"; final static String VALUE = "value"; // attributes final static String NAME = "name"; final static String MULTIPLE = "multiple"; final static String TYPE = "type"; // values final static String BINARY = "Binary"; final static String JCR_CONTENT = "jcr:content"; private Writer out; private Session session; private Set contentPaths = new TreeSet<>(); boolean prettyPrint = true; private final String parentPath; // private boolean inSystem = false; public BackupContentHandler(Writer out, Node node) { super(); this.out = out; this.session = Jcr.getSession(node); parentPath = Jcr.getParentPath(node); } private int currentDepth = -1; private String[] currentPath = new String[MAX_DEPTH]; private boolean currentPropertyIsMultiple = false; private String currentEncoded = null; private Base64.Encoder base64encore = Base64.getEncoder(); @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { boolean isNode; boolean isProperty; switch (localName) { case NODE: isNode = true; isProperty = false; break; case PROPERTY: isNode = false; isProperty = true; break; default: isNode = false; isProperty = false; } if (isNode) { String nodeName = attributes.getValue(SV_NAMESPACE_URI, NAME); currentDepth = currentDepth + 1; // if (currentDepth >= 0) currentPath[currentDepth] = nodeName; // System.out.println(getCurrentPath() + " , depth=" + currentDepth); // if ("jcr:system".equals(nodeName)) { // inSystem = true; // } } // if (inSystem) // return; if (SV_NAMESPACE_URI.equals(uri)) try { if (prettyPrint) { if (isNode) { out.write(spaces()); out.write("\n"); out.write(spaces()); } else if (isProperty) out.write(spaces()); else if (currentPropertyIsMultiple) out.write(spaces()); } out.write("<"); out.write(SV_PREFIX + ":" + localName); if (isProperty) currentPropertyIsMultiple = false; // always reset for (int i = 0; i < attributes.getLength(); i++) { String ns = attributes.getURI(i); if (SV_NAMESPACE_URI.equals(ns)) { String attrName = attributes.getLocalName(i); String attrValue = attributes.getValue(i); out.write(" "); out.write(SV_PREFIX + ":" + attrName); out.write("="); out.write("\""); out.write(attrValue); out.write("\""); if (isProperty) { if (MULTIPLE.equals(attrName)) currentPropertyIsMultiple = Boolean.parseBoolean(attrValue); else if (TYPE.equals(attrName)) { if (BINARY.equals(attrValue)) { if (JCR_CONTENT.equals(getCurrentName())) { contentPaths.add(getCurrentJcrPath()); } else { Binary binary = session.getNode(getCurrentJcrPath()).getProperty(attrName) .getBinary(); try (InputStream in = binary.getStream()) { currentEncoded = base64encore.encodeToString(IOUtils.toByteArray(in)); } finally { } } } } } } } if (isNode && currentDepth == 0) { // out.write(" xmlns=\"" + SV_NAMESPACE_URI + "\""); out.write(" xmlns:" + SV_PREFIX + "=\"" + SV_NAMESPACE_URI + "\""); } out.write(">"); if (prettyPrint) if (isNode) out.write("\n"); else if (isProperty && currentPropertyIsMultiple) out.write("\n"); } catch (IOException e) { throw new RuntimeException(e); } catch (RepositoryException e) { throw new JcrException(e); } } @Override public void endElement(String uri, String localName, String qName) throws SAXException { boolean isNode = localName.equals(NODE); boolean isValue = localName.equals(VALUE); if (prettyPrint) if (!isValue) try { if (isNode || currentPropertyIsMultiple) out.write(spaces()); } catch (IOException e1) { throw new RuntimeException(e1); } if (isNode) { // System.out.println("endElement " + getCurrentPath() + " , depth=" + currentDepth); // if (currentDepth > 0) currentPath[currentDepth] = null; currentDepth = currentDepth - 1; // if (inSystem) { // // System.out.println("Skip " + getCurrentPath()+" , // // currentDepth="+currentDepth); // if (currentDepth == 0) { // inSystem = false; // return; // } // } } // if (inSystem) // return; if (SV_NAMESPACE_URI.equals(uri)) try { if (isValue && currentEncoded != null) { out.write(currentEncoded); } currentEncoded = null; out.write(""); if (prettyPrint) if (!isValue) out.write("\n"); else { if (currentPropertyIsMultiple) out.write("\n"); } if (currentDepth == 0) out.flush(); } catch (IOException e) { throw new RuntimeException(e); } } private char[] spaces() { char[] arr = new char[currentDepth]; Arrays.fill(arr, ' '); return arr; } @Override public void characters(char[] ch, int start, int length) throws SAXException { // if (inSystem) // return; try { out.write(ch, start, length); } catch (IOException e) { throw new RuntimeException(e); } } protected String getCurrentName() { assert currentDepth >= 0; // if (currentDepth == 0) // return "jcr:root"; return currentPath[currentDepth]; } protected String getCurrentJcrPath() { // if (currentDepth == 0) // return "/"; StringBuilder sb = new StringBuilder(parentPath.equals("/") ? "" : parentPath); for (int i = 0; i <= currentDepth; i++) { // if (i != 0) sb.append('/'); sb.append(currentPath[i]); } return sb.toString(); } public Set getContentPaths() { return contentPaths; } }