]> git.argeo.org Git - gpl/argeo-suite.git/blob - org.argeo.app.servlet.publish/src/org/argeo/app/servlet/publish/DbkServlet.java
Start improving People UI
[gpl/argeo-suite.git] / org.argeo.app.servlet.publish / src / org / argeo / app / servlet / publish / DbkServlet.java
1 package org.argeo.app.servlet.publish;
2
3 import java.io.BufferedOutputStream;
4 import java.io.ByteArrayInputStream;
5 import java.io.ByteArrayOutputStream;
6 import java.io.File;
7 import java.io.FileOutputStream;
8 import java.io.IOException;
9 import java.io.InputStream;
10 import java.io.OutputStream;
11 import java.net.URI;
12 import java.net.URLDecoder;
13 import java.nio.charset.StandardCharsets;
14 import java.nio.file.Files;
15 import java.nio.file.Paths;
16 import java.util.Collections;
17 import java.util.HashMap;
18 import java.util.Map;
19
20 import javax.jcr.Node;
21 import javax.jcr.Repository;
22 import javax.jcr.RepositoryException;
23 import javax.jcr.Session;
24 import javax.jcr.nodetype.NodeType;
25 import javax.servlet.ServletException;
26 import javax.servlet.http.HttpServlet;
27 import javax.servlet.http.HttpServletRequest;
28 import javax.servlet.http.HttpServletResponse;
29 import javax.xml.parsers.DocumentBuilder;
30 import javax.xml.parsers.DocumentBuilderFactory;
31 import javax.xml.transform.Result;
32 import javax.xml.transform.Source;
33 import javax.xml.transform.Templates;
34 import javax.xml.transform.Transformer;
35 import javax.xml.transform.TransformerConfigurationException;
36 import javax.xml.transform.TransformerFactory;
37 import javax.xml.transform.dom.DOMSource;
38 import javax.xml.transform.sax.SAXResult;
39 import javax.xml.transform.stream.StreamResult;
40 import javax.xml.transform.stream.StreamSource;
41
42 import org.apache.commons.io.IOUtils;
43 import org.apache.fop.apps.Fop;
44 import org.apache.fop.apps.FopFactory;
45 import org.apache.xalan.processor.TransformerFactoryImpl;
46 import org.argeo.api.cms.ux.CmsTheme;
47 import org.argeo.app.docbook.DbkType;
48 import org.argeo.app.docbook.DbkUtils;
49 import org.argeo.cms.auth.RemoteAuthUtils;
50 import org.argeo.cms.servlet.ServletHttpRequest;
51 import org.argeo.jcr.Jcr;
52 import org.argeo.jcr.JcrException;
53 import org.argeo.jcr.JcrUtils;
54 import org.w3c.dom.Document;
55
56 /**
57 * A servlet transforming a dbk:* JCR node into HTML, using the DocBook XSL.
58 */
59 public class DbkServlet extends HttpServlet {
60 private static final long serialVersionUID = 6906020513498289335L;
61
62 private Repository repository;
63
64 private DocumentBuilderFactory documentBuilderFactory;
65 private TransformerFactory transformerFactory;
66 private Templates docBoookHtmlTemplates;
67
68 // FOP
69 private Templates docBoookFoTemplates;
70
71 private Map<String, CmsTheme> themes = Collections.synchronizedMap(new HashMap<>());
72
73 @Override
74 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
75
76 String pathInfo = req.getPathInfo();
77 if (pathInfo.startsWith("//"))
78 pathInfo = pathInfo.substring(1);
79 String path = URLDecoder.decode(pathInfo, StandardCharsets.UTF_8);
80
81 if (path.toLowerCase().endsWith(".css")) {
82 path = path.substring(1);
83 int firstSlash = path.indexOf('/');
84 String themeId = path.substring(0, firstSlash);
85 String cssPath = path.substring(firstSlash);
86 CmsTheme cmsTheme = themes.get(themeId);
87 if (cmsTheme == null)
88 throw new IllegalArgumentException("Theme " + themeId + " not found.");
89 resp.setContentType("text/css");
90 IOUtils.copy(cmsTheme.getResourceAsStream(cssPath), resp.getOutputStream());
91 return;
92 }
93
94 if (path.toLowerCase().endsWith("/index.html")) {
95 path = path.substring(0, path.length() - "/index.html".length());
96 }
97
98 boolean pdf = false;
99 if (path.toLowerCase().endsWith(".pdf")) {
100 pdf = true;
101 if (path.toLowerCase().endsWith("/index.pdf")) {
102 path = path.substring(0, path.length() - "/index.pdf".length());
103 }
104 }
105
106 Session session = null;
107 try {
108 session = RemoteAuthUtils.doAs(() -> Jcr.login(repository, null), new ServletHttpRequest(req));
109 Node node = session.getNode(path);
110
111 if (node.hasNode(DbkType.article.get())) {
112 Node dbkNode = node.getNode(DbkType.article.get());
113 if (DbkUtils.isDbk(dbkNode)) {
114 CmsTheme cmsTheme = null;
115 String themeId = req.getParameter("themeId");
116 if (themeId != null) {
117 cmsTheme = themes.get(themeId);
118 if (cmsTheme == null)
119 throw new IllegalArgumentException("Theme " + themeId + " not found.");
120 }
121
122 // TODO customise DocBook so that it outputs UTF-8
123 // see http://www.sagehill.net/docbookxsl/OutputEncoding.html
124 resp.setContentType("text/html; charset=ISO-8859-1");
125
126 // TODO optimise with pipes, SAX, etc. ?
127 byte[] arr;
128 try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
129 session.exportDocumentView(dbkNode.getPath(), out, true, false);
130 arr = out.toByteArray();
131 } catch (RepositoryException e) {
132 throw new JcrException(e);
133 }
134
135 try (InputStream in = new ByteArrayInputStream(arr);) {
136
137 DocumentBuilder docBuilder = documentBuilderFactory.newDocumentBuilder();
138 Document doc = docBuilder.parse(in);
139 Source xmlInput = new DOMSource(doc);
140
141 if (pdf) {
142 //String baseUri = req.getRequestURI();
143 FopFactory fopFactory = FopFactory.newInstance(URI.create(req.getRequestURL().toString()));
144 resp.setContentType("application/pdf");
145
146 // // DocBook to FO
147 // byte[] foBytes;
148 // try (ByteArrayOutputStream out = new ByteArrayOutputStream();) {
149 // Result xmlOutput = new StreamResult(out);
150 // Transformer docBookTransformer = docBoookFoTemplates.newTransformer();
151 // docBookTransformer.transform(xmlInput, xmlOutput);
152 // foBytes = out.toByteArray();
153 // }
154 //
155 // // FO to PDF
156 // try (InputStream foIn = new ByteArrayInputStream(foBytes)) {
157 // Fop fop = fopFactory.newFop("application/pdf", resp.getOutputStream());
158 // Transformer fopTransformer = transformerFactory.newTransformer(); // identity
159 // Source src = new StreamSource(foIn);
160 // Result fopResult = new SAXResult(fop.getDefaultHandler());
161 // fopTransformer.transform(src, fopResult);
162 // }
163 //
164
165 Fop fop = fopFactory.newFop("application/pdf", resp.getOutputStream());
166 Transformer docBookTransformer = docBoookFoTemplates.newTransformer();
167 Result fopResult = new SAXResult(fop.getDefaultHandler());
168 docBookTransformer.transform(xmlInput, fopResult);
169
170 } else {
171 Result xmlOutput = new StreamResult(resp.getOutputStream());
172 resp.setContentType("text/html");
173 Transformer docBookTransformer = docBoookHtmlTemplates.newTransformer();
174
175 // gather CSS
176 if (cmsTheme != null) {
177 StringBuilder sb = new StringBuilder();
178 for (String cssPath : cmsTheme.getWebCssPaths()) {
179 sb.append(req.getContextPath()).append(req.getServletPath()).append('/');
180 sb.append(themeId).append('/').append(cssPath).append(' ');
181 }
182 // FIXME make it more generic
183 sb.append("https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap")
184 .append(' ');
185 sb.append(
186 "https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,300;0,400;0,600;1,400&display=swap")
187 .append(' ');
188 if (sb.length() > 0)
189 docBookTransformer.setParameter("html.stylesheet", sb.toString());
190 }
191 docBookTransformer.transform(xmlInput, xmlOutput);
192 }
193 // resp.getOutputStream().write(out.toByteArray());
194 } catch (Exception e) {
195 throw new ServletException("Cannot transform " + path, e);
196 }
197 }
198 } else {
199 if (node.isNodeType(NodeType.NT_FILE)) {// media download etc.
200 String fileNameLowerCase = node.getName().toLowerCase();
201 if (fileNameLowerCase.endsWith(".jpg") || fileNameLowerCase.endsWith(".jpeg")) {
202 resp.setContentType("image/jpeg");
203 } else if (fileNameLowerCase.endsWith(".png")) {
204 resp.setContentType("image/png");
205 } else if (fileNameLowerCase.endsWith(".gif")) {
206 resp.setContentType("image/gif");
207 } else if (fileNameLowerCase.endsWith(".svg")) {
208 resp.setContentType("image/svg+xml");
209 } else {
210 // TODO know more content types...
211 resp.setHeader("Content-Disposition", "attachment; filename=\"" + node.getName() + "\"");
212 }
213 IOUtils.copy(JcrUtils.getFileAsStream(node), resp.getOutputStream());
214 } else {
215 throw new IllegalArgumentException("Unsupported node " + node);
216 }
217 }
218 } catch (RepositoryException e1) {
219 throw new JcrException(e1);
220 } finally {
221 Jcr.logout(session);
222 }
223 }
224
225 @Override
226 public void init() throws ServletException {
227
228 // TODO improve configuration and provisioning of DocBook XSL
229 String xslBase = System.getProperty("argeo.docbook.xsl");
230 if (xslBase == null) {
231 // We need namespace aware XSL!
232 // Fedora (sudo dnf install docbook5-style-xsl)
233 String defaultXslBase = "/usr/share/xml/docbook/stylesheet/docbook-xsl-ns/";
234 if (!Files.exists(Paths.get(defaultXslBase))) {
235 defaultXslBase = "/opt/docbook-xsl";
236 if (!Files.exists(Paths.get(defaultXslBase))) {
237 throw new ServletException("System property argeo.docbook.xsl is not set and default location "
238 + defaultXslBase + " does not exist.");
239 }
240 }
241 xslBase = defaultXslBase;
242
243 }
244
245 documentBuilderFactory = DocumentBuilderFactory.newInstance();
246 documentBuilderFactory.setXIncludeAware(true);
247 documentBuilderFactory.setNamespaceAware(true);
248
249 // We must explicitly use the non-XSLTC transformer, as XSLTC is not working
250 // with DocBook stylesheets
251 transformerFactory = new TransformerFactoryImpl();
252
253 String htmlXsl = xslBase + "/html/docbook.xsl";
254 docBoookHtmlTemplates = createDocBookTemplates(htmlXsl);
255 String foXsl = xslBase + "/fo/docbook.xsl";
256 docBoookFoTemplates = createDocBookTemplates(foXsl);
257 }
258
259 protected Templates createDocBookTemplates(String xsl) {
260 try {
261 Source xslSource = new StreamSource(xsl);
262 Templates templates = transformerFactory.newTemplates(xslSource);
263 if (templates == null)
264 throw new IllegalStateException("Could not instantiate XSL " + xsl);
265 return templates;
266 } catch (TransformerConfigurationException e) {
267 throw new IllegalStateException("Cannot instantiate XSL " + xsl, e);
268 }
269
270 }
271
272 public void setRepository(Repository repository) {
273 this.repository = repository;
274 }
275
276 public void addTheme(CmsTheme theme, Map<String, String> properties) {
277 themes.put(theme.getThemeId(), theme);
278 }
279
280 public void removeTheme(CmsTheme theme, Map<String, String> properties) {
281 themes.remove(theme.getThemeId());
282 }
283
284 public static void main(String[] args) throws Exception {
285 // Step 1: Construct a FopFactory by specifying a reference to the configuration
286 // file
287 // (reuse if you plan to render multiple documents!)
288 FopFactory fopFactory = FopFactory.newInstance(new File(".").toURI());
289
290 // Step 2: Set up output stream.
291 // Note: Using BufferedOutputStream for performance reasons (helpful with
292 // FileOutputStreams).
293 OutputStream out = new BufferedOutputStream(new FileOutputStream(new File(
294 System.getProperty("user.home") + "/dev/git/unstable/argeo-qa/doc/platform/argeo-platform-test.pdf")));
295
296 try {
297 // Step 3: Construct fop with desired output format
298 Fop fop = fopFactory.newFop("application/pdf", out);
299
300 // Step 4: Setup JAXP using identity transformer
301 TransformerFactory fopTransformerFactory = TransformerFactory.newInstance();
302 Transformer fopTransformer = fopTransformerFactory.newTransformer(); // identity transformer
303
304 // Step 5: Setup input and output for XSLT transformation
305 // Setup input stream
306 Source src = new StreamSource(new File(
307 System.getProperty("user.home") + "/dev/git/unstable/argeo-qa/doc/platform/argeo-platform.fo"));
308
309 // Resulting SAX events (the generated FO) must be piped through to FOP
310 Result res = new SAXResult(fop.getDefaultHandler());
311
312 // Step 6: Start XSLT transformation and FOP processing
313 fopTransformer.transform(src, res);
314
315 } finally {
316 // Clean-up
317 out.close();
318 }
319 }
320 }