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