001package jmri.web.servlet; 002 003import java.io.File; 004import java.io.FileInputStream; 005import java.io.IOException; 006import java.nio.charset.StandardCharsets; 007import java.util.Date; 008import java.util.Locale; 009import javax.servlet.http.HttpServletResponse; 010import jmri.InstanceManager; 011import jmri.InstanceManagerAutoDefault; 012import jmri.util.FileUtil; 013import jmri.web.server.WebServerPreferences; 014 015/** 016 * Utility methods to reduce code duplication in servlets. 017 * 018 * @author Randall Wood 019 */ 020public class ServletUtil implements InstanceManagerAutoDefault { 021 022 public static final String UTF8 = StandardCharsets.UTF_8.toString(); // NOI18N 023 // media types 024 public static final String APPLICATION_JAVASCRIPT = "application/javascript"; // NOI18N 025 public static final String APPLICATION_JSON = "application/json"; // NOI18N 026 public static final String APPLICATION_XML = "application/xml"; // NOI18N 027 public static final String IMAGE_PNG = "image/png"; // NOI18N 028 public static final String TEXT_HTML = "text/html"; // NOI18N 029 public static final String UTF8_CHARSET = "; charset=utf-8"; // NOI18N 030 public static final String UTF8_APPLICATION_JAVASCRIPT = APPLICATION_JAVASCRIPT + UTF8_CHARSET; // NOI18N 031 public static final String UTF8_APPLICATION_JSON = APPLICATION_JSON + UTF8_CHARSET; // NOI18N 032 public static final String UTF8_APPLICATION_XML = APPLICATION_XML + UTF8_CHARSET; // NOI18N 033 public static final String UTF8_TEXT_HTML = TEXT_HTML + UTF8_CHARSET; // NOI18N 034 public static final String HIDDEN = "hidden"; // NOI18N 035 036 /** 037 * Get the railroad name for HTML documents. 038 * 039 * @param inComments Return the railroad name prepended and appended by 040 * closing and opening comment markers 041 * @return the Railroad name, possibly with formatting 042 */ 043 public String getRailroadName(boolean inComments) { 044 if (inComments) { 045 return "-->" + InstanceManager.getDefault(WebServerPreferences.class).getRailroadName() + "<!--"; // NOI18N 046 } 047 return InstanceManager.getDefault(WebServerPreferences.class).getRailroadName(); 048 } 049 050 /** 051 * Create a common footer. 052 * 053 * @param locale If a template is not available in locale, will return US 054 * English. 055 * @param context divs included in footer template with class 056 * {@code context-<context>-only} will be shown. 057 * @return an HTML footer 058 * @throws IOException if template cannot be located 059 */ 060 public String getFooter(Locale locale, String context) throws IOException { 061 // Should return a built NavBar with li class for current context set to "active" 062 String footer = String.format(locale, String.format(locale, "-->%s<!--", // NOI18N 063 FileUtil.readURL(FileUtil.findURL(Bundle.getMessage(locale, "Footer.html")))), // NOI18N 064 this.getRailroadName(true)); 065 String clazz = "context" + context.replace("/", "-"); // NOI18N 066 // replace class "context-<this-context>-only" with class "show" 067 footer = footer.replace(clazz + "-only", "show"); // NOI18N 068 // replace class "context-<some-other-context>-only" with class "hidden" 069 footer = footer.replaceAll("context-[\\w-]*-only", HIDDEN); // NOI18N 070 // replace class "context-<this-context>" with class "active" 071 footer = footer.replace(clazz, "active"); // NOI18N 072 return footer; 073 } 074 075 /** 076 * Create a common navigation header. 077 * 078 * @param locale If a template is not available in locale, will return US 079 * English. 080 * @param context divs included in navigation bar template with class 081 * {@code context-<context>-only} will be shown. 082 * @return an HTML navigation bar 083 * @throws IOException if template cannot be located 084 */ 085 public String getNavBar(Locale locale, String context) throws IOException { 086 // Should return a built NavBar with li class for current context set to "active" 087 String navBar = String.format(locale, String.format(locale, "-->%s<!--", // NOI18N 088 FileUtil.readURL(FileUtil.findURL(Bundle.getMessage(locale, "NavBar.html")))), // NOI18N 089 this.getRailroadName(true)); 090 String clazz = "context" + context.replace("/", "-"); // NOI18N 091 // replace class "context-<this-context>-only" with class "show" 092 navBar = navBar.replace(clazz + "-only", "show"); // NOI18N 093 // replace class "context-<some-other-context>-only" with class "hidden" 094 navBar = navBar.replaceAll("context-[\\w-]*-only", HIDDEN); // NOI18N 095 // replace class "context-<this-context>" with class "active" 096 navBar = navBar.replace(clazz, "active"); // NOI18N 097 if (InstanceManager.getDefault(WebServerPreferences.class).allowRemoteConfig()) { 098 navBar = navBar.replace("config-enabled-only", "show"); // NOI18N 099 navBar = navBar.replace("config-disabled-only", HIDDEN); // NOI18N 100 } else { 101 navBar = navBar.replace("config-enabled-only", HIDDEN); // NOI18N 102 navBar = navBar.replace("config-disabled-only", "show"); // NOI18N 103 } 104 if (!InstanceManager.getDefault(WebServerPreferences.class).isReadonlyPower()) { 105 navBar = navBar.replace("data-power=\"readonly\"", "data-power=\"readwrite\""); // NOI18N 106 } 107 return navBar; 108 } 109 110 /** 111 * Set HTTP headers to prevent caching. 112 * 113 * @param response the response to set headers in 114 * @return the date used for headers setting expiration and modification times 115 */ 116 public Date setNonCachingHeaders(HttpServletResponse response) { 117 Date now = new Date(); 118 response.setDateHeader("Date", now.getTime()); // NOI18N 119 response.setDateHeader("Last-Modified", now.getTime()); // NOI18N 120 response.setDateHeader("Expires", now.getTime()); // NOI18N 121 response.setHeader("Cache-control", "no-cache, no-store"); // NOI18N 122 response.setHeader("Pragma", "no-cache"); // NOI18N 123 return now; 124 } 125 126 /** 127 * Write a file to the given response. 128 * 129 * @param response the response to write the file into 130 * @param file file to write 131 * @param contentType file mime content type 132 * @throws java.io.IOException if communications lost with client 133 */ 134 public void writeFile(HttpServletResponse response, File file, String contentType) throws IOException { 135 if (file.exists()) { 136 if (file.canRead()) { 137 response.setContentType(contentType); 138 response.setStatus(HttpServletResponse.SC_OK); 139 response.setContentLength((int) file.length()); 140 try (FileInputStream fileInputStream = new FileInputStream(file)) { 141 int bytes = fileInputStream.read(); 142 while (bytes != -1) { 143 response.getOutputStream().write(bytes); 144 bytes = fileInputStream.read(); 145 } 146 } 147 } else { 148 response.sendError(HttpServletResponse.SC_FORBIDDEN); 149 } 150 } else { 151 response.sendError(HttpServletResponse.SC_NOT_FOUND); 152 } 153 } 154 155 /** 156 * Return the complete title for an HTML document given the portion of the 157 * title specific to the document. 158 * 159 * @param locale The requested Locale 160 * @param title Portion of title specific to page 161 * @return The complete title 162 */ 163 public String getTitle(Locale locale, String title) { 164 return String.format(Bundle.getMessage(locale, "HtmlTitle"), this.getRailroadName(false), title); 165 } 166}