| /*=============================================================================# |
| # Copyright (c) 2010, 2020 Stephan Wahlbrink and others. |
| # |
| # This program and the accompanying materials are made available under the |
| # terms of the Eclipse Public License 2.0 which is available at |
| # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 |
| # which is available at https://www.apache.org/licenses/LICENSE-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 |
| # |
| # Contributors: |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation |
| #=============================================================================*/ |
| |
| package org.eclipse.statet.rhelp.core.http; |
| |
| import static org.eclipse.statet.internal.rhelp.core.RHelpWebapp.CAT_DOC; |
| import static org.eclipse.statet.internal.rhelp.core.RHelpWebapp.CAT_LIBRARY; |
| import static org.eclipse.statet.internal.rhelp.core.RHelpWebapp.IMAGES; |
| import static org.eclipse.statet.internal.rhelp.core.RHelpWebapp.LIBRARY_DOC; |
| import static org.eclipse.statet.internal.rhelp.core.RHelpWebapp.LIBRARY_HELP; |
| import static org.eclipse.statet.internal.rhelp.core.RHelpWebapp.LIBRARY_HTML; |
| |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.io.Writer; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.nio.file.Files; |
| import java.nio.file.LinkOption; |
| import java.nio.file.Path; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import javax.servlet.ServletConfig; |
| import javax.servlet.ServletException; |
| import javax.servlet.ServletOutputStream; |
| import javax.servlet.http.HttpServlet; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| import org.eclipse.statet.jcommons.collections.ImCollections; |
| import org.eclipse.statet.jcommons.collections.ImList; |
| import org.eclipse.statet.jcommons.lang.NonNullByDefault; |
| import org.eclipse.statet.jcommons.lang.Nullable; |
| import org.eclipse.statet.jcommons.runtime.CommonsRuntime; |
| import org.eclipse.statet.jcommons.runtime.bundle.BundleEntry; |
| import org.eclipse.statet.jcommons.runtime.bundle.BundleSpec; |
| import org.eclipse.statet.jcommons.status.Status; |
| import org.eclipse.statet.jcommons.status.StatusException; |
| |
| import org.eclipse.statet.internal.rhelp.core.REnvHelpImpl; |
| import org.eclipse.statet.internal.rhelp.core.REnvHelpIndex; |
| import org.eclipse.statet.internal.rhelp.core.RHelpWebapp; |
| import org.eclipse.statet.internal.rhelp.core.RHelpWebapp.RequestInfo; |
| import org.eclipse.statet.internal.rhelp.core.http.HttpHeaderUtils; |
| import org.eclipse.statet.internal.rhelp.core.index.RHelpHtmlUtils; |
| import org.eclipse.statet.internal.rhelp.core.server.ServerClientSupport; |
| import org.eclipse.statet.rhelp.core.DocResource; |
| import org.eclipse.statet.rhelp.core.REnvHelp; |
| import org.eclipse.statet.rhelp.core.REnvHelpConfiguration; |
| import org.eclipse.statet.rhelp.core.RHelpCore; |
| import org.eclipse.statet.rhelp.core.RHelpManager; |
| import org.eclipse.statet.rhelp.core.RHelpPage; |
| import org.eclipse.statet.rhelp.core.RHelpTopicEntry; |
| import org.eclipse.statet.rhelp.core.RPkgHelp; |
| import org.eclipse.statet.rhelp.core.TopicDocResource; |
| import org.eclipse.statet.rj.renv.core.REnv; |
| import org.eclipse.statet.rj.renv.core.REnvConfiguration; |
| import org.eclipse.statet.rj.renv.core.RLibLocation; |
| import org.eclipse.statet.rj.renv.core.RPkgDescription; |
| |
| |
| /** |
| * Abstract R help servlet. |
| */ |
| @NonNullByDefault |
| public abstract class RHelpHttpServlet extends HttpServlet { |
| |
| private static final long serialVersionUID= 1L; |
| |
| |
| private static final String PACKAGE_INDEX_PAGE_NAME= "00Index"; //$NON-NLS-1$ |
| |
| private static final String ATTR_RENV_ID= "rhelp.renv.id"; //$NON-NLS-1$ |
| private static final String ATTR_RENV_RESOLVED= "rhelp.renv.resolved"; //$NON-NLS-1$ |
| private static final String ATTR_RENV_HELP= "rhelp.renv.help"; //$NON-NLS-1$ |
| private static final String ATTR_RENV_CONFIG= "rhelp.renv.config"; //$NON-NLS-1$ |
| |
| |
| private static final MediaTypeProvider DOC_MEDIA_TYPES; |
| static { |
| final CustomMediaTypeProvider docMediaTypes= new CustomMediaTypeProvider(); |
| docMediaTypes.addName("README", "text/plain;charset=iso-8859-1"); //$NON-NLS-1$ //$NON-NLS-2$ |
| docMediaTypes.addName("COPYING", "text/plain;charset=iso-8859-1"); //$NON-NLS-1$ //$NON-NLS-2$ |
| docMediaTypes.addName("LICENSE", "text/plain;charset=iso-8859-1"); //$NON-NLS-1$ //$NON-NLS-2$ |
| docMediaTypes.addName("AUTHORS", "text/plain;charset=iso-8859-1"); //$NON-NLS-1$ //$NON-NLS-2$ |
| docMediaTypes.addName("THANKS", "text/plain;charset=iso-8859-1"); //$NON-NLS-1$ //$NON-NLS-2$ |
| docMediaTypes.addName("DESCRIPTION", "text/plain"); //$NON-NLS-1$ //$NON-NLS-2$ |
| docMediaTypes.addExt("Rnw", "text/plain"); //$NON-NLS-1$ //$NON-NLS-2$ |
| DOC_MEDIA_TYPES= docMediaTypes; |
| } |
| |
| @SuppressWarnings("null") |
| private static String getREnvId(final HttpServletRequest req) { |
| return (String) req.getAttribute(ATTR_RENV_ID); |
| } |
| |
| @SuppressWarnings("null") |
| private static REnv getREnv(final HttpServletRequest req) { |
| return (REnv) req.getAttribute(ATTR_RENV_RESOLVED); |
| } |
| |
| @SuppressWarnings("null") |
| private static REnvHelpImpl getREnvHelp(final HttpServletRequest req) { |
| return (REnvHelpImpl) req.getAttribute(ATTR_RENV_HELP); |
| } |
| |
| @SuppressWarnings("null") |
| private static REnvHelpConfiguration getREnvConfig(final HttpServletRequest req) { |
| return (REnvHelpConfiguration) req.getAttribute(ATTR_RENV_CONFIG); |
| } |
| |
| private static void printSaveHtml(final Writer writer, final String s) throws IOException { |
| final int length= s.length(); |
| int next= 0; |
| for (int i= 0; i < length; ) { |
| final char c= s.charAt(i); |
| switch (c) { |
| case '"': |
| if (i > next) { |
| writer.write(s, next, i - next); |
| } |
| writer.write("""); //$NON-NLS-1$ |
| next= ++i; |
| continue; |
| case '&': |
| if (i > next) { |
| writer.write(s, next, i - next); |
| } |
| writer.write("&"); //$NON-NLS-1$ |
| next= ++i; |
| continue; |
| case '\'': |
| if (i > next) { |
| writer.write(s, next, i - next); |
| } |
| // ' see http://www.w3.org/TR/xhtml1/#C_16 |
| writer.write("'"); //$NON-NLS-1$ |
| next= ++i; |
| continue; |
| case '<': |
| if (i > next) { |
| writer.write(s, next, i - next); |
| } |
| writer.write("<"); //$NON-NLS-1$ |
| next= ++i; |
| continue; |
| case '>': |
| if (i > next) { |
| writer.write(s, next, i - next); |
| } |
| writer.write(">"); //$NON-NLS-1$ |
| next= ++i; |
| continue; |
| default: |
| i++; |
| continue; |
| } |
| } |
| if (length > next) { |
| writer.write(s, next, length - next); |
| } |
| } |
| |
| |
| private RHelpManager rHelpManager; |
| |
| private ResourceHandler fileResourceHandler; |
| |
| private @Nullable HttpForwardHandler serverForwardHandler; |
| |
| private final Map<String, byte[]> images= new HashMap<>(); |
| |
| |
| @SuppressWarnings("null") |
| public RHelpHttpServlet() { |
| } |
| |
| |
| protected void init(final RHelpManager rHelpManager, |
| final @Nullable ResourceHandler fileResourceHandler, |
| final @Nullable HttpForwardHandler serverForwardHandler) { |
| this.rHelpManager= rHelpManager; |
| |
| this.fileResourceHandler= (fileResourceHandler != null) ? fileResourceHandler : |
| new SimpleResourceHandler(new ServletMediaTypeProvider(getServletContext())); |
| this.fileResourceHandler.setSpecialMediaTypes(DOC_MEDIA_TYPES); |
| this.fileResourceHandler.setCacheControl("max-age=600, must-revalidate"); //$NON-NLS-1$ |
| |
| this.serverForwardHandler= serverForwardHandler; |
| |
| loadImages(); |
| } |
| |
| @Override |
| public void init(final ServletConfig config) throws ServletException { |
| super.init(config); |
| } |
| |
| @Override |
| public void destroy() { |
| super.destroy(); |
| } |
| |
| |
| private void loadImages() { |
| final List<String> names= ImCollections.newList( |
| "rpackage.png", //$NON-NLS-1$ |
| "rhelp-page.png", "rhelp-page-internal.png", //$NON-NLS-1$ //$NON-NLS-2$ |
| "rhelp-topic.png", "rhelp-topic-internal.png" ); //$NON-NLS-1$ //$NON-NLS-2$ |
| try { |
| final List<BundleEntry> bundles= CommonsRuntime.getEnvironment().resolveBundles( |
| ImCollections.newList(new BundleSpec(RHelpCore.BUNDLE_ID, RHelpCore.class)) ); |
| ITER_IMAGES: for (final String name : names) { |
| final String resourcePathString= "images/" + name; //$NON-NLS-1$ |
| for (final BundleEntry bundle : bundles) { |
| final Path resourcePath= bundle.getResourcePath(resourcePathString); |
| if (resourcePath != null && Files.isReadable(resourcePath)) { |
| final byte[] bytes= Files.readAllBytes(resourcePath); |
| this.images.put(name, bytes); |
| continue ITER_IMAGES; |
| } |
| } |
| throw new IOException(String.format("File note found '%1$s'.", resourcePathString)); |
| } |
| } |
| catch (final Exception e) { |
| log("An error occurred when loading images.", e); |
| this.images.clear(); |
| } |
| } |
| |
| |
| @Override |
| protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) |
| throws ServletException, IOException { |
| final String path= req.getPathInfo(); |
| try { |
| if (path != null) { |
| if (path.startsWith('/' + IMAGES + '/')) { |
| processImage(path.substring(IMAGES.length() + 2), req, resp); |
| return; |
| } |
| if (path.endsWith("/R.css")) { //$NON-NLS-1$ |
| processCss(req, resp); |
| return; |
| } |
| final RequestInfo info= RHelpWebapp.extractRequestInfo(path); |
| if (info != null) { |
| if (!checkREnv(info.rEnvId, req, resp)) { |
| return; |
| } |
| if (info.cat == null) { |
| printEnvIndex(req, resp); |
| return; |
| } |
| else if (info.cat == RHelpWebapp.CAT_LIBRARY) { |
| switch (info.cmd) { |
| case RHelpWebapp.PKGCMD_IDX: |
| processPkgIndex(info.pkgName, req, resp); |
| return; |
| case RHelpWebapp.PKGCMD_HTML_PAGE: |
| processHelpPage(info.pkgName, info.detail, req, resp); |
| return; |
| case RHelpWebapp.PKGCMD_HTML_RESOURCE: |
| processPkgRes(info.pkgName, LIBRARY_HELP, info.detail, req, resp); |
| return; |
| case RHelpWebapp.PKGCMD_TOPIC: |
| processTopic(info.pkgName, info.detail, req, resp); |
| return; |
| case RHelpWebapp.PKGCMD_DOC_IDX: |
| processPkgRes(info.pkgName, LIBRARY_DOC, "index.html", req, resp); //$NON-NLS-1$ |
| return; |
| case RHelpWebapp.PKGCMD_DOC_RES: |
| processPkgRes(info.pkgName, LIBRARY_DOC, info.detail, req, resp); |
| return; |
| case RHelpWebapp.PKGCMD_DESCRIPTION_RES: |
| processPkgRes(info.pkgName, null, "DESCRIPTION", req, resp); //$NON-NLS-1$ |
| return; |
| } |
| } |
| else if (info.cat == RHelpWebapp.CAT_DOC) { |
| processEnvDoc(info.detail, req, resp); |
| return; |
| } |
| } |
| } |
| resp.sendError(HttpServletResponse.SC_BAD_REQUEST); |
| return; |
| } |
| catch (final StatusException e) { |
| final Status status= e.getStatus(); |
| final int httpStatus; |
| switch (status.getCode()) { |
| case REnvHelpIndex.TIMEOUT_ERROR: |
| case REnvHelpIndex.CONNECT_ERROR: |
| httpStatus= HttpServletResponse.SC_GATEWAY_TIMEOUT; |
| break; |
| default: |
| httpStatus= HttpServletResponse.SC_INTERNAL_SERVER_ERROR; |
| break; |
| } |
| resp.sendError(httpStatus, status.getMessage()); |
| return; |
| } |
| finally { |
| final REnvHelp help= (REnvHelp) req.getAttribute(ATTR_RENV_HELP); |
| if (help != null) { |
| help.unlock(); |
| } |
| } |
| } |
| |
| protected StringBuilder getServletPath(final HttpServletRequest req) { |
| return new StringBuilder() |
| .append(req.getContextPath()) |
| .append(req.getServletPath()); |
| } |
| |
| protected void sendPathRedirect(final String path, |
| final HttpServletRequest req, final HttpServletResponse resp) throws IOException { |
| // resp.sendRedirect(path.toString()); // converts relativ path to absolute |
| |
| resp.resetBuffer(); |
| resp.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); |
| resp.setHeader(HttpHeaderUtils.LOCATION_NAME, path); |
| } |
| |
| |
| private boolean checkREnv(final String id, |
| final HttpServletRequest req, final HttpServletResponse resp) throws IOException { |
| REnv rEnv= this.rHelpManager.getREnv(id); |
| if (rEnv != null) { |
| rEnv= rEnv.resolve(); |
| } |
| final REnvHelpConfiguration config; |
| if (rEnv != null && (config= rEnv.get(REnvHelpConfiguration.class)) != null) { |
| req.setAttribute(ATTR_RENV_ID, id); |
| req.setAttribute(ATTR_RENV_RESOLVED, rEnv); |
| final REnvHelp help= this.rHelpManager.getHelp(rEnv); |
| if (help != null) { |
| req.setAttribute(ATTR_RENV_HELP, help); |
| req.setAttribute(ATTR_RENV_CONFIG, config); |
| return true; |
| } |
| resp.sendError(HttpServletResponse.SC_NOT_FOUND, |
| "The R library of the requested R environment <code>" + rEnv.getName() + "</code> " + |
| "is not yet indexed. Please run the indexer first to enable R help support."); |
| return false; |
| } |
| else { |
| final String message= (id.startsWith("default-")) ? //$NON-NLS-1$ |
| "The requested default R environment is missing. " + |
| "Please configure an environment as default." : |
| "The requested R environment doesn't exist. " + |
| "Please change the environment."; |
| resp.sendError(HttpServletResponse.SC_NOT_FOUND, message); |
| return false; |
| } |
| } |
| |
| private void processHelpPage(final String pkgName, final String detail, |
| final HttpServletRequest req, final HttpServletResponse resp) throws IOException, |
| StatusException { |
| if (detail != null && detail.equalsIgnoreCase(PACKAGE_INDEX_PAGE_NAME)) { |
| sendPathRedirect(getServletPath(req) |
| .append('/').append(getREnvId(req)) |
| .append('/' + CAT_LIBRARY + |
| '/').append(pkgName).append('/') |
| .toString(), req, resp ); |
| return; |
| } |
| |
| final REnvHelpImpl help= getREnvHelp(req); |
| final RPkgHelp pkgHelp= help.getPkgHelp(pkgName); |
| if (pkgHelp != null) { |
| final String qs= req.getParameter(RHelpWebapp.PAR_QUERY_STRING); |
| String html= help.getHtmlPage(pkgHelp, detail, qs); |
| if (html != null) { |
| if (qs != null) { |
| html= RHelpHtmlUtils.formatHtmlMatches(html); |
| } |
| printHtmlPage(html, req, resp); |
| return; |
| } |
| final RHelpPage page= pkgHelp.getPageForTopic(detail); |
| if (page != null) { |
| redirect(page, req, resp); |
| return; |
| } |
| } |
| resp.sendError(HttpServletResponse.SC_NOT_FOUND, |
| "Help page <code>" + detail + "</code> {<code>" + pkgName + "</code>} not found."); |
| return; |
| } |
| |
| private void processPkgRes(final String pkgName, final @Nullable String resSub, final String path, |
| final HttpServletRequest req, final HttpServletResponse resp) throws IOException, ServletException { |
| final REnvHelpImpl help= getREnvHelp(req); |
| final REnvHelpConfiguration rEnvConfig= getREnvConfig(req); |
| |
| Path libDirectory= null; |
| if (rEnvConfig.isLocal()) { |
| final RPkgHelp pkgHelp= help.getPkgHelp(pkgName); |
| if (pkgHelp != null) { |
| final RPkgDescription pkgDescription= pkgHelp.getPkgDescription(); |
| final RLibLocation libLocation= pkgDescription.getLibLocation(); |
| libDirectory= libLocation.getDirectoryPath(); |
| } |
| } |
| else { |
| switch (rEnvConfig.getStateSharedType()) { |
| case REnvConfiguration.SHARED_SERVER: |
| forwardToServer(rEnvConfig, req, resp); |
| return; |
| case REnvConfiguration.SHARED_DIRECTORY: |
| // TODO |
| break; |
| default: |
| resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); |
| return; |
| } |
| } |
| if (libDirectory == null) { |
| resp.sendError(HttpServletResponse.SC_NOT_FOUND); |
| return; |
| } |
| Path directory= libDirectory.resolve(pkgName); |
| if (resSub != null) { |
| directory= directory.resolve(resSub); |
| } |
| |
| serveFileResource(directory, path, null, req, resp); |
| } |
| |
| private void processPkgIndex(final String pkgName, |
| final HttpServletRequest req, final HttpServletResponse resp) throws IOException, |
| StatusException { |
| final REnvHelpImpl help= getREnvHelp(req); |
| final RPkgHelp pkgHelp= help.getPkgHelp(pkgName); |
| if (pkgHelp != null) { |
| final ImList<RHelpTopicEntry> topics= pkgHelp.getTopics(); |
| if (topics != null) { |
| printPackageIndex(pkgHelp, topics, req, resp); |
| return; |
| } |
| } |
| resp.sendError(HttpServletResponse.SC_NOT_FOUND, |
| "Help for package <code>" + pkgName + "</code> not found."); |
| return; |
| } |
| |
| private void processTopic(final String pkgName, final String detail, |
| final HttpServletRequest req, final HttpServletResponse resp) throws IOException, |
| StatusException { |
| final REnvHelp help= getREnvHelp(req); |
| final RPkgHelp pkgHelp= help.getPkgHelp(pkgName); |
| if (pkgHelp != null) { |
| final RHelpPage page= pkgHelp.getPageForTopic(detail); |
| if (page != null) { |
| redirect(page, req, resp); |
| return; |
| } |
| } |
| final List<RHelpPage> pages= help.getPagesForTopic(detail, null); |
| if (pages.size() == 1) { |
| redirect(pages.get(0), req, resp); |
| return; |
| } |
| else { |
| printTopicList(detail, pages, req, resp); |
| return; |
| } |
| } |
| |
| private void processCss( |
| final HttpServletRequest req, final HttpServletResponse resp) throws IOException { |
| final PrintWriter writer= createCssDoc(req, resp); |
| |
| writer.println("span.acronym { font-size: small }\n" + //$NON-NLS-1$ |
| "span.env { font-family: monospace }\n" + //$NON-NLS-1$ |
| "span.file { font-family: monospace }\n" + //$NON-NLS-1$ |
| "span.option { font-family: monospace }\n" + //$NON-NLS-1$ |
| "span.pkg { font-weight: bold }\n" + //$NON-NLS-1$ |
| "span.samp { font-family: monospace }"); //$NON-NLS-1$ |
| |
| writer.println("body { line-height: 125%; margin: 1em; padding: 0; color: black; }"); //$NON-NLS-1$ |
| writer.println("table { margin: 0.4em 0 0.4em 0; border-collapse:collapse; border:0px; font-size: 100% }"); //$NON-NLS-1$ |
| writer.println("td { padding: 0.2em 0.8em 0.2em 0; border:0px; }"); //$NON-NLS-1$ |
| writer.println("h2 { font-size: 120%; font-weight: bold; margin: 0 0 0.6em 0; }"); //$NON-NLS-1$ |
| writer.println("h3 { font-size: 110%; font-weight: bold; letter-spacing: 0.05em; margin: 1.0em 0 0.6em 0; }"); //$NON-NLS-1$ |
| writer.println("p, pre { margin: 0.6em 0 0.6em 0; }"); //$NON-NLS-1$ |
| writer.println("td { vertical-align: top; }"); //$NON-NLS-1$ |
| writer.println("hr { margin-top: 0.8em; clear: both; }"); //$NON-NLS-1$ |
| |
| writer.println("div.toc { display: none; font-size: 80%; line-height: 125%; padding: 0.2em 0.8em 0.4em; }"); //$NON-NLS-1$ |
| writer.println("div.toc ul { list-style: none; padding: 0; margin: 0 }"); //$NON-NLS-1$ |
| writer.println("div.toc pre { margin: 0 0 0.4em; }"); //$NON-NLS-1$ |
| writer.println("div.toc a { text-decoration: none; color: black; }"); //$NON-NLS-1$ |
| writer.println("div.toc a:visited { text-decoration: none; color: black; }"); //$NON-NLS-1$ |
| |
| writer.println("a.action { text-decoration: none; }"); |
| writer.println("a.action small { padding-left: 1px; padding-right: 1px; }"); |
| writer.println("a.action:hover small { background-color: lightgrey; color: black; }"); |
| |
| writer.println("img.icon { vertical-align: text-top; padding-top: 1px; padding-right: 2px; margin-right: 2px; }"); |
| |
| customizeCss(writer); |
| } |
| |
| private void processImage(final String imgName, |
| final HttpServletRequest req, final HttpServletResponse resp) throws IOException { |
| final byte[] content= this.images.get(imgName); |
| if (content != null) { |
| resp.setContentType("image/png"); //$NON-NLS-1$ |
| resp.setHeader("Cache-Control", "max-age=600, public"); //$NON-NLS-1$ |
| final ServletOutputStream out= resp.getOutputStream(); |
| out.write(content); |
| out.close(); |
| return; |
| } |
| else { |
| resp.sendError(HttpServletResponse.SC_NOT_FOUND); |
| return; |
| } |
| } |
| |
| private void processEnvDoc(final String path, |
| final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { |
| final REnvHelpImpl help= getREnvHelp(req); |
| final REnvHelpConfiguration rEnvConfig= getREnvConfig(req); |
| |
| Path directory= null; |
| if (rEnvConfig.isLocal()) { |
| final String docDir= help.getDocDir(); |
| if (docDir != null) { |
| directory= Path.of(docDir); |
| } |
| } |
| else { |
| switch (rEnvConfig.getStateSharedType()) { |
| case REnvConfiguration.SHARED_SERVER: |
| forwardToServer(rEnvConfig, req, resp); |
| return; |
| case REnvConfiguration.SHARED_DIRECTORY: |
| // TODO |
| break; |
| default: |
| resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); |
| return; |
| } |
| } |
| if (directory == null) { |
| resp.sendError(HttpServletResponse.SC_NOT_FOUND, "R doc directory not found."); |
| return; |
| } |
| |
| serveFileResource(directory, path, req.getParameter(RHelpWebapp.PAR_ACTION), req, resp); |
| } |
| |
| private PrintWriter createHtmlDoc(final String title, |
| final HttpServletRequest req, final HttpServletResponse resp) throws IOException { |
| resp.setContentType("text/html;charset=UTF-8"); //$NON-NLS-1$ |
| resp.setHeader("Cache-Control", "max-age=30, must-revalidate"); //$NON-NLS-1$ //$NON-NLS-2$ |
| final PrintWriter writer= resp.getWriter(); |
| writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); //$NON-NLS-1$ |
| writer.println("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"); //$NON-NLS-1$ |
| |
| writer.println("<html><head>"); //$NON-NLS-1$ |
| writer.write("<title>"); //$NON-NLS-1$ |
| printSaveHtml(writer, title); |
| writer.write("</title>"); //$NON-NLS-1$ |
| writer.write("<link rel=\"stylesheet\" type=\"text/css\" href=\""); //$NON-NLS-1$ |
| writer.write(getServletPath(req) |
| .append("/R.css") |
| .toString() ); |
| writer.println("\"/>"); //$NON-NLS-1$ |
| return writer; |
| } |
| |
| private void redirect(final RHelpPage page, |
| final HttpServletRequest req, final HttpServletResponse resp) throws IOException { |
| sendPathRedirect(getServletPath(req) |
| .append('/').append(getREnvId(req)) |
| .append('/' + CAT_LIBRARY + |
| '/').append(page.getPackage().getName()) |
| .append('/' + LIBRARY_HTML + |
| '/').append(page.getName()).append(".html") //$NON-NLS-1$ |
| .toString(), req, resp ); |
| } |
| |
| private PrintWriter createCssDoc( |
| final HttpServletRequest req, final HttpServletResponse resp) throws IOException { |
| resp.setContentType("text/css;charset=UTF-8"); //$NON-NLS-1$ |
| final PrintWriter writer= resp.getWriter(); |
| writer.println("@charset \"UTF-8\";"); //$NON-NLS-1$ |
| return writer; |
| } |
| |
| private void printHtmlPage(final String html, |
| final HttpServletRequest req, final HttpServletResponse resp) throws IOException { |
| resp.setContentType("text/html;charset=UTF-8"); //$NON-NLS-1$ |
| resp.setHeader("Cache-Control", "max-age=30, must-revalidate"); //$NON-NLS-1$ //$NON-NLS-2$ |
| final PrintWriter writer= resp.getWriter(); |
| final int idxHead= html.indexOf("</head>"); //$NON-NLS-1$ |
| if (idxHead > 0) { |
| writer.write(html, 0, idxHead); |
| customizePageHtmlHeader(req, writer); |
| int idxEndExamples= html.lastIndexOf(RHelpWebapp.HTML_END_EXAMPLES); |
| if (idxEndExamples > 0) { |
| final int idxBeginExamples= html.lastIndexOf(RHelpWebapp.HTML_BEGIN_EXAMPLES, idxEndExamples); |
| writer.write(html, idxHead, idxBeginExamples - idxHead); |
| customizeExamples(writer, html.substring( |
| idxBeginExamples + RHelpWebapp.HTML_BEGIN_EXAMPLES.length(), idxEndExamples)); |
| idxEndExamples+= RHelpWebapp.HTML_END_EXAMPLES.length(); |
| writer.write(html, idxEndExamples, html.length() - idxEndExamples); |
| } |
| else { |
| writer.write(html, idxHead, html.length() - idxHead); |
| } |
| } |
| else { |
| writer.write(html); |
| } |
| } |
| |
| private void printTopicList(final String topic, final List<RHelpPage> pages, |
| final HttpServletRequest req, final HttpServletResponse resp) throws IOException { |
| final PrintWriter writer= createHtmlDoc(String.format("Help on topic '%1$s'", topic), |
| req, resp ); |
| final String codeTopic= "<code>" + topic + "</code>"; //$NON-NLS-1$ //$NON-NLS-2$ |
| customizeIndexHtmlHeader(req, writer); |
| writer.println("</head><body>"); //$NON-NLS-1$ |
| writer.write("<h2>"); //$NON-NLS-1$ |
| writer.write(String.format("Help on topic %1$s", codeTopic)); |
| writer.write("</h2>"); //$NON-NLS-1$ |
| |
| final String basePath= "../../"; //$NON-NLS-1$ |
| final String baseImagesPath= "../../../" + IMAGES + '/'; |
| if (pages != null && !pages.isEmpty()) { |
| writer.write("<p>"); //$NON-NLS-1$ |
| writer.write(String.format("Help on topic %1$s was found in the following pages:", codeTopic)); |
| writer.println("</p>"); //$NON-NLS-1$ |
| |
| Collections.sort(pages); |
| writer.write("<table>"); //$NON-NLS-1$ |
| for (final RHelpPage page : pages) { |
| writer.write("<tr><td style=\"white-space: nowrap;\">"); //$NON-NLS-1$ |
| printIcon(writer, baseImagesPath, |
| (page.isInternal()) ? "rhelp-topic-internal.png" : "rhelp-topic.png" ); //$NON-NLS-1$ //$NON-NLS-2$ |
| writer.write("<a href=\""); //$NON-NLS-1$ |
| writer.write(basePath); |
| writer.write(page.getPackage().getName()); |
| writer.write('/' + LIBRARY_HTML + '/'); |
| writer.write(page.getName()); |
| writer.write(".html" + "\"><code>"); //$NON-NLS-1$ |
| writer.write(page.getName()); |
| writer.write("</code></a> {"); //$NON-NLS-1$ |
| writer.write("<a href=\""); //$NON-NLS-1$ |
| writer.write(basePath); |
| writer.write(page.getPackage().getName()); |
| writer.write("/" + "\" title=\""); //$NON-NLS-1$ |
| writer.write(page.getPackage().getName()); |
| writer.write(" ["); //$NON-NLS-1$ |
| writer.write(page.getPackage().getVersion().toString()); |
| writer.write("]\n"); //$NON-NLS-1$ |
| printSaveHtml(writer, page.getPackage().getTitle()); |
| writer.write("\"><code>"); //$NON-NLS-1$ |
| writer.write(page.getPackage().getName()); |
| writer.write("</code></a>}</td>"); //$NON-NLS-1$ |
| writer.write("<td>"); //$NON-NLS-1$ |
| printSaveHtml(writer, page.getTitle()); |
| writer.write("</td></tr>"); //$NON-NLS-1$ |
| } |
| writer.write("</table>"); //$NON-NLS-1$ |
| } |
| else { |
| writer.write(String.format("No help found on topic %1$s in any package in the R library.", codeTopic)); |
| } |
| writer.println("</body></html>"); //$NON-NLS-1$ |
| } |
| |
| private void printPackageIndex(final RPkgHelp pkgHelp, final List<RHelpTopicEntry> packageTopics, |
| final HttpServletRequest req, final HttpServletResponse resp) throws IOException { |
| final RPkgDescription pkgDescription= pkgHelp.getPkgDescription(); |
| final List<TopicDocResource> vignettes= ImCollections.emptyList(); |
| final boolean showInternal= getShowInternal(); |
| final PrintWriter writer= createHtmlDoc(String.format("Package '%1$s' - %2$s", pkgHelp.getName(), pkgHelp.getTitle()), req, |
| resp ); |
| customizeIndexHtmlHeader(req, writer); |
| writer.println("</head><body>"); //$NON-NLS-1$ |
| writer.write("<table class=\"header\"><tr><td>"); //$NON-NLS-1$ |
| writer.write(pkgHelp.getName()); |
| writer.write(" ["); //$NON-NLS-1$ |
| writer.write(pkgHelp.getVersion().toString()); |
| writer.write("]"); //$NON-NLS-1$ |
| writer.println("</td></tr></table>"); //$NON-NLS-1$ |
| |
| writer.println("<div class=\"toc\"><ul>"); //$NON-NLS-1$ |
| // writer.println("<li><a href=\"#description\">Description</a></li>"); |
| writer.write("<li><a href=\"#topics\">Help Topics</a><pre>"); //$NON-NLS-1$ |
| TOC: for (int i= 'A', j= 0; i <= 'Z'; i++) { |
| if ((i - 'A') % 7 == 0) { |
| writer.println(); |
| } |
| writer.print(' '); |
| for (; j < packageTopics.size(); j++) { |
| final RHelpTopicEntry topic= packageTopics.get(j); |
| if (topic.getTopic().length() > 0) { |
| final char c= Character.toUpperCase(topic.getTopic().charAt(0)); |
| if (c >= 'A' && c <= 'Z') { |
| if (c > i) { |
| break; |
| } |
| if (c == i && (showInternal || !topic.getPage().isInternal())) { |
| writer.write("<a href=\"#idx"); //$NON-NLS-1$ |
| writer.print((char) (32 + c)); // lowercase |
| writer.write("\" class=\"mnemonic\">"); //$NON-NLS-1$ |
| writer.print(c); |
| writer.write("</a>"); //$NON-NLS-1$ |
| continue TOC; |
| } |
| } |
| } |
| } |
| writer.print((char) i); |
| } |
| writer.println("</pre></li>"); //$NON-NLS-1$ |
| if (!vignettes.isEmpty()) { |
| writer.println("<li><a href=\"#vignettes\">Other Documentation</a></li>"); //$NON-NLS-1$ |
| } |
| writer.println("<li><a href=\"#about\">About</a></li>"); //$NON-NLS-1$ |
| writer.println("</ul></div>"); //$NON-NLS-1$ |
| |
| writer.write("<h2>"); //$NON-NLS-1$ |
| printSaveHtml(writer, pkgHelp.getTitle()); |
| writer.write("</h2>"); //$NON-NLS-1$ |
| |
| if (pkgDescription != null) { |
| final String description= pkgDescription.getDescription(); |
| if (description.length() > 0) { |
| writer.write("<h3 id=\"description\">Description</h3>"); //$NON-NLS-1$ |
| writer.write("<p>"); //$NON-NLS-1$ |
| printSaveHtml(writer, description); |
| if (description.charAt(description.length() - 1) != '.') { |
| writer.print('.'); |
| } |
| writer.write("</p>"); //$NON-NLS-1$ |
| } |
| } |
| |
| writer.write("<h3 id=\"topics\">Help Topics</h3>"); //$NON-NLS-1$ |
| writer.write("<table>"); //$NON-NLS-1$ |
| final String baseTopicsPath= LIBRARY_HTML + '/'; |
| final String baseImagesPath= "../../../" + IMAGES + '/'; |
| int lastChar= 0; |
| for (final RHelpTopicEntry topic : packageTopics) { |
| final RHelpPage page= topic.getPage(); |
| if (showInternal || !page.isInternal()) { |
| final String alias= topic.getTopic(); |
| writer.write("<tr><td style=\"white-space: nowrap;\">"); //$NON-NLS-1$ |
| writer.write("<a href=\""); //$NON-NLS-1$ |
| writer.write(baseTopicsPath); |
| writer.write(page.getName()); |
| writer.write(".html"); //$NON-NLS-1$ |
| writer.write('"'); |
| if (alias.length() > 0) { |
| final char c= Character.toUpperCase(alias.charAt(0)); |
| if (c >= 'A' && c <= 'Z' && c > lastChar) { |
| lastChar= c; |
| writer.write(" id=\"idx"); //$NON-NLS-1$ |
| writer.print((char) (32 + c)); // lowercase |
| writer.print('"'); |
| } |
| } |
| writer.write(" title=\""); //$NON-NLS-1$ |
| writer.write(page.getName()); |
| writer.write(" {"); //$NON-NLS-1$ |
| writer.write(pkgHelp.getName()); |
| writer.write("}\n"); //$NON-NLS-1$ |
| printSaveHtml(writer, page.getTitle()); |
| writer.write("\">"); //$NON-NLS-1$ |
| printIcon(writer, baseImagesPath, |
| ((page.isInternal()) ? "rhelp-topic-internal.png" : "rhelp-topic.png") ); //$NON-NLS-1$ //$NON-NLS-2$ |
| writer.write("<code>"); //$NON-NLS-1$ |
| writer.write(alias); |
| writer.write("</code></a>"); //$NON-NLS-1$ |
| writer.write("</td><td>"); //$NON-NLS-1$ |
| printSaveHtml(writer, page.getTitle()); |
| writer.write("</td></tr>"); //$NON-NLS-1$ |
| } |
| } |
| writer.write("</table>"); //$NON-NLS-1$ |
| |
| if (!vignettes.isEmpty()) { |
| writer.write("<h3 id=\"vignettes\">Vignettes and Other Documentation</h3>"); //$NON-NLS-1$ |
| } |
| |
| writer.write("<h3 id=\"about\">About</h3>"); //$NON-NLS-1$ |
| if (pkgDescription != null) { |
| writer.write("<table>"); |
| if (pkgDescription.getAuthor() != null && pkgDescription.getAuthor().length() > 0) { |
| writer.write("<tr><td>Author(s):</td>"); |
| writer.write("<td>"); //$NON-NLS-1$ |
| printSaveHtml(writer, pkgDescription.getAuthor()); |
| writer.write("</td>"); //$NON-NLS-1$ |
| } |
| if (pkgDescription.getMaintainer() != null && pkgDescription.getMaintainer().length() > 0) { |
| writer.write("<tr><td>Maintainer:</td>"); //$NON-NLS-1$ |
| writer.write("<td>"); //$NON-NLS-1$ |
| printSaveHtml(writer, pkgDescription.getMaintainer()); |
| writer.write("</td>"); //$NON-NLS-1$ |
| } |
| if (pkgDescription.getUrl() != null && pkgDescription.getUrl().length() > 0) { |
| writer.write("<tr><td>URL:</td>"); //$NON-NLS-1$ |
| writer.write("<td><a href=\""); //$NON-NLS-1$ |
| printSaveHtml(writer, pkgDescription.getUrl()); |
| writer.write("\"><code>"); //$NON-NLS-1$ |
| printSaveHtml(writer, pkgDescription.getUrl()); |
| writer.write("</code></a></td>"); //$NON-NLS-1$ |
| } |
| writer.write("</table>"); |
| } |
| writer.write("<p><a href=\"description\">DESCRIPTION file</a></p>"); //$NON-NLS-1$ |
| |
| writer.println("</body></html>"); //$NON-NLS-1$ |
| } |
| |
| private void printEnvIndex( |
| final HttpServletRequest req, final HttpServletResponse resp) throws IOException { |
| final REnvHelpImpl help= getREnvHelp(req); |
| final REnvHelpConfiguration rEnvConfig= getREnvConfig(req); |
| final REnv rEnv= help.getREnv(); |
| final List<RPkgHelp> packages= help.getPkgs(); |
| final PrintWriter writer= createHtmlDoc(String.format("R Environment '%1$s'", rEnv.getName()), req, |
| resp ); |
| final String basePath= getServletPath(req) |
| .append('/').append(rEnv.getId()) |
| .toString(); |
| final String baseLibPath= basePath + '/' + CAT_LIBRARY + '/'; |
| final String baseDocPath= basePath + '/' + CAT_DOC + '/'; |
| final String baseImagesPath= basePath + "../../" + IMAGES + '/'; //$NON-NLS-1$ |
| customizeIndexHtmlHeader(req, writer); |
| writer.println("</head><body>"); //$NON-NLS-1$ |
| |
| // toc |
| writer.println("<div class=\"toc\"><ul>"); //$NON-NLS-1$ |
| writer.write("<li><a href=\"#manuals\">Manuals</a></li>"); //$NON-NLS-1$ |
| writer.write("<li><a href=\"#packages\">Packages</a><pre>"); //$NON-NLS-1$ |
| TOC: for (int i= 'A', j= 0; i <= 'Z'; i++) { |
| if ((i - 'A') % 7 == 0) { |
| writer.println(); |
| } |
| writer.print(' '); |
| String name; |
| while (j < packages.size() && |
| (name= packages.get(j).getName()) != null && name.length() > 0) { |
| final char c= Character.toUpperCase(name.charAt(0)); |
| if (c >= 'A' && c <= 'Z') { |
| if (c > i) { |
| break; |
| } |
| if (c == i) { |
| writer.write("<a href=\"#idx"); //$NON-NLS-1$ |
| writer.print((char) (32 + c)); // lowercase |
| writer.write("\" class=\"mnemonic\">"); //$NON-NLS-1$ |
| writer.print(c); |
| writer.write("</a>"); //$NON-NLS-1$ |
| continue TOC; |
| } |
| } |
| j++; |
| } |
| writer.print((char) i); |
| } |
| writer.println("</pre></li>"); //$NON-NLS-1$ |
| if (!help.getMiscResources().isEmpty()) { |
| writer.write("<li><a href=\"#misc\">Misc. Material</a></li>"); //$NON-NLS-1$ |
| } |
| writer.println("</ul></div>"); //$NON-NLS-1$ |
| |
| writer.write("<h2>"); //$NON-NLS-1$ |
| writer.write(rEnv.getName()); |
| writer.write("</h2>"); //$NON-NLS-1$ |
| |
| writer.write("<h3 id=\"manuals\">Manuals</h3>"); //$NON-NLS-1$ |
| if (!help.getManuals().isEmpty()) { |
| printDocTable(writer, help.getManuals(), baseDocPath, rEnvConfig.isLocal()); |
| } |
| else { |
| writer.write("<p>No manuals available for this R installation.</p>"); |
| } |
| |
| writer.write("<h3 id=\"packages\">Packages</h3>"); //$NON-NLS-1$ |
| writer.write("<table>"); //$NON-NLS-1$ |
| char lastChar= 0; |
| for (final RPkgHelp pkgHelp : packages) { |
| final String name= pkgHelp.getName(); |
| writer.write("<tr><td style=\"white-space: nowrap;\">"); //$NON-NLS-1$ |
| writer.write("<a href=\""); //$NON-NLS-1$ |
| writer.write(baseLibPath); |
| writer.write(name); |
| writer.write("/" + "\" title=\""); //$NON-NLS-1$ |
| writer.write(name); |
| writer.write(" ["); //$NON-NLS-1$ |
| printSaveHtml(writer, pkgHelp.getVersion().toString()); |
| writer.print(']'); |
| writer.print('"'); |
| if (name.length() > 0) { |
| final char c= Character.toUpperCase(name.charAt(0)); |
| if (c >= 'A' && c <= 'Z' && c > lastChar) { |
| lastChar= c; |
| writer.write(" id=\"idx"); //$NON-NLS-1$ |
| writer.print((char) (32 + c)); // lowercase |
| writer.print('"'); |
| } |
| } |
| writer.write(">"); //$NON-NLS-1$ |
| printIcon(writer, baseImagesPath, "rpackage.png"); //$NON-NLS-1$ |
| writer.write("<code>"); //$NON-NLS-1$ |
| writer.write(pkgHelp.getName()); |
| writer.write("</code></a>"); //$NON-NLS-1$ |
| writer.write("</td><td>"); //$NON-NLS-1$ |
| printSaveHtml(writer, pkgHelp.getTitle()); |
| writer.write("</td></tr>"); //$NON-NLS-1$ |
| } |
| writer.write("</table>"); //$NON-NLS-1$ |
| |
| if (!help.getMiscResources().isEmpty()) { |
| writer.write("<h3 id=\"misc\">Miscellaneous Material</h3>"); //$NON-NLS-1$ |
| printDocTable(writer, help.getMiscResources(), baseDocPath, rEnvConfig.isLocal()); |
| } |
| |
| writer.write("<hr/>"); //$NON-NLS-1$ |
| |
| writer.println("</body></html>"); //$NON-NLS-1$ |
| } |
| |
| |
| private void printDocTable(final PrintWriter writer, final List<DocResource> docs, |
| final String baseUrl, final boolean local) { |
| writer.write("<table>"); //$NON-NLS-1$ |
| for (final DocResource doc : docs) { |
| writer.write("<tr><td>"); //$NON-NLS-1$ |
| writer.write("<a href=\""); //$NON-NLS-1$ |
| writer.write(baseUrl); |
| writer.write(doc.getPath()); |
| writer.write("\">"); //$NON-NLS-1$ |
| writer.write(doc.getTitle()); |
| writer.write("</a>"); //$NON-NLS-1$ |
| if (doc.getPdfPath() != null) { |
| writer.write(" [ <a"); //$NON-NLS-1$ |
| writer.write(" href=\""); //$NON-NLS-1$ |
| writer.write(baseUrl); |
| writer.write(doc.getPdfPath()); |
| writer.write("\">PDF</a>"); |
| if (local && canOpenFile("pdf")) { //$NON-NLS-1$ |
| writer.write(" <a class=\"action\""); //$NON-NLS-1$ |
| writer.write(" href=\""); //$NON-NLS-1$ |
| writer.write(baseUrl); |
| writer.write(doc.getPdfPath()); |
| writer.write("?action=open"); |
| writer.write("\" title=\"Open PDF with Eclipse\"><small>(open)</small></a>"); |
| } |
| writer.write(" ]"); //$NON-NLS-1$ |
| } |
| writer.write("</td></tr>"); //$NON-NLS-1$ |
| } |
| writer.write("</table>"); //$NON-NLS-1$ |
| } |
| |
| private void printIcon(final PrintWriter writer, final String imageBasePath, |
| final String imageName) { |
| if (this.images.isEmpty()) { |
| return; |
| } |
| writer.write("<img class=\"icon\" src=\""); //$NON-NLS-1$ |
| writer.write(imageBasePath); |
| writer.write(imageName); |
| writer.write("\"/>"); //$NON-NLS-1$ |
| } |
| |
| |
| private void customizeExamples(final PrintWriter writer, final String html) { |
| int idx= 0; |
| while (idx < html.length()) { |
| int begin= html.indexOf("<pre", idx); //$NON-NLS-1$ |
| if (begin >= 0) { |
| begin= html.indexOf('>', begin + 4); |
| if (begin >= 0) { |
| begin ++; |
| final int end= html.indexOf("</pre", begin); //$NON-NLS-1$ |
| if (end >= 0) { |
| writer.write(html, idx, begin - idx); |
| printRCode(writer, html.substring(begin, end)); |
| idx= end; |
| continue; |
| } |
| } |
| } |
| break; |
| } |
| writer.write(html, idx, html.length() - idx); |
| } |
| |
| protected boolean getShowInternal() { |
| return true; |
| } |
| |
| protected void customizeCss(final PrintWriter writer) { |
| } |
| |
| protected void customizePageHtmlHeader(final HttpServletRequest req, final PrintWriter writer) { |
| } |
| |
| protected void customizeIndexHtmlHeader(final HttpServletRequest req, final PrintWriter writer) { |
| } |
| |
| protected void printRCode(final PrintWriter writer, final String html) { |
| writer.write(html); |
| } |
| |
| |
| private @Nullable Path checkPath(final Path directory, final String path) { |
| try { |
| final Path file= directory.resolve(path).toRealPath(LinkOption.NOFOLLOW_LINKS); |
| if (file.getNameCount() >= directory.getNameCount() && file.startsWith(directory)) { |
| return file; |
| } |
| } |
| catch (final Exception e) {} |
| return null; |
| } |
| |
| private void serveFileResource(final Path directory, final String path, |
| final @Nullable String action, |
| final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { |
| final Path file= checkPath(directory, path); |
| if (file == null) { |
| resp.sendError(HttpServletResponse.SC_BAD_REQUEST); |
| return; |
| } |
| if (!Files.isRegularFile(file)) { |
| resp.sendError(HttpServletResponse.SC_NOT_FOUND, path); |
| return; |
| } |
| |
| if (action != null && action.equals(RHelpWebapp.ACTION_OPEN)) { |
| doOpenFile(file); |
| resp.setStatus(HttpServletResponse.SC_NO_CONTENT); |
| return; |
| } |
| |
| this.fileResourceHandler.doGet(file, req, resp); |
| } |
| |
| private void forwardToServer(final REnvHelpConfiguration rEnvConfig, |
| final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { |
| if (this.serverForwardHandler == null) { |
| resp.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); |
| return; |
| } |
| try { |
| final ServerClientSupport serverSupport= ServerClientSupport.getInstance(); |
| final String localId= getREnvId(req); |
| final URI serverUrl= serverSupport.toServerBrowseUrl(rEnvConfig, |
| req.getPathInfo().substring(localId.length() + 1) ); |
| this.serverForwardHandler.forward(serverUrl, req, resp); |
| } |
| catch (final StatusException | URISyntaxException e) { |
| throw new ServletException(e); |
| } |
| } |
| |
| protected boolean canOpenFile(final String ext) { |
| return false; |
| } |
| |
| protected void doOpenFile(final Path file) { |
| } |
| |
| } |