/* * (C) Copyright 2016 Netcentric AG. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package biz.netcentric.cq.tools.actool.helper; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collection; import java.util.Properties; import java.util.Set; import javax.jcr.AccessDeniedException; import javax.jcr.InvalidSerializedDataException; import javax.jcr.ItemExistsException; import javax.jcr.Node; import javax.jcr.PathNotFoundException; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.version.VersionException; import org.apache.commons.lang.StringUtils; import org.apache.jackrabbit.vault.fs.api.PathFilterSet; import org.apache.jackrabbit.vault.fs.api.ProgressTrackerListener; import org.apache.jackrabbit.vault.fs.api.VaultInputSource; import org.apache.jackrabbit.vault.fs.config.ConfigurationException; import org.apache.jackrabbit.vault.fs.config.DefaultMetaInf; import org.apache.jackrabbit.vault.fs.config.DefaultWorkspaceFilter; import org.apache.jackrabbit.vault.fs.config.MetaInf; import org.apache.jackrabbit.vault.fs.config.VaultSettings; import org.apache.jackrabbit.vault.fs.io.Archive; import org.apache.jackrabbit.vault.fs.io.ImportOptions; import org.apache.jackrabbit.vault.fs.io.Importer; import org.apache.jackrabbit.vault.fs.io.SubArchive; import org.apache.jackrabbit.vault.util.Constants; import org.apache.jackrabbit.vault.util.Text; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import biz.netcentric.cq.tools.actool.configmodel.AceBean; import biz.netcentric.cq.tools.actool.history.AcInstallationLog; import biz.netcentric.cq.tools.actool.installationhistory.AcInstallationHistoryPojo; public class ContentHelper { public static final Logger LOG = LoggerFactory.getLogger(ContentHelper.class); private ContentHelper() { } public static boolean createInitialContent(final Session session, final AcInstallationLog history, String path, Set<AceBean> aceBeanSetFromConfig) throws RepositoryException, PathNotFoundException, ItemExistsException, ConstraintViolationException, VersionException, InvalidSerializedDataException, LockException, AccessDeniedException { String initialContent = findInitialContentInConfigsForPath(aceBeanSetFromConfig, history); if (StringUtils.isBlank(initialContent)) { return false; } else { try { importContent(session, path, initialContent); history.addMessage(LOG, "Created initial content for path " + path); return true; } catch (Exception e) { history.addWarning(LOG, "Failed creating initial content for path " + path + ": " + e); LOG.debug("Exception: " + e, e); // log stack trace only debug return false; } } } private static String findInitialContentInConfigsForPath(Set<AceBean> aceBeanSetFromConfig, AcInstallationHistoryPojo history) { String initialContent = null; for (AceBean aceBean : aceBeanSetFromConfig) { String currentInitialContent = aceBean.getInitialContent(); if (StringUtils.isNotBlank(currentInitialContent)) { if (initialContent == null) { initialContent = currentInitialContent; } else { // this should not happen as it is validated at YamlConfigurationsValidator#validateInitialContentForNoDuplic already throw new IllegalStateException("Invalid Configuration: Path " + aceBean.getJcrPath() + " defines initial content at two locations"); } } } return initialContent; } public static void importContent(final Session session, final String path, String contentXmlStr) throws RepositoryException { String parentPath = StringUtils.substringBeforeLast(path, "/"); try { session.getNode(parentPath); } catch (PathNotFoundException e) { throw new PathNotFoundException("Parent path " + parentPath + " for creating content at " + path + " does not exist", e); } String rootElementStr = "<jcr:root "; if (!contentXmlStr.contains(rootElementStr)) { throw new IllegalStateException("Invalid initial content for path " + path + ": " + rootElementStr + " must be provided as root element in XML"); } String contentXmlStrAdjusted = contentXmlStr; if (!contentXmlStrAdjusted.contains("xmlns:cq")) { contentXmlStrAdjusted = contentXmlStrAdjusted.replace(rootElementStr, rootElementStr + " xmlns:cq=\"http://www.day.com/jcr/cq/1.0\" "); } if (!contentXmlStrAdjusted.contains("xmlns:jcr")) { contentXmlStrAdjusted = contentXmlStrAdjusted.replace(rootElementStr, rootElementStr + " xmlns:jcr=\"http://www.jcp.org/jcr/1.0\" "); } if (!contentXmlStrAdjusted.contains("xmlns:sling")) { contentXmlStrAdjusted = contentXmlStrAdjusted.replace(rootElementStr, rootElementStr + " xmlns:sling=\"http://sling.apache.org/jcr/sling/1.0\" "); } LOG.debug("Importing content for path {}\n{}", path, contentXmlStrAdjusted); try { Archive archive = new SingleContentFileArchive(path, contentXmlStrAdjusted); ImportOptions importOptions = new ImportOptions(); importOptions.setAutoSaveThreshold(Integer.MAX_VALUE); // IMPORTANT: this disables saving the session in Importer importOptions.setStrict(true); importOptions.setListener(new ProgressTrackerListener() { @Override public void onMessage(Mode mode, String action, String pathForListener) { LOG.debug("FileVault: " + action + " " + pathForListener); } @Override public void onError(Mode mode, String pathForListener, Exception e) { throw new IllegalArgumentException("Invalid content fragment at path " + pathForListener + ": " + e, e); } }); Importer importer = new Importer(importOptions); Node importRoot = session.getNode("/"); // importer uses session from importRoot node importer.run(archive, importRoot); } catch (IOException e) { throw new RepositoryException("I/O Error during import operation: " + e, e); } catch (ConfigurationException e) { throw new RepositoryException("ConfigurationException during import operation: " + e, e); } LOG.debug("Imported content for path {}\n{}", path, contentXmlStrAdjusted); } static class SingleContentFileArchive implements Archive { private final String filterPath; private final String xmlContent; private final Entry root; public SingleContentFileArchive(String path, String xmlContent) { this.filterPath = path; this.xmlContent = xmlContent; // the contructor SingleContentFileArchiveEntry automatically creates the tree to the xml file this.root = new SingleContentFileArchiveEntry("/" + Constants.ROOT_DIR + path + "/" + Constants.DOT_CONTENT_XML); } @Override public void open(boolean strict) throws IOException { // nothing to do on open and close } @Override public void close() { // nothing to do on open and close } @Override public InputStream openInputStream(Entry entry) throws IOException { if (Constants.DOT_CONTENT_XML.equals(entry.getName())) { ByteArrayInputStream inputStream = new ByteArrayInputStream(xmlContent.getBytes(StandardCharsets.UTF_8)); return inputStream; } else { return null; } } @Override public VaultInputSource getInputSource(Entry entry) throws IOException { if (Constants.DOT_CONTENT_XML.equals(entry.getName())) { InputStream is = openInputStream(entry); VaultInputSource vaultInputSource = new VaultInputSource(is) { @Override public long getContentLength() { return xmlContent.length(); } @Override public long getLastModified() { return 0; } }; return vaultInputSource; } else { return null; } } @Override public Entry getRoot() throws IOException { return root; } public Entry getEntry(String path) throws IOException { String[] segs = Text.explode(path, '/'); Entry root = getRoot(); for (String name : segs) { root = root.getChild(name); if (root == null) { break; } } return root; } public Entry getJcrRoot() throws IOException { return getRoot().getChild(Constants.ROOT_DIR); } public Archive getSubArchive(String rootPath, boolean asJcrRoot) throws IOException { Entry root = getEntry(rootPath); return root == null ? null : new SubArchive(this, root, asJcrRoot); } @Override public MetaInf getMetaInf() { DefaultMetaInf defaultMetaInf = new DefaultMetaInf(); DefaultWorkspaceFilter filter = new DefaultWorkspaceFilter(); PathFilterSet pathFilterSet = new PathFilterSet(); pathFilterSet.setRoot(filterPath); // the only non-default value filter.add(pathFilterSet); defaultMetaInf.setFilter(filter); Properties props = new Properties(); defaultMetaInf.setProperties(props); VaultSettings settings = new VaultSettings(); defaultMetaInf.setSettings(settings); return defaultMetaInf; } static class SingleContentFileArchiveEntry implements Entry { private Entry child; private String name; SingleContentFileArchiveEntry(String relPath) { String[] pathBits = relPath.split("/", 2); name = pathBits[0]; child = pathBits.length == 2 ? new SingleContentFileArchiveEntry(pathBits[1]) : null; } @Override public String getName() { return name; } @Override public boolean isDirectory() { return child != null; } @Override public Collection<? extends Entry> getChildren() { return child != null ? Arrays.asList(child) : null; } @Override public Entry getChild(String name) { return (name != null && name.equals(child.getName())) ? child : null; } } } }