]> git.argeo.org Git - gpl/argeo-suite.git/blob - org.argeo.app.servlet.publish/src/org/argeo/app/servlet/publish/DbkServlet.java
[maven-release-plugin] prepare release argeo-suite-2.3.3
[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.ByteArrayInputStream;
4 import java.io.ByteArrayOutputStream;
5 import java.io.IOException;
6 import java.io.InputStream;
7 import java.net.URLDecoder;
8 import java.nio.charset.StandardCharsets;
9 import java.nio.file.Files;
10 import java.nio.file.Paths;
11 import java.util.Collections;
12 import java.util.HashMap;
13 import java.util.Map;
14
15 import javax.jcr.Node;
16 import javax.jcr.Repository;
17 import javax.jcr.RepositoryException;
18 import javax.jcr.Session;
19 import javax.jcr.nodetype.NodeType;
20 import javax.servlet.ServletException;
21 import javax.servlet.http.HttpServlet;
22 import javax.servlet.http.HttpServletRequest;
23 import javax.servlet.http.HttpServletResponse;
24 import javax.xml.parsers.DocumentBuilder;
25 import javax.xml.parsers.DocumentBuilderFactory;
26 import javax.xml.transform.Result;
27 import javax.xml.transform.Source;
28 import javax.xml.transform.Templates;
29 import javax.xml.transform.Transformer;
30 import javax.xml.transform.TransformerConfigurationException;
31 import javax.xml.transform.TransformerFactory;
32 import javax.xml.transform.dom.DOMSource;
33 import javax.xml.transform.stream.StreamResult;
34 import javax.xml.transform.stream.StreamSource;
35
36 import org.apache.commons.io.IOUtils;
37 import org.apache.xalan.processor.TransformerFactoryImpl;
38 import org.argeo.api.cms.CmsTheme;
39 import org.argeo.app.docbook.DbkType;
40 import org.argeo.app.docbook.DbkUtils;
41 import org.argeo.cms.auth.RemoteAuthUtils;
42 import org.argeo.cms.servlet.ServletHttpRequest;
43 import org.argeo.jcr.Jcr;
44 import org.argeo.jcr.JcrException;
45 import org.argeo.jcr.JcrUtils;
46 import org.w3c.dom.Document;
47
48 /**
49 * A servlet transforming a dbk:* JCR node into HTML, using the DocBook XSL.
50 */
51 public class DbkServlet extends HttpServlet {
52 private static final long serialVersionUID = 6906020513498289335L;
53
54 private Repository repository;
55
56 private DocumentBuilderFactory documentBuilderFactory;
57 private TransformerFactory transformerFactory;
58 private Templates docBoookTemplates;
59
60 private Map<String, CmsTheme> themes = Collections.synchronizedMap(new HashMap<>());
61
62 @Override
63 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
64
65 String pathInfo = req.getPathInfo();
66 if (pathInfo.startsWith("//"))
67 pathInfo = pathInfo.substring(1);
68 String path = URLDecoder.decode(pathInfo, StandardCharsets.UTF_8);
69
70 if (path.toLowerCase().endsWith(".css")) {
71 path = path.substring(1);
72 int firstSlash = path.indexOf('/');
73 String themeId = path.substring(0, firstSlash);
74 String cssPath = path.substring(firstSlash);
75 CmsTheme cmsTheme = themes.get(themeId);
76 if (cmsTheme == null)
77 throw new IllegalArgumentException("Theme " + themeId + " not found.");
78 resp.setContentType("text/css");
79 IOUtils.copy(cmsTheme.getResourceAsStream(cssPath), resp.getOutputStream());
80 return;
81 }
82
83 if (path.toLowerCase().endsWith("/index.html")) {
84 path = path.substring(0, path.length() - "/index.html".length());
85 }
86
87 Session session = null;
88 try {
89 session = RemoteAuthUtils.doAs(() -> Jcr.login(repository, null), new ServletHttpRequest(req));
90 Node node = session.getNode(path);
91
92 if (node.hasNode(DbkType.article.get())) {
93 Node dbkNode = node.getNode(DbkType.article.get());
94 if (DbkUtils.isDbk(dbkNode)) {
95 CmsTheme cmsTheme = null;
96 String themeId = req.getParameter("themeId");
97 if (themeId != null) {
98 cmsTheme = themes.get(themeId);
99 if (cmsTheme == null)
100 throw new IllegalArgumentException("Theme " + themeId + " not found.");
101 }
102
103 // TODO customise DocBook so that it outputs UTF-8
104 // see http://www.sagehill.net/docbookxsl/OutputEncoding.html
105 resp.setContentType("text/html; charset=ISO-8859-1");
106
107 // TODO optimise with pipes, SAX, etc. ?
108 byte[] arr;
109 try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
110 session.exportDocumentView(dbkNode.getPath(), out, true, false);
111 arr = out.toByteArray();
112 // System.out.println(new String(arr, StandardCharsets.UTF_8));
113 } catch (RepositoryException e) {
114 throw new JcrException(e);
115 }
116
117 try (InputStream in = new ByteArrayInputStream(arr);
118 // ByteArrayOutputStream out = new ByteArrayOutputStream();
119 ) {
120
121 Result xmlOutput = new StreamResult(resp.getOutputStream());
122
123 DocumentBuilder docBuilder = documentBuilderFactory.newDocumentBuilder();
124 // Document doc = docBuilder.parse(new File(
125 // System.getProperty("user.home") + "/dev/git/gpl/argeo-qa/doc/platform/argeo-platform.dbk.xml"));
126 Document doc = docBuilder.parse(in);
127 Source xmlInput = new DOMSource(doc);
128
129 Transformer transformer = docBoookTemplates.newTransformer();
130
131 // gather CSS
132 if (cmsTheme != null) {
133 StringBuilder sb = new StringBuilder();
134 for (String cssPath : cmsTheme.getWebCssPaths()) {
135 sb.append(req.getContextPath()).append(req.getServletPath()).append('/');
136 sb.append(themeId).append('/').append(cssPath).append(' ');
137 }
138 // FIXME make it more generic
139 sb.append("https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap")
140 .append(' ');
141 sb.append(
142 "https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,300;0,400;0,600;1,400&display=swap")
143 .append(' ');
144 if (sb.length() > 0)
145 transformer.setParameter("html.stylesheet", sb.toString());
146 }
147 transformer.transform(xmlInput, xmlOutput);
148 // resp.getOutputStream().write(out.toByteArray());
149 } catch (Exception e) {
150 throw new ServletException("Cannot transform " + path, e);
151 }
152 }
153 } else {
154 if (node.isNodeType(NodeType.NT_FILE)) {// media download etc.
155 String fileNameLowerCase = node.getName().toLowerCase();
156 if (fileNameLowerCase.endsWith(".jpg") || fileNameLowerCase.endsWith(".jpeg")) {
157 resp.setContentType("image/jpeg");
158 } else if (fileNameLowerCase.endsWith(".png")) {
159 resp.setContentType("image/png");
160 } else if (fileNameLowerCase.endsWith(".gif")) {
161 resp.setContentType("image/gif");
162 } else if (fileNameLowerCase.endsWith(".svg")) {
163 resp.setContentType("image/svg+xml");
164 } else {
165 // TODO know more content types...
166 resp.setHeader("Content-Disposition", "attachment; filename=\"" + node.getName() + "\"");
167 }
168 IOUtils.copy(JcrUtils.getFileAsStream(node), resp.getOutputStream());
169 } else {
170 throw new IllegalArgumentException("Unsupported node " + node);
171 }
172 }
173 } catch (RepositoryException e1) {
174 throw new JcrException(e1);
175 } finally {
176 Jcr.logout(session);
177 }
178 }
179
180 @Override
181 public void init() throws ServletException {
182
183 // TODO improve configuration and provisioning of DocBook XSL
184 String xslBase = System.getProperty("argeo.docbook.xsl");
185 if (xslBase == null) {
186 // We need namespace aware XSL!
187 // Fedora (sudo dnf install docbook5-style-xsl)
188 String defaultXslBase = "/usr/share/sgml/docbook/xsl-ns-stylesheets";
189 if (!Files.exists(Paths.get(defaultXslBase))) {
190 defaultXslBase = "/opt/docbook-xsl";
191 if (!Files.exists(Paths.get(defaultXslBase))) {
192 throw new ServletException("System property argeo.docbook.xsl is not set and default location "
193 + defaultXslBase + " does not exist.");
194 }
195 }
196 xslBase = defaultXslBase;
197
198 }
199 String xsl = xslBase + "/html/docbook.xsl";
200
201 documentBuilderFactory = DocumentBuilderFactory.newInstance();
202 documentBuilderFactory.setXIncludeAware(true);
203 documentBuilderFactory.setNamespaceAware(true);
204
205 // We must explicitly use the non-XSLTC transformer, as XSLTC is not working
206 // with DocBook stylesheets
207 transformerFactory = new TransformerFactoryImpl();
208
209 Source xslSource = new StreamSource(xsl);
210 try {
211 docBoookTemplates = transformerFactory.newTemplates(xslSource);
212 if (docBoookTemplates == null)
213 throw new ServletException("Could not instantiate XSL " + xsl);
214 } catch (TransformerConfigurationException e) {
215 throw new ServletException("Cannot instantiate XSL " + xsl, e);
216 }
217 }
218
219 public void setRepository(Repository repository) {
220 this.repository = repository;
221 }
222
223 public void addTheme(CmsTheme theme, Map<String, String> properties) {
224 themes.put(theme.getThemeId(), theme);
225 }
226
227 public void removeTheme(CmsTheme theme, Map<String, String> properties) {
228 themes.remove(theme.getThemeId());
229 }
230
231 }