View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.common.xml;
20  
21  import java.io.ByteArrayOutputStream;
22  import java.io.IOException;
23  import java.text.MessageFormat;
24  import java.util.Locale;
25  
26  import javax.xml.transform.OutputKeys;
27  import javax.xml.transform.Transformer;
28  import javax.xml.transform.TransformerException;
29  import javax.xml.transform.TransformerFactory;
30  import javax.xml.transform.stream.StreamResult;
31  
32  import org.apache.logging.log4j.LogManager;
33  import org.apache.logging.log4j.Logger;
34  import org.mycore.common.MCRException;
35  import org.mycore.common.MCRSessionMgr;
36  import org.mycore.common.MCRTransactionHelper;
37  import org.mycore.common.config.MCRConfiguration2;
38  import org.mycore.common.content.MCRContent;
39  import org.mycore.common.content.transformer.MCRContentTransformer;
40  import org.mycore.common.content.transformer.MCRParameterizedTransformer;
41  import org.mycore.common.xsl.MCRParameterCollector;
42  import org.xml.sax.SAXException;
43  
44  import jakarta.servlet.ServletOutputStream;
45  import jakarta.servlet.http.HttpServletRequest;
46  import jakarta.servlet.http.HttpServletResponse;
47  
48  /**
49   * Does the layout for other MyCoRe servlets by transforming XML input to
50   * various output formats, using XSL stylesheets.
51   * 
52   * @author Frank Lützenkirchen
53   * @author Thomas Scheffler (yagee)
54   */
55  public class MCRLayoutService {
56  
57      private static final int INITIAL_BUFFER_SIZE = 32 * 1024;
58  
59      static final Logger LOGGER = LogManager.getLogger(MCRLayoutService.class);
60  
61      private static final MCRLayoutService SINGLETON = new MCRLayoutService();
62  
63      private static final String TRANSFORMER_FACTORY_PROPERTY = "MCR.Layout.Transformer.Factory";
64  
65      public static MCRLayoutService instance() {
66          return SINGLETON;
67      }
68  
69      public void sendXML(HttpServletRequest req, HttpServletResponse res, MCRContent xml) throws IOException {
70          res.setContentType("text/xml; charset=UTF-8");
71          try {
72              Transformer transformer = TransformerFactory.newInstance().newTransformer();
73              transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
74              transformer.setOutputProperty(OutputKeys.INDENT, "no");
75              StreamResult result = new StreamResult(res.getOutputStream());
76              transformer.transform(xml.getSource(), result);
77          } catch (TransformerException e) {
78              throw new MCRException(e);
79          }
80          res.flushBuffer();
81      }
82  
83      public void doLayout(HttpServletRequest req, HttpServletResponse res, MCRContent source) throws IOException,
84          TransformerException, SAXException {
85          if (res.isCommitted()) {
86              LOGGER.warn("Response already committed: {}:{}", res.getStatus(), res.getContentType());
87              return;
88          }
89          String docType = source.getDocType();
90          try {
91              MCRParameterCollector parameter = new MCRParameterCollector(req);
92              MCRContentTransformer transformer = getContentTransformer(docType, parameter);
93              String filename = getFileName(req, parameter);
94              transform(res, transformer, source, parameter, filename);
95          } catch (IOException | TransformerException | SAXException ex) {
96              throw ex;
97          } catch (MCRException ex) {
98              // Check if it is an error page to suppress later recursively
99              // generating an error page when there is an error in the stylesheet
100             if (!"mcr_error".equals(docType)) {
101                 throw ex;
102             }
103 
104             String msg = "Error while generating error page!";
105             LOGGER.warn(msg, ex);
106             res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, msg);
107         } catch (Exception e) {
108             throw new MCRException(e);
109         }
110     }
111 
112     public MCRContent getTransformedContent(HttpServletRequest req, HttpServletResponse res, MCRContent source)
113         throws IOException, TransformerException, SAXException {
114         String docType = source.getDocType();
115         try {
116             MCRParameterCollector parameter = new MCRParameterCollector(req);
117             MCRContentTransformer transformer = getContentTransformer(docType, parameter);
118             String filename = getFileName(req, parameter);
119             return transform(transformer, source, parameter, filename);
120         } catch (IOException | TransformerException | SAXException ex) {
121             throw ex;
122         } catch (MCRException ex) {
123             // Check if it is an error page to suppress later recursively
124             // generating an error page when there is an error in the stylesheet
125             if (!"mcr_error".equals(docType)) {
126                 throw ex;
127             }
128 
129             String msg = "Error while generating error page!";
130             LOGGER.warn(msg, ex);
131             res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, msg);
132             return null;
133         } catch (Exception e) {
134             throw new MCRException(e);
135         }
136     }
137 
138     public static MCRContentTransformer getContentTransformer(String docType, MCRParameterCollector parameter)
139         throws Exception {
140         String transformerId = parameter.getParameter("Transformer", null);
141         if (transformerId == null) {
142             String style = parameter.getParameter("Style", "default");
143             transformerId = new MessageFormat("{0}-{1}", Locale.ROOT).format(new Object[] { docType, style });
144         }
145         MCRLayoutTransformerFactory factory = MCRConfiguration2.<MCRLayoutTransformerFactory>getInstanceOf(
146             TRANSFORMER_FACTORY_PROPERTY)
147             .orElseGet(MCRLayoutTransformerFactory::new);
148         return factory.getTransformer(transformerId);
149     }
150 
151     private String getFileName(HttpServletRequest req, MCRParameterCollector parameter) {
152         String filename = parameter.getParameter("FileName", null);
153         if (filename != null) {
154             if (req.getServletPath().contains(filename)) {
155                 //filter out MCRStaticXMLFileServlet as it defines "FileName"
156                 return extractFileName(req.getServletPath());
157             }
158             return filename;
159         }
160         if (req.getPathInfo() != null) {
161             return extractFileName(req.getPathInfo());
162         }
163         return new MessageFormat("{0}-{1}", Locale.ROOT).format(
164             new Object[] { extractFileName(req.getServletPath()), String.valueOf(System.currentTimeMillis())});
165     }
166 
167     private String extractFileName(String filename) {
168         int filePosition = filename.lastIndexOf('/') + 1;
169         filename = filename.substring(filePosition);
170         filePosition = filename.lastIndexOf('.');
171         if (filePosition > 0) {
172             filename = filename.substring(0, filePosition);
173         }
174         return filename;
175     }
176 
177     private void transform(HttpServletResponse response, MCRContentTransformer transformer, MCRContent source,
178         MCRParameterCollector parameter, String filename) throws IOException, TransformerException, SAXException {
179         try {
180             String fileExtension = transformer.getFileExtension();
181             if (fileExtension != null && fileExtension.length() > 0) {
182                 filename += "." + fileExtension;
183             }
184             response.setHeader("Content-Disposition",
185                 transformer.getContentDisposition() + ";filename=\"" + filename + "\"");
186             String ct = transformer.getMimeType();
187             String enc = transformer.getEncoding();
188             if (enc != null) {
189                 response.setCharacterEncoding(enc);
190                 response.setContentType(ct + "; charset=" + enc);
191             } else {
192                 response.setContentType(ct);
193             }
194             LOGGER.debug("MCRLayoutService starts to output {}", response.getContentType());
195             ServletOutputStream servletOutputStream = response.getOutputStream();
196             long start = System.currentTimeMillis();
197             try {
198                 ByteArrayOutputStream bout = new ByteArrayOutputStream(INITIAL_BUFFER_SIZE);
199                 if (transformer instanceof MCRParameterizedTransformer) {
200                     MCRParameterizedTransformer paramTransformer = (MCRParameterizedTransformer) transformer;
201                     paramTransformer.transform(source, bout, parameter);
202                 } else {
203                     transformer.transform(source, bout);
204                 }
205                 endCurrentTransaction();
206                 response.setContentLength(bout.size());
207                 bout.writeTo(servletOutputStream);
208             } finally {
209                 LOGGER.debug("MCRContent transformation took {} ms.", System.currentTimeMillis() - start);
210             }
211         } catch (TransformerException | IOException | SAXException e) {
212             throw e;
213         } catch (Exception e) {
214             Throwable cause = e.getCause();
215             while (cause != null) {
216                 if (cause instanceof TransformerException) {
217                     throw (TransformerException) cause;
218                 } else if (cause instanceof SAXException) {
219                     throw (SAXException) cause;
220                 } else if (cause instanceof IOException) {
221                     throw (IOException) cause;
222                 }
223                 cause = cause.getCause();
224             }
225             throw new IOException(e);
226         }
227     }
228 
229     private MCRContent transform(MCRContentTransformer transformer, MCRContent source, MCRParameterCollector parameter,
230         String filename) throws IOException, TransformerException, SAXException {
231         LOGGER.debug("MCRLayoutService starts to output {}", getMimeType(transformer));
232         long start = System.currentTimeMillis();
233         try {
234             if (transformer instanceof MCRParameterizedTransformer) {
235                 MCRParameterizedTransformer paramTransformer = (MCRParameterizedTransformer) transformer;
236                 return paramTransformer.transform(source, parameter);
237             } else {
238                 return transformer.transform(source);
239             }
240         } finally {
241             LOGGER.debug("MCRContent transformation took {} ms.", System.currentTimeMillis() - start);
242         }
243     }
244 
245     private String getMimeType(MCRContentTransformer transformer) throws IOException, TransformerException,
246         SAXException {
247         try {
248             return transformer.getMimeType();
249         } catch (IOException | TransformerException | SAXException | RuntimeException e) {
250             throw e;
251         } catch (Exception e) {
252             return "application/octet-stream";
253         }
254     }
255 
256     /**
257      * Called before sending data to end hibernate transaction.
258      */
259     private static void endCurrentTransaction() {
260         MCRSessionMgr.getCurrentSession();
261         MCRTransactionHelper.commitTransaction();
262     }
263 }