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 &lt;sv:property sv:name="jcr:uuid" sv:type="String"&gt; and get their &lt;sv:value&gt;,
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 =&gt; ignore the node import</li>
054 * <li>if does not exist in the repository =&gt; 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}