package gov.samhsa.consent2share.infrastructure.report;
import static java.util.stream.Collectors.toMap;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
import javax.annotation.PostConstruct;
import javax.servlet.ServletContext;
import net.sf.jasperreports.engine.JRDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
/**
* This is the base abstract class for a concrete report configuration
* implementation. The abstract methods in this class require the minimum
* implementation to apply auto-configuration for reports. This
* auto-configuration is useful if a certain convention is followed as explained
* in the project README.md documentation. If the convention is not fully
* followed, the relevant methods in this class can be overridden for
* customization.
*/
public abstract class AbstractReportConfig {
/**
* The default webpath location for image resources that can be resolved for
* a report. This path is meaningful if the report is rendered as HTML.
*/
public static final String DEFAULT_BASE_WEBPATH_IMG_RESOURCES = "/resources/report/img/";
/**
* The default classpath location for image resources that can be resolved
* for a report. This path is meaningful if the report is rendered as a
* binary file such as PDF or XLS.
*/
public static final String DEFAULT_BASE_CLASSPATH_IMG_RESOURCES = "../.."
+ DEFAULT_BASE_WEBPATH_IMG_RESOURCES;
/** The logger. */
private final Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* This is the chain of report parameter configurer tasks that will be run
* by report controllers to configure the report model/parameters. This
* model will eventually used by report views to fill the report and display
* it.
*/
private final List<Supplier<ReportParameterConfigurerTask>> reportParameterConfigurerChain;
/**
* The servlet context. The servlet context is only needed to get the
* context path for image webpath resolutions.
*/
private final ServletContext servletContext;
/**
* The report props contain the basic report properties including the report
* name, template URL and datasource key in the report model/parameters.
*/
private ReportProps reportProps;
/**
* The report image resolver. This is an optional field and only required if
* there are any images in the report that need to be resolved.
*/
private Optional<ReportImageResolver> reportImageResolver;
/**
* Instantiates a new abstract report config.
*
* @param servletContext
* the servlet context
* @param reportParameterConfigurerChain
* the report parameter configurer chain
*/
public AbstractReportConfig(
ServletContext servletContext,
List<Supplier<ReportParameterConfigurerTask>> reportParameterConfigurerChain) {
super();
Assert.notEmpty(reportParameterConfigurerChain,
"reportParameterConfigurerChain cannot be empty");
this.servletContext = servletContext;
this.reportImageResolver = Optional.empty();
this.reportParameterConfigurerChain = reportParameterConfigurerChain;
}
/**
* Initialization callback after properties are set.
*/
@PostConstruct
public final void afterPropertiesSet() {
final Optional<Map<String, String>> imageMapping = Optional
.ofNullable(imageMapping());
this.reportProps = new ReportProps(getReportName(), getTemplateUrl(),
getDatasourceKey(), imageMapping,
getBaseClasspathForSqlScriptResources(), getSqlScriptFileName());
imageMapping.ifPresent(mapping -> this.reportImageResolver = Optional
.of(new ReportImageResolverImpl(getContextPath(),
getBaseWebpathForImgResources(),
getBaseClasspathForImgResources(), mapping)));
}
/**
* Runs all report parameter configurer tasks in the chain, builds and
* returns the report model/parameters as the result.
*
* @param reportFormat
* the report format
* @param datasource
* the datasource
* @return the map (report model/parameters)
*/
public Map<String, Object> configure(ReportFormat reportFormat,
JRDataSource datasource) {
final Map<String, Object> parameters = this.reportParameterConfigurerChain
.stream()
.map(Supplier::get)
.peek(task -> logger
.debug("Running report parameter configurer task:"
+ task.getClass().getName()))
.map(task -> task.configure(getReportProps(), reportFormat,
datasource))
.flatMap(map -> map.entrySet().stream())
.collect(
toMap(entry -> entry.getKey(),
entry -> entry.getValue()));
return parameters;
}
/**
* Gets the base classpath for img resources. Can be overridden for
* customization.
*
* @return the base classpath for img resources
*/
public String getBaseClasspathForImgResources() {
return DEFAULT_BASE_CLASSPATH_IMG_RESOURCES;
}
/**
* Gets the base classpath for sql script resources. Can be overridden for
* customization.
*
* @return the base classpath for sql script resources
*/
public Optional<String> getBaseClasspathForSqlScriptResources() {
return Optional.empty();
}
/**
* Gets the base webpath for img resources. Can be overridden for
* customization.
*
* @return the base webpath for img resources
*/
public String getBaseWebpathForImgResources() {
return DEFAULT_BASE_WEBPATH_IMG_RESOURCES;
}
/**
* Gets the context path.
*
* @return the context path
*/
public String getContextPath() {
Assert.notNull(this.servletContext, "servletContext cannot be null");
return this.servletContext.getContextPath();
}
/**
* Gets the datasource key. Can be overridden for customization.
*
* @return the datasource key
*/
public Optional<String> getDatasourceKey() {
return Optional.empty();
}
/**
* Gets the report config name.
*
* @return the report config name
*/
public abstract String getReportConfigName();
/**
* Gets the report data provider name.
*
* @return the report data provider name
*/
public abstract String getReportDataProviderName();
/**
* Gets the report image resolver.
*
* @return the report image resolver
*/
public Optional<ReportImageResolver> getReportImageResolver() {
return reportImageResolver;
}
/**
* Gets the report name. This report name must be globally unique across all
* reports.
*
* @return the report name
*/
public abstract String getReportName();
/**
* Gets the report props.
*
* @return the report props
*/
public ReportProps getReportProps() {
return reportProps;
}
/**
* Gets the sql script file name. Can be overridden for customization.
*
* @return the sql script file name
*/
public Optional<String> getSqlScriptFileName() {
return Optional.empty();
}
/**
* Gets the template url. Can be overridden for customization.
*
* @return the template url
*/
public Optional<String> getTemplateUrl() {
return Optional.empty();
}
/**
* Image mapping for this report. Needs to be overridden in the concrete
* class if there are any images in the report.
*
* @return the image mapping for this report
*/
public Map<String, String> imageMapping() {
return null;
}
/**
* This method can be used to consolidate several image
* {@link #mapping(String, String)}s as a single map. These mappings should
* be from the image's parameter name in the JRXML template to the file name
* that can be resolved by the {@link #reportImageResolver}.
*
* @param imageMappings
* the image mappings
* @return the map (consolidated image mappings)
*/
@SafeVarargs
protected static final Map<String, String> imageMappings(
Map<String, String>... imageMappings) {
return Arrays
.stream(imageMappings)
.flatMap(mappings -> mappings.entrySet().stream())
.collect(
toMap(entry -> entry.getKey(),
entry -> entry.getValue()));
}
/**
* Creates a single mapping from image's parameter name in the JRXML
* template to the file name that can be resolved by the report image.
*
* @param paramInReportTemplate
* the parameter name in report template (JRXML file)
* @param actualFileName
* the actual file name that can be resolved by the
* {@link #reportImageResolver}
* @return the map (a single image mapping)
*/
protected static final Map<String, String> mapping(
String paramInReportTemplate, String actualFileName) {
Assert.hasText(paramInReportTemplate);
Assert.hasText(actualFileName);
final Map<String, String> map = new HashMap<>();
map.put(paramInReportTemplate, actualFileName);
return map;
}
}