| /******************************************************************************* |
| * Copyright (c) 2007, 2016 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.help.internal.webapp.data; |
| |
| import java.io.BufferedReader; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.Reader; |
| import java.io.Writer; |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.StringTokenizer; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import javax.servlet.RequestDispatcher; |
| import javax.servlet.ServletContext; |
| import javax.servlet.ServletException; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| import org.eclipse.help.HelpSystem; |
| import org.eclipse.help.IToc; |
| import org.eclipse.help.ITopic; |
| import org.eclipse.help.base.AbstractHelpScope; |
| import org.eclipse.help.internal.HelpPlugin; |
| import org.eclipse.help.internal.base.BaseHelpSystem; |
| import org.eclipse.help.internal.base.scope.ScopeUtils; |
| import org.eclipse.help.internal.search.HTMLDocParser; |
| import org.eclipse.help.internal.webapp.HelpWebappPlugin; |
| import org.eclipse.help.internal.xhtml.DynamicXHTMLProcessor; |
| |
| /* |
| * Used by the print jsp to access print-related data. |
| */ |
| public class PrintData extends RequestData { |
| |
| // default max connections for concurrent print |
| private static final int defaultMaxConnections = 10; |
| |
| // default max topics allowed for one print request |
| private static final int defaultMaxTopics = 500; |
| |
| // where to inject the section numbers |
| private static final Pattern PATTERN_HEADING = Pattern.compile("<body.*?>[\\s]*?([^<\\s])", Pattern.MULTILINE | Pattern.DOTALL | Pattern.CASE_INSENSITIVE); //$NON-NLS-1$ |
| |
| // to normalize external links to new base href |
| private static final Pattern PATTERN_LINK = Pattern.compile("(src|href)=\"(.*?\")", Pattern.MULTILINE | Pattern.DOTALL | Pattern.CASE_INSENSITIVE); //$NON-NLS-1$ |
| |
| // Where to inject css |
| private static final Pattern PATTERN_END_HEAD = Pattern.compile("</head.*?>", Pattern.MULTILINE | Pattern.DOTALL | Pattern.CASE_INSENSITIVE); //$NON-NLS-1$ |
| |
| private static boolean initialized = false; |
| |
| private static int allowedConnections; |
| |
| private static int allowedMaxTopics; |
| |
| private boolean confirmed; |
| |
| // flag right-to-left direction of text |
| private boolean isRTL; |
| |
| private AbstractHelpScope scope; |
| |
| /* |
| * Constructs the print data for the given request. |
| */ |
| public PrintData(ServletContext context, HttpServletRequest request, HttpServletResponse response) { |
| super(context, request, response); |
| |
| if (!initialized) { |
| initPreferences(preferences); |
| } |
| |
| isRTL = UrlUtil.isRTL(request, response); |
| scope = RequestScope.getScope(request, response, false); |
| |
| String confirmString = request.getParameter("confirmed"); //$NON-NLS-1$ |
| if ((confirmString != null) && ("true".equals(confirmString))) { //$NON-NLS-1$ |
| confirmed = true; |
| } else { |
| confirmed = false; |
| } |
| |
| } |
| |
| /* |
| * Returns the overall topic's title. |
| */ |
| public String getTitle() { |
| return getTopic().getLabel(); |
| } |
| |
| /* |
| * Returns the href of the toc containing the topic(s) to print. |
| */ |
| public String getTocHref() { |
| return getToc().getHref(); |
| } |
| |
| /* |
| * Returns the href of the root topic to print. |
| */ |
| public String getTopicHref() { |
| return getTopic().getHref(); |
| } |
| |
| /* |
| * init properties set in base preference.ini for quick print |
| */ |
| private static synchronized void initPreferences(WebappPreferences preferences) { |
| |
| if (initialized) { |
| return; |
| } |
| |
| // set max connection numbers for concurrent access |
| String maxConnections = preferences.getQuickPrintMaxConnections(); |
| if ((null == maxConnections) || ("".equals(maxConnections.trim()))) { //$NON-NLS-1$ |
| allowedConnections = defaultMaxConnections; |
| } else { |
| try { |
| allowedConnections = Integer.parseInt(maxConnections); |
| } catch (NumberFormatException e) { |
| HelpWebappPlugin.logError("Init maxConnections error. Set to default.", e); //$NON-NLS-1$ |
| allowedConnections = defaultMaxConnections; |
| } |
| } |
| |
| // set max topics allowed to print in one request |
| String maxTopics = preferences.getQuickPrintMaxTopics(); |
| if ((null == maxTopics) || ("".equals(maxTopics.trim()))) { //$NON-NLS-1$ |
| allowedMaxTopics = defaultMaxTopics; |
| } else { |
| try { |
| allowedMaxTopics = Integer.parseInt(maxTopics); |
| } catch (NumberFormatException e) { |
| HelpWebappPlugin.logError("Init maxTopics error. Set to default.", e); //$NON-NLS-1$ |
| allowedMaxTopics = defaultMaxTopics; |
| } |
| } |
| |
| initialized = true; |
| } |
| |
| /* |
| * Generates resources to print |
| */ |
| public void generateResources(Writer out) throws IOException, ServletException { |
| // check resource allocation |
| if (!getConnection()) { |
| RequestDispatcher rd = context.getRequestDispatcher("/advanced/printError.jsp"); //$NON-NLS-1$ |
| request.setAttribute("msg", "noConnection"); //$NON-NLS-1$ //$NON-NLS-2$ |
| rd.forward(request, response); |
| return; |
| } |
| |
| |
| ITopic topic = getTopic(); // topic selected for print |
| int topicRequested = topicsRequested(topic); |
| if (topicRequested > allowedMaxTopics) { |
| if (!confirmed) { |
| releaseConnection(); |
| RequestDispatcher rd = context.getRequestDispatcher("/advanced/printConfirm.jsp"); //$NON-NLS-1$ |
| request.setAttribute("topicsRequested", String.valueOf(topicRequested)); //$NON-NLS-1$ |
| request.setAttribute("allowedMaxTopics", String.valueOf(allowedMaxTopics)); //$NON-NLS-1$ |
| rd.forward(request, response); |
| return; |
| } |
| } |
| |
| try { |
| generateToc(out); |
| generateContent(out); |
| } catch (IOException e) { |
| RequestDispatcher rd = context.getRequestDispatcher("/advanced/printError.jsp"); //$NON-NLS-1$ |
| request.setAttribute("msg", "ioException"); //$NON-NLS-1$ //$NON-NLS-2$ |
| rd.forward(request, response); |
| } finally { |
| releaseConnection(); |
| } |
| } |
| |
| private static synchronized boolean getConnection() { |
| if (allowedConnections > 0) { |
| allowedConnections--; |
| return true; |
| } |
| return false; |
| } |
| |
| private static synchronized void releaseConnection() { |
| allowedConnections++; |
| } |
| |
| /* |
| * Calculate the amount of topics to print in one request |
| */ |
| private int topicsRequested(ITopic topic) { |
| int topicsRequested = 0; |
| if (topic.getHref() != null && topic.getHref().length() > 0) { |
| topicsRequested++; |
| } |
| |
| ITopic[] subtopics = ScopeUtils.inScopeTopics(topic.getSubtopics(), scope); |
| for (ITopic subtopic : subtopics) { |
| topicsRequested += topicsRequested(subtopic); |
| } |
| return topicsRequested; |
| } |
| |
| /* |
| * Generates and outputs a table of contents div with links. |
| */ |
| private void generateToc(Writer out) throws IOException { |
| int tocGenerated = 0; |
| |
| out.write("<html>\n"); //$NON-NLS-1$ |
| out.write("<head>\n"); //$NON-NLS-1$ |
| out.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n"); //$NON-NLS-1$ |
| out.write("<title>" + UrlUtil.htmlEncode(getTitle()) +"</title>\n"); //$NON-NLS-1$ //$NON-NLS-2$ |
| out.write("<link rel=\"stylesheet\" href=\"print.css\" charset=\"utf-8\" type=\"text/css\">\n"); //$NON-NLS-1$ |
| out.write("</head>\n"); //$NON-NLS-1$ |
| out.write("<body dir=\"" + (isRTL ? "right" : "left") + "\" onload=\"print()\">\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ |
| out.write("<div id=\"toc\">\n"); //$NON-NLS-1$ |
| out.write("<h1>"); //$NON-NLS-1$ |
| out.write(getTitle()); |
| out.write("</h1>\n"); //$NON-NLS-1$ |
| out.write("<h2>"); //$NON-NLS-1$ |
| out.write(ServletResources.getString("TocHeading", request)); //$NON-NLS-1$ |
| out.write("</h2>\n"); //$NON-NLS-1$ |
| out.write("<div id=\"toc_content\">\n"); //$NON-NLS-1$ |
| ITopic topic = getTopic(); |
| |
| String href = topic.getHref(); |
| if (href != null && href.length() > 0) { |
| tocGenerated++; |
| } |
| ITopic[] subtopics = ScopeUtils.inScopeTopics(topic.getSubtopics(), scope); |
| for (int i = 0; i < subtopics.length; ++i) { |
| tocGenerated = generateToc(subtopics[i], String.valueOf(i + 1), tocGenerated, out); |
| } |
| |
| out.write("</div>\n"); //$NON-NLS-1$ |
| out.write("</div>\n"); //$NON-NLS-1$ |
| out.write("</body>\n"); //$NON-NLS-1$ |
| out.write("</html>\n"); //$NON-NLS-1$ |
| } |
| |
| /* |
| * Auxiliary method for recursively generating table of contents div. |
| */ |
| private int generateToc(ITopic topic, String sectionId, int tocGenerated, Writer out) throws IOException { |
| if (tocGenerated < allowedMaxTopics) { |
| out.write("<div class=\"toc_" + (sectionId.length() > 2 ? "sub" : "") + "entry\">\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ |
| out.write(sectionId + ". " + "<a href=\"#section" + sectionId + "\">" + topic.getLabel() + "</a>\n"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ |
| |
| String href = topic.getHref(); |
| if (href != null && href.length() > 0) { |
| tocGenerated++; |
| } |
| |
| ITopic[] subtopics = ScopeUtils.inScopeTopics(topic.getSubtopics(), scope); |
| for (int i = 0; i < subtopics.length; ++i) { |
| String subsectionId = sectionId + "." + (i + 1); //$NON-NLS-1$ |
| tocGenerated = generateToc(subtopics[i], subsectionId, tocGenerated, out); |
| } |
| out.write("</div>\n"); //$NON-NLS-1$ |
| return tocGenerated; |
| } |
| return tocGenerated; |
| } |
| |
| /* |
| * Generates the content to print (the merged topics). |
| */ |
| public void generateContent(Writer out) throws IOException { |
| int topicsGenerated = 0; |
| generateContent(getTopic(), null, topicsGenerated, new HashSet<String>(), out); |
| } |
| |
| /* |
| * Auxiliary method for recursively generating print content. |
| */ |
| private int generateContent(ITopic topic, String sectionId, int topicsGenerated, Set<String> generated, Writer out) throws IOException { |
| if (topicsGenerated < allowedMaxTopics) { |
| String href = topic.getHref(); |
| if (href != null && href.length() > 0) { |
| topicsGenerated++; |
| // get the topic content |
| href = removeAnchor(href); |
| String pathHref = href.substring(0, href.lastIndexOf('/') + 1); |
| String baseHref = "../topic" + pathHref; //$NON-NLS-1$ |
| String content; |
| if (!generated.contains(href)) { |
| generated.add(href); |
| content = getContent(href, locale); |
| // root topic doesn't have sectionId |
| if (sectionId != null) { |
| content = injectHeading(content, sectionId); |
| } |
| content = normalizeHrefs(content, baseHref); |
| content = injectCss(content); |
| out.write(content); |
| } |
| } |
| ITopic[] subtopics = ScopeUtils.inScopeTopics(topic.getSubtopics(), scope); |
| for (int i = 0; i < subtopics.length; ++i) { |
| String subsectionId = (sectionId != null ? sectionId + "." : "") + (i + 1); //$NON-NLS-1$ //$NON-NLS-2$ |
| topicsGenerated = generateContent(subtopics[i], subsectionId, topicsGenerated, generated, out); |
| } |
| return topicsGenerated; |
| } else { |
| return topicsGenerated; |
| } |
| } |
| |
| /* |
| * Injects the sectionId into the document heading. |
| * public static for JUnit Testing |
| */ |
| public static String injectHeading(String content, String sectionId) { |
| Matcher matcher = PATTERN_HEADING.matcher(content); |
| if (matcher.find()) { |
| String heading = "<a id=\"section" + sectionId + "\">" + sectionId + ". </a>"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| return content.substring(0, matcher.start(1)) + heading + content.substring(matcher.start(1)); |
| } |
| return content; |
| } |
| /* |
| * |
| * Injects the sectionId into the document heading. |
| */ |
| private String injectCss(String content) { |
| Matcher matcher = PATTERN_END_HEAD.matcher(content); |
| if (matcher.find()) { |
| String css = getCssIncludes(); //"<link rel=\"stylesheet\" type=\"text/css\" href=\"../testbook.css\">"; |
| return content.substring(0, matcher.start(0)) + css + content.substring(matcher.start(0)); |
| } |
| return content; |
| } |
| |
| /* |
| * Normalizes all external links since we're not at the same base href as the |
| * topics we're printing. |
| */ |
| private String normalizeHrefs(String content, String baseHref) { |
| StringBuffer buf = new StringBuffer(); |
| Matcher matcher = PATTERN_LINK.matcher(content); |
| int prev = 0; |
| while (matcher.find()) { |
| buf.append(content.substring(prev, matcher.start(2))); |
| buf.append(baseHref); |
| buf.append(matcher.group(2)); |
| prev = matcher.end(); |
| } |
| buf.append(content.substring(prev)); |
| return buf.toString(); |
| } |
| |
| /* |
| * Returns the string content of the referenced topic in UTF-8. |
| */ |
| private String getContent(String href, String locale) { |
| InputStream in = HelpSystem.getHelpContent(href, locale); |
| StringBuffer buf = new StringBuffer(); |
| InputStream rawInput=null; |
| if (in != null) { |
| try { |
| String charset = HTMLDocParser.getCharsetFromHTML(in); |
| if (charset == null) { |
| charset = "UTF-8"; //$NON-NLS-1$ |
| } |
| rawInput = HelpSystem.getHelpContent(href, locale); |
| boolean filter = BaseHelpSystem.getMode() != BaseHelpSystem.MODE_INFOCENTER; |
| in = DynamicXHTMLProcessor.process(href, rawInput, locale, filter); |
| |
| if (in == null) { |
| in = HelpSystem.getHelpContent(href, locale); |
| } |
| |
| try (Reader reader = new BufferedReader(new InputStreamReader(in, charset))) { |
| char[] cbuf = new char[4096]; |
| int num; |
| while ((num = reader.read(cbuf)) > 0) { |
| buf.append(cbuf, 0, num); |
| } |
| } |
| } |
| catch (Exception e) { |
| String msg = "Error retrieving print preview content for " + href; //$NON-NLS-1$ |
| HelpWebappPlugin.logError(msg, e); |
| } |
| finally { |
| try { |
| in.close(); |
| } |
| catch (IOException e) { |
| } |
| try { |
| if (rawInput != null) |
| rawInput.close(); |
| } |
| catch (Exception e) {} |
| } |
| } |
| return buf.toString(); |
| } |
| |
| /* |
| * Returns the toc containing the selected topic(s). |
| */ |
| private IToc getToc() { |
| String tocParam = request.getParameter("toc"); //$NON-NLS-1$ |
| if (tocParam != null && tocParam.length() > 0) { |
| return HelpPlugin.getTocManager().getToc(tocParam, getLocale()); |
| } |
| String topicParam = request.getParameter("topic"); //$NON-NLS-1$ |
| if (topicParam != null && topicParam.length() > 0) { |
| if (topicParam.startsWith("/../nav/")) { //$NON-NLS-1$ |
| String navPath = topicParam.substring(8); |
| StringTokenizer tok = new StringTokenizer(navPath, "_"); //$NON-NLS-1$ |
| int index = Integer.parseInt(tok.nextToken()); |
| return HelpPlugin.getTocManager().getTocs(getLocale())[index]; |
| } |
| IToc[] tocs = HelpPlugin.getTocManager().getTocs(getLocale()); |
| for (IToc toc : tocs) { |
| if (toc.getTopic(topicParam) != null) { |
| return toc; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /* |
| * Returns the selected topic. |
| */ |
| private ITopic getTopic() { |
| String topicParam = request.getParameter("topic"); //$NON-NLS-1$ |
| String anchorParam = request.getParameter("anchor"); //$NON-NLS-1$ |
| if (anchorParam!=null) { |
| topicParam = topicParam + '#' + anchorParam; |
| } |
| if (topicParam != null && topicParam.length() > 0) { |
| if (topicParam.startsWith("/../nav/")) { //$NON-NLS-1$ |
| String navPath = topicParam.substring(8); |
| StringTokenizer tok = new StringTokenizer(navPath, "_"); //$NON-NLS-1$ |
| int index = Integer.parseInt(tok.nextToken()); |
| ITopic topic = HelpPlugin.getTocManager().getTocs(getLocale())[index].getTopic(null); |
| while (tok.hasMoreTokens()) { |
| index = Integer.parseInt(tok.nextToken()); |
| topic = topic.getSubtopics()[index]; |
| } |
| return topic; |
| } |
| else { |
| IToc[] tocs = HelpPlugin.getTocManager().getTocs(getLocale()); |
| for (IToc toc : tocs) { |
| ITopic topic = toc.getTopic(topicParam); |
| if (topic != null) { |
| return topic; |
| } |
| // Test for root node as topic |
| topic = toc.getTopic(null); |
| if (topicParam.equals(topic.getHref())) { |
| return topic; |
| } |
| } |
| } |
| return null; |
| } |
| return getToc().getTopic(null); |
| } |
| |
| private static String removeAnchor(String href) { |
| int index = href.indexOf('#'); |
| if (index != -1) { |
| return href.substring(0, index); |
| } |
| return href; |
| } |
| |
| private String getCssIncludes() { |
| List<String> css = new ArrayList<>(); |
| CssUtil.addCssFiles("topic_css", css); //$NON-NLS-1$ |
| return CssUtil.createCssIncludes(css, "../"); //$NON-NLS-1$ |
| } |
| } |