From 364bd4ee4cee8bd2347c020c9264e19a8fca2b9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Berg=20Glasius?= Date: Tue, 30 Sep 2025 10:17:58 +0200 Subject: [PATCH 01/12] Added @CompileStatic to services, and enhanced typing as a result --- .../plugins/rendering/Application.groovy | 2 + .../plugins/rendering/RenderingService.groovy | 27 +- .../image/GifRenderingService.groovy | 7 +- .../image/ImageRenderingService.groovy | 287 +++++++++--------- .../image/JpegRenderingService.groovy | 9 +- .../image/PngRenderingService.groovy | 7 +- .../rendering/pdf/PdfRenderingService.groovy | 9 +- .../rendering/RenderingGrailsPlugin.groovy | 14 +- .../document/RenderEnvironment.groovy | 13 +- 9 files changed, 198 insertions(+), 177 deletions(-) diff --git a/grails-app/init/grails/plugins/rendering/Application.groovy b/grails-app/init/grails/plugins/rendering/Application.groovy index 311a082..8e55c76 100644 --- a/grails-app/init/grails/plugins/rendering/Application.groovy +++ b/grails-app/init/grails/plugins/rendering/Application.groovy @@ -2,7 +2,9 @@ package grails.plugins.rendering import grails.boot.GrailsApp import grails.boot.config.GrailsAutoConfiguration +import grails.plugins.metadata.PluginSource +@PluginSource class Application extends GrailsAutoConfiguration { static void main(String[] args) { GrailsApp.run(Application, args) diff --git a/grails-app/services/grails/plugins/rendering/RenderingService.groovy b/grails-app/services/grails/plugins/rendering/RenderingService.groovy index f3d324c..9b8eaf8 100644 --- a/grails-app/services/grails/plugins/rendering/RenderingService.groovy +++ b/grails-app/services/grails/plugins/rendering/RenderingService.groovy @@ -15,25 +15,28 @@ */ package grails.plugins.rendering +import grails.core.GrailsApplication +import grails.plugins.rendering.document.XhtmlDocumentService import grails.util.GrailsUtil - +import groovy.transform.CompileStatic import jakarta.servlet.http.HttpServletResponse import org.w3c.dom.Document +@CompileStatic abstract class RenderingService { static transactional = false - def xhtmlDocumentService - def grailsApplication + XhtmlDocumentService xhtmlDocumentService + GrailsApplication grailsApplication protected abstract doRender(Map args, Document document, OutputStream outputStream) - protected abstract getDefaultContentType() + protected abstract String getDefaultContentType() OutputStream render(Map args, OutputStream outputStream = new ByteArrayOutputStream()) { - def document = args.document ?: xhtmlDocumentService.createDocument(args) + Document document = args.document as Document ?: xhtmlDocumentService.createDocument(args) render(args, document, outputStream) } @@ -54,15 +57,15 @@ abstract class RenderingService { boolean render(Map args, HttpServletResponse response) { processArgs(args) if (args.bytes) { - writeToResponse(args, response, args.bytes) + writeToResponse(args, response, args.bytes as byte[]) } else if (args.input) { - writeToResponse(args, response, args.input) + writeToResponse(args, response, args.input as InputStream) } else { if (args.stream) { configureResponse(args, response) render(args, response.outputStream) } else { - writeToResponse(args, response, render(args).toByteArray()) + writeToResponse(args, response, (render(args) as ByteArrayOutputStream).toByteArray()) } } false @@ -70,8 +73,8 @@ abstract class RenderingService { protected writeToResponse(Map args, HttpServletResponse response, InputStream input) { configureResponse(args, response) - if (args.contentLength > 0) { - response.setContentLength(args.contentLength) + if ((args.contentLength as int) > 0) { + response.setContentLength(args.contentLength as int) } response.outputStream << input } @@ -92,7 +95,7 @@ abstract class RenderingService { } protected setContentType(Map args, HttpServletResponse response) { - response.setContentType(args.contentType ?: getDefaultContentType()) + response.setContentType(args.contentType as String ?: getDefaultContentType()) } protected setContentDisposition(Map args, HttpServletResponse response) { @@ -103,7 +106,7 @@ abstract class RenderingService { protected processArgs(Map args) { if (!args.base) { - args.base = grailsApplication.config.grails.serverURL ?: null + args.base = grailsApplication.config.get('grails.serverURL') ?: null } } } diff --git a/grails-app/services/grails/plugins/rendering/image/GifRenderingService.groovy b/grails-app/services/grails/plugins/rendering/image/GifRenderingService.groovy index 29ac45b..9c339a5 100644 --- a/grails-app/services/grails/plugins/rendering/image/GifRenderingService.groovy +++ b/grails-app/services/grails/plugins/rendering/image/GifRenderingService.groovy @@ -15,13 +15,16 @@ */ package grails.plugins.rendering.image +import groovy.transform.CompileStatic + +@CompileStatic class GifRenderingService extends ImageRenderingService { - protected getImageType() { + protected String getImageType() { "gif" } - protected getDefaultContentType() { + protected String getDefaultContentType() { "image/gif" } } diff --git a/grails-app/services/grails/plugins/rendering/image/ImageRenderingService.groovy b/grails-app/services/grails/plugins/rendering/image/ImageRenderingService.groovy index 03cba5b..61648f2 100644 --- a/grails-app/services/grails/plugins/rendering/image/ImageRenderingService.groovy +++ b/grails-app/services/grails/plugins/rendering/image/ImageRenderingService.groovy @@ -15,154 +15,157 @@ */ package grails.plugins.rendering.image + import grails.plugins.rendering.RenderingService import grails.plugins.rendering.datauri.DataUriAwareNaiveUserAgent +import groovy.transform.CompileDynamic +import groovy.transform.CompileStatic +import org.w3c.dom.Document +import org.xhtmlrenderer.simple.Graphics2DRenderer -import java.awt.Dimension -import java.awt.RenderingHints +import javax.imageio.ImageIO +import java.awt.* import java.awt.geom.AffineTransform import java.awt.image.BufferedImage +import java.awt.image.RenderedImage -import javax.imageio.ImageIO - -import org.w3c.dom.Document -import org.xhtmlrenderer.simple.Graphics2DRenderer - +@CompileStatic abstract class ImageRenderingService extends RenderingService { - static transactional = false - - static DEFAULT_BUFFERED_IMAGE_TYPE = BufferedImage.TYPE_INT_ARGB - - protected abstract getImageType() - - protected configureRenderer(Graphics2DRenderer renderer) { - renderer.sharedContext.userAgentCallback = new DataUriAwareNaiveUserAgent() - } - - protected doRender(Map args, Document document, OutputStream outputStream) { - convert(args, createBufferedImage(args, document), outputStream) - } - - protected convert(Map args, BufferedImage image, OutputStream outputStream) { - def imageType = getImageType() - if (!ImageIO.write(image, imageType, outputStream)) { - throw new IllegalArgumentException("ImageIO.write() failed to find writer for type '$type'") - } - } - - protected getDefaultBufferedImageType() { - DEFAULT_BUFFERED_IMAGE_TYPE - } - - protected BufferedImage createBufferedImage(Map args, Document document) { - def bufferedImageType = args.bufferedImageType ?: getDefaultBufferedImageType() - - int renderWidth = args.render?.width?.toInteger() ?: 10 - Integer renderHeight = args.render?.height?.toInteger() - - boolean autosizeWidth = args.autosize?.width == null || args.autosize?.width == true - boolean autosizeHeight = args.autosize?.height == null || args.autosize?.height == true - - def renderer = new Graphics2DRenderer() - configureRenderer(renderer) - renderer.setDocument(document, args.base) - - int imageWidth = renderWidth - Integer imageHeight = renderHeight - boolean needsLayout = true - - if (!renderHeight || autosizeWidth || autosizeHeight) { - def tempRenderHeight = renderHeight ?: 10000 - def dim = new Dimension(renderWidth, tempRenderHeight) - - // do layout with temp buffer to calculate height - def tempImage = new BufferedImage(dim.width.intValue(), dim.height.intValue(), bufferedImageType) - def tempGraphics = tempImage.graphics - renderer.layout(tempGraphics, dim) - needsLayout = false - tempGraphics.dispose() - - if (autosizeWidth) { - imageWidth = renderer.minimumSize.width.intValue() - } - if (!renderHeight || autosizeHeight) { - imageHeight = renderer.minimumSize.height.intValue() - } - } - - def image = new BufferedImage(imageWidth, imageHeight, bufferedImageType) - def graphics = image.graphics - if (needsLayout) { - renderer.layout(graphics, new Dimension(imageWidth, imageHeight)) - } - renderer.render(graphics) - graphics.dispose() - - if (args.scale) { - scale(image, args.scale, bufferedImageType) - } else if (args.resize) { - resize(image, args.resize, bufferedImageType) - } else { - image - } - } - - protected scale(image, Map scaleArgs, bufferedImageType) { - Integer width = scaleArgs.width?.toInteger() - Integer height = scaleArgs.height?.toInteger() - - if (width && height) { - scale(image, width, height, bufferedImageType) - } else if (width && !height) { - scale(image, width, width, bufferedImageType) - } else if (!width && height) { - scale(image, height, height, bufferedImageType) - } else { - throw new IllegalStateException("Unhandled scale height/width combination") - } - } - - protected resize(image, Map resizeArgs, bufferedImageType) { - Integer width = resizeArgs.width?.toInteger() - Integer height = resizeArgs.height?.toInteger() - - if (width && height) { - resize(image, width, height, bufferedImageType) - } else if (width && !height) { - height = (image.height * (width / image.width)).toInteger() - resize(image, width, height, bufferedImageType) - } else if (!width && height) { - width = (image.width * (height / image.height)).toInteger() - resize(image, width, height, bufferedImageType) - } else { - throw new IllegalStateException("Unhandled resize height/width combination") - } - } - - protected resize(image, width, height, bufferedImageType) { - double widthScale = width / image.width - double heightScale = height / image.height - - doScaleTransform(image, width, height, widthScale, heightScale, bufferedImageType) - } - - protected scale(image, widthScale, heightScale, bufferedImageType) { - int width = image.width * widthScale - int height = image.height * heightScale - - doScaleTransform(image, width, height, widthScale, heightScale, bufferedImageType) - } - - protected doScaleTransform(image, width, height, widthScale, heightScale, bufferedImageType) { - def scaled = new BufferedImage(width, height, bufferedImageType) - - def graphics = scaled.createGraphics() - graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC) - def transform = AffineTransform.getScaleInstance(widthScale, heightScale) - graphics.drawRenderedImage(image, transform) - graphics.dispose() - - scaled - } + static int DEFAULT_BUFFERED_IMAGE_TYPE = BufferedImage.TYPE_INT_ARGB + + protected abstract String getImageType() + + protected void configureRenderer(Graphics2DRenderer renderer) { + renderer.sharedContext.userAgentCallback = new DataUriAwareNaiveUserAgent() + } + + protected doRender(Map args, Document document, OutputStream outputStream) { + convert(args, createBufferedImage(args, document), outputStream) + } + + protected convert(Map args, BufferedImage image, OutputStream outputStream) { + def imageType = getImageType() + if (!ImageIO.write(image, imageType, outputStream)) { + throw new IllegalArgumentException("ImageIO.write() failed to find writer for type '$imageType'") + } + } + + protected int getDefaultBufferedImageType() { + DEFAULT_BUFFERED_IMAGE_TYPE + } + + @CompileDynamic + protected BufferedImage createBufferedImage(Map args, Document document) { + def bufferedImageType = args.bufferedImageType as Integer ?: getDefaultBufferedImageType() + + int renderWidth = args.render?.width?.toInteger() ?: 10 + Integer renderHeight = args.render?.height?.toInteger() + + boolean autosizeWidth = args.autosize?.width == null || args.autosize?.width == true + boolean autosizeHeight = args.autosize?.height == null || args.autosize?.height == true + + def renderer = new Graphics2DRenderer() + configureRenderer(renderer) + renderer.setDocument(document, args.base as String) + + int imageWidth = renderWidth + Integer imageHeight = renderHeight + boolean needsLayout = true + + if (!renderHeight || autosizeWidth || autosizeHeight) { + def tempRenderHeight = renderHeight ?: 10000 + def dim = new Dimension(renderWidth, tempRenderHeight) + + // do layout with temp buffer to calculate height + def tempImage = new BufferedImage(dim.width.intValue(), dim.height.intValue(), bufferedImageType) + def tempGraphics = tempImage.createGraphics() + renderer.layout(tempGraphics, dim) + needsLayout = false + tempGraphics.dispose() + + if (autosizeWidth) { + imageWidth = renderer.minimumSize.width.intValue() + } + if (!renderHeight || autosizeHeight) { + imageHeight = renderer.minimumSize.height.intValue() + } + } + + def image = new BufferedImage(imageWidth, imageHeight, bufferedImageType) + def graphics = image.createGraphics() + if (needsLayout) { + renderer.layout(graphics, new Dimension(imageWidth, imageHeight)) + } + renderer.render(graphics) + graphics.dispose() + + if (args.scale) { + scale(image, args.scale as Map, bufferedImageType) + } else if (args.resize) { + resize(image, args.resize as Map, bufferedImageType) + } else { + image + } + } + + @CompileDynamic + protected BufferedImage scale(Image image, Map scaleArgs, bufferedImageType) { + Integer width = scaleArgs.width?.toInteger() + Integer height = scaleArgs.height?.toInteger() + + if (width && height) { + scale(image, width, height, bufferedImageType) + } else if (width && !height) { + scale(image, width, width, bufferedImageType) + } else if (!width && height) { + scale(image, height, height, bufferedImageType) + } else { + throw new IllegalStateException("Unhandled scale height/width combination") + } + } + + @CompileDynamic + protected BufferedImage resize(BufferedImage image, Map resizeArgs, int bufferedImageType) { + Integer width = resizeArgs.width?.toInteger() + Integer height = resizeArgs.height?.toInteger() + + if (width && height) { + resize(image, width, height, bufferedImageType) + } else if (width && !height) { + height = (image.height * (width / image.width)).toInteger() + resize(image, width, height, bufferedImageType) + } else if (!width && height) { + width = (image.width * (height / image.height)).toInteger() + resize(image, width, height, bufferedImageType) + } else { + throw new IllegalStateException("Unhandled resize height/width combination") + } + } + + protected BufferedImage resize(BufferedImage image, int width, int height, int bufferedImageType) { + double widthScale = width / image.width + double heightScale = height / image.height + + doScaleTransform(image, width, height, widthScale, heightScale, bufferedImageType) + } + + protected BufferedImage scale(BufferedImage image, double widthScale, double heightScale, int bufferedImageType) { + int width = (image.width * widthScale).toInteger() + int height = (image.height * heightScale).toInteger() + + doScaleTransform(image, width, height, widthScale, heightScale, bufferedImageType) + } + + protected BufferedImage doScaleTransform(BufferedImage image, int width, int height, double widthScale, double heightScale, int bufferedImageType) { + def scaled = new BufferedImage(width, height, bufferedImageType) + + def graphics = scaled.createGraphics() + graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC) + def transform = AffineTransform.getScaleInstance(widthScale, heightScale) + graphics.drawRenderedImage(image as RenderedImage, transform) + graphics.dispose() + + scaled + } } diff --git a/grails-app/services/grails/plugins/rendering/image/JpegRenderingService.groovy b/grails-app/services/grails/plugins/rendering/image/JpegRenderingService.groovy index 80693ef..c534b6c 100644 --- a/grails-app/services/grails/plugins/rendering/image/JpegRenderingService.groovy +++ b/grails-app/services/grails/plugins/rendering/image/JpegRenderingService.groovy @@ -15,19 +15,22 @@ */ package grails.plugins.rendering.image +import groovy.transform.CompileStatic + import java.awt.image.BufferedImage +@CompileStatic class JpegRenderingService extends ImageRenderingService { - protected getImageType() { + protected String getImageType() { "jpeg" } - protected getDefaultContentType() { + protected String getDefaultContentType() { "image/jpeg" } - protected getDefaultBufferedImageType() { + protected int getDefaultBufferedImageType() { BufferedImage.TYPE_INT_RGB } } diff --git a/grails-app/services/grails/plugins/rendering/image/PngRenderingService.groovy b/grails-app/services/grails/plugins/rendering/image/PngRenderingService.groovy index fd584be..fdb4e86 100644 --- a/grails-app/services/grails/plugins/rendering/image/PngRenderingService.groovy +++ b/grails-app/services/grails/plugins/rendering/image/PngRenderingService.groovy @@ -15,13 +15,16 @@ */ package grails.plugins.rendering.image +import groovy.transform.CompileStatic + +@CompileStatic class PngRenderingService extends ImageRenderingService { - protected getImageType() { + protected String getImageType() { "png" } - protected getDefaultContentType() { + protected String getDefaultContentType() { "image/png" } } diff --git a/grails-app/services/grails/plugins/rendering/pdf/PdfRenderingService.groovy b/grails-app/services/grails/plugins/rendering/pdf/PdfRenderingService.groovy index 16972fa..b3dd054 100644 --- a/grails-app/services/grails/plugins/rendering/pdf/PdfRenderingService.groovy +++ b/grails-app/services/grails/plugins/rendering/pdf/PdfRenderingService.groovy @@ -17,11 +17,12 @@ package grails.plugins.rendering.pdf import grails.plugins.rendering.RenderingService import grails.plugins.rendering.datauri.DataUriAwareITextUserAgent - +import groovy.transform.CompileStatic import org.springframework.util.ReflectionUtils import org.w3c.dom.Document import org.xhtmlrenderer.pdf.ITextRenderer +@CompileStatic class PdfRenderingService extends RenderingService { static transactional = false @@ -33,16 +34,16 @@ class PdfRenderingService extends RenderingService { protected doRender(Map args, Document document, OutputStream outputStream) { def renderer = new ITextRenderer() configureRenderer(renderer) - renderer.setDocument(document, args.base) + renderer.setDocument(document, args.base as String) renderer.layout() renderer.createPDF(outputStream) } - protected getDefaultContentType() { + protected String getDefaultContentType() { "application/pdf" } - protected configureRenderer(ITextRenderer renderer) { + protected void configureRenderer(ITextRenderer renderer) { def outputDevice = renderer.@_outputDevice def userAgent = new DataUriAwareITextUserAgent(outputDevice) def sharedContext = renderer.sharedContext diff --git a/src/main/groovy/grails/plugins/rendering/RenderingGrailsPlugin.groovy b/src/main/groovy/grails/plugins/rendering/RenderingGrailsPlugin.groovy index 6173a0b..35bee79 100644 --- a/src/main/groovy/grails/plugins/rendering/RenderingGrailsPlugin.groovy +++ b/src/main/groovy/grails/plugins/rendering/RenderingGrailsPlugin.groovy @@ -19,15 +19,11 @@ import grails.plugins.* class RenderingGrailsPlugin extends Plugin { - def grailsVersion = "3.0 > *" + def grailsVersion = "7.0 > *" def pluginExcludes = [ "grails-app/views/**", - "RenderingController**", - "grails-app/services/grails/plugin/rendering/test/**", - "src/groovy/grails/plugin/rendering/test/**", - "plugins/**", - "web-app/**" + "example/**" ] def observe = ["controllers"] @@ -40,8 +36,8 @@ class RenderingGrailsPlugin extends Plugin { def documentation = "http://gpc.github.com/grails-rendering" def license = 'APACHE' - def organization = [name: 'Grails Plugin Collective', url: 'http://github.com/gpc'] - def issueManagement = [system: 'JIRA', url: 'http://jira.grails.org/browse/GPRENDERING'] - def scm = [url: 'https://github.com/gpc/grails-rendering'] + def organization = [name: 'Grails Plugin Collective', url: 'https://github.com/gpc'] + def issueManagement = [system: 'JIRA', url: 'https://github.com/gpc/rendering/issues'] + def scm = [url: 'https://github.com/gpc/rendering.git'] } diff --git a/src/main/groovy/grails/plugins/rendering/document/RenderEnvironment.groovy b/src/main/groovy/grails/plugins/rendering/document/RenderEnvironment.groovy index fca586f..4679cd3 100644 --- a/src/main/groovy/grails/plugins/rendering/document/RenderEnvironment.groovy +++ b/src/main/groovy/grails/plugins/rendering/document/RenderEnvironment.groovy @@ -1,5 +1,6 @@ package grails.plugins.rendering.document +import grails.util.Environment import grails.util.GrailsWebMockUtil import org.grails.web.servlet.WrappedResponseHolder @@ -27,6 +28,7 @@ class RenderEnvironment { } private init() { + if(Environment.current == Environment.TEST) { originalRequestAttributes = RequestContextHolder.getRequestAttributes() renderRequestAttributes = GrailsWebMockUtil.bindMockWebRequest(applicationContext) @@ -46,11 +48,16 @@ class RenderEnvironment { renderRequestAttributes.setOut(out) WrappedResponseHolder.wrappedResponse = renderRequestAttributes.currentResponse - } + + } + + } private close() { - RequestContextHolder.setRequestAttributes(originalRequestAttributes) // null ok - WrappedResponseHolder.wrappedResponse = originalRequestAttributes?.currentResponse + if(originalRequestAttributes) { + RequestContextHolder.setRequestAttributes(originalRequestAttributes) // null ok + WrappedResponseHolder.wrappedResponse = originalRequestAttributes?.currentResponse + } } /** From 85552e8a5a0eabadd708dfc7181c4d15d6ba3bff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Berg=20Glasius?= Date: Tue, 30 Sep 2025 10:18:20 +0200 Subject: [PATCH 02/12] Documentation moved to AsciiDocs --- src/.gitignore | 2 +- src/docs/guide/1. Introduction.gdoc | 15 ---- src/docs/guide/2. GSP Considerations.gdoc | 20 ----- src/docs/guide/3. Rendering.gdoc | 58 -------------- src/docs/guide/4. Sizing.gdoc | 41 ---------- .../guide/5. Rendering To The Response.adoc | 39 ---------- .../guide/5. Rendering To The Response.gdoc | 36 --------- .../guide/6. Caching And Performance.gdoc | 76 ------------------- src/docs/guide/7. Inline Images.gdoc | 32 -------- src/docs/guide/8. Exotic Characters.gdoc | 18 ----- src/docs/ref/Controller/renderGif.adoc | 16 ---- src/docs/ref/Controller/renderGif.gdoc | 21 ----- src/docs/ref/Controller/renderJpeg.adoc | 16 ---- src/docs/ref/Controller/renderJpeg.gdoc | 21 ----- src/docs/ref/Controller/renderPdf.adoc | 16 ---- src/docs/ref/Controller/renderPdf.gdoc | 21 ----- src/docs/ref/Controller/renderPng.adoc | 16 ---- src/docs/ref/Controller/renderPng.gdoc | 21 ----- .../asciidoc}/1. Introduction.adoc | 4 +- .../asciidoc}/2. GSP Considerations.adoc | 6 +- .../guide => main/asciidoc}/3. Rendering.adoc | 9 ++- .../guide => main/asciidoc}/4. Sizing.adoc | 14 ++-- .../5. Rendering To The Response.adoc | 50 ++++++++++++ .../asciidoc}/6. Caching And Performance.adoc | 11 +-- .../asciidoc}/7. Inline Images.adoc | 12 +-- .../asciidoc}/8. Exotic Characters.adoc | 4 +- src/main/asciidoc/index.adoc | 15 ++++ 27 files changed, 97 insertions(+), 513 deletions(-) delete mode 100644 src/docs/guide/1. Introduction.gdoc delete mode 100644 src/docs/guide/2. GSP Considerations.gdoc delete mode 100644 src/docs/guide/3. Rendering.gdoc delete mode 100644 src/docs/guide/4. Sizing.gdoc delete mode 100644 src/docs/guide/5. Rendering To The Response.adoc delete mode 100644 src/docs/guide/5. Rendering To The Response.gdoc delete mode 100644 src/docs/guide/6. Caching And Performance.gdoc delete mode 100644 src/docs/guide/7. Inline Images.gdoc delete mode 100644 src/docs/guide/8. Exotic Characters.gdoc delete mode 100644 src/docs/ref/Controller/renderGif.adoc delete mode 100644 src/docs/ref/Controller/renderGif.gdoc delete mode 100644 src/docs/ref/Controller/renderJpeg.adoc delete mode 100644 src/docs/ref/Controller/renderJpeg.gdoc delete mode 100644 src/docs/ref/Controller/renderPdf.adoc delete mode 100644 src/docs/ref/Controller/renderPdf.gdoc delete mode 100644 src/docs/ref/Controller/renderPng.adoc delete mode 100644 src/docs/ref/Controller/renderPng.gdoc rename src/{docs/guide => main/asciidoc}/1. Introduction.adoc (81%) rename src/{docs/guide => main/asciidoc}/2. GSP Considerations.adoc (91%) rename src/{docs/guide => main/asciidoc}/3. Rendering.adoc (95%) rename src/{docs/guide => main/asciidoc}/4. Sizing.adoc (94%) create mode 100644 src/main/asciidoc/5. Rendering To The Response.adoc rename src/{docs/guide => main/asciidoc}/6. Caching And Performance.adoc (90%) rename src/{docs/guide => main/asciidoc}/7. Inline Images.adoc (82%) rename src/{docs/guide => main/asciidoc}/8. Exotic Characters.adoc (78%) create mode 100644 src/main/asciidoc/index.adoc diff --git a/src/.gitignore b/src/.gitignore index 30821b6..6349bf2 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1 +1 @@ -!docs \ No newline at end of file +!main/docs \ No newline at end of file diff --git a/src/docs/guide/1. Introduction.gdoc b/src/docs/guide/1. Introduction.gdoc deleted file mode 100644 index 4a341c6..0000000 --- a/src/docs/guide/1. Introduction.gdoc +++ /dev/null @@ -1,15 +0,0 @@ -This plugin adds additional rendering capabilities to Grails applications via the "XHTML Renderer":https://xhtmlrenderer.dev.java.net/ library. - -Rendering is either done directly via @«format»RenderingService@ services ... - -{code} -ByteArrayOutputStream bytes = pdfRenderingService.render(template: "/pdfs/report", model: [data: data]) -{code} - -Or via the @render«format»()@ methods added to controllers ... - -{code} -renderPdf(template: "/pdfs/report", model: [report: reportObject], filename: reportObject.name) -{code} - -The plugin is released under the "Apache License 2.0":http://www.apache.org/licenses/LICENSE-2.0.html license and is produced under the "Grails Plugin Collective":http://gpc.github.com/ . \ No newline at end of file diff --git a/src/docs/guide/2. GSP Considerations.gdoc b/src/docs/guide/2. GSP Considerations.gdoc deleted file mode 100644 index b5b6148..0000000 --- a/src/docs/guide/2. GSP Considerations.gdoc +++ /dev/null @@ -1,20 +0,0 @@ -There are a few things that you do need to be aware of when writing GSPs to be rendered via this plugin. - -h3. Link resources must be resolvable - -All links to resources (e.g. images, css) must be _accessible by the application_ . This is due to the linked resources being accessed by _application_ and not a browser. Depending on your network config in production, this may require some special consideration. - -The rendering engine resolves all relative links relative to the @grails.serverURL@ config property. - -h3. Must be well formed - -The GSP must render to well formed, valid, XHTML. If it does not, a @grails.plugins.rendering.document.XmlParseException@ will be thrown. - -h3. Must declare DOCTYPE - -Without a doctype, you are likely to get parse failures due to unresolvable entity references (e.g. @ @). Be sure to declare the XHTML doctype at the start of your GSP like so ... - -{code:lang=xml} - -{code} \ No newline at end of file diff --git a/src/docs/guide/3. Rendering.gdoc b/src/docs/guide/3. Rendering.gdoc deleted file mode 100644 index 4845a1a..0000000 --- a/src/docs/guide/3. Rendering.gdoc +++ /dev/null @@ -1,58 +0,0 @@ -There are four services available for rendering: - -* pdfRenderingService -* gifRenderingService -* pngRenderingService -* jpegRenderingService - -All services have the same method… - -{code:java} -OutputStream render(Map args, OutputStream destination = new ByteArrayOutputStream()) -{code} - -The @args@ define the render operation, with the bytes written to the given output stream. The given output stream is returned from the method. If no @destination@ is provided, the render will write to a @ByteArrayOutputStream@ that is returned. - -Here are some examples: - -{code:java} -// Get the bytes -def bytes = gifRenderingService.render(template: '/images/coupon', model: [serial: 12345]) - -// Render to a file -new File("coupon.jpg").withOutputStream { outputStream -> - jpegRenderingService.render([template: '/images/coupon', model: [serial: 12345]], outputStream) -} -{code} - -For information on rendering to the HTTP response, see [Rendering To The Response|guide:5. Rendering To The Response]. - -h3. Basic Render Arguments - -All rendering methods take a @Map@ argument that specifies which template to render and the model to use (in most cases). - -The following map arguments are common to all rendering methods: - -* @template@ (required) - The template to render -* @model@ (optional) - The model to use -* @plugin@ (optional) - The plug-in containing the template -* @controller@ (optional) - The controller _instance_ or _name_ to resolve the template against (set automatically in provided @render«format»@ methods on controllers). - -h3. Template Resolution - -The plugin uses the same resolution strategy as the @render()@ method in Grails controllers and taglibs. - -That is, - -* template files must start with an underscore (@_template.gsp@) -* template paths starting with "/" are resolved relative to the @views@ directory -* template paths NOT starting with "/" are resolved relative to the @views/«controller»@ directory - -If the @template@ argument does not start with a "/", the @controller@ argument must be provided. The methods added to controllers (e.g. @renderPdf()@) automatically pass the @controller@ param for you. - -h3. Debuging - -To get more visibility about what is going on inside, you can activate the logging within Flying Saucer by providing the system property xr.util-logging.loggingEnabled like so: -{code} -grails -Dxr.util-logging.loggingEnabled=true run-app -{code} diff --git a/src/docs/guide/4. Sizing.gdoc b/src/docs/guide/4. Sizing.gdoc deleted file mode 100644 index 56d5c11..0000000 --- a/src/docs/guide/4. Sizing.gdoc +++ /dev/null @@ -1,41 +0,0 @@ -h3. Documents - -When rendering PDF documents, you can specify the page size via CSS… - -{code} - -{code} - -h3. Images - -The image rendering methods take extra arguments to control the size of the rendered image. The extra arguments are maps containing @width@ or @height@ keys, or both. - -h4. render - -The @render@ argument is the size of the view port that the document is rendered into. This is equivalent to the dimensions of the browser window for html rendering. - -The default value for @render@ is @[width: 10, height: 10000]@ (i.e. 10 pixels wide by 10000 pixels high). - -h4. autosize - -The @autosize@ argument specifies whether to adjust the size of the image to exactly be the rendered content. - -The default value for @autosize@ is @[width: true, height: true]@. - -h4. scale - -The @scale@ argument specifies the factor to scale the image by after initial rendering. For example, the value @[width: 0.5, height: 0.5]@ produces an image half the size of the original render. - -The default value for @autosize@ is null. - -h4. resize - -The @resize@ argument specifies the adjusted mage after initial rendering. For example, the value @[width: 200, height: 400]@ will resize the image to 200 pixels X 400 pixels regardless of the original render size. - -(note that @resize@ & @scale@ are mutually exclusive with @scale@ taking precedence). - -The default value for @resize@ is null. diff --git a/src/docs/guide/5. Rendering To The Response.adoc b/src/docs/guide/5. Rendering To The Response.adoc deleted file mode 100644 index dbb67f6..0000000 --- a/src/docs/guide/5. Rendering To The Response.adoc +++ /dev/null @@ -1,39 +0,0 @@ -= Rendering To The Response - -There are four methods added to all controllers for rendering: - -* renderPdf(Map args) -* renderGif(Map args) -* renderPng(Map args) -* renderJpeg(Map args) - -Each of the methods is equivalent to: - -[source,groovy] ----- -«format»RenderingService.render(args + [controller: this], response) ----- - -All methods take all of the arguments that their respective service's `render()` method take, plus some extras. - -=== Extra Render Arguments - -All rendering methods take a `Map` argument that specifies which template to render and the model to use (in most cases). - -The following map arguments are common to all rendering methods: - -* `filename` (optional) - sets the `Content-Disposition` header with `attachment; filename="$filename"` (asking the browser to download the file with the given filename) -* `contentType` (optional) - the `Content-Type` header value (see Content Type Defaults below) - -=== Default Content Types - -The default content types are: - -* application/pdf -* image/gif -* image/png -* image/jpeg - -=== Large Files/Renders - -See the section on <> for some other arguments that can help with large renders. diff --git a/src/docs/guide/5. Rendering To The Response.gdoc b/src/docs/guide/5. Rendering To The Response.gdoc deleted file mode 100644 index 41cc951..0000000 --- a/src/docs/guide/5. Rendering To The Response.gdoc +++ /dev/null @@ -1,36 +0,0 @@ -There are four methods added to all controllers for rendering: - -* renderPdf(Map args) -* renderGif(Map args) -* renderPng(Map args) -* renderJpeg(Map args) - -Each of the methods is equivalent to… - -{code} -«format»RenderingService.render(args + [controller: this], response) -{code} - -All methods take all of the arguments that their respective service's @render()@ method take, plus some extras. - -h3. Extra Render Arguments - -All rendering methods take a @Map@ argument that specifies which template to render and the model to use (in most cases). - -The following map arguments are common to all rendering methods: - -* @filename@ (option) - sets the @Content-Disposition@ header with @attachment; filename="$filename";@ (asking the browser to download the file with the given filename) -* @contentType@ (optional) - the @Content-Type@ header value (see Content Type Defaults below) - -h3. Default Content Types - -The default content types are… - -* application/pdf -* image/gif -* image/png -* image/jpeg - -h3. Large Files/Renders - -See the section on [caching and performance|guide:6. Caching And Performance] for some other arguments that can help with large renders. \ No newline at end of file diff --git a/src/docs/guide/6. Caching And Performance.gdoc b/src/docs/guide/6. Caching And Performance.gdoc deleted file mode 100644 index bd3f334..0000000 --- a/src/docs/guide/6. Caching And Performance.gdoc +++ /dev/null @@ -1,76 +0,0 @@ -h3. Caching - -Rendering can be an expensive operation so you may need to implement caching (using the excellent [spring-cache:http://grails.org/plugin/springcache] plugin) - -h4. Document Caching - -Rendering works internally by creating a @org.w3c.dom.Document@ instance from the GSP page via the @xhtmlDocumentService@. If you plan to render the same GSP as different output formats, you may want to cache the document. - -{code} -import grails.plugin.springcache.annotations.Cacheable - -class CouponDocumentService { - def xhmlDocumentService - - @Cacheable('couponDocumentCache')@ - class getDocument(serial) { - xhmlDocumentService.createDocument(template: '/coupon', model: [serial: serial]) - } -} -{code} - -All of the render methods can take a @document@ parameter instead of the usual @template@/@model@ properties. - -{code} -class CouponController { - - def couponDocumentService - - def gif = { - def serial = params.id - def document = couponDocumentService.getDocument(serial) - - renderGif(filename: "${serial}.gif", document) - } -} -{code} - -h4. Byte Caching - -You can take things further and actually cache the rendered bytes. - -{code} -import grails.plugin.springcache.annotations.Cacheable - -class CouponGifService { - - def couponDocumentService - def gifRenderingService - - def getGif(serial) { - def document = couponDocumentService.getDocument(serial) - def byteArrayOutputStream = gifRenderingService.gif([:], document) - byteArrayOutputStream.toByteArray() - } -} -{code} - -{code} -class CouponController { - - def couponGifService - - def gif = { - def serial = params.id - def bytes = couponGifService.getGif(serial) - - renderGif(bytes: bytes, filename: "${serial}.gif") - } -} -{code} - -h3. Avoiding Byte Copying - -When rendering to the response, the content is first written to a temp buffer before being written to the response. This is so the number of bytes can be determined and the @Content-Length@ header set (this also applies when passing the @bytes@ directly). - -This copy can be avoided and the render (or bytes) can be written directly to the response output stream. This means that the @Content-Length@ header will not be set unless you manually specify the length via the @contentLength@ property to the render method. \ No newline at end of file diff --git a/src/docs/guide/7. Inline Images.gdoc b/src/docs/guide/7. Inline Images.gdoc deleted file mode 100644 index 0578aab..0000000 --- a/src/docs/guide/7. Inline Images.gdoc +++ /dev/null @@ -1,32 +0,0 @@ -This plugin adds support for inline images via "data uris":http://en.wikipedia.org/wiki/Data_URI_scheme. This is useful for situations where the images you need to imbed in a rendered PDF or image are generated by the application itself. - -For example, your application may generate barcodes that you don't necessarily want to expose but want to include in your generated PDFs or images. Using inline images, you can include the image bytes in the document to be rendered. - -To make this easier, the plugin provides tags to render byte arrays as common image formats (i.e. gif, png and jpeg). - -The tags are under the namespace @rendering@ and are called @inlinePng@, @inlineGif@ and @inlineJpeg@. They all take a single argument, @bytes@, which is a @byte[]@ containing the raw bytes of the images. This will result in an @img@ tag with a @src@ attribute of a suitable data uri. Any other parameters passed to the tag will be expressed as attributes of the resultant @img@ tag. - -Here is an example of how this could be used to include a local (i.e. from the filesystem) image in a generated pdf/image. - -{code} -class SomeController { - - def generate = { - def file = new File("path/to/image.png") - renderPng(template: "thing", model: [imageBytes: file.bytes]) - } - -} -{code} - -In the view… - -{code} - - - -

Below is an inline image

- - - -{code} diff --git a/src/docs/guide/8. Exotic Characters.gdoc b/src/docs/guide/8. Exotic Characters.gdoc deleted file mode 100644 index 260d327..0000000 --- a/src/docs/guide/8. Exotic Characters.gdoc +++ /dev/null @@ -1,18 +0,0 @@ -In most cases, there are no issues with dealing with exotic Unicode characters. However, certain characters will not render in PDF documents without some extra work (the same problem does not exist when rendering images). This is a quirk with the way iText works, which is the library underpinning the PDF generation. - -This "thread":http://www.mail-archive.com/itext-questions@lists.sourceforge.net/msg48788.html explains the issue. - -The solution is to register the font to use with a particular encoding. Because we are using XHTMLRenderer we can specify this in CSS as opposed to programatically registering. - -{code} - @font-face { - src: url(path/to/arial.ttf); - -fs-pdf-font-embed: embed; - -fs-pdf-font-encoding: cp1250; - } - body { - font-family: "Arial Unicode MS", Arial, sans-serif; - } -{code} - -See "this page":http://pigeonholdings.com/projects/flyingsaucer/R8/doc/guide/users-guide-R8.html#xil_44 for details on these CSS directives. \ No newline at end of file diff --git a/src/docs/ref/Controller/renderGif.adoc b/src/docs/ref/Controller/renderGif.adoc deleted file mode 100644 index e858252..0000000 --- a/src/docs/ref/Controller/renderGif.adoc +++ /dev/null @@ -1,16 +0,0 @@ -= renderGif - -== Purpose - -== Examples - -[source,java] ----- -foo.renderGif(map) ----- - -== Description - -Arguments: - -* `map` \ No newline at end of file diff --git a/src/docs/ref/Controller/renderGif.gdoc b/src/docs/ref/Controller/renderGif.gdoc deleted file mode 100644 index a42a392..0000000 --- a/src/docs/ref/Controller/renderGif.gdoc +++ /dev/null @@ -1,21 +0,0 @@ - -h1. renderGif - -h2. Purpose - - - -h2. Examples - -{code:java} -foo.renderGif(map) -{code} - -h2. Description - - - -Arguments: - -[* @map@ -] diff --git a/src/docs/ref/Controller/renderJpeg.adoc b/src/docs/ref/Controller/renderJpeg.adoc deleted file mode 100644 index cfe62a7..0000000 --- a/src/docs/ref/Controller/renderJpeg.adoc +++ /dev/null @@ -1,16 +0,0 @@ -= renderJpeg - -== Purpose - -== Examples - -[source,java] ----- -foo.renderJpeg(map) ----- - -== Description - -Arguments: - -* `map` \ No newline at end of file diff --git a/src/docs/ref/Controller/renderJpeg.gdoc b/src/docs/ref/Controller/renderJpeg.gdoc deleted file mode 100644 index 5b08fb2..0000000 --- a/src/docs/ref/Controller/renderJpeg.gdoc +++ /dev/null @@ -1,21 +0,0 @@ - -h1. renderJpeg - -h2. Purpose - - - -h2. Examples - -{code:java} -foo.renderJpeg(map) -{code} - -h2. Description - - - -Arguments: - -[* @map@ -] diff --git a/src/docs/ref/Controller/renderPdf.adoc b/src/docs/ref/Controller/renderPdf.adoc deleted file mode 100644 index 0db6970..0000000 --- a/src/docs/ref/Controller/renderPdf.adoc +++ /dev/null @@ -1,16 +0,0 @@ -= renderPdf - -== Purpose - -== Examples - -[source,java] ----- -foo.renderPdf(map) ----- - -== Description - -Arguments: - -* `map` \ No newline at end of file diff --git a/src/docs/ref/Controller/renderPdf.gdoc b/src/docs/ref/Controller/renderPdf.gdoc deleted file mode 100644 index 497a25a..0000000 --- a/src/docs/ref/Controller/renderPdf.gdoc +++ /dev/null @@ -1,21 +0,0 @@ - -h1. renderPdf - -h2. Purpose - - - -h2. Examples - -{code:java} -foo.renderPdf(map) -{code} - -h2. Description - - - -Arguments: - -[* @map@ -] diff --git a/src/docs/ref/Controller/renderPng.adoc b/src/docs/ref/Controller/renderPng.adoc deleted file mode 100644 index 2dd14f8..0000000 --- a/src/docs/ref/Controller/renderPng.adoc +++ /dev/null @@ -1,16 +0,0 @@ -= renderPng - -== Purpose - -== Examples - -[source,java] ----- -foo.renderPng(map) ----- - -== Description - -Arguments: - -* `map` \ No newline at end of file diff --git a/src/docs/ref/Controller/renderPng.gdoc b/src/docs/ref/Controller/renderPng.gdoc deleted file mode 100644 index 234dde4..0000000 --- a/src/docs/ref/Controller/renderPng.gdoc +++ /dev/null @@ -1,21 +0,0 @@ - -h1. renderPng - -h2. Purpose - - - -h2. Examples - -{code:java} -foo.renderPng(map) -{code} - -h2. Description - - - -Arguments: - -[* @map@ -] diff --git a/src/docs/guide/1. Introduction.adoc b/src/main/asciidoc/1. Introduction.adoc similarity index 81% rename from src/docs/guide/1. Introduction.adoc rename to src/main/asciidoc/1. Introduction.adoc index a5887c4..be2763d 100644 --- a/src/docs/guide/1. Introduction.adoc +++ b/src/main/asciidoc/1. Introduction.adoc @@ -9,11 +9,11 @@ Rendering is either done directly via `«format»RenderingService` services ... ByteArrayOutputStream bytes = pdfRenderingService.render(template: "/pdfs/report", model: [data: data]) ---- -Or via the `render«format»()` methods added to controllers ... +Or via the `render«format»()` methods in `RenderingTrait` that is to controllers ... [source,groovy] ---- renderPdf(template: "/pdfs/report", model: [report: reportObject], filename: reportObject.name) ---- -The plugin is released under the http://www.apache.org/licenses/LICENSE-2.0.html[Apache License 2.0] license and is produced under the http://gpc.github.com/[Grails Plugin Collective]. +The plugin is released under the http://www.apache.org/licenses/LICENSE-2.0.html[Apache License 2.0] license and is produced under the http://github.com/gpc[Grails Plugin Collective]. diff --git a/src/docs/guide/2. GSP Considerations.adoc b/src/main/asciidoc/2. GSP Considerations.adoc similarity index 91% rename from src/docs/guide/2. GSP Considerations.adoc rename to src/main/asciidoc/2. GSP Considerations.adoc index 7d06ec3..c941050 100644 --- a/src/docs/guide/2. GSP Considerations.adoc +++ b/src/main/asciidoc/2. GSP Considerations.adoc @@ -2,17 +2,17 @@ There are a few things that you do need to be aware of when writing GSPs to be rendered via this plugin. -=== Link resources must be resolvable +== Link resources must be resolvable All links to resources (e.g. images, css) must be _accessible by the application_. This is due to the linked resources being accessed by _application_ and not a browser. Depending on your network setup, ensure resources are available. The rendering engine resolves all relative links relative to the `grails.serverURL` config property. -=== Must be well formed +== Must be well formed The GSP must render to well formed, valid, XHTML. If it does not, a `grails.plugins.rendering.document.XmlParseException` will be thrown. -=== Must declare DOCTYPE +== Must declare DOCTYPE Without a doctype, you are likely to get parse failures due to unresolvable entity references (e.g. ` `). Be sure to declare the XHTML doctype at the start of your GSP like so: diff --git a/src/docs/guide/3. Rendering.adoc b/src/main/asciidoc/3. Rendering.adoc similarity index 95% rename from src/docs/guide/3. Rendering.adoc rename to src/main/asciidoc/3. Rendering.adoc index 52fac9f..2488b9a 100644 --- a/src/docs/guide/3. Rendering.adoc +++ b/src/main/asciidoc/3. Rendering.adoc @@ -1,11 +1,12 @@ +[#rendering] = Rendering There are four services available for rendering: -* pdfRenderingService -* gifRenderingService -* pngRenderingService -* jpegRenderingService +* `pdfRenderingService` +* `gifRenderingService` +* `pngRenderingService` +* `jpegRenderingService` All services have the same method: diff --git a/src/docs/guide/4. Sizing.adoc b/src/main/asciidoc/4. Sizing.adoc similarity index 94% rename from src/docs/guide/4. Sizing.adoc rename to src/main/asciidoc/4. Sizing.adoc index 9a7985d..425b988 100644 --- a/src/docs/guide/4. Sizing.adoc +++ b/src/main/asciidoc/4. Sizing.adoc @@ -1,10 +1,10 @@ = Sizing -=== Documents +== Documents When rendering PDF documents, you can specify the page size via CSS: -[source,css] +[source,html] ---- ---- -=== Images +== Images The image rendering methods take extra arguments to control the size of the rendered image. The extra arguments are maps containing `width` or `height` keys, or both. -==== render +=== render The `render` argument is the size of the view port that the document is rendered into. This is equivalent to the dimensions of the browser window for html rendering. The default value for `render` is `[width: 10, height: 10000]` (i.e. 10 pixels wide by 10000 pixels high). -==== autosize +=== autosize The `autosize` argument specifies whether to adjust the size of the image to exactly be the rendered content. The default value for `autosize` is `[width: true, height: true]`. -==== scale +=== scale The `scale` argument specifies the factor to scale the image by after initial rendering. For example, the value `[width: 0.5, height: 0.5]` produces an image half the size of the original render. The default value for `scale` is `null`. -==== resize +=== resize The `resize` argument specifies the adjusted image after initial rendering. For example, the value `[width: 200, height: 400]` will resize the image to 200 pixels x 400 pixels regardless of the original size. diff --git a/src/main/asciidoc/5. Rendering To The Response.adoc b/src/main/asciidoc/5. Rendering To The Response.adoc new file mode 100644 index 0000000..92fe3d3 --- /dev/null +++ b/src/main/asciidoc/5. Rendering To The Response.adoc @@ -0,0 +1,50 @@ += Rendering To The Response + +Rendering is available via the `RenderingTrait` that is automatically applied to Controllers + +[source,groovy] +---- +class MyController { + def action() { + renderPdf(template: 'simple-template') + } +} +---- + +== Methods in `RenderingTrait` + +* `renderPdf(Map args)` +* `renderGif(Map args)` +* `renderPng(Map args)` +* `renderJpeg(Map args)` + +Each of the methods is equivalent to: + +[source,groovy] +---- +«format»RenderingService.render(args + [controller: this], response) +---- + +All methods take all the arguments that their respective service's `render()` method take, plus some extras. + +== Extra Render Arguments + +All rendering methods take a `Map` argument that specifies which template to render and the model to use (in most cases). + +The following map arguments are common to all rendering methods: + +* `filename` (optional) - sets the `Content-Disposition` header with `attachment; filename="$filename"` (asking the browser to download the file with the given filename) +* `contentType` (optional) - the `Content-Type` header value (see Content Type Defaults below) + +== Default Content Types + +The default content types are: + +* `application/pdf` +* `image/gif` +* `image/png` +* `image/jpeg` + +== Large Files/Renders + +See the section on <> for some other arguments that can help with large renders. diff --git a/src/docs/guide/6. Caching And Performance.adoc b/src/main/asciidoc/6. Caching And Performance.adoc similarity index 90% rename from src/docs/guide/6. Caching And Performance.adoc rename to src/main/asciidoc/6. Caching And Performance.adoc index d8e6a8b..6d2124c 100644 --- a/src/docs/guide/6. Caching And Performance.adoc +++ b/src/main/asciidoc/6. Caching And Performance.adoc @@ -1,10 +1,11 @@ +[#caching_and_performance] = Caching And Performance -=== Caching +== Caching -Rendering can be an expensive operation so you may need to implement caching (using the excellent http://grails.org/plugin/springcache[spring-cache] plugin). +Rendering can be an expensive operation so you may need to implement caching (using the excellent https://github.com/apache/grails-core/tree/7.0.x/grails-cache[grails-cache] plugin). -==== Document Caching +=== Document Caching Rendering works internally by creating a `org.w3c.dom.Document` instance from the GSP page via the `xhtmlDocumentService`. If you plan to render the same GSP as different output formats, you may want to cache the created Document. @@ -39,7 +40,7 @@ class CouponController { } ---- -==== Byte Caching +=== Byte Caching You can take things further and actually cache the rendered bytes. @@ -75,7 +76,7 @@ class CouponController { } ---- -=== Avoiding Byte Copying +== Avoiding Byte Copying When rendering to the response, the content is first written to a temp buffer before being written to the response. This is so the number of bytes can be determined and the `Content-Length` header can be set. diff --git a/src/docs/guide/7. Inline Images.adoc b/src/main/asciidoc/7. Inline Images.adoc similarity index 82% rename from src/docs/guide/7. Inline Images.adoc rename to src/main/asciidoc/7. Inline Images.adoc index 6f02066..0a272e8 100644 --- a/src/docs/guide/7. Inline Images.adoc +++ b/src/main/asciidoc/7. Inline Images.adoc @@ -1,6 +1,6 @@ = Inline Images -This plugin adds support for inline images via http://en.wikipedia.org/wiki/Data_URI_scheme[data URIs]. This is useful for situations where the images you need to imbed in a rendered PDF or image are not accessible via a URL. +This plugin adds support for inline images via http://en.wikipedia.org/wiki/Data_URI_scheme[data URIs]. This is useful for situations where the images you need to embed in a rendered PDF or image are not accessible via a URL. For example, your application may generate barcodes that you don't necessarily want to expose but want to include in your generated PDFs or images. Using inline images, you can include the image bytes directly in the output. @@ -14,17 +14,17 @@ Here is an example of how this could be used to include a local (i.e. from the f ---- class SomeController { - def generate = { - def file = new File("path/to/image.png") - renderPng(template: "thing", model: [imageBytes: file.bytes]) - } - + def generate() { + URL resource = this.class.getResource("/path/to/image.png") + renderPng(template: "thing", model: [imageBytes: resource.bytes]) + } } ---- In the view ... [source,html] +._thing.gsp ---- diff --git a/src/docs/guide/8. Exotic Characters.adoc b/src/main/asciidoc/8. Exotic Characters.adoc similarity index 78% rename from src/docs/guide/8. Exotic Characters.adoc rename to src/main/asciidoc/8. Exotic Characters.adoc index d4f2a8d..6d5d9cb 100644 --- a/src/docs/guide/8. Exotic Characters.adoc +++ b/src/main/asciidoc/8. Exotic Characters.adoc @@ -4,7 +4,7 @@ In most cases, there are no issues with dealing with exotic Unicode characters. This http://www.mail-archive.com/itext-questions@lists.sourceforge.net/msg48788.html[thread] explains the issue. -The solution is to register the font to use with a particular encoding. Because we are using XHTMLRenderer we can specify this in CSS as opposed to programatically registering. +The solution is to register the font to use with a particular encoding. Because we are using XHTMLRenderer we can specify this in CSS as opposed to programmatically registering. [source,css] ---- @@ -17,4 +17,4 @@ body { font-family: "Arial Unicode MS", Arial, sans-serif; } ---- -See http://pigeonholdings.com/projects/flyingsaucer/R8/doc/guide/users-guide-R8.html#xil_44[this page] for details on these CSS directives. +See https://flyingsaucerproject.github.io/flyingsaucer/r8/guide/users-guide-R8.html#xil_43[this page] for details on these CSS directives. diff --git a/src/main/asciidoc/index.adoc b/src/main/asciidoc/index.adoc new file mode 100644 index 0000000..8555870 --- /dev/null +++ b/src/main/asciidoc/index.adoc @@ -0,0 +1,15 @@ += Grails Render +:doctype: book +:author: Grails Plugin Collective (GPC) +:source-highlighter: coderay +:numbered: +:imagesdir: ./images + +include::1. Introduction.adoc[leveloffset=2] +include::2. GSP Considerations.adoc[leveloffset=2] +include::3. Rendering.adoc[leveloffset=2] +include::4. Sizing.adoc[leveloffset=2] +include::5. Rendering To The Response.adoc[leveloffset=2] +include::6. Caching And Performance.adoc[leveloffset=2] +include::7. Inline Images.adoc[leveloffset=2] +include::8. Exotic Characters.adoc[leveloffset=2] From 4adf0e6185cef0f483c5424fba131785751edf55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Berg=20Glasius?= Date: Tue, 30 Sep 2025 10:20:14 +0200 Subject: [PATCH 03/12] Adding grails-gradle.-grails-publish and supporting files --- build.gradle | 54 +++++++++++++++++++++++++------ buildSrc/build.gradle | 26 +++++++++++++++ gradle.properties | 5 ++- gradle/docs-config.gradle | 67 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 142 insertions(+), 10 deletions(-) create mode 100644 buildSrc/build.gradle create mode 100644 gradle/docs-config.gradle diff --git a/build.gradle b/build.gradle index 823c6dc..ff2ecb8 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,7 @@ +import java.time.Instant +import java.time.ZoneOffset +import java.time.format.DateTimeFormatter + buildscript { ext { grailsVersion = project.grailsVersion @@ -19,18 +23,23 @@ buildscript { } } -version "3.0.0-SNAPSHOT" +version = projectVersion group "org.grails.plugins" apply plugin: 'maven-publish' apply plugin:"org.apache.grails.gradle.grails-plugin" apply plugin:"org.apache.grails.gradle.grails-gsp" -// Used for publishing to central repository, remove if not needed -//apply from:'https://raw.githubusercontent.com/grails/grails-profile-repository/master/profiles/plugin/templates/grailsCentralPublishing.gradle' +apply plugin: 'org.apache.grails.gradle.grails-publish' +apply plugin: 'java-library' ext { - grailsVersion = project.grailsVersion - gradleWrapperVersion = project.gradleWrapperVersion + buildInstant = java.util.Optional.ofNullable(System.getenv("SOURCE_DATE_EPOCH")) + .filter(s -> !s.isEmpty()) + .map(Long::parseLong) + .map(Instant::ofEpochSecond) + .orElseGet(Instant::now) as Instant + formattedBuildDate = DateTimeFormatter.ISO_INSTANT.format(buildInstant) + buildDate = buildInstant.atZone(ZoneOffset.UTC) // for reproducible builds } repositories { @@ -49,15 +58,15 @@ dependencies { compileOnly platform("org.apache.grails:grails-bom:$grailsVersion") compileOnly 'org.apache.grails:grails-dependencies-starter-web' + api 'org.xhtmlrenderer:flying-saucer-pdf-openpdf:9.1.22' + api("org.apache.pdfbox:pdfbox:3.0.5") + + testImplementation platform("org.apache.grails:grails-bom:$grailsVersion") testImplementation "org.apache.grails:grails-testing-support-datamapping" testImplementation "org.spockframework:spock-core" testImplementation "org.apache.grails:grails-testing-support-web" - implementation 'org.xhtmlrenderer:flying-saucer-pdf-openpdf:9.1.22' - testImplementation("org.apache.pdfbox:pdfbox:3.0.5") { - exclude module:'jempbox' - } } compileJava.options.release = 17 @@ -73,4 +82,31 @@ jar { tasks.withType(Test).configureEach { useJUnitPlatform() + testLogging { + events 'passed', 'skipped', 'failed' + } +} + +groovydoc { + excludes = ['**/*GrailsPlugin.groovy', '**/Application.groovy'] } + +grailsPublish { + githubSlug = 'gpc/rendering' + license { + name = 'Apache-2.0' + } + title = 'Rendering Plugin' + desc = 'Render GSPs as PDFs, JPEGs, GIFs and PNGs' + developers = [ + ldaley: 'Luke Daley', + graemerocher: 'Graeme Rocher', + rd: 'Randall Dietz', + sbglasius : 'Søren Berg Glasius', + burtbeckwith: 'Burt Beckwith', + ] +} + +compileJava.options.release = javaVersion.toInteger() + +apply from: layout.projectDirectory.file('gradle/docs-config.gradle') diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle new file mode 100644 index 0000000..766bcee --- /dev/null +++ b/buildSrc/build.gradle @@ -0,0 +1,26 @@ +plugins { + id 'groovy-gradle-plugin' +} + +file('../gradle.properties').withInputStream { + def gradleProperties = new Properties() + gradleProperties.load(it) + gradleProperties.each { k, v -> ext.set(k, v) } + +} + +repositories { + maven { url = 'https://repo.grails.org/grails/restricted' } + maven { + url = 'https://repository.apache.org/content/groups/snapshots' + content { + includeVersionByRegex('org[.]apache[.](grails|groovy).*', '.*', '.*-SNAPSHOT') + } + } +} + +dependencies { + implementation platform("org.apache.grails:grails-bom:$grailsVersion") + implementation "org.asciidoctor.jvm.convert:org.asciidoctor.jvm.convert.gradle.plugin:$asciidoctorGradlePluginVersion" + implementation 'org.apache.grails:grails-gradle-plugins' +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 1d10095..dbbf8d1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,5 @@ +projectVersion=7.0.0-SNAPSHOT grailsVersion=7.0.0-RC2 -gradleWrapperVersion=8.14.3 +javaVersion=17 +asciidoctorGradlePluginVersion=4.0.4 + diff --git a/gradle/docs-config.gradle b/gradle/docs-config.gradle new file mode 100644 index 0000000..7088073 --- /dev/null +++ b/gradle/docs-config.gradle @@ -0,0 +1,67 @@ +import org.asciidoctor.gradle.jvm.AsciidoctorTask + +apply plugin: 'org.asciidoctor.jvm.convert' + +tasks.withType(Groovydoc).configureEach { + access = GroovydocAccess.PROTECTED + processScripts = false + includeMainForScripts = false + includeAuthor = false + destinationDir = layout.buildDirectory.dir('docs/api').get().asFile +} + +tasks.withType(AsciidoctorTask).configureEach { + baseDir = layout.projectDirectory.dir('src/main/asciidoc') + sourceDir = layout.projectDirectory.dir('src/main/asciidoc') + outputDir = layout.buildDirectory.dir('docs/manual') + sources { + include 'index.adoc' + } + jvm { + jvmArgs += [ + '--add-opens', 'java.base/sun.nio.ch=ALL-UNNAMED', + '--add-opens', 'java.base/java.io=ALL-UNNAMED' + ] + } + attributes = [ + copyright : 'Apache License, Version 2.0', + docinfo1 : 'true', + doctype : 'book', + encoding : 'utf-8', + icons : 'font', + id : "$rootProject.name:$version", + idprefix : '', + idseparator : '-', + lang : 'en', + linkattrs : true, + numbered : '', + producer : 'Asciidoctor', + revnumber : version, + setanchors : true, + 'source-highlighter' : 'prettify', + toc : 'left', + toc2 : '', + toclevels : '2', + projectVersion : version + ] +} + +tasks.register('docs') { + group = 'documentation' + def outputFile = layout.buildDirectory.file('docs/index.html') + inputs.files(tasks.named('asciidoctor'), tasks.named('groovydoc')) + outputs.file(outputFile) + doLast { + File redirectPage = outputFile.get().asFile + redirectPage.delete() + redirectPage.text = ''' + + + Redirecting... + + + + + '''.stripIndent(8) + } +} \ No newline at end of file From 74c15f4399145a452dc7c013368ded04865d729f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Berg=20Glasius?= Date: Tue, 30 Sep 2025 10:42:05 +0200 Subject: [PATCH 04/12] Replace travis with github actions --- .github/release-drafter.yml | 134 ++++++++++++++++++++++++++++ .github/renovate.json | 61 +++++++++++++ .github/workflows/gradle.yml | 71 +++++++++++++++ .github/workflows/release-notes.yml | 23 +++++ .github/workflows/release.yml | 130 +++++++++++++++++++++++++++ .travis.yml | 21 ----- travis-build.sh | 66 -------------- 7 files changed, 419 insertions(+), 87 deletions(-) create mode 100644 .github/release-drafter.yml create mode 100644 .github/renovate.json create mode 100644 .github/workflows/gradle.yml create mode 100644 .github/workflows/release-notes.yml create mode 100644 .github/workflows/release.yml delete mode 100644 .travis.yml delete mode 100755 travis-build.sh diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000..38a91ce --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,134 @@ +name-template: $RESOLVED_VERSION +tag-template: v$RESOLVED_VERSION +pull-request: + title-templates: + fix: '🐛 $TITLE (#$NUMBER)' + feat: '🚀 $TITLE (#$NUMBER)' + default: '$TITLE (#$NUMBER)' +autolabeler: + - label: 'bug' + branch: + - '/fix\/.+/' + title: + - '/fix/i' + - label: 'improvement' + branch: + - '/improv\/.+/' + title: + - '/improv/i' + - label: 'feature' + branch: + - '/feature\/.+/' + title: + - '/feat/i' + - label: 'documentation' + branch: + - '/docs\/.+/' + title: + - '/docs/i' + - label: 'maintenance' + branch: + - '/(chore|refactor|style|test|ci|perf|build)\/.+/' + title: + - '/(chore|refactor|style|test|ci|perf|build)/i' + - label: 'chore' + branch: + - '/chore\/.+/' + title: + - '/chore/i' + - label: 'refactor' + branch: + - '/refactor\/.+/' + title: + - '/refactor/i' + - label: 'style' + branch: + - '/style\/.+/' + title: + - '/style/i' + - label: 'test' + branch: + - '/test\/.+/' + title: + - '/test/i' + - label: 'ci' + branch: + - '/ci\/.+/' + title: + - '/ci/i' + - label: 'perf' + branch: + - '/perf\/.+/' + title: + - '/perf/i' + - label: 'build' + branch: + - '/build\/.+/' + title: + - '/build/i' + - label: 'deps' + branch: + - '/deps\/.+/' + title: + - '/deps/i' + - label: 'revert' + branch: + - '/revert\/.+/' + title: + - '/revert/i' +categories: + - title: '🚀 Features' + labels: + - 'feature' + - "type: enhancement" + - "type: new feature" + - "type: major" + - "type: minor" + - title: '💡 Improvements' + labels: + - 'improvement' + - "type: improvement" + + - title: '🐛 Bug Fixes' + labels: + - 'fix' + - 'bug' + - "type: bug" + - title: '📚 Documentation' + labels: + - 'docs' + - title: '🔧 Maintenance' + labels: + - 'maintenance' + - 'chore' + - 'refactor' + - 'style' + - 'test' + - 'ci' + - 'perf' + - 'build' + - "type: ci" + - "type: build" + - title: '⏪ Reverts' + labels: + - 'revert' +change-template: '- $TITLE @$AUTHOR (#$NUMBER)' +version-resolver: + major: + labels: + - 'type: major' + minor: + labels: + - 'type: minor' + patch: + labels: + - 'type: patch' + default: patch +template: | + ## What's Changed + + $CHANGES + + ## Contributors + + $CONTRIBUTORS \ No newline at end of file diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 0000000..30740f8 --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,61 @@ +{ + "extends": [ + "config:base" + ], + "labels": ["type: dependency upgrade"], + "packageRules": [ + { + "matchUpdateTypes": ["major"], + "enabled": false + }, + { + "matchPackagePatterns": ["*"], + "allowedVersions": "!/SNAPSHOT$/" + }, + { + "matchPackagePatterns": [ + "^org\\.codehaus\\.groovy" + ], + "groupName": "groovy monorepo" + }, + { + "matchPackageNames": [ + "org.grails:grails-bom", + "org.grails:grails-bootstrap", + "org.grails:grails-codecs", + "org.grails:grails-console", + "org.grails:grails-core", + "org.grails:grails-databinding", + "org.grails:grails-dependencies", + "org.grails:grails-docs", + "org.grails:grails-encoder", + "org.grails:grails-gradle-model", + "org.grails:grails-logging", + "org.grails:grails-plugin-codecs", + "org.grails:grails-plugin-controllers", + "org.grails:grails-plugin-databinding", + "org.grails:grails-plugin-datasource", + "org.grails:grails-plugin-domain-class", + "org.grails:grails-plugin-i18n", + "org.grails:grails-plugin-interceptors", + "org.grails:grails-plugin-mimetypes", + "org.grails:grails-plugin-rest", + "org.grails:grails-plugin-services", + "org.grails:grails-plugin-url-mappings", + "org.grails:grails-plugin-url-validation", + "org.grails:grails-shell", + "org.grails:grails-spring", + "org.grails:grails-test", + "org.grails:grails-validation", + "org.grails:grails-web", + "org.grails:grails-web-boot", + "org.grails:grails-web-common", + "org.grails:grails-web-databinding", + "org.grails:grails-web-fileupload", + "org.grails:grails-web-mvc", + "org.grails:grails-web-url-mappings" + ], + "groupName": "grails monorepo" + } + ] +} \ No newline at end of file diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 0000000..5d525a0 --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,71 @@ +name: "Java CI" +on: + push: + branches: + - '[4-9]+.[0-9]+.x' + pull_request: + branches: + - '[4-9]+.[0-9]+.x' + workflow_dispatch: +jobs: + test_project: + name: "Test Project" + runs-on: ubuntu-24.04 + strategy: + fail-fast: true + matrix: + java: [17, 21] + steps: + - name: "📥 Checkout repository" + uses: actions/checkout@v4 + - name: "☕️ Setup JDK" + uses: actions/setup-java@v4 + with: + java-version: ${{ matrix.java }} + distribution: liberica + - name: "🐘 Setup Gradle" + uses: gradle/actions/setup-gradle@v4 + with: + develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }} + - name: "🏃 Run tests" + run: ./gradlew check + - name: "🏃 Run integration tests" + working-directory: ./examples/testapp1 + run: ./gradlew integrationTest + publish_snapshot: + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + name: "Build Project and Publish Snapshot release" + needs: test_project + runs-on: ubuntu-24.04 + permissions: + contents: write # updates gh-pages branch + packages: write # publishes snapshot to GitHub Packages + steps: + - name: "📥 Checkout repository" + uses: actions/checkout@v4 + - name: "☕️ Setup JDK" + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: liberica + - name: "🐘 Setup Gradle" + uses: gradle/actions/setup-gradle@v4 + with: + develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }} + - name: "🔨 Build Project" + run: ./gradlew build + - name: "📤 Publish Snapshot version to Artifactory (repo.grails.org)" + env: + GRAILS_PUBLISH_RELEASE: 'false' + MAVEN_PUBLISH_USERNAME: ${{ secrets.MAVEN_PUBLISH_USERNAME }} + MAVEN_PUBLISH_PASSWORD: ${{ secrets.MAVEN_PUBLISH_PASSWORD }} + MAVEN_PUBLISH_URL: 'https://repo.grails.org/artifactory/plugins3-snapshots-local' + run: ./gradlew publish + - name: "📖 Generate Snapshot Documentation" + run: ./gradlew docs + - name: "📤 Publish Snapshot Documentation to Github Pages" + uses: apache/grails-github-actions/deploy-github-pages@asf + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GRADLE_PUBLISH_RELEASE: 'false' + SOURCE_FOLDER: build/docs \ No newline at end of file diff --git a/.github/workflows/release-notes.yml b/.github/workflows/release-notes.yml new file mode 100644 index 0000000..e41d6b4 --- /dev/null +++ b/.github/workflows/release-notes.yml @@ -0,0 +1,23 @@ +name: "Release Drafter" +on: + issues: + types: [closed, reopened] + push: + branches: + - master + - '[4-9]+.[0-9]+.x' + pull_request: + types: [opened, reopened, synchronize] + pull_request_target: + types: [opened, reopened, synchronize] +jobs: + update_release_draft: + permissions: + contents: write + pull-requests: write + runs-on: ubuntu-24.04 + steps: + - name: "📝 Update Release Draft" + uses: release-drafter/release-drafter@v6 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..ab000b1 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,130 @@ +name: Release +on: + release: + types: [ published ] +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + JAVA_VERSION: '17.0.15' # this must be a specific version for reproducible builds + RELEASE_TAG_PREFIX: 'v' +jobs: + publish: + permissions: + packages: read # pre-release workflow + contents: write # to create release + issues: write # to modify milestones + runs-on: ubuntu-latest + outputs: + release_version: ${{ steps.release_version.outputs.value }} + extract_repository_name: ${{ steps.extract_repository_name.outputs.repository_name }} + steps: + - name: "📝 Store the current release version" + id: release_version + run: | + export RELEASE_VERSION="${{ github.ref_name }}" + export RELEASE_VERSION=${RELEASE_VERSION:${#RELEASE_TAG_PREFIX}} + echo "Found Release Version: ${RELEASE_VERSION}" + echo "value=${RELEASE_VERSION}" >> $GITHUB_OUTPUT + - name: "Extract repository name" + id: extract_repository_name + run: | + echo "repository_name=${GITHUB_REPOSITORY##*/}" >> $GITHUB_OUTPUT + - name: "📥 Checkout the repository" + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ref: v${{ steps.release_version.outputs.value }} + - name: 'Ensure Common Build Date' # to ensure a reproducible build + run: echo "SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)" >> "$GITHUB_ENV" + - name: "Ensure source files use common date" + run: | + find . -depth \( -type f -o -type d \) -exec touch -d "@${SOURCE_DATE_EPOCH}" {} + + - name: "☕️ Setup JDK" + uses: actions/setup-java@v4 + with: + distribution: liberica + java-version: ${{ env.JAVA_VERSION }} + - name: "🐘 Setup Gradle" + uses: gradle/actions/setup-gradle@v4 + - name: "⚙️ Run pre-release" + uses: grails/github-actions/pre-release@asf + env: + RELEASE_VERSION: ${{ steps.release_version.outputs.value }} + - name: "🔐 Generate key file for artifact signing" + env: + SECRING_FILE: ${{ secrets.SECRING_FILE }} + run: | + printf "%s" "$SECRING_FILE" | base64 -d > "${{ github.workspace }}/secring.gpg" + - name: "🧩 Run Assemble" + id: assemble + run: | + ./gradlew -U assemble -Psigning.secretKeyRingFile=${{ github.workspace }}/secring.gpg -Psigning.keyId=${{ secrets.SIGNING_KEY }} + env: + GRAILS_PUBLISH_RELEASE: 'true' + SIGNING_KEY: ${{ secrets.SIGNING_KEY }} + SIGNING_PASSPHRASE: ${{ secrets.SIGNING_PASSPHRASE }} + - name: "📤 Publish to Maven Central" + env: + GRAILS_PUBLISH_RELEASE: 'true' + NEXUS_PUBLISH_USERNAME: ${{ secrets.NEXUS_PUBLISH_USERNAME }} + NEXUS_PUBLISH_PASSWORD: ${{ secrets.NEXUS_PUBLISH_PASSWORD }} + NEXUS_PUBLISH_URL: 'https://ossrh-staging-api.central.sonatype.com/service/local/' + NEXUS_PUBLISH_DESCRIPTION: '${{ steps.extract_repository_name.outputs.repository_name }}:${{ steps.release_version.outputs.value }}' + SIGNING_KEY: ${{ secrets.SIGNING_KEY }} + SIGNING_PASSPHRASE: ${{ secrets.SIGNING_PASSPHRASE }} + run: > + ./gradlew + -Psigning.keyId=${{ secrets.SIGNING_KEY }} + -Psigning.secretKeyRingFile=${{ github.workspace }}/secring.gpg + publishMavenPublicationToSonatypeRepository + closeSonatypeStagingRepository + - name: "Generate Build Date file" + run: echo "$SOURCE_DATE_EPOCH" >> build/BUILD_DATE.txt + - name: "Upload Build Date file" + uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 + with: + files: build/BUILD_DATE.txt + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + release: + needs: publish + runs-on: ubuntu-latest + environment: release + permissions: + contents: write + issues: write + pull-requests: write + steps: + - name: "📥 Checkout repository" + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + ref: v${{ needs.publish.outputs.release_version }} + - name: "☕️ Setup JDK" + uses: actions/setup-java@v4 + with: + distribution: liberica + java-version: ${{ env.JAVA_VERSION }} + - name: "🐘 Setup Gradle" + uses: gradle/actions/setup-gradle@v4 + - name: "📤 Release staging repository" + env: + GRAILS_PUBLISH_RELEASE: 'true' + NEXUS_PUBLISH_USERNAME: ${{ secrets.NEXUS_PUBLISH_USERNAME }} + NEXUS_PUBLISH_PASSWORD: ${{ secrets.NEXUS_PUBLISH_PASSWORD }} + NEXUS_PUBLISH_URL: 'https://ossrh-staging-api.central.sonatype.com/service/local/' + NEXUS_PUBLISH_DESCRIPTION: '${{ needs.publish.outputs.extract_repository_name }}:${{ needs.publish.outputs.release_version }}' + run: > + ./gradlew + findSonatypeStagingRepository + releaseSonatypeStagingRepository + - name: "📖 Generate Documentation" + run: ./gradlew docs + - name: "📤 Publish Documentation to Github Pages" + uses: apache/grails-github-actions/deploy-github-pages@asf + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GRADLE_PUBLISH_RELEASE: 'true' + SOURCE_FOLDER: build/docs + VERSION: ${{ needs.publish.outputs.release_version }} + - name: "⚙️ Run post-release" + uses: apache/grails-github-actions/post-release@asf diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index f1e4dcf..0000000 --- a/.travis.yml +++ /dev/null @@ -1,21 +0,0 @@ -sudo: false -cache: - directories: - - $HOME/.gradle -language: groovy -jdk: -- openjdk17 -before_script: -- rm -rf target -script: ./travis-build.sh -env: - global: - - GIT_NAME="Graeme Rocher" - - GIT_EMAIL="graeme.rocher@gmail.com" - - secure: qutwo7+0pfBh2fo2NrTdMdhK6Z7jGAGmfQUUms5IewycAEcO/XYD61uM9Om8y03puBXeo2aoN9YUtF5C+iou6KaGnLIMNDE6E5h9f36fTUk3RebwwV7cgsyzn4Zzma+EYYw6S2KF0Kj+JHPNDnlEdYLdmVJ1eq4KXVA17t5F8Eg= - - secure: G5bnlZc73YxAy9acnFuHpsEQ/JeKIawwT4pzv9S8ZIshVYdELWqBZiiDmiDiS1x4WhT1J9THWWy3/0Rp6taYA2IxNRgu4f9kPlv6m+Yb4pCl9E3zDfmdd1nnPeKpL7vrxawWQnF3PqqTX6qkY3PPiIdVUXdN1flUQOC0RoBXKBE= - - secure: IdiQoWkRWpDrWLNdROmGUgfdKGvccpozSXaicc3KV+Qe/ASyIKq54+oe196yvj5kTzkQqSkcHlt7icHuQfJBNjNCXhwP0TFoE55GoWzNmrJaxmr9jcvXOJzRicWp1ZrzksuoBiLxxyx2mfr3eSR7ByIyBN0aOY8n6Zcda5kE9wY= - - secure: Y9QjAlhmbRnE94ylUkg2dkOZh4u7aBgE9vol06dya/sjo8PpTtcMx1R5MNPPcPglvCiv0mmnNIu5QWPKWnWd+hp7wEI8NdLze8WL1iLaSIETtimrfuaMxRB8etFSsm9cs8B5SLbpNCqc0uCrA86Rts4hSQTrFIYJ416Ft/E8WSE= - - secure: o+BWtaPb6AldRYXHU15rBN/p/FIvEbar36LahiiXLKZ+2gTanBZAiDG6E/JuXRzVFqEMhOt8qvUXhpXXcxqHRvRVZqF0oqZJ8ratKwNB3W07ajEKMH8axK+Hxt/2P12Bk62ttDGxI/aFeH74RbQtNmFc+gLTS3O1QoLDLpqlVgk= - - secure: MBg9I3LeXHMhjhXkf+b8Gyy6afTUHfpsaK5IKXPJr9AycSx8r/CnFQTesdGFcrIhLfDYCmxL7YcA+/Fj9fOhu2bUo9rysG5pOlLmnU7DE56awIeMcdYB7ecty8mWJRSS4MJWsZ8I3kbncIVRORTvfgZfjh5WdmZaCKs11hX9x28= - - secure: SQc5o2xCxsuz8tlfBrxue7tDjanQ6NiI5/khYYc+7avECv+lH0W1b2Ho6G5ydk3eHQVxrrlF8LVrOeRlxg11UFcSmGoe6v3FO7KzvIXlZgHpq1CAR2Mojfp0YFXrAwvKetbGpiUOI+bMk5RCgUam8f1SWCkubwhQDXOO/T8I5a4= diff --git a/travis-build.sh b/travis-build.sh deleted file mode 100755 index 7c01178..0000000 --- a/travis-build.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/bin/bash -set -e -rm -rf *.zip -./gradlew clean test assemble - -filename=$(find build/libs -name "*.jar" | head -1) -filename=$(basename "$filename") - -EXIT_STATUS=0 -echo "Publishing archives for branch $TRAVIS_BRANCH" -if [[ -n $TRAVIS_TAG ]] || [[ $TRAVIS_BRANCH == 'master' && $TRAVIS_PULL_REQUEST == 'false' ]]; then - - echo "Publishing archives" - - if [[ -n $TRAVIS_TAG ]]; then - ./gradlew bintrayUpload || EXIT_STATUS=$? - else - ./gradlew publish || EXIT_STATUS=$? - fi - - ./gradlew docs || EXIT_STATUS=$? - - git config --global user.name "$GIT_NAME" - git config --global user.email "$GIT_EMAIL" - git config --global credential.helper "store --file=~/.git-credentials" - echo "https://$GH_TOKEN:@github.com" > ~/.git-credentials - - git clone https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git -b gh-pages gh-pages --single-branch > /dev/null - cd gh-pages - - # If this is the master branch then update the snapshot - if [[ $TRAVIS_BRANCH == 'master' ]]; then - mkdir -p snapshot - cp -r ../build/docs/manual/. ./snapshot/ - - git add snapshot/* - fi - - # If there is a tag present then this becomes the latest - if [[ -n $TRAVIS_TAG ]]; then - mkdir -p latest - cp -r ../build/docs/manual/. ./latest/ - git add latest/* - - version="$TRAVIS_TAG" - version=${version:1} - majorVersion=${version:0:4} - majorVersion="${majorVersion}x" - - mkdir -p "$version" - cp -r ../build/docs/manual/. "./$version/" - git add "$version/*" - - mkdir -p "$majorVersion" - cp -r ../build/docs/manual/. "./$majorVersion/" - git add "$majorVersion/*" - - fi - - git commit -a -m "Updating docs for Travis build: https://travis-ci.org/$TRAVIS_REPO_SLUG/builds/$TRAVIS_BUILD_ID" - git push origin HEAD - cd .. - rm -rf gh-pages -fi - -exit $EXIT_STATUS \ No newline at end of file From b2d855cbb4821f06cc37e7550531cddb13fdbc08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Berg=20Glasius?= Date: Tue, 30 Sep 2025 10:42:36 +0200 Subject: [PATCH 05/12] Cleanup (remove obsolete files) --- plugins/pdf-plugin-test/.classpath | 14 - plugins/pdf-plugin-test/.project | 19 - ....codehaus.groovy.eclipse.preferences.prefs | 3 - .../PdfPluginTestGrailsPlugin.groovy | 50 -- .../pdf-plugin-test/application.properties | 6 - .../grails-app/conf/BuildConfig.groovy | 31 - .../grails-app/conf/DataSource.groovy | 32 - .../grails-app/conf/UrlMappings.groovy | 11 - .../grails-app/views/_plugin-pdf.gsp | 8 - .../grails-app/views/error.gsp | 54 -- .../web-app/WEB-INF/applicationContext.xml | 42 -- .../web-app/WEB-INF/sitemesh.xml | 14 - .../pdf-plugin-test/web-app/WEB-INF/tld/c.tld | 563 --------------- .../web-app/WEB-INF/tld/fmt.tld | 671 ------------------ .../web-app/WEB-INF/tld/grails.tld | 551 -------------- .../web-app/WEB-INF/tld/spring.tld | 311 -------- web-app/WEB-INF/applicationContext.xml | 42 -- web-app/WEB-INF/sitemesh.xml | 14 - web-app/WEB-INF/tld/c.tld | 563 --------------- web-app/WEB-INF/tld/fmt.tld | 671 ------------------ web-app/WEB-INF/tld/grails.tld | 551 -------------- web-app/WEB-INF/tld/spring.tld | 311 -------- web-app/images/grails.png | Bin 21146 -> 0 bytes 23 files changed, 4532 deletions(-) delete mode 100644 plugins/pdf-plugin-test/.classpath delete mode 100644 plugins/pdf-plugin-test/.project delete mode 100644 plugins/pdf-plugin-test/.settings/org.codehaus.groovy.eclipse.preferences.prefs delete mode 100644 plugins/pdf-plugin-test/PdfPluginTestGrailsPlugin.groovy delete mode 100644 plugins/pdf-plugin-test/application.properties delete mode 100644 plugins/pdf-plugin-test/grails-app/conf/BuildConfig.groovy delete mode 100644 plugins/pdf-plugin-test/grails-app/conf/DataSource.groovy delete mode 100644 plugins/pdf-plugin-test/grails-app/conf/UrlMappings.groovy delete mode 100644 plugins/pdf-plugin-test/grails-app/views/_plugin-pdf.gsp delete mode 100644 plugins/pdf-plugin-test/grails-app/views/error.gsp delete mode 100644 plugins/pdf-plugin-test/web-app/WEB-INF/applicationContext.xml delete mode 100644 plugins/pdf-plugin-test/web-app/WEB-INF/sitemesh.xml delete mode 100644 plugins/pdf-plugin-test/web-app/WEB-INF/tld/c.tld delete mode 100644 plugins/pdf-plugin-test/web-app/WEB-INF/tld/fmt.tld delete mode 100644 plugins/pdf-plugin-test/web-app/WEB-INF/tld/grails.tld delete mode 100644 plugins/pdf-plugin-test/web-app/WEB-INF/tld/spring.tld delete mode 100644 web-app/WEB-INF/applicationContext.xml delete mode 100644 web-app/WEB-INF/sitemesh.xml delete mode 100644 web-app/WEB-INF/tld/c.tld delete mode 100644 web-app/WEB-INF/tld/fmt.tld delete mode 100644 web-app/WEB-INF/tld/grails.tld delete mode 100644 web-app/WEB-INF/tld/spring.tld delete mode 100644 web-app/images/grails.png diff --git a/plugins/pdf-plugin-test/.classpath b/plugins/pdf-plugin-test/.classpath deleted file mode 100644 index ebd5145..0000000 --- a/plugins/pdf-plugin-test/.classpath +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/plugins/pdf-plugin-test/.project b/plugins/pdf-plugin-test/.project deleted file mode 100644 index fbb2f05..0000000 --- a/plugins/pdf-plugin-test/.project +++ /dev/null @@ -1,19 +0,0 @@ - - - pdf-plugin-test - - - - - - org.eclipse.jdt.core.javabuilder - - - - - - com.springsource.sts.grails.core.nature - org.eclipse.jdt.groovy.core.groovyNature - org.eclipse.jdt.core.javanature - - diff --git a/plugins/pdf-plugin-test/.settings/org.codehaus.groovy.eclipse.preferences.prefs b/plugins/pdf-plugin-test/.settings/org.codehaus.groovy.eclipse.preferences.prefs deleted file mode 100644 index bf339c7..0000000 --- a/plugins/pdf-plugin-test/.settings/org.codehaus.groovy.eclipse.preferences.prefs +++ /dev/null @@ -1,3 +0,0 @@ -#Created by grails -eclipse.preferences.version=1 -groovy.dont.generate.class.files=true diff --git a/plugins/pdf-plugin-test/PdfPluginTestGrailsPlugin.groovy b/plugins/pdf-plugin-test/PdfPluginTestGrailsPlugin.groovy deleted file mode 100644 index a488d88..0000000 --- a/plugins/pdf-plugin-test/PdfPluginTestGrailsPlugin.groovy +++ /dev/null @@ -1,50 +0,0 @@ -class PdfPluginTestGrailsPlugin { - // the plugin version - def version = "0.1" - // the version or versions of Grails the plugin is designed for - def grailsVersion = "1.2.0 > *" - // the other plugins this plugin depends on - def dependsOn = [:] - // resources that are excluded from plugin packaging - def pluginExcludes = [ - "grails-app/views/error.gsp" - ] - - // TODO Fill in these fields - def author = "Your name" - def authorEmail = "" - def title = "Plugin summary/headline" - def description = '''\\ -Brief description of the plugin. -''' - - // URL to the plugin's documentation - def documentation = "http://grails.org/plugin/pdf-plugin-test" - - def doWithWebDescriptor = { xml -> - // TODO Implement additions to web.xml (optional), this event occurs before - } - - def doWithSpring = { - // TODO Implement runtime spring config (optional) - } - - def doWithDynamicMethods = { ctx -> - // TODO Implement registering dynamic methods to classes (optional) - } - - def doWithApplicationContext = { applicationContext -> - // TODO Implement post initialization spring config (optional) - } - - def onChange = { event -> - // TODO Implement code that is executed when any artefact that this plugin is - // watching is modified and reloaded. The event contains: event.source, - // event.application, event.manager, event.ctx, and event.plugin. - } - - def onConfigChange = { event -> - // TODO Implement code that is executed when the project configuration changes. - // The event is the same as for 'onChange'. - } -} diff --git a/plugins/pdf-plugin-test/application.properties b/plugins/pdf-plugin-test/application.properties deleted file mode 100644 index b17a930..0000000 --- a/plugins/pdf-plugin-test/application.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Grails Metadata file -#Mon Mar 22 17:46:49 EST 2010 -app.grails.version=1.2.0 -app.name=pdf-plugin-test -plugins.hibernate=1.2.0 -plugins.tomcat=1.2.0 diff --git a/plugins/pdf-plugin-test/grails-app/conf/BuildConfig.groovy b/plugins/pdf-plugin-test/grails-app/conf/BuildConfig.groovy deleted file mode 100644 index 2d9d6b4..0000000 --- a/plugins/pdf-plugin-test/grails-app/conf/BuildConfig.groovy +++ /dev/null @@ -1,31 +0,0 @@ -grails.project.class.dir = "target/classes" -grails.project.test.class.dir = "target/test-classes" -grails.project.test.reports.dir = "target/test-reports" -//grails.project.war.file = "target/${appName}-${appVersion}.war" -grails.project.dependency.resolution = { - // inherit Grails' default dependencies - inherits( "global" ) { - // uncomment to disable ehcache - // excludes 'ehcache' - } - log "warn" // log level of Ivy resolver, either 'error', 'warn', 'info', 'debug' or 'verbose' - repositories { - grailsPlugins() - grailsHome() - - // uncomment the below to enable remote dependency resolution - // from public Maven repositories - //mavenLocal() - //mavenCentral() - //mavenRepo "http://snapshots.repository.codehaus.org" - //mavenRepo "http://repository.codehaus.org" - //mavenRepo "http://download.java.net/maven/2/" - //mavenRepo "http://repository.jboss.com/maven2/" - } - dependencies { - // specify dependencies here under either 'build', 'compile', 'runtime', 'test' or 'provided' scopes eg. - - // runtime 'mysql:mysql-connector-java:5.1.5' - } - -} \ No newline at end of file diff --git a/plugins/pdf-plugin-test/grails-app/conf/DataSource.groovy b/plugins/pdf-plugin-test/grails-app/conf/DataSource.groovy deleted file mode 100644 index 20bd881..0000000 --- a/plugins/pdf-plugin-test/grails-app/conf/DataSource.groovy +++ /dev/null @@ -1,32 +0,0 @@ -dataSource { - pooled = true - driverClassName = "org.hsqldb.jdbcDriver" - username = "sa" - password = "" -} -hibernate { - cache.use_second_level_cache=true - cache.use_query_cache=true - cache.provider_class='net.sf.ehcache.hibernate.EhCacheProvider' -} -// environment specific settings -environments { - development { - dataSource { - dbCreate = "create-drop" // one of 'create', 'create-drop','update' - url = "jdbc:hsqldb:mem:devDB" - } - } - test { - dataSource { - dbCreate = "update" - url = "jdbc:hsqldb:mem:testDb" - } - } - production { - dataSource { - dbCreate = "update" - url = "jdbc:hsqldb:file:prodDb;shutdown=true" - } - } -} \ No newline at end of file diff --git a/plugins/pdf-plugin-test/grails-app/conf/UrlMappings.groovy b/plugins/pdf-plugin-test/grails-app/conf/UrlMappings.groovy deleted file mode 100644 index 41daf57..0000000 --- a/plugins/pdf-plugin-test/grails-app/conf/UrlMappings.groovy +++ /dev/null @@ -1,11 +0,0 @@ -class UrlMappings { - static mappings = { - "/$controller/$action?/$id?"{ - constraints { - // apply constraints here - } - } - "/"(view:"/index") - "500"(view:'/error') - } -} diff --git a/plugins/pdf-plugin-test/grails-app/views/_plugin-pdf.gsp b/plugins/pdf-plugin-test/grails-app/views/_plugin-pdf.gsp deleted file mode 100644 index 7d279c8..0000000 --- a/plugins/pdf-plugin-test/grails-app/views/_plugin-pdf.gsp +++ /dev/null @@ -1,8 +0,0 @@ - - - - -

This is a PDF from a plugin!

-

${var}

- - \ No newline at end of file diff --git a/plugins/pdf-plugin-test/grails-app/views/error.gsp b/plugins/pdf-plugin-test/grails-app/views/error.gsp deleted file mode 100644 index cfc512a..0000000 --- a/plugins/pdf-plugin-test/grails-app/views/error.gsp +++ /dev/null @@ -1,54 +0,0 @@ - - - Grails Runtime Exception - - - - -

Grails Runtime Exception

-

Error Details

- -
- Error ${request.'javax.servlet.error.status_code'}: ${request.'javax.servlet.error.message'.encodeAsHTML()}
- Servlet: ${request.'javax.servlet.error.servlet_name'}
- URI: ${request.'javax.servlet.error.request_uri'}
- - Exception Message: ${exception.message?.encodeAsHTML()}
- Caused by: ${exception.cause?.message?.encodeAsHTML()}
- Class: ${exception.className}
- At Line: [${exception.lineNumber}]
- Code Snippet:
-
- - ${cs?.encodeAsHTML()}
-
-
-
-
- -

Stack Trace

-
-
${it.encodeAsHTML()}
-
-
- - \ No newline at end of file diff --git a/plugins/pdf-plugin-test/web-app/WEB-INF/applicationContext.xml b/plugins/pdf-plugin-test/web-app/WEB-INF/applicationContext.xml deleted file mode 100644 index 6f42796..0000000 --- a/plugins/pdf-plugin-test/web-app/WEB-INF/applicationContext.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - Grails application factory bean - - - - - - A bean that manages Grails plugins - - - - - - - - - - - - - - - - - - classpath*:**/grails-app/**/*.groovy - - - - - - utf-8 - - - \ No newline at end of file diff --git a/plugins/pdf-plugin-test/web-app/WEB-INF/sitemesh.xml b/plugins/pdf-plugin-test/web-app/WEB-INF/sitemesh.xml deleted file mode 100644 index a547b41..0000000 --- a/plugins/pdf-plugin-test/web-app/WEB-INF/sitemesh.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/plugins/pdf-plugin-test/web-app/WEB-INF/tld/c.tld b/plugins/pdf-plugin-test/web-app/WEB-INF/tld/c.tld deleted file mode 100644 index 22698c9..0000000 --- a/plugins/pdf-plugin-test/web-app/WEB-INF/tld/c.tld +++ /dev/null @@ -1,563 +0,0 @@ - - - - - JSTL 1.1 core library - JSTL core - 1.1 - c - http://java.sun.com/jsp/jstl/core - - - - Provides core validation features for JSTL tags. - - - org.apache.taglibs.standard.tlv.JstlCoreTLV - - - - - - Catches any Throwable that occurs in its body and optionally - exposes it. - - catch - org.apache.taglibs.standard.tag.common.core.CatchTag - JSP - - -Name of the exported scoped variable for the -exception thrown from a nested action. The type of the -scoped variable is the type of the exception thrown. - - var - false - false - - - - - - Simple conditional tag that establishes a context for - mutually exclusive conditional operations, marked by - <when> and <otherwise> - - choose - org.apache.taglibs.standard.tag.common.core.ChooseTag - JSP - - - - - Simple conditional tag, which evalutes its body if the - supplied condition is true and optionally exposes a Boolean - scripting variable representing the evaluation of this condition - - if - org.apache.taglibs.standard.tag.rt.core.IfTag - JSP - - -The test condition that determines whether or -not the body content should be processed. - - test - true - true - boolean - - - -Name of the exported scoped variable for the -resulting value of the test condition. The type -of the scoped variable is Boolean. - - var - false - false - - - -Scope for var. - - scope - false - false - - - - - - Retrieves an absolute or relative URL and exposes its contents - to either the page, a String in 'var', or a Reader in 'varReader'. - - import - org.apache.taglibs.standard.tag.rt.core.ImportTag - org.apache.taglibs.standard.tei.ImportTEI - JSP - - -The URL of the resource to import. - - url - true - true - - - -Name of the exported scoped variable for the -resource's content. The type of the scoped -variable is String. - - var - false - false - - - -Scope for var. - - scope - false - false - - - -Name of the exported scoped variable for the -resource's content. The type of the scoped -variable is Reader. - - varReader - false - false - - - -Name of the context when accessing a relative -URL resource that belongs to a foreign -context. - - context - false - true - - - -Character encoding of the content at the input -resource. - - charEncoding - false - true - - - - - - The basic iteration tag, accepting many different - collection types and supporting subsetting and other - functionality - - forEach - org.apache.taglibs.standard.tag.rt.core.ForEachTag - org.apache.taglibs.standard.tei.ForEachTEI - JSP - - -Collection of items to iterate over. - - items - false - true - java.lang.Object - - - -If items specified: -Iteration begins at the item located at the -specified index. First item of the collection has -index 0. -If items not specified: -Iteration begins with index set at the value -specified. - - begin - false - true - int - - - -If items specified: -Iteration ends at the item located at the -specified index (inclusive). -If items not specified: -Iteration ends when index reaches the value -specified. - - end - false - true - int - - - -Iteration will only process every step items of -the collection, starting with the first one. - - step - false - true - int - - - -Name of the exported scoped variable for the -current item of the iteration. This scoped -variable has nested visibility. Its type depends -on the object of the underlying collection. - - var - false - false - - - -Name of the exported scoped variable for the -status of the iteration. Object exported is of type -javax.servlet.jsp.jstl.core.LoopTagStatus. This scoped variable has nested -visibility. - - varStatus - false - false - - - - - - Iterates over tokens, separated by the supplied delimeters - - forTokens - org.apache.taglibs.standard.tag.rt.core.ForTokensTag - JSP - - -String of tokens to iterate over. - - items - true - true - java.lang.String - - - -The set of delimiters (the characters that -separate the tokens in the string). - - delims - true - true - java.lang.String - - - -Iteration begins at the token located at the -specified index. First token has index 0. - - begin - false - true - int - - - -Iteration ends at the token located at the -specified index (inclusive). - - end - false - true - int - - - -Iteration will only process every step tokens -of the string, starting with the first one. - - step - false - true - int - - - -Name of the exported scoped variable for the -current item of the iteration. This scoped -variable has nested visibility. - - var - false - false - - - -Name of the exported scoped variable for the -status of the iteration. Object exported is of -type -javax.servlet.jsp.jstl.core.LoopTag -Status. This scoped variable has nested -visibility. - - varStatus - false - false - - - - - - Like <%= ... >, but for expressions. - - out - org.apache.taglibs.standard.tag.rt.core.OutTag - JSP - - -Expression to be evaluated. - - value - true - true - - - -Default value if the resulting value is null. - - default - false - true - - - -Determines whether characters <,>,&,'," in the -resulting string should be converted to their -corresponding character entity codes. Default value is -true. - - escapeXml - false - true - - - - - - - Subtag of <choose> that follows <when> tags - and runs only if all of the prior conditions evaluated to - 'false' - - otherwise - org.apache.taglibs.standard.tag.common.core.OtherwiseTag - JSP - - - - - Adds a parameter to a containing 'import' tag's URL. - - param - org.apache.taglibs.standard.tag.rt.core.ParamTag - JSP - - -Name of the query string parameter. - - name - true - true - - - -Value of the parameter. - - value - false - true - - - - - - Redirects to a new URL. - - redirect - org.apache.taglibs.standard.tag.rt.core.RedirectTag - JSP - - -The URL of the resource to redirect to. - - url - false - true - - - -Name of the context when redirecting to a relative URL -resource that belongs to a foreign context. - - context - false - true - - - - - - Removes a scoped variable (from a particular scope, if specified). - - remove - org.apache.taglibs.standard.tag.common.core.RemoveTag - empty - - -Name of the scoped variable to be removed. - - var - true - false - - - -Scope for var. - - scope - false - false - - - - - - Sets the result of an expression evaluation in a 'scope' - - set - org.apache.taglibs.standard.tag.rt.core.SetTag - JSP - - -Name of the exported scoped variable to hold the value -specified in the action. The type of the scoped variable is -whatever type the value expression evaluates to. - - var - false - false - - - -Expression to be evaluated. - - value - false - true - - - -Target object whose property will be set. Must evaluate to -a JavaBeans object with setter property property, or to a -java.util.Map object. - - target - false - true - - - -Name of the property to be set in the target object. - - property - false - true - - - -Scope for var. - - scope - false - false - - - - - - Creates a URL with optional query parameters. - - url - org.apache.taglibs.standard.tag.rt.core.UrlTag - JSP - - -Name of the exported scoped variable for the -processed url. The type of the scoped variable is -String. - - var - false - false - - - -Scope for var. - - scope - false - false - - - -URL to be processed. - - value - false - true - - - -Name of the context when specifying a relative URL -resource that belongs to a foreign context. - - context - false - true - - - - - - Subtag of <choose> that includes its body if its - condition evalutes to 'true' - - when - org.apache.taglibs.standard.tag.rt.core.WhenTag - JSP - - -The test condition that determines whether or not the -body content should be processed. - - test - true - true - boolean - - - - diff --git a/plugins/pdf-plugin-test/web-app/WEB-INF/tld/fmt.tld b/plugins/pdf-plugin-test/web-app/WEB-INF/tld/fmt.tld deleted file mode 100644 index 3b9a54a..0000000 --- a/plugins/pdf-plugin-test/web-app/WEB-INF/tld/fmt.tld +++ /dev/null @@ -1,671 +0,0 @@ - - - - - JSTL 1.1 i18n-capable formatting library - JSTL fmt - 1.1 - fmt - http://java.sun.com/jsp/jstl/fmt - - - - Provides core validation features for JSTL tags. - - - org.apache.taglibs.standard.tlv.JstlFmtTLV - - - - - - Sets the request character encoding - - requestEncoding - org.apache.taglibs.standard.tag.rt.fmt.RequestEncodingTag - empty - - -Name of character encoding to be applied when -decoding request parameters. - - value - false - true - - - - - - Stores the given locale in the locale configuration variable - - setLocale - org.apache.taglibs.standard.tag.rt.fmt.SetLocaleTag - empty - - -A String value is interpreted as the -printable representation of a locale, which -must contain a two-letter (lower-case) -language code (as defined by ISO-639), -and may contain a two-letter (upper-case) -country code (as defined by ISO-3166). -Language and country codes must be -separated by hyphen (-) or underscore -(_). - - value - true - true - - - -Vendor- or browser-specific variant. -See the java.util.Locale javadocs for -more information on variants. - - variant - false - true - - - -Scope of the locale configuration variable. - - scope - false - false - - - - - - Specifies the time zone for any time formatting or parsing actions - nested in its body - - timeZone - org.apache.taglibs.standard.tag.rt.fmt.TimeZoneTag - JSP - - -The time zone. A String value is interpreted as -a time zone ID. This may be one of the time zone -IDs supported by the Java platform (such as -"America/Los_Angeles") or a custom time zone -ID (such as "GMT-8"). See -java.util.TimeZone for more information on -supported time zone formats. - - value - true - true - - - - - - Stores the given time zone in the time zone configuration variable - - setTimeZone - org.apache.taglibs.standard.tag.rt.fmt.SetTimeZoneTag - empty - - -The time zone. A String value is interpreted as -a time zone ID. This may be one of the time zone -IDs supported by the Java platform (such as -"America/Los_Angeles") or a custom time zone -ID (such as "GMT-8"). See java.util.TimeZone for -more information on supported time zone -formats. - - value - true - true - - - -Name of the exported scoped variable which -stores the time zone of type -java.util.TimeZone. - - var - false - false - - - -Scope of var or the time zone configuration -variable. - - scope - false - false - - - - - - Loads a resource bundle to be used by its tag body - - bundle - org.apache.taglibs.standard.tag.rt.fmt.BundleTag - JSP - - -Resource bundle base name. This is the bundle's -fully-qualified resource name, which has the same -form as a fully-qualified class name, that is, it uses -"." as the package component separator and does not -have any file type (such as ".class" or ".properties") -suffix. - - basename - true - true - - - -Prefix to be prepended to the value of the message -key of any nested <fmt:message> action. - - prefix - false - true - - - - - - Loads a resource bundle and stores it in the named scoped variable or - the bundle configuration variable - - setBundle - org.apache.taglibs.standard.tag.rt.fmt.SetBundleTag - empty - - -Resource bundle base name. This is the bundle's -fully-qualified resource name, which has the same -form as a fully-qualified class name, that is, it uses -"." as the package component separator and does not -have any file type (such as ".class" or ".properties") -suffix. - - basename - true - true - - - -Name of the exported scoped variable which stores -the i18n localization context of type -javax.servlet.jsp.jstl.fmt.LocalizationC -ontext. - - var - false - false - - - -Scope of var or the localization context -configuration variable. - - scope - false - false - - - - - - Maps key to localized message and performs parametric replacement - - message - org.apache.taglibs.standard.tag.rt.fmt.MessageTag - JSP - - -Message key to be looked up. - - key - false - true - - - -Localization context in whose resource -bundle the message key is looked up. - - bundle - false - true - - - -Name of the exported scoped variable -which stores the localized message. - - var - false - false - - - -Scope of var. - - scope - false - false - - - - - - Supplies an argument for parametric replacement to a containing - <message> tag - - param - org.apache.taglibs.standard.tag.rt.fmt.ParamTag - JSP - - -Argument used for parametric replacement. - - value - false - true - - - - - - Formats a numeric value as a number, currency, or percentage - - formatNumber - org.apache.taglibs.standard.tag.rt.fmt.FormatNumberTag - JSP - - -Numeric value to be formatted. - - value - false - true - - - -Specifies whether the value is to be -formatted as number, currency, or -percentage. - - type - false - true - - - -Custom formatting pattern. - - pattern - false - true - - - -ISO 4217 currency code. Applied only -when formatting currencies (i.e. if type is -equal to "currency"); ignored otherwise. - - currencyCode - false - true - - - -Currency symbol. Applied only when -formatting currencies (i.e. if type is equal -to "currency"); ignored otherwise. - - currencySymbol - false - true - - - -Specifies whether the formatted output -will contain any grouping separators. - - groupingUsed - false - true - - - -Maximum number of digits in the integer -portion of the formatted output. - - maxIntegerDigits - false - true - - - -Minimum number of digits in the integer -portion of the formatted output. - - minIntegerDigits - false - true - - - -Maximum number of digits in the -fractional portion of the formatted output. - - maxFractionDigits - false - true - - - -Minimum number of digits in the -fractional portion of the formatted output. - - minFractionDigits - false - true - - - -Name of the exported scoped variable -which stores the formatted result as a -String. - - var - false - false - - - -Scope of var. - - scope - false - false - - - - - - Parses the string representation of a number, currency, or percentage - - parseNumber - org.apache.taglibs.standard.tag.rt.fmt.ParseNumberTag - JSP - - -String to be parsed. - - value - false - true - - - -Specifies whether the string in the value -attribute should be parsed as a number, -currency, or percentage. - - type - false - true - - - -Custom formatting pattern that determines -how the string in the value attribute is to be -parsed. - - pattern - false - true - - - -Locale whose default formatting pattern (for -numbers, currencies, or percentages, -respectively) is to be used during the parse -operation, or to which the pattern specified -via the pattern attribute (if present) is -applied. - - parseLocale - false - true - - - -Specifies whether just the integer portion of -the given value should be parsed. - - integerOnly - false - true - - - -Name of the exported scoped variable which -stores the parsed result (of type -java.lang.Number). - - var - false - false - - - -Scope of var. - - scope - false - false - - - - - - Formats a date and/or time using the supplied styles and pattern - - formatDate - org.apache.taglibs.standard.tag.rt.fmt.FormatDateTag - empty - - -Date and/or time to be formatted. - - value - true - true - - - -Specifies whether the time, the date, or both -the time and date components of the given -date are to be formatted. - - type - false - true - - - -Predefined formatting style for dates. Follows -the semantics defined in class -java.text.DateFormat. Applied only -when formatting a date or both a date and -time (i.e. if type is missing or is equal to -"date" or "both"); ignored otherwise. - - dateStyle - false - true - - - -Predefined formatting style for times. Follows -the semantics defined in class -java.text.DateFormat. Applied only -when formatting a time or both a date and -time (i.e. if type is equal to "time" or "both"); -ignored otherwise. - - timeStyle - false - true - - - -Custom formatting style for dates and times. - - pattern - false - true - - - -Time zone in which to represent the formatted -time. - - timeZone - false - true - - - -Name of the exported scoped variable which -stores the formatted result as a String. - - var - false - false - - - -Scope of var. - - scope - false - false - - - - - - Parses the string representation of a date and/or time - - parseDate - org.apache.taglibs.standard.tag.rt.fmt.ParseDateTag - JSP - - -Date string to be parsed. - - value - false - true - - - -Specifies whether the date string in the -value attribute is supposed to contain a -time, a date, or both. - - type - false - true - - - -Predefined formatting style for days -which determines how the date -component of the date string is to be -parsed. Applied only when formatting a -date or both a date and time (i.e. if type -is missing or is equal to "date" or "both"); -ignored otherwise. - - dateStyle - false - true - - - -Predefined formatting styles for times -which determines how the time -component in the date string is to be -parsed. Applied only when formatting a -time or both a date and time (i.e. if type -is equal to "time" or "both"); ignored -otherwise. - - timeStyle - false - true - - - -Custom formatting pattern which -determines how the date string is to be -parsed. - - pattern - false - true - - - -Time zone in which to interpret any time -information in the date string. - - timeZone - false - true - - - -Locale whose predefined formatting styles -for dates and times are to be used during -the parse operation, or to which the -pattern specified via the pattern -attribute (if present) is applied. - - parseLocale - false - true - - - -Name of the exported scoped variable in -which the parsing result (of type -java.util.Date) is stored. - - var - false - false - - - -Scope of var. - - scope - false - false - - - - diff --git a/plugins/pdf-plugin-test/web-app/WEB-INF/tld/grails.tld b/plugins/pdf-plugin-test/web-app/WEB-INF/tld/grails.tld deleted file mode 100644 index 868ec1f..0000000 --- a/plugins/pdf-plugin-test/web-app/WEB-INF/tld/grails.tld +++ /dev/null @@ -1,551 +0,0 @@ - - - The Grails (Groovy on Rails) custom tag library - 0.2 - grails - http://grails.codehaus.org/tags - - - - link - org.codehaus.groovy.grails.web.taglib.jsp.JspLinkTag - JSP - - action - false - true - - - controller - false - true - - - id - false - true - - - url - false - true - - - params - false - true - - true - - - form - org.codehaus.groovy.grails.web.taglib.jsp.JspFormTag - JSP - - action - false - true - - - controller - false - true - - - id - false - true - - - url - false - true - - - method - true - true - - true - - - select - org.codehaus.groovy.grails.web.taglib.jsp.JspSelectTag - JSP - - name - true - true - - - value - false - true - - - optionKey - false - true - - - optionValue - false - true - - true - - - datePicker - org.codehaus.groovy.grails.web.taglib.jsp.JspDatePickerTag - empty - - name - true - true - - - value - false - true - - - precision - false - true - - false - - - currencySelect - org.codehaus.groovy.grails.web.taglib.jsp.JspCurrencySelectTag - empty - - name - true - true - - - value - false - true - - true - - - localeSelect - org.codehaus.groovy.grails.web.taglib.jsp.JspLocaleSelectTag - empty - - name - true - true - - - value - false - true - - true - - - timeZoneSelect - org.codehaus.groovy.grails.web.taglib.jsp.JspTimeZoneSelectTag - empty - - name - true - true - - - value - false - true - - true - - - checkBox - org.codehaus.groovy.grails.web.taglib.jsp.JspCheckboxTag - empty - - name - true - true - - - value - true - true - - true - - - hasErrors - org.codehaus.groovy.grails.web.taglib.jsp.JspHasErrorsTag - JSP - - model - false - true - - - bean - false - true - - - field - false - true - - false - - - eachError - org.codehaus.groovy.grails.web.taglib.jsp.JspEachErrorTag - JSP - - model - false - true - - - bean - false - true - - - field - false - true - - false - - - renderErrors - org.codehaus.groovy.grails.web.taglib.jsp.JspEachErrorTag - JSP - - model - false - true - - - bean - false - true - - - field - false - true - - - as - true - true - - false - - - message - org.codehaus.groovy.grails.web.taglib.jsp.JspMessageTag - JSP - - code - false - true - - - error - false - true - - - default - false - true - - false - - - remoteFunction - org.codehaus.groovy.grails.web.taglib.jsp.JspRemoteFunctionTag - empty - - before - false - true - - - after - false - true - - - action - false - true - - - controller - false - true - - - id - false - true - - - url - false - true - - - params - false - true - - - asynchronous - false - true - - - method - false - true - - - update - false - true - - - onSuccess - false - true - - - onFailure - false - true - - - onComplete - false - true - - - onLoading - false - true - - - onLoaded - false - true - - - onInteractive - false - true - - true - - - remoteLink - org.codehaus.groovy.grails.web.taglib.jsp.JspRemoteLinkTag - JSP - - before - false - true - - - after - false - true - - - action - false - true - - - controller - false - true - - - id - false - true - - - url - false - true - - - params - false - true - - - asynchronous - false - true - - - method - false - true - - - update - false - true - - - onSuccess - false - true - - - onFailure - false - true - - - onComplete - false - true - - - onLoading - false - true - - - onLoaded - false - true - - - onInteractive - false - true - - true - - - formRemote - org.codehaus.groovy.grails.web.taglib.jsp.JspFormRemoteTag - JSP - - before - false - true - - - after - false - true - - - action - false - true - - - controller - false - true - - - id - false - true - - - url - false - true - - - params - false - true - - - asynchronous - false - true - - - method - false - true - - - update - false - true - - - onSuccess - false - true - - - onFailure - false - true - - - onComplete - false - true - - - onLoading - false - true - - - onLoaded - false - true - - - onInteractive - false - true - - true - - - invokeTag - org.codehaus.groovy.grails.web.taglib.jsp.JspInvokeGrailsTagLibTag - JSP - - it - java.lang.Object - true - NESTED - - - tagName - true - true - - true - - - diff --git a/plugins/pdf-plugin-test/web-app/WEB-INF/tld/spring.tld b/plugins/pdf-plugin-test/web-app/WEB-INF/tld/spring.tld deleted file mode 100644 index 1bc7091..0000000 --- a/plugins/pdf-plugin-test/web-app/WEB-INF/tld/spring.tld +++ /dev/null @@ -1,311 +0,0 @@ - - - - - - 1.1.1 - - 1.2 - - Spring - - http://www.springframework.org/tags - - Spring Framework JSP Tag Library. Authors: Rod Johnson, Juergen Hoeller - - - - - htmlEscape - org.springframework.web.servlet.tags.HtmlEscapeTag - JSP - - - Sets default HTML escape value for the current page. - Overrides a "defaultHtmlEscape" context-param in web.xml, if any. - - - - defaultHtmlEscape - true - true - - - - - - - - escapeBody - org.springframework.web.servlet.tags.EscapeBodyTag - JSP - - - Escapes its enclosed body content, applying HTML escaping and/or JavaScript escaping. - The HTML escaping flag participates in a page-wide or application-wide setting - (i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml). - - - - htmlEscape - false - true - - - - javaScriptEscape - false - true - - - - - - - - message - org.springframework.web.servlet.tags.MessageTag - JSP - - - Retrieves the message with the given code, or text if code isn't resolvable. - The HTML escaping flag participates in a page-wide or application-wide setting - (i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml). - - - - code - false - true - - - - arguments - false - true - - - - text - false - true - - - - var - false - true - - - - scope - false - true - - - - htmlEscape - false - true - - - - javaScriptEscape - false - true - - - - - - - - theme - org.springframework.web.servlet.tags.ThemeTag - JSP - - - Retrieves the theme message with the given code, or text if code isn't resolvable. - The HTML escaping flag participates in a page-wide or application-wide setting - (i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml). - - - - code - false - true - - - - arguments - false - true - - - - text - false - true - - - - var - false - true - - - - scope - false - true - - - - htmlEscape - false - true - - - - javaScriptEscape - false - true - - - - - - - - hasBindErrors - org.springframework.web.servlet.tags.BindErrorsTag - JSP - - - Provides Errors instance in case of bind errors. - The HTML escaping flag participates in a page-wide or application-wide setting - (i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml). - - - - errors - org.springframework.validation.Errors - - - - name - true - true - - - - htmlEscape - false - true - - - - - - - - nestedPath - org.springframework.web.servlet.tags.NestedPathTag - JSP - - - Sets a nested path to be used by the bind tag's path. - - - - nestedPath - java.lang.String - - - - path - true - true - - - - - - - - bind - org.springframework.web.servlet.tags.BindTag - JSP - - - Provides BindStatus object for the given bind path. - The HTML escaping flag participates in a page-wide or application-wide setting - (i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml). - - - - status - org.springframework.web.servlet.support.BindStatus - - - - path - true - true - - - - ignoreNestedPath - false - true - - - - htmlEscape - false - true - - - - - - - - transform - org.springframework.web.servlet.tags.TransformTag - JSP - - - Provides transformation of variables to Strings, using an appropriate - custom PropertyEditor from BindTag (can only be used inside BindTag). - The HTML escaping flag participates in a page-wide or application-wide setting - (i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml). - - - - value - true - true - - - - var - false - true - - - - scope - false - true - - - - htmlEscape - false - true - - - - - diff --git a/web-app/WEB-INF/applicationContext.xml b/web-app/WEB-INF/applicationContext.xml deleted file mode 100644 index 6f42796..0000000 --- a/web-app/WEB-INF/applicationContext.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - Grails application factory bean - - - - - - A bean that manages Grails plugins - - - - - - - - - - - - - - - - - - classpath*:**/grails-app/**/*.groovy - - - - - - utf-8 - - - \ No newline at end of file diff --git a/web-app/WEB-INF/sitemesh.xml b/web-app/WEB-INF/sitemesh.xml deleted file mode 100644 index a547b41..0000000 --- a/web-app/WEB-INF/sitemesh.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/web-app/WEB-INF/tld/c.tld b/web-app/WEB-INF/tld/c.tld deleted file mode 100644 index 22698c9..0000000 --- a/web-app/WEB-INF/tld/c.tld +++ /dev/null @@ -1,563 +0,0 @@ - - - - - JSTL 1.1 core library - JSTL core - 1.1 - c - http://java.sun.com/jsp/jstl/core - - - - Provides core validation features for JSTL tags. - - - org.apache.taglibs.standard.tlv.JstlCoreTLV - - - - - - Catches any Throwable that occurs in its body and optionally - exposes it. - - catch - org.apache.taglibs.standard.tag.common.core.CatchTag - JSP - - -Name of the exported scoped variable for the -exception thrown from a nested action. The type of the -scoped variable is the type of the exception thrown. - - var - false - false - - - - - - Simple conditional tag that establishes a context for - mutually exclusive conditional operations, marked by - <when> and <otherwise> - - choose - org.apache.taglibs.standard.tag.common.core.ChooseTag - JSP - - - - - Simple conditional tag, which evalutes its body if the - supplied condition is true and optionally exposes a Boolean - scripting variable representing the evaluation of this condition - - if - org.apache.taglibs.standard.tag.rt.core.IfTag - JSP - - -The test condition that determines whether or -not the body content should be processed. - - test - true - true - boolean - - - -Name of the exported scoped variable for the -resulting value of the test condition. The type -of the scoped variable is Boolean. - - var - false - false - - - -Scope for var. - - scope - false - false - - - - - - Retrieves an absolute or relative URL and exposes its contents - to either the page, a String in 'var', or a Reader in 'varReader'. - - import - org.apache.taglibs.standard.tag.rt.core.ImportTag - org.apache.taglibs.standard.tei.ImportTEI - JSP - - -The URL of the resource to import. - - url - true - true - - - -Name of the exported scoped variable for the -resource's content. The type of the scoped -variable is String. - - var - false - false - - - -Scope for var. - - scope - false - false - - - -Name of the exported scoped variable for the -resource's content. The type of the scoped -variable is Reader. - - varReader - false - false - - - -Name of the context when accessing a relative -URL resource that belongs to a foreign -context. - - context - false - true - - - -Character encoding of the content at the input -resource. - - charEncoding - false - true - - - - - - The basic iteration tag, accepting many different - collection types and supporting subsetting and other - functionality - - forEach - org.apache.taglibs.standard.tag.rt.core.ForEachTag - org.apache.taglibs.standard.tei.ForEachTEI - JSP - - -Collection of items to iterate over. - - items - false - true - java.lang.Object - - - -If items specified: -Iteration begins at the item located at the -specified index. First item of the collection has -index 0. -If items not specified: -Iteration begins with index set at the value -specified. - - begin - false - true - int - - - -If items specified: -Iteration ends at the item located at the -specified index (inclusive). -If items not specified: -Iteration ends when index reaches the value -specified. - - end - false - true - int - - - -Iteration will only process every step items of -the collection, starting with the first one. - - step - false - true - int - - - -Name of the exported scoped variable for the -current item of the iteration. This scoped -variable has nested visibility. Its type depends -on the object of the underlying collection. - - var - false - false - - - -Name of the exported scoped variable for the -status of the iteration. Object exported is of type -javax.servlet.jsp.jstl.core.LoopTagStatus. This scoped variable has nested -visibility. - - varStatus - false - false - - - - - - Iterates over tokens, separated by the supplied delimeters - - forTokens - org.apache.taglibs.standard.tag.rt.core.ForTokensTag - JSP - - -String of tokens to iterate over. - - items - true - true - java.lang.String - - - -The set of delimiters (the characters that -separate the tokens in the string). - - delims - true - true - java.lang.String - - - -Iteration begins at the token located at the -specified index. First token has index 0. - - begin - false - true - int - - - -Iteration ends at the token located at the -specified index (inclusive). - - end - false - true - int - - - -Iteration will only process every step tokens -of the string, starting with the first one. - - step - false - true - int - - - -Name of the exported scoped variable for the -current item of the iteration. This scoped -variable has nested visibility. - - var - false - false - - - -Name of the exported scoped variable for the -status of the iteration. Object exported is of -type -javax.servlet.jsp.jstl.core.LoopTag -Status. This scoped variable has nested -visibility. - - varStatus - false - false - - - - - - Like <%= ... >, but for expressions. - - out - org.apache.taglibs.standard.tag.rt.core.OutTag - JSP - - -Expression to be evaluated. - - value - true - true - - - -Default value if the resulting value is null. - - default - false - true - - - -Determines whether characters <,>,&,'," in the -resulting string should be converted to their -corresponding character entity codes. Default value is -true. - - escapeXml - false - true - - - - - - - Subtag of <choose> that follows <when> tags - and runs only if all of the prior conditions evaluated to - 'false' - - otherwise - org.apache.taglibs.standard.tag.common.core.OtherwiseTag - JSP - - - - - Adds a parameter to a containing 'import' tag's URL. - - param - org.apache.taglibs.standard.tag.rt.core.ParamTag - JSP - - -Name of the query string parameter. - - name - true - true - - - -Value of the parameter. - - value - false - true - - - - - - Redirects to a new URL. - - redirect - org.apache.taglibs.standard.tag.rt.core.RedirectTag - JSP - - -The URL of the resource to redirect to. - - url - false - true - - - -Name of the context when redirecting to a relative URL -resource that belongs to a foreign context. - - context - false - true - - - - - - Removes a scoped variable (from a particular scope, if specified). - - remove - org.apache.taglibs.standard.tag.common.core.RemoveTag - empty - - -Name of the scoped variable to be removed. - - var - true - false - - - -Scope for var. - - scope - false - false - - - - - - Sets the result of an expression evaluation in a 'scope' - - set - org.apache.taglibs.standard.tag.rt.core.SetTag - JSP - - -Name of the exported scoped variable to hold the value -specified in the action. The type of the scoped variable is -whatever type the value expression evaluates to. - - var - false - false - - - -Expression to be evaluated. - - value - false - true - - - -Target object whose property will be set. Must evaluate to -a JavaBeans object with setter property property, or to a -java.util.Map object. - - target - false - true - - - -Name of the property to be set in the target object. - - property - false - true - - - -Scope for var. - - scope - false - false - - - - - - Creates a URL with optional query parameters. - - url - org.apache.taglibs.standard.tag.rt.core.UrlTag - JSP - - -Name of the exported scoped variable for the -processed url. The type of the scoped variable is -String. - - var - false - false - - - -Scope for var. - - scope - false - false - - - -URL to be processed. - - value - false - true - - - -Name of the context when specifying a relative URL -resource that belongs to a foreign context. - - context - false - true - - - - - - Subtag of <choose> that includes its body if its - condition evalutes to 'true' - - when - org.apache.taglibs.standard.tag.rt.core.WhenTag - JSP - - -The test condition that determines whether or not the -body content should be processed. - - test - true - true - boolean - - - - diff --git a/web-app/WEB-INF/tld/fmt.tld b/web-app/WEB-INF/tld/fmt.tld deleted file mode 100644 index 3b9a54a..0000000 --- a/web-app/WEB-INF/tld/fmt.tld +++ /dev/null @@ -1,671 +0,0 @@ - - - - - JSTL 1.1 i18n-capable formatting library - JSTL fmt - 1.1 - fmt - http://java.sun.com/jsp/jstl/fmt - - - - Provides core validation features for JSTL tags. - - - org.apache.taglibs.standard.tlv.JstlFmtTLV - - - - - - Sets the request character encoding - - requestEncoding - org.apache.taglibs.standard.tag.rt.fmt.RequestEncodingTag - empty - - -Name of character encoding to be applied when -decoding request parameters. - - value - false - true - - - - - - Stores the given locale in the locale configuration variable - - setLocale - org.apache.taglibs.standard.tag.rt.fmt.SetLocaleTag - empty - - -A String value is interpreted as the -printable representation of a locale, which -must contain a two-letter (lower-case) -language code (as defined by ISO-639), -and may contain a two-letter (upper-case) -country code (as defined by ISO-3166). -Language and country codes must be -separated by hyphen (-) or underscore -(_). - - value - true - true - - - -Vendor- or browser-specific variant. -See the java.util.Locale javadocs for -more information on variants. - - variant - false - true - - - -Scope of the locale configuration variable. - - scope - false - false - - - - - - Specifies the time zone for any time formatting or parsing actions - nested in its body - - timeZone - org.apache.taglibs.standard.tag.rt.fmt.TimeZoneTag - JSP - - -The time zone. A String value is interpreted as -a time zone ID. This may be one of the time zone -IDs supported by the Java platform (such as -"America/Los_Angeles") or a custom time zone -ID (such as "GMT-8"). See -java.util.TimeZone for more information on -supported time zone formats. - - value - true - true - - - - - - Stores the given time zone in the time zone configuration variable - - setTimeZone - org.apache.taglibs.standard.tag.rt.fmt.SetTimeZoneTag - empty - - -The time zone. A String value is interpreted as -a time zone ID. This may be one of the time zone -IDs supported by the Java platform (such as -"America/Los_Angeles") or a custom time zone -ID (such as "GMT-8"). See java.util.TimeZone for -more information on supported time zone -formats. - - value - true - true - - - -Name of the exported scoped variable which -stores the time zone of type -java.util.TimeZone. - - var - false - false - - - -Scope of var or the time zone configuration -variable. - - scope - false - false - - - - - - Loads a resource bundle to be used by its tag body - - bundle - org.apache.taglibs.standard.tag.rt.fmt.BundleTag - JSP - - -Resource bundle base name. This is the bundle's -fully-qualified resource name, which has the same -form as a fully-qualified class name, that is, it uses -"." as the package component separator and does not -have any file type (such as ".class" or ".properties") -suffix. - - basename - true - true - - - -Prefix to be prepended to the value of the message -key of any nested <fmt:message> action. - - prefix - false - true - - - - - - Loads a resource bundle and stores it in the named scoped variable or - the bundle configuration variable - - setBundle - org.apache.taglibs.standard.tag.rt.fmt.SetBundleTag - empty - - -Resource bundle base name. This is the bundle's -fully-qualified resource name, which has the same -form as a fully-qualified class name, that is, it uses -"." as the package component separator and does not -have any file type (such as ".class" or ".properties") -suffix. - - basename - true - true - - - -Name of the exported scoped variable which stores -the i18n localization context of type -javax.servlet.jsp.jstl.fmt.LocalizationC -ontext. - - var - false - false - - - -Scope of var or the localization context -configuration variable. - - scope - false - false - - - - - - Maps key to localized message and performs parametric replacement - - message - org.apache.taglibs.standard.tag.rt.fmt.MessageTag - JSP - - -Message key to be looked up. - - key - false - true - - - -Localization context in whose resource -bundle the message key is looked up. - - bundle - false - true - - - -Name of the exported scoped variable -which stores the localized message. - - var - false - false - - - -Scope of var. - - scope - false - false - - - - - - Supplies an argument for parametric replacement to a containing - <message> tag - - param - org.apache.taglibs.standard.tag.rt.fmt.ParamTag - JSP - - -Argument used for parametric replacement. - - value - false - true - - - - - - Formats a numeric value as a number, currency, or percentage - - formatNumber - org.apache.taglibs.standard.tag.rt.fmt.FormatNumberTag - JSP - - -Numeric value to be formatted. - - value - false - true - - - -Specifies whether the value is to be -formatted as number, currency, or -percentage. - - type - false - true - - - -Custom formatting pattern. - - pattern - false - true - - - -ISO 4217 currency code. Applied only -when formatting currencies (i.e. if type is -equal to "currency"); ignored otherwise. - - currencyCode - false - true - - - -Currency symbol. Applied only when -formatting currencies (i.e. if type is equal -to "currency"); ignored otherwise. - - currencySymbol - false - true - - - -Specifies whether the formatted output -will contain any grouping separators. - - groupingUsed - false - true - - - -Maximum number of digits in the integer -portion of the formatted output. - - maxIntegerDigits - false - true - - - -Minimum number of digits in the integer -portion of the formatted output. - - minIntegerDigits - false - true - - - -Maximum number of digits in the -fractional portion of the formatted output. - - maxFractionDigits - false - true - - - -Minimum number of digits in the -fractional portion of the formatted output. - - minFractionDigits - false - true - - - -Name of the exported scoped variable -which stores the formatted result as a -String. - - var - false - false - - - -Scope of var. - - scope - false - false - - - - - - Parses the string representation of a number, currency, or percentage - - parseNumber - org.apache.taglibs.standard.tag.rt.fmt.ParseNumberTag - JSP - - -String to be parsed. - - value - false - true - - - -Specifies whether the string in the value -attribute should be parsed as a number, -currency, or percentage. - - type - false - true - - - -Custom formatting pattern that determines -how the string in the value attribute is to be -parsed. - - pattern - false - true - - - -Locale whose default formatting pattern (for -numbers, currencies, or percentages, -respectively) is to be used during the parse -operation, or to which the pattern specified -via the pattern attribute (if present) is -applied. - - parseLocale - false - true - - - -Specifies whether just the integer portion of -the given value should be parsed. - - integerOnly - false - true - - - -Name of the exported scoped variable which -stores the parsed result (of type -java.lang.Number). - - var - false - false - - - -Scope of var. - - scope - false - false - - - - - - Formats a date and/or time using the supplied styles and pattern - - formatDate - org.apache.taglibs.standard.tag.rt.fmt.FormatDateTag - empty - - -Date and/or time to be formatted. - - value - true - true - - - -Specifies whether the time, the date, or both -the time and date components of the given -date are to be formatted. - - type - false - true - - - -Predefined formatting style for dates. Follows -the semantics defined in class -java.text.DateFormat. Applied only -when formatting a date or both a date and -time (i.e. if type is missing or is equal to -"date" or "both"); ignored otherwise. - - dateStyle - false - true - - - -Predefined formatting style for times. Follows -the semantics defined in class -java.text.DateFormat. Applied only -when formatting a time or both a date and -time (i.e. if type is equal to "time" or "both"); -ignored otherwise. - - timeStyle - false - true - - - -Custom formatting style for dates and times. - - pattern - false - true - - - -Time zone in which to represent the formatted -time. - - timeZone - false - true - - - -Name of the exported scoped variable which -stores the formatted result as a String. - - var - false - false - - - -Scope of var. - - scope - false - false - - - - - - Parses the string representation of a date and/or time - - parseDate - org.apache.taglibs.standard.tag.rt.fmt.ParseDateTag - JSP - - -Date string to be parsed. - - value - false - true - - - -Specifies whether the date string in the -value attribute is supposed to contain a -time, a date, or both. - - type - false - true - - - -Predefined formatting style for days -which determines how the date -component of the date string is to be -parsed. Applied only when formatting a -date or both a date and time (i.e. if type -is missing or is equal to "date" or "both"); -ignored otherwise. - - dateStyle - false - true - - - -Predefined formatting styles for times -which determines how the time -component in the date string is to be -parsed. Applied only when formatting a -time or both a date and time (i.e. if type -is equal to "time" or "both"); ignored -otherwise. - - timeStyle - false - true - - - -Custom formatting pattern which -determines how the date string is to be -parsed. - - pattern - false - true - - - -Time zone in which to interpret any time -information in the date string. - - timeZone - false - true - - - -Locale whose predefined formatting styles -for dates and times are to be used during -the parse operation, or to which the -pattern specified via the pattern -attribute (if present) is applied. - - parseLocale - false - true - - - -Name of the exported scoped variable in -which the parsing result (of type -java.util.Date) is stored. - - var - false - false - - - -Scope of var. - - scope - false - false - - - - diff --git a/web-app/WEB-INF/tld/grails.tld b/web-app/WEB-INF/tld/grails.tld deleted file mode 100644 index 868ec1f..0000000 --- a/web-app/WEB-INF/tld/grails.tld +++ /dev/null @@ -1,551 +0,0 @@ - - - The Grails (Groovy on Rails) custom tag library - 0.2 - grails - http://grails.codehaus.org/tags - - - - link - org.codehaus.groovy.grails.web.taglib.jsp.JspLinkTag - JSP - - action - false - true - - - controller - false - true - - - id - false - true - - - url - false - true - - - params - false - true - - true - - - form - org.codehaus.groovy.grails.web.taglib.jsp.JspFormTag - JSP - - action - false - true - - - controller - false - true - - - id - false - true - - - url - false - true - - - method - true - true - - true - - - select - org.codehaus.groovy.grails.web.taglib.jsp.JspSelectTag - JSP - - name - true - true - - - value - false - true - - - optionKey - false - true - - - optionValue - false - true - - true - - - datePicker - org.codehaus.groovy.grails.web.taglib.jsp.JspDatePickerTag - empty - - name - true - true - - - value - false - true - - - precision - false - true - - false - - - currencySelect - org.codehaus.groovy.grails.web.taglib.jsp.JspCurrencySelectTag - empty - - name - true - true - - - value - false - true - - true - - - localeSelect - org.codehaus.groovy.grails.web.taglib.jsp.JspLocaleSelectTag - empty - - name - true - true - - - value - false - true - - true - - - timeZoneSelect - org.codehaus.groovy.grails.web.taglib.jsp.JspTimeZoneSelectTag - empty - - name - true - true - - - value - false - true - - true - - - checkBox - org.codehaus.groovy.grails.web.taglib.jsp.JspCheckboxTag - empty - - name - true - true - - - value - true - true - - true - - - hasErrors - org.codehaus.groovy.grails.web.taglib.jsp.JspHasErrorsTag - JSP - - model - false - true - - - bean - false - true - - - field - false - true - - false - - - eachError - org.codehaus.groovy.grails.web.taglib.jsp.JspEachErrorTag - JSP - - model - false - true - - - bean - false - true - - - field - false - true - - false - - - renderErrors - org.codehaus.groovy.grails.web.taglib.jsp.JspEachErrorTag - JSP - - model - false - true - - - bean - false - true - - - field - false - true - - - as - true - true - - false - - - message - org.codehaus.groovy.grails.web.taglib.jsp.JspMessageTag - JSP - - code - false - true - - - error - false - true - - - default - false - true - - false - - - remoteFunction - org.codehaus.groovy.grails.web.taglib.jsp.JspRemoteFunctionTag - empty - - before - false - true - - - after - false - true - - - action - false - true - - - controller - false - true - - - id - false - true - - - url - false - true - - - params - false - true - - - asynchronous - false - true - - - method - false - true - - - update - false - true - - - onSuccess - false - true - - - onFailure - false - true - - - onComplete - false - true - - - onLoading - false - true - - - onLoaded - false - true - - - onInteractive - false - true - - true - - - remoteLink - org.codehaus.groovy.grails.web.taglib.jsp.JspRemoteLinkTag - JSP - - before - false - true - - - after - false - true - - - action - false - true - - - controller - false - true - - - id - false - true - - - url - false - true - - - params - false - true - - - asynchronous - false - true - - - method - false - true - - - update - false - true - - - onSuccess - false - true - - - onFailure - false - true - - - onComplete - false - true - - - onLoading - false - true - - - onLoaded - false - true - - - onInteractive - false - true - - true - - - formRemote - org.codehaus.groovy.grails.web.taglib.jsp.JspFormRemoteTag - JSP - - before - false - true - - - after - false - true - - - action - false - true - - - controller - false - true - - - id - false - true - - - url - false - true - - - params - false - true - - - asynchronous - false - true - - - method - false - true - - - update - false - true - - - onSuccess - false - true - - - onFailure - false - true - - - onComplete - false - true - - - onLoading - false - true - - - onLoaded - false - true - - - onInteractive - false - true - - true - - - invokeTag - org.codehaus.groovy.grails.web.taglib.jsp.JspInvokeGrailsTagLibTag - JSP - - it - java.lang.Object - true - NESTED - - - tagName - true - true - - true - - - diff --git a/web-app/WEB-INF/tld/spring.tld b/web-app/WEB-INF/tld/spring.tld deleted file mode 100644 index 1bc7091..0000000 --- a/web-app/WEB-INF/tld/spring.tld +++ /dev/null @@ -1,311 +0,0 @@ - - - - - - 1.1.1 - - 1.2 - - Spring - - http://www.springframework.org/tags - - Spring Framework JSP Tag Library. Authors: Rod Johnson, Juergen Hoeller - - - - - htmlEscape - org.springframework.web.servlet.tags.HtmlEscapeTag - JSP - - - Sets default HTML escape value for the current page. - Overrides a "defaultHtmlEscape" context-param in web.xml, if any. - - - - defaultHtmlEscape - true - true - - - - - - - - escapeBody - org.springframework.web.servlet.tags.EscapeBodyTag - JSP - - - Escapes its enclosed body content, applying HTML escaping and/or JavaScript escaping. - The HTML escaping flag participates in a page-wide or application-wide setting - (i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml). - - - - htmlEscape - false - true - - - - javaScriptEscape - false - true - - - - - - - - message - org.springframework.web.servlet.tags.MessageTag - JSP - - - Retrieves the message with the given code, or text if code isn't resolvable. - The HTML escaping flag participates in a page-wide or application-wide setting - (i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml). - - - - code - false - true - - - - arguments - false - true - - - - text - false - true - - - - var - false - true - - - - scope - false - true - - - - htmlEscape - false - true - - - - javaScriptEscape - false - true - - - - - - - - theme - org.springframework.web.servlet.tags.ThemeTag - JSP - - - Retrieves the theme message with the given code, or text if code isn't resolvable. - The HTML escaping flag participates in a page-wide or application-wide setting - (i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml). - - - - code - false - true - - - - arguments - false - true - - - - text - false - true - - - - var - false - true - - - - scope - false - true - - - - htmlEscape - false - true - - - - javaScriptEscape - false - true - - - - - - - - hasBindErrors - org.springframework.web.servlet.tags.BindErrorsTag - JSP - - - Provides Errors instance in case of bind errors. - The HTML escaping flag participates in a page-wide or application-wide setting - (i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml). - - - - errors - org.springframework.validation.Errors - - - - name - true - true - - - - htmlEscape - false - true - - - - - - - - nestedPath - org.springframework.web.servlet.tags.NestedPathTag - JSP - - - Sets a nested path to be used by the bind tag's path. - - - - nestedPath - java.lang.String - - - - path - true - true - - - - - - - - bind - org.springframework.web.servlet.tags.BindTag - JSP - - - Provides BindStatus object for the given bind path. - The HTML escaping flag participates in a page-wide or application-wide setting - (i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml). - - - - status - org.springframework.web.servlet.support.BindStatus - - - - path - true - true - - - - ignoreNestedPath - false - true - - - - htmlEscape - false - true - - - - - - - - transform - org.springframework.web.servlet.tags.TransformTag - JSP - - - Provides transformation of variables to Strings, using an appropriate - custom PropertyEditor from BindTag (can only be used inside BindTag). - The HTML escaping flag participates in a page-wide or application-wide setting - (i.e. by HtmlEscapeTag or a "defaultHtmlEscape" context-param in web.xml). - - - - value - true - true - - - - var - false - true - - - - scope - false - true - - - - htmlEscape - false - true - - - - - diff --git a/web-app/images/grails.png b/web-app/images/grails.png deleted file mode 100644 index 9cb734d8f7e9f11645326d61f24dbe711d8cc062..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21146 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 Date: Tue, 30 Sep 2025 10:43:26 +0200 Subject: [PATCH 06/12] Added correct contributors --- CONTRIBUTORS.md | 4 +--- build.gradle | 15 ++++++++++----- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 587d235..5728e28 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,3 +1 @@ -* Luke Daley - Grails Plugin Collective -* Graeme Rocher - Grails Plugin Collective -* Randall Dietz - Software Projects +See https://github.com/gpc/rendering/graphs/contributors for contributors \ No newline at end of file diff --git a/build.gradle b/build.gradle index ff2ecb8..f609eeb 100644 --- a/build.gradle +++ b/build.gradle @@ -99,11 +99,16 @@ grailsPublish { title = 'Rendering Plugin' desc = 'Render GSPs as PDFs, JPEGs, GIFs and PNGs' developers = [ - ldaley: 'Luke Daley', - graemerocher: 'Graeme Rocher', - rd: 'Randall Dietz', - sbglasius : 'Søren Berg Glasius', - burtbeckwith: 'Burt Beckwith', + ldaley : "Luke Daley", + graemerocher: "Graeme Rocher", + burtbeckwith: "Burt Beckwith", + sbglasius : "Søren Berg Glasius", + magx2 : "Martin", + Ari651 : "Ari Bustamante", + billgonemad : "William Malinowski", + ZacharyKlein: "Zachary Klein", + halfbaked : "Eamonn O'Connell", + codeconsole : "Scott Murphy", ] } From 767c51216eee90776aece853309939bf42f322db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Berg=20Glasius?= Date: Tue, 30 Sep 2025 10:44:14 +0200 Subject: [PATCH 07/12] Moved integration tests in to example subproject, dependent on root project --- .gitignore | 1 + example/.gitignore | 45 ++++ example/README.md | 25 ++ example/build.gradle | 99 +++++++ example/gradle.properties | 6 + example/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43764 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 + example/gradlew | 251 ++++++++++++++++++ example/gradlew.bat | 94 +++++++ .../grails-app/assets}/fonts/arial.ttf | Bin .../assets/images/advancedgrails.svg | 27 ++ .../assets/images/apple-touch-icon-retina.png | Bin 0 -> 7038 bytes .../assets/images/apple-touch-icon.png | Bin 0 -> 3077 bytes .../assets/images/documentation.svg | 19 ++ example/grails-app/assets/images/favicon.ico | Bin 0 -> 5558 bytes .../images/grails-cupsonly-logo-white.svg | 26 ++ example/grails-app/assets/images/grails.png | Bin 0 -> 3996 bytes example/grails-app/assets/images/grails.svg | 13 + example/grails-app/assets/images/slack.svg | 18 ++ .../assets/javascripts/application.js | 20 ++ .../assets/stylesheets/application.css | 14 + .../grails-app/assets/stylesheets/errors.css | 90 +++++++ .../grails-app/assets/stylesheets/grails.css | 77 ++++++ example/grails-app/conf/application.yml | 82 ++++++ example/grails-app/conf/logback-spring.xml | 39 +++ .../grails-app/conf/spring/resources.groovy | 3 + .../example}/RenderingController.groovy | 23 +- .../controllers/example/UrlMappings.groovy | 16 ++ example/grails-app/i18n/messages.properties | 56 ++++ .../init/example/Application.groovy | 12 + .../grails-app/init/example/BootStrap.groovy | 11 + .../test/BackgroundRenderingService.groovy | 0 .../grails-app}/views/_bad-xml.gsp | 0 .../grails-app}/views/_datauri.gsp | 2 +- .../grails-app}/views/_encoding-test.gsp | 2 +- .../grails-app}/views/_simple.gsp | 0 example/grails-app/views/error.gsp | 33 +++ example/grails-app/views/index.gsp | 84 ++++++ example/grails-app/views/layouts/main.gsp | 81 ++++++ example/grails-app/views/notFound.gsp | 20 ++ .../grails-app}/views/rendering/_relative.gsp | 4 +- example/grails-app/views/rendering/index.gsp | 28 ++ example/grails-forge-cli.yml | 8 + example/grails-wrapper.jar | Bin 0 -> 28481 bytes example/grailsw | 250 +++++++++++++++++ example/grailsw.bat | 94 +++++++ .../groovy/example/ExampleSpec.groovy | 19 ++ .../rendering/BackgroundRenderingSpec.groovy | 0 .../ControllerRelativeTemplateSpec.groovy | 57 ++++ .../rendering/RenderingServiceSpec.groovy | 0 .../document/XhtmlDocumentServiceSpec.groovy | 0 .../image/GifRenderingServiceSpec.groovy | 0 .../image/ImageRenderingServiceSpec.groovy | 0 .../image/JpegRenderingServiceSpec.groovy | 0 .../image/PngRenderingServiceSpec.groovy | 0 .../pdf/PdfRenderingServiceSpec.groovy | 2 +- example/src/main/groovy/.gitkeep | 0 example/src/test/groovy/.gitkeep | 0 settings.gradle | 3 + .../ControllerRelativeTemplateSpec.groovy | 37 --- .../RenderingGrailsPluginSpec.groovy | 55 ---- 61 files changed, 1746 insertions(+), 107 deletions(-) create mode 100644 example/.gitignore create mode 100644 example/README.md create mode 100644 example/build.gradle create mode 100644 example/gradle.properties create mode 100644 example/gradle/wrapper/gradle-wrapper.jar create mode 100644 example/gradle/wrapper/gradle-wrapper.properties create mode 100755 example/gradlew create mode 100644 example/gradlew.bat rename {web-app => example/grails-app/assets}/fonts/arial.ttf (100%) create mode 100644 example/grails-app/assets/images/advancedgrails.svg create mode 100644 example/grails-app/assets/images/apple-touch-icon-retina.png create mode 100644 example/grails-app/assets/images/apple-touch-icon.png create mode 100644 example/grails-app/assets/images/documentation.svg create mode 100644 example/grails-app/assets/images/favicon.ico create mode 100644 example/grails-app/assets/images/grails-cupsonly-logo-white.svg create mode 100644 example/grails-app/assets/images/grails.png create mode 100644 example/grails-app/assets/images/grails.svg create mode 100644 example/grails-app/assets/images/slack.svg create mode 100644 example/grails-app/assets/javascripts/application.js create mode 100644 example/grails-app/assets/stylesheets/application.css create mode 100644 example/grails-app/assets/stylesheets/errors.css create mode 100644 example/grails-app/assets/stylesheets/grails.css create mode 100644 example/grails-app/conf/application.yml create mode 100644 example/grails-app/conf/logback-spring.xml create mode 100644 example/grails-app/conf/spring/resources.groovy rename {grails-app/controllers => example/grails-app/controllers/example}/RenderingController.groovy (81%) create mode 100644 example/grails-app/controllers/example/UrlMappings.groovy create mode 100644 example/grails-app/i18n/messages.properties create mode 100644 example/grails-app/init/example/Application.groovy create mode 100644 example/grails-app/init/example/BootStrap.groovy rename {grails-app => example/grails-app}/services/grails/plugins/rendering/test/BackgroundRenderingService.groovy (100%) rename {grails-app => example/grails-app}/views/_bad-xml.gsp (100%) rename {grails-app => example/grails-app}/views/_datauri.gsp (71%) rename {grails-app => example/grails-app}/views/_encoding-test.gsp (94%) rename {grails-app => example/grails-app}/views/_simple.gsp (100%) create mode 100644 example/grails-app/views/error.gsp create mode 100644 example/grails-app/views/index.gsp create mode 100644 example/grails-app/views/layouts/main.gsp create mode 100644 example/grails-app/views/notFound.gsp rename {grails-app => example/grails-app}/views/rendering/_relative.gsp (68%) create mode 100644 example/grails-app/views/rendering/index.gsp create mode 100644 example/grails-forge-cli.yml create mode 100644 example/grails-wrapper.jar create mode 100755 example/grailsw create mode 100644 example/grailsw.bat create mode 100644 example/src/integration-test/groovy/example/ExampleSpec.groovy rename {src => example/src}/integration-test/groovy/grails/plugins/rendering/BackgroundRenderingSpec.groovy (100%) create mode 100644 example/src/integration-test/groovy/grails/plugins/rendering/ControllerRelativeTemplateSpec.groovy rename {src => example/src}/integration-test/groovy/grails/plugins/rendering/RenderingServiceSpec.groovy (100%) rename {src => example/src}/integration-test/groovy/grails/plugins/rendering/document/XhtmlDocumentServiceSpec.groovy (100%) rename {src => example/src}/integration-test/groovy/grails/plugins/rendering/image/GifRenderingServiceSpec.groovy (100%) rename {src => example/src}/integration-test/groovy/grails/plugins/rendering/image/ImageRenderingServiceSpec.groovy (100%) rename {src => example/src}/integration-test/groovy/grails/plugins/rendering/image/JpegRenderingServiceSpec.groovy (100%) rename {src => example/src}/integration-test/groovy/grails/plugins/rendering/image/PngRenderingServiceSpec.groovy (100%) rename {src => example/src}/integration-test/groovy/grails/plugins/rendering/pdf/PdfRenderingServiceSpec.groovy (97%) create mode 100644 example/src/main/groovy/.gitkeep create mode 100644 example/src/test/groovy/.gitkeep delete mode 100644 src/integration-test/groovy/grails/plugins/rendering/ControllerRelativeTemplateSpec.groovy delete mode 100644 src/integration-test/groovy/grails/plugins/rendering/RenderingGrailsPluginSpec.groovy diff --git a/.gitignore b/.gitignore index 74835a1..e14fb07 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ grails-rendering-* .settings .vscode/ bin/ +.idea diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 0000000..dcdf017 --- /dev/null +++ b/example/.gitignore @@ -0,0 +1,45 @@ +### Gradle ### +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ +!**/src/integration-test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ +!**/src/integration-test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ +!**/src/integration-test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Other ### +Thumbs.db +.DS_Store +target/ diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..d7b8a8f --- /dev/null +++ b/example/README.md @@ -0,0 +1,25 @@ +## Grails 7.0.0-RC2 Documentation + +- [User Guide](https://docs.grails.org/7.0.0-RC2/guide/index.html) +- [API Reference](https://docs.grails.org/7.0.0-RC2/api/index.html) +- [Grails Guides](https://guides.grails.org/index.html) +--- + +## Feature scaffolding documentation + +- [Grails Scaffolding documentation](https://docs.grails.org/7.0.0-RC2/guide/scaffolding.html) + +## Feature asset-pipeline-grails documentation + +- [Grails Asset Pipeline documentation](https://github.com/wondrify/asset-pipeline#readme) + +## Feature spring-boot-devtools documentation + +- [Grails SpringBoot Developer Tools documentation](https://docs.spring.io/spring-boot/reference/using/devtools.html) + +## Feature geb-with-testcontainers documentation + +- [Grails Geb Functional Testing for Grails with Testcontainers documentation](https://github.com/apache/grails-geb#readme) + +- [https://groovy.apache.org/geb/manual/current/](https://groovy.apache.org/geb/manual/current/) + diff --git a/example/build.gradle b/example/build.gradle new file mode 100644 index 0000000..f007554 --- /dev/null +++ b/example/build.gradle @@ -0,0 +1,99 @@ +buildscript { + repositories { + mavenCentral() + maven { + url = 'https://repo.grails.org/grails/restricted' + } + } + dependencies { // Not Published to Gradle Plugin Portal + classpath "cloud.wondrify:asset-pipeline-gradle" + classpath platform("org.apache.grails:grails-bom:$grailsVersion") + classpath "org.apache.grails:grails-data-hibernate5" + classpath "org.apache.grails:grails-gradle-plugins" + } +} + +plugins { + id "war" + id "idea" + id "eclipse" +} + +// Not Published to Gradle Plugin Portal +apply plugin: "org.apache.grails.gradle.grails-web" +apply plugin: "org.apache.grails.gradle.grails-gsp" +apply plugin: "cloud.wondrify.asset-pipeline" + +group = "example" + +repositories { + mavenCentral() + maven { + url = 'https://repo.grails.org/grails/restricted' + } +} + +dependencies { + profile "org.apache.grails.profiles:web" + developmentOnly "org.springframework.boot:spring-boot-devtools" // Spring Boot DevTools may cause performance slowdowns or compatibility issues on larger applications + testAndDevelopmentOnly "org.webjars.npm:bootstrap" + testAndDevelopmentOnly "org.webjars.npm:bootstrap-icons" + testAndDevelopmentOnly "org.webjars.npm:jquery" + implementation platform("org.apache.grails:grails-bom:$grailsVersion") + implementation "org.apache.grails:grails-core" + implementation "org.apache.grails:grails-data-hibernate5" + implementation "org.apache.grails:grails-databinding" + implementation "org.apache.grails:grails-events" + implementation "org.apache.grails:grails-gsp" + implementation "org.apache.grails:grails-interceptors" + implementation "org.apache.grails:grails-layout" + implementation "org.apache.grails:grails-logging" + implementation "org.apache.grails:grails-rest-transforms" + implementation "org.apache.grails:grails-scaffolding" + implementation "org.apache.grails:grails-services" + implementation "org.apache.grails:grails-url-mappings" + implementation "org.apache.grails:grails-web-boot" + implementation "org.springframework.boot:spring-boot-autoconfigure" + implementation "org.springframework.boot:spring-boot-starter" + implementation "org.springframework.boot:spring-boot-starter-actuator" + implementation "org.springframework.boot:spring-boot-starter-logging" + implementation "org.springframework.boot:spring-boot-starter-tomcat" + implementation "org.springframework.boot:spring-boot-starter-validation" + console "org.apache.grails:grails-console" + runtimeOnly "cloud.wondrify:asset-pipeline-grails" + runtimeOnly "com.h2database:h2" + runtimeOnly "com.zaxxer:HikariCP" + runtimeOnly "org.fusesource.jansi:jansi" + integrationTestImplementation testFixtures("org.apache.grails:grails-geb") + testImplementation "org.apache.grails:grails-testing-support-datamapping" + testImplementation "org.apache.grails:grails-testing-support-web" + testImplementation "org.spockframework:spock-core" + + implementation project(':') + implementation("org.apache.pdfbox:pdfbox:3.0.5") { + exclude module: 'jempbox' + } + +} + +compileJava.options.release = 17 + +tasks.withType(Test).configureEach { + useJUnitPlatform() +} + +assets { + excludes = [ + 'webjars/jquery/**', + 'webjars/bootstrap/**', + 'webjars/bootstrap-icons/**' + ] + includes = [ + 'webjars/jquery/*/dist/jquery.js', + 'webjars/bootstrap/*/dist/js/bootstrap.bundle.js', + 'webjars/bootstrap/*/dist/css/bootstrap.css', + 'webjars/bootstrap-icons/*/font/bootstrap-icons.css', + 'webjars/bootstrap-icons/*/font/fonts/*', + ] +} + diff --git a/example/gradle.properties b/example/gradle.properties new file mode 100644 index 0000000..6d7deba --- /dev/null +++ b/example/gradle.properties @@ -0,0 +1,6 @@ +grailsVersion=7.0.0-RC2 +version=0.1 +org.gradle.caching=true +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Xmx1024M diff --git a/example/gradle/wrapper/gradle-wrapper.jar b/example/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..1b33c55baabb587c669f562ae36f953de2481846 GIT binary patch literal 43764 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 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/example/gradlew.bat b/example/gradlew.bat new file mode 100644 index 0000000..db3a6ac --- /dev/null +++ b/example/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/web-app/fonts/arial.ttf b/example/grails-app/assets/fonts/arial.ttf similarity index 100% rename from web-app/fonts/arial.ttf rename to example/grails-app/assets/fonts/arial.ttf diff --git a/example/grails-app/assets/images/advancedgrails.svg b/example/grails-app/assets/images/advancedgrails.svg new file mode 100644 index 0000000..8b63ec8 --- /dev/null +++ b/example/grails-app/assets/images/advancedgrails.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + diff --git a/example/grails-app/assets/images/apple-touch-icon-retina.png b/example/grails-app/assets/images/apple-touch-icon-retina.png new file mode 100644 index 0000000000000000000000000000000000000000..d5bc4c0da0bf34d32c86eee86f14845f1e5bf412 GIT binary patch literal 7038 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 TOgI^o00000NkvXXu0mjfLl)`& literal 0 HcmV?d00001 diff --git a/example/grails-app/assets/images/documentation.svg b/example/grails-app/assets/images/documentation.svg new file mode 100644 index 0000000..29bc9d5 --- /dev/null +++ b/example/grails-app/assets/images/documentation.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + diff --git a/example/grails-app/assets/images/favicon.ico b/example/grails-app/assets/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..76e4b11feda94c87ef50f4350eceeb1506e31311 GIT binary patch literal 5558 zcmds5TWB0r7@oAD-jLoD5kymO6!D=TzFEzK#d}{Q^hKedq)oD$o!QjZ7iB>ZDuT5Z zQK*!*+QLGbCUe=S#R?S^iw{!UT3ecmm$p`8F14H8xg5XmOinU8o!!i8KrnDJXU{qR z_xt7+PjCE@Rlt(vwLV=FO+C0aWg*UiUYXHhK9 zn3Z+9<$b8z{)wCwjIy!MG0t(#d~iiDT6zyu?$2577Q^;F&)edh;RIub9e91iLa?!~ z+updG!y2xo3@}K`6Fsg z9&0H>Sz~U8^;XC|F);Ud`BFoqbL@^Hv6TmA~KYha=;`uIpVVF zcXrTw3i|(+gdz2yErx~LYx+G(Mm=+;`;cx27wXA!oY>@0GSR0#P;P@y9ZlMIjSW7= znOfd(G|r^mO!u`pWu*?@QF2AODKGteSOoH%WA=;kb04}DT-abv-J>w*f7EF%eu)hZ zaz)R9v8p@X$lUUQU_3yd%RPhc!-~JNU)*X~{_%RYkLphja!uz43Bi0Zja%byQ90G; zI-KJb!*(B6{MmkS8^+G2;U^9;$VvIm;^<=Lk~DjlvOGg4_`Q3ex=Qi)9GF-R-~S>l z2G^03+!@nb2i?!5(VcorS z`5OJsTJCDt>N9EAkgvwK$(gf!+~3LkJZI>4jCrjQPop_~f4L1gaX{+8`y*mo#ZMd1 z7E$|$XC3^QG2Qk?_-W=gGOs$`;hNY`_Q%;@<$rQcRI+BZHw}K8HJO2Od)^AphyD=8 zzw*C${B4r?JvfprPTrgbJIxy0jSF?t+kv|a`5pW+{=@jI%|D1UNBF2^UP*)7%o@XT zA7vcT7oz!x7%Klte|P-J+~CxjX0X$&eQ{9Sg1mW}9MnPOU&{7cynp-=+Mef4- zLUDXq&+dukT_24tUBBzkCA@EGt8@+bA!H~^90QH_CH(Nqmp-}r{^=+d7pnUgdBdYF zsDZvm?8uh4{geNsDS2Y$9si7Bi@)dt@%xuHn5jQt1KNmZG@ngk(zj9L#On|8^H0`a zPJ{X@l0TxkBu$_EIRCo!7x`r!RjOZqrr}9iBmJHAC$OV+4>{w1jMe1&JNYh6;+q+p zjy2Zu#3Xi{FX=bm6|Q>!Xwvp7&YFGyi0YH{{-toE_XYcmY3XCr@%?Y!zoI&zU-SNn z95#aceH1n>^A2N^eT>Pud3^tj>494DKY9O-tMR}3&F>`mbRxfyH2a;TdvNM*)KBjq zUysXX`#U%`mw{Nn>Lvd@`0XX+`|nU&z6<&7x+A_u{d$RWNi{BUyN9X|!naRu{!|Fw zB{{pgLGksPQ%^DmHXlN*g*9BO=J+Hgz7L$D9CHNkyS(Ea>L?yx#CgP23aUl?og~B!g%=2 zVLI1vo-&k$ZrhX`pUkP8$M_!B#xi%C?i(0$!+p)>OUfcnFDiLCZ;NC3A@A8Iwx7O+ z`tgtXv2V~_gEKf1#V~9i-+O#1PhRZvn~kV#rBK2@zK`i8F|07iO&vmei7VNm@*@di zHA9_dWhL~G-y$e0?PmuQ$fsY`ENt)t?>7C|^JU6%8TPOf_W1?xu5X9U-$v|sQ*-do zQm-|ufmy$AH<51n|(Hp aAB7=xL9F5|-{N;0+0aiDzfp{|{l5XfK*m4- literal 0 HcmV?d00001 diff --git a/example/grails-app/assets/images/grails-cupsonly-logo-white.svg b/example/grails-app/assets/images/grails-cupsonly-logo-white.svg new file mode 100644 index 0000000..d3fe882 --- /dev/null +++ b/example/grails-app/assets/images/grails-cupsonly-logo-white.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/grails-app/assets/images/grails.png b/example/grails-app/assets/images/grails.png new file mode 100644 index 0000000000000000000000000000000000000000..93df8f99dd054911818a0414a356c7b5064900b6 GIT binary patch literal 3996 zcmV;N4`c9&P)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H101R|R zSaeirbZlh+XL4a_Y;)_bYHk1k4-rX3K~#90<(+$URpphyfBW3LbCVDf2!Rj?1_D|@ zMII{3%gRIBI(0zD*3L|4jdl$>EzxOL*K)~XmNTt@!Evmu%dXa`c1ESsD&?s_L6H_z zriws#DKB}y@`4+Zd-nYCB_tvD-1FT>0?ho@%HrO0zO%o5_WjQH*n6K3QB^J*%fH_T zny&|^!WFF$v;w3zN;X0dAPBfX18^3!9^|OPPDivFVHdE4sa2;h*DXl58gSk0Afe1V zUDZhflYwiKAk_svR%ERsx)yC(!}RAGlOWfMt~cPkc@AUiZgJIpP>=^Ot*R@jQPCxi z@GGV+S_55sgI#C9r4MJ}Y?n}&^dm)*ElWV{gm0wZhc`jFr^JkB!KFW-$mB|vA90RVZELT@v z0KS~Ku&zhpb;oh%Fm=(9L}evzz!i`GrJ_Fv@)A|1n>i`Sx0t?gMIu7tH{iN?8Sc5- zr$zY3L=?Eps4D;N6xBZ-?;sPO0hfHQNK*sLfg9tg^a|u1X-c`5uRdKD>xHo!aOIqV zs?I84NUW7z(Fh-jWaKv>J&11k z4`>c`me==2r1VzEDlqz--iz*e8=B5{E~{*otk4uD{-oZ|G>%^ZmOh--GGKTbPs(mT zB)5FGklxopS~iFS`DIWx0V%!N6bht6!4T+K4!QlIU@(;4ij4gS=r_8v%mKHW@&j&z z?81&G18L@5Uc32vewjJu1Uf5NdW$h|{s2%Ha*CnXP>={^0faFc%2>?2dA{~Z@hjju z!RI8}Xq(&bG(*2uf`-v;{|$#eFiK_@LZ9nl@0yO~YY(9t{|yjxW7mBhDZ3s0;)Av_ zfix%?YvhQ747d&5{yRTaJ&}R81E3%M5{_?!%$|V4g(wY7nZ}r!CxCyBCOf)?S3dSl z5&kZ!a%B^G0|JdX_0W*U+Q@43y9rV=;j^{S+-O>8N=8&wqatGd z$twXT1Fe(GCGc9E&<}7SOlkbgWH52{n%VGV7%#Dn^~4!ZgYXgJZieEm`M{VFr^Iw_!G+CSzp@HX?I zumZW`DP;7+=6hBF^iIHmU4IPS{wBKqU*P0U(?t~8tp!B*TUO4wInue#23+!k92Kdu z)k_a-H2q$`n~;jT%&!_qH=|-&w)b^AmmB3OzS7peW%q)@q0n>=4*sd_??kmJ+cFqL zrROzfG%gqd`Gd@{+!Ey4uR}%;Q&%9}9KXNIS3?gLZ9j{^N{B~y0< z-TDUdrI|2jGBRj#$MatE%(MH%K~L?0+Cxa$L^M=u`ZX{=UTS8?TW)SNGl869r2kk5_5nAHp83q^5NX04 zo$azYcQ|wBx~&H~8gRv9-&0r=tJMEbh{(N6Tlm}7?`?hH;#mQO%FCkFRfMYh{r2D6 z3>Yk#0`%{0g|0qiEUPN7YyEc7fUe4emq($i57*^^*3YI7TsJ$Y=QEB1-t&$~WEUbM z+u1#kYUXxVGR>j3nL+3by5;3~q#TgVxn<0q>zcbgmoXKI(Rg4K$t#IRfvcTT#@3Aj zK5zhBho}o?PeL_q&V#FwOQ%Y)pgI{^`an@8M=$Mx+YEJw<5%J`qczr^`_9*58l7WO z%orNCq_g|uzVxzlHhu#F<5@f}%|YB7=;SW3N6+j_M2X9XhRt#=ezM7~sc=9m5>n*E z4)Z8qZJgL?Tw4htLmW5J_-@mAIQD6RigZ7EXmjF1L@FFfv;_jXcXe0iF(#L~qi{U& zAKgVOQb2lpmgsp+jg;SEl7<55k)v33hmBYH@P`qjYgZz=)40lH)VCqsBPXi)oGH6& zh38-+P?Q4Xc-9n1b13k6!t+Ytz(#b(?~z`^Ag!x2coXFC7UQ5T8Ve5$|GKgt<@2Y}?Y{*lfDD=DUtYsd7bS&IjRC!zOz2F2P>p{@ z1%shvG@PhLcPEr?*L5_9Ox8lvc^EXsI9L6sJ?uA9=Qwx=ZVi^Bm0bH5kk{V?(m!8k zbxpSsZbCo$1=JpbUc-=byLg!%`Lj;})B&RA#OZ;JcJsVIn(36Yd!yA$SVBb18wa2Pr(t;*v-ETB}w$|jMTukBUMo#TP zH$H2YYl9{sqaSJuXZmq?i}~|KYaPHpQ(YC^@(VcgnVkZGRAj{c$c=wvl8N4%5ABnO zx{~G)`sr`b%|8Pt1sU@lq~dP-tcGKsqFZ0HDyqnlz?0v&d5pkV-_mXvj(m*dm)WL7 zn6PF4apv=$x8c-oBs1Us`dDzAO+5MISE2qWGGrPu{JzeqanU&X3Hs+(TCyelagf(U zmsQSI;d$#yoD^j6R4AJmTdqoT2oC-UJ-7){(vX1@pm?Nrj-D+S>e2o0!-0*E(E}+R z2Yqic;RRdVW_0&T__M@r`$F`b5vLUA2asVVC!$eok>8Y-qz$RNY62T(V z$M_}ws0;hvZAtlUH*Ps8$PM4JTvoNpQgqv5laJV*%zUK$PLm4f#}^-&Ru{ipzeeg0 z7Ko~%D<0bd+z_uM02y`v8*=SEo~N~X zDSZA1ySlttBGaGwnggK7TEEhMX!Tp(9^U;Myp0EDrLf~oyNW_J#(frz{qMoiPa>Z? zzAYKXL)6&J0dO2iT%szv@4ZMT(0%LuuRpcNDn_CE-ZdHie&s%oT&E^&h2*vw5VvtP z;GZDTCGpe>is{yA!*Qm-H3CoQ%1&AeX z>}P0I2%WXm#bYk#+RgEzA?PcupD&KQj`FL7B}a0mrexYrEt(^|^wQyc$o>cMa_lDV z@Ksb^ZT-@AXLnWo5v4jI(OCtZkBQi?Jp9(GDQQ^GIV0&3?YKQ_nX&L;O)A`jE zRaZ}R9W$zyU3#>$0cXy=AddS;OtSL_ns`VwO2$MVBr@_Kr^vS|DfGz$$jE~}!=sn! zZTCd9qe*aPM+!Mco?lF@TCIqkR}qk6i)D05hIz%*i#{W<8ew}3Ft3n#${FGBA`_>j zK06c^F@53oNas8!XpR%E1gv~K`q>{MzchWZK-fh0j&JiQARS&#jEXo&|A=p08NT=s zj&1dhPpcX|`~pU8>~aE*=PR$#-&kMy_*7M0j;*UJBM)->nUOLj!$h`1wNQI7@|)qU z6jqp+g)NPB=(?w(M+?(}Mwj$l^IlxjdALvqwO@2}r$r|u4&#{mjOQBD_mzKl)R&&q+Y@@ zJ>j+TrKDzp%?R#+)!Z^xl+BVEy3ckiDEm0b-3w2O8=jg}Bj)xu8%)GYg6rDO=q3s1 zberu?T8S-?boa9d#Fg7nlGA&i+7*8eO>@Y^fRo2^2?VCZx}j}+n~%NyPuUs{F9w+& z`^6C`C^rH4p5@U!GO8y0czl~ldb0F%`=QFqPEq|s@$BFh|MqwD<~fY1c|wsVfdr%D z?MTlx^U1WJ@y@tmGk?3#U_y}1N!_3~B&u_mQT4NU1tehu#>xqb{3p;iF+ttVE>ZVB zW-Rha0*p?a`-rq-dewSKYaF4%&$+k<$`wtM!h8uhHzsO4NcJYQ%N`%4qAws=qy}6T z-WAuG&0PzU_F_x&w?SS!D?qUHeno!>#E`GimFOl>dCK_nl6%8lw-LA5!TcVx$AI{! z&m|uP){3J)zSIq$OuA<;{kI<<>Ne~BB0LCKMwsG7cB<-Yjs#v|`ur$9wj|Zot^XN; zX_cc;CMh}@7*DL*40NUjSZ{Jw+_kM41KmaU8t_s+dN`G1*+UVh0vO`L5J8I(f~W>T zObAY?L#RWjSC=mwkzK$p0#s9TU^g@0NIcyjPW~6yDRlJPDonBf0000 + + + grails + Created with Sketch. + + + + + + + + \ No newline at end of file diff --git a/example/grails-app/assets/images/slack.svg b/example/grails-app/assets/images/slack.svg new file mode 100644 index 0000000..34fcf4c --- /dev/null +++ b/example/grails-app/assets/images/slack.svg @@ -0,0 +1,18 @@ + + + + slack_orange + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/example/grails-app/assets/javascripts/application.js b/example/grails-app/assets/javascripts/application.js new file mode 100644 index 0000000..c290381 --- /dev/null +++ b/example/grails-app/assets/javascripts/application.js @@ -0,0 +1,20 @@ +// This is a manifest file that'll be compiled into application.js. +// +// Any JavaScript file within this directory can be referenced here using a relative path. +// +// You're free to add application-wide JavaScript to this file, but it's generally better +// to create separate JavaScript files as needed. +// +//= require webjars/jquery/3.7.1/dist/jquery.js +//= require webjars/bootstrap/5.3.7/dist/js/bootstrap.bundle +//= require_self + +if (typeof jQuery !== 'undefined') { + (function($) { + $('#spinner').ajaxStart(function() { + $(this).fadeIn(); + }).ajaxStop(function() { + $(this).fadeOut(); + }); + })(jQuery); +} \ No newline at end of file diff --git a/example/grails-app/assets/stylesheets/application.css b/example/grails-app/assets/stylesheets/application.css new file mode 100644 index 0000000..ec60949 --- /dev/null +++ b/example/grails-app/assets/stylesheets/application.css @@ -0,0 +1,14 @@ +/* +* This is a manifest file that'll be compiled into application.css, which will include all the files +* listed below. +* +* Any CSS file within this directory can be referenced here using a relative path. +* +* You're free to add application-wide styles to this file and they'll appear at the top of the +* compiled file, but it's generally better to create a new file per style scope. +* +*= require webjars/bootstrap/5.3.7/dist/css/bootstrap +*= require webjars/bootstrap-icons/1.13.1/font/bootstrap-icons +*= require grails +*= require_self +*/ diff --git a/example/grails-app/assets/stylesheets/errors.css b/example/grails-app/assets/stylesheets/errors.css new file mode 100644 index 0000000..bd71149 --- /dev/null +++ b/example/grails-app/assets/stylesheets/errors.css @@ -0,0 +1,90 @@ +.filename { + font-style: italic; +} + +.exceptionMessage { + margin: 10px; + border: 1px solid #000; + padding: 5px; + background-color: #E9E9E9; +} + +.stack, +.snippet { + margin: 10px 0; +} + +.stack, +.snippet { + border: 1px solid #ccc; +} + +/* error details */ +.error-details { + border: 1px solid #FFAAAA; + background-color:#FFF3F3; + line-height: 1.5; + overflow: hidden; + padding: 10px 0 5px 25px; +} + +.error-details dt { + clear: left; + float: left; + font-weight: bold; + margin-right: 5px; +} + +.error-details dt:after { + content: ":"; +} + +.error-details dd { + display: block; +} + +/* stack trace */ +.stack { + padding: 5px; + overflow: auto; + height: 300px; +} + +/* code snippet */ +.snippet { + background-color: #fff; + font-family: monospace; +} + +.snippet .line { + display: block; +} + +.snippet .lineNumber { + background-color: #ddd; + color: #999; + display: inline-block; + margin-right: 5px; + padding: 0 3px; + text-align: right; + width: 3em; +} + +.snippet .error { + background-color: #fff3f3; + font-weight: bold; +} + +.snippet .error .lineNumber { + background-color: #faa; + color: #333; + font-weight: bold; +} + +.snippet .line:first-child .lineNumber { + padding-top: 5px; +} + +.snippet .line:last-child .lineNumber { + padding-bottom: 5px; +} \ No newline at end of file diff --git a/example/grails-app/assets/stylesheets/grails.css b/example/grails-app/assets/stylesheets/grails.css new file mode 100644 index 0000000..2ab6505 --- /dev/null +++ b/example/grails-app/assets/stylesheets/grails.css @@ -0,0 +1,77 @@ +table.scaffold tr>td:first-child, tr>th:first-child { + padding-left: 1.25em; +} + +table.scaffold tr>td:last-child, tr>th:last-child { + padding-right: 1.25em; +} + +table.scaffold th { + background-image: linear-gradient( + to bottom, + #ffffff 0%, + #f8f8f8 30%, + #eaeaea 70%, + #d4d4d4 100% + ); + border-bottom: 2px solid #b3b3b3; /* Adding a subtle shadow effect */ + box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1); /* Adding a drop shadow */ +} + +[data-bs-theme=dark] table.scaffold th { + background-image: linear-gradient( + to bottom, + #4a4a4a 0%, + #3e3e3e 30%, + #2a2a2a 70%, + #1e1e1e 100% + ); + border-bottom: 2px solid #141414; /* Adding a subtle shadow effect */ + box-shadow: 0 2px 3px rgba(0, 0, 0, 0.3); /* Adding a drop shadow */ +} + +table.scaffold thead th { + white-space: nowrap; +} + +table.scaffold th a { + display: block; + text-decoration: none; +} + +table.scaffold th a:link, th a:visited { + color: #666666; +} + +table.scaffold th a:hover, th a:focus { + color: #333333; +} + +table.scaffold th.sortable a { + background-position: right; + background-repeat: no-repeat; + padding-right: 1.1em; +} + +table.scaffold th { + position: relative; +} + + +table.scaffold th.asc a:after { + content: '▲'; + position: absolute; + right: 10px; + font-size: 0.8em; +} + +table.scaffold th.desc a:after { + content: '▼'; + position: absolute; + right: 10px; + font-size: 0.8em; +} + +table.scaffold th:hover { + background: #f5f5f5 !important; +} \ No newline at end of file diff --git a/example/grails-app/conf/application.yml b/example/grails-app/conf/application.yml new file mode 100644 index 0000000..00e7eb3 --- /dev/null +++ b/example/grails-app/conf/application.yml @@ -0,0 +1,82 @@ +info: + app: + name: '@info.app.name@' + version: '@info.app.version@' + grailsVersion: '@info.app.grailsVersion@' +grails: + views: + default: + codec: html + gsp: + encoding: UTF-8 + htmlcodec: xml + codecs: + expression: html + scriptlet: html + taglib: none + staticparts: none + mime: + disable: + accept: + header: + userAgents: + - Gecko + - WebKit + - Presto + - Trident + types: + all: '*/*' + atom: application/atom+xml + css: text/css + csv: text/csv + form: application/x-www-form-urlencoded + html: + - text/html + - application/xhtml+xml + js: text/javascript + json: + - application/json + - text/json + multipartForm: multipart/form-data + pdf: application/pdf + rss: application/rss+xml + text: text/plain + hal: + - application/hal+json + - application/hal+xml + xml: + - text/xml + - application/xml + codegen: + defaultPackage: example + profile: web +dataSource: + driverClassName: org.h2.Driver + username: sa + password: '' + pooled: true + jmxExport: true +environments: + development: + dataSource: + dbCreate: create-drop + url: jdbc:h2:mem:devDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE + test: + dataSource: + dbCreate: update + url: jdbc:h2:mem:testDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE + production: + dataSource: + dbCreate: none + url: jdbc:h2:./prodDb;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE +hibernate: + cache: + queries: false + use_second_level_cache: false + use_query_cache: false +--- +server: + servlet: + context-path: /rendering +grails: + serverURL: http://localhost:8080/rendering \ No newline at end of file diff --git a/example/grails-app/conf/logback-spring.xml b/example/grails-app/conf/logback-spring.xml new file mode 100644 index 0000000..2f198ed --- /dev/null +++ b/example/grails-app/conf/logback-spring.xml @@ -0,0 +1,39 @@ + + + + + + true + + ${CONSOLE_LOG_THRESHOLD} + + + ${CONSOLE_LOG_PATTERN} + ${CONSOLE_LOG_CHARSET} + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/grails-app/conf/spring/resources.groovy b/example/grails-app/conf/spring/resources.groovy new file mode 100644 index 0000000..5008c27 --- /dev/null +++ b/example/grails-app/conf/spring/resources.groovy @@ -0,0 +1,3 @@ +// Place your Spring DSL code here +beans = { +} \ No newline at end of file diff --git a/grails-app/controllers/RenderingController.groovy b/example/grails-app/controllers/example/RenderingController.groovy similarity index 81% rename from grails-app/controllers/RenderingController.groovy rename to example/grails-app/controllers/example/RenderingController.groovy index 319cee4..ee06edc 100644 --- a/grails-app/controllers/RenderingController.groovy +++ b/example/grails-app/controllers/example/RenderingController.groovy @@ -1,3 +1,5 @@ +package example + /* * Copyright 2010 Grails Plugin Collective * @@ -13,38 +15,39 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -class RenderingController implements grails.plugins.rendering.RenderingTrait{ +class RenderingController { - def pdf = { + def index() {} + + def pdf() { renderPdf(template) } - def jpeg = { + def jpeg() { renderJpeg(template + [width: 200]) } - def gif = { + def gif() { renderGif(template + [render: [width: 600, height: 200], clip: [height: true, width: true], resize: [width: 600, height: 200]]) } - def png = { + def png() { renderPng(template + [width: 100]) } - def relative = { + def relative() { renderGif(template: 'relative') -/* render(template: 'relative')*/ } - def dataUriPdf = { + def dataUriPdf() { renderPdf(template: '/datauri') } - def dataUriImg = { + def dataUriImg() { renderGif(template: '/datauri') } - def encodingTest = { + def encodingTest() { renderPdf(template: "/encoding-test") } diff --git a/example/grails-app/controllers/example/UrlMappings.groovy b/example/grails-app/controllers/example/UrlMappings.groovy new file mode 100644 index 0000000..c2bc78b --- /dev/null +++ b/example/grails-app/controllers/example/UrlMappings.groovy @@ -0,0 +1,16 @@ +package example + +class UrlMappings { + static mappings = { + "/$controller/$action?/$id?(.$format)?"{ + constraints { + // apply constraints here + } + } + + "/"(view:"/index") + "500"(view:'/error') + "404"(view:'/notFound') + + } +} diff --git a/example/grails-app/i18n/messages.properties b/example/grails-app/i18n/messages.properties new file mode 100644 index 0000000..b045136 --- /dev/null +++ b/example/grails-app/i18n/messages.properties @@ -0,0 +1,56 @@ +default.doesnt.match.message=Property [{0}] of class [{1}] with value [{2}] does not match the required pattern [{3}] +default.invalid.url.message=Property [{0}] of class [{1}] with value [{2}] is not a valid URL +default.invalid.creditCard.message=Property [{0}] of class [{1}] with value [{2}] is not a valid credit card number +default.invalid.email.message=Property [{0}] of class [{1}] with value [{2}] is not a valid e-mail address +default.invalid.range.message=Property [{0}] of class [{1}] with value [{2}] does not fall within the valid range from [{3}] to [{4}] +default.invalid.size.message=Property [{0}] of class [{1}] with value [{2}] does not fall within the valid size range from [{3}] to [{4}] +default.invalid.max.message=Property [{0}] of class [{1}] with value [{2}] exceeds maximum value [{3}] +default.invalid.min.message=Property [{0}] of class [{1}] with value [{2}] is less than minimum value [{3}] +default.invalid.max.size.message=Property [{0}] of class [{1}] with value [{2}] exceeds the maximum size of [{3}] +default.invalid.min.size.message=Property [{0}] of class [{1}] with value [{2}] is less than the minimum size of [{3}] +default.invalid.validator.message=Property [{0}] of class [{1}] with value [{2}] does not pass custom validation +default.not.inlist.message=Property [{0}] of class [{1}] with value [{2}] is not contained within the list [{3}] +default.blank.message=Property [{0}] of class [{1}] cannot be blank +default.not.equal.message=Property [{0}] of class [{1}] with value [{2}] cannot equal [{3}] +default.null.message=Property [{0}] of class [{1}] cannot be null +default.not.unique.message=Property [{0}] of class [{1}] with value [{2}] must be unique + +default.paginate.prev=Previous +default.paginate.next=Next +default.boolean.true=True +default.boolean.false=False +default.date.format=yyyy-MM-dd HH:mm:ss z +default.number.format=0 + +default.created.message={0} {1} created +default.updated.message={0} {1} updated +default.deleted.message={0} {1} deleted +default.not.deleted.message={0} {1} could not be deleted +default.not.found.message={0} not found with id {1} +default.optimistic.locking.failure=Another user has updated this {0} while you were editing + +default.home.label=Home +default.list.label={0} List +default.add.label=Add {0} +default.new.label=New {0} +default.create.label=Create {0} +default.show.label=Show {0} +default.edit.label=Edit {0} + +default.button.create.label=Create +default.button.edit.label=Edit +default.button.update.label=Update +default.button.delete.label=Delete +default.button.delete.confirm.message=Are you sure? + +# Data binding errors. Use "typeMismatch.$className.$propertyName to customize (eg typeMismatch.Book.author) +typeMismatch.java.net.URL=Property {0} must be a valid URL +typeMismatch.java.net.URI=Property {0} must be a valid URI +typeMismatch.java.util.Date=Property {0} must be a valid Date +typeMismatch.java.lang.Double=Property {0} must be a valid number +typeMismatch.java.lang.Integer=Property {0} must be a valid number +typeMismatch.java.lang.Long=Property {0} must be a valid number +typeMismatch.java.lang.Short=Property {0} must be a valid number +typeMismatch.java.math.BigDecimal=Property {0} must be a valid number +typeMismatch.java.math.BigInteger=Property {0} must be a valid number +typeMismatch=Property {0} is type-mismatched diff --git a/example/grails-app/init/example/Application.groovy b/example/grails-app/init/example/Application.groovy new file mode 100644 index 0000000..1737c76 --- /dev/null +++ b/example/grails-app/init/example/Application.groovy @@ -0,0 +1,12 @@ +package example + +import grails.boot.GrailsApp +import grails.boot.config.GrailsAutoConfiguration +import groovy.transform.CompileStatic + +@CompileStatic +class Application extends GrailsAutoConfiguration { + static void main(String[] args) { + GrailsApp.run(Application, args) + } +} diff --git a/example/grails-app/init/example/BootStrap.groovy b/example/grails-app/init/example/BootStrap.groovy new file mode 100644 index 0000000..e9e593e --- /dev/null +++ b/example/grails-app/init/example/BootStrap.groovy @@ -0,0 +1,11 @@ +package example + +class BootStrap { + + def init = { + } + + def destroy = { + } + +} \ No newline at end of file diff --git a/grails-app/services/grails/plugins/rendering/test/BackgroundRenderingService.groovy b/example/grails-app/services/grails/plugins/rendering/test/BackgroundRenderingService.groovy similarity index 100% rename from grails-app/services/grails/plugins/rendering/test/BackgroundRenderingService.groovy rename to example/grails-app/services/grails/plugins/rendering/test/BackgroundRenderingService.groovy diff --git a/grails-app/views/_bad-xml.gsp b/example/grails-app/views/_bad-xml.gsp similarity index 100% rename from grails-app/views/_bad-xml.gsp rename to example/grails-app/views/_bad-xml.gsp diff --git a/grails-app/views/_datauri.gsp b/example/grails-app/views/_datauri.gsp similarity index 71% rename from grails-app/views/_datauri.gsp rename to example/grails-app/views/_datauri.gsp index 33d0892..964de61 100644 --- a/grails-app/views/_datauri.gsp +++ b/example/grails-app/views/_datauri.gsp @@ -18,6 +18,6 @@

Below is an inline image

- Red dot + \ No newline at end of file diff --git a/grails-app/views/_encoding-test.gsp b/example/grails-app/views/_encoding-test.gsp similarity index 94% rename from grails-app/views/_encoding-test.gsp rename to example/grails-app/views/_encoding-test.gsp index 6788dd1..9bab581 100644 --- a/grails-app/views/_encoding-test.gsp +++ b/example/grails-app/views/_encoding-test.gsp @@ -6,7 +6,7 @@ Osobowość - + \ No newline at end of file diff --git a/example/grails-app/views/rendering/index.gsp b/example/grails-app/views/rendering/index.gsp new file mode 100644 index 0000000..b9139cb --- /dev/null +++ b/example/grails-app/views/rendering/index.gsp @@ -0,0 +1,28 @@ +<%-- + Created by IntelliJ IDEA. + User: sbglasius + Date: 29/09/2025 + Time: 22.17 +--%> + +<%@ page contentType="text/html;charset=UTF-8" %> + + + + Rendering + + + +
    +
  • GIF
  • +
  • JPEG
  • +
  • PNG
  • +
  • PDF
  • +
  • Relative
  • +
  • Data Uri PDF
  • +
  • Data Uri GIF
  • +
  • Encoding Test
  • +
+ + + \ No newline at end of file diff --git a/example/grails-forge-cli.yml b/example/grails-forge-cli.yml new file mode 100644 index 0000000..f00dc1d --- /dev/null +++ b/example/grails-forge-cli.yml @@ -0,0 +1,8 @@ +applicationType: web +defaultPackage: example +testFramework: spock +sourceLanguage: groovy +buildTool: gradle +gormImpl: gorm-hibernate5 +servletImpl: spring-boot-starter-tomcat +features: [app-name, asset-pipeline-grails, base, geb-with-testcontainers, gorm-hibernate5, gradle, grails-application, grails-console, grails-dependencies, grails-gorm-testing-support, grails-gradle-plugin, grails-gsp, grails-profiles, grails-url-mappings, grails-web, grails-web-testing-support, grails-wrapper, h2, logback, readme, scaffolding, spock, spring-boot-autoconfigure, spring-boot-devtools, spring-boot-starter, spring-boot-starter-tomcat, spring-resources, yaml] diff --git a/example/grails-wrapper.jar b/example/grails-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..0498baab25a5f2860a6161264334e09520775b6c GIT binary patch literal 28481 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 ik$<8`EdGN2=K)he8Upf<9Z=pLzusi+?gT{AHN literal 0 HcmV?d00001 diff --git a/example/grailsw b/example/grailsw new file mode 100755 index 0000000..c27e487 --- /dev/null +++ b/example/grailsw @@ -0,0 +1,250 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# grailsw start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh grailsw +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRAILSW_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/grails-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRAILSW_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRAILSW_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + -classpath "$CLASSPATH" \ + grails.init.Start \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRAILSW_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/example/grailsw.bat b/example/grailsw.bat new file mode 100644 index 0000000..810aba1 --- /dev/null +++ b/example/grailsw.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem grailsw startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRAILSW_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\grails-wrapper.jar + + +@rem Execute grailsw +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRAILSW_OPTS% -classpath "%CLASSPATH%" grails.init.Start %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRAILSW_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRAILSW_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/example/src/integration-test/groovy/example/ExampleSpec.groovy b/example/src/integration-test/groovy/example/ExampleSpec.groovy new file mode 100644 index 0000000..ae1c8d9 --- /dev/null +++ b/example/src/integration-test/groovy/example/ExampleSpec.groovy @@ -0,0 +1,19 @@ +package example +import grails.plugin.geb.ContainerGebSpec +import grails.testing.mixin.integration.Integration + +/** + * See https://docs.grails.org/latest/guide/testing.html#functionalTesting and https://groovy.apache.org/geb/manual/current/ + * for more instructions on how to write functional tests with Grails and Geb. + */ +@Integration +class ExampleSpec extends ContainerGebSpec { + + void 'should display the correct title on the home page'() { + when: 'visiting the home page' + go('/rendering') + + then: 'the page title is correct' + title == 'Welcome to Grails' + } +} diff --git a/src/integration-test/groovy/grails/plugins/rendering/BackgroundRenderingSpec.groovy b/example/src/integration-test/groovy/grails/plugins/rendering/BackgroundRenderingSpec.groovy similarity index 100% rename from src/integration-test/groovy/grails/plugins/rendering/BackgroundRenderingSpec.groovy rename to example/src/integration-test/groovy/grails/plugins/rendering/BackgroundRenderingSpec.groovy diff --git a/example/src/integration-test/groovy/grails/plugins/rendering/ControllerRelativeTemplateSpec.groovy b/example/src/integration-test/groovy/grails/plugins/rendering/ControllerRelativeTemplateSpec.groovy new file mode 100644 index 0000000..20a0cab --- /dev/null +++ b/example/src/integration-test/groovy/grails/plugins/rendering/ControllerRelativeTemplateSpec.groovy @@ -0,0 +1,57 @@ +/* + * Copyright 2010 Grails Plugin Collective + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package grails.plugins.rendering + +import grails.testing.mixin.integration.Integration +import org.springframework.beans.factory.annotation.Value +import org.springframework.http.HttpMethod +import org.springframework.web.client.RestTemplate +import spock.lang.Specification + +@Integration +class ControllerRelativeTemplateSpec extends Specification { + + @Value('${local.server.port}') + Integer port + RestTemplate restTemplate = new RestTemplate() + + def 'accessing controllers that does rendering'() { + when: + def resp = restTemplate.exchange( + getUrl(uri), + HttpMethod.GET, + null, + byte[].class + ) + then: + resp.statusCode.value() == 200 + resp.headers['Content-Type'] == [expectedContentType] + where: + uri | expectedContentType + 'gif' | 'image/gif' + 'jpeg' | 'image/jpeg' + 'png' | 'image/png' + 'pdf' | 'application/pdf' + 'dataUriImg' | 'image/gif' + 'dataUriPdf' | 'application/pdf' + 'relative' | 'image/gif' + 'encodingTest' | 'application/pdf' + } + + private String getUrl(String uri) { + return "http://localhost:${port}/rendering/rendering/${uri}" + } +} diff --git a/src/integration-test/groovy/grails/plugins/rendering/RenderingServiceSpec.groovy b/example/src/integration-test/groovy/grails/plugins/rendering/RenderingServiceSpec.groovy similarity index 100% rename from src/integration-test/groovy/grails/plugins/rendering/RenderingServiceSpec.groovy rename to example/src/integration-test/groovy/grails/plugins/rendering/RenderingServiceSpec.groovy diff --git a/src/integration-test/groovy/grails/plugins/rendering/document/XhtmlDocumentServiceSpec.groovy b/example/src/integration-test/groovy/grails/plugins/rendering/document/XhtmlDocumentServiceSpec.groovy similarity index 100% rename from src/integration-test/groovy/grails/plugins/rendering/document/XhtmlDocumentServiceSpec.groovy rename to example/src/integration-test/groovy/grails/plugins/rendering/document/XhtmlDocumentServiceSpec.groovy diff --git a/src/integration-test/groovy/grails/plugins/rendering/image/GifRenderingServiceSpec.groovy b/example/src/integration-test/groovy/grails/plugins/rendering/image/GifRenderingServiceSpec.groovy similarity index 100% rename from src/integration-test/groovy/grails/plugins/rendering/image/GifRenderingServiceSpec.groovy rename to example/src/integration-test/groovy/grails/plugins/rendering/image/GifRenderingServiceSpec.groovy diff --git a/src/integration-test/groovy/grails/plugins/rendering/image/ImageRenderingServiceSpec.groovy b/example/src/integration-test/groovy/grails/plugins/rendering/image/ImageRenderingServiceSpec.groovy similarity index 100% rename from src/integration-test/groovy/grails/plugins/rendering/image/ImageRenderingServiceSpec.groovy rename to example/src/integration-test/groovy/grails/plugins/rendering/image/ImageRenderingServiceSpec.groovy diff --git a/src/integration-test/groovy/grails/plugins/rendering/image/JpegRenderingServiceSpec.groovy b/example/src/integration-test/groovy/grails/plugins/rendering/image/JpegRenderingServiceSpec.groovy similarity index 100% rename from src/integration-test/groovy/grails/plugins/rendering/image/JpegRenderingServiceSpec.groovy rename to example/src/integration-test/groovy/grails/plugins/rendering/image/JpegRenderingServiceSpec.groovy diff --git a/src/integration-test/groovy/grails/plugins/rendering/image/PngRenderingServiceSpec.groovy b/example/src/integration-test/groovy/grails/plugins/rendering/image/PngRenderingServiceSpec.groovy similarity index 100% rename from src/integration-test/groovy/grails/plugins/rendering/image/PngRenderingServiceSpec.groovy rename to example/src/integration-test/groovy/grails/plugins/rendering/image/PngRenderingServiceSpec.groovy diff --git a/src/integration-test/groovy/grails/plugins/rendering/pdf/PdfRenderingServiceSpec.groovy b/example/src/integration-test/groovy/grails/plugins/rendering/pdf/PdfRenderingServiceSpec.groovy similarity index 97% rename from src/integration-test/groovy/grails/plugins/rendering/pdf/PdfRenderingServiceSpec.groovy rename to example/src/integration-test/groovy/grails/plugins/rendering/pdf/PdfRenderingServiceSpec.groovy index 2444710..d1f4300 100644 --- a/src/integration-test/groovy/grails/plugins/rendering/pdf/PdfRenderingServiceSpec.groovy +++ b/example/src/integration-test/groovy/grails/plugins/rendering/pdf/PdfRenderingServiceSpec.groovy @@ -19,7 +19,7 @@ import grails.plugins.rendering.RenderingServiceSpec import org.apache.pdfbox.pdfparser.PDFParser import org.apache.pdfbox.pdmodel.PDDocument -import org.apache.pdfbox.util.PDFTextStripper +import org.apache.pdfbox.text.PDFTextStripper class PdfRenderingServiceSpec extends RenderingServiceSpec { diff --git a/example/src/main/groovy/.gitkeep b/example/src/main/groovy/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/example/src/test/groovy/.gitkeep b/example/src/test/groovy/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/settings.gradle b/settings.gradle index e69de29..6cc8768 100644 --- a/settings.gradle +++ b/settings.gradle @@ -0,0 +1,3 @@ +rootProject.name = 'rendering' + +include 'example' \ No newline at end of file diff --git a/src/integration-test/groovy/grails/plugins/rendering/ControllerRelativeTemplateSpec.groovy b/src/integration-test/groovy/grails/plugins/rendering/ControllerRelativeTemplateSpec.groovy deleted file mode 100644 index 29d5968..0000000 --- a/src/integration-test/groovy/grails/plugins/rendering/ControllerRelativeTemplateSpec.groovy +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2010 Grails Plugin Collective - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package grails.plugins.rendering - - -import org.springframework.http.HttpMethod -import org.springframework.web.client.RestTemplate -import spock.lang.Specification - -class ControllerRelativeTemplateSpec extends Specification { - - def accessingControllerRelativeTemplateWorks() { - when: - RestTemplate restTemplate = new RestTemplate() - def resp = restTemplate.exchange( - "http://localhost:8080/rendering/relative", - HttpMethod.GET, - null, - String - ) - then: - resp.statusCode.value() == 200 - } -} diff --git a/src/integration-test/groovy/grails/plugins/rendering/RenderingGrailsPluginSpec.groovy b/src/integration-test/groovy/grails/plugins/rendering/RenderingGrailsPluginSpec.groovy deleted file mode 100644 index 5693291..0000000 --- a/src/integration-test/groovy/grails/plugins/rendering/RenderingGrailsPluginSpec.groovy +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2010 Grails Plugin Collective - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package grails.plugins.rendering - -import grails.testing.mixin.integration.Integration -import grails.util.Holders -import spock.lang.Ignore -import spock.lang.Specification -import spock.lang.Unroll - -@Integration -class RenderingGrailsPluginSpec extends Specification { - - def grailsApplication - - @Ignore - @Unroll("rendering #action works from controllers and survives a reload") - def supportReloadingControllerClasses() { - when: - def controller = createController() - controller."$action"() - then: - notThrown(MissingMethodException) - when: - Holders.pluginManager.informOfClassChange(reloadControllerClass()) - controller = createController() - and: - controller."$action"() - then: - notThrown(MissingMethodException) - where: - action << ['pdf', 'gif', 'png', 'jpeg'] - } - - protected createController() { - grailsApplication.mainContext['RenderingController'] - } - - protected reloadControllerClass() { - grailsApplication.classLoader.reloadClass('RenderingController') - } -} From 69f5250d6da3684ad45b032134cb678e33a433a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Berg=20Glasius?= Date: Tue, 30 Sep 2025 11:06:52 +0200 Subject: [PATCH 08/12] Removed obsolete dependency from example project, that is now api in root project --- example/build.gradle | 3 --- 1 file changed, 3 deletions(-) diff --git a/example/build.gradle b/example/build.gradle index f007554..4facb35 100644 --- a/example/build.gradle +++ b/example/build.gradle @@ -70,9 +70,6 @@ dependencies { testImplementation "org.spockframework:spock-core" implementation project(':') - implementation("org.apache.pdfbox:pdfbox:3.0.5") { - exclude module: 'jempbox' - } } From 4a630d47e663292253ae325e1dafde887a5f9cfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Berg=20Glasius?= Date: Tue, 30 Sep 2025 11:09:33 +0200 Subject: [PATCH 09/12] Fixed word missing in sentence --- src/main/asciidoc/1. Introduction.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/asciidoc/1. Introduction.adoc b/src/main/asciidoc/1. Introduction.adoc index be2763d..975b78b 100644 --- a/src/main/asciidoc/1. Introduction.adoc +++ b/src/main/asciidoc/1. Introduction.adoc @@ -9,7 +9,7 @@ Rendering is either done directly via `«format»RenderingService` services ... ByteArrayOutputStream bytes = pdfRenderingService.render(template: "/pdfs/report", model: [data: data]) ---- -Or via the `render«format»()` methods in `RenderingTrait` that is to controllers ... +Or via the `render«format»()` methods in `RenderingTrait` that is added to controllers ... [source,groovy] ---- From 3c96c96af201d80b85dd15c409bc9ee9c0982396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Berg=20Glasius?= Date: Tue, 30 Sep 2025 11:12:33 +0200 Subject: [PATCH 10/12] Cleanup .gitignore files --- .gitignore | 4 ---- src/.gitignore | 1 - 2 files changed, 5 deletions(-) delete mode 100644 src/.gitignore diff --git a/.gitignore b/.gitignore index e14fb07..46eac81 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,8 @@ build/ .gradle/ -target -plugin.xml -docs *.log *.zip .DS_Store -grails-rendering-* .settings .vscode/ bin/ diff --git a/src/.gitignore b/src/.gitignore deleted file mode 100644 index 6349bf2..0000000 --- a/src/.gitignore +++ /dev/null @@ -1 +0,0 @@ -!main/docs \ No newline at end of file From b24ebb1b3fcad0e3ddff88f2882ecc545fafabf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Berg=20Glasius?= Date: Tue, 30 Sep 2025 22:31:10 +0200 Subject: [PATCH 11/12] Made sure, that there are not traces of itext, but instead OpenPDF is used * Did some extra refactorings, added more @CompileStatic * ITextRenderer from the latest flyingsaucer handles inlined images in .gsps --- build.gradle | 4 +- example/grails-app/views/_datauri.gsp | 4 +- .../document/XhtmlDocumentService.groovy | 196 +++++++++--------- .../rendering/pdf/PdfRenderingService.groovy | 35 +--- grails-app/views/error.gsp | 54 ----- .../rendering/GrailsRenderingException.groovy | 12 +- .../rendering/RenderingException.groovy | 5 +- .../rendering/RenderingGrailsPlugin.groovy | 37 ++-- .../plugins/rendering/RenderingTrait.groovy | 2 + .../plugins/rendering/datauri/DataUri.groovy | 7 +- .../datauri/DataUriAwareITextUserAgent.groovy | 66 ------ .../datauri/DataUriAwareNaiveUserAgent.groovy | 35 ++-- .../document/RenderEnvironment.groovy | 111 +++++----- .../document/UnknownTemplateException.groovy | 2 + .../document/XmlParseException.groovy | 9 +- 15 files changed, 236 insertions(+), 343 deletions(-) delete mode 100644 grails-app/views/error.gsp delete mode 100644 src/main/groovy/grails/plugins/rendering/datauri/DataUriAwareITextUserAgent.groovy diff --git a/build.gradle b/build.gradle index f609eeb..741b4ff 100644 --- a/build.gradle +++ b/build.gradle @@ -58,7 +58,7 @@ dependencies { compileOnly platform("org.apache.grails:grails-bom:$grailsVersion") compileOnly 'org.apache.grails:grails-dependencies-starter-web' - api 'org.xhtmlrenderer:flying-saucer-pdf-openpdf:9.1.22' + api('org.xhtmlrenderer:flying-saucer-pdf-openpdf:9.4.0') api("org.apache.pdfbox:pdfbox:3.0.5") testImplementation platform("org.apache.grails:grails-bom:$grailsVersion") @@ -83,7 +83,7 @@ jar { tasks.withType(Test).configureEach { useJUnitPlatform() testLogging { - events 'passed', 'skipped', 'failed' + events 'passed', 'skipped', 'failed', 'standardOut', 'standardError' } } diff --git a/example/grails-app/views/_datauri.gsp b/example/grails-app/views/_datauri.gsp index 964de61..ce35637 100644 --- a/example/grails-app/views/_datauri.gsp +++ b/example/grails-app/views/_datauri.gsp @@ -18,6 +18,8 @@

Below is an inline image

- +

It's a red dot.

+ Red dot \ No newline at end of file diff --git a/grails-app/services/grails/plugins/rendering/document/XhtmlDocumentService.groovy b/grails-app/services/grails/plugins/rendering/document/XhtmlDocumentService.groovy index b5922bc..f1f52ee 100644 --- a/grails-app/services/grails/plugins/rendering/document/XhtmlDocumentService.groovy +++ b/grails-app/services/grails/plugins/rendering/document/XhtmlDocumentService.groovy @@ -15,107 +15,111 @@ */ package grails.plugins.rendering.document +import grails.core.GrailsApplication import grails.util.GrailsUtil - - +import grails.util.Holders +import groovy.text.Template +import groovy.transform.CompileStatic +import org.grails.gsp.GroovyPagesTemplateEngine +import org.grails.web.pages.GroovyPagesUriSupport import org.w3c.dom.Document import org.xhtmlrenderer.resource.XMLResource import org.xml.sax.InputSource -import grails.util.Holders +@CompileStatic class XhtmlDocumentService { - static transactional = false - - def groovyPagesTemplateEngine - def groovyPagesUriService - def grailsApplication - - Document createDocument(Map args) { - createDocument(generateXhtml(args)) - } - - protected createDocument(String xhtml) { - try { - createDocument(new InputSource(new StringReader(xhtml))) - } catch (XmlParseException e) { - if (log.errorEnabled) { - GrailsUtil.deepSanitize(e) - log.error("caught xml parse exception for xhtml: $xhtml", e) - } - throw new XmlParseException(xhtml, e) - } - } - - protected createDocument(InputSource xhtml) { - try { - XMLResource.load(xhtml).document - } catch (Exception e) { - if (log.errorEnabled) { - GrailsUtil.deepSanitize(e) - log.error("xml parse exception for input source: $xhtml", e) - } - throw new XmlParseException(xhtml, e) - } - } - - protected generateXhtml(Map args) { - def xhtmlWriter = new StringWriter() - - RenderEnvironment.with(grailsApplication.mainContext, xhtmlWriter) { - createTemplate(args).make(args.model).writeTo(xhtmlWriter) - } - - def xhtml = xhtmlWriter.toString() - xhtmlWriter.close() - - if (log.debugEnabled) { - log.debug("xhtml for $args -- \n ${xhtml}") - } - - xhtml - } - - protected createTemplate(Map args) { - if (!args.template) { - throw new IllegalArgumentException("The 'template' argument must be specified") - } - def templateName = args.template - - if (templateName.startsWith("/")) { - if (!args.controller) { - args.controller = "" - } - } else { - if (!args.controller) { - throw new IllegalArgumentException("template names must start with '/' if controller is not provided") - } - } - - def contextPath = getContextPath(args) - def controllerName = args.controller instanceof CharSequence ? args.controller : groovyPagesUriService.getLogicalControllerName(args.controller) - def templateUri = groovyPagesUriService.getTemplateURI(controllerName, templateName) - def uris = ["$contextPath/$templateUri", "$contextPath/grails-app/views/$templateUri"] as String[] - def template = groovyPagesTemplateEngine.createTemplateForUri(uris) - - if (!template) { - throw new UnknownTemplateException(args.template, args.plugin) - } - - template - } - - protected getContextPath(args) { - def contextPath = args.contextPath?.toString() ?: "" - def pluginName = args.plugin - - if (pluginName) { - def plugin = Holders.pluginManager.getGrailsPlugin(pluginName) - if (plugin && !plugin.isBasePlugin()) { - contextPath = plugin.pluginPath - } - } - - contextPath - } + static transactional = false + + GroovyPagesTemplateEngine groovyPagesTemplateEngine + GroovyPagesUriSupport groovyPagesUriService + GrailsApplication grailsApplication + + Document createDocument(Map args) { + createDocument(generateXhtml(args)) + } + + protected Document createDocument(String xhtml) { + try { + createDocument(new InputSource(new StringReader(xhtml))) + } catch (XmlParseException e) { + if (log.errorEnabled) { + GrailsUtil.deepSanitize(e) + log.error("caught xml parse exception for xhtml: $xhtml", e) + } + throw new XmlParseException(xhtml, e) + } + } + + protected Document createDocument(InputSource xhtml) { + try { + XMLResource.load(xhtml).document + } catch (Exception e) { + if (log.errorEnabled) { + GrailsUtil.deepSanitize(e) + log.error("xml parse exception for input source: $xhtml", e) + } + throw new XmlParseException(xhtml, e) + } + } + + protected String generateXhtml(Map args) { + StringWriter xhtmlWriter = new StringWriter() + + RenderEnvironment.with(grailsApplication.mainContext, xhtmlWriter) { + createTemplate(args).make(args.model as Map).writeTo(xhtmlWriter) + } + + def xhtml = xhtmlWriter.toString() + xhtmlWriter.close() + + if (log.debugEnabled) { + log.debug("xhtml for $args -- \n ${xhtml}") + } + + xhtml + } + + protected Template createTemplate(Map args) { + if (!args.template) { + throw new IllegalArgumentException("The 'template' argument must be specified") + } + String templateName = args.template as String + + if (templateName.startsWith("/")) { + if (!args.controller) { + args.controller = "" + } + } else { + if (!args.controller) { + throw new IllegalArgumentException("template names must start with '/' if controller is not provided") + } + } + + String contextPath = getContextPath(args) + String controllerName = args.controller instanceof CharSequence ? args.controller : groovyPagesUriService.getLogicalControllerName(args.controller as GroovyObject) + String templateUri = groovyPagesUriService.getTemplateURI(controllerName, templateName) + String[] uris = ["$contextPath/$templateUri", "$contextPath/grails-app/views/$templateUri"] as String[] + Template template = groovyPagesTemplateEngine.createTemplateForUri(uris) + + if (!template) { + throw new UnknownTemplateException(args.template as String, args.plugin as String) + } + + template + } + + protected String getContextPath(Map args) { + String contextPath = args.contextPath?.toString() ?: "" + String pluginName = args.plugin + + if (pluginName) { + def plugin = Holders.pluginManager.getGrailsPlugin(pluginName) + if (plugin && !plugin.isBasePlugin()) { + contextPath = plugin.pluginPath + } + } + + contextPath + } } diff --git a/grails-app/services/grails/plugins/rendering/pdf/PdfRenderingService.groovy b/grails-app/services/grails/plugins/rendering/pdf/PdfRenderingService.groovy index b3dd054..7dfcb72 100644 --- a/grails-app/services/grails/plugins/rendering/pdf/PdfRenderingService.groovy +++ b/grails-app/services/grails/plugins/rendering/pdf/PdfRenderingService.groovy @@ -16,39 +16,24 @@ package grails.plugins.rendering.pdf import grails.plugins.rendering.RenderingService -import grails.plugins.rendering.datauri.DataUriAwareITextUserAgent import groovy.transform.CompileStatic -import org.springframework.util.ReflectionUtils import org.w3c.dom.Document import org.xhtmlrenderer.pdf.ITextRenderer @CompileStatic class PdfRenderingService extends RenderingService { - static transactional = false + static transactional = false - PdfRenderingService() { - ReflectionUtils.makeAccessible(ITextRenderer.getDeclaredField("_outputDevice")) - } + protected doRender(Map args, Document document, OutputStream outputStream) { + def renderer = new ITextRenderer() + renderer.setDocument(document, args.base as String) + renderer.layout() + renderer.createPDF(outputStream) + } - protected doRender(Map args, Document document, OutputStream outputStream) { - def renderer = new ITextRenderer() - configureRenderer(renderer) - renderer.setDocument(document, args.base as String) - renderer.layout() - renderer.createPDF(outputStream) - } + protected String getDefaultContentType() { + "application/pdf" + } - protected String getDefaultContentType() { - "application/pdf" - } - - protected void configureRenderer(ITextRenderer renderer) { - def outputDevice = renderer.@_outputDevice - def userAgent = new DataUriAwareITextUserAgent(outputDevice) - def sharedContext = renderer.sharedContext - - sharedContext.userAgentCallback = userAgent - userAgent.sharedContext = sharedContext - } } diff --git a/grails-app/views/error.gsp b/grails-app/views/error.gsp deleted file mode 100644 index cfc512a..0000000 --- a/grails-app/views/error.gsp +++ /dev/null @@ -1,54 +0,0 @@ - - - Grails Runtime Exception - - - - -

Grails Runtime Exception

-

Error Details

- -
- Error ${request.'javax.servlet.error.status_code'}: ${request.'javax.servlet.error.message'.encodeAsHTML()}
- Servlet: ${request.'javax.servlet.error.servlet_name'}
- URI: ${request.'javax.servlet.error.request_uri'}
- - Exception Message: ${exception.message?.encodeAsHTML()}
- Caused by: ${exception.cause?.message?.encodeAsHTML()}
- Class: ${exception.className}
- At Line: [${exception.lineNumber}]
- Code Snippet:
-
- - ${cs?.encodeAsHTML()}
-
-
-
-
- -

Stack Trace

-
-
${it.encodeAsHTML()}
-
-
- - \ No newline at end of file diff --git a/src/main/groovy/grails/plugins/rendering/GrailsRenderingException.groovy b/src/main/groovy/grails/plugins/rendering/GrailsRenderingException.groovy index 0a9a80a..b4627f5 100644 --- a/src/main/groovy/grails/plugins/rendering/GrailsRenderingException.groovy +++ b/src/main/groovy/grails/plugins/rendering/GrailsRenderingException.groovy @@ -16,10 +16,10 @@ package grails.plugins.rendering -class GrailsRenderingException extends RuntimeException { - - GrailsRenderingException(CharSequence message, Throwable cause = null) { - super(message.toString(), cause) - } +import groovy.transform.CompileStatic +import groovy.transform.InheritConstructors -} \ No newline at end of file +@CompileStatic +@InheritConstructors +class GrailsRenderingException extends RuntimeException { +} diff --git a/src/main/groovy/grails/plugins/rendering/RenderingException.groovy b/src/main/groovy/grails/plugins/rendering/RenderingException.groovy index f92e352..3591cbb 100644 --- a/src/main/groovy/grails/plugins/rendering/RenderingException.groovy +++ b/src/main/groovy/grails/plugins/rendering/RenderingException.groovy @@ -16,9 +16,12 @@ package grails.plugins.rendering +import groovy.transform.CompileStatic + +@CompileStatic class RenderingException extends GrailsRenderingException { - RenderingException(cause) { + RenderingException(Exception cause) { super("Render failure", cause) } diff --git a/src/main/groovy/grails/plugins/rendering/RenderingGrailsPlugin.groovy b/src/main/groovy/grails/plugins/rendering/RenderingGrailsPlugin.groovy index 35bee79..a578544 100644 --- a/src/main/groovy/grails/plugins/rendering/RenderingGrailsPlugin.groovy +++ b/src/main/groovy/grails/plugins/rendering/RenderingGrailsPlugin.groovy @@ -15,29 +15,32 @@ */ package grails.plugins.rendering -import grails.plugins.* +import grails.plugins.Plugin +import groovy.transform.CompileStatic + +@CompileStatic class RenderingGrailsPlugin extends Plugin { - def grailsVersion = "7.0 > *" + def grailsVersion = "7.0 > *" - def pluginExcludes = [ - "grails-app/views/**", - "example/**" - ] + def pluginExcludes = [ + "grails-app/views/**", + "example/**" + ] - def observe = ["controllers"] - def loadAfter = ["controllers"] + def observe = ["controllers"] + def loadAfter = ["controllers"] - def author = "Grails Plugin Collective" - def authorEmail = "grails.plugin.collective@gmail.com" - def title = "Grails Rendering" - def description = 'Render GSPs as PDFs, JPEGs, GIFs and PNGs' - def documentation = "http://gpc.github.com/grails-rendering" + def author = "Grails Plugin Collective" + def authorEmail = "grails.plugin.collective@gmail.com" + def title = "Grails Rendering" + def description = 'Render GSPs as PDFs, JPEGs, GIFs and PNGs' + def documentation = "http://gpc.github.com/grails-rendering" - def license = 'APACHE' - def organization = [name: 'Grails Plugin Collective', url: 'https://github.com/gpc'] - def issueManagement = [system: 'JIRA', url: 'https://github.com/gpc/rendering/issues'] - def scm = [url: 'https://github.com/gpc/rendering.git'] + def license = 'APACHE' + def organization = [name: 'Grails Plugin Collective', url: 'https://github.com/gpc'] + def issueManagement = [system: 'JIRA', url: 'https://github.com/gpc/rendering/issues'] + def scm = [url: 'https://github.com/gpc/rendering.git'] } diff --git a/src/main/groovy/grails/plugins/rendering/RenderingTrait.groovy b/src/main/groovy/grails/plugins/rendering/RenderingTrait.groovy index dd550a0..6710dee 100644 --- a/src/main/groovy/grails/plugins/rendering/RenderingTrait.groovy +++ b/src/main/groovy/grails/plugins/rendering/RenderingTrait.groovy @@ -21,12 +21,14 @@ import grails.plugins.rendering.image.JpegRenderingService import grails.plugins.rendering.image.PngRenderingService import grails.plugins.rendering.pdf.PdfRenderingService import grails.web.api.ServletAttributes +import groovy.transform.CompileStatic import org.springframework.beans.factory.annotation.Autowired /** * Trait that applies to controllers adding new methods for rendering PDFs, Gif etc. */ @Enhances("Controller") +@CompileStatic trait RenderingTrait extends ServletAttributes{ @Autowired(required = false) diff --git a/src/main/groovy/grails/plugins/rendering/datauri/DataUri.groovy b/src/main/groovy/grails/plugins/rendering/datauri/DataUri.groovy index 8363449..16aa148 100644 --- a/src/main/groovy/grails/plugins/rendering/datauri/DataUri.groovy +++ b/src/main/groovy/grails/plugins/rendering/datauri/DataUri.groovy @@ -16,6 +16,9 @@ package grails.plugins.rendering.datauri +import groovy.transform.CompileStatic + +@CompileStatic class DataUri { // Default values sourced from http://en.wikipedia.org/wiki/Data_URI_scheme @@ -38,7 +41,9 @@ class DataUri { throw new IllegalArgumentException("data url does not contain a ',' delimiter: " + value) } - def (metadata, data) = value.split(",", 2) + def split = value.split(",", 2) + String metadata = split[0] + String data = split[1] if (metadata != "") { processMetadata(metadata.split(';')) } diff --git a/src/main/groovy/grails/plugins/rendering/datauri/DataUriAwareITextUserAgent.groovy b/src/main/groovy/grails/plugins/rendering/datauri/DataUriAwareITextUserAgent.groovy deleted file mode 100644 index 1abd2d4..0000000 --- a/src/main/groovy/grails/plugins/rendering/datauri/DataUriAwareITextUserAgent.groovy +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2010 Grails Plugin Collective - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package grails.plugins.rendering.datauri - -import grails.util.GrailsUtil - -import org.slf4j.LoggerFactory -import org.xhtmlrenderer.pdf.ITextFSImage -import org.xhtmlrenderer.pdf.ITextOutputDevice -import org.xhtmlrenderer.pdf.ITextUserAgent -import org.xhtmlrenderer.resource.ImageResource - -import com.lowagie.text.Image - -class DataUriAwareITextUserAgent extends ITextUserAgent { - - private static log = LoggerFactory.getLogger(DataUriAwareITextUserAgent) - - DataUriAwareITextUserAgent(ITextOutputDevice outputDevice) { - super(outputDevice) - } - - ImageResource getImageResource(String uri) { - def resource = _imageCache.get(uri) - if (resource) { - return resource - } - - if (DataUri.isDataUri(uri)) { - def dataUri = new DataUri(uri) - if (dataUri.mimeType.startsWith("image/")) { - try { - def image = Image.getInstance(dataUri.bytes) - def factor = sharedContext.dotsPerPixel - image.scaleAbsolute((image.plainWidth * factor) as float, (image.plainHeight * factor) as float) - resource = new ImageResource(uri, new ITextFSImage(image)) - _imageCache.put(uri, resource) - resource - } catch (Exception e) { - GrailsUtil.deepSanitize(e) - log.error("exception creating image from data uri (will use empty image): $dataUri", e) - new ImageResource(uri, null) - } - } else { - log.error("data uri has a non image mime type (will use empty image): $dataUri") - new ImageResource(uri, null) - } - } else { - super.getImageResource(uri) - } - } -} diff --git a/src/main/groovy/grails/plugins/rendering/datauri/DataUriAwareNaiveUserAgent.groovy b/src/main/groovy/grails/plugins/rendering/datauri/DataUriAwareNaiveUserAgent.groovy index cffd1e0..f77c0b2 100644 --- a/src/main/groovy/grails/plugins/rendering/datauri/DataUriAwareNaiveUserAgent.groovy +++ b/src/main/groovy/grails/plugins/rendering/datauri/DataUriAwareNaiveUserAgent.groovy @@ -16,26 +16,27 @@ package grails.plugins.rendering.datauri -import org.slf4j.LoggerFactory +import groovy.transform.CompileStatic +import groovy.util.logging.Slf4j import org.xhtmlrenderer.swing.NaiveUserAgent +@Slf4j +@CompileStatic class DataUriAwareNaiveUserAgent extends NaiveUserAgent { - private static log = LoggerFactory.getLogger(this) + protected InputStream resolveAndOpenStream(String uri) { + if (DataUri.isDataUri(uri)) { + new DataUri(uri).inputStream + } else { + super.resolveAndOpenStream(uri) + } + } - protected InputStream resolveAndOpenStream(String uri) { - if (DataUri.isDataUri(uri)) { - new DataUri(uri).inputStream - } else { - super.resolveAndOpenStream(uri) - } - } - - String resolveURI(String uri) { - if (DataUri.isDataUri(uri)) { - uri - } else { - super.resolveURI(uri) - } - } + String resolveURI(String uri) { + if (DataUri.isDataUri(uri)) { + uri + } else { + super.resolveURI(uri) + } + } } diff --git a/src/main/groovy/grails/plugins/rendering/document/RenderEnvironment.groovy b/src/main/groovy/grails/plugins/rendering/document/RenderEnvironment.groovy index 4679cd3..63f5efb 100644 --- a/src/main/groovy/grails/plugins/rendering/document/RenderEnvironment.groovy +++ b/src/main/groovy/grails/plugins/rendering/document/RenderEnvironment.groovy @@ -2,7 +2,6 @@ package grails.plugins.rendering.document import grails.util.Environment import grails.util.GrailsWebMockUtil - import org.grails.web.servlet.WrappedResponseHolder import org.springframework.context.ApplicationContext import org.springframework.web.context.request.RequestContextHolder @@ -12,75 +11,75 @@ import org.springframework.web.servlet.support.RequestContextUtils class RenderEnvironment { - final Writer out - final Locale locale - final ApplicationContext applicationContext + final Writer out + final Locale locale + final ApplicationContext applicationContext - private originalRequestAttributes - private renderRequestAttributes + private originalRequestAttributes + private renderRequestAttributes - private originalOut + private originalOut - RenderEnvironment(ApplicationContext applicationContext, Writer out, Locale locale = null) { - this.out = out - this.locale = locale - this.applicationContext = applicationContext - } + RenderEnvironment(ApplicationContext applicationContext, Writer out, Locale locale = null) { + this.out = out + this.locale = locale + this.applicationContext = applicationContext + } - private init() { - if(Environment.current == Environment.TEST) { - originalRequestAttributes = RequestContextHolder.getRequestAttributes() - renderRequestAttributes = GrailsWebMockUtil.bindMockWebRequest(applicationContext) + private init() { + if (Environment.current == Environment.TEST) { + originalRequestAttributes = RequestContextHolder.getRequestAttributes() + renderRequestAttributes = GrailsWebMockUtil.bindMockWebRequest(applicationContext) - if (originalRequestAttributes) { - renderRequestAttributes.controllerName = originalRequestAttributes.controllerName - } + if (originalRequestAttributes) { + renderRequestAttributes.controllerName = originalRequestAttributes.controllerName + } - def renderLocale - if (locale) { - renderLocale = locale - } else if (originalRequestAttributes) { - renderLocale = RequestContextUtils.getLocale(originalRequestAttributes.request) - } + def renderLocale + if (locale) { + renderLocale = locale + } else if (originalRequestAttributes) { + renderLocale = RequestContextUtils.getLocale(originalRequestAttributes.request) + } - renderRequestAttributes.request.setAttribute(DispatcherServlet.LOCALE_RESOLVER_ATTRIBUTE, - new FixedLocaleResolver(defaultLocale: renderLocale)) + renderRequestAttributes.request.setAttribute(DispatcherServlet.LOCALE_RESOLVER_ATTRIBUTE, + new FixedLocaleResolver(defaultLocale: renderLocale)) - renderRequestAttributes.setOut(out) - WrappedResponseHolder.wrappedResponse = renderRequestAttributes.currentResponse + renderRequestAttributes.setOut(out) + WrappedResponseHolder.wrappedResponse = renderRequestAttributes.currentResponse } } - private close() { - if(originalRequestAttributes) { + private close() { + if (originalRequestAttributes) { RequestContextHolder.setRequestAttributes(originalRequestAttributes) // null ok WrappedResponseHolder.wrappedResponse = originalRequestAttributes?.currentResponse } - } - - /** - * Establish an environment inheriting the locale of the current request if there is one - */ - static with(ApplicationContext applicationContext, Writer out, Closure block) { - with(applicationContext, out, null, block) - } - - /** - * Establish an environment with a specific locale - */ - static with(ApplicationContext applicationContext, Writer out, Locale locale, Closure block) { - def env = new RenderEnvironment(applicationContext, out, locale) - env.init() - try { - block(env) - } finally { - env.close() - } - } - - String getControllerName() { - renderRequestAttributes.controllerName - } + } + + /** + * Establish an environment inheriting the locale of the current request if there is one + */ + static with(ApplicationContext applicationContext, Writer out, Closure block) { + with(applicationContext, out, null, block) + } + + /** + * Establish an environment with a specific locale + */ + static with(ApplicationContext applicationContext, Writer out, Locale locale, Closure block) { + def env = new RenderEnvironment(applicationContext, out, locale) + env.init() + try { + block(env) + } finally { + env.close() + } + } + + String getControllerName() { + renderRequestAttributes.controllerName + } } diff --git a/src/main/groovy/grails/plugins/rendering/document/UnknownTemplateException.groovy b/src/main/groovy/grails/plugins/rendering/document/UnknownTemplateException.groovy index 6b8e374..7f9ad22 100644 --- a/src/main/groovy/grails/plugins/rendering/document/UnknownTemplateException.groovy +++ b/src/main/groovy/grails/plugins/rendering/document/UnknownTemplateException.groovy @@ -17,7 +17,9 @@ package grails.plugins.rendering.document import grails.plugins.rendering.GrailsRenderingException +import groovy.transform.CompileStatic +@CompileStatic class UnknownTemplateException extends GrailsRenderingException { UnknownTemplateException(String template, String plugin = null) { diff --git a/src/main/groovy/grails/plugins/rendering/document/XmlParseException.groovy b/src/main/groovy/grails/plugins/rendering/document/XmlParseException.groovy index 0389659..826e816 100644 --- a/src/main/groovy/grails/plugins/rendering/document/XmlParseException.groovy +++ b/src/main/groovy/grails/plugins/rendering/document/XmlParseException.groovy @@ -17,10 +17,17 @@ package grails.plugins.rendering.document import grails.plugins.rendering.GrailsRenderingException +import groovy.transform.CompileStatic +import org.xml.sax.InputSource +@CompileStatic class XmlParseException extends GrailsRenderingException { - XmlParseException(xml, cause) { + XmlParseException(String xml, Exception cause) { + super("Could not parse: $xml", cause) + } + + XmlParseException(InputSource xml, Exception cause) { super("Could not parse: $xml", cause) } From 82f9489cc08c8cac766480cf8ea70081745e76a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Berg=20Glasius?= Date: Tue, 30 Sep 2025 22:46:52 +0200 Subject: [PATCH 12/12] Gradle Github Workflow fixed to only run ./gradlew check --- .github/workflows/gradle.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 5d525a0..a01e1eb 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -29,9 +29,6 @@ jobs: develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }} - name: "🏃 Run tests" run: ./gradlew check - - name: "🏃 Run integration tests" - working-directory: ./examples/testapp1 - run: ./gradlew integrationTest publish_snapshot: if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' name: "Build Project and Publish Snapshot release"