/*
* gvNIX is an open source tool for rapid application development (RAD).
* Copyright (C) 2010 Generalitat Valenciana
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.gvnix.web.report.roo.addon.addon;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.gvnix.web.report.roo.addon.addon.util.ReportValidTypes;
import org.gvnix.web.report.roo.addon.annotations.GvNIXReports;
import org.springframework.roo.addon.propfiles.PropFileOperations;
import org.springframework.roo.addon.web.mvc.controller.addon.details.WebMetadataService;
import org.springframework.roo.addon.web.mvc.controller.addon.scaffold.WebScaffoldAnnotationValues;
import org.springframework.roo.addon.web.mvc.controller.addon.scaffold.WebScaffoldMetadata;
import org.springframework.roo.classpath.PhysicalTypeIdentifier;
import org.springframework.roo.classpath.PhysicalTypeIdentifierNamingUtils;
import org.springframework.roo.classpath.PhysicalTypeMetadata;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.classpath.details.FieldMetadata;
import org.springframework.roo.classpath.details.ItdTypeDetailsBuilder;
import org.springframework.roo.classpath.details.MethodMetadata;
import org.springframework.roo.classpath.details.MethodMetadataBuilder;
import org.springframework.roo.classpath.details.annotations.AnnotatedJavaType;
import org.springframework.roo.classpath.details.annotations.AnnotationAttributeValue;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder;
import org.springframework.roo.classpath.details.annotations.BooleanAttributeValue;
import org.springframework.roo.classpath.details.annotations.EnumAttributeValue;
import org.springframework.roo.classpath.details.annotations.StringAttributeValue;
import org.springframework.roo.classpath.itd.AbstractItdTypeDetailsProvidingMetadataItem;
import org.springframework.roo.classpath.itd.InvocableMemberBodyBuilder;
import org.springframework.roo.classpath.scanner.MemberDetails;
import org.springframework.roo.classpath.scanner.MemberDetailsScanner;
import org.springframework.roo.metadata.MetadataDependencyRegistry;
import org.springframework.roo.metadata.MetadataIdentificationUtils;
import org.springframework.roo.metadata.MetadataService;
import org.springframework.roo.model.EnumDetails;
import org.springframework.roo.model.ImportRegistrationResolver;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.process.manager.FileManager;
import org.springframework.roo.process.manager.MutableFile;
import org.springframework.roo.project.LogicalPath;
import org.springframework.roo.project.Path;
import org.springframework.roo.project.PathResolver;
import org.springframework.roo.project.ProjectOperations;
import org.springframework.roo.support.util.FileUtils;
import org.springframework.roo.support.util.XmlElementBuilder;
import org.springframework.roo.support.util.XmlUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* This type produces metadata for a new ITD. It uses an
* {@link ItdTypeDetailsBuilder} provided by
* {@link AbstractItdTypeDetailsProvidingMetadataItem} to register a field in
* the ITD and a new method.
*
* @author <a href="http://www.disid.com">DISID Corporation S.L.</a> made for <a
* href="http://www.dgti.gva.es">General Directorate for Information
* Technologies (DGTI)</a>
* @since 0.6
*/
public class ReportMetadata extends AbstractItdTypeDetailsProvidingMetadataItem {
private static final JavaSymbolName METHOD_SYMBOL_NAME = new JavaSymbolName(
"method");
private static final JavaSymbolName PARAMS_SYMBOL_NAME = new JavaSymbolName(
"params");
private static final JavaSymbolName VALUE_SYMBOL_NAME = new JavaSymbolName(
"value");
private static final JavaType REQUEST_PARAM_TYPE = new JavaType(
"org.springframework.web.bind.annotation.RequestParam");
private static final JavaType MODEL_TYPE = new JavaType(
"org.springframework.ui.Model");
private static final JavaType REQUEST_METHOD_TYPE = new JavaType(
"org.springframework.web.bind.annotation.RequestMethod");
// private static final Logger logger =
// HandlerUtils.getLogger(ReportMetadata.class);
private static final JavaType REQUEST_MAPPING_TYPE = new JavaType(
"org.springframework.web.bind.annotation.RequestMapping");
private static final String PROVIDES_TYPE_STRING = ReportMetadata.class
.getName();
private static final String PROVIDES_TYPE = MetadataIdentificationUtils
.create(PROVIDES_TYPE_STRING);
private List<MethodMetadata> reportMethods;
private List<MethodMetadata> controllerMethods;
private List<String> installedReports;
private final WebScaffoldMetadata webScaffoldMetadata;
private final FileManager fileManager;
private final ProjectOperations projectOperations;
private final WebScaffoldAnnotationValues annotationValues;
private final PropFileOperations propFileOperations;
private final MetadataService metadataService;
private final MemberDetailsScanner memberDetailsScanner;
private final WebMetadataService webMetadataService;
public ReportMetadata(String identifier, JavaType aspectName,
PhysicalTypeMetadata governorPhysicalTypeMetadata,
List<MethodMetadata> controllerMethods,
MetadataService metadataService,
MemberDetailsScanner memberDetailsScanner,
MetadataDependencyRegistry metadataDependencyRegistry,
WebScaffoldMetadata webScaffoldMetadata,
WebMetadataService webMetadataService, FileManager fileManager,
ProjectOperations projectOperations,
PropFileOperations propFileOperations,
List<StringAttributeValue> definedReports) {
super(identifier, aspectName, governorPhysicalTypeMetadata);
Validate.notNull(controllerMethods,
"List of controller methods required");
this.fileManager = fileManager;
this.projectOperations = projectOperations;
this.propFileOperations = propFileOperations;
this.metadataService = metadataService;
this.webMetadataService = webMetadataService;
this.memberDetailsScanner = memberDetailsScanner;
Validate.isTrue(isValid(identifier), "Metadata identification string '"
.concat(identifier).concat("' does not appear to be a valid"));
this.annotationValues = new WebScaffoldAnnotationValues(
governorPhysicalTypeMetadata);
JavaType fromBackingObject = annotationValues.getFormBackingObject();
this.webScaffoldMetadata = webScaffoldMetadata;
this.controllerMethods = controllerMethods;
List<MethodMetadata> reportMethods = new ArrayList<MethodMetadata>();
List<String> installedReports = new ArrayList<String>();
for (StringAttributeValue definedReport : definedReports) {
String[] reportNameFormat = stripGvNixReportValue(definedReport
.getValue());
// Add a sample JasperReport
installJasperReportTemplate(reportNameFormat[0], fromBackingObject);
// Add config to jasper-views.xml
addNewJasperReportBean(fromBackingObject, reportNameFormat[0],
reportNameFormat[1]);
// Add methodForm and method in the webScaffold
MethodMetadata reportFormMethod = addGenerateReportFormMethod(
fromBackingObject, reportNameFormat[0], reportNameFormat[1]);
reportMethods.add(reportFormMethod);
MethodMetadata reportMethod = addGenerateReportMethod(
fromBackingObject, reportNameFormat[0], reportNameFormat[1]);
reportMethods.add(reportMethod);
installedReports.add(definedReport.getValue());
}
// Create a representation of the desired output ITD
itdTypeDetails = builder.build();
this.reportMethods = Collections.unmodifiableList(reportMethods);
// TODO: Maybe this will disappear in the future
this.installedReports = Collections.unmodifiableList(installedReports);
}
/**
* Add to the aspect the MethodMetadata of the generateReportForm method. It
* uses AbstractMetadataItem.builder for add the new method.
*
* @param reportName
* @param entity
* @param reportMethods2
* @return
*/
private MethodMetadata addGenerateReportFormMethod(JavaType entity,
String reportName, String reportFormats) {
// Specify the desired method name
JavaSymbolName methodName = new JavaSymbolName(generateMethodName(
reportName, true));
// Define method parameter types
List<AnnotatedJavaType> parameterTypes = new ArrayList<AnnotatedJavaType>();
parameterTypes.add(new AnnotatedJavaType(MODEL_TYPE,
new ArrayList<AnnotationMetadata>()));
// Check if a method with the same signature already exists in the
// target type
MethodMetadata reportMethod = reportMethodExists(methodName);
if (reportMethod != null) {
// If it already exists, just return the method and omit its
// generation via the ITD
return reportMethod;
}
// Define method parameter names
List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>();
parameterNames.add(new JavaSymbolName("uiModel"));
// Define method annotations
List<AnnotationAttributeValue<?>> requestMappingAttributes = new ArrayList<AnnotationAttributeValue<?>>();
requestMappingAttributes.add(new StringAttributeValue(
VALUE_SYMBOL_NAME, "/reports/".concat(reportName)));
requestMappingAttributes.add(new StringAttributeValue(
PARAMS_SYMBOL_NAME, "form"));
requestMappingAttributes
.add(new EnumAttributeValue(METHOD_SYMBOL_NAME,
new EnumDetails(REQUEST_METHOD_TYPE,
new JavaSymbolName("GET"))));
AnnotationMetadataBuilder requestMapping = new AnnotationMetadataBuilder(
REQUEST_MAPPING_TYPE, requestMappingAttributes);
List<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>();
annotations.add(requestMapping);
// Create the method body
InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();
// Populate report_formats list for select
String reportFormatsAsArray = getReportFormatsAsArray(reportFormats);
bodyBuilder.appendFormalLine("String[] reportFormats = ".concat(
reportFormatsAsArray).concat(";"));
bodyBuilder
.appendFormalLine("Collection<String> reportFormatsList = Arrays.asList(reportFormats);");
bodyBuilder
.appendFormalLine("uiModel.addAttribute(\"report_formats\", reportFormatsList);");
// return the View
bodyBuilder.appendFormalLine("return \""
.concat(annotationValues.getPath()).concat("/")
.concat(reportName).concat("\";"));
// ImportRegistrationResolver gives access to imports in the
// Java/AspectJ source
ImportRegistrationResolver irr = builder
.getImportRegistrationResolver();
irr.addImport(new JavaType("java.util.Arrays"));
irr.addImport(new JavaType("java.util.Collection"));
// Use the MethodMetadataBuilder for easy creation of MethodMetadata
MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder(
getId(), Modifier.PUBLIC, methodName, JavaType.STRING,
parameterTypes, parameterNames, bodyBuilder);
methodBuilder.setAnnotations(annotations);
reportMethod = methodBuilder.build();
builder.addMethod(reportMethod);
controllerMethods.add(reportMethod);
return reportMethod;
}
/**
* Returns a new MethodMetadata based on given parameters. It uses
* AbstractMetadataItem.builder for add the new method and add needed
* imports
*
* @param reportName
* @param entity
* @param reportNameFormat
* @return
*/
private MethodMetadata addGenerateReportMethod(JavaType entity,
String reportName, String reportFormats) {
// Specify the desired method name
Map<String, String> properties = new HashMap<String, String>();
JavaSymbolName methodName = new JavaSymbolName(generateMethodName(
reportName, false));
List<AnnotationAttributeValue<?>> reportAttributes = new ArrayList<AnnotationAttributeValue<?>>();
reportAttributes.add(new StringAttributeValue(VALUE_SYMBOL_NAME,
"format"));
reportAttributes.add(new BooleanAttributeValue(new JavaSymbolName(
"required"), true));
List<AnnotationMetadata> reportAttributesAnnotations = new ArrayList<AnnotationMetadata>();
AnnotationMetadataBuilder reportAttributesAnnotation = new AnnotationMetadataBuilder(
REQUEST_PARAM_TYPE, reportAttributes);
reportAttributesAnnotations.add(reportAttributesAnnotation.build());
// Define method parameter types
List<AnnotatedJavaType> parameterTypes = new ArrayList<AnnotatedJavaType>();
parameterTypes.add(new AnnotatedJavaType(new JavaType(String.class
.getName()), reportAttributesAnnotations));
parameterTypes.add(new AnnotatedJavaType(MODEL_TYPE,
new ArrayList<AnnotationMetadata>()));
// Check if a method with the same signature already exists in the
// target type
MethodMetadata reportMethod = reportMethodExists(methodName);
if (reportMethod != null) {
// If it already exists, just return the method and omit its
// generation via the ITD
return reportMethod;
}
// Define method parameter names
List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>();
parameterNames.add(new JavaSymbolName("format"));
parameterNames.add(new JavaSymbolName("uiModel"));
// Define method annotations
List<AnnotationAttributeValue<?>> requestMappingAttributes = new ArrayList<AnnotationAttributeValue<?>>();
requestMappingAttributes.add(new StringAttributeValue(
VALUE_SYMBOL_NAME, "/reports/".concat(reportName)));
requestMappingAttributes
.add(new EnumAttributeValue(METHOD_SYMBOL_NAME,
new EnumDetails(REQUEST_METHOD_TYPE,
new JavaSymbolName("GET"))));
AnnotationMetadataBuilder requestMapping = new AnnotationMetadataBuilder(
REQUEST_MAPPING_TYPE, requestMappingAttributes);
List<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>();
annotations.add(requestMapping);
// Create the method body
InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();
// test if format is not informed and return to the report form if so
bodyBuilder
.appendFormalLine("if ( null == format || format.length() <= 0 ) {");
bodyBuilder
.appendIndent()
.appendFormalLine(
"uiModel.addAttribute(\"error\", \"message_format_required\");");
bodyBuilder.appendIndent().appendFormalLine(
"return \"".concat(annotationValues.getPath()).concat("/")
.concat(reportName).concat("\";"));
bodyBuilder.appendFormalLine("}");
// Add the message error to the application.properties
properties.put("message_format_required",
"Format required: (pdf, xls, csv, html)");
/*
* This is the trick to make changes in the ITD when a new format is
* added to the report. This changes triggers ReportJspMetadataListener
* performing changes in JSPXs
*/
// test if requested format is supported for this report
bodyBuilder.appendFormalLine("final String REGEX = \"(".concat(
reportFormats.replace(",", "|")).concat(")\";"));
bodyBuilder
.appendFormalLine("Pattern pattern = Pattern.compile(REGEX, Pattern.CASE_INSENSITIVE);");
bodyBuilder
.appendFormalLine("Matcher matcher = pattern.matcher(format);");
bodyBuilder.appendFormalLine("if ( !matcher.matches() ) {");
bodyBuilder.appendIndent().appendFormalLine(
"uiModel.addAttribute(\"error\", \"message_format_invalid\");");
bodyBuilder.appendIndent().appendFormalLine(
"return \"".concat(annotationValues.getPath()).concat("/")
.concat(reportName).concat("\";"));
bodyBuilder.appendFormalLine("}");
// Add the message error to the application.properties
properties.put("message_format_invalid",
"The requested format is not supported by this report");
// ImportRegistrationResolver gives access to imports in the
// Java/AspectJ source
ImportRegistrationResolver irr = builder
.getImportRegistrationResolver();
// We need import java.util.regex.Matcher and java.util.regex.Pattern
irr.addImport(new JavaType("java.util.regex.Matcher"));
irr.addImport(new JavaType("java.util.regex.Pattern"));
// Code populating the JasperReport DataSource
bodyBuilder.appendFormalLine("Collection<"
.concat(entity.getSimpleTypeName()).concat("> dataSource = ")
.concat(entity.getSimpleTypeName()).concat(".find")
.concat(entity.getSimpleTypeName()).concat("Entries(0, 10);"));
// test if DataSource is empty and return to the report form if so
bodyBuilder.appendFormalLine("if (dataSource.isEmpty()) {");
bodyBuilder
.appendIndent()
.appendFormalLine(
"uiModel.addAttribute(\"error\", \"message_emptyresults_noreportgeneration\");");
bodyBuilder.appendIndent().appendFormalLine(
"return \"".concat(annotationValues.getPath()).concat("/")
.concat(reportName).concat("\";"));
bodyBuilder.appendFormalLine("}");
// Add the message error to the application.properties
properties.put("message_emptyresults_noreportgeneration",
"No results found to generate report");
// We need import java.util.Collection and the Entity
irr.addImport(new JavaType("java.util.Collection"));
irr.addImport(entity);
// set required attributes
bodyBuilder
.appendFormalLine("uiModel.addAttribute(\"format\", format);");
bodyBuilder.appendFormalLine("uiModel.addAttribute(\"title\", \""
.concat(reportName.toUpperCase()).concat("\");"));
bodyBuilder.appendFormalLine("uiModel.addAttribute(\"".concat(
reportName).concat("List\", dataSource);"));
// return the JasperReport View
bodyBuilder.appendFormalLine("return \""
.concat(entity.getSimpleTypeName().toLowerCase()).concat("_")
.concat(reportName.toLowerCase()).concat("\";"));
// Use the MethodMetadataBuilder for easy creation of MethodMetadata
MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder(
getId(), Modifier.PUBLIC, methodName, JavaType.STRING,
parameterTypes, parameterNames, bodyBuilder);
methodBuilder.setAnnotations(annotations);
reportMethod = methodBuilder.build();
builder.addMethod(reportMethod);
controllerMethods.add(reportMethod);
propFileOperations
.addProperties(
LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""),
"/WEB-INF/i18n/application.properties", properties,
true, false);
return reportMethod;
}
/**
* Checks if Method exists in the ReportMetadata.
*
* @param methodName
* @param paramTypes
* @return
*/
private MethodMetadata reportMethodExists(JavaSymbolName methodName) {
for (MethodMetadata methodMetadata : controllerMethods) {
if (methodMetadata.getMethodName().equals(methodName)) {
return methodMetadata; // The method already exists. Just return
// it
}
}
return null;
}
/**
* Add a new <bean/> to jasper-views.xml file with the name of the new
* report
*
* @param reportName the name of the report
* @return jasperReportBeanId
*/
private void addNewJasperReportBean(JavaType entity, String reportName,
String format) {
PathResolver pathResolver = projectOperations.getPathResolver();
// Install CustomJasperReportsMultiFormatView into
// top.level.package.<web controllers
// sub-package>.servlet.view.jasperreports
// if it is not already installed
String classMultiFormatView = installCustomJasperReportMultiFormatView();
// The bean id will be entity_reportName
String reportBeanId = entity.getSimpleTypeName().toLowerCase()
.concat("_").concat(reportName.toLowerCase());
// Add config to jasper-views.xml
String jasperReportsConfig = pathResolver.getIdentifier(
LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""),
"WEB-INF/spring/jasper-views.xml");
MutableFile mutableJasperViewsConfigFile = fileManager
.updateFile(jasperReportsConfig);
Document jasperViewsConfigDocument;
try {
jasperViewsConfigDocument = XmlUtils.getDocumentBuilder().parse(
mutableJasperViewsConfigFile.getInputStream());
}
catch (Exception ex) {
throw new IllegalStateException(
"Could not open jasper-views.xml config file '".concat(
jasperReportsConfig).concat("'"), ex);
}
Element beans = jasperViewsConfigDocument.getDocumentElement();
if (null != XmlUtils.findFirstElement(
"/beans/bean[@id='".concat(reportBeanId).concat("']"), beans)) {
// logger.warning("A report with the name " + reportBeanId +
// " is already defined");
return; // There is a bean with the reportName already
// defined, nothing to do
}
// Create a DOM element defining the new bean for the JasperReport view
InputStream templateInputStream = null;
OutputStream jasperViewsConfigOutStream = null;
try {
templateInputStream = FileUtils.getInputStream(getClass(),
"jasperreports-bean-config-template.xml");
Document reportBeanConfigDocument;
try {
reportBeanConfigDocument = XmlUtils.getDocumentBuilder().parse(
templateInputStream);
}
catch (Exception ex) {
throw new IllegalStateException(
"Could not open jasperreports-bean-config-template.xml file",
ex);
}
Element configElement = (Element) reportBeanConfigDocument
.getDocumentElement();
Element bean = XmlUtils.findFirstElement("/config/bean",
configElement);
// Set the right attributes dynamically
bean.setAttribute("id", reportBeanId);
bean.setAttribute("class", classMultiFormatView);
bean.setAttribute("p:url", "/WEB-INF/reports/".concat(reportBeanId)
.concat(".jrxml"));
bean.setAttribute("p:reportDataKey", reportName.concat("List"));
// Add the new bean handling the new report view
Element rootElement = (Element) jasperViewsConfigDocument
.getFirstChild();
Node importedBean = jasperViewsConfigDocument
.importNode(bean, true);
rootElement.appendChild(importedBean);
jasperViewsConfigOutStream = mutableJasperViewsConfigFile
.getOutputStream();
XmlUtils.writeXml(jasperViewsConfigOutStream,
jasperViewsConfigDocument);
}
finally {
IOUtils.closeQuietly(templateInputStream);
IOUtils.closeQuietly(jasperViewsConfigOutStream);
}
fileManager.scan();
}
/**
* Creates a sample jrxml report file based in the template
* report/JasperReport-template.jrxml with:
* <ul>
* <li>3 field elements or less if the entity in fromBackingObject doesn't
* has more than 3 member fields</li>
* <li>As many staticText/textField element pairs as fields have been
* created</li>
* </ul>
* The jrxml is copied to the WEB-INF/reports foder with the name
* entity_report_name.jrxml
*
* @param installedReport
* @param fromBackingObject
*/
private void installJasperReportTemplate(String installedReport,
JavaType fromBackingObject) {
// Check if a jrxml file exists
PathResolver pathResolver = projectOperations.getPathResolver();
String reportJrXml = pathResolver
.getIdentifier(
LogicalPath.getInstance(Path.SRC_MAIN_WEBAPP, ""),
"WEB-INF/reports/"
.concat(fromBackingObject.getSimpleTypeName()
.toLowerCase()).concat("_")
.concat(installedReport.toLowerCase())
.concat(".jrxml"));
if (fileManager.exists(reportJrXml)) {
// We can't modify the existing jrxml file just in case the user has
// modified it
return;
}
// Get member details of fromBackingObject type
MemberDetails memberDetails = getMemberDetails(fromBackingObject);
List<FieldMetadata> elegibleFields = webMetadataService
.getScaffoldEligibleFieldMetadata(fromBackingObject,
memberDetails, governorPhysicalTypeMetadata.getId());
/*
* We only use 3 fields in the sample report. By now we only use field
* types accepted by JasperReports XMLSchema
*/
List<FieldMetadata> usableFields = new ArrayList<FieldMetadata>();
Iterator<FieldMetadata> it = elegibleFields.iterator();
FieldMetadata field = null;
while (it.hasNext() && usableFields.size() < 3) {
field = it.next();
if (ReportValidTypes.VALID_TYPES.contains(field.getFieldType()
.getFullyQualifiedTypeName())) {
usableFields.add(field);
}
}
InputStream templateInputStream = FileUtils.getInputStream(getClass(),
"report/JasperReport-template.jrxml");
Document jrxml;
try {
jrxml = XmlUtils.getDocumentBuilder().parse(templateInputStream);
}
catch (Exception ex) {
throw new IllegalStateException(
"Could not open JasperReport-template.jrxml file", ex);
}
Element jasperReport = jrxml.getDocumentElement();
// Add a field definition and its use in the detail band per
// usableFields
String textHeight = "20";
String sTextW = "64";
String fTextW = "119";
String yPos = "0";
int xPos = 0; // we need xPos as int in order to modify its value.
Element staticText;
Element textField;
Element detailBand = XmlUtils.findFirstElement(
"/jasperReport/detail/band", jasperReport);
Node backgroundNode = XmlUtils.findNode("/jasperReport/background",
jasperReport);
for (FieldMetadata fieldMetadata : usableFields) {
jasperReport.insertBefore(
new XmlElementBuilder("field", jrxml)
.addAttribute(
"name",
fieldMetadata.getFieldName()
.getSymbolName())
.addAttribute(
"class",
fieldMetadata.getFieldType()
.getFullyQualifiedTypeName())
.build(), backgroundNode);
staticText = (Element) new XmlElementBuilder("staticText", jrxml)
.build();
staticText
.appendChild(new XmlElementBuilder("reportElement", jrxml)
.addAttribute("x", String.valueOf(xPos))
.addAttribute("y", yPos)
.addAttribute("width", sTextW)
.addAttribute("height", textHeight).build());
staticText.appendChild(new XmlElementBuilder("textElement", jrxml)
.build());
staticText.appendChild(new XmlElementBuilder("text", jrxml)
.addChild(
jrxml.createCDATASection(fieldMetadata
.getFieldName().getReadableSymbolName()))
.build());
detailBand.appendChild(staticText);
// Increment xPos for the next text box
xPos += (Integer.parseInt(sTextW) + 1);
textField = (Element) new XmlElementBuilder("textField", jrxml)
.build();
textField.appendChild(new XmlElementBuilder("reportElement", jrxml)
.addAttribute("x", String.valueOf(xPos))
.addAttribute("y", yPos).addAttribute("width", fTextW)
.addAttribute("height", textHeight).build());
textField.appendChild(new XmlElementBuilder("textElement", jrxml)
.build());
textField.appendChild(new XmlElementBuilder("textFieldExpression",
jrxml)
.addAttribute(
"class",
fieldMetadata.getFieldType()
.getFullyQualifiedTypeName())
.addChild(
jrxml.createCDATASection("$F{".concat(
fieldMetadata.getFieldName()
.getSymbolName()).concat("}")))
.build());
detailBand.appendChild(textField);
// Increment xPos for the next text box
xPos += (Integer.parseInt(fTextW) + 1);
}
// We are sure that reportJrXml file doesn't exist so we can write it
// with the jrxml document content
MutableFile mutableFile = fileManager.createFile(reportJrXml);
Validate.notNull(mutableFile,
"Could not create jrxml file '".concat(reportJrXml).concat("'"));
try {
// Build a string representation of the jrxml
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
XmlUtils.writeXml(XmlUtils.createIndentingTransformer(),
byteArrayOutputStream, jrxml);
String jrxmlContent = byteArrayOutputStream.toString();
// We need to write the file out (it's a new file, or the existing
// file has different contents)
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = IOUtils.toInputStream(jrxmlContent);
outputStream = mutableFile.getOutputStream();
IOUtils.copy(inputStream, outputStream);
}
finally {
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(outputStream);
}
}
catch (IOException ioe) {
throw new IllegalStateException("Could not output '".concat(
mutableFile.getCanonicalPath()).concat("'"), ioe);
}
}
/**
* Install the Java file CustomJasperReportsMultiFormatView.java as part of
* the web package. The sub-package will be
* <b>controller_package.servlet.view.jasperreports</b>
*
* @return Qualified name of the installed Java file without extension
*/
private String installCustomJasperReportMultiFormatView() {
String controllerPackage = this.aspectName.getPackage()
.getFullyQualifiedPackageName();
String customMultiFormatViewPackage = controllerPackage
.concat(".servlet.view.jasperreports");
String customMultiFormatViewPath = projectOperations.getPathResolver()
.getIdentifier(
LogicalPath.getInstance(Path.SRC_MAIN_JAVA, ""),
customMultiFormatViewPackage.replace(".",
File.separator).concat(
"/CustomJasperReportsMultiFormatView.java"));
MutableFile mutCMultFormVCl = null;
if (!fileManager.exists(customMultiFormatViewPath)) {
mutCMultFormVCl = fileManager.createFile(customMultiFormatViewPath);
InputStream template = FileUtils
.getInputStream(
getClass(),
"web/servlet/view/jasperreports/CustomJasperReportsMultiFormatView-template.java");
String javaTemplate;
try {
javaTemplate = IOUtils
.toString(new InputStreamReader(template));
// Replace package definition
javaTemplate = StringUtils.replace(javaTemplate, "${PACKAGE}",
customMultiFormatViewPackage);
// Write final java file
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = IOUtils.toInputStream(javaTemplate);
outputStream = mutCMultFormVCl.getOutputStream();
IOUtils.copy(inputStream, outputStream);
}
finally {
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(outputStream);
}
}
catch (IOException ioe) {
throw new IllegalStateException(
"Unable load CustomJasperReportsMultiFormatView-template.java template",
ioe);
}
finally {
IOUtils.closeQuietly(template);
}
}
return customMultiFormatViewPackage
.concat(".CustomJasperReportsMultiFormatView");
}
/**
* Return the member details of the given Type
*
* @param type
* @return
*/
private MemberDetails getMemberDetails(JavaType type) {
PhysicalTypeMetadata fBackObjPhTypeMD = (PhysicalTypeMetadata) metadataService
.get(PhysicalTypeIdentifier.createIdentifier(type,
LogicalPath.getInstance(Path.SRC_MAIN_JAVA, "")));
Validate.notNull(
fBackObjPhTypeMD,
"Unable to obtain physical type metdata for type "
+ type.getFullyQualifiedTypeName());
return memberDetailsScanner.getMemberDetails(getClass().getName(),
(ClassOrInterfaceTypeDetails) fBackObjPhTypeMD
.getMemberHoldingTypeDetails());
}
/**
* Return an string with the following content: <b>{"format1","format2"}</b>
* as many fromatX as reportFromats values.
*
* @param reportFormats
* @return
*/
private String getReportFormatsAsArray(String reportFormats) {
StringBuilder sb = new StringBuilder("{");
for (String format : reportFormats.split(",")) {
sb.append("\"").append(format).append("\",");
}
sb.deleteCharAt(sb.length() - 1);
sb.append("}");
return sb.toString();
}
/**
* Given a reportName returns the name for the controller method
* "generate<reportName>". If isForm = true, it will add "Form" to the
* returned method name.
*
* @param reportName
* @param isForm
*/
public static String generateMethodName(String reportName, boolean isForm) {
String methodName = "generate" + StringUtils.capitalize(reportName);
if (isForm) {
methodName = methodName.concat("Form");
}
return methodName;
}
/**
* Strip a report|format string in 2 parts
*
* @param definedReport
* @return string[0]=reportname, string[1]=format
*/
public static String[] stripGvNixReportValue(String definedReport) {
Validate.isTrue(definedReport.contains("|"),
"GvNixReport Annotation value must be reportName|format");
return definedReport.toLowerCase().split("\\|");
}
/**
* Check if formats are a comma separated value matching supported report
* formats.
*
* @param formats
* @return
*/
public static boolean isValidFormat(String formats) {
final String REGEX = "(pdf|xls|csv|html)";
Pattern pattern = Pattern.compile(REGEX, Pattern.CASE_INSENSITIVE);
Matcher matcher = null;
boolean isValid = false;
for (String string : formats.split(",")) {
matcher = pattern.matcher(string);
isValid = matcher.matches();
if (!isValid)
break;
}
return isValid;
}
/**
* Update the format part in the value of a report definition in the
* {@link GvNIXReports}
*
* @param oldFormats
* @param formats
* @return
*/
public static String updateFormat(String oldFormats, String formats) {
StringBuilder sb = new StringBuilder(oldFormats);
String[] splitedFormats = formats.split(",");
for (String format : splitedFormats) {
if (!oldFormats.contains(format)) {
sb.append(",").append(format);
}
}
return sb.toString().toLowerCase();
}
public List<MethodMetadata> getReportMethods() {
return reportMethods;
}
public List<MethodMetadata> getControllerMethods() {
return controllerMethods;
}
public List<String> getInstalledReports() {
return installedReports;
}
public WebScaffoldMetadata getWebScaffoldMetadata() {
return webScaffoldMetadata;
}
// Typically, no changes are required beyond this point
public String toString() {
ToStringBuilder tsc = new ToStringBuilder(this);
tsc.append("identifier", getId());
tsc.append("valid", valid);
tsc.append("aspectName", aspectName);
tsc.append("destinationType", destination);
tsc.append("governor", governorPhysicalTypeMetadata.getId());
tsc.append("reportMethods", reportMethods);
tsc.append("itdTypeDetails", itdTypeDetails);
return tsc.toString();
}
public static final String getMetadataIdentiferType() {
return PROVIDES_TYPE;
}
public static final String createIdentifier(JavaType javaType,
LogicalPath path) {
return PhysicalTypeIdentifierNamingUtils.createIdentifier(
PROVIDES_TYPE_STRING, javaType, path);
}
public static final JavaType getJavaType(String metadataIdentificationString) {
return PhysicalTypeIdentifierNamingUtils.getJavaType(
PROVIDES_TYPE_STRING, metadataIdentificationString);
}
public static final LogicalPath getPath(String metadataIdentificationString) {
return PhysicalTypeIdentifierNamingUtils.getPath(PROVIDES_TYPE_STRING,
metadataIdentificationString);
}
public static boolean isValid(String metadataIdentificationString) {
return PhysicalTypeIdentifierNamingUtils.isValid(PROVIDES_TYPE_STRING,
metadataIdentificationString);
}
}