Caros,
Segue aqui minha colaboração na criação de uma aplicação Swing usando iReport.
Após muitas horas de estudo, correção de bugs, buscas intermináveis no Google e testes, consegui resolver o meu problema de abrir um relatório iReport, em uma aplicação Swing, usando JPA TopLink.
I. Bibliotecas utilizadas
Para essa aplicação, estou usando o JDK1.6 e configurei o uso das seguintes bilbiotecas:
beansbinding-1.2.1.jar
mysql-connector-java-5.1.7-bin.jar
toplink-essentials-agent.jar
toplink-essentials.jar
commons-beanutils-1.7.jar
commons-collections-2.1.jar
commons-digester-1.7.jar
commons-javaflow-20060411.jar
commons-logging-1.0.2.jar
iReport.jar
itext-1.3.1.jar
jasperreports-3.0.0.jar
jcommon-1.0.0.jar
slf4j-simple-1.5.6.jar
slf4j-log4j12-1.5.6.jar
slf4j-api-1.5.6.jar
II. Classe Reports
Essa classe tem por objetivo chamar o relatório a partir da sua aplicação, compilando o relatório (aqui neste exemplo, chama-se Clientes.jxml) e apresentando-o no JasperViewer.
Para que isso funcione corretamente, crie o seu relatório usando o iReports (usei a versão 3.0.0 - explicarei mais adiante), e salve-o na mesma pasta do seu projeto, para ser armazenado dentro do arquivo JAR, quando o projeto for compilado.
A unidade de persistência neste exemplo chama-se MechOfficePU, mas basta substituir pela usada na sua aplicação.
O arquivo jxml também pode apresentar alguns problemas com as tags xml, e para isso estou usando uma classe chamada LegacyJasperInputStream, que corrige esse problema. Apresentarei o código mais adiante.
Segue abaixo o código da classe Reports:
public class Reports {
private Cadastro cadastro;
private EntityManager entityManager;
private Query cadastroQuery;
private List cadastroList;
public Reports() {
entityManager = java.beans.Beans.isDesignTime() ? null : javax.persistence.Persistence.createEntityManagerFactory("MechOfficePU").createEntityManager();
}
public void Clientes(){
try{
InputStream rclientes = Reports.class.getResourceAsStream("Clientes.jrxml");
cadastroQuery = java.beans.Beans.isDesignTime() ? null : entityManager.createQuery("SELECT c FROM Cadastro c WHERE c.idpermissao = :idpermissao");
Permissao idpermissao = new Permissao(5);
cadastroQuery.setParameter("idpermissao", idpermissao);
cadastroList = java.beans.Beans.isDesignTime() ? java.util.Collections.emptyList() : cadastroQuery.getResultList();
Map parameters = getParameters(entityManager);
JasperDesign design = JRXmlLoader.load(new LegacyJasperInputStream(rclientes));
JRBeanCollectionDataSource ds = new JRBeanCollectionDataSource(cadastroList);
JasperReport compilado = JasperCompileManager.compileReport(design);
JasperPrint jp = JasperFillManager.fillReport(compilado, new HashMap(), ds);
JasperViewer.viewReport(jp, false);
} catch (Exception e) {
System.out.print(e.getMessage());
}
}
}
III. Classe LegacyJasperInputStream
Essa classe tem por objetivo incluir algumas tags xml, quando o relatório for gerado no iReports, corrigindo alguns problemas de compatibilidade, se houver.
Ela pode ser desnecessária, mas no meu projeto foi a garantia para que o relatório seja compilado com sucesso.
Segue abaixo o código desta classe:
package <o_nome_do_seu_projeto>;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
/**
* This is a decorator around an InputStream, which will convert a modern XSD based Jasper design into the legacy DTD
* version. This is desirable since modern tools i.e. the iReport plugin for NetBeans 6.5 generates designs based on the
* modern XSD schema, whereas the Jasper engine itself that we use apparently can not handle XSD but instead require DTD's.
*
* All you have to do to use this converting decorator, is to pipe your Jasper design InputStream through this one, and
* it will automatically take care of adding a DTD docType and remove attributes which will typically cause a legacy
* Jasper version to choke.
*
* @author Casper Bang
*/
public class LegacyJasperInputStream extends FilterInputStream
{
/**
* @param is The InputStream with the modern XSD based Jasper design
*/
public LegacyJasperInputStream(final InputStream is) {
super( convertToLegacyFormat(is) );
}
private static InputStream convertToLegacyFormat(final InputStream is){
Document document = convertInputStreamToDOM( is );
document.getDocumentElement().removeAttribute("xmlns");
document.getDocumentElement().removeAttribute("xmlns:xsi");
document.getDocumentElement().removeAttribute("xsi:schemaLocation");
return convertStringToInputStream( addDocTypeAndConvertDOMToString(document) );
}
private static Document convertInputStreamToDOM(final InputStream is){
Document document = null;
BufferedInputStream bis = new BufferedInputStream(is);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = null;
try {
builder = factory.newDocumentBuilder();
}
catch (ParserConfigurationException ex) {
LoggerFactory.getLogger(LegacyJasperInputStream.class).error(ex.getMessage(), ex);
}
try {
document = builder.parse(bis);
} catch (SAXException ex) {
LoggerFactory.getLogger(LegacyJasperInputStream.class).error(ex.getMessage(), ex);
} catch (IOException ex) {
LoggerFactory.getLogger(LegacyJasperInputStream.class).error(ex.getMessage(), ex);
}
return document;
}
private static String addDocTypeAndConvertDOMToString(final Document document){
TransformerFactory transfac = TransformerFactory.newInstance();
Transformer trans = null;
try {
trans = transfac.newTransformer();
} catch (TransformerConfigurationException ex) {
LoggerFactory.getLogger(LegacyJasperInputStream.class).error(ex.getMessage(), ex);
}
trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
trans.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "//JasperReports//DTD Report Design//EN");
trans.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "http://jasperreports.sourceforge.net/dtds/jasperreport.dtd");
StringWriter sw = new StringWriter();
StreamResult result = new StreamResult(sw);
DOMSource source = new DOMSource(document);
try {
trans.transform(source, result);
} catch (TransformerException ex) {
LoggerFactory.getLogger(LegacyJasperInputStream.class).error(ex.getMessage(), ex);
}
return sw.toString();
}
private static InputStream convertStringToInputStream(final String template){
InputStream is = null;
try {
is = new ByteArrayInputStream(template.getBytes("UTF-8"));
} catch (UnsupportedEncodingException ex) {
LoggerFactory.getLogger(LegacyJasperInputStream.class).debug(ex.getMessage(), ex);
}
return is;
}
}
IV. Relatório Clientes.jxml
Segue o código XML do meu relatório. Ele apenas recebe as informações de nome, endereco, complemento, ddd e telefone que vem da tabela de clientes do banco de dados. Esse código pode ser alterado facilmente, se você criar um arquivo .jxml e abri-lo com o iReport. Depois é só salvar na pasta do seu projeto, sem precisar compilar (formato .jasper). A classe Reports faz o resto do trabalho para você.
Segue abaixo o código do relatório:
<?xml version="1.0" encoding="UTF-8" ?>
<!-- Created with iReport - A designer for JasperReports -->
<!DOCTYPE jasperReport PUBLIC "//JasperReports//DTD Report Design//EN" "http://jasperreports.sourceforge.net/dtds/jasperreport.dtd">
<jasperReport
name="Clientes"
columnCount="1"
printOrder="Vertical"
orientation="Portrait"
pageWidth="595"
pageHeight="842"
columnWidth="535"
columnSpacing="0"
leftMargin="30"
rightMargin="30"
topMargin="20"
bottomMargin="20"
whenNoDataType="NoPages"
isTitleNewPage="false"
isSummaryNewPage="false">
<property name="ireport.scriptlethandling" value="0" />
<property name="ireport.encoding" value="UTF-8" />
<import value="java.util.*" />
<import value="net.sf.jasperreports.engine.*" />
<import value="net.sf.jasperreports.engine.data.*" />
<field name="nome" class="java.lang.String"/>
<field name="endereco" class="java.lang.String"/>
<field name="complemento" class="java.lang.String"/>
<field name="dddtel1" class="java.lang.String"/>
<field name="telefone1" class="java.lang.String"/>
<background>
<band height="0" isSplitAllowed="true" >
</band>
</background>
<title>
<band height="49" isSplitAllowed="true" >
<staticText>
<reportElement
x="0"
y="0"
width="535"
height="49"
key="staticText"/>
<box></box>
<textElement textAlignment="Center" verticalAlignment="Middle">
<font size="26"/>
</textElement>
<text><![CDATA[Clientes]]></text>
</staticText>
</band>
</title>
<pageHeader>
<band height="16" isSplitAllowed="true" >
</band>
</pageHeader>
<columnHeader>
<band height="20" isSplitAllowed="true" >
<staticText>
<reportElement
x="6"
y="-1"
width="100"
height="20"
key="staticText"/>
<box></box>
<textElement verticalAlignment="Middle">
<font size="12" />
</textElement>
<text><![CDATA[Nome]]></text>
</staticText>
<staticText>
<reportElement
x="198"
y="0"
width="100"
height="20"
key="staticText"/>
<box></box>
<textElement verticalAlignment="Middle">
<font size="12" />
</textElement>
<text><![CDATA[Endereco]]></text>
</staticText>
<staticText>
<reportElement
x="400"
y="0"
width="111"
height="20"
key="staticText"/>
<box></box>
<textElement verticalAlignment="Middle">
<font size="12" />
</textElement>
<text><![CDATA[Telefone]]></text>
</staticText>
</band>
</columnHeader>
<detail>
<band height="19" isSplitAllowed="true" >
<textField isStretchWithOverflow="false" isBlankWhenNull="false" evaluationTime="Now" hyperlinkType="None" hyperlinkTarget="Self" >
<reportElement
x="6"
y="1"
width="173"
height="18"
key="textField"/>
<box></box>
<textElement verticalAlignment="Middle">
<font/>
</textElement>
<textFieldExpression class="java.lang.String"><![CDATA[$F{nome}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="false" isBlankWhenNull="false" evaluationTime="Now" hyperlinkType="None" hyperlinkTarget="Self" >
<reportElement
x="198"
y="1"
width="148"
height="18"
key="textField"/>
<box></box>
<textElement verticalAlignment="Middle">
<font/>
</textElement>
<textFieldExpression class="java.lang.String"><![CDATA[$F{endereco}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="false" isBlankWhenNull="false" evaluationTime="Now" hyperlinkType="None" hyperlinkTarget="Self" >
<reportElement
x="346"
y="1"
width="36"
height="18"
key="textField"/>
<box></box>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font/>
</textElement>
<textFieldExpression class="java.lang.String"><![CDATA[$F{complemento}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="false" isBlankWhenNull="false" evaluationTime="Now" hyperlinkType="None" hyperlinkTarget="Self" >
<reportElement
x="420"
y="1"
width="100"
height="18"
key="textField"/>
<box></box>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font/>
</textElement>
<textFieldExpression class="java.lang.String"><![CDATA[$F{telefone1}]]></textFieldExpression>
</textField>
<textField isStretchWithOverflow="false" isBlankWhenNull="false" evaluationTime="Now" hyperlinkType="None" hyperlinkTarget="Self" >
<reportElement
x="400"
y="1"
width="19"
height="18"
key="textField"/>
<box></box>
<textElement textAlignment="Left" verticalAlignment="Middle">
<font/>
</textElement>
<textFieldExpression class="java.lang.String"><![CDATA[$F{dddtel1}]]></textFieldExpression>
</textField>
</band>
</detail>
<columnFooter>
<band height="0" isSplitAllowed="true" >
</band>
</columnFooter>
<pageFooter>
<band height="0" isSplitAllowed="true" >
</band>
</pageFooter>
<summary>
<band height="0" isSplitAllowed="true" >
</band>
</summary>
</jasperReport>
V. Abrindo o relatório
Agora, basta colocar na sua aplicação um botão ou menu que faça a chamada do relatório:
private void mniRClientesActionPerformed(java.awt.event.ActionEvent evt) {
Reports rclientes = new Reports();
rclientes.Clientes();
}
No entanto, existe um detalhe importante a ser observado. Como o relatório está sendo compilado usando o arquivo JXML, para abri-lo OBRIGATORIAMENTE é necessário que o computador esteja conectado à Internet. Existe uma diretiva no cabeçalho do arquivo XML que será acessada por uma das classes para compilar em tempo de execução o arquivo .jasper. Essa diretiva é a seguinte:
<!DOCTYPE jasperReport PUBLIC “//JasperReports//DTD Report Design//EN” “http://jasperreports.sourceforge.net/dtds/jasperreport.dtd”>
Essa diretiva aparece em todos os arquivos JXML e até o momento não descobri um modo de ignorá-la.
Então, se compilar o arquivo .jxml para .jasper usando o iReport, basta colocar na pasta do projeto e alterar o código da classe Reports, o que veremos na próxima seção.
VI. Arquivo jasper e a classe Reports alterada
Para resolver o problema do uso do arquivo JXML, que obriga a estação estar conectada à Internet (nem todos os usuários da sua aplicação podem ter esse direito ou acesso), então você deve gerar o arquivo .jasper usando o iReport (eu usei a versão 3.0.0), e salvar o arquivo na sua pasta de projeto.
Com isso, a classe LegacyJasperInputStream será dispensada.
Altere a classe Reports no método Clientes(), como segue:
public void Clientes(){
try{
InputStream rclientes = Reports.class.getResourceAsStream("Clientes.jasper");
cadastroQuery = java.beans.Beans.isDesignTime() ? null : entityManager.createQuery("SELECT c FROM Cadastro c WHERE c.idpermissao = :idpermissao");
Permissao idpermissao = new Permissao(5);
cadastroQuery.setParameter("idpermissao", idpermissao);
cadastroList = java.beans.Beans.isDesignTime() ? java.util.Collections.emptyList() : cadastroQuery.getResultList();
Map parameters = getParameters(entityManager);
JRBeanCollectionDataSource ds = new JRBeanCollectionDataSource(cadastroList);
JasperPrint jp = JasperFillManager.fillReport(rclientes, new HashMap(), ds);
JasperViewer.viewReport(jp, false);
} catch (Exception e) {
System.out.print(e.getMessage());
}
}
Por fim, sobre a unidade de persistência, eu criei todas as classes facilmente usando o NetBeans 6.5.0. Mas acho que quanto a isso, não tenho muito o que escrever, visto que já existe um tutorial bem mais qualificado do que eu colocaria aqui. Então segue ao menos a referência sobre como criar essa persistência, usando TopLink: http://www.netbeans.org/kb/docs/java/gui-db-custom_pt_BR.html
Abs