/*
* Copyright (C) 2014 Camptocamp
*
* This file is part of MapFish Print
*
* MapFish Print is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MapFish Print is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MapFish Print. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mapfish.print.servlet;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.google.common.io.Files;
import org.jfree.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
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.config.Template;
import org.mapfish.print.servlet.job.FailedPrintJob;
import org.mapfish.print.servlet.job.JobManager;
import org.mapfish.print.servlet.job.NoSuchReferenceException;
import org.mapfish.print.servlet.job.PrintJob;
import org.mapfish.print.servlet.job.PrintJobStatus;
import org.mapfish.print.servlet.job.SuccessfulPrintJob;
import org.mapfish.print.servlet.job.loader.ReportLoader;
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.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.context.SecurityContextHolder;
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.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.URI;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import static org.mapfish.print.servlet.ServletMapPrinterFactory.DEFAULT_CONFIGURATION_FILE_KEY;
/**
* The default servlet.
*
* @author Jesse
* CSOFF: RedundantThrowsCheck
*/
@Controller
public class MapPrinterServlet extends BaseMapServlet {
private static final Logger LOGGER = LoggerFactory.getLogger(MapPrinterServlet.class);
/**
* The url path for capabilities requests.
*/
public static final String CAPABILITIES_URL = "/capabilities.json";
/**
* The url path to list all registered configurations.
*/
public static final String LIST_APPS_URL = "/apps.json";
/**
* The url path to get a sample print request.
*/
public static final String EXAMPLE_REQUEST_URL = "/exampleRequest.json";
/**
* The url path to create and get a report.
*/
public static final String CREATE_AND_GET_URL = "/buildreport";
/**
* The url path to get the status for a print task.
*/
public static final String STATUS_URL = "/status";
/**
* The url path to cancel a print task.
*/
public static final String CANCEL_URL = "/cancel";
/**
* The url path to create a print task and to get a finished print.
*/
public static final String REPORT_URL = "/report";
/* Registry keys */
/**
* The key containing an error message for failed jobs.
*/
public static final String JSON_ERROR = "error";
/**
* The application ID which indicates the configuration file to load.
*/
public static final String JSON_APP = "app";
/**
* The number of print jobs done by the cluster (or this server if count is not shared through-out cluster).
* <p/>
* Part of the {@link #getStatus(String, String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
* response
*/
public static final String JSON_COUNT = "count";
/**
* The json property name of the property that contains the request spec.
*/
public static final String JSON_SPEC = "spec";
/**
* If the job is done (value is true) or not (value is false).
* <p/>
* Part of the {@link #getStatus(String, String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
* response
*/
public static final String JSON_DONE = "done";
/**
* The time taken for the job to complete.
* <p/>
* Part of the {@link #getStatus(String, String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
* response
*/
public static final String JSON_TIME = "time";
/**
* The key containing the print job reference ID in the create report response.
*/
public static final String JSON_PRINT_JOB_REF = "ref";
/**
* The json key in the create report response containing a link to get the status of the print job.
*/
public static final String JSON_STATUS_LINK = "statusURL";
/**
* The json key in the create report and status responses containing a link to download the report.
*/
public static final String JSON_DOWNLOAD_LINK = "downloadURL";
private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\$\\{(\\S+)}");
/**
* The JSON key in the request spec that contains the outputFormat. This value will be put into
* the spec by the servlet. there is not need for the post to do this.
*/
public static final String JSON_OUTPUT_FORMAT = "outputFormat";
private static final int JSON_INDENT_FACTOR = 4;
/**
* The json tag referring to the attributes.
*/
public static final String JSON_ATTRIBUTES = "attributes";
/**
* The json property to add the request headers from the print request.
*
* The request headers from the print request are needed by certain processors,
* the headers are added to the request JSON data for those processors.
*/
public static final String JSON_REQUEST_HEADERS = "requestHeaders";
@Autowired
private JobManager jobManager;
@Autowired
private List<ReportLoader> reportLoaders;
@Autowired
private MapPrinterFactory printerFactory;
@Autowired
private ApplicationContext context;
@Autowired
private ServletInfo servletInfo;
private long maxCreateAndGetWaitTimeInSeconds;
@Autowired
private MapPrinterFactory mapPrinterFactory;
/**
* Get a status report on a job. Returns the following json:
* <p/>
* <pre><code>
* {"time":0,"count":0,"done":false}
* </code></pre>
*
* @param referenceId the job reference
* @param jsonpCallback if given the result is returned with a function call wrapped around it
* @param statusRequest the request object
* @param statusResponse the response object
*/
@RequestMapping(value = STATUS_URL + "/{referenceId:\\S+}.json", method = RequestMethod.GET)
public final void getStatus(
@PathVariable final String referenceId,
@RequestParam(value = "jsonp", defaultValue = "") final String jsonpCallback,
final HttpServletRequest statusRequest,
final HttpServletResponse statusResponse) {
PrintWriter writer = null;
try {
boolean done = this.jobManager.isDone(referenceId);
setContentType(statusResponse, jsonpCallback);
writer = statusResponse.getWriter();
appendJsonpCallback(jsonpCallback, writer);
JSONWriter json = new JSONWriter(writer);
json.object();
{
json.key(JSON_DONE).value(done);
Optional<? extends PrintJobStatus> metadata = this.jobManager.getCompletedPrintJob(referenceId);
if (metadata.isPresent() && metadata.get() instanceof FailedPrintJob) {
json.key(JSON_ERROR).value(((FailedPrintJob) metadata.get()).getError());
}
json.key(JSON_COUNT).value(this.jobManager.getLastPrintCount());
json.key(JSON_TIME).value(this.jobManager.getAverageTimeSpentPrinting());
addDownloadLinkToJson(statusRequest, referenceId, json);
}
json.endObject();
appendJsonpCallbackEnd(jsonpCallback, writer);
} catch (JSONException e) {
LOGGER.error("Error obtaining status", e);
throw ExceptionUtils.getRuntimeException(e);
} catch (IOException e) {
LOGGER.error("Error obtaining status", e);
throw ExceptionUtils.getRuntimeException(e);
} catch (NoSuchReferenceException e) {
error(statusResponse, e.getMessage(), HttpStatus.NOT_FOUND);
} finally {
if (writer != null) {
writer.close();
}
}
}
/**
* Cancel a job.
*
* Even if a job was already finished, subsequent status requests will
* return that the job was canceled.
*
* @param referenceId the job reference
* @param statusResponse the response object
*/
@RequestMapping(value = CANCEL_URL + "/{referenceId:\\S+}", method = RequestMethod.DELETE)
public final void cancel(
@PathVariable final String referenceId,
final HttpServletResponse statusResponse) {
try {
this.jobManager.cancel(referenceId);
} catch (NoSuchReferenceException e) {
error(statusResponse, e.getMessage(), HttpStatus.NOT_FOUND);
}
}
/**
* Add the print job to the job queue.
*
* @param appId the id of the app to get the request for.
* @param format the format of the returned report
* @param requestData a json formatted string with the request data required to perform the report generation.
* @param createReportRequest the request object
* @param createReportResponse the response object
*/
@RequestMapping(value = "/{appId}" + REPORT_URL + ".{format:\\w+}", method = RequestMethod.POST)
public final void createReport(@PathVariable final String appId,
@PathVariable final String format,
@RequestBody final String requestData,
final HttpServletRequest createReportRequest,
final HttpServletResponse createReportResponse) throws JSONException, NoSuchAppException {
String ref = createAndSubmitPrintJob(appId, format, requestData, createReportRequest, createReportResponse);
if (ref == null) {
error(createReportResponse, "Failed to create a print job", HttpStatus.INTERNAL_SERVER_ERROR);
}
PrintWriter writer = null;
try {
createReportResponse.setContentType("application/json; charset=utf-8");
writer = createReportResponse.getWriter();
JSONWriter json = new JSONWriter(writer);
json.object();
{
json.key(JSON_PRINT_JOB_REF).value(ref);
String statusURL = getBaseUrl(createReportRequest) + STATUS_URL + "/" + ref + ".json";
json.key(JSON_STATUS_LINK).value(statusURL);
addDownloadLinkToJson(createReportRequest, ref, json);
}
json.endObject();
} catch (JSONException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (writer != null) {
writer.close();
}
}
}
/**
* To get the PDF created previously.
*
* @param referenceId the path to the file.
* @param inline whether or not to inline the
* @param getReportResponse the response object
*/
@RequestMapping(value = REPORT_URL + "/{referenceId:\\S+}", method = RequestMethod.GET)
public final void getReport(@PathVariable final String referenceId,
@RequestParam(value = "inline", defaultValue = "false") final boolean inline,
final HttpServletResponse getReportResponse)
throws IOException, ServletException {
loadReport(referenceId, getReportResponse, new HandleReportLoadResult<Void>() {
@Override
public Void unknownReference(final HttpServletResponse httpServletResponse, final String referenceId) {
error(httpServletResponse, "Print with ref=" + referenceId + " unknown", HttpStatus.NOT_FOUND);
return null;
}
@Override
public Void unsupportedLoader(final HttpServletResponse httpServletResponse, final String referenceId) {
error(httpServletResponse, "Print with ref=" + referenceId + " can not be loaded", HttpStatus.NOT_FOUND);
return null;
}
@Override
public Void successfulPrint(final SuccessfulPrintJob successfulPrintResult, final HttpServletResponse httpServletResponse,
final URI reportURI, final ReportLoader loader) throws IOException, ServletException {
sendReportFile(successfulPrintResult, httpServletResponse, loader, reportURI, inline);
return null;
}
@Override
public Void failedPrint(final FailedPrintJob failedPrintJob, final HttpServletResponse httpServletResponse) {
error(httpServletResponse, failedPrintJob.getError(), HttpStatus.INTERNAL_SERVER_ERROR);
return null;
}
@Override
public Void printJobPending(final HttpServletResponse httpServletResponse, final String referenceId) {
error(httpServletResponse, "Report has not yet completed processing", HttpStatus.ACCEPTED);
return null;
}
});
}
/**
* Add the print job to the job queue.
*
* @param format the format of the returned report
* @param requestData a json formatted string with the request data required to perform the report generation.
* @param createReportRequest the request object
* @param createReportResponse the response object
*/
@RequestMapping(value = REPORT_URL + ".{format:\\w+}", method = RequestMethod.POST)
public final void createReport(@PathVariable final String format,
@RequestBody final String requestData,
final HttpServletRequest createReportRequest,
final HttpServletResponse createReportResponse) throws JSONException, NoSuchAppException {
PJsonObject spec = parseJson(requestData, createReportResponse);
String appId = spec.optString(JSON_APP, DEFAULT_CONFIGURATION_FILE_KEY);
createReport(appId, format, requestData, createReportRequest, createReportResponse);
}
/**
* add the print job to the job queue.
*
* @param appId the id of the app to get the request for.
* @param format the format of the returned report
* @param requestData a json formatted string with the request data required to perform the report generation.
* @param inline whether or not to inline the content
* @param createReportRequest the request object
* @param createReportResponse the response object
*/
@RequestMapping(value = "/{appId}" + CREATE_AND_GET_URL + ".{format:\\w+}", method = RequestMethod.POST)
public final void createReportAndGet(@PathVariable final String appId,
@PathVariable final String format,
@RequestBody final String requestData,
@RequestParam(value = "inline", defaultValue = "false") final boolean inline,
final HttpServletRequest createReportRequest,
final HttpServletResponse createReportResponse)
throws IOException, ServletException, InterruptedException, JSONException, NoSuchAppException {
String ref = createAndSubmitPrintJob(appId, format, requestData, createReportRequest, createReportResponse);
if (ref == null) {
error(createReportResponse, "Failed to create a print job", HttpStatus.INTERNAL_SERVER_ERROR);
}
final HandleReportLoadResult<Boolean> handler = new HandleReportLoadResult<Boolean>() {
@Override
public Boolean unknownReference(final HttpServletResponse httpServletResponse, final String referenceId) {
error(httpServletResponse, "Print with ref=" + referenceId + " unknown", HttpStatus.NOT_FOUND);
return true;
}
@Override
public Boolean unsupportedLoader(final HttpServletResponse httpServletResponse, final String referenceId) {
error(httpServletResponse, "Print with ref=" + referenceId + " can not be loaded", HttpStatus.NOT_FOUND);
return true;
}
@Override
public Boolean successfulPrint(final SuccessfulPrintJob successfulPrintResult,
final HttpServletResponse httpServletResponse,
final URI reportURI, final ReportLoader loader) throws IOException, ServletException {
sendReportFile(successfulPrintResult, httpServletResponse, loader, reportURI, inline);
return true;
}
@Override
public Boolean failedPrint(final FailedPrintJob failedPrintJob, final HttpServletResponse httpServletResponse) {
error(httpServletResponse, failedPrintJob.getError(), HttpStatus.INTERNAL_SERVER_ERROR);
return true;
}
@Override
public Boolean printJobPending(final HttpServletResponse httpServletResponse, final String referenceId) {
return false;
}
};
boolean isDone = false;
long startWaitTime = System.currentTimeMillis();
final long maxWaitTimeInMillis = TimeUnit.SECONDS.toMillis(this.maxCreateAndGetWaitTimeInSeconds);
while (!isDone && System.currentTimeMillis() - startWaitTime < maxWaitTimeInMillis) {
Thread.sleep(TimeUnit.SECONDS.toMillis(1));
isDone = loadReport(ref, createReportResponse, handler);
}
}
/**
* add the print job to the job queue.
*
* @param format the format of the returned report
* @param requestData a json formatted string with the request data required to perform the report generation.
* @param inline whether or not to inline the content
* @param createReportRequest the request object
* @param createReportResponse the response object
*/
@RequestMapping(value = CREATE_AND_GET_URL + ".{format:\\w+}", method = RequestMethod.POST)
public final void createReportAndGetNoAppId(@PathVariable final String format,
@RequestBody final String requestData,
@RequestParam(value = "inline", defaultValue = "false") final boolean inline,
final HttpServletRequest createReportRequest,
final HttpServletResponse createReportResponse)
throws IOException, ServletException, InterruptedException, JSONException, NoSuchAppException {
PJsonObject spec = parseJson(requestData, createReportResponse);
String appId = spec.optString(JSON_APP, DEFAULT_CONFIGURATION_FILE_KEY);
createReportAndGet(appId, format, requestData, inline, createReportRequest, createReportResponse);
}
/**
* To get (in JSON) the information about the available formats and CO.
*
* @param jsonpCallback if given the result is returned with a function call wrapped around it
* @param listAppsResponse the response object
*/
@RequestMapping(value = LIST_APPS_URL, method = RequestMethod.GET)
public final void listAppIds(
@RequestParam(value = "jsonp", defaultValue = "") final String jsonpCallback,
final HttpServletResponse listAppsResponse) throws ServletException,
IOException {
Set<String> appIds = this.printerFactory.getAppIds();
setContentType(listAppsResponse, jsonpCallback);
final PrintWriter writer = listAppsResponse.getWriter();
try {
appendJsonpCallback(jsonpCallback, writer);
JSONWriter json = new JSONWriter(writer);
try {
json.array();
for (String appId : appIds) {
json.value(appId);
}
json.endArray();
} catch (JSONException e) {
throw new ServletException(e);
}
appendJsonpCallbackEnd(jsonpCallback, writer);
} finally {
if (writer != null) {
writer.close();
}
}
}
/**
* To get (in JSON) the information about the available formats and CO.
*
* @param pretty if true then pretty print the capabilities
* @param jsonpCallback if given the result is returned with a function call wrapped around it
* @param capabilitiesResponse the response object
*/
@RequestMapping(value = CAPABILITIES_URL, method = RequestMethod.GET)
public final void getCapabilities(
@RequestParam(value = "pretty", defaultValue = "false") final boolean pretty,
@RequestParam(value = "jsonp", defaultValue = "") final String jsonpCallback,
final HttpServletResponse capabilitiesResponse) throws ServletException,
IOException, JSONException {
getCapabilities(DEFAULT_CONFIGURATION_FILE_KEY, pretty, jsonpCallback, capabilitiesResponse);
}
/**
* To get (in JSON) the information about the available formats and CO.
*
* @param appId the name of the "app" or in other words, a mapping to the configuration file for this request.
* @param pretty if true then pretty print the capabilities
* @param jsonpCallback if given the result is returned with a function call wrapped around it
* @param capabilitiesResponse the response object
*/
@RequestMapping(value = "/{appId:\\w+}" + CAPABILITIES_URL, method = RequestMethod.GET)
public final void getCapabilities(
@PathVariable final String appId,
@RequestParam(value = "pretty", defaultValue = "false") final boolean pretty,
@RequestParam(value = "jsonp", defaultValue = "") final String jsonpCallback,
final HttpServletResponse capabilitiesResponse) throws ServletException,
IOException, JSONException {
MapPrinter printer;
try {
printer = this.printerFactory.create(appId);
} catch (NoSuchAppException e) {
error(capabilitiesResponse, e.getMessage(), HttpStatus.NOT_FOUND);
return;
}
setContentType(capabilitiesResponse, jsonpCallback);
final Writer writer;
final ByteArrayOutputStream prettyPrintBuffer = new ByteArrayOutputStream();
if (pretty) {
writer = new OutputStreamWriter(prettyPrintBuffer, Constants.DEFAULT_CHARSET);
} else {
writer = capabilitiesResponse.getWriter();
}
try {
if (!pretty && !Strings.isNullOrEmpty(jsonpCallback)) {
writer.append(jsonpCallback + "(");
}
JSONWriter json = new JSONWriter(writer);
try {
json.object();
{
json.key(JSON_APP).value(appId);
printer.printClientConfig(json);
}
{
json.key("formats");
Set<String> formats = printer.getOutputFormatsNames();
json.array();
for (String format : formats) {
json.value(format);
}
json.endArray();
}
json.endObject();
} catch (JSONException e) {
throw new ServletException(e);
}
if (!pretty && !Strings.isNullOrEmpty(jsonpCallback)) {
writer.append(");");
}
} finally {
if (writer != null) {
writer.close();
}
}
if (pretty) {
final JSONObject jsonObject = new JSONObject(new String(prettyPrintBuffer.toByteArray(), Constants.DEFAULT_CHARSET));
if (!Strings.isNullOrEmpty(jsonpCallback)) {
capabilitiesResponse.getOutputStream().print(jsonpCallback + "(");
}
capabilitiesResponse.getOutputStream().print(jsonObject.toString(JSON_INDENT_FACTOR));
if (!Strings.isNullOrEmpty(jsonpCallback)) {
capabilitiesResponse.getOutputStream().print(");");
}
}
}
/**
* Get a sample request for the app. An empty response may be returned if there is not example request.
*
* @param jsonpCallback if given the result is returned with a function call wrapped around it
* @param getExampleResponse the response object
*/
@RequestMapping(value = EXAMPLE_REQUEST_URL, method = RequestMethod.GET)
public final void getExampleRequest(
@RequestParam(value = "jsonp", defaultValue = "") final String jsonpCallback,
final HttpServletResponse getExampleResponse) throws ServletException, IOException {
getExampleRequest(DEFAULT_CONFIGURATION_FILE_KEY, jsonpCallback, getExampleResponse);
}
/**
* Get a sample request for the app. An empty response may be returned if there is not example request.
*
* @param appId the id of the app to get the request for.
* @param jsonpCallback if given the result is returned with a function call wrapped around it
* @param getExampleResponse the response object
*/
@RequestMapping(value = "{appId}" + EXAMPLE_REQUEST_URL, method = RequestMethod.GET)
public final void getExampleRequest(
@PathVariable final String appId,
@RequestParam(value = "jsonp", defaultValue = "") final String jsonpCallback,
final HttpServletResponse getExampleResponse) throws ServletException,
IOException {
PrintWriter writer = null;
try {
final MapPrinter mapPrinter = this.printerFactory.create(appId);
final Iterable<File> children = Files.fileTreeTraverser().children(mapPrinter.getConfiguration().getDirectory());
JSONObject allExamples = new JSONObject();
for (File child : children) {
final String requestDataPrefix = "requestData";
if (child.isFile() && child.getName().startsWith(requestDataPrefix) && child.getName().endsWith(".json")) {
String requestData = Files.toString(child, Constants.DEFAULT_CHARSET);
try {
final JSONObject jsonObject = new JSONObject(requestData);
jsonObject.remove(JSON_OUTPUT_FORMAT);
jsonObject.remove(JSON_APP);
requestData = jsonObject.toString(JSON_INDENT_FACTOR);
setContentType(getExampleResponse, jsonpCallback);
} catch (JSONException e) {
// ignore, return raw text;
}
String name = child.getName();
name = name.substring(requestDataPrefix.length());
if (name.startsWith("-")) {
name = name.substring(1);
}
name = Files.getNameWithoutExtension(name);
name = name.trim();
if (name.isEmpty()) {
name = Files.getNameWithoutExtension(child.getName());
}
try {
allExamples.put(name, requestData);
} catch (JSONException e) {
Log.error("Error translating object to json", e);
error(getExampleResponse, "Error translating object to json: " + e.getMessage(),
HttpStatus.INTERNAL_SERVER_ERROR);
return;
}
}
}
final String result;
try {
result = allExamples.toString(JSON_INDENT_FACTOR);
} catch (JSONException e) {
Log.error("Error translating object to json", e);
error(getExampleResponse, "Error translating object to json: " + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
return;
}
writer = getExampleResponse.getWriter();
appendJsonpCallback(jsonpCallback, writer);
writer.append(result);
appendJsonpCallbackEnd(jsonpCallback, writer);
} catch (NoSuchAppException e) {
error(getExampleResponse, "No print app identified by: " + appId, HttpStatus.NOT_FOUND);
} finally {
if (writer != null) {
writer.close();
}
}
}
/**
* Maximum time to wait for a createAndGet request to complete before returning an error.
*
* @param maxCreateAndGetWaitTimeInSeconds the maximum time in seconds to wait for a report to be generated.
*/
public final void setMaxCreateAndGetWaitTimeInSeconds(final long maxCreateAndGetWaitTimeInSeconds) {
this.maxCreateAndGetWaitTimeInSeconds = maxCreateAndGetWaitTimeInSeconds;
}
/**
* Copy the PDF into the output stream.
*
* @param metadata the client request data
* @param httpServletResponse the response object
* @param reportLoader the object used for loading the report
* @param reportURI the uri of the report
* @param inline whether or not to inline the content
*/
protected final void sendReportFile(final SuccessfulPrintJob metadata, final HttpServletResponse httpServletResponse,
final ReportLoader reportLoader, final URI reportURI, final boolean inline)
throws IOException, ServletException {
final OutputStream response = httpServletResponse.getOutputStream();
try {
httpServletResponse.setContentType(metadata.getMimeType());
if (!inline) {
String fileName = metadata.getFileName();
Matcher matcher = VARIABLE_PATTERN.matcher(fileName);
while (matcher.find()) {
final String variable = matcher.group(1);
String replacement = findReplacement(variable, metadata.getCompletionDate());
fileName = fileName.replace("${" + variable + "}", replacement);
matcher = VARIABLE_PATTERN.matcher(fileName);
}
fileName += "." + metadata.getFileExtension();
httpServletResponse.setHeader("Content-disposition", "attachment; filename=" + cleanUpName(fileName));
}
reportLoader.loadReport(reportURI, response);
} finally {
response.close();
}
}
private void addDownloadLinkToJson(final HttpServletRequest httpServletRequest, final String ref,
final JSONWriter json) throws JSONException {
String downloadURL = getBaseUrl(httpServletRequest) + REPORT_URL + "/" + ref;
json.key(JSON_DOWNLOAD_LINK).value(downloadURL);
}
/**
* Read the headers from the request.
*
* @param httpServletRequest the request object
*/
protected final JSONObject getHeaders(final HttpServletRequest httpServletRequest) throws JSONException {
@SuppressWarnings("rawtypes")
Enumeration headersName = httpServletRequest.getHeaderNames();
JSONObject headers = new JSONObject();
while (headersName.hasMoreElements()) {
String name = headersName.nextElement().toString();
@SuppressWarnings("unchecked")
Enumeration<String> e = httpServletRequest.getHeaders(name);
while (e.hasMoreElements()) {
headers.append(name, e.nextElement());
}
}
final JSONObject requestHeadersAttribute = new JSONObject();
requestHeadersAttribute.put(JSON_REQUEST_HEADERS, headers);
return requestHeadersAttribute;
}
/**
* Parse the print request json data.
*
* @param requestDataRaw the request json in string form
* @param httpServletResponse the response object to use for returning errors if needed
*/
public static PJsonObject parseJson(final String requestDataRaw, final HttpServletResponse httpServletResponse) {
try {
if (requestDataRaw == null) {
error(httpServletResponse, "Missing post data. The post payload must either be a form post with a spec parameter or " +
"must be a raw json post with the request.", HttpStatus.INTERNAL_SERVER_ERROR);
return null;
}
String requestData = requestDataRaw;
if (!requestData.startsWith("spec=") && !requestData.startsWith("{")) {
try {
requestData = URLDecoder.decode(requestData, Constants.DEFAULT_ENCODING);
} catch (UnsupportedEncodingException e) {
throw ExceptionUtils.getRuntimeException(e);
}
}
if (requestData.startsWith("spec=")) {
requestData = requestData.substring("spec=".length());
}
try {
return MapPrinter.parseSpec(requestData);
} catch (RuntimeException e) {
try {
return MapPrinter.parseSpec(URLDecoder.decode(requestData, Constants.DEFAULT_ENCODING));
} catch (UnsupportedEncodingException uee) {
throw ExceptionUtils.getRuntimeException(e);
}
}
} catch (RuntimeException e) {
LOGGER.warn("Error parsing request data: " + requestDataRaw);
throw e;
}
}
/**
* Start a print job.
*
* @param appId the id of the printer app
* @param format the format of the returned report.
* @param requestDataRaw the request json in string form
* @param httpServletRequest the request object
* @param httpServletResponse the response object
* @return the job reference id
*/
public final String createAndSubmitPrintJob(final String appId, final String format, final String requestDataRaw,
final HttpServletRequest httpServletRequest,
final HttpServletResponse httpServletResponse) throws JSONException, NoSuchAppException {
PJsonObject specJson = parseJson(requestDataRaw, httpServletResponse);
if (specJson == null) {
return null;
}
if (SPEC_LOGGER.isInfoEnabled()) {
SPEC_LOGGER.info(specJson.toString());
}
specJson.getInternalObj().remove(JSON_OUTPUT_FORMAT);
specJson.getInternalObj().put(JSON_OUTPUT_FORMAT, format);
specJson.getInternalObj().remove(JSON_APP);
specJson.getInternalObj().put(JSON_APP, appId);
final JSONObject requestHeaders = getHeaders(httpServletRequest);
if (requestHeaders.length() > 0) {
specJson.getInternalObj().getJSONObject(JSON_ATTRIBUTES).put(JSON_REQUEST_HEADERS, requestHeaders);
}
String ref = UUID.randomUUID().toString() + "@" + this.servletInfo.getServletId();
PrintJob job = this.context.getBean(PrintJob.class);
job.setReferenceId(ref);
job.setRequestData(specJson);
job.setSecurityContext(SecurityContextHolder.getContext());
// check that we have authorization and configure the job so it can only be access by users with sufficient authorization
final String templateName = specJson.getString(Constants.JSON_LAYOUT_KEY);
final MapPrinter mapPrinter = this.mapPrinterFactory.create(appId);
final Template template = mapPrinter.getConfiguration().getTemplate(templateName);
job.configureAccess(template);
try {
this.jobManager.submit(job);
} catch (RuntimeException exc) {
LOGGER.error("Error when creating job", exc);
ref = null;
}
return ref;
}
private <R> R loadReport(final String referenceId, final HttpServletResponse httpServletResponse,
final HandleReportLoadResult<R> handler) throws IOException, ServletException {
Optional<? extends PrintJobStatus> metadata;
try {
metadata = this.jobManager.getCompletedPrintJob(referenceId);
} catch (NoSuchReferenceException e) {
return handler.unknownReference(httpServletResponse, referenceId);
}
if (!metadata.isPresent()) {
return handler.printJobPending(httpServletResponse, referenceId);
} else if (metadata.get() instanceof SuccessfulPrintJob) {
SuccessfulPrintJob successfulPrintJob = (SuccessfulPrintJob) metadata.get();
URI pdfURI = successfulPrintJob.getURI();
ReportLoader loader = null;
for (ReportLoader reportLoader : this.reportLoaders) {
if (reportLoader.accepts(pdfURI)) {
loader = reportLoader;
break;
}
}
if (loader == null) {
return handler.unsupportedLoader(httpServletResponse, referenceId);
} else {
return handler.successfulPrint(successfulPrintJob, httpServletResponse, pdfURI, loader);
}
} else if (metadata.get() instanceof FailedPrintJob) {
FailedPrintJob failedPrintJob = (FailedPrintJob) metadata.get();
return handler.failedPrint(failedPrintJob, httpServletResponse);
} else {
throw new ServletException("Unexpected state");
}
}
private void setContentType(final HttpServletResponse statusResponse,
final String jsonpCallback) {
if (Strings.isNullOrEmpty(jsonpCallback)) {
statusResponse.setContentType("application/json; charset=utf-8");
} else {
statusResponse.setContentType("application/javascript; charset=utf-8");
}
}
private void appendJsonpCallback(final String jsonpCallback,
final PrintWriter writer) {
if (!Strings.isNullOrEmpty(jsonpCallback)) {
writer.append(jsonpCallback + "(");
}
}
private void appendJsonpCallbackEnd(final String jsonpCallback,
final PrintWriter writer) {
if (!Strings.isNullOrEmpty(jsonpCallback)) {
writer.append(");");
}
}
}