package org.mapfish.print.config;
import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.geotools.styling.Style;
import org.json.JSONException;
import org.json.JSONWriter;
import org.mapfish.print.attribute.Attribute;
import org.mapfish.print.attribute.InternalAttribute;
import org.mapfish.print.config.access.AccessAssertion;
import org.mapfish.print.config.access.AlwaysAllowAssertion;
import org.mapfish.print.config.access.RoleAccessAssertion;
import org.mapfish.print.map.style.StyleParser;
import org.mapfish.print.processor.Processor;
import org.mapfish.print.processor.ProcessorDependencyGraph;
import org.mapfish.print.processor.ProcessorDependencyGraphFactory;
import org.mapfish.print.processor.map.CreateMapProcessor;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.client.ClientHttpRequestFactory;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
/**
* Represents a report template configuration.
*/
public class Template implements ConfigurationObject, HasConfiguration {
private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(Template.class);
@Autowired
private ProcessorDependencyGraphFactory processorGraphFactory;
@Autowired
private ClientHttpRequestFactory httpRequestFactory;
@Autowired
private StyleParser styleParser;
private String reportTemplate;
private Map<String, Attribute> attributes = Maps.newHashMap();
private List<Processor> processors = Lists.newArrayList();
private boolean mapExport;
private String jdbcUrl;
private String jdbcUser;
private String jdbcPassword;
private volatile ProcessorDependencyGraph processorGraph;
private Map<String, String> styles = new HashMap<String, String>();
private Configuration configuration;
private AccessAssertion accessAssertion = AlwaysAllowAssertion.INSTANCE;
private PDFConfig pdfConfig = new PDFConfig();
private String tableDataKey;
private String outputFilename;
/**
* The default output file name of the report (takes precedence over
* {@link org.mapfish.print.config.Configuration#setOutputFilename(String)}). This can be overridden by the outputFilename
* parameter in the request JSON.
* <p>
* This can be a string and can also have a date section in the string that will be filled when the report is created for
* example a section with ${<dateFormatString>} will be replaced with the current date formatted in the way defined
* by the <dateFormatString> string. The format rules are the rules in
* <a href="http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html">java.text.SimpleDateFormat</a>
* (do a google search if the link above is broken).
* </p>
* <p>
* Example: <code>outputFilename: print-${dd-MM-yyyy}</code> should output: <code>print-22-11-2014.pdf</code>
* </p>
* <p>
* Note: the suffix will be appended to the end of the name.
* </p>
*
* @param outputFilename default output file name of the report.
*/
public final void setOutputFilename(final String outputFilename) {
this.outputFilename = outputFilename;
}
public final String getOutputFilename() {
return this.outputFilename;
}
/**
* Get the merged configuration between this template and the configuration's template. The settings in the template take
* priority over the configurations settings but if not set in the template then the default will be the configuration's options.
*/
public PDFConfig getPdfConfig() {
return this.pdfConfig.getMergedInstance(this.configuration.getPdfConfig());
}
/**
* Print out the template information that the client needs for performing a request.
*
* @param json the writer to write the information to.
*/
public final void printClientConfig(final JSONWriter json) throws JSONException {
json.key("attributes");
json.array();
for (Map.Entry<String, Attribute> entry : this.attributes.entrySet()) {
Attribute attribute = entry.getValue();
if (attribute.getClass().getAnnotation(InternalAttribute.class) == null) {
json.object();
json.key("name").value(entry.getKey());
attribute.printClientConfig(json, this);
json.endObject();
}
}
json.endArray();
}
/**
* Configure various properties related to the reports generated as PDFs.
* @param pdfConfig the pdf configuration
*/
public final void setPdfConfig(final PDFConfig pdfConfig) {
this.pdfConfig = pdfConfig;
}
public final Map<String, Attribute> getAttributes() {
return this.attributes;
}
/**
* Set the attributes for this template.
*
* @param attributes the attribute map
*/
public final void setAttributes(final Map<String, Attribute> attributes) {
for (Map.Entry<String, Attribute> entry : attributes.entrySet()) {
Object attribute = entry.getValue();
if (!(attribute instanceof Attribute)) {
final String msg = "Attribute: '" + entry.getKey() + "' is not an attribute. It is a: " + attribute;
LOGGER.error("Error setting the Attributes: " + msg);
throw new IllegalArgumentException(msg);
} else {
((Attribute) attribute).setConfigName(entry.getKey());
}
}
this.attributes = attributes;
}
public final String getReportTemplate() {
return this.reportTemplate;
}
public final void setReportTemplate(final String reportTemplate) {
this.reportTemplate = reportTemplate;
}
public final List<Processor> getProcessors() {
return this.processors;
}
/**
* Set the normal processors.
*
* @param processors the processors to set.
*/
public final void setProcessors(final List<Processor> processors) {
assertProcessors(processors);
this.processors = processors;
}
private void assertProcessors(final List<Processor> processorsToCheck) {
for (Processor entry : processorsToCheck) {
if (!(entry instanceof Processor)) {
final String msg = "Processor: " + entry + " is not a processor.";
LOGGER.error("Error setting the Attributes: " + msg);
throw new IllegalArgumentException(msg);
}
}
}
/**
* Set the key of the data that is the datasource for the main table in the report.
*
* @param tableData the key of the data that is the datasource for the main table in the report.
*/
public final void setTableData(final String tableData) {
this.tableDataKey = tableData;
}
public final String getTableDataKey() {
return this.tableDataKey;
}
public final String getJdbcUrl() {
return this.jdbcUrl;
}
public final void setJdbcUrl(final String jdbcUrl) {
this.jdbcUrl = jdbcUrl;
}
public final String getJdbcUser() {
return this.jdbcUser;
}
public final void setJdbcUser(final String jdbcUser) {
this.jdbcUser = jdbcUser;
}
public final String getJdbcPassword() {
return this.jdbcPassword;
}
public final void setJdbcPassword(final String jdbcPassword) {
this.jdbcPassword = jdbcPassword;
}
/**
* Get the processor graph to use for executing all the processors for the template.
*
* @return the processor graph.
*/
public final ProcessorDependencyGraph getProcessorGraph() {
if (this.processorGraph == null) {
synchronized (this) {
if (this.processorGraph == null) {
final Map<String, Class<?>> attcls = new HashMap<String, Class<?>>();
for (String attributeName: this.attributes.keySet()) {
attcls.put(attributeName, this.attributes.get(attributeName).getValueType());
}
this.processorGraph = this.processorGraphFactory.build(this.processors, attcls);
}
}
}
return this.processorGraph;
}
/**
* Set the named styles defined in the configuration for this.
*
* @param styles set the styles specific for this template.
*/
public final void setStyles(final Map<String, String> styles) {
this.styles = styles;
}
/**
* Look for a style in the named styles provided in the configuration.
*
* @param styleName the name of the style to look for.
*/
@SuppressWarnings("unchecked")
@Nonnull
public final Optional<Style> getStyle(final String styleName) {
final String styleRef = this.styles.get(styleName);
Optional<Style> style;
if (styleRef != null) {
style = (Optional<Style>) this.styleParser.loadStyle(getConfiguration(), this.httpRequestFactory, styleRef);
} else {
style = Optional.absent();
}
return style.or(this.configuration.getStyle(styleName));
}
@Override
public final void setConfiguration(final Configuration configuration) {
this.configuration = configuration;
}
public final Configuration getConfiguration() {
return this.configuration;
}
@Override
public final void validate(final List<Throwable> validationErrors, final Configuration config) {
this.accessAssertion.validate(validationErrors, config);
int numberOfTableConfigurations = this.tableDataKey == null ? 0 : 1;
numberOfTableConfigurations += this.jdbcUrl == null ? 0 : 1;
if (numberOfTableConfigurations > 1) {
validationErrors.add(new ConfigurationException("Only one of 'iterValue' or 'tableData' or 'jdbcUrl' should be defined."));
}
for (Attribute attribute : this.attributes.values()) {
attribute.validate(validationErrors, config);
}
ProcessorDependencyGraphFactory.fillProcessorAttributes(this.processors, this.attributes);
for (Processor processor : this.processors) {
processor.validate(validationErrors, config);
}
try {
getProcessorGraph();
} catch (Throwable t) {
validationErrors.add(t);
}
if (getJdbcUrl() != null) {
Connection connection = null;
try {
if (getJdbcUser() != null) {
connection = DriverManager.getConnection(getJdbcUrl(), getJdbcUser(), getJdbcPassword());
} else {
connection = DriverManager.getConnection(getJdbcUrl());
}
} catch (SQLException e) {
validationErrors.add(e);
} finally {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
validationErrors.add(e);
}
}
}
}
if (this.mapExport) {
int count = 0;
for (Processor<?, ?> processor : getProcessors()) {
if (processor instanceof CreateMapProcessor) {
count++;
}
if (count > 1) {
break;
}
}
if (count != 1) {
validationErrors.add(new ConfigurationException("When using MapExport, exactly one CreateMapProcessor should be defined."));
}
}
}
final void assertAccessible(final String name) {
this.accessAssertion.assertAccess("Template '" + name + "'", this);
}
/**
* The roles required to access this template. If empty or not set then it is a <em>public</em> template. If there are
* many roles then a user must have one of the roles in order to access the template.
* <p></p>
* The security (how authentication/authorization is done) is configured in the /WEB-INF/classes/mapfish-spring-security.xml
* <p>
* Any user without the required role will get an error when trying to access the template and the template will not
* be visible in the capabilities requests.
* </p>
*
* @param access the roles needed to access this
*/
public final void setAccess(final List<String> access) {
final RoleAccessAssertion assertion = new RoleAccessAssertion();
assertion.setRequiredRoles(access);
this.accessAssertion = assertion;
}
public final AccessAssertion getAccessAssertion() {
return this.accessAssertion;
}
public final boolean isMapExport() {
return this.mapExport;
}
public final void setMapExport(final boolean mapExport) {
this.mapExport = mapExport;
}
}