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}