diff --git a/core/.dockerignore b/core/.dockerignore index 8fc7d95cf8..68f3d70ecd 100644 --- a/core/.dockerignore +++ b/core/.dockerignore @@ -1,3 +1,3 @@ * !docker/ -!jettyRunExtraFiles/ +!extraConfigFor/ diff --git a/core/Dockerfile b/core/Dockerfile index a42ceb430b..a3e35d2388 100644 --- a/core/Dockerfile +++ b/core/Dockerfile @@ -50,8 +50,7 @@ CMD ["/usr/local/tomcat/bin/docker-start-print"] FROM runner AS tester -COPY jettyRunExtraFiles/mapfish-spring-application-context-override-acceptencetests.xml \ - /usr/local/tomcat/webapps/ROOT/WEB-INF/classes/mapfish-spring-application-context-override.xml +COPY extraConfigFor/acceptanceTests/mapfish-spring-application-context-override.xml /usr/local/tomcat/webapps/ROOT/WEB-INF/classes/ FROM runner AS watcher diff --git a/core/build.gradle b/core/build.gradle index 8de1dd2260..44876fe706 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -45,7 +45,7 @@ distributions { } -def appDir = new File(project.buildDir, 'install') +def appDir = new File(getLayout().getBuildDirectory().getAsFile().get(), 'install') installDist.doFirst { appDir.deleteDir() } diff --git a/core/jettyRunExtraFiles/mapfish-spring-application-context-override-acceptencetests.xml b/core/extraConfigFor/acceptanceTests/mapfish-spring-application-context-override.xml similarity index 88% rename from core/jettyRunExtraFiles/mapfish-spring-application-context-override-acceptencetests.xml rename to core/extraConfigFor/acceptanceTests/mapfish-spring-application-context-override.xml index 4b33a55f86..6d0465805b 100644 --- a/core/jettyRunExtraFiles/mapfish-spring-application-context-override-acceptencetests.xml +++ b/core/extraConfigFor/acceptanceTests/mapfish-spring-application-context-override.xml @@ -1,12 +1,10 @@ - - - + diff --git a/core/src/main/java/org/mapfish/print/http/ConfigFileResolvingRequest.java b/core/src/main/java/org/mapfish/print/http/ConfigFileResolvingRequest.java index 903d83ab67..59874de66a 100644 --- a/core/src/main/java/org/mapfish/print/http/ConfigFileResolvingRequest.java +++ b/core/src/main/java/org/mapfish/print/http/ConfigFileResolvingRequest.java @@ -173,7 +173,9 @@ private ClientHttpResponse attemptToFetchResponse( ClientHttpResponse response = executeCallbacksAndRequest(requestUsed); if (response.getRawStatusCode() < 500) { LOGGER.debug( - "Fetching success URI resource {}, error code {}", getURI(), response.getRawStatusCode()); + "Fetching success URI resource {}, status code {}", + getURI(), + response.getRawStatusCode()); return response; } LOGGER.debug( diff --git a/core/src/main/java/org/mapfish/print/http/ErrorResponseClientHttpResponse.java b/core/src/main/java/org/mapfish/print/http/ErrorResponseClientHttpResponse.java new file mode 100644 index 0000000000..9fbf768fc6 --- /dev/null +++ b/core/src/main/java/org/mapfish/print/http/ErrorResponseClientHttpResponse.java @@ -0,0 +1,42 @@ +package org.mapfish.print.http; + +import java.io.InputStream; +import javax.annotation.Nonnull; +import org.springframework.http.HttpHeaders; +import org.springframework.http.client.AbstractClientHttpResponse; +import org.springframework.util.StreamUtils; + +public class ErrorResponseClientHttpResponse extends AbstractClientHttpResponse { + private final Exception exception; + + public ErrorResponseClientHttpResponse(final Exception e) { + assert e != null; + this.exception = e; + } + + @Override + @Nonnull + public HttpHeaders getHeaders() { + return new HttpHeaders(); + } + + @Override + @Nonnull + public InputStream getBody() { + return StreamUtils.emptyInput(); + } + + @Override + public int getRawStatusCode() { + return 500; + } + + @Override + @Nonnull + public String getStatusText() { + return exception.getMessage(); + } + + @Override + public void close() {} +} diff --git a/core/src/main/java/org/mapfish/print/http/HttpRequestFetcher.java b/core/src/main/java/org/mapfish/print/http/HttpRequestFetcher.java index 57b723f584..f49b46a144 100644 --- a/core/src/main/java/org/mapfish/print/http/HttpRequestFetcher.java +++ b/core/src/main/java/org/mapfish/print/http/HttpRequestFetcher.java @@ -24,7 +24,6 @@ import org.springframework.http.client.AbstractClientHttpResponse; import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpResponse; -import org.springframework.util.StreamUtils; /** * Schedule tasks for caching Http Requests that can be run simultaneously. @@ -139,7 +138,7 @@ private final class CachedClientHttpRequest implements ClientHttpRequest, Callab private final ClientHttpRequest originalRequest; private final Processor.ExecutionContext context; @Nullable private ClientHttpResponse response; - @Nullable private ForkJoinTask future; + private ForkJoinTask future; private CachedClientHttpRequest( final ClientHttpRequest request, final Processor.ExecutionContext context) { @@ -155,7 +154,7 @@ public HttpMethod getMethod() { @Override @Nonnull public String getMethodValue() { - final HttpMethod method = this.originalRequest.getMethod(); + final HttpMethod method = getMethod(); return method != null ? method.name() : ""; } @@ -182,13 +181,10 @@ public OutputStream getBody() { @Nonnull public ClientHttpResponse execute() { assert this.future != null; - final Timer.Context timerWait = - HttpRequestFetcher.this - .registry - .timer(HttpRequestFetcher.class.getName() + ".waitDownloader") - .time(); - this.future.join(); - timerWait.stop(); + Timer timerWait = HttpRequestFetcher.this.registry.timer(buildMetricName(".waitDownloader")); + try (Timer.Context ignored = timerWait.time()) { + this.future.join(); + } assert this.response != null; LOGGER.debug("Loading cached URI resource {}", this.originalRequest.getURI()); @@ -200,58 +196,32 @@ public ClientHttpResponse execute() { return result; } + private String buildMetricName(final String suffix) { + return HttpRequestFetcher.class.getName() + suffix; + } + @Override public Void call() throws Exception { return context.mdcContextEx( () -> { final String baseMetricName = - HttpRequestFetcher.class.getName() - + ".read." - + StatsUtils.quotePart(getURI().getHost()); - final Timer.Context timerDownload = - HttpRequestFetcher.this.registry.timer(baseMetricName).time(); - try { - context.stopIfCanceled(); - this.response = new CachedClientHttpResponse(this.originalRequest.execute()); - } catch (IOException e) { - LOGGER.error("Request failed {}", this.originalRequest.getURI(), e); - this.response = - new AbstractClientHttpResponse() { - @Override - @Nonnull - public HttpHeaders getHeaders() { - return new HttpHeaders(); - } - - @Override - @Nonnull - public InputStream getBody() { - return StreamUtils.emptyInput(); - } - - @Override - public int getRawStatusCode() { - return 500; - } - - @Override - @Nonnull - public String getStatusText() { - return e.getMessage(); - } - - @Override - public void close() {} - }; - HttpRequestFetcher.this.registry.counter(baseMetricName + ".error").inc(); - } finally { - timerDownload.stop(); + buildMetricName(".read." + StatsUtils.quotePart(getURI().getHost())); + Timer timerDownload = HttpRequestFetcher.this.registry.timer(baseMetricName); + try (Timer.Context ignored = timerDownload.time()) { + try { + context.stopIfCanceled(); + this.response = new CachedClientHttpResponse(this.originalRequest.execute()); + } catch (IOException | RuntimeException e) { + LOGGER.error("Request failed {}", this.originalRequest.getURI(), e); + this.response = new ErrorResponseClientHttpResponse(e); + HttpRequestFetcher.this.registry.counter(baseMetricName + ".error").inc(); + } } return null; }); } - public void setFuture(final ForkJoinTask future) { + public void setFuture(final @Nonnull ForkJoinTask future) { this.future = future; } } diff --git a/core/src/main/java/org/mapfish/print/processor/AbstractProcessor.java b/core/src/main/java/org/mapfish/print/processor/AbstractProcessor.java index 6676968f58..8bd8fd099a 100644 --- a/core/src/main/java/org/mapfish/print/processor/AbstractProcessor.java +++ b/core/src/main/java/org/mapfish/print/processor/AbstractProcessor.java @@ -206,7 +206,7 @@ public String toString() { public static final class Context implements ExecutionContext { @Nonnull private final Map mdcContext; private volatile boolean canceled = false; - private ExecutionStats stats = new ExecutionStats(); + private final ExecutionStats stats = new ExecutionStats(); /** * @param mdcContext The MDC context. diff --git a/core/src/main/java/org/mapfish/print/processor/ExecutionStats.java b/core/src/main/java/org/mapfish/print/processor/ExecutionStats.java index f89bcac1de..bc00600cc5 100644 --- a/core/src/main/java/org/mapfish/print/processor/ExecutionStats.java +++ b/core/src/main/java/org/mapfish/print/processor/ExecutionStats.java @@ -13,9 +13,9 @@ /** Statisctics about the execution of a print job. */ public class ExecutionStats { - private List mapStats = new ArrayList<>(); - private List pageStats = new ArrayList<>(); - private List emailDests = new ArrayList<>(); + private final List mapStats = new ArrayList<>(); + private final List pageStats = new ArrayList<>(); + private final List emailDests = new ArrayList<>(); private boolean storageUsed = false; /** diff --git a/core/src/main/java/org/mapfish/print/processor/ProcessorGraphNode.java b/core/src/main/java/org/mapfish/print/processor/ProcessorGraphNode.java index 65ef19adb4..2600d77692 100644 --- a/core/src/main/java/org/mapfish/print/processor/ProcessorGraphNode.java +++ b/core/src/main/java/org/mapfish/print/processor/ProcessorGraphNode.java @@ -190,19 +190,25 @@ protected Values compute() { final In inputParameter = ProcessorUtils.populateInputParameter(process, values); Out output; + boolean isThrowingException = false; try { LOGGER.debug("Executing process: {}", process); output = process.execute(inputParameter, this.execContext.getContext()); LOGGER.debug("Succeeded in executing process: {}", process); } catch (RuntimeException e) { + isThrowingException = true; + LOGGER.info("Error while executing process: {}", process, e); throw e; } catch (Exception e) { + isThrowingException = true; + LOGGER.info("Error while executing process: {}", process, e); throw new PrintException("Failed to execute process:" + process, e); } finally { - // the processor is already canceled, so we don't care if something fails - this.execContext.getContext().stopIfCanceled(); - LOGGER.info("Error while executing process: {}", process); - registry.counter(name + ".error").inc(); + if (isThrowingException) { + // the processor is already canceled, so we don't care if something fails + this.execContext.getContext().stopIfCanceled(); + registry.counter(name + ".error").inc(); + } } if (output != null) { diff --git a/core/src/main/java/org/mapfish/print/processor/map/CreateMapProcessor.java b/core/src/main/java/org/mapfish/print/processor/map/CreateMapProcessor.java index 8e2fe35034..3bb4c8d97a 100644 --- a/core/src/main/java/org/mapfish/print/processor/map/CreateMapProcessor.java +++ b/core/src/main/java/org/mapfish/print/processor/map/CreateMapProcessor.java @@ -741,7 +741,7 @@ && getSupportedRenderType(layers.get(i).getRenderType()) == renderType && imageBufferScaling == layers.get(i).getImageBufferScaling()) { // will always go there the first time l = layers.get(i); - LOGGER.debug("Adding layer {} to the group", l.getName()); + LOGGER.debug("Adding layer: {} named: {} to the group", i, l.getName()); group.layers.add(l); group.opaque = group.opaque || (renderType == RenderType.JPEG && l.getOpacity() == 1.0); ++i; diff --git a/core/src/main/java/org/mapfish/print/processor/map/NorthArrowGraphic.java b/core/src/main/java/org/mapfish/print/processor/map/NorthArrowGraphic.java index 57c52fd12c..147e111327 100644 --- a/core/src/main/java/org/mapfish/print/processor/map/NorthArrowGraphic.java +++ b/core/src/main/java/org/mapfish/print/processor/map/NorthArrowGraphic.java @@ -8,21 +8,22 @@ import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.io.BufferedInputStream; +import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; +import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import javax.imageio.ImageIO; import org.apache.batik.anim.dom.SAXSVGDocumentFactory; import org.apache.batik.anim.dom.SVGDOMImplementation; import org.apache.batik.dom.util.DOMUtilities; import org.apache.batik.util.SVGConstants; import org.apache.batik.util.XMLResourceDescriptor; -import org.apache.commons.io.output.FileWriterWithEncoding; import org.apache.commons.lang3.StringUtils; import org.mapfish.print.FloatingPointUtil; import org.mapfish.print.ImageUtils; @@ -341,10 +342,8 @@ private static SVGElement parseSvg(final InputStream inputStream) throws IOExcep private static File writeSvgToFile(final Document document, final File workingDir) throws IOException { final File path = File.createTempFile("north-arrow-", ".svg", workingDir); - try (FileWriterWithEncoding fw = - new FileWriterWithEncoding(path, Charset.forName("UTF-8").newEncoder())) { + try (BufferedWriter fw = new BufferedWriter(new FileWriter(path, StandardCharsets.UTF_8))) { DOMUtilities.writeDocument(document, fw); - fw.flush(); } return path; } diff --git a/core/src/main/java/org/mapfish/print/servlet/BaseMapServlet.java b/core/src/main/java/org/mapfish/print/servlet/BaseMapServlet.java index a56e1ce55d..1675be4b09 100644 --- a/core/src/main/java/org/mapfish/print/servlet/BaseMapServlet.java +++ b/core/src/main/java/org/mapfish/print/servlet/BaseMapServlet.java @@ -2,9 +2,6 @@ import java.io.IOException; import java.io.PrintWriter; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Date; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.mapfish.print.PrintException; @@ -17,38 +14,6 @@ public abstract class BaseMapServlet { private static final Logger LOGGER = LoggerFactory.getLogger(BaseMapServlet.class); private int cacheDurationInSeconds = 3600; - /** - * Remove commas and whitespace from a string. - * - * @param original the starting string. - */ - protected static String cleanUpName(final String original) { - return original.replace(",", "").replaceAll("\\s+", "_"); - } - - /** - * Update a variable name with a date if the variable is detected as being a date. - * - * @param variableName the variable name. - * @param date the date to replace the value with if the variable is a date variable. - */ - public static String findReplacement(final String variableName, final Date date) { - if (variableName.equalsIgnoreCase("date")) { - return cleanUpName(DateFormat.getDateInstance().format(date)); - } else if (variableName.equalsIgnoreCase("datetime")) { - return cleanUpName(DateFormat.getDateTimeInstance().format(date)); - } else if (variableName.equalsIgnoreCase("time")) { - return cleanUpName(DateFormat.getTimeInstance().format(date)); - } else { - try { - return new SimpleDateFormat(variableName).format(date); - } catch (Exception e) { - LOGGER.error("Unable to format timestamp according to pattern: {}", variableName, e); - return "${" + variableName + "}"; - } - } - } - /** * Send an error to the client with a message. * @@ -82,23 +47,6 @@ protected static void setNoCache(final HttpServletResponse response) { response.setHeader("Cache-Control", "max-age=0, must-revalidate, no-cache, no-store"); } - /** - * Send an error to the client with an exception. - * - * @param httpServletResponse the http response to send the error to - * @param e the error that occurred - */ - protected final void error(final HttpServletResponse httpServletResponse, final Throwable e) { - httpServletResponse.setContentType("text/plain"); - httpServletResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); - try (PrintWriter out = httpServletResponse.getWriter()) { - out.println("Error while processing request:"); - LOGGER.warn("Error while processing request", e); - } catch (IOException ex) { - throw new PrintException("", e); - } - } - /** * Returns the base URL of the print servlet. * diff --git a/core/src/main/java/org/mapfish/print/servlet/BooleanHandleReportLoadResult.java b/core/src/main/java/org/mapfish/print/servlet/BooleanHandleReportLoadResult.java new file mode 100644 index 0000000000..3007ae8666 --- /dev/null +++ b/core/src/main/java/org/mapfish/print/servlet/BooleanHandleReportLoadResult.java @@ -0,0 +1,59 @@ +package org.mapfish.print.servlet; + +import java.io.IOException; +import java.net.URI; +import javax.servlet.http.HttpServletResponse; +import org.mapfish.print.servlet.job.PrintJobStatus; +import org.mapfish.print.servlet.job.loader.ReportLoader; +import org.springframework.http.HttpStatus; + +class BooleanHandleReportLoadResult extends HandleReportLoadResult { + private final boolean isInlining; + + BooleanHandleReportLoadResult(final boolean inline) { + isInlining = inline; + } + + @Override + public Boolean unknownReference( + final HttpServletResponse httpServletResponse, final String referenceId) { + BaseMapServlet.error( + httpServletResponse, "Print with ref=" + referenceId + " unknown", HttpStatus.NOT_FOUND); + return true; + } + + @Override + public Boolean unsupportedLoader( + final HttpServletResponse httpServletResponse, final String referenceId) { + BaseMapServlet.error( + httpServletResponse, + "Print with ref=" + referenceId + " can not be loaded", + HttpStatus.NOT_FOUND); + return true; + } + + @Override + public Boolean successfulPrint( + final PrintJobStatus successfulPrintResult, + final HttpServletResponse httpServletResponse, + final URI reportURI, + final ReportLoader loader) + throws IOException { + sendReportFile(successfulPrintResult, httpServletResponse, loader, reportURI, isInlining); + return true; + } + + @Override + public Boolean failedPrint( + final PrintJobStatus failedPrintJob, final HttpServletResponse httpServletResponse) { + BaseMapServlet.error( + httpServletResponse, failedPrintJob.getError(), HttpStatus.INTERNAL_SERVER_ERROR); + return true; + } + + @Override + public Boolean printJobPending( + final HttpServletResponse httpServletResponse, final String referenceId) { + return false; + } +} diff --git a/core/src/main/java/org/mapfish/print/servlet/HandleReportLoadResult.java b/core/src/main/java/org/mapfish/print/servlet/HandleReportLoadResult.java index ad94397efc..c6d4789701 100644 --- a/core/src/main/java/org/mapfish/print/servlet/HandleReportLoadResult.java +++ b/core/src/main/java/org/mapfish/print/servlet/HandleReportLoadResult.java @@ -1,18 +1,30 @@ package org.mapfish.print.servlet; import java.io.IOException; +import java.io.OutputStream; import java.net.URI; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.servlet.ServletException; import javax.servlet.http.HttpServletResponse; import org.mapfish.print.servlet.job.PrintJobStatus; import org.mapfish.print.servlet.job.loader.ReportLoader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Called when a report is loaded to be sent to the user. * * @param The return value */ -public interface HandleReportLoadResult { +abstract class HandleReportLoadResult { + private static final int FILENAME_MAX_LENGTH = 1000; + private static final Pattern VARIABLE_PATTERN = + Pattern.compile("\\$\\{(\\S{1," + FILENAME_MAX_LENGTH + "})}"); + private static final Logger LOGGER = LoggerFactory.getLogger(HandleReportLoadResult.class); /** * Called if the report reference is unknown. @@ -20,7 +32,7 @@ public interface HandleReportLoadResult { * @param httpServletResponse response object * @param referenceId report id */ - R unknownReference(HttpServletResponse httpServletResponse, String referenceId); + abstract R unknownReference(HttpServletResponse httpServletResponse, String referenceId); /** * Called if no loader can be found for loading the report. @@ -28,7 +40,7 @@ public interface HandleReportLoadResult { * @param httpServletResponse response object * @param referenceId report id */ - R unsupportedLoader(HttpServletResponse httpServletResponse, String referenceId); + abstract R unsupportedLoader(HttpServletResponse httpServletResponse, String referenceId); /** * Called when a print succeeded. @@ -38,7 +50,7 @@ public interface HandleReportLoadResult { * @param reportURI the uri to the report * @param loader the loader for loading the report. */ - R successfulPrint( + abstract R successfulPrint( PrintJobStatus successfulPrintResult, HttpServletResponse httpServletResponse, URI reportURI, @@ -51,7 +63,7 @@ R successfulPrint( * @param failedPrintJob the failed print job * @param httpServletResponse the object for writing response */ - R failedPrint(PrintJobStatus failedPrintJob, HttpServletResponse httpServletResponse); + abstract R failedPrint(PrintJobStatus failedPrintJob, HttpServletResponse httpServletResponse); /** * Called when the print job has not yet completed. @@ -59,5 +71,81 @@ R successfulPrint( * @param httpServletResponse the object for writing response * @param referenceId report id */ - R printJobPending(HttpServletResponse httpServletResponse, String referenceId); + abstract R printJobPending(HttpServletResponse httpServletResponse, String referenceId); + + /** + * 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 to inline the content + */ + protected final void sendReportFile( + final PrintJobStatus metadata, + final HttpServletResponse httpServletResponse, + final ReportLoader reportLoader, + final URI reportURI, + final boolean inline) + throws IOException { + + try (OutputStream response = httpServletResponse.getOutputStream()) { + httpServletResponse.setContentType(metadata.getResult().getMimeType()); + if (!inline) { + String fileName = metadata.getResult().getFileName(); + Matcher matcher = getFileNameMatcher(fileName); + while (matcher.find()) { + final String variable = matcher.group(1); + String replacement = findReplacement(variable, metadata.getCompletionDate()); + fileName = fileName.replace("${" + variable + "}", replacement); + matcher = getFileNameMatcher(fileName); + } + + fileName += "." + metadata.getResult().getFileExtension(); + httpServletResponse.setHeader( + "Content-disposition", "attachment; filename=" + cleanUpName(fileName)); + } + reportLoader.loadReport(reportURI, response); + } + } + + private static Matcher getFileNameMatcher(final String fileName) { + if (fileName.length() > FILENAME_MAX_LENGTH) { + throw new IllegalArgumentException("File name is too long"); + } + return VARIABLE_PATTERN.matcher(fileName); + } + + /** + * Update a variable name with a date if the variable is detected as being a date. + * + * @param variableName the variable name. + * @param date the date to replace the value with if the variable is a date variable. + */ + private String findReplacement(final String variableName, final Date date) { + if (variableName.equalsIgnoreCase("date")) { + return cleanUpName(DateFormat.getDateInstance().format(date)); + } else if (variableName.equalsIgnoreCase("datetime")) { + return cleanUpName(DateFormat.getDateTimeInstance().format(date)); + } else if (variableName.equalsIgnoreCase("time")) { + return cleanUpName(DateFormat.getTimeInstance().format(date)); + } else { + try { + return new SimpleDateFormat(variableName).format(date); + } catch (RuntimeException e) { + LOGGER.error("Unable to format timestamp according to pattern: {}", variableName, e); + return "${" + variableName + "}"; + } + } + } + + /** + * Remove commas and whitespace from a string. + * + * @param original the starting string. + */ + private String cleanUpName(final String original) { + return original.replace(",", "").replaceAll("\\s+", "_").replaceAll("[^a-zA-Z0-9-_.:+]", "_"); + } } diff --git a/core/src/main/java/org/mapfish/print/servlet/MapPrinterServlet.java b/core/src/main/java/org/mapfish/print/servlet/MapPrinterServlet.java index 920b163b79..e2a6d31844 100644 --- a/core/src/main/java/org/mapfish/print/servlet/MapPrinterServlet.java +++ b/core/src/main/java/org/mapfish/print/servlet/MapPrinterServlet.java @@ -6,7 +6,6 @@ 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; @@ -21,12 +20,11 @@ import java.util.Arrays; import java.util.Enumeration; import java.util.List; +import java.util.Objects; import java.util.Optional; 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.annotation.Nonnull; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -204,7 +202,6 @@ public class MapPrinterServlet extends BaseMapServlet { public static final String JSON_OUTPUT_FONTCONFIG_WEIGHT = "weight"; private static final Logger LOGGER = LoggerFactory.getLogger(MapPrinterServlet.class); - private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\$\\{(\\S+)}"); private static final int JSON_INDENT_FACTOR = 4; private static final List REQUEST_ID_HEADERS = Arrays.asList( @@ -250,18 +247,17 @@ public MapPrinterServlet( LOGGER.info( "Sentry event, logger: {}, message: {}", event.getLogger(), - event.getMessage().getMessage()); - if (event.getLogger().equals("org.hibernate.engine.jdbc.spi.SqlExceptionHelper") - && (event - .getMessage() - .getMessage() - .equals( + event.getMessage() != null ? event.getMessage().getMessage() : null); + if (Objects.equals( + event.getLogger(), "org.hibernate.engine.jdbc.spi.SqlExceptionHelper") + && ((event.getMessage() != null) + && (Objects.equals( + event.getMessage().getMessage(), "ERROR: could not obtain lock on row in relation" + " \"print_job_statuses\"") - || event - .getMessage() - .getMessage() - .equals("SQL Error: 0, SQLState: 55P03"))) { + || Objects.equals( + event.getMessage().getMessage(), + "SQL Error: 0, SQLState: 55P03")))) { return null; } return event; @@ -532,7 +528,7 @@ public final void createReport( * * @param appId the app ID * @param referenceId the path to the file. - * @param inline whether or not to inline the + * @param inline whether to inline the * @param getReportResponse the response object */ @RequestMapping( @@ -551,7 +547,7 @@ public final void getReportSpecificAppId( * To get the PDF created previously. * * @param referenceId the job reference - * @param inline whether or not to inline the + * @param inline whether to inline the * @param getReportResponse the response object */ @RequestMapping(value = REPORT_URL + "/{referenceId:\\S+}", method = RequestMethod.GET) @@ -568,7 +564,7 @@ public final void getReportPath( * * @param applicationId the application ID * @param referenceId the job reference - * @param inline whether or not to inline the + * @param inline whether to inline the * @param getReportResponse the response object */ public final void getReport( @@ -580,59 +576,7 @@ public final void getReport( MDC.put(Processor.MDC_APPLICATION_ID_KEY, applicationId); MDC.put(Processor.MDC_JOB_ID_KEY, referenceId); setNoCache(getReportResponse); - loadReport( - referenceId, - getReportResponse, - new HandleReportLoadResult() { - - @Override - public Void unknownReference( - final HttpServletResponse httpServletResponse, final String referenceId) { - error( - httpServletResponse, - "Error getting print with ref=" + referenceId + ": unknown reference", - HttpStatus.NOT_FOUND); - return null; - } - - @Override - public Void unsupportedLoader( - final HttpServletResponse httpServletResponse, final String referenceId) { - error( - httpServletResponse, - "Error getting print with ref=" + referenceId + " can not be loaded", - HttpStatus.NOT_FOUND); - return null; - } - - @Override - public Void successfulPrint( - final PrintJobStatus successfulPrintResult, - final HttpServletResponse httpServletResponse, - final URI reportURI, - final ReportLoader loader) - throws IOException { - sendReportFile(successfulPrintResult, httpServletResponse, loader, reportURI, inline); - return null; - } - - @Override - public Void failedPrint( - final PrintJobStatus 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; - } - }); + loadReport(referenceId, getReportResponse, new VoidHandleReportLoadResult(inline)); } /** @@ -670,7 +614,7 @@ public final void createReport( * @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 inline whether to inline the content * @param createReportRequest the request object * @param createReportResponse the response object */ @@ -695,58 +639,11 @@ public final void createReportAndGet( return; } - final HandleReportLoadResult handler = - new HandleReportLoadResult() { - - @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 PrintJobStatus successfulPrintResult, - final HttpServletResponse httpServletResponse, - final URI reportURI, - final ReportLoader loader) - throws IOException { - sendReportFile(successfulPrintResult, httpServletResponse, loader, reportURI, inline); - return true; - } - - @Override - public Boolean failedPrint( - final PrintJobStatus 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; - } - }; - + final BooleanHandleReportLoadResult handler = new BooleanHandleReportLoadResult(inline); boolean isDone = false; - long startWaitTime = System.currentTimeMillis(); final long maxWaitTimeInMillis = TimeUnit.SECONDS.toMillis(this.maxCreateAndGetWaitTimeInSeconds); + long startWaitTime = System.currentTimeMillis(); while (!isDone && System.currentTimeMillis() - startWaitTime < maxWaitTimeInMillis) { Thread.sleep(TimeUnit.SECONDS.toMillis(1)); isDone = loadReport(ref, createReportResponse, handler); @@ -759,7 +656,7 @@ public Boolean printJobPending( * @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 inline whether to inline the content * @param createReportRequest the request object * @param createReportResponse the response object */ @@ -891,7 +788,7 @@ public final void getCapabilities( if (pretty) { final JSONObject jsonObject = - new JSONObject(new String(prettyPrintBuffer.toByteArray(), Constants.DEFAULT_CHARSET)); + new JSONObject(prettyPrintBuffer.toString(Constants.DEFAULT_CHARSET)); capabilitiesResponse.getOutputStream().print(jsonObject.toString(JSON_INDENT_FACTOR)); } } @@ -948,8 +845,7 @@ public final void getExampleRequest( for (File child : children) { if (child.isFile()) { - String requestData = - new String(Files.readAllBytes(child.toPath()), Constants.DEFAULT_CHARSET); + String requestData = Files.readString(child.toPath(), Constants.DEFAULT_CHARSET); try { final JSONObject jsonObject = new JSONObject(requestData); jsonObject.remove(JSON_OUTPUT_FORMAT); @@ -1082,43 +978,6 @@ public final void setMaxCreateAndGetWaitTimeInSeconds( 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 - */ - private void sendReportFile( - final PrintJobStatus metadata, - final HttpServletResponse httpServletResponse, - final ReportLoader reportLoader, - final URI reportURI, - final boolean inline) - throws IOException { - - try (OutputStream response = httpServletResponse.getOutputStream()) { - httpServletResponse.setContentType(metadata.getResult().getMimeType()); - if (!inline) { - String fileName = metadata.getResult().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.getResult().getFileExtension(); - httpServletResponse.setHeader( - "Content-disposition", "attachment; filename=" + cleanUpName(fileName)); - } - reportLoader.loadReport(reportURI, response); - } - } - private void addDownloadLinkToJson( final HttpServletRequest httpServletRequest, final String ref, final JSONWriter json) { String downloadURL = getBaseUrl(httpServletRequest) + REPORT_URL + "/" + ref; @@ -1168,27 +1027,27 @@ private String createAndSubmitPrintJob( if (specJson == null) { return null; } - String ref = + final String ref = maybeAddRequestId( - UUID.randomUUID().toString() + "@" + this.servletInfo.getServletId(), - httpServletRequest); + UUID.randomUUID() + "@" + this.servletInfo.getServletId(), httpServletRequest); MDC.put(Processor.MDC_APPLICATION_ID_KEY, appId); MDC.put(Processor.MDC_JOB_ID_KEY, ref); - LOGGER.debug("{}", specJson); + LOGGER.debug("Created Ref:{} for {}", ref, specJson); 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) { + if (!requestHeaders.isEmpty()) { specJson .getInternalObj() .getJSONObject(JSON_ATTRIBUTES) .put(JSON_REQUEST_HEADERS, requestHeaders); } - // check that we have authorization and configure the job so it can only be access by users with + // check that we have authorization and configure the job, so it can only be accessed by users + // with // sufficient authorization final String templateName = specJson.getString(Constants.JSON_LAYOUT_KEY); final MapPrinter mapPrinter = this.mapPrinterFactory.create(appId); @@ -1205,7 +1064,7 @@ private String createAndSubmitPrintJob( this.jobManager.submit(jobEntry); } catch (RuntimeException exc) { LOGGER.error("Error when creating job on {}: {}", appId, specJson, exc); - ref = null; + return null; } return ref; } diff --git a/core/src/main/java/org/mapfish/print/servlet/ServletMapPrinterFactory.java b/core/src/main/java/org/mapfish/print/servlet/ServletMapPrinterFactory.java index 5a7e241df2..80ab86f0b0 100644 --- a/core/src/main/java/org/mapfish/print/servlet/ServletMapPrinterFactory.java +++ b/core/src/main/java/org/mapfish/print/servlet/ServletMapPrinterFactory.java @@ -1,11 +1,12 @@ package org.mapfish.print.servlet; +import static java.nio.file.Files.walk; + import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Collection; +import java.nio.file.Path; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -13,10 +14,11 @@ import java.util.NoSuchElementException; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.PostConstruct; -import org.apache.commons.io.DirectoryWalker; import org.apache.commons.lang3.StringUtils; import org.locationtech.jts.util.Assert; import org.mapfish.print.MapPrinter; @@ -199,8 +201,7 @@ public final void setAppsRootDirectory(final String directory) } } - final AppWalker walker = new AppWalker(); - for (File child : walker.getAppDirs(realRoot)) { + for (File child : getAppDirs(realRoot)) { final File configFile = new File(child, CONFIG_YAML); String appName = realRoot.toURI().relativize(child.toURI()).getPath().replace('/', ':'); if (appName.endsWith(":")) { @@ -218,6 +219,14 @@ public final void setAppsRootDirectory(final String directory) } } + private List getAppDirs(final File base) throws IOException { + try (Stream walk = walk(base.toPath())) { + return walk.map(Path::toFile) + .filter(file -> file.isDirectory() && new File(file, CONFIG_YAML).exists()) + .collect(Collectors.toList()); + } + } + private void pickDefaultApp() { final Iterator> iterator = this.configurationFiles.entrySet().iterator(); if (iterator.hasNext()) { @@ -262,22 +271,4 @@ private URI checkForAddedApp(@Nonnull final String app) { } return null; } - - private static class AppWalker extends DirectoryWalker { - public List getAppDirs(final File base) throws IOException { - List results = new ArrayList<>(); - walk(base, results); - return results; - } - - @Override - protected boolean handleDirectory( - final File directory, final int depth, final Collection results) { - final File configFile = new File(directory, CONFIG_YAML); - if (configFile.exists()) { - results.add(directory); - } - return depth < MAX_DEPTH; - } - } } diff --git a/core/src/main/java/org/mapfish/print/servlet/VoidHandleReportLoadResult.java b/core/src/main/java/org/mapfish/print/servlet/VoidHandleReportLoadResult.java new file mode 100644 index 0000000000..bba96e3c99 --- /dev/null +++ b/core/src/main/java/org/mapfish/print/servlet/VoidHandleReportLoadResult.java @@ -0,0 +1,64 @@ +package org.mapfish.print.servlet; + +import java.io.IOException; +import java.net.URI; +import javax.servlet.http.HttpServletResponse; +import org.mapfish.print.servlet.job.PrintJobStatus; +import org.mapfish.print.servlet.job.loader.ReportLoader; +import org.springframework.http.HttpStatus; + +class VoidHandleReportLoadResult extends HandleReportLoadResult { + + private final boolean isInlining; + + VoidHandleReportLoadResult(final boolean inline) { + this.isInlining = inline; + } + + @Override + public Void unknownReference( + final HttpServletResponse httpServletResponse, final String referenceId) { + BaseMapServlet.error( + httpServletResponse, + "Error getting print with ref=" + referenceId + ": unknown reference", + HttpStatus.NOT_FOUND); + return null; + } + + @Override + public Void unsupportedLoader( + final HttpServletResponse httpServletResponse, final String referenceId) { + BaseMapServlet.error( + httpServletResponse, + "Error getting print with ref=" + referenceId + " can not be loaded", + HttpStatus.NOT_FOUND); + return null; + } + + @Override + public Void successfulPrint( + final PrintJobStatus successfulPrintResult, + final HttpServletResponse httpServletResponse, + final URI reportURI, + final ReportLoader loader) + throws IOException { + sendReportFile(successfulPrintResult, httpServletResponse, loader, reportURI, isInlining); + return null; + } + + @Override + public Void failedPrint( + final PrintJobStatus failedPrintJob, final HttpServletResponse httpServletResponse) { + BaseMapServlet.error( + httpServletResponse, failedPrintJob.getError(), HttpStatus.INTERNAL_SERVER_ERROR); + return null; + } + + @Override + public Void printJobPending( + final HttpServletResponse httpServletResponse, final String referenceId) { + BaseMapServlet.error( + httpServletResponse, "Report has not yet completed processing", HttpStatus.ACCEPTED); + return null; + } +} diff --git a/core/src/main/resources/mapfish-spring-application-context.xml b/core/src/main/resources/mapfish-spring-application-context.xml index 7b69183cb7..ff4ced5242 100644 --- a/core/src/main/resources/mapfish-spring-application-context.xml +++ b/core/src/main/resources/mapfish-spring-application-context.xml @@ -42,7 +42,7 @@ - + diff --git a/core/src/main/webapp/index.html b/core/src/main/webapp/index.html index 5c0e62e2f0..22cddb3aff 100644 --- a/core/src/main/webapp/index.html +++ b/core/src/main/webapp/index.html @@ -54,7 +54,7 @@ $.ajax({ type: 'GET', url: 'print/' + appId + '/exampleRequest.json', - dataType: 'json', + dataType: 'Json', success: function (data) { var setExample = true; requestExamples = data; @@ -111,13 +111,13 @@ url: 'print/' + appId + '/report.' + format, data: data, success: function (data) { - downloadWhenReady(startTime, $.parseJSON(data)); + downloadWhenReady(startTime, data); }, error: function (data) { $('#messages').text('Error creating report: ' + data.statusText); disableUI(false); }, - dataType: 'application/json', + dataType: 'Json', }); } @@ -142,7 +142,7 @@ $('#messages').text('Error creating report: ' + data.statusText); disableUI(false); }, - dataType: 'application/json', + dataType: 'Json', }); } diff --git a/docs/build.gradle b/docs/build.gradle index 3ccc8adb93..7d3349a29f 100644 --- a/docs/build.gradle +++ b/docs/build.gradle @@ -29,7 +29,7 @@ dependencies { ) } -def site = new File(buildDir, "site").path +def site = new File(getLayout().getBuildDirectory().getAsFile().get(), "site").path tasks.register('copyJavadocs', Copy) { dependsOn ':core:javadoc' @@ -59,11 +59,10 @@ tasks.register('buildDocs', JavaExec) { def javadocs = project(':core').tasks.javadoc.destinationDir def basicAppContext = 'classpath*:mapfish-spring-application-context.xml' - def appContext = project(':core').projectDir.toURI().toString() + '/jettyRunExtraFiles/*.xml' + def appContext = project(':core').projectDir.toURI().toString() + '/extraConfigFor/docs/*.xml' def appContext2 = project(':core').webAppDir.toURI().toString() + '/WEB-INF/mapfish-print-servlet.xml' def overrideAppContext = 'classpath*:mapfish-spring-application-context-override.xml' args site, javadocs, basicAppContext, appContext, appContext2, overrideAppContext - systemProperty 'path_to_examples', "${project(':examples').projectDir}/src/test/resources/examples" } build {