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.util.Collections;
22  import java.util.List;
23  import java.util.Locale;
24  import java.util.Map;
25  import java.util.Optional;
26  import java.util.concurrent.ConcurrentHashMap;
27  import java.util.stream.Collectors;
28  
29  import javax.xml.parsers.ParserConfigurationException;
30  import javax.xml.transform.OutputKeys;
31  import javax.xml.transform.TransformerException;
32  
33  import org.apache.logging.log4j.LogManager;
34  import org.apache.logging.log4j.Logger;
35  import org.mycore.common.MCRClassTools;
36  import org.mycore.common.MCRException;
37  import org.mycore.common.config.MCRConfiguration2;
38  import org.mycore.common.content.transformer.MCRContentTransformer;
39  import org.mycore.common.content.transformer.MCRContentTransformerFactory;
40  import org.mycore.common.content.transformer.MCRIdentityTransformer;
41  import org.mycore.common.content.transformer.MCRXSLTransformer;
42  import org.xml.sax.SAXException;
43  
44  import com.google.common.collect.Lists;
45  
46  /**
47   * This class acts as a {@link MCRContentTransformer} factory for {@link MCRLayoutService}.
48   * @author Thomas Scheffler (yagee)
49   * @author Sebastian Hofmann
50   */
51  public class MCRLayoutTransformerFactory {
52      /** Map of transformer instances by ID */
53      private static Map<String, MCRContentTransformer> transformers = new ConcurrentHashMap<>();
54  
55      private static Logger LOGGER = LogManager.getLogger(MCRLayoutTransformerFactory.class);
56  
57      protected static final MCRIdentityTransformer NOOP_TRANSFORMER = new MCRIdentityTransformer("text/xml", "xml");
58  
59      /**
60       * Returns the transformer with the given ID. If the transformer is not instantiated yet,
61       * it is created and initialized.
62       */
63      public MCRContentTransformer getTransformer(String id) {
64          return transformers.computeIfAbsent(id, (transformerID) -> {
65              try {
66                  Optional<MCRContentTransformer> configuredTransformer = getConfiguredTransformer(id);
67                  if (configuredTransformer.isPresent()) {
68                      return configuredTransformer.get();
69                  }
70                  return buildLayoutTransformer(id);
71              } catch (Exception e) {
72                  throw new MCRException("Error while creating Transformer!", e);
73              }
74          });
75      }
76  
77      protected Optional<MCRContentTransformer> getConfiguredTransformer(String id) {
78          return Optional.ofNullable(MCRContentTransformerFactory.getTransformer(id.replaceAll("-default$", "")));
79      }
80  
81      private MCRContentTransformer buildLayoutTransformer(String id)
82          throws ParserConfigurationException, TransformerException, SAXException {
83          String idStripped = id.replaceAll("-default$", "");
84          LOGGER.debug("Configure property MCR.ContentTransformer.{}.Class if you do not want to use default behaviour.",
85              idStripped);
86          String stylesheet = getResourceName(id);
87          if (stylesheet == null) {
88              LOGGER.debug("Using noop transformer for {}", idStripped);
89              return NOOP_TRANSFORMER;
90          }
91          String[] stylesheets = getStylesheets(idStripped, stylesheet);
92          MCRContentTransformer transformer = MCRXSLTransformer.getInstance(stylesheets);
93          LOGGER.debug("Using stylesheet '{}' for {}", Lists.newArrayList(stylesheets), idStripped);
94          return transformer;
95      }
96  
97      protected String[] getStylesheets(String id, String stylesheet)
98          throws TransformerException, SAXException, ParserConfigurationException {
99          List<String> ignore = MCRConfiguration2.getString("MCR.LayoutTransformerFactory.Default.Ignore")
100             .map(MCRConfiguration2::splitValue)
101             .map(s1 -> s1.collect(Collectors.toList()))
102             .orElseGet(Collections::emptyList);
103         List<String> defaults = Collections.emptyList();
104         if (!ignore.contains(id)) {
105             MCRXSLTransformer transformerTest = MCRXSLTransformer.getInstance(stylesheet);
106             String outputMethod = transformerTest.getOutputProperties().getProperty(OutputKeys.METHOD, "xml");
107             if (isXMLOutput(outputMethod, transformerTest)) {
108                 defaults = MCRConfiguration2.getString("MCR.LayoutTransformerFactory.Default.Stylesheets")
109                     .map(MCRConfiguration2::splitValue)
110                     .map(s -> s.collect(Collectors.toList()))
111                     .orElseGet(Collections::emptyList);
112             }
113         }
114         String[] stylesheets = new String[1 + defaults.size()];
115         stylesheets[0] = stylesheet;
116         for (int i = 0; i < defaults.size(); i++) {
117             stylesheets[i + 1] = defaults.get(i);
118         }
119         return stylesheets;
120     }
121 
122     protected boolean isXMLOutput(String outputMethod, MCRXSLTransformer transformerTest)
123         throws ParserConfigurationException, TransformerException, SAXException {
124         return "xml".equals(outputMethod);
125     }
126 
127     private String getResourceName(String id) {
128         LOGGER.debug("MCRLayoutService using style {}", id);
129 
130         String styleName = buildStylesheetName(id);
131         try {
132             if (MCRXMLResource.instance().exists(styleName, MCRClassTools.getClassLoader())) {
133                 return styleName;
134             }
135         } catch (Exception e) {
136             throw new MCRException("Error while loading stylesheet: " + styleName, e);
137         }
138 
139         // If no stylesheet exists, forward raw xml instead
140         // You can transform raw xml code by providing a stylesheed named
141         // [doctype]-xml.xsl now
142         if (id.endsWith("-xml") || id.endsWith("-default")) {
143             LOGGER.warn("XSL stylesheet not found: {}", styleName);
144             return null;
145         }
146         throw new MCRException("XSL stylesheet not found: " + styleName);
147     }
148 
149     /**
150      * Builds the filename of the stylesheet to use, e. g. "playlist-simple.xsl"
151      */
152     private String buildStylesheetName(String id) {
153         return String.format(Locale.ROOT, "xsl/%s.xsl", id.replaceAll("-default$", ""));
154     }
155 
156 }