001package jmri.web.servlet.operations; 002 003import static jmri.web.servlet.ServletUtil.*; 004 005import com.fasterxml.jackson.databind.JsonNode; 006import com.fasterxml.jackson.databind.ObjectMapper; 007import com.fasterxml.jackson.databind.node.ObjectNode; 008 009import java.io.IOException; 010 011import javax.servlet.ServletException; 012import javax.servlet.annotation.WebServlet; 013import javax.servlet.http.*; 014 015import jmri.InstanceManager; 016import jmri.jmrit.operations.OperationsManager; 017import jmri.jmrit.operations.rollingstock.cars.CarManager; 018import jmri.jmrit.operations.setup.Setup; 019import jmri.jmrit.operations.trains.*; 020import jmri.jmrit.operations.trains.trainbuilder.TrainCommon; 021import jmri.server.json.JSON; 022import jmri.server.json.operations.JsonOperations; 023import jmri.server.json.operations.JsonUtil; 024import jmri.util.FileUtil; 025import jmri.web.server.WebServer; 026import jmri.web.servlet.ServletUtil; 027 028import org.apache.commons.text.StringEscapeUtils; 029import org.openide.util.lookup.ServiceProvider; 030import org.slf4j.Logger; 031import org.slf4j.LoggerFactory; 032 033/** 034 * 035 * @author Randall Wood (C) 2014 036 * @author Steve Todd (C) 2013 037 */ 038@WebServlet(name = "OperationsServlet", 039 urlPatterns = { 040 "/operations", // default 041 "/web/operationsConductor.html", // redirect to default since ~ 13 May 2014 042 "/web/operationsManifest.html", // redirect to default since ~ 13 May 2014 043 "/web/operationsTrains.html" // redirect to default since ~ 13 May 2014 044 }) 045@ServiceProvider(service = HttpServlet.class) 046public class OperationsServlet extends HttpServlet { 047 048 private ObjectMapper mapper; 049 050 private static final Logger log = LoggerFactory.getLogger(OperationsServlet.class); 051 052 @Override 053 public void init() throws ServletException { 054 // only do complete initialization for default path, not redirections 055 if (this.getServletContext().getContextPath().equals("/operations")) { // NOI18N 056 this.mapper = new ObjectMapper(); 057 // ensure all operations managers are functional before handling first request 058 InstanceManager.getDefault(OperationsManager.class); 059 } 060 } 061 062 /* 063 * Valid paths are: 064 * /operations/trains -or- /operations - get a list of trains for operations 065 * /operations/manifest/id - get the manifest for train with Id "id" 066 * /operations/conductor/id - get the conductor's screen for train with Id "id" 067 */ 068 protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 069 if (request.getRequestURI().equals("/web/operationsConductor.html") // NOI18N 070 || request.getRequestURI().equals("/web/operationsManifest.html") // NOI18N 071 || request.getRequestURI().equals("/web/operationsTrains.html")) { // NOI18N 072 response.sendRedirect("/operations"); // NOI18N 073 return; 074 } 075 String[] pathInfo = request.getPathInfo().substring(1).split("/"); 076 response.setHeader("Connection", "Keep-Alive"); // NOI18N 077 if (pathInfo[0].equals("") || (pathInfo[0].equals(JsonOperations.TRAINS) && pathInfo.length == 1)) { 078 this.processTrains(request, response); 079 } else { 080 if (pathInfo.length == 1) { 081 response.sendError(HttpServletResponse.SC_BAD_REQUEST); 082 } else { 083 String id = pathInfo[1]; 084 String report = pathInfo[0]; 085 if (report.equals(JsonOperations.TRAINS) && pathInfo.length == 3) { 086 report = pathInfo[2]; 087 } 088 log.debug("Handling {} with id {}", report, id); 089 switch (report) { 090 case "manifest": 091 this.processManifest(id, request, response); 092 break; 093 case "conductor": 094 this.processConductor(id, request, response); 095 break; 096 case "trains": 097 // TODO: allow for editing/building/reseting train 098 log.warn("Unhandled request for \"trains\""); 099 response.sendError(HttpServletResponse.SC_BAD_REQUEST); 100 break; 101 default: 102 // Don't know what to do 103 log.warn("Unparsed request for \"{}\"", report); 104 response.sendError(HttpServletResponse.SC_BAD_REQUEST); 105 break; 106 } 107 } 108 } 109 } 110 111 protected void processTrains(HttpServletRequest request, HttpServletResponse response) throws IOException { 112 if (JSON.JSON.equals(request.getParameter("format"))) { 113 response.setContentType(UTF8_APPLICATION_JSON); 114 InstanceManager.getDefault(ServletUtil.class).setNonCachingHeaders(response); 115 JsonUtil utilities = new JsonUtil(this.mapper); 116 response.getWriter().print(utilities.getTrains(request.getLocale())); 117 } else if ("html".equals(request.getParameter("format"))) { 118 response.setContentType(UTF8_TEXT_HTML); 119 InstanceManager.getDefault(ServletUtil.class).setNonCachingHeaders(response); 120 boolean showAll = ("all".equals(request.getParameter("show"))); 121 StringBuilder html = new StringBuilder(); 122 String format = FileUtil.readURL(FileUtil.findURL(Bundle.getMessage(request.getLocale(), "TrainsSnippet.html"))); 123 for (Train train : InstanceManager.getDefault(TrainManager.class).getTrainsByNameList()) { 124 if (showAll || !InstanceManager.getDefault(CarManager.class).getByTrainDestinationList(train).isEmpty()) { 125 html.append(String.format(request.getLocale(), format, 126 train.getIconName(), 127 StringEscapeUtils.escapeHtml4(train.getDescription()), 128 train.getLeadEngine() != null ? train.getLeadEngine().toString() : "", 129 StringEscapeUtils.escapeHtml4(train.getTrainDepartsName()), 130 train.getDepartureTime(), 131 train.getStatus(), 132 StringEscapeUtils.escapeHtml4(train.getCurrentLocationName()), 133 StringEscapeUtils.escapeHtml4(train.getTrainTerminatesName()), 134 StringEscapeUtils.escapeHtml4(train.getTrainRouteName()), 135 train.getId() 136 )); 137 } 138 } 139 response.getWriter().print(html.toString()); 140 } else { 141 response.setContentType(UTF8_TEXT_HTML); 142 response.getWriter().print(String.format(request.getLocale(), 143 FileUtil.readURL(FileUtil.findURL(Bundle.getMessage(request.getLocale(), "Operations.html"))), 144 String.format(request.getLocale(), 145 Bundle.getMessage(request.getLocale(), "HtmlTitle"), 146 InstanceManager.getDefault(ServletUtil.class).getRailroadName(false), 147 Bundle.getMessage(request.getLocale(), "TrainsTitle") 148 ), 149 InstanceManager.getDefault(ServletUtil.class).getNavBar(request.getLocale(), request.getContextPath()), 150 InstanceManager.getDefault(ServletUtil.class).getRailroadName(false), 151 InstanceManager.getDefault(ServletUtil.class).getFooter(request.getLocale(), request.getContextPath()), 152 "" // no train Id 153 )); 154 } 155 } 156 157 private void processManifest(String id, HttpServletRequest request, HttpServletResponse response) throws IOException { 158 Train train = InstanceManager.getDefault(TrainManager.class).getTrainById(id); 159 if ("html".equals(request.getParameter("format"))) { 160 log.debug("Getting manifest HTML code for train {}", id); 161 HtmlManifest manifest = new HtmlManifest(request.getLocale(), train); 162 InstanceManager.getDefault(ServletUtil.class).setNonCachingHeaders(response); 163 response.setContentType(UTF8_TEXT_HTML); 164 response.getWriter().print(String.format(request.getLocale(), 165 FileUtil.readURL(FileUtil.findURL(Bundle.getMessage(request.getLocale(), "ManifestSnippet.html"))), 166 train.getIconName(), 167 StringEscapeUtils.escapeHtml4(train.getDescription()), 168 Setup.isPrintValidEnabled() ? manifest.getValidity() : "", 169 HtmlTrainCommon.convertToHTMLColor(StringEscapeUtils 170 .escapeHtml4(TrainCommon.getTextSizeString(train.getCommentCurrentWithColor()))), 171 Setup.isPrintRouteCommentsEnabled() ? train.getRoute().getComment() : "", 172 HtmlTrainCommon.convertToHTMLColor(manifest.getLocations()))); 173 } else if (JSON.JSON.equals(request.getParameter("format"))) { 174 log.debug("Getting manifest JSON code for train {}", id); 175 JsonNode manifest = this.mapper.readTree(new JsonManifest(train).getFile()); 176 if (manifest.path(JSON.IMAGE).isTextual()) { 177 ((ObjectNode) manifest).put(JSON.IMAGE, WebServer.portablePathToURI(FileUtil.getPortableFilename(manifest.path(JSON.IMAGE).asText()))); 178 } 179 String content = this.mapper.writeValueAsString(manifest); 180 response.setContentType(ServletUtil.UTF8_APPLICATION_JSON); 181 response.setContentLength(content.getBytes(UTF8).length); 182 response.getWriter().print(content); 183 } else { 184 response.setContentType(UTF8_TEXT_HTML); 185 response.getWriter().print(String.format(request.getLocale(), 186 FileUtil.readURL(FileUtil.findURL(Bundle.getMessage(request.getLocale(), "Operations.html"))), 187 String.format(request.getLocale(), 188 Bundle.getMessage(request.getLocale(), "HtmlTitle"), 189 InstanceManager.getDefault(ServletUtil.class).getRailroadName(false), 190 String.format(request.getLocale(), 191 Bundle.getMessage(request.getLocale(), "ManifestTitle"), 192 train.getIconName(), 193 StringEscapeUtils.escapeHtml4(train.getDescription()) 194 ) 195 ), 196 InstanceManager.getDefault(ServletUtil.class).getNavBar(request.getLocale(), request.getContextPath()), 197 !train.getRailroadName().equals("") ? train.getRailroadName() : InstanceManager.getDefault(ServletUtil.class).getRailroadName(false), 198 InstanceManager.getDefault(ServletUtil.class).getFooter(request.getLocale(), request.getContextPath()), 199 train.getId() 200 )); 201 } 202 } 203 204 private void processConductor(String id, HttpServletRequest request, HttpServletResponse response) throws IOException { 205 Train train = InstanceManager.getDefault(TrainManager.class).getTrainById(id); 206 JsonNode data; 207 if (request.getContentType() != null && request.getContentType().contains(APPLICATION_JSON)) { 208 data = this.mapper.readTree(request.getReader()); 209 if (!data.path(JSON.DATA).isMissingNode()) { 210 data = data.path(JSON.DATA); 211 } 212 } else { 213 data = this.mapper.createObjectNode(); 214 ((ObjectNode) data).put("format", request.getParameter("format")); 215 } 216 if (data.path("format").asText().equals("html")) { 217 JsonNode location = data.path(JSON.LOCATION); 218 if (!location.isMissingNode()) { 219 if (location.isNull() || train.getNextLocationName().equals(location.asText())) { 220 train.move(); 221 return; // done; property change will cause update to client 222 } 223 } 224 log.debug("Getting conductor HTML code for train {}", id); 225 HtmlConductor conductor = new HtmlConductor(request.getLocale(), train); 226 InstanceManager.getDefault(ServletUtil.class).setNonCachingHeaders(response); 227 response.setContentType(UTF8_TEXT_HTML); 228 response.getWriter().print(conductor.getLocation()); 229 } else { 230 response.setContentType(UTF8_TEXT_HTML); 231 response.getWriter().print(String.format(request.getLocale(), 232 FileUtil.readURL(FileUtil.findURL(Bundle.getMessage(request.getLocale(), "Operations.html"))), 233 String.format(request.getLocale(), 234 Bundle.getMessage(request.getLocale(), "HtmlTitle"), 235 InstanceManager.getDefault(ServletUtil.class).getRailroadName(false), 236 String.format(request.getLocale(), 237 Bundle.getMessage(request.getLocale(), "ConductorTitle"), 238 train.getIconName(), 239 StringEscapeUtils.escapeHtml4(train.getDescription()) 240 ) 241 ), 242 InstanceManager.getDefault(ServletUtil.class).getNavBar(request.getLocale(), request.getContextPath()), 243 !train.getRailroadName().equals("") ? train.getRailroadName() : InstanceManager.getDefault(ServletUtil.class).getRailroadName(false), 244 InstanceManager.getDefault(ServletUtil.class).getFooter(request.getLocale(), request.getContextPath()), 245 train.getId() 246 )); 247 } 248 } 249// <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code."> 250 251 /** 252 * Handles the HTTP <code>GET</code> method. 253 * 254 * @param request servlet request 255 * @param response servlet response 256 * @throws ServletException if a servlet-specific error occurs 257 * @throws IOException if an I/O error occurs 258 */ 259 @Override 260 protected void doGet(HttpServletRequest request, HttpServletResponse response) 261 throws ServletException, IOException { 262 processRequest(request, response); 263 } 264 265 /** 266 * Handles the HTTP <code>POST</code> method. 267 * 268 * @param request servlet request 269 * @param response servlet response 270 * @throws ServletException if a servlet-specific error occurs 271 * @throws IOException if an I/O error occurs 272 */ 273 @Override 274 protected void doPost(HttpServletRequest request, HttpServletResponse response) 275 throws ServletException, IOException { 276 processRequest(request, response); 277 } 278 279 /** 280 * Handles the HTTP <code>PUT</code> method. 281 * 282 * @param request servlet request 283 * @param response servlet response 284 * @throws ServletException if a servlet-specific error occurs 285 * @throws IOException if an I/O error occurs 286 */ 287 @Override 288 protected void doPut(HttpServletRequest request, HttpServletResponse response) 289 throws ServletException, IOException { 290 processRequest(request, response); 291 } 292 293 /** 294 * Returns a short description of the servlet. 295 * 296 * @return a String containing servlet description 297 */ 298 @Override 299 public String getServletInfo() { 300 return "Operations Servlet"; 301 }// </editor-fold> 302 303}