001package jmri.web.servlet.roster; 002 003import static jmri.server.json.JSON.ADDRESS; 004import static jmri.server.json.JSON.DATA; 005import static jmri.server.json.JSON.DECODER_FAMILY; 006import static jmri.server.json.JSON.DECODER_MODEL; 007import static jmri.server.json.JSON.FORMAT; 008import static jmri.server.json.JSON.GROUP; 009import static jmri.server.json.JSON.ID; 010import static jmri.server.json.JSON.LIST; 011import static jmri.server.json.JSON.MFG; 012import static jmri.server.json.JSON.NAME; 013import static jmri.server.json.JSON.NUMBER; 014import static jmri.server.json.JSON.ROAD; 015import static jmri.server.json.JSON.V5; 016import static jmri.web.servlet.ServletUtil.IMAGE_PNG; 017import static jmri.web.servlet.ServletUtil.UTF8; 018import static jmri.web.servlet.ServletUtil.UTF8_APPLICATION_JSON; 019import static jmri.web.servlet.ServletUtil.UTF8_APPLICATION_XML; 020import static jmri.web.servlet.ServletUtil.UTF8_TEXT_HTML; 021 022import com.fasterxml.jackson.databind.JsonNode; 023import com.fasterxml.jackson.databind.ObjectMapper; 024import com.fasterxml.jackson.databind.node.ObjectNode; 025import java.awt.Graphics2D; 026import java.awt.RenderingHints; 027import java.awt.image.BufferedImage; 028import java.io.ByteArrayOutputStream; 029import java.io.File; 030import java.io.FileOutputStream; 031import java.io.IOException; 032import java.io.InputStream; 033import java.io.OutputStream; 034import java.io.UnsupportedEncodingException; 035import java.net.URLDecoder; 036import java.net.URLEncoder; 037import java.util.ArrayList; 038import java.util.List; 039import java.util.Locale; 040import javax.imageio.ImageIO; 041import javax.servlet.ServletException; 042import javax.servlet.annotation.MultipartConfig; 043import javax.servlet.annotation.WebServlet; 044import javax.servlet.http.HttpServlet; 045import javax.servlet.http.HttpServletRequest; 046import javax.servlet.http.HttpServletResponse; 047import jmri.InstanceManager; 048import jmri.jmrit.roster.Roster; 049import jmri.jmrit.roster.RosterEntry; 050import jmri.server.json.JSON; 051import jmri.server.json.JsonException; 052import jmri.server.json.roster.JsonRosterServiceFactory; 053import jmri.util.FileUtil; 054import jmri.web.servlet.ServletUtil; 055import org.jdom2.JDOMException; 056import org.openide.util.lookup.ServiceProvider; 057import org.slf4j.Logger; 058import org.slf4j.LoggerFactory; 059 060/** 061 * Provide roster data to HTTP clients. 062 * <p> 063 * Each method of this Servlet responds to a unique URL pattern. 064 * 065 * @author Randall Wood 066 */ 067/* 068 * TODO: Implement an XSLT that respects newlines in comments. 069 * TODO: Include decoder defs and CVs in roster entry response. 070 * 071 */ 072@MultipartConfig 073@WebServlet(name = "RosterServlet", 074 urlPatterns = { 075 "/roster", // default 076 "/prefs/roster.xml", // redirect to /roster?format=xml since ~ 9 Apr 2012 077 }) 078@ServiceProvider(service = HttpServlet.class) 079public class RosterServlet extends HttpServlet { 080 081 private transient ObjectMapper mapper; 082 083 private final static Logger log = LoggerFactory.getLogger(RosterServlet.class); 084 085 @Override 086 public void init() throws ServletException { 087 if (this.getServletContext().getContextPath().equals("/roster")) { // NOI18N 088 this.mapper = new ObjectMapper(); 089 } 090 } 091 092 /** 093 * Route the request and response to the appropriate methods. 094 * 095 * @param request servlet request 096 * @param response servlet response 097 * @throws java.io.IOException if communications is cut with client 098 */ 099 @Override 100 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { 101 if (request.getRequestURI().startsWith("/prefs/roster.xml")) { // NOI18N 102 response.sendRedirect("/roster?format=xml"); // NOI18N 103 return; 104 } 105 if (request.getPathInfo().length() == 1) { 106 this.doList(request, response); 107 } else { 108 // split the path after removing the leading / 109 String[] pathInfo = request.getPathInfo().substring(1).split("/"); // NOI18N 110 switch (pathInfo[0]) { 111 case LIST: 112 this.doList(request, response); 113 break; 114 case GROUP: 115 if (pathInfo.length == 2) { 116 this.doGroup(request, response, pathInfo[1]); 117 } else { 118 this.doList(request, response); 119 } 120 break; 121 default: 122 this.doEntry(request, response); 123 break; 124 } 125 } 126 } 127 128 /** 129 * Handle any POST request as an upload of a roster file from client. 130 * 131 * @param request servlet request 132 * @param response servlet response 133 * @throws javax.servlet.ServletException if unable to process uploads 134 * @throws java.io.IOException if communications is cut with 135 * client 136 */ 137 @Override 138 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { 139 140 OutputStream out = null; 141 InputStream fileContent = null; 142 File rosterFolder = new File(Roster.getDefault().getRosterLocation(), "roster"); 143 if (!rosterFolder.exists()) { //insure roster folder exists 144 if (rosterFolder.mkdir()) { 145 log.debug("Roster folder not found, created '{}'", rosterFolder.getPath()); 146 } else { 147 log.error("Could not create roster directory: '{}'", rosterFolder.getPath()); 148 } 149 } 150 File tempFolder = new File(System.getProperty("java.io.tmpdir")); 151 Locale rl = request.getLocale(); 152 153 //get the uploaded file(s) 154 List<FileMeta> files = MultipartRequestHandler.uploadByJavaServletAPI(request); 155 156 List<String> msgList = new ArrayList<>(); 157 158 //loop thru files returned and validate and (if ok) save each 159 for (FileMeta fm : files) { 160 log.debug("processing uploaded '{}' file '{}' ({}), group='{}', roster='{}', temp='{}'", fm.getFileType(), fm.getFileName(), 161 fm.getFileSize(), fm.getRosterGroup(), rosterFolder, tempFolder); 162 163 //only allow xml files or image files 164 if (!fm.getFileType().equals("text/xml") 165 && !fm.getFileType().startsWith("image")) { 166 String m = String.format(rl, Bundle.getMessage(rl, "ErrorInvalidFileType"), fm.getFileName(), fm.getFileType()); 167 log.error("{} : Invalid File Type", m); 168 msgList.add(m); 169 break; //stop processing this one 170 } 171 //save received file to temporary folder 172 File fileTemp = new File(tempFolder, fm.getFileName()); 173 try { 174 out = new FileOutputStream(fileTemp); 175 fileContent = fm.getContent(); 176 int read; 177 final byte[] bytes = new byte[1024]; 178 while ((read = fileContent.read(bytes)) != -1) { 179 out.write(bytes, 0, read); 180 } 181 log.debug("file '{}' of type '{}' temp saved to {}", fm.getFileType(), fm.getFileName(), tempFolder); 182 } catch (IOException e) { 183 String m = String.format(rl, Bundle.getMessage(rl, "ErrorSavingFile"), fm.getFileName()); 184 log.error("{} : Error Saving File", m); 185 msgList.add(m); 186 break; //stop processing this one 187 } finally { 188 if (out != null) { 189 out.close(); 190 } 191 if (fileContent != null) { 192 fileContent.close(); 193 } 194 } //finally 195 196 //reference to target file name and location 197 File fileNew = new File(rosterFolder, fm.getFileName()); 198 199 //save image file, replacing if parm is set that way. return appropriate message 200 if (fm.getFileType().startsWith("image")) { 201 if (fileNew.exists()) { 202 if (!fm.getFileReplace()) { 203 String m = String.format(rl, Bundle.getMessage(rl, "ErrorFileExists"), fm.getFileName()); 204 log.error("{} : File Already Exists", m); 205 msgList.add(m); 206 if (!fileTemp.delete()) { //get rid of temp file 207 log.error("Unable to delete {}", fileTemp); 208 } 209 } else { 210 if (!fileNew.delete()) { //delete the old file 211 String m = String.format(rl, Bundle.getMessage(rl, "ErrorDeletingFile"), fileNew.getName()); 212 log.debug("{} : Error Deleting File", m); 213 msgList.add(m); 214 } 215 if (fileTemp.renameTo(fileNew)) { 216 String m = String.format(rl, Bundle.getMessage(rl, "FileReplaced"), fm.getFileName()); 217 log.debug("{} : File Replaced", m); 218 msgList.add(m); 219 } else { 220 String m = String.format(rl, Bundle.getMessage(rl, "ErrorRenameFailed"), fm.getFileName()); 221 log.error("{} : Rename Failed", m); 222 msgList.add(m); 223 if (!fileTemp.delete()) { //get rid of temp file 224 log.error("Unable to delete {}", fileTemp); 225 } 226 } 227 } 228 } else { 229 if (fileTemp.renameTo(fileNew)) { 230 String m = String.format(rl, Bundle.getMessage(rl, "FileAdded"), fm.getFileName()); 231 log.debug("{} : File Added", m); 232 msgList.add(m); 233 } else { 234 String m = String.format(rl, Bundle.getMessage(rl, "ErrorRenameFailed"), fm.getFileName()); 235 log.error("{} : Rename Failed", m); 236 msgList.add(m); 237 if (!fileTemp.delete()) { //get rid of temp file 238 log.error("Unable to delete {}", fileTemp); 239 } 240 } 241 242 } 243 } else { 244 RosterEntry reTemp; // create a temp rosterentry to check, based on uploaded file 245 try { 246 reTemp = RosterEntry.fromFile(new File(tempFolder, fm.getFileName())); 247 } catch (JDOMException e) { //handle XML failures 248 String m = String.format(rl, Bundle.getMessage(rl, "ErrorInvalidXML"), fm.getFileName(), e.getMessage()); 249 log.error("{} : Invalid XML", m); 250 msgList.add(m); 251 if (!fileTemp.delete()) { //get rid of temp file 252 log.error("Unable to delete {}", fileTemp); 253 } 254 break; 255 } 256 RosterEntry reOld = Roster.getDefault().getEntryForId(reTemp.getId()); //get existing entry if found 257 if (reOld != null) { 258 if (!fm.getFileReplace()) { 259 String m = String.format(rl, Bundle.getMessage(rl, "ErrorFileExists"), fm.getFileName()); 260 log.error("{} : File Already Exists", m); 261 msgList.add(m); 262 if (!fileTemp.delete()) { //get rid of temp file 263 log.error("Unable to delete {}", fileTemp); 264 } 265 } else { //replace specified 266 Roster.getDefault().removeEntry(reOld); //remove the old entry from roster 267 reTemp.updateFile(); //saves XML file to roster folder and makes backup 268 Roster.getDefault().addEntry(reTemp); //add the new entry to roster 269 Roster.getDefault().writeRoster(); //save modified roster.xml file 270 String m = String.format(rl, Bundle.getMessage(rl, "RosterEntryReplaced"), fm.getFileName(), reTemp.getDisplayName()); 271 log.debug("{} : Roster Entry Replaced", m); 272 msgList.add(m); 273 if (!fileTemp.delete()) { //get rid of temp file 274 log.error("Unable to delete {}", fileTemp); 275 } 276 } 277 } else { 278 if (fileTemp.renameTo(fileNew)) { //move the file to proper roster location 279 Roster.getDefault().addEntry(reTemp); 280 Roster.getDefault().writeRoster(); 281 String m = String.format(rl, Bundle.getMessage(rl, "RosterEntryAdded"), fm.getFileName(), reTemp.getId()); 282 log.debug("{} : Roster Entry Added", m); 283 msgList.add(m); 284 } else { 285 String m = String.format(rl, Bundle.getMessage(rl, "ErrorMoveFailed"), fm.getFileName(), reTemp.getPathName()); 286 log.error("{} : File Move Failed", m); 287 msgList.add(m); 288 } 289 } 290 291 } 292 293 } //for FileMeta 294 295 //respond with a json list of messages from the upload attempts 296 response.setContentType("application/json"); 297 mapper.writeValue(response.getOutputStream(), msgList); 298 } 299 300 /** 301 * Get a roster group. 302 * <p> 303 * Lists roster entries in the specified group and return an XML document 304 * conforming to the JMRI JSON schema. This method can be passed multiple 305 * filters matching the filter in {@link jmri.jmrit.roster.Roster#getEntriesMatchingCriteria(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String) 306 * }. <b>Note:</b> Any given filter can be specified only once. 307 * <p> 308 * This method responds to the following GET URL patterns: <ul> 309 * <li>{@code/roster/group/<group name>}</li> 310 * <li>{@code/roster/group/<group name>?filter=filter[&filter=filter]}</li> 311 * </ul> 312 * <p> 313 * This method responds to the POST URL {@code/roster/group/<group name>} 314 * with a JSON payload for the filter. 315 * 316 * @param request servlet request 317 * @param response servlet response 318 * @param group The group name 319 * @throws java.io.IOException if communications is cut with client 320 */ 321 protected void doGroup(HttpServletRequest request, HttpServletResponse response, String group) throws IOException { 322 log.debug("Getting group {}", group); 323 ObjectNode data; 324 if (request.getContentType() != null && request.getContentType().contains(UTF8_APPLICATION_JSON)) { 325 data = (ObjectNode) this.mapper.readTree(request.getReader()); 326 if (data.path(DATA).isObject()) { 327 data = (ObjectNode) data.path(DATA); 328 } 329 } else { 330 data = this.mapper.createObjectNode(); 331 for (String filter : request.getParameterMap().keySet()) { 332 if (filter.equals(ID)) { 333 data.put(NAME, URLDecoder.decode(request.getParameter(filter), UTF8)); 334 } else { 335 data.put(filter, URLDecoder.decode(request.getParameter(filter), UTF8)); 336 } 337 } 338 } 339 data.put(GROUP, URLDecoder.decode(group, UTF8)); 340 log.debug("Getting roster with {}", data); 341 this.doRoster(request, response, data); 342 } 343 344 /** 345 * List roster entries. 346 * <p> 347 * Lists roster entries and return an XML document conforming to the JMRI 348 * Roster XML schema. This method can be passed multiple filter filter 349 * matching the filter in 350 * {@link jmri.jmrit.roster.Roster#getEntriesMatchingCriteria(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String)}. 351 * <b>Note:</b> Any given filter can be specified only once. 352 * <p> 353 * This method responds to the following GET URL patterns: <ul> 354 * <li>{@code/roster/}</li> <li>{@code/roster/list}</li> 355 * <li>{@code/roster/list?filter=filter[&filter=filter]}</li> </ul> 356 * 357 * @param request servlet request 358 * @param response servlet response 359 * @throws java.io.IOException if communications is cut with client 360 */ 361 protected void doList(HttpServletRequest request, HttpServletResponse response) throws IOException { 362 ObjectNode data; 363 if (request.getContentType() != null && request.getContentType().contains(UTF8_APPLICATION_JSON)) { 364 data = (ObjectNode) this.mapper.readTree(request.getReader()); 365 if (data.path(DATA).isObject()) { 366 data = (ObjectNode) data.path(DATA); 367 } 368 } else { 369 data = this.mapper.createObjectNode(); 370 for (String filter : request.getParameterMap().keySet()) { 371 switch (filter) { 372 case GROUP: 373 String group = URLDecoder.decode(request.getParameter(filter), UTF8); 374 if (!group.equals(Roster.allEntries(request.getLocale()))) { 375 data.put(GROUP, group); 376 } 377 break; 378 case ID: 379 data.put(NAME, URLDecoder.decode(request.getParameter(filter), UTF8)); 380 break; 381 default: 382 data.put(filter, URLDecoder.decode(request.getParameter(filter), UTF8)); 383 break; 384 } 385 } 386 } 387 this.doRoster(request, response, data); 388 } 389 390 /** 391 * Provide the XML representation of a roster entry given its ID. 392 * <p> 393 * Lists roster entries and return an XML document conforming to the JMRI 394 * Roster XML schema. Requests for roster entry images and icons can include 395 * width and height specifiers, and always return PNG images. 396 * <p> 397 * This method responds to the following URL patterns: <ul> 398 * <li>{@code/roster/<ID>}</li> <li>{@code/roster/entry/<ID>}</li> 399 * <li>{@code/roster/<ID>/image}</li> <li>{@code/roster/<ID>/icon}</li></ul> 400 * <b>Note:</b> The use of the term <em>entry</em> in URLs is optional. 401 * <p> 402 * Images and icons can be rescaled using the following parameters:<ul> 403 * <li>height</li> <li>maxHeight</li> <li>minHeight</li> <li>width</li> 404 * <li>maxWidth</li> <li>minWidth</li></ul> 405 * 406 * @param request servlet request 407 * @param response servlet response 408 * @throws java.io.IOException if communications is cut with client 409 */ 410 protected void doEntry(HttpServletRequest request, HttpServletResponse response) throws IOException { 411 String[] pathInfo = request.getRequestURI().substring(1).split("/"); 412 int idOffset = 1; 413 String type = null; 414 if (pathInfo[1].equals("entry")) { 415 if (pathInfo.length == 2) { 416 // path must be /roster/<id> or /roster/entry/<id> 417 response.sendError(HttpServletResponse.SC_BAD_REQUEST); 418 } 419 idOffset = 2; 420 } 421 String id = URLDecoder.decode(pathInfo[idOffset], UTF8); 422 if (pathInfo.length > (1 + idOffset)) { 423 type = pathInfo[pathInfo.length - 1]; 424 } 425 RosterEntry re = Roster.getDefault().getEntryForId(id); 426 try { 427 if (re == null) { 428 response.sendError(HttpServletResponse.SC_NOT_FOUND, "Could not find roster entry " + id); 429 } else if (type == null || type.equals("entry")) { 430 // this should be an entirely different format than the table 431 this.doRoster(request, response, this.mapper.createObjectNode().put(ID, id)); 432 } else if (type.equals(JSON.IMAGE)) { 433 if (re.getImagePath() != null) { 434 this.doImage(request, response, FileUtil.getFile(re.getImagePath())); 435 } else { 436 response.sendError(HttpServletResponse.SC_NOT_FOUND); 437 } 438 } else if (type.equals(JSON.ICON)) { 439 int function = -1; 440 if (pathInfo.length != (2 + idOffset)) { 441 function = Integer.parseInt(pathInfo[pathInfo.length - 2].substring(1)); 442 } 443 if (function == -1) { 444 if (re.getIconPath() != null) { 445 this.doImage(request, response, FileUtil.getFile(re.getIconPath())); 446 } else { 447 response.sendError(HttpServletResponse.SC_NOT_FOUND); 448 } 449 } else if (re.getFunctionImage(function) != null) { 450 this.doImage(request, response, FileUtil.getFile(re.getFunctionImage(function))); 451 } else { 452 response.sendError(HttpServletResponse.SC_NOT_FOUND); 453 } 454 } else if (type.equals(JSON.SELECTED_ICON)) { 455 if (pathInfo.length != (2 + idOffset)) { 456 int function = Integer.parseInt(pathInfo[pathInfo.length - 2].substring(1)); 457 this.doImage(request, response, FileUtil.getFile(re.getFunctionSelectedImage(function))); 458 } 459 } else if (type.equals("file")) { 460 InstanceManager.getDefault(ServletUtil.class).writeFile(response, new File(Roster.getDefault().getRosterLocation(), "roster" + File.separator + re.getFileName()), ServletUtil.UTF8_APPLICATION_XML); // NOI18N 461 } else if (type.equals("throttle")) { 462 InstanceManager.getDefault(ServletUtil.class).writeFile(response, new File(FileUtil.getUserFilesPath(), "throttle" + File.separator + id + ".xml"), ServletUtil.UTF8_APPLICATION_XML); // NOI18N 463 } else { 464 // don't know what to do 465 response.sendError(HttpServletResponse.SC_BAD_REQUEST); 466 } 467 } catch (NullPointerException ex) { 468 // triggered by instanciating a File with null path 469 // this would happen when an image or icon is requested for a 470 // rosterEntry that has no such image or icon associated with it 471 response.sendError(HttpServletResponse.SC_NOT_FOUND); 472 } 473 } 474 475 /** 476 * Generate the JSON, XML, or HTML output specified by {@link #doList(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}, 477 * {@link #doGroup(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.String)}, 478 * or 479 * {@link #doEntry(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}. 480 * 481 * @param request servlet request with format and locale for response 482 * @param response servlet response 483 * @param filter a JSON object with name-value pairs of parameters for 484 * {@link jmri.jmrit.roster.Roster#getEntriesMatchingCriteria(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String)}. 485 * @throws java.io.IOException if communications is cut with client 486 */ 487 protected void doRoster(HttpServletRequest request, HttpServletResponse response, JsonNode filter) throws IOException { 488 InstanceManager.getDefault(ServletUtil.class).setNonCachingHeaders(response); 489 log.debug("Getting roster with filter {}", filter); 490 String group = (!filter.path(GROUP).isMissingNode()) ? filter.path(GROUP).asText() : null; 491 log.debug("Group {} was in filter", group); 492 493 String format = request.getParameter(FORMAT); 494 if (format == null) { 495 format = ""; 496 } 497 switch (format) { 498 case JSON.JSON: 499 response.setContentType(UTF8_APPLICATION_JSON); 500 JsonRosterServiceFactory factory = new JsonRosterServiceFactory(); 501 try { 502 response.getWriter().print(factory.getHttpService(mapper, V5).getRoster(request.getLocale(), filter, 0)); 503 } catch (JsonException ex) { 504 response.sendError(ex.getCode(), mapper.writeValueAsString(ex.getJsonMessage())); 505 } 506 break; 507 case JSON.XML: 508 response.setContentType(UTF8_APPLICATION_XML); 509 File roster = new File(Roster.getDefault().getRosterIndexPath()); 510 if (roster.exists()) { 511 response.getWriter().print(FileUtil.readFile(roster)); 512 } 513 break; 514 case "html": 515 String row; 516 if ("simple".equals(request.getParameter("view"))) { 517 row = FileUtil.readURL(FileUtil.findURL(Bundle.getMessage(request.getLocale(), "SimpleTableRow.html"))); 518 } else { 519 row = FileUtil.readURL(FileUtil.findURL(Bundle.getMessage(request.getLocale(), "TableRow.html"))); 520 } 521 StringBuilder builder = new StringBuilder(); 522 response.setContentType(UTF8_TEXT_HTML); // NOI18N 523 if (Roster.allEntries(request.getLocale()).equals(group)) { 524 group = null; 525 } 526 List<RosterEntry> entries = Roster.getDefault().getEntriesMatchingCriteria( 527 (!filter.path(ROAD).isMissingNode()) ? filter.path(ROAD).asText() : null, 528 (!filter.path(NUMBER).isMissingNode()) ? filter.path(NUMBER).asText() : null, 529 (!filter.path(ADDRESS).isMissingNode()) ? filter.path(ADDRESS).asText() : null, 530 (!filter.path(MFG).isMissingNode()) ? filter.path(MFG).asText() : null, 531 (!filter.path(DECODER_MODEL).isMissingNode()) ? filter.path(DECODER_MODEL).asText() : null, 532 (!filter.path(DECODER_FAMILY).isMissingNode()) ? filter.path(DECODER_FAMILY).asText() : null, 533 (!filter.path(NAME).isMissingNode()) ? filter.path(NAME).asText() : null, 534 group 535 ); 536 for (RosterEntry entry : entries) { 537 try { 538 // NOTE: changing the following order will break JavaScript and HTML code 539 builder.append(String.format(request.getLocale(), row, 540 entry.getId(), 541 entry.getRoadName(), 542 entry.getRoadNumber(), 543 entry.getMfg(), 544 entry.getModel(), 545 entry.getOwner(), 546 entry.getDccAddress(), 547 entry.getDecoderModel(), 548 entry.getDecoderFamily(), 549 entry.getDecoderComment(), 550 entry.getComment(), 551 entry.getURL(), 552 entry.getMaxSpeedPCT(), 553 entry.getFileName(), 554 URLEncoder.encode(entry.getId(), UTF8) 555 // get function buttons in a formatting loop 556 // get attributes in a formatting loop 557 )); 558 } catch (UnsupportedEncodingException ex) { 559 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to encode entry Id in UTF-8."); // NOI18N 560 } 561 } 562 response.getWriter().print(builder.toString()); 563 break; 564 default: 565 if (group == null) { 566 group = Roster.allEntries(request.getLocale()); 567 } 568 response.setContentType(UTF8_TEXT_HTML); // NOI18N 569 response.getWriter().print(String.format(request.getLocale(), 570 FileUtil.readURL(FileUtil.findURL(Bundle.getMessage(request.getLocale(), "Roster.html"))), 571 String.format(request.getLocale(), 572 Bundle.getMessage(request.getLocale(), "HtmlTitle"), 573 InstanceManager.getDefault(ServletUtil.class).getRailroadName(false), 574 Bundle.getMessage(request.getLocale(), "RosterTitle") 575 ), 576 InstanceManager.getDefault(ServletUtil.class).getNavBar(request.getLocale(), request.getContextPath()), 577 InstanceManager.getDefault(ServletUtil.class).getRailroadName(false), 578 InstanceManager.getDefault(ServletUtil.class).getFooter(request.getLocale(), request.getContextPath()), 579 group 580 )); 581 break; 582 } 583 } 584 585 /** 586 * Process the image for a roster entry image or icon request. 587 * 588 * @param file {@link java.io.File} object containing an image 589 * @param request contains parameters for drawing the image 590 * @param response sends a PNG image or a 403 Not Found error. 591 * @throws java.io.IOException if communications is cut with client 592 */ 593 void doImage(HttpServletRequest request, HttpServletResponse response, File file) throws IOException { 594 BufferedImage image; 595 try { 596 image = ImageIO.read(file); 597 } catch (IOException ex) { 598 // file not found or unreadable 599 response.sendError(HttpServletResponse.SC_NOT_FOUND); 600 return; 601 } 602 String fname = file.getName(); 603 int height = image.getHeight(); 604 int width = image.getWidth(); 605 int pWidth = width; 606 int pHeight = height; 607 if (request.getParameter("maxWidth") != null) { 608 pWidth = Integer.parseInt(request.getParameter("maxWidth")); 609 if (pWidth < width) { 610 width = pWidth; 611 } 612 log.debug("{} @maxWidth: width: {}, pWidth: {}, height: {}, pHeight: {}", fname, width, pWidth, height, pHeight); 613 } 614 if (request.getParameter("minWidth") != null) { 615 pWidth = Integer.parseInt(request.getParameter("minWidth")); 616 if (pWidth > width) { 617 width = pWidth; 618 } 619 log.debug("{} @minWidth: width: {}, pWidth: {}, height: {}, pHeight: {}", fname, width, pWidth, height, pHeight); 620 } 621 if (request.getParameter("width") != null) { 622 width = Integer.parseInt(request.getParameter("width")); 623 } 624 if (width != image.getWidth()) { 625 height = (int) (height * (1.0 * width / image.getWidth())); 626 pHeight = height; 627 log.debug("{} @adjusting height: width: {}, pWidth: {}, height: {}, pHeight: {}", fname, width, pWidth, height, pHeight); 628 } 629 if (request.getParameter("maxHeight") != null) { 630 pHeight = Integer.parseInt(request.getParameter("maxHeight")); 631 if (pHeight < height) { 632 height = pHeight; 633 } 634 log.debug("{} @maxHeight: width: {}, pWidth: {}, height: {}, pHeight: {}", fname, width, pWidth, height, pHeight); 635 } 636 if (request.getParameter("minHeight") != null) { 637 pHeight = Integer.parseInt(request.getParameter("minHeight")); 638 if (pHeight > height) { 639 height = pHeight; 640 } 641 log.debug("{} @minHeight: width: {}, pWidth: {}, height: {}, pHeight: {}", fname, width, pWidth, height, pHeight); 642 } 643 if (request.getParameter("height") != null) { 644 height = Integer.parseInt(request.getParameter("height")); 645 log.debug("{} @height: width: {}, pWidth: {}, height: {}, pHeight: {}", fname, width, pWidth, height, pHeight); 646 } 647 if (height != image.getHeight() && width == image.getWidth()) { 648 width = (int) (width * (1.0 * height / image.getHeight())); 649 log.debug("{} @adjusting width: width: {}, pWidth: {}, height: {}, pHeight: {}", fname, width, pWidth, height, pHeight); 650 } 651 log.debug("{} @responding: width: {}, pWidth: {}, height: {}, pHeight: {}", fname, width, pWidth, height, pHeight); 652 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 653 if (height != image.getHeight() || width != image.getWidth()) { 654 BufferedImage resizedImage = new BufferedImage(width, height, image.getType()); 655 Graphics2D g = resizedImage.createGraphics(); 656 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); 657 g.drawImage(image, 0, 0, width, height, 0, 0, image.getWidth(), image.getHeight(), null); 658 g.dispose(); 659 // ImageIO needs the simple type ("jpeg", "png") instead of the mime type ("image/jpeg", "image/png") 660 ImageIO.write(resizedImage, "png", baos); 661 } else { 662 ImageIO.write(image, "png", baos); 663 } 664 baos.close(); 665 response.setContentType(IMAGE_PNG); 666 response.setStatus(HttpServletResponse.SC_OK); 667 response.setContentLength(baos.size()); 668 response.getOutputStream().write(baos.toByteArray()); 669 response.getOutputStream().close(); 670 } 671}