001/* 002 * Copyright 2020 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.contentio.archive; 017 018import java.io.IOException; 019import java.io.InputStream; 020import java.util.Map; 021import java.util.Objects; 022import java.util.concurrent.ConcurrentHashMap; 023 024import javax.jcr.ItemNotFoundException; 025import javax.jcr.Node; 026import javax.jcr.RepositoryException; 027import javax.jcr.Session; 028import javax.xml.transform.Templates; 029import javax.xml.transform.TransformerConfigurationException; 030import javax.xml.transform.TransformerException; 031import javax.xml.transform.sax.SAXResult; 032import javax.xml.transform.sax.SAXTransformerFactory; 033import javax.xml.transform.sax.TransformerHandler; 034import javax.xml.transform.stream.StreamSource; 035 036import org.apache.cocoon.components.xslt.TraxErrorListener; 037import org.apache.cocoon.util.log.SLF4JLoggerAdapter; 038import org.apache.excalibur.source.Source; 039import org.apache.jackrabbit.commons.xml.ParsingContentHandler; 040import org.slf4j.Logger; 041import org.slf4j.LoggerFactory; 042import org.xml.sax.ContentHandler; 043import org.xml.sax.SAXException; 044 045import org.ametys.plugins.repository.AmetysRepositoryException; 046 047/** 048 * For {@link Mergers#IGNORE}, we need to use session.getImportContentHandler and filter the SAX events from the XML input stream 049 * <br>For all <sv:property sv:name="jcr:uuid" sv:type="String"> and get their <sv:value>, 050 * query the repository to check if it already exists and if it should be ignored 051 * <br>And for each sax event of sv:node 052 * <ol> 053 * <li>if exists in the repository => ignore the node import</li> 054 * <li>if does not exist in the repository => send SAX event</li> 055 * </ol> 056 */ 057public final class IgnoreMergerHelper 058{ 059 private static final Logger __LOGGER = LoggerFactory.getLogger(IgnoreMergerHelper.class.getPackageName()); 060 private static final org.apache.avalon.framework.logger.Logger __AVALON_LOGGER = new SLF4JLoggerAdapter(__LOGGER); 061 062 // a way to pass the Session object to the helper method #uuidIsNotPresent, which called by the XSL 063 private static Map<Thread, Session> __SESSIONS_BY_REQUEST = new ConcurrentHashMap<>(); 064 065 private IgnoreMergerHelper() 066 { } 067 068 /** 069 * Tells if the given node UUID is not present in the reposiotry. 070 * @param nodeUuid The node UUID to test 071 * @return <code>true</code> if no node exists in the repository with this UUID. <code>false</code> if a node exists in the repository with this UUID. 072 */ 073 public static boolean uuidIsNotPresent(String nodeUuid) 074 { 075 try 076 { 077 Session session = __SESSIONS_BY_REQUEST.get(Thread.currentThread()); 078 Node node = session.getNodeByIdentifier(nodeUuid); 079 Objects.requireNonNull(node, "Node cannot be null."); 080 __LOGGER.debug("IgnoreMergerHelper#uuidIsNotPresent is called for argument '{}'. A node with this uuid was found. Returned result is false.", nodeUuid); 081 return false; 082 } 083 catch (ItemNotFoundException e) 084 { 085 __LOGGER.debug("IgnoreMergerHelper#uuidIsNotPresent is called for argument '{}'. No node was found with this uuid. Returned result is true.", nodeUuid); 086 return true; 087 } 088 catch (RepositoryException e) 089 { 090 throw new AmetysRepositoryException(e); 091 } 092 } 093 094 static void jcrImportXml(Session session, String parentAbsPath, InputStream in, int importUuidBehavior, Mergers mergers) throws RepositoryException, IOException 095 { 096 ContentHandler importContentHandler = session.getImportContentHandler(parentAbsPath, importUuidBehavior); 097 Thread currentThread = Thread.currentThread(); 098 try 099 { 100 __SESSIONS_BY_REQUEST.put(currentThread, session); 101 TransformerHandler filteringContentHandler = _sendFilteredSaxEvents(importContentHandler, mergers); 102 _sendSaxEventsFromInputStream(in, filteringContentHandler); 103 } 104 catch (TransformerException | SAXException e) 105 { 106 throw new IOException(e); 107 } 108 finally 109 { 110 __SESSIONS_BY_REQUEST.remove(currentThread); 111 } 112 } 113 114 private static TransformerHandler _sendFilteredSaxEvents(ContentHandler receiverContentHandler, Mergers mergers) throws IOException, TransformerConfigurationException 115 { 116 // Filter thanks to a XSL 117 SAXTransformerFactory saxTransformerFactory = Archivers.getSaxTransformerFactory(); 118 Source xsltSource = _getXsltStringReader(mergers); 119 var xsltStreamSource = new StreamSource(xsltSource.getURI()); 120 Templates xsltTemplates = saxTransformerFactory.newTemplates(xsltStreamSource); 121 TransformerHandler filteringTransformerHandler = saxTransformerFactory.newTransformerHandler(xsltTemplates); 122 TraxErrorListener errorListener = new TraxErrorListener(__AVALON_LOGGER, xsltSource.getURI()); 123 filteringTransformerHandler.getTransformer().setErrorListener(errorListener); 124 125 filteringTransformerHandler.setResult(new SAXResult(receiverContentHandler)); 126 127 return filteringTransformerHandler; 128 } 129 130 private static Source _getXsltStringReader(Mergers mergers) throws IOException 131 { 132 Source xsltSource = mergers._sourceResolver.resolveURI("plugin:contentio://stylesheets/archive/ignore-merger-helper.xsl"); 133 return xsltSource; 134 } 135 136 private static void _sendSaxEventsFromInputStream(InputStream in, ContentHandler wrappedContentHandler) throws SAXException, IOException 137 { 138 var parsingContentHandler = new ParsingContentHandler(wrappedContentHandler); 139 parsingContentHandler.parse(in); 140 } 141}