001/*
002 *  Copyright 2018 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.extraction.execution.pipeline.impl;
017
018import java.io.IOException;
019import java.io.OutputStream;
020import java.util.ArrayDeque;
021import java.util.ArrayList;
022import java.util.List;
023import java.util.Queue;
024import java.util.stream.Collector;
025
026import javax.xml.transform.Templates;
027import javax.xml.transform.TransformerConfigurationException;
028import javax.xml.transform.sax.SAXResult;
029import javax.xml.transform.sax.SAXTransformerFactory;
030import javax.xml.transform.sax.TransformerHandler;
031import javax.xml.transform.stream.StreamSource;
032
033import org.apache.cocoon.components.xslt.TraxErrorListener;
034import org.apache.cocoon.util.log.SLF4JLoggerAdapter;
035import org.apache.excalibur.source.Source;
036import org.apache.excalibur.source.SourceResolver;
037import org.apache.excalibur.source.impl.FileSource;
038import org.slf4j.Logger;
039import org.slf4j.LoggerFactory;
040import org.xml.sax.ContentHandler;
041
042import org.ametys.core.util.LambdaUtils;
043import org.ametys.plugins.extraction.ExtractionConstants;
044import org.ametys.plugins.extraction.execution.pipeline.Pipeline;
045import org.ametys.plugins.extraction.execution.pipeline.PipelineDescriptor;
046import org.ametys.plugins.extraction.execution.pipeline.PipelineSerializerModel;
047import org.ametys.plugins.extraction.execution.pipeline.Pipelines;
048
049class PipelineImpl implements Pipeline
050{
051    private static final Logger __LOGGER = LoggerFactory.getLogger(Pipeline.class);
052    private static final org.apache.avalon.framework.logger.Logger __AVALON_LOGGER = new SLF4JLoggerAdapter(__LOGGER);
053    
054    private PipelineDescriptor _desc;
055    private OutputStream _out;
056    private SourceResolver _resolver;
057    private List<Source> _sources = new ArrayList<>();
058    private TransformerHandler _firstHandler;
059    private TransformerHandler _lastHandler;
060    private PipelineSerializer _serializer;
061
062    PipelineImpl(PipelineDescriptor desc, OutputStream out, SourceResolver resolver)
063    {
064        _desc = desc;
065        _out = out;
066        _resolver = resolver;
067    }
068    
069    @Override
070    public ContentHandler getHandler() throws Exception
071    {
072        if (_firstHandler != null || _lastHandler != null)
073        {
074            throw new IllegalStateException("Pipeline was alreay opened.");
075        }
076        
077        SAXTransformerFactory stf = Pipelines.getSaxTransformerFactory();
078        if (_desc.getStylesheets().isEmpty())
079        {
080            _firstHandler = stf.newTransformerHandler();
081            _lastHandler = _firstHandler;
082            Pipelines.setStandardOutputProperties(_lastHandler);
083        }
084        else
085        {
086            _getTransformerHandler(stf);
087        }
088        
089        PipelineSerializerModel serializerModel = _desc.getSerializerModel();
090        _serializer = serializerModel.newSerializer(_lastHandler, _out, _desc.getOutputParameters());
091        
092        _serializer.prepare();
093        return _firstHandler;
094    }
095    
096    // case _xslt is not empty
097    private void _getTransformerHandler(SAXTransformerFactory stf)
098    {
099        final String suffix = ExtractionConstants.XSLT_DIR + "/";
100        Queue<TransformerHandler> xslts = _desc.getStylesheets().stream()
101                .map(suffix::concat)
102                .map(LambdaUtils.wrap(_resolver::resolveURI))
103                .map(LambdaUtils.wrap(source -> _newTransformerHandler(stf, source)))
104                .collect(Collector.of(
105                    ArrayDeque::new, 
106                    (deque, th) -> deque.add(th), 
107                    (deque1, deque2) ->
108                    {
109                        throw new IllegalStateException("Should not be parallel");
110                    }
111                ));
112        
113        _firstHandler = xslts.element();
114        
115        // chain
116        while (true)
117        {
118            TransformerHandler currentHandler = xslts.remove();
119            if (xslts.isEmpty())
120            {
121                // it is last => set result
122                _lastHandler = currentHandler;
123                break;
124            }
125            else
126            {
127                // Set next one as the result of current
128                currentHandler.setResult(new SAXResult(xslts.element()));
129            }
130        }
131    }
132    
133    private TransformerHandler _newTransformerHandler(SAXTransformerFactory stf, Source source) throws TransformerConfigurationException
134    {
135        // Need to keep sources references
136        _sources.add(source);
137        String uri = source.getURI();
138        _checkExists(source, uri);
139        FileSource fileSource = _checkFileSource(source, uri);
140        
141        // Create TransformerHandler with custom error listener
142        StreamSource streamSource = new StreamSource(fileSource.getFile());
143        Templates templates = stf.newTemplates(streamSource);
144        TransformerHandler th = stf.newTransformerHandler(templates);
145        TraxErrorListener errorListener = new TraxErrorListener(__AVALON_LOGGER, uri);
146        th.getTransformer().setErrorListener(errorListener);
147        
148        // Output properties
149        Pipelines.setStandardOutputProperties(th);
150        
151        return th;
152    }
153    
154    private void _checkExists(Source s, String uri)
155    {
156        boolean exists = s.exists();
157        if (!exists)
158        {
159            throw new IllegalArgumentException("XSL file '" + uri + " does not exist. The pipeline cannot be executed.");
160        }
161    }
162    
163    private FileSource _checkFileSource(Source source, String uri)
164    {
165        if (source instanceof FileSource)
166        {
167            return (FileSource) source;
168        }
169        throw new IllegalArgumentException("Source '" + uri + " is not a file source. The pipeline cannot be executed.");
170    }
171    
172    @Override
173    public void serialize() throws Exception
174    {
175        _serializer.serialize();
176    }
177
178    @Override
179    public void close() throws IOException
180    {
181        _release();
182        if (_serializer != null)
183        {
184            _serializer.close();
185        }
186    }
187    
188    private void _release()
189    {
190        if (_resolver != null)
191        {
192            _sources.forEach(_resolver::release);
193        }
194    }
195}