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.component.fo.common.fo;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.OutputStream;
24  import java.net.URI;
25  import java.net.URL;
26  import java.net.URLConnection;
27  import java.text.MessageFormat;
28  import java.util.Arrays;
29  import java.util.HashMap;
30  import java.util.Locale;
31  import java.util.Map;
32  import java.util.Optional;
33  
34  import javax.xml.transform.Result;
35  import javax.xml.transform.Source;
36  import javax.xml.transform.Transformer;
37  import javax.xml.transform.TransformerException;
38  import javax.xml.transform.TransformerFactory;
39  import javax.xml.transform.TransformerFactoryConfigurationError;
40  import javax.xml.transform.sax.SAXResult;
41  
42  import org.apache.fop.apps.EnvironmentalProfileFactory;
43  import org.apache.fop.apps.FOPException;
44  import org.apache.fop.apps.FOUserAgent;
45  import org.apache.fop.apps.Fop;
46  import org.apache.fop.apps.FopFactory;
47  import org.apache.fop.apps.FopFactoryBuilder;
48  import org.apache.fop.apps.MimeConstants;
49  import org.apache.fop.configuration.Configuration;
50  import org.apache.fop.configuration.ConfigurationException;
51  import org.apache.fop.configuration.DefaultConfigurationBuilder;
52  import org.apache.logging.log4j.LogManager;
53  import org.apache.logging.log4j.Logger;
54  import org.apache.xmlgraphics.io.Resource;
55  import org.apache.xmlgraphics.io.ResourceResolver;
56  import org.mycore.common.MCRCoreVersion;
57  import org.mycore.common.config.MCRConfiguration2;
58  import org.mycore.common.config.MCRConfigurationDir;
59  import org.mycore.common.content.MCRContent;
60  import org.mycore.common.content.MCRSourceContent;
61  import org.mycore.common.xml.MCRURIResolver;
62  import org.mycore.common.xsl.MCRErrorListener;
63  
64  /**
65   * This class implements the interface to use configured XSL-FO formatters for the layout service.
66   * 
67   * @author Jens Kupferschmidt
68   * @author Ren\u00E9 Adler (eagle)
69   * @version $Revision: 1.8 $ $Date: 2008/05/28 13:43:31 $
70   */
71  
72  public class MCRFoFormatterFOP implements MCRFoFormatterInterface {
73  
74      private static final Logger LOGGER = LogManager.getLogger();
75  
76      private FopFactory fopFactory;
77  
78      final ResourceResolver resolver = new ResourceResolver() {
79          public OutputStream getOutputStream(URI uri) throws IOException {
80              URL url = MCRURIResolver.getServletContext().getResource(uri.toString());
81              return url.openConnection().getOutputStream();
82          }
83  
84          public Resource getResource(URI uri) throws IOException {
85              MCRContent content;
86              try {
87                  content = MCRSourceContent.getInstance(uri.toString());
88                  return new Resource(uri.getScheme(), content.getInputStream());
89              } catch (TransformerException e) {
90                  LOGGER.error("Error while resolving uri: {}", uri);
91              }
92  
93              return null;
94          }
95      };
96  
97      public MCRFoFormatterFOP() {
98          FopFactoryBuilder fopFactoryBuilder;
99          // use restricted io to prevent issues with font caching on some systems
100         fopFactoryBuilder = new FopFactoryBuilder(
101             EnvironmentalProfileFactory.createRestrictedIO(URI.create("resource:/"), resolver));
102         final String foCfg = MCRConfiguration2.getString("MCR.LayoutService.FoFormatter.FOP.config").orElse("");
103         if (!foCfg.isEmpty()) {
104             try {
105                 URL configResource = MCRConfigurationDir.getConfigResource(foCfg);
106                 URLConnection con = configResource.openConnection();
107                 final DefaultConfigurationBuilder cfgBuilder = new DefaultConfigurationBuilder();
108                 final Configuration cfg;
109                 try (InputStream is = con.getInputStream();) {
110                     cfg = cfgBuilder.build(is);
111                 }
112                 fopFactoryBuilder.setConfiguration(cfg);
113 
114                 // FIXME Workaround to get hyphenation work in FOP.
115                 // FOP should use "hyphenation-base" to get base URI for patterns
116                 Optional<Configuration[]> hyphPat = Optional.ofNullable(cfg.getChildren("hyphenation-pattern"));
117                 hyphPat.ifPresent(configurations -> {
118                     Map<String, String> hyphPatMap = new HashMap<>();
119                     Arrays.stream(configurations).forEach(c -> {
120                         try {
121                             String lang = c.getAttribute("lang");
122                             String file = c.getValue();
123 
124                             if ((lang != null && !lang.isEmpty()) && (file != null && !file.isEmpty())) {
125                                 hyphPatMap.put(lang, file);
126                             }
127                         } catch (Exception e) {
128                         }
129                     });
130                     fopFactoryBuilder.setHyphPatNames(hyphPatMap);
131                 });
132 
133             } catch (ConfigurationException | IOException e) {
134                 LOGGER.error("Exception while loading FOP configuration from {}.", foCfg, e);
135             }
136         }
137         fopFactory = fopFactoryBuilder.build();
138         getTransformerFactory();
139     }
140 
141     private static TransformerFactory getTransformerFactory() throws TransformerFactoryConfigurationError {
142         TransformerFactory transformerFactory = TransformerFactory.newInstance();
143         transformerFactory.setURIResolver(MCRURIResolver.instance());
144         transformerFactory.setErrorListener(MCRErrorListener.getInstance());
145         return transformerFactory;
146     }
147 
148     @Override
149     public void transform(MCRContent input, OutputStream out) throws TransformerException, IOException {
150         try {
151             final FOUserAgent userAgent = fopFactory.newFOUserAgent();
152             userAgent.setProducer(new MessageFormat("MyCoRe {0} ({1})", Locale.ROOT)
153                 .format(new Object[] { MCRCoreVersion.getCompleteVersion(), userAgent.getProducer() }));
154 
155             final Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, userAgent, out);
156             final Source src = input.getSource();
157             final Result res = new SAXResult(fop.getDefaultHandler());
158             Transformer transformer = getTransformerFactory().newTransformer();
159             transformer.transform(src, res);
160         } catch (FOPException e) {
161             throw new TransformerException(e);
162         } finally {
163             out.close();
164         }
165     }
166 }