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