package org.mapfish.print.servlet.oldapi; import com.google.common.base.Strings; import org.json.JSONException; import org.json.JSONWriter; import org.mapfish.print.Constants; import org.mapfish.print.ExceptionUtils; import org.mapfish.print.MapPrinter; import org.mapfish.print.MapPrinterFactory; import org.mapfish.print.attribute.Attribute; import org.mapfish.print.attribute.map.MapAttribute; import org.mapfish.print.attribute.map.MapAttribute.MapAttributeValues; import org.mapfish.print.attribute.map.ZoomLevels; import org.mapfish.print.config.Configuration; import org.mapfish.print.config.Template; import org.mapfish.print.map.DistanceUnit; import org.mapfish.print.map.Scale; import org.mapfish.print.servlet.BaseMapServlet; import org.mapfish.print.servlet.MapPrinterServlet; import org.mapfish.print.servlet.NoSuchAppException; import org.mapfish.print.servlet.job.JobManager; import org.mapfish.print.servlet.job.NoSuchReferenceException; import org.mapfish.print.wrapper.json.PJsonObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import java.io.IOException; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.text.DecimalFormat; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import static org.mapfish.print.Constants.PDF_DPI; import static org.mapfish.print.servlet.ServletMapPrinterFactory.DEFAULT_CONFIGURATION_FILE_KEY; /** * Servlet with the old print API. */ @Controller public class OldAPIMapPrinterServlet extends BaseMapServlet { private static final Logger LOGGER = LoggerFactory.getLogger(OldAPIMapPrinterServlet.class); static final String REPORT_SUFFIX = ".printout"; private static final String DEP_SEG = "/dep"; private static final String INFO_URL = "/info.json"; private static final String DEP_INFO_URL = DEP_SEG + INFO_URL; private static final String PRINT_URL = "/print.pdf"; private static final String DEP_PRINT_URL = DEP_SEG + PRINT_URL; private static final String CREATE_URL = "/create.json"; private static final String DEP_CREATE_URL = DEP_SEG + CREATE_URL; private static final int HALF_SECOND = 500; static final String JSON_PRINT_URL = "printURL"; static final String JSON_CREATE_URL = "createURL"; @Autowired private MapPrinterFactory printerFactory; @Autowired private MapPrinterServlet primaryApiServlet; @Autowired private JobManager jobManager; /** * Print the report from a POST request. * * @param requestData the request spec as POST body * @param httpServletRequest the request object * @param httpServletResponse the response object */ @RequestMapping(value = DEP_PRINT_URL, method = RequestMethod.POST) public final void printReportPost( @RequestBody final String requestData, final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse) throws ServletException, IOException { if (Strings.isNullOrEmpty(requestData)) { error(httpServletResponse, "Missing 'spec' parameter", HttpStatus.INTERNAL_SERVER_ERROR); return; } createAndGetPDF(httpServletRequest, httpServletResponse, requestData); } /** * Print the report from a GET request. Avoid to use * it, the accents in the spec are not all supported. * * @param spec the request spec as GET parameter * @param httpServletRequest the request object * @param httpServletResponse the response object */ @RequestMapping(value = DEP_PRINT_URL, method = RequestMethod.GET) public final void printReport( @RequestParam(value = "spec", defaultValue = "") final String spec, final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse) throws ServletException { if (Strings.isNullOrEmpty(spec)) { error(httpServletResponse, "Missing 'spec' parameter", HttpStatus.INTERNAL_SERVER_ERROR); return; } createAndGetPDF(httpServletRequest, httpServletResponse, spec); } /** * Create the report from a POST request. * * @param baseUrl the base url to the servlet * @param spec if spec is form data then this will be nonnull * @param requestData the request spec as POST body * @param httpServletRequest the request object * @param httpServletResponse the response object */ @RequestMapping(value = DEP_CREATE_URL + "**", method = RequestMethod.POST) public final void createReportPost( @RequestParam(value = "url", defaultValue = "") final String baseUrl, @RequestParam(value = "spec", required = false) final String spec, @RequestBody final String requestData, final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse) throws IOException, JSONException { if (Strings.isNullOrEmpty(requestData)) { // TODO in case the POST body is empty, status code 415 is returned automatically, so we never get here error(httpServletResponse, "Missing 'spec' parameter", HttpStatus.INTERNAL_SERVER_ERROR); return; } String baseUrlPath = getBaseUrl(DEP_CREATE_URL, URLDecoder.decode(baseUrl, Constants.DEFAULT_ENCODING), httpServletRequest); String specData = spec == null ? requestData : spec; createPDF(httpServletRequest, httpServletResponse, baseUrlPath, specData); } /** * All in one method: create and returns the PDF to the client. * * @param httpServletRequest the request object * @param httpServletResponse the response object * @param spec the request spec */ private void createAndGetPDF(final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse, final String spec) { try { httpServletRequest.setCharacterEncoding("UTF-8"); } catch (UnsupportedEncodingException e) { throw ExceptionUtils.getRuntimeException(e); } try { String jobRef = doCreatePDFFile(spec, httpServletRequest, httpServletResponse); this.primaryApiServlet.getReport(jobRef, false, httpServletResponse); } catch (NoSuchAppException e) { error(httpServletResponse, e.getMessage(), HttpStatus.NOT_FOUND); } catch (Throwable e) { error(httpServletResponse, e); } } /** * Create the PDF and returns to the client (in JSON) the URL to get the PDF. * * @param httpServletRequest the request object * @param httpServletResponse the response object * @param basePath the path of the webapp * @param spec the request spec */ protected final void createPDF(final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse, final String basePath, final String spec) throws IOException, JSONException { String jobRef; try { try { jobRef = doCreatePDFFile(spec, httpServletRequest, httpServletResponse); httpServletResponse.setContentType("application/json; charset=utf-8"); PrintWriter writer = null; try { writer = httpServletResponse.getWriter(); JSONWriter json = new JSONWriter(writer); json.object(); { json.key("getURL").value(basePath + "/" + jobRef + REPORT_SUFFIX); } json.endObject(); } finally { if (writer != null) { writer.close(); } } } catch (NoSuchAppException e) { error(httpServletResponse, e.getMessage(), HttpStatus.NOT_FOUND); return; } } catch (Throwable e) { error(httpServletResponse, e); return; } } /** * To get the PDF created previously and write it to the http response. * * @param inline if true then inline the response * @param response the http response * @param id the id for the file */ @RequestMapping(DEP_SEG + "/{id:.+}" + REPORT_SUFFIX) public final void getFile(@PathVariable final String id, @RequestParam(value = "inline", defaultValue = "false") final boolean inline, final HttpServletResponse response) throws IOException, ServletException { this.primaryApiServlet.getReport(id, inline, response); } /** * To get (in JSON) the information about the available formats and CO. * * @param baseUrl the path to the webapp * @param jsonpVar if given the result is returned as a variable assignment * @param req the http request * @param appId the app request * @param resp the http response */ @RequestMapping(DEP_INFO_URL) public final void getInfo( @RequestParam(value = "url", defaultValue = "") final String baseUrl, @RequestParam(value = "var", defaultValue = "") final String jsonpVar, @RequestParam(value = "app", defaultValue = DEFAULT_CONFIGURATION_FILE_KEY) final String appId, final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { final MapPrinter printer; try { printer = this.printerFactory.create(appId); } catch (NoSuchAppException e) { error(resp, e.getMessage(), HttpStatus.NOT_FOUND); return; } resp.setContentType("application/json; charset=utf-8"); final PrintWriter writer = resp.getWriter(); try { if (!Strings.isNullOrEmpty(jsonpVar)) { writer.print("var " + jsonpVar + "="); } JSONWriter json = new JSONWriter(writer); try { json.object(); writeInfoJson(json, baseUrl, printer, req); json.endObject(); } catch (JSONException e) { throw new ServletException(e); } if (!Strings.isNullOrEmpty(jsonpVar)) { writer.print(";"); } } catch (UnsupportedOperationException exc) { error(resp, exc.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); throw new ServletException(exc); } catch (Exception exc) { error(resp, "Unexpected error, please see the server logs", HttpStatus.INTERNAL_SERVER_ERROR); throw new ServletException(exc); } finally { writer.close(); } } private void writeInfoJson(final JSONWriter json, final String baseUrl, final MapPrinter printer, final HttpServletRequest req) throws JSONException { json.key("outputFormats"); json.array(); { for (String format : printer.getOutputFormatsNames()) { json.object(); json.key("name").value(format); json.endObject(); } } json.endArray(); writeInfoLayouts(json, printer.getConfiguration()); String urlToUseInSpec = getBaseUrl(DEP_INFO_URL, baseUrl, req); json.key(JSON_PRINT_URL).value(urlToUseInSpec + PRINT_URL); json.key(JSON_CREATE_URL).value(urlToUseInSpec + CREATE_URL); } private void writeInfoLayouts(final JSONWriter json, final Configuration configuration) throws JSONException { Double maxDpi = null; double[] dpiSuggestions = null; ZoomLevels zoomLevels = null; json.key("layouts"); json.array(); for (String name : configuration.getTemplates().keySet()) { json.object(); { json.key("name").value(name); json.key("rotation").value(true); Template template = configuration.getTemplates().get(name); // find the map attribute MapAttribute map = null; for (Attribute attribute : template.getAttributes().values()) { if (attribute instanceof MapAttribute) { if (map != null) { throw new UnsupportedOperationException(String.format( "Template '%s' contains more than one map configuration. " + "The legacy API supports only one map per template.", name)); } else { map = (MapAttribute) attribute; } } } if (map == null) { LOGGER.warn(String.format("Template '%s' contains no map configuration.", name)); } else { MapAttributeValues mapValues = map.createValue(template); json.key("map"); json.object(); { json.key("width").value(mapValues.getMapSize().width); json.key("height").value(mapValues.getMapSize().height); } json.endObject(); // get the zoom levels and dpi values from the first template if (maxDpi == null) { maxDpi = map.getMaxDpi(); dpiSuggestions = map.getDpiSuggestions(); } if (zoomLevels == null) { zoomLevels = mapValues.getZoomLevels(); } } } json.endObject(); } json.endArray(); json.key("dpis"); json.array(); { if (dpiSuggestions != null) { for (Double dpi : dpiSuggestions) { json.object(); { json.key("name").value(Integer.toString(dpi.intValue())); json.key("value").value(Integer.toString(dpi.intValue())); } json.endObject(); } } } json.endArray(); json.key("scales"); json.array(); { if (zoomLevels != null) { { for (int i = 0; i < zoomLevels.size(); i++) { Scale scale = zoomLevels.get(i, DistanceUnit.M); json.object(); { String scaleValue = new DecimalFormat("#.##").format( scale.getDenominator(PDF_DPI)); json.key("name").value("1:" + scaleValue); json.key("value").value(scaleValue); } json.endObject(); } } } } json.endArray(); } private String getBaseUrl(final String suffix, final String baseUrl, final HttpServletRequest req) { String urlToUseInSpec; if (!Strings.isNullOrEmpty(baseUrl) && baseUrl.endsWith(suffix)) { urlToUseInSpec = baseUrl.replace(suffix, DEP_SEG); } else if (!Strings.isNullOrEmpty(baseUrl)) { urlToUseInSpec = removeLastSlash(baseUrl); } else { urlToUseInSpec = removeLastSlash(super.getBaseUrl(req).toString()) + DEP_SEG; } urlToUseInSpec = removeLastSlash(urlToUseInSpec); return urlToUseInSpec; } private String removeLastSlash(final String urlToUseInSpec) { if (urlToUseInSpec.endsWith("/")) { return urlToUseInSpec.substring(1); } return urlToUseInSpec; } /** * Do the actual work of creating the PDF temporary file. * * @param spec the json specification in the old API format * @param httpServletRequest the request */ private String doCreatePDFFile( final String spec, final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse) throws IOException, ServletException, InterruptedException, NoSuchAppException, NoSuchReferenceException { LOGGER.debug("\nOLD-API:\n{}", spec); PJsonObject specJson = MapPrinterServlet.parseJson(spec, httpServletResponse); String appId; if (specJson.has("app")) { appId = specJson.getString("app"); } else { appId = DEFAULT_CONFIGURATION_FILE_KEY; } MapPrinter mapPrinter = this.printerFactory.create(appId); PJsonObject updatedSpecJson = null; try { updatedSpecJson = OldAPIRequestConverter.convert(specJson, mapPrinter.getConfiguration()); String format = updatedSpecJson.optString(MapPrinterServlet.JSON_OUTPUT_FORMAT, "pdf"); final String jobReferenceId = this.primaryApiServlet.createAndSubmitPrintJob(appId, format, updatedSpecJson.getInternalObj().toString(), httpServletRequest, httpServletResponse); boolean isDone = false; while (!isDone) { Thread.sleep(HALF_SECOND); isDone = this.jobManager.getStatus(jobReferenceId).isDone(); } return jobReferenceId; } catch (JSONException e) { throw ExceptionUtils.getRuntimeException(e); } } }