diff --git a/plugins/jasperreports7/README.md b/plugins/jasperreports7/README.md new file mode 100644 index 0000000000..0dc43af31b --- /dev/null +++ b/plugins/jasperreports7/README.md @@ -0,0 +1,6 @@ +# Jasper Reports plugin +This plugin allows to use Jasper reports as a one of the result types. +You will find more details in [documentation](https://struts.apache.org/plugins/jasperreports/). + +## Installation +Just drop this plugin JAR into `WEB-INF/lib` folder or add it as a Maven dependency. diff --git a/plugins/jasperreports7/pom.xml b/plugins/jasperreports7/pom.xml new file mode 100644 index 0000000000..04412474ee --- /dev/null +++ b/plugins/jasperreports7/pom.xml @@ -0,0 +1,82 @@ + + + + 4.0.0 + + org.apache.struts + struts2-plugins + 7.0.1-SNAPSHOT + + + struts2-jasperreports7-plugin + jar + Struts 2 Jasper Reports 7 Plugin [EXPERIMENTAL] + + + UTF-8 + 7.0.1 + + + + + net.sf.jasperreports + jasperreports + ${jasperreports7.version} + + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.core + jackson-annotations + + + + + net.sf.jasperreports + jasperreports-pdf + ${jasperreports7.version} + true + + + com.fasterxml.jackson.core + jackson-databind + + + org.apache.struts + struts2-junit-plugin + test + + + org.springframework + spring-web + test + + + org.easymock + easymock + test + + + diff --git a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/JasperReport7Aware.java b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/JasperReport7Aware.java new file mode 100644 index 0000000000..97621e06ea --- /dev/null +++ b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/JasperReport7Aware.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.struts2.views.jasperreports7; + +import net.sf.jasperreports.engine.JasperReport; +import org.apache.struts2.ActionInvocation; +import org.apache.struts2.StrutsException; + +import java.util.Locale; + +public interface JasperReport7Aware { + + /** + * Used to perform an action before report is going to be generated + * + * @param invocation current {@link ActionInvocation} + */ + default void beforeReportGeneration(ActionInvocation invocation) throws StrutsException { + } + + /** + * Used to perform an action before report is going to be generated + * + * @param invocation current {@link ActionInvocation} + */ + default void afterReportGeneration(ActionInvocation invocation, JasperReport jasperReport) throws StrutsException { + } + + /** + * Allows to specify action specific CSV delimiter, if returns null, + * default one specified by {@link JasperReport7Constants#STRUTS_JASPER_REPORT_CSV_DELIMITER} will be used + * + * @return delimiter or null + */ + default String getCsvDelimiter(ActionInvocation invocation) { + return null; + } + + /** + * Allows to specify different local than used by the framework or an action + * + * @param invocation current {@link ActionInvocation} + * @return locale or null + */ + default Locale getReportLocale(ActionInvocation invocation) { + return invocation.getInvocationContext().getLocale(); + } + +} diff --git a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/JasperReport7Constants.java b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/JasperReport7Constants.java new file mode 100644 index 0000000000..f70f9d909d --- /dev/null +++ b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/JasperReport7Constants.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.struts2.views.jasperreports7; + +public interface JasperReport7Constants { + + /** + * PDF format constant + */ + String FORMAT_PDF = "pdf"; + + /** + * XML format constant + */ + String FORMAT_XML = "xml"; + + /** + * HTML format constant + */ + String FORMAT_HTML = "html"; + + /** + * XLSX format constant + */ + String FORMAT_XLSX = "xlsx"; + + /** + * CSV format constant + */ + String FORMAT_CSV = "csv"; + + /** + * RTF format constant + */ + String FORMAT_RTF = "rtf"; + + /** + * Allows to define a custom default delimiter when exporting report into CSV file + */ + String STRUTS_JASPER_REPORT_CSV_DELIMITER = "struts.jasperReport7.csv.defaultDelimiter"; + + /** + * Allows to define a custom url to image servlet used when exporting report into HTML + */ + String STRUTS_JASPER_REPORT_HTML_IMAGE_SERVLET_URL = "struts.jasperReport7.html.imageServletUrl"; + +} diff --git a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/JasperReport7Result.java b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/JasperReport7Result.java new file mode 100644 index 0000000000..793d5376fe --- /dev/null +++ b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/JasperReport7Result.java @@ -0,0 +1,385 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.struts2.views.jasperreports7; + +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletResponse; +import net.sf.jasperreports.engine.JRException; +import net.sf.jasperreports.engine.JRParameter; +import net.sf.jasperreports.engine.JasperFillManager; +import net.sf.jasperreports.engine.JasperPrint; +import net.sf.jasperreports.engine.JasperReport; +import net.sf.jasperreports.engine.util.JRLoader; +import net.sf.jasperreports.export.Exporter; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.struts2.ActionInvocation; +import org.apache.struts2.StrutsException; +import org.apache.struts2.inject.Inject; +import org.apache.struts2.result.StrutsResultSupport; +import org.apache.struts2.security.NotExcludedAcceptedPatternsChecker; +import org.apache.struts2.util.ValueStack; +import org.apache.struts2.views.jasperreports7.export.JasperReport7ExporterProvider; + +import java.io.File; +import java.sql.Connection; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; + +/** + * + *

+ * Generates a JasperReports report using the specified format or PDF if no + * format is specified. + *

+ * + *

+ * This result type takes the following parameters: + *

+ * + * + * + *

+ * This result follows the same rules from {@link StrutsResultSupport}. + * Specifically, all parameters will be parsed if the "parse" parameter + * is not set to false. + *

+ * + *

Example:

+ *
+ * 
+ * <result name="success" type="jasperReport7">
+ *   <param name="location">foo.jasper</param>
+ *   <param name="dataSource">mySource</param>
+ *   <param name="format">CSV</param>
+ * </result>
+ * 
+ * 
+ *

+ * or for pdf + * + *

+ * 
+ * <result name="success" type="jasperReport7">
+ *   <param name="location">foo.jasper</param>
+ *   <param name="dataSource">mySource</param>
+ * </result>
+ * 
+ * 
+ */ +public class JasperReport7Result extends StrutsResultSupport implements JasperReport7Constants { + + private static final Logger LOG = LogManager.getLogger(JasperReport7Result.class); + + private String parsedDataSource; + + protected String dataSource; + protected String format; + protected String documentName; + protected String contentDisposition; + protected String timeZone; + + protected boolean wrapField = true; + + /** + * Connection can be passed to the report instead of dataSource. + */ + protected String connection; + + /** + * Names a report parameters map stack value, allowing additional report parameters from the action. + */ + protected String reportParameters; + private String parsedReportParameters; + + /** + * Parameters validator, excludes not accepted params + */ + private NotExcludedAcceptedPatternsChecker notExcludedAcceptedPatterns; + + public JasperReport7Result() { + super(); + } + + @Inject + public void setNotExcludedAcceptedPatterns(NotExcludedAcceptedPatternsChecker notExcludedAcceptedPatterns) { + this.notExcludedAcceptedPatterns = notExcludedAcceptedPatterns; + } + + protected void doExecute(String finalLocation, ActionInvocation invocation) throws Exception { + initializeProperties(invocation); + + LOG.debug("Creating JasperReport for dataSource: {} and format: {}", dataSource, format); + // Construct the data source for the report. + ValueStack stack = invocation.getStack(); + Connection reportConnection = (Connection) stack.findValue(connection); + ValueStackDataSource reportDataSource = null; + if (reportConnection == null) { + reportDataSource = prepareDataSource(stack); + } + + if (invocation.getAction() instanceof JasperReport7Aware action) { + LOG.debug("Passing control to action: {} before generating report", invocation.getInvocationContext().getActionName()); + action.beforeReportGeneration(invocation); + } + + ServletContext servletContext = invocation.getInvocationContext().getServletContext(); + String systemId = servletContext.getRealPath(finalLocation); + Map parameters = new ValueStackShadowMap(stack); + File directory = new File(systemId.substring(0, systemId.lastIndexOf(File.separator))); + parameters.put("reportDirectory", directory); + + applyLocale(invocation, parameters); + applyTimeZone(invocation, parameters); + applyCustomParameters(stack, parameters); + + JasperPrint jasperPrint; + + // Fill the report and produce a print object + try { + JasperReport jasperReport = (JasperReport) JRLoader.loadObject(new File(systemId)); + if (reportConnection == null) { + jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, reportDataSource); + } else { + jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, reportConnection); + } + + if (invocation.getAction() instanceof JasperReport7Aware action) { + LOG.debug("Passing control to action: {} after generating report: {}", + invocation.getInvocationContext().getActionName(), jasperReport.getName()); + action.afterReportGeneration(invocation, jasperReport); + } + } catch (JRException e) { + LOG.error("Error building report for uri: {}", systemId, e); + throw new ServletException(e.getMessage(), e); + } + + try { + LOG.debug("Export the print object to the desired output format: {}", format); + JasperReport7ExporterProvider exporterProvider = invocation.getInvocationContext().getContainer().getInstance(JasperReport7ExporterProvider.class, format); + if (exporterProvider == null) { + throw new StrutsException("No exporter found for format: " + format); + } + exportReport(invocation, jasperPrint, exporterProvider); + } catch (StrutsException e) { + LOG.error("Error producing {} report for uri {}", format, systemId, e); + throw new ServletException(e.getMessage(), e); + } finally { + try { + if (reportConnection != null) { + reportConnection.close(); + } + } catch (Exception e) { + LOG.warn("Could not close db connection properly", e); + } + } + } + + protected ValueStackDataSource prepareDataSource(ValueStack stack) throws ServletException { + boolean evaluated = parsedDataSource != null && !parsedDataSource.equals(dataSource); + boolean reevaluate = !evaluated || isAcceptableExpression(parsedDataSource); + if (reevaluate) { + return new ValueStackDataSource(stack, parsedDataSource, wrapField); + } else { + throw new ServletException(String.format("Error building dataSource for excluded or not accepted [%s]", + parsedDataSource)); + } + } + + protected void applyLocale(ActionInvocation invocation, Map parameters) { + Locale locale = null; + if (invocation.getAction() instanceof JasperReport7Aware action) { + locale = action.getReportLocale(invocation); + } + if (locale == null) { + locale = invocation.getInvocationContext().getLocale(); + } + + LOG.debug("Using locale: {} to generate report", locale); + parameters.put(JRParameter.REPORT_LOCALE, locale); + } + + protected void applyTimeZone(ActionInvocation invocation, Map parameters) { + if (timeZone != null) { + timeZone = conditionalParse(timeZone, invocation); + LOG.debug("Puts timezone in jasper report parameter: {}", timeZone); + final TimeZone tz = TimeZone.getTimeZone(timeZone); + if (tz != null) { + parameters.put(JRParameter.REPORT_TIME_ZONE, tz); + } + } + } + + @SuppressWarnings("unchecked") + protected void applyCustomParameters(ValueStack stack, Map parameters) { + boolean evaluated = parsedReportParameters != null && !parsedReportParameters.equals(reportParameters); + boolean reevaluate = !evaluated || isAcceptableExpression(parsedReportParameters); + Map reportParams = reevaluate ? (Map) stack.findValue(parsedReportParameters) : null; + if (reportParams != null) { + LOG.debug("Found report parameters; adding to parameters..."); + parameters.putAll(reportParams); + } + } + + protected void exportReport(ActionInvocation invocation, JasperPrint jasperPrint, JasperReport7ExporterProvider exporterProvider) throws StrutsException { + HttpServletResponse response = prepapreHttpServletResponse(invocation); + try { + Exporter exporter = exporterProvider.createExporter(invocation, jasperPrint); + + LOG.debug("Exporting report: {} as: {} and flushing response stream", jasperPrint.getName(), format); + exporter.exportReport(); + response.getOutputStream().flush(); + } catch (Exception e) { + throw new StrutsException(e); + } + } + + private HttpServletResponse prepapreHttpServletResponse(ActionInvocation invocation) { + HttpServletResponse response = invocation.getInvocationContext().getServletResponse(); + + if (contentDisposition != null || documentName != null) { + final StringBuilder tmp = new StringBuilder(); + tmp.append((contentDisposition == null) ? "inline" : contentDisposition); + + if (documentName != null) { + tmp.append("; filename="); + tmp.append(documentName); + tmp.append("."); + tmp.append(format); + } + + response.setHeader("Content-disposition", tmp.toString()); + } + return response; + } + + /** + * Sets up result properties, parsing etc. + * + * @param invocation Current invocation. + */ + private void initializeProperties(ActionInvocation invocation) { + if (dataSource == null && connection == null) { + String message = "No dataSource specified..."; + LOG.error(message); + throw new RuntimeException(message); + } + if (dataSource != null) { + parsedDataSource = conditionalParse(dataSource, invocation); + } + + format = conditionalParse(format, invocation); + if (StringUtils.isEmpty(format)) { + format = FORMAT_PDF; + } + + if (contentDisposition != null) { + contentDisposition = conditionalParse(contentDisposition, invocation); + } + + if (documentName != null) { + documentName = conditionalParse(documentName, invocation); + } + + parsedReportParameters = conditionalParse(reportParameters, invocation); + } + + /** + * Checks if expression doesn't contain vulnerable code + * + * @param expression of result + * @return true|false + * @since 6.0.0 + */ + protected boolean isAcceptableExpression(String expression) { + NotExcludedAcceptedPatternsChecker.IsAllowed isAllowed = notExcludedAcceptedPatterns.isAllowed(expression); + if (isAllowed.isAllowed()) { + return true; + } + + LOG.warn("Expression [{}] isn't allowed by pattern [{}]! See Accepted / Excluded patterns at\n" + + "https://struts.apache.org/security/", expression, isAllowed.getAllowedPattern()); + + return false; + } + + public void setDataSource(String dataSource) { + this.dataSource = dataSource; + } + + public void setFormat(String format) { + this.format = format; + } + + public void setDocumentName(String documentName) { + this.documentName = documentName; + } + + public void setContentDisposition(String contentDisposition) { + this.contentDisposition = contentDisposition; + } + + public void setTimeZone(final String timeZone) { + this.timeZone = timeZone; + } + + public void setWrapField(boolean wrapField) { + this.wrapField = wrapField; + } + + public void setReportParameters(String reportParameters) { + this.reportParameters = reportParameters; + } + + public void setConnection(String connection) { + this.connection = connection; + } + +} diff --git a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/ValueStackDataSource.java b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/ValueStackDataSource.java new file mode 100644 index 0000000000..577e03b7b6 --- /dev/null +++ b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/ValueStackDataSource.java @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.struts2.views.jasperreports7; + +import net.sf.jasperreports.engine.JRException; +import net.sf.jasperreports.engine.JRField; +import net.sf.jasperreports.engine.JRRewindableDataSource; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.struts2.util.MakeIterator; +import org.apache.struts2.util.ValueStack; + +import java.util.Iterator; + +/** + * Ported to Struts. + */ +public class ValueStackDataSource implements JRRewindableDataSource { + + private static final Logger LOG = LogManager.getLogger(ValueStackDataSource.class); + + private final ValueStack valueStack; + private final String dataSource; + private final boolean wrapField; + + private Iterator iterator; + + private boolean firstTimeThrough = true; + + /** + * Create a value stack data source on the given iterable property + * + * @param valueStack The value stack to base the data source on + * @param dataSourceParam The property to iterate over for the report + */ + public ValueStackDataSource(ValueStack valueStack, String dataSourceParam, boolean wrapField) { + this.valueStack = valueStack; + this.dataSource = dataSourceParam; + this.wrapField = wrapField; + + Object dataSourceValue = valueStack.findValue(dataSource); + + if (dataSourceValue != null) { + if (MakeIterator.isIterable(dataSourceValue)) { + iterator = MakeIterator.convert(dataSourceValue); + } else { + Object[] array = new Object[1]; + array[0] = dataSourceValue; + iterator = MakeIterator.convert(array); + } + } else { + LOG.warn("Data source value for data source: {} was null", dataSource); + } + } + + + /** + * Get the value of a given field + * + * @param field The field to get the value for. The expression language to get the value + * of the field is either taken from the description property or from the name of the field + * if the description is null. + * @return an Object containing the field value or a new + * ValueStackDataSource object if the field value evaluates to + * an object that can be iterated over. + */ + public Object getFieldValue(JRField field) { + String expression = field.getName(); + + Object value = valueStack.findValue(expression); + LOG.debug("Field [{}] = [{}]", field.getName(), value); + + if (!wrapField && MakeIterator.isIterable(value) && field.getValueClass().isInstance(value)) { + return value; + } else if (MakeIterator.isIterable(value)) { + // wrap value with ValueStackDataSource if not already wrapped + return new ValueStackDataSource(this.valueStack, expression, wrapField); + } else { + return value; + } + } + + /** + * Move to the first item. + */ + public void moveFirst() { + Object dataSourceValue = valueStack.findValue(dataSource); + if (dataSourceValue != null) { + if (MakeIterator.isIterable(dataSourceValue)) { + iterator = MakeIterator.convert(dataSourceValue); + } else { + Object[] array = new Object[1]; + array[0] = dataSourceValue; + iterator = MakeIterator.convert(array); + } + } else { + LOG.warn("Data source value for data source [{}] was null", dataSource); + } + } + + /** + * Is there any more data + * + * @return true if there are more elements to iterate over and + * false otherwise + * @throws JRException if there is a problem determining whether there + * is more data + */ + public boolean next() throws JRException { + if (firstTimeThrough) { + firstTimeThrough = false; + } else { + valueStack.pop(); + } + + if ((iterator != null) && (iterator.hasNext())) { + valueStack.push(iterator.next()); + if (LOG.isDebugEnabled()) { + LOG.debug("Pushed next value: {}", valueStack.findValue(".")); + } + + return true; + } else { + LOG.debug("No more values"); + + return false; + } + } +} diff --git a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/ValueStackShadowMap.java b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/ValueStackShadowMap.java new file mode 100644 index 0000000000..32b8440a58 --- /dev/null +++ b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/ValueStackShadowMap.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.struts2.views.jasperreports7; + +import org.apache.struts2.util.ValueStack; + +import java.util.HashMap; + + +/** + * Ported to Struts: + */ +public class ValueStackShadowMap extends HashMap { + + /** + * valueStack reference + */ + transient ValueStack valueStack; + + /** + * Constructs an instance of ValueStackShadowMap. + * + * @param valueStack - the underlying valuestack + */ + public ValueStackShadowMap(ValueStack valueStack) { + this.valueStack = valueStack; + } + + + /** + * Implementation of containsKey(), overriding HashMap implementation. + * + * @param key - The key to check in HashMap and if not found to check on valueStack. + * @return true, if contains key, false otherwise. + * @see java.util.HashMap#containsKey + */ + public boolean containsKey(String key) { + boolean hasKey = super.containsKey(key); + + if (!hasKey && valueStack.findValue(key) != null) { + hasKey = true; + } + + return hasKey; + } + + /** + * Implementation of get(), overriding HashMap implementation. + * + * @param key - The key to get in HashMap and if not found there from the valueStack. + * @return value - The object from HashMap or if null, from the valueStack. + * @see java.util.HashMap#get + */ + public Object get(String key) { + Object value = super.get(key); + + if ((value == null)) { + value = valueStack.findValue(key); + } + + return value; + } +} diff --git a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7CsvExporterProvider.java b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7CsvExporterProvider.java new file mode 100644 index 0000000000..f1c368e968 --- /dev/null +++ b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7CsvExporterProvider.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.struts2.views.jasperreports7.export; + +import jakarta.servlet.http.HttpServletResponse; +import net.sf.jasperreports.engine.JasperPrint; +import net.sf.jasperreports.engine.export.JRCsvExporter; +import net.sf.jasperreports.export.SimpleCsvExporterConfiguration; +import net.sf.jasperreports.export.SimpleExporterInput; +import net.sf.jasperreports.export.SimpleWriterExporterOutput; +import net.sf.jasperreports.export.WriterExporterOutput; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.struts2.ActionInvocation; +import org.apache.struts2.StrutsException; +import org.apache.struts2.inject.Inject; +import org.apache.struts2.views.jasperreports7.JasperReport7Aware; +import org.apache.struts2.views.jasperreports7.JasperReport7Constants; + +import java.io.IOException; +import java.io.OutputStream; + +public class JasperReport7CsvExporterProvider implements JasperReport7ExporterProvider { + + private static final Logger LOG = LogManager.getLogger(JasperReport7CsvExporterProvider.class); + + /** + * A delimiter used when generating CSV report. By default, "," is used. + */ + private String defaultDelimiter = ","; + + public JasperReport7CsvExporterProvider() { + } + + public JasperReport7CsvExporterProvider( + @Inject(value = JasperReport7Constants.STRUTS_JASPER_REPORT_CSV_DELIMITER, required = false) + String defaultDelimiter + ) { + if (StringUtils.isNoneEmpty(defaultDelimiter)) { + LOG.debug("Using custom delimiter [{}]", defaultDelimiter); + this.defaultDelimiter = defaultDelimiter; + } + } + + @Override + public JRCsvExporter createExporter(ActionInvocation invocation, JasperPrint jasperPrint) throws StrutsException { + LOG.debug("Creating: {} exporter", this.getClass().getSimpleName()); + + HttpServletResponse response = invocation.getInvocationContext().getServletResponse(); + response.setContentType("text/csv"); + + JRCsvExporter exporter = new JRCsvExporter(); + + String reportDelimiter = null; + if (invocation.getAction() instanceof JasperReport7Aware action) { + reportDelimiter = action.getCsvDelimiter(invocation); + } + if (StringUtils.isEmpty(reportDelimiter)) { + reportDelimiter = defaultDelimiter; + } + LOG.debug("Using delimiter: [{}]", reportDelimiter); + + SimpleCsvExporterConfiguration config = new SimpleCsvExporterConfiguration(); + config.setFieldDelimiter(reportDelimiter); + config.setRecordDelimiter(reportDelimiter); + exporter.setConfiguration(config); + + SimpleExporterInput input = new SimpleExporterInput(jasperPrint); + exporter.setExporterInput(input); + + try (OutputStream responseStream = response.getOutputStream()) { + WriterExporterOutput exporterOutput = new SimpleWriterExporterOutput(responseStream); + exporter.setExporterOutput(exporterOutput); + } catch (IOException e) { + LOG.error("Error writing report output", e); + throw new StrutsException(e.getMessage(), e); + } + + return exporter; + } +} diff --git a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7ExporterProvider.java b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7ExporterProvider.java new file mode 100644 index 0000000000..24cb6bfc10 --- /dev/null +++ b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7ExporterProvider.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.struts2.views.jasperreports7.export; + +import net.sf.jasperreports.engine.JRAbstractExporter; +import net.sf.jasperreports.engine.JasperPrint; +import net.sf.jasperreports.engine.export.JRExporterContext; +import net.sf.jasperreports.export.ExporterConfiguration; +import net.sf.jasperreports.export.ExporterOutput; +import net.sf.jasperreports.export.ReportExportConfiguration; +import org.apache.struts2.ActionInvocation; +import org.apache.struts2.StrutsException; + +public interface JasperReport7ExporterProvider> { + + T createExporter(ActionInvocation invocation, JasperPrint jasperPrint) throws StrutsException; + +} diff --git a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7HtmlExporterProvider.java b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7HtmlExporterProvider.java new file mode 100644 index 0000000000..5debf2ee34 --- /dev/null +++ b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7HtmlExporterProvider.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.struts2.views.jasperreports7.export; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import net.sf.jasperreports.engine.JasperPrint; +import net.sf.jasperreports.engine.export.HtmlExporter; +import net.sf.jasperreports.engine.export.HtmlResourceHandler; +import net.sf.jasperreports.export.SimpleExporterInput; +import net.sf.jasperreports.export.SimpleHtmlExporterOutput; +import net.sf.jasperreports.web.util.WebHtmlResourceHandler; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.struts2.ActionInvocation; +import org.apache.struts2.StrutsException; +import org.apache.struts2.inject.Inject; +import org.apache.struts2.views.jasperreports7.JasperReport7Constants; + +import java.io.IOException; +import java.io.OutputStream; + +public class JasperReport7HtmlExporterProvider implements JasperReport7ExporterProvider { + + private static final Logger LOG = LogManager.getLogger(JasperReport7HtmlExporterProvider.class); + + /** + * Name of the url that, when prefixed with the context page, can return report images + */ + private String imageServletUrl = "/images/"; + + public JasperReport7HtmlExporterProvider() { + } + + public JasperReport7HtmlExporterProvider( + @Inject(value = JasperReport7Constants.STRUTS_JASPER_REPORT_HTML_IMAGE_SERVLET_URL, required = false) + String imageServletUrl + ) { + if (StringUtils.isNoneEmpty(imageServletUrl)) { + LOG.debug("Using custom image servlet url: {}", imageServletUrl); + this.imageServletUrl = imageServletUrl; + } + } + + @Override + public HtmlExporter createExporter(ActionInvocation invocation, JasperPrint jasperPrint) throws StrutsException { + LOG.debug("Creating: {} exporter with image servlet url: {}", this.getClass().getSimpleName(), imageServletUrl); + + HttpServletRequest request = invocation.getInvocationContext().getServletRequest(); + HttpServletResponse response = invocation.getInvocationContext().getServletResponse(); + + response.setContentType("text/html"); + HtmlExporter exporter = new HtmlExporter(); + + SimpleExporterInput input = new SimpleExporterInput(jasperPrint); + exporter.setExporterInput(input); + + try (OutputStream responseStream = response.getOutputStream()) { + SimpleHtmlExporterOutput exporterOutput = new SimpleHtmlExporterOutput(responseStream); + HtmlResourceHandler imageHandler = new WebHtmlResourceHandler(request.getContextPath() + imageServletUrl + "%s"); + exporterOutput.setImageHandler(imageHandler); + exporter.setExporterOutput(exporterOutput); + } catch (IOException e) { + LOG.error("Error writing report output", e); + throw new StrutsException(e.getMessage(), e); + } + + return exporter; + } +} diff --git a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7PdfExporterProvider.java b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7PdfExporterProvider.java new file mode 100644 index 0000000000..b6f44c1fac --- /dev/null +++ b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7PdfExporterProvider.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.struts2.views.jasperreports7.export; + +import jakarta.servlet.http.HttpServletResponse; +import net.sf.jasperreports.engine.JasperPrint; +import net.sf.jasperreports.export.OutputStreamExporterOutput; +import net.sf.jasperreports.export.SimpleExporterInput; +import net.sf.jasperreports.export.SimpleOutputStreamExporterOutput; +import net.sf.jasperreports.pdf.JRPdfExporter; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.struts2.ActionInvocation; +import org.apache.struts2.StrutsException; + +import java.io.IOException; +import java.io.OutputStream; + +public class JasperReport7PdfExporterProvider implements JasperReport7ExporterProvider { + + private static final Logger LOG = LogManager.getLogger(JasperReport7PdfExporterProvider.class); + + @Override + public JRPdfExporter createExporter(ActionInvocation invocation, JasperPrint jasperPrint) throws StrutsException { + LOG.debug("Creating: {} exporter", this.getClass().getSimpleName()); + + HttpServletResponse response = invocation.getInvocationContext().getServletResponse(); + response.setContentType("application/pdf"); + + JRPdfExporter exporter = new JRPdfExporter(); + + SimpleExporterInput input = new SimpleExporterInput(jasperPrint); + exporter.setExporterInput(input); + + try (OutputStream responseStream = response.getOutputStream()) { + OutputStreamExporterOutput exporterOutput = new SimpleOutputStreamExporterOutput(responseStream); + exporter.setExporterOutput(exporterOutput); + } catch (IOException e) { + LOG.error("Error writing report output", e); + throw new StrutsException(e.getMessage(), e); + } + + return exporter; + } +} diff --git a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7RtfExporterProvider.java b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7RtfExporterProvider.java new file mode 100644 index 0000000000..106bed8ec7 --- /dev/null +++ b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7RtfExporterProvider.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.struts2.views.jasperreports7.export; + +import jakarta.servlet.http.HttpServletResponse; +import net.sf.jasperreports.engine.JasperPrint; +import net.sf.jasperreports.engine.export.JRRtfExporter; +import net.sf.jasperreports.export.SimpleExporterInput; +import net.sf.jasperreports.export.SimpleWriterExporterOutput; +import net.sf.jasperreports.export.WriterExporterOutput; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.struts2.ActionInvocation; +import org.apache.struts2.StrutsException; + +import java.io.IOException; +import java.io.OutputStream; + +public class JasperReport7RtfExporterProvider implements JasperReport7ExporterProvider { + + private static final Logger LOG = LogManager.getLogger(JasperReport7RtfExporterProvider.class); + + @Override + public JRRtfExporter createExporter(ActionInvocation invocation, JasperPrint jasperPrint) throws StrutsException { + LOG.debug("Creating: {} exporter", this.getClass().getSimpleName()); + + HttpServletResponse response = invocation.getInvocationContext().getServletResponse(); + response.setContentType("application/rtf"); + + JRRtfExporter exporter = new JRRtfExporter(); + + SimpleExporterInput input = new SimpleExporterInput(jasperPrint); + exporter.setExporterInput(input); + + try (OutputStream responseStream = response.getOutputStream()) { + WriterExporterOutput exporterOutput = new SimpleWriterExporterOutput(responseStream); + exporter.setExporterOutput(exporterOutput); + } catch (IOException e) { + LOG.error("Error writing report output", e); + throw new StrutsException(e.getMessage(), e); + } + + return exporter; + } +} diff --git a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7XlsxExporterProvider.java b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7XlsxExporterProvider.java new file mode 100644 index 0000000000..c2dcbfeb96 --- /dev/null +++ b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7XlsxExporterProvider.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.struts2.views.jasperreports7.export; + +import jakarta.servlet.http.HttpServletResponse; +import net.sf.jasperreports.engine.JasperPrint; +import net.sf.jasperreports.engine.export.ooxml.JRXlsxExporter; +import net.sf.jasperreports.export.OutputStreamExporterOutput; +import net.sf.jasperreports.export.SimpleExporterInput; +import net.sf.jasperreports.export.SimpleOutputStreamExporterOutput; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.struts2.ActionInvocation; +import org.apache.struts2.StrutsException; + +import java.io.IOException; +import java.io.OutputStream; + +public class JasperReport7XlsxExporterProvider implements JasperReport7ExporterProvider { + + private static final Logger LOG = LogManager.getLogger(JasperReport7XlsxExporterProvider.class); + + @Override + public JRXlsxExporter createExporter(ActionInvocation invocation, JasperPrint jasperPrint) throws StrutsException { + LOG.debug("Creating: {} exporter", this.getClass().getSimpleName()); + + HttpServletResponse response = invocation.getInvocationContext().getServletResponse(); + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + + JRXlsxExporter exporter = new JRXlsxExporter(); + + SimpleExporterInput input = new SimpleExporterInput(jasperPrint); + exporter.setExporterInput(input); + + try (OutputStream responseStream = response.getOutputStream()) { + OutputStreamExporterOutput exporterOutput = new SimpleOutputStreamExporterOutput(responseStream); + exporter.setExporterOutput(exporterOutput); + } catch (IOException e) { + LOG.error("Error writing report output", e); + throw new StrutsException(e.getMessage(), e); + } + + return exporter; + } +} diff --git a/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7XmlExporterProvider.java b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7XmlExporterProvider.java new file mode 100644 index 0000000000..1b37710bf9 --- /dev/null +++ b/plugins/jasperreports7/src/main/java/org/apache/struts2/views/jasperreports7/export/JasperReport7XmlExporterProvider.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.struts2.views.jasperreports7.export; + +import jakarta.servlet.http.HttpServletResponse; +import net.sf.jasperreports.engine.JasperPrint; +import net.sf.jasperreports.engine.export.JRXmlExporter; +import net.sf.jasperreports.export.SimpleExporterInput; +import net.sf.jasperreports.export.SimpleXmlExporterOutput; +import net.sf.jasperreports.export.XmlExporterOutput; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.struts2.ActionInvocation; +import org.apache.struts2.StrutsException; + +import java.io.IOException; +import java.io.OutputStream; + +public class JasperReport7XmlExporterProvider implements JasperReport7ExporterProvider { + + private static final Logger LOG = LogManager.getLogger(JasperReport7XmlExporterProvider.class); + + @Override + public JRXmlExporter createExporter(ActionInvocation invocation, JasperPrint jasperPrint) throws StrutsException { + LOG.debug("Creating: {} exporter", this.getClass().getSimpleName()); + + HttpServletResponse response = invocation.getInvocationContext().getServletResponse(); + response.setContentType("text/xml"); + + JRXmlExporter exporter = new JRXmlExporter(); + + SimpleExporterInput input = new SimpleExporterInput(jasperPrint); + exporter.setExporterInput(input); + + try (OutputStream responseOutput = response.getOutputStream()) { + XmlExporterOutput exporterOutput = new SimpleXmlExporterOutput(responseOutput); + exporter.setExporterOutput(exporterOutput); + } catch (IOException e) { + LOG.error("Error writing report output using: {}", JRXmlExporter.class.getName(), e); + throw new StrutsException(e.getMessage(), e); + } + + return exporter; + } +} diff --git a/plugins/jasperreports7/src/main/resources/LICENSE.txt b/plugins/jasperreports7/src/main/resources/LICENSE.txt new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/plugins/jasperreports7/src/main/resources/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/plugins/jasperreports7/src/main/resources/NOTICE.txt b/plugins/jasperreports7/src/main/resources/NOTICE.txt new file mode 100644 index 0000000000..1b4ebccdd0 --- /dev/null +++ b/plugins/jasperreports7/src/main/resources/NOTICE.txt @@ -0,0 +1,5 @@ +Apache Struts +Copyright 2000-2024 The Apache Software Foundation + +This product includes software developed by +The Apache Software Foundation (http://www.apache.org/). \ No newline at end of file diff --git a/plugins/jasperreports7/src/main/resources/struts-plugin.xml b/plugins/jasperreports7/src/main/resources/struts-plugin.xml new file mode 100644 index 0000000000..6be7c3209c --- /dev/null +++ b/plugins/jasperreports7/src/main/resources/struts-plugin.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/jasperreports7/src/site/site.xml b/plugins/jasperreports7/src/site/site.xml new file mode 100644 index 0000000000..54fdcf4f47 --- /dev/null +++ b/plugins/jasperreports7/src/site/site.xml @@ -0,0 +1,56 @@ + + + + + org.apache.maven.skins + maven-fluido-skin + ${fluido-skin.version} + + + Apache Software Foundation + http://www.apache.org/images/asf-logo.gif + http://www.apache.org/ + + + Apache Struts + http://struts.apache.org/img/struts-logo.svg + http://struts.apache.org/ + + + + + + + + + + + + +
+ + Apache Struts, Struts, Apache, the Apache feather logo, and the Apache Struts project + logos are trademarks of The Apache Software Foundation. + ]]> +
+ + diff --git a/plugins/jasperreports7/src/test/java/org/apache/struts2/views/jasperreports7/JasperReport7ResultTest.java b/plugins/jasperreports7/src/test/java/org/apache/struts2/views/jasperreports7/JasperReport7ResultTest.java new file mode 100644 index 0000000000..ca08cc2af9 --- /dev/null +++ b/plugins/jasperreports7/src/test/java/org/apache/struts2/views/jasperreports7/JasperReport7ResultTest.java @@ -0,0 +1,369 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.struts2.views.jasperreports7; + +import jakarta.servlet.ServletException; +import net.sf.jasperreports.engine.JasperCompileManager; +import org.apache.struts2.ActionContext; +import org.apache.struts2.junit.StrutsTestCase; +import org.apache.struts2.mock.MockActionInvocation; +import org.apache.struts2.security.NotExcludedAcceptedPatternsChecker; +import org.apache.struts2.util.ClassLoaderUtil; +import org.apache.struts2.util.ValueStack; + +import java.net.URL; +import java.sql.Connection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.expectLastCall; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; + +public class JasperReport7ResultTest extends StrutsTestCase { + + private MockActionInvocation invocation; + private ValueStack stack; + private JasperReport7Result result; + + public void testConnClose() throws Exception { + // given + Connection connection = createMock(Connection.class); + final Boolean[] closed = {Boolean.FALSE}; + connection.close(); + expectLastCall().andAnswer(() -> { + closed[0] = true; + return null; + }); + replay(connection); + + stack.push(connection); + result.setConnection("top"); + assertFalse(closed[0]); + + // when + result.execute(this.invocation); + + // then + verify(connection); + assertTrue(closed[0]); + } + + public void testDataSourceNotAccepted() throws Exception { + // given + stack.push(new Object() { + public String getDatasourceName() { + return "getDatasource()"; + } + + public List> getDatasource() { + return JR_MAP_ARRAY_DATA_SOURCE; + } + }); + result.setDataSource("${datasourceName}"); + + try { + result.execute(this.invocation); + } catch (ServletException e) { + assertEquals("Error building dataSource for excluded or not accepted [getDatasource()]", + e.getMessage()); + } + + // verify that above test has really effect + result.setNotExcludedAcceptedPatterns(NO_EXCLUSION_ACCEPT_ALL_PATTERNS_CHECKER); + + // when + result.execute(this.invocation); + + // then + assertThat(response.getContentType()).isEqualTo("text/xml"); + assertThat(response.getContentAsString()).contains("Hello Foo Bar!"); + } + + public void testDataSourceAccepted() throws Exception { + // given + stack.push(new Object() { + public String getDatasourceName() { + return "datasource"; + } + + public List> getDatasource() { + return JR_MAP_ARRAY_DATA_SOURCE; + } + }); + result.setDataSource("${datasourceName}"); + + // when + result.execute(this.invocation); + + // then + assertThat(response.getContentType()).isEqualTo("text/xml"); + assertTrue(response.getContentAsString().contains("Hello Foo Bar!")); + } + + public void testDataSourceExpressionAccepted() throws Exception { + // given + result.setDataSource("{#{'firstName':'Qux', 'lastName':'Quux'}}"); + + // when + result.execute(this.invocation); + + // then + assertThat(response.getContentType()).isEqualTo("text/xml"); + assertThat(response.getContentAsString()).contains("Hello Qux Quux!"); + } + + public void testReportParametersNotAccepted() throws Exception { + // given + result.setDataSource("{#{'firstName':'ignore', 'lastName':'ignore'}}"); + + stack.push(new Object() { + public String getReportParametersName() { + return "getReportParameters()"; + } + + public Map getReportParameters() { + return new HashMap<>() {{ + put("title", "Baz"); + }}; + } + }); + + result.setReportParameters("${reportParametersName}"); + + // when + result.execute(this.invocation); + assertTrue(response.getContentAsString().contains("null Report")); + + // verify that above test has really effect + response.setCommitted(false); + response.reset(); + result.setNotExcludedAcceptedPatterns(NO_EXCLUSION_ACCEPT_ALL_PATTERNS_CHECKER); + + // when + result.execute(this.invocation); + + // then + assertThat(response.getContentType()).isEqualTo("text/xml"); + assertTrue(response.getContentAsString().contains("Baz Report")); + } + + public void testReportParametersAccepted() throws Exception { + // given + result.setDataSource("{#{'firstName':'ignore', 'lastName':'ignore'}}"); + + stack.push(new Object() { + public String getReportParametersName() { + return "reportParameters"; + } + + public Map getReportParameters() { + return new HashMap<>() {{ + put("title", "Baz"); + }}; + } + }); + + result.setReportParameters("${reportParametersName}"); + + // when + result.execute(this.invocation); + + // then + assertThat(response.getContentType()).isEqualTo("text/xml"); + assertThat(response.getContentAsString()).contains("Baz Report"); + } + + public void testExportToXml() throws Exception { + // given + result.setDataSource("{#{'firstName':'ignore', 'lastName':'ignore'}}"); + result.setReportParameters("#{'title':'Qux'}"); + result.setFormat(JasperReport7Constants.FORMAT_XML); + + // when + result.execute(this.invocation); + + // then + assertThat(response.getContentType()).isEqualTo("text/xml"); + assertThat(response.getContentAsString()).contains("Qux Report"); + } + + public void testExportToCsv() throws Exception { + // given + result.setDataSource("{#{'firstName':'ignore', 'lastName':'ignore'}}"); + result.setReportParameters("#{'title':'Qux'}"); + result.setFormat(JasperReport7Constants.FORMAT_CSV); + + // when + result.execute(this.invocation); + + // then + assertThat(response.getContentType()).isEqualTo("text/csv"); + assertThat(response.getContentAsString()).contains("Qux Report"); + } + + public void testExportToRtf() throws Exception { + // given + result.setDataSource("{#{'firstName':'ignore', 'lastName':'ignore'}}"); + result.setReportParameters("#{'title':'Qux'}"); + result.setFormat(JasperReport7Constants.FORMAT_RTF); + + // when + result.execute(this.invocation); + + // then + assertThat(response.getContentType()).isEqualTo("application/rtf"); + assertThat(response.getContentAsString()).contains("Qux Report"); + } + + public void testExportToPdf() throws Exception { + // given + result.setDataSource("{#{'firstName':'ignore', 'lastName':'ignore'}}"); + result.setReportParameters("#{'title':'Qux'}"); + result.setFormat(JasperReport7Constants.FORMAT_PDF); + + // when + result.execute(this.invocation); + + // then + assertThat(response.getContentType()).isEqualTo("application/pdf"); + assertThat(response.getContentAsByteArray()).hasSizeGreaterThan(0); + } + + public void testExportToHtml() throws Exception { + // given + result.setDataSource("{#{'firstName':'ignore', 'lastName':'ignore'}}"); + result.setReportParameters("#{'title':'Qux'}"); + result.setFormat(JasperReport7Constants.FORMAT_HTML); + + // when + result.execute(this.invocation); + + // then + assertThat(response.getContentType()).isEqualTo("text/html"); + assertThat(response.getContentAsString()).contains("Qux Report"); + } + + public void testExportToXlsx() throws Exception { + // given + result.setDataSource("{#{'firstName':'ignore', 'lastName':'ignore'}}"); + result.setReportParameters("#{'title':'Qux'}"); + result.setFormat(JasperReport7Constants.FORMAT_XLSX); + + // when + result.execute(this.invocation); + + // then + assertThat(response.getContentType()).isEqualTo("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + assertThat(response.getContentAsByteArray()).hasSizeGreaterThan(0); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + this.request.setRequestURI("http://someuri"); + ActionContext context = ActionContext.getContext() + .withServletResponse(this.response) + .withServletRequest(this.request) + .withServletContext(this.servletContext); + this.stack = context.getValueStack(); + + this.invocation = new MockActionInvocation(); + this.invocation.setInvocationContext(context); + this.invocation.setStack(this.stack); + + result = new JasperReport7Result(); + container.inject(result); + URL url = ClassLoaderUtil.getResource("org/apache/struts2/views/jasperreports7/simple.jrxml", this.getClass()); + JasperCompileManager.compileReportToFile(url.getFile(), url.getFile() + ".jasper"); + result.setLocation("org/apache/struts2/views/jasperreports7/simple.jrxml.jasper"); + result.setFormat(JasperReport7Constants.FORMAT_XML); + } + + private static final List> JR_MAP_ARRAY_DATA_SOURCE = Stream.>of( + new HashMap<>() {{ + put("firstName", "Foo"); + put("lastName", "Bar"); + }} + ).toList(); + + private static final NotExcludedAcceptedPatternsChecker NO_EXCLUSION_ACCEPT_ALL_PATTERNS_CHECKER + = new NotExcludedAcceptedPatternsChecker() { + @Override + public IsAllowed isAllowed(String value) { + return IsAllowed.yes("*"); + } + + @Override + public IsAccepted isAccepted(String value) { + return null; + } + + @Override + public void setAcceptedPatterns(String commaDelimitedPatterns) { + + } + + @Override + public void setAcceptedPatterns(String[] patterns) { + + } + + @Override + public void setAcceptedPatterns(Set patterns) { + + } + + @Override + public Set getAcceptedPatterns() { + return null; + } + + @Override + public IsExcluded isExcluded(String value) { + return null; + } + + @Override + public void setExcludedPatterns(String commaDelimitedPatterns) { + + } + + @Override + public void setExcludedPatterns(String[] patterns) { + + } + + @Override + public void setExcludedPatterns(Set patterns) { + + } + + @Override + public Set getExcludedPatterns() { + return null; + } + }; +} diff --git a/plugins/jasperreports7/src/test/resources/log4j2.xml b/plugins/jasperreports7/src/test/resources/log4j2.xml new file mode 100644 index 0000000000..00a6142e6a --- /dev/null +++ b/plugins/jasperreports7/src/test/resources/log4j2.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + diff --git a/plugins/jasperreports7/src/test/resources/org/apache/struts2/views/jasperreports7/simple.jrxml b/plugins/jasperreports7/src/test/resources/org/apache/struts2/views/jasperreports7/simple.jrxml new file mode 100644 index 0000000000..efbb643ba0 --- /dev/null +++ b/plugins/jasperreports7/src/test/resources/org/apache/struts2/views/jasperreports7/simple.jrxml @@ -0,0 +1,42 @@ + + + + + + + + + <element kind="textField" x="0" y="10" width="515" height="30" fontSize="22.0" hTextAlign="Center" + uuid="26fc2f4f-de0f-411a-b386-e67caf96f441"> + <expression><![CDATA[$P{title}]]> + " Report"</expression> + </element> + + + + + "Hello " + + " " + + "!" + + + + \ No newline at end of file diff --git a/plugins/pom.xml b/plugins/pom.xml index 730ca9686d..7f40582e57 100644 --- a/plugins/pom.xml +++ b/plugins/pom.xml @@ -38,6 +38,7 @@ config-browser convention jasperreports + jasperreports7 javatemplates jfreechart json