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}