/******************************************************************************* * 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$ } }