Présentation

Cet article montre comment créer un rapport à trois niveaux de maitre détail en java. On utilisera exclusivement jasper et java sans utiliser la moindre ligne SQL pour générer les rapports. Il sera intéressant de voir ici deux techniques pour passer des listes de détails de manière différentes aux sous rapports. Soit en implémentant directement l'interface JRDataSource, soit en créant de simple collection et en configurant dans Ireport le passage des listes en paramètre.
Le rapport que nous allons créer va afficher dans un premier temps la liste des pays européens et un troisième niveau avec les principales villes de chacun des pays. En maitre, nous aurons les informations concernant l'Europe, nom et couleur associée à ce continent à savoir le bleu. En détail, nous trouverons la liste des pays européens. Dans ce détail, nous allons créer un nouveau détail qui affichera la liste des principales villes du pays. NB : Toute ressemblance entre la réalité et les données affichées dans ces rapports n'est que pure coincidence !!!

Création d'un maître détail avec jasper en implémentant la datasource

Partie Java

La première étape de ce rapport consiste à afficher les informations générales du continent européens, ici le nom du continent, la couleur associé qui est le bleu, puis de faire un sous rapport dans le détail afin d'afficher les pays européens. Voici la classe définissant un pays :

public class Pays {
        private String nom;
        private Integer population;
        public String getNom() {
                return nom;
        }
        public void setNom(String nom) {
                this.nom = nom;
        }
        public Integer getPopulation() {
                return population;
        }
        public void setPopulation(Integer population) {
                this.population = population;
        }
}

Cette classe ne présente rien de spécial, il s'agit d'un simple POJO avec des attributs et des accesseurs.
Voyons maintenant la classe Continent contenant les informations générales liées à un continent ainsi qu'une liste des pays.

public class Continent extends AbstractJRDataSource<Pays> {
        private String nom;
        private String couleur;
        
        public String getNom() {
                return nom;
        }
        public void setNom(String nom) {
                this.nom = nom;
        }
        public String getCouleur() {
                return couleur;
        }
        public void setCouleur(String couleur) {
                this.couleur = couleur;
        }
}

La liste des pays n'apparait pas ici en Collection, en revanche, on étend une classe maison AbstractJRDataSource<Pays> dont voici le code :

public abstract class AbstractJRDataSource<T> implements JRDataSource {
    protected int index=0;
    protected List<T>listeObj = null;
    public boolean next() throws JRException {
        index++;
        return (index <= listeObj.size());
    }

    public Object getFieldValue(JRField field) throws JRException {
        T obj = listeObj.get(index-1);
        Object value;
        try {
            String methodName = "get"+field.getName().substring(0,1).toUpperCase()+field.getName().substring(1);
            value = MethodUtils.invokeExactMethod( obj, methodName, null);
        } catch (Exception e) {
            throw new JRException(e.getMessage());
        }
        return value;
    }
    public List<T> getListeObj() {
        return listeObj;
    }
    public void setListeObj(List<T> listeObj) {
        this.listeObj = listeObj;
    }
}

Il s'agit d'une classe générique dont un peut définir librement le type de la liste implémentée via le template, celle-ci implémente les méthodes getFieldValue() et next() de l'interface JRDataSource de Jasper. L'implémentation de l'interface JRDataSource va nous permettre de facilement alimenter les sous raports en créant de simple liste de pays. Voici maintenant le code de création des rapports :

public class SubReportDemo {
        public static void main(String[] args) {
        Continent continent = init();
        JasperReport jasperReport = null;
        JasperPrint jasperPrint = null;
        Map<Object,Object> parameterMap = new HashMap<Object,Object>();
        parameterMap.put( "reportName", "Rapport DEMO" );       
        parameterMap.put( "nom", continent.getNom() );
        parameterMap.put( "couleur", continent.getCouleur() );
        InputStream reportStream = SubReportDemo.class.getClassLoader().getResourceAsStream("rapport_demo_parent.jrxml");
        try {
                        jasperReport = JasperCompileManager.compileReport(reportStream);
                        jasperPrint = JasperFillManager.fillReport(jasperReport, parameterMap, continent);
                        JasperExportManager.exportReportToPdfFile(jasperPrint, "/tmp/reportEurope_"+new Date()+".pdf");
                } catch (JRException e) {
                        e.printStackTrace();
                }
        }
        private static Continent init(){                
                Continent europe = new Continent();             
                List<Pays> paysEurope = new ArrayList<Pays>();
                Pays france = new Pays();
                france.setNom("FRANCE"); france.setPopulation(60000000);
                paysEurope.add(france);
                
                Pays italie = new Pays();
                italie.setNom("Italie"); italie.setPopulation(45000000);      
                paysEurope.add(italie);
                
                Pays angleterre = new Pays();
                angleterre.setNom("Angleterre"); angleterre.setPopulation(55000000);
                paysEurope.add(angleterre);
                
                europe.setListeObj(paysEurope);
                europe.setNom("Europe");
                europe.setCouleur("bleu");    
                return europe;
        }
}

la Liste des pays est renseigné dans la classe continent via la méthode setListeObj() définit comme template Pays => public class Continent extends AbstractJRDataSource<Pays>

Partie iReport

Création du rapport principal

Pour commencer, nous allons définir les paramètres, Le titre du rapport sera passé en paramètre au rapport puis affiché sur celui-ci. Reste les données à afficher dans la partie entête en l'occurence les données de l'objet continent : le nom et la couleur que nous passons en tant que paramètres, En tant que champs, nous passerons les données affichées dans la partie détail (partie automatiquement alimentée avec les méthodes de l'interface que nous avons implémentée : JRDataSource) provenant de la classe Pays. Jasper Paramètres Maitre Ensuite, nous disposons les données sur le rapports : asper report maitre Voici alors le résultat obtenu avec la partie Maître Continent et les Détails Pays Jasper Resultat Maitre Detail

Etape 2 : Sous Rapport en java avec jasper

Nous avons vu en première partie comment faire un simple maître détail en utilisant Jasper et en implémentant l'interface JRDataSource mise à disposition par l'API jasper. Voyons maintenant comment créer un sous rapport dans la partie détail du rapport. Nous allons ajouter en dessous de chaque pays, une liste des principales villes. Nous allons créer un sous rapport dans la partie détail. Voyons tout d'abord les modification au niveau structure objet.

Partie JAVA

On commence par ajouter la classe ville dont voici les attributs :

public class Ville {
        private String nom;
        private Integer population;
        private Double superficie;
        public String getNom() {
                return nom;
        }
        public void setNom(String nom) {
                this.nom = nom;
        }
        public Integer getPopulation() {
                return population;
        }
        public void setPopulation(Integer population) {
                this.population = population;
        }
        public Double getSuperficie() {
                return superficie;
        }
        public void setSuperficie(Double superficie) {
                this.superficie = superficie;
        }
}

Ensuite, on modifie la classe Pays pour y ajouter la collection de ville. Ici, on n'étend pas l'interface DataSource, on verra comment sera créer la dataSource dans iReport un peu plus tard...

public class Pays {
        private String nom;
        private Integer population;
        private Collection<Ville> villes;
        public String getNom() {
                return nom;
        }
        public void setNom(String nom) {
                this.nom = nom;
        }
        public Integer getPopulation() {
                return population;
        }
        public void setPopulation(Integer population) {
                this.population = population;
        }
        public Collection<Ville> getVilles() {
                return villes;
        }
        public void setVilles(Collection<Ville> villes) {
                this.villes = villes;
        }       
}

Pour la partie java... on a finit !

Partie iReport

En dessous des informations concernant le pays, nous allons créer un rapport secondaire. Une boîte de dialogue s'ouvre alors, nous sélectionnerons just create subreport element
Dans les propriétés du rapport secondaire, nous nommons le rapport qui sera appélé dans l'onglet Rapport Secondaire (autre) par exemple Rapport_Villes.jasper.
Ensuite, nous passons à l'onglet Rapport Secondaire et nous définissons les paramètres du rapport secondaire en réutilisant les paramètres du rapport principal dans Expression de mappe de paramètre avec la valeur : new HashMap($P{REPORT_PARAMETERS_MAP}), En dessous, on sélectionne : Utiliser l'expression de source de donnée et en valeur, on y met notre collection qui alimente la datasource : new JRBeanCollectionDataSource($F{villes}). Une fois fait, l'onglet problem mentionne que nous n'avons pas tout a fait terminé, il faut rajouter dans le rapport principal la propriété que nous avons ajouté dans je code java de la classe Pays à savoir villes de type : java.util.Collection. On rajoute le champs et nous pouvons passer au sous rapport.

A venir !!!