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}