/* * (C) Copyright 2015 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.history.impl; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.TreeSet; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.ValueFormatException; import javax.jcr.lock.LockException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.version.VersionException; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import biz.netcentric.cq.tools.actool.comparators.TimestampPropertyComparator; import biz.netcentric.cq.tools.actool.history.AcInstallationLog; public class HistoryUtils { private static final Logger LOG = LoggerFactory.getLogger(HistoryUtils.class); public static final String HISTORY_NODE_NAME_PREFIX = "history_"; public static final String NODETYPE_NT_UNSTRUCTURED = "nt:unstructured"; private static final String PROPERTY_SLING_RESOURCE_TYPE = "sling:resourceType"; public static final String ACHISTORY_ROOT_NODE = "achistory"; public static final String STATISTICS_ROOT_NODE = "var/statistics"; public static final String PROPERTY_TIMESTAMP = "timestamp"; private static final String PROPERTY_MESSAGES = "messages"; private static final String PROPERTY_EXECUTION_TIME = "executionTime"; public static final String PROPERTY_SUCCESS = "success"; private static final String PROPERTY_INSTALLATION_DATE = "installationDate"; public static final String PROPERTY_INSTALLED_FROM = "installedFrom"; public static Node getAcHistoryRootNode(final Session session) throws RepositoryException { final Node rootNode = session.getRootNode(); Node statisticsRootNode = safeGetNode(rootNode, STATISTICS_ROOT_NODE, NODETYPE_NT_UNSTRUCTURED); Node acHistoryRootNode = safeGetNode(statisticsRootNode, ACHISTORY_ROOT_NODE, "sling:OrderedFolder"); return acHistoryRootNode; } /** * Method that persists a new history log in CRX under * '/var/statistics/achistory' * * @param session the jcr session * @param installLog * history to persist * @param nrOfHistoriesToSave * number of newest histories which should be kept in CRX. older * histories get automatically deleted * @return the node being created */ public static Node persistHistory(final Session session, AcInstallationLog installLog, final int nrOfHistoriesToSave) throws RepositoryException { Node acHistoryRootNode = getAcHistoryRootNode(session); String name = HISTORY_NODE_NAME_PREFIX + System.currentTimeMillis(); if (StringUtils.isNotBlank(installLog.getCrxPackageName())) { name += "_via_" + installLog.getCrxPackageName(); } else { name += "_via_api"; } Node newHistoryNode = safeGetNode(acHistoryRootNode, name, NODETYPE_NT_UNSTRUCTURED); String path = newHistoryNode.getPath(); setHistoryNodeProperties(newHistoryNode, installLog); deleteObsoleteHistoryNodes(acHistoryRootNode, nrOfHistoriesToSave); Node previousHistoryNode = (Node) acHistoryRootNode.getNodes().next(); if (previousHistoryNode != null) { acHistoryRootNode.orderBefore(newHistoryNode.getName(), previousHistoryNode.getName()); } installLog.addMessage(LOG, "Saved history in node: " + path); return newHistoryNode; } private static Node safeGetNode(final Node baseNode, final String name, final String typeToCreate) throws RepositoryException { if (!baseNode.hasNode(name)) { LOG.debug("create node: {}", name); return baseNode.addNode(name, typeToCreate); } else { return baseNode.getNode(name); } } public static void setHistoryNodeProperties(final Node historyNode, AcInstallationLog installLog) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException { historyNode.setProperty(PROPERTY_INSTALLATION_DATE, installLog .getInstallationDate().toString()); historyNode.setProperty(PROPERTY_SUCCESS, installLog.isSuccess()); historyNode.setProperty(PROPERTY_EXECUTION_TIME, installLog.getExecutionTime()); String messageHistory = installLog.getVerboseMessageHistory(); // 16777216 bytes = ~ 16MB was the error in #145, assuming chars*2, hence 16777216 / 2 = 8MB, using 7MB to consider the BSON // overhead if (messageHistory.length() > (7 * 1024 * 1024)) { messageHistory = installLog.getMessageHistory(); // just use non-verbose history for this case } historyNode.setProperty(PROPERTY_MESSAGES, messageHistory); historyNode.setProperty(PROPERTY_TIMESTAMP, installLog .getInstallationDate().getTime()); historyNode.setProperty(PROPERTY_SLING_RESOURCE_TYPE, "/apps/netcentric/actool/components/historyRenderer"); Map<String, String> configFileContentsByName = installLog.getConfigFileContentsByName(); if (configFileContentsByName != null) { String commonPrefix = StringUtils .getCommonPrefix(configFileContentsByName.keySet().toArray(new String[configFileContentsByName.size()])); String crxPackageName = installLog.getCrxPackageName(); // for install hook case historyNode.setProperty(PROPERTY_INSTALLED_FROM, StringUtils.defaultString(crxPackageName) + commonPrefix); } } /** * Method that ensures that only the number of history logs is persisted in * CRX which is configured in nrOfHistoriesToSave * * @param acHistoryRootNode * node in CRX under which the history logs are located * @param nrOfHistoriesToSave * number of history logs which get stored in CRX (as direct * child nodes of acHistoryRootNode in insertion order - newest * always on top) * @throws RepositoryException */ private static void deleteObsoleteHistoryNodes( final Node acHistoryRootNode, final int nrOfHistoriesToSave) throws RepositoryException { NodeIterator childNodeIt = acHistoryRootNode.getNodes(); Set<Node> historyChildNodes = new TreeSet<Node>( new TimestampPropertyComparator()); while (childNodeIt.hasNext()) { Node node = childNodeIt.nextNode(); if (node.getName().startsWith(HISTORY_NODE_NAME_PREFIX)) { historyChildNodes.add(node); } } int index = 1; for (Node node : historyChildNodes) { if (index > nrOfHistoriesToSave) { LOG.debug("delete obsolete history node: ", node.getPath()); node.remove(); } index++; } } /** * Method that returns a string which contains a number, path of a stored * history log in CRX, and the success status of that installation * * @param acHistoryRootNode * node in CRX under which the history logs get stored * @return String array which holds the single history infos * @throws RepositoryException * @throws PathNotFoundException */ static String[] getHistoryInfos(final Session session) throws RepositoryException, PathNotFoundException { Node acHistoryRootNode = getAcHistoryRootNode(session); Set<String> messages = new LinkedHashSet<String>(); int cnt = 1; for (NodeIterator iterator = acHistoryRootNode.getNodes(); iterator .hasNext();) { Node node = (Node) iterator.next(); if (node != null && node.getName().startsWith("history_")) { String successStatusString = "failed"; if (node.getProperty(PROPERTY_SUCCESS).getBoolean()) { successStatusString = "ok"; } String installationDate = node.getProperty( PROPERTY_INSTALLATION_DATE).getString(); messages.add(cnt + ". " + node.getPath() + " " + "" + "(" + installationDate + ")" + "(" + successStatusString + ")"); } cnt++; } return messages.toArray(new String[messages.size()]); } public static String getLogTxt(final Session session, final String path) { return getLog(session, path, "\n").toString(); } public static String getLogHtml(final Session session, final String path) { return getLog(session, path, "<br />").toString(); } /** * Method which assembles String containing informations of the properties * of the respective history node which is specified by the path parameter */ public static String getLog(final Session session, final String path, final String lineFeedSymbol) { StringBuilder sb = new StringBuilder(); try { Node acHistoryRootNode = getAcHistoryRootNode(session); Node historyNode = acHistoryRootNode.getNode(path); if (historyNode != null) { sb.append("Installation triggered: " + historyNode.getProperty(PROPERTY_INSTALLATION_DATE) .getString()); sb.append(lineFeedSymbol + historyNode.getProperty(PROPERTY_MESSAGES) .getString().replace("\n", lineFeedSymbol)); sb.append(lineFeedSymbol + "Execution time: " + historyNode.getProperty(PROPERTY_EXECUTION_TIME) .getLong() + " ms"); sb.append(lineFeedSymbol + "Success: " + historyNode.getProperty(PROPERTY_SUCCESS) .getBoolean()); } } catch (RepositoryException e) { LOG.error("RepositoryException: {}", e); } return sb.toString(); } }