Lecture résultats recherche avec Apache Commons

De EjnTricks
Révision de 2 janvier 2017 à 01:22 par Etienne (discussion | contributions) (Page créée avec « Cet article va présenter comment effectuer une lecture de résultats de recherche avec une simulation de construction de facettes. Le principe est de parcourir un ensembl... »)

(diff) ← Version précédente | Voir la version courante (diff) | Version suivante → (diff)

Cet article va présenter comment effectuer une lecture de résultats de recherche avec une simulation de construction de facettes. Le principe est de parcourir un ensemble de résultat et de construire des listes de valeurs distinctes.

Les classes d'exemple présentes sur la page d'accueil seront reprises.

Tout le code présenté est disponible ici : http://www.jouvinio.net/svn/study/trunk/apacheCommons

Hand-icon.png Votre avis

Nobody voted on this yet

 You need to enable JavaScript to vote


Study icon.png Objectif

Le principe est de lire un ensemble d'instance de People et de les convertir en tableau. Durant cette lecture, trois listes seront construites avec les valeurs distinctes et ordonnées des propriétés :

  • Prénom
  • Nom
  • Gender


Java format icon.png Utilitaires

Update icon.png Transformation

La première classe mise en place va permettre de transformer les instances de fr.ejn.tutorial.datas.People en tableau de chaînes de caractères. Pour cela une implémentatire de l'interface Transformer est utilisée.

package fr.ejn.tutorial.apache.commons.tool.results.transformer;

import fr.ejn.tutorial.datas.People;
import org.apache.commons.collections4.Transformer;

/**
 * Transformer instance to build a String array from the People instance.
 *
 * @author Etienne Jouvin
 *
 */
public final class PeopleToArrayTransformer implements Transformer<People, String[]> {

  private static PeopleToArrayTransformer INSTANCE = new PeopleToArrayTransformer();

  /**
   * Get default instance.
   *
   * @return Default instance.
   */
  public static PeopleToArrayTransformer getInstance() {
    return INSTANCE;
  }

  /**
   * Private constructor.
   */
  private PeopleToArrayTransformer() {
  }

  /** {@inheritDoc} */
  @Override
  public String[] transform(People input) {
    return new String[] { input.getName(), input.getSurname(), input.getGender().name() };
  }

}

L'implémentation est très simple et seule la fonction transform présente un intérêt. A partir de la variable de la fonction, un tableau est construit avec les données.


Multiples-icon.png Comparateurs

Lors de la construction des valeurs distinctes, un ordre sera mis en place afin d'ordonner les valeurs. Deux implémentations sont construites en étendant la classe ComparableComparator disponible dans le package org.apache.commons.collections4.comparators

Icon-memory.png Ordre alphabétique

Le premier comparateur mis en place va permettre d'effectuer une comparaison en ignorant la casse des chaînes de caractères. Pour cela, la fonction compareIgnoreCase de la classe StringUtils est utilisée.

package fr.ejn.tutorial.apache.commons.tool.results.comparator;

import org.apache.commons.collections4.comparators.ComparableComparator;
import org.apache.commons.lang3.StringUtils;

/**
 * Comparator implementation on String by ignoring case.
 *
 * @author Etienne Jouvin
 *
 */
public final class IgnoreCaseStringComparator extends ComparableComparator<String> {

  private static final IgnoreCaseStringComparator INST = new IgnoreCaseStringComparator();
  private static final long serialVersionUID = 752790054048664906L;

  /**
   * Get default instance.
   *
   * @return Default instance.
   */
  public static IgnoreCaseStringComparator getInstance() {
    return INST;
  }

  /**
   * Private constructor.
   */
  private IgnoreCaseStringComparator() {
  }

  /** {@inheritDoc} */
  @Override
  public int compare(String obj1, String obj2) {
    return StringUtils.compareIgnoreCase(obj1, obj2);
  }

}

Robot-icon.png Comparaison par ordre prédéfini

Le second comparateur sera utilisé pour ordonner les valeurs Gender en fonction d'un ordre prédéfini. Le code mis en place est le uivant.

package fr.ejn.tutorial.apache.commons.tool.results.comparator;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.collections4.PredicateUtils;
import org.apache.commons.collections4.comparators.ComparableComparator;

import java.util.ArrayList;
import java.util.List;

/**
 * Comparator implementation on value position in a referential.
 *
 * @author Etienne Jouvin
 *
 * @param <E> Type of object to compare.
 */
public final class SpecificOrderComparator<E extends Comparable<? super E>>
    extends ComparableComparator<E> {

  private static final long serialVersionUID = 2451589821688727842L;
  private transient List<E> orderRef;

  /**
   * Default constructor with the referential used to sort.
   *
   * @param orderRef Ordered referential.
   */
  public SpecificOrderComparator(List<E> orderRef) {
    this.orderRef = new ArrayList<E>(orderRef);
  }

  /** {@inheritDoc} */
  @Override
  public int compare(E obj1, E obj2) {
    int indx1 = ListUtils.indexOf(orderRef, PredicateUtils.equalPredicate(obj1));
    int indx2 = ListUtils.indexOf(orderRef, PredicateUtils.equalPredicate(obj2));

    ComparableComparator<Integer> comparator = ComparableComparator.comparableComparator();
    return comparator.compare(indx1, indx2);
  }

  /** {@inheritDoc} */
  @Override
  public boolean equals(final Object object) {
    boolean res = false;

    if (super.equals(object) && object.getClass().equals(SpecificOrderComparator.class)) {
      res = CollectionUtils.isEqualCollection(CollectionUtils.emptyIfNull(orderRef), CollectionUtils
          .emptyIfNull(((SpecificOrderComparator<?>) object).orderRef));
    }

    return res;
  }

  /** {@inheritDoc} */
  @Override
  public int hashCode() {
    return "SpecificOrderComparator".hashCode();
  }

}

Au niveau du constructeur, une liste de valeurs est fournie afin de spécifier l'ordre souhaité. Celle-ci est ensuite utilisée au niveau de la fonction compare pour trouver les positions des deux valeurs. La comparaison est ensuite effectuée sur ces positions dans la liste de référence. Ainsi, les données peuvent être triées en fonction de l'ordre souhaité.


Start-icon.png Construction facettes

La construction de facettes sera réalisée à l'aide d'implémentation de Closure. Le principe est d'extraire une valeur, prénom / nom / gender, et de la stockée dans une liste de valeur unique et trié selon un ordre souhaité.

Share-icon.png Code commun

Beaucoup de code sera commun pour les différentes implémentations.

  • Construction de la liste unique;
  • Stockage d'une valeur;
  • Récupération des valeurs ordonnées.

Le code est le suivant.

package fr.ejn.tutorial.apache.commons.tool.results.closure;

import fr.ejn.tutorial.datas.People;
import org.apache.commons.collections4.Closure;
import org.apache.commons.collections4.list.SetUniqueList;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * Common facet builder instance. Use an internal list to store value with unicity specified by the
 * instance SetUniqueList.
 *
 * @author Etienne Jouvin
 *
 * @param <O> Type of object in the facet collection.
 */
public abstract class FacetAddClosure<O> implements Closure<People> {

  private List<O> items;

  /**
   * Default constructor with the target list.
   */
  public FacetAddClosure() {
    this.items = SetUniqueList.setUniqueList(new ArrayList<O>());
  }

  /**
   * Add item into the list.
   *
   * @param item item to add to the list.
   */
  protected void addItem(O item) {
    items.add(item);
  }

  /**
   * Get the comparator instance to use in order to sort results.
   *
   * @return Comparator instance to use.
   */
  abstract Comparator<O> getComparator();

  /**
   * Get the built list.
   *
   * @return Built list.
   */
  public List<O> getItems() {
    ArrayList<O> res = new ArrayList<>(items);

    // Sort the list.
    Collections.sort(res, getComparator());

    // Return the list now sorted.
    return res;
  }

}

Au niveau du constructeur, la liste unique est construire à l'aide de la fonction setUniqueList de la classe SetUniqueList. Cette classe est fournie par commons-collections et permet de constuire une liste qui s'assurera de l'unicité des valeurs insérées.

La fonction addItems est uniquement mise en place pour ajouter une valeur dans cette liste, et s'assurer que la liste sera modifiée de façon uniforme.

Enfin la fonction getItems permet de récupérer les valeurs ajoutées et triées à l'aide du comparateur retourné par la fonction getComparator. Ainsi les sous classes ne devront se charger que de l'extraction des valeurs, et la construction des valeurs distinctes est centralisée et "sécurisée".

User-icon.png Facette prénom

La première implémentation doit permettre d'extraire les données unique des prénoms, récupérés à l'aide de la fonction getName des instances People. Le tri s'effectue par ordre alphabétique en ignorant la casse à l'aide de l'instance IgnoreCaseStringComparator. Le code est très simple, grâce à la classe parente mise en place précédement.

package fr.ejn.tutorial.apache.commons.tool.results.closure;

import fr.ejn.tutorial.apache.commons.tool.results.comparator.IgnoreCaseStringComparator;
import fr.ejn.tutorial.datas.People;

import java.util.Comparator;

/**
 * Facet closure for the people name value. Get value from the method {@link People#getName()}. Use
 * a comparator on the value in upper case.
 *
 * @author Etienne Jouvin
 *
 */
public class FacetPeopleName extends FacetAddClosure<String> {

  /** {@inheritDoc} */
  @Override
  public void execute(People input) {
    super.addItem(input.getName());
  }

  /** {@inheritDoc} */
  @Override
  Comparator<String> getComparator() {
    return IgnoreCaseStringComparator.getInstance();
  }

}

User-group-icon.png Facette nom

Le principe de cette implémentation est exactement le même que pour la précédente. La donnée est extraite depuis la fonction getSurname.

package fr.ejn.tutorial.apache.commons.tool.results.closure;

import fr.ejn.tutorial.apache.commons.tool.results.comparator.IgnoreCaseStringComparator;
import fr.ejn.tutorial.datas.People;

import java.util.Comparator;

/**
 * Facet closure for the people surname value. Get value from the method
 * {@link People#getSurname()}. Use a comparator on the value in upper case.
 *
 * @author Etienne Jouvin
 *
 */
public class FacetPeopleSurname extends FacetAddClosure<String> {

  /** {@inheritDoc} */
  @Override
  public void execute(People input) {
    super.addItem(input.getSurname());
  }

  /** {@inheritDoc} */
  @Override
  Comparator<String> getComparator() {
    return IgnoreCaseStringComparator.getInstance();
  }

}

File-find-icon.png Facette Gender

Ce constructeur est un peu plus compliqué, car l'ordre s'effectue depuis un ordre de valeur souhaité à savoir WOMAN en premier puis MAN. Cependant, grace à l'externalisation du comparateur et de la classe parente, l'implémentation tien en quelques lignes.

package fr.ejn.tutorial.apache.commons.tool.results.closure;

import fr.ejn.tutorial.apache.commons.tool.results.comparator.SpecificOrderComparator;
import fr.ejn.tutorial.datas.Gender;
import fr.ejn.tutorial.datas.People;

import java.util.Arrays;
import java.util.Comparator;

/**
 * Facet closure for the people gender value. Get value from the method {@link People#getGender()}.
 * Use a comparator on the value in upper case.
 *
 * @author Etienne Jouvin
 *
 */
public class FacetPeopleGender extends FacetAddClosure<Gender> {

  private Comparator<Gender> comparator;

  /**
   * Default constructor.
   */
  public FacetPeopleGender() {
    this.comparator = new SpecificOrderComparator<>(Arrays.asList(Gender.WOMAN, Gender.MAN));
  }

  /** {@inheritDoc} */
  @Override
  public void execute(People input) {
    super.addItem(input.getGender());
  }

  /** {@inheritDoc} */
  @Override
  Comparator<Gender> getComparator() {
    return this.comparator;
  }

}

La valeur est donc extraite depuis la fonction getGender, et le comparateur est initialisé dans le constructeur.


Start-icon.png Exécution

Une fois tous les outils mis en place, l'exécution est codée dans une classe type Reader qui va orchestrer l'ensemble de ceux-ci dans une fonction search. Le code est le suivant.

package fr.ejn.tutorial.apache.commons.tool.results;

import fr.ejn.tutorial.apache.commons.tool.results.closure.FacetAddClosure;
import fr.ejn.tutorial.apache.commons.tool.results.closure.FacetPeopleGender;
import fr.ejn.tutorial.apache.commons.tool.results.closure.FacetPeopleName;
import fr.ejn.tutorial.apache.commons.tool.results.closure.FacetPeopleSurname;
import fr.ejn.tutorial.apache.commons.tool.results.model.Results;
import fr.ejn.tutorial.apache.commons.tool.results.transformer.PeopleToArrayTransformer;
import fr.ejn.tutorial.dao.PeopleDao;
import fr.ejn.tutorial.datas.Gender;
import fr.ejn.tutorial.datas.People;
import org.apache.commons.collections4.Closure;
import org.apache.commons.collections4.ClosureUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.collections4.TransformerUtils;

import java.util.Arrays;
import java.util.Collection;

/**
 * Query results builder instance.
 * <p>
 * REad result from the PeopleDao instance and extract all possibles facets data.
 * </p>
 *
 * @author Etienne Jouvin
 *
 */
public class QueryResultsReader {

  private PeopleDao peopleDao;

  /**
   * Do a search and retrieve results + built facets.
   *
   * @return Query results.
   */
  public Results search() {
    Collection<People> peoples = this.peopleDao.getPeopleByGender();

    FacetAddClosure<Gender> facetPeopleGender = new FacetPeopleGender();
    FacetAddClosure<String> facetPeopleName = new FacetPeopleName();
    FacetAddClosure<String> facetPeopleSurname = new FacetPeopleSurname();

    Closure<People> closures = ClosureUtils
        .chainedClosure(Arrays.asList(facetPeopleGender, facetPeopleName, facetPeopleSurname));

    Iterable<People> peoplesIterable = IterableUtils
        .transformedIterable(peoples, TransformerUtils.asTransformer(closures));

    Collection<String[]> searchResults = CollectionUtils
        .collect(peoplesIterable, PeopleToArrayTransformer.getInstance());

    Results res = new Results();
    res.setGendersFacet(facetPeopleGender.getItems());
    res.setNamesFacet(facetPeopleName.getItems());
    res.setSurnamesFacet(facetPeopleSurname.getItems());
    res.setResults(searchResults);

    return res;
  }

  /**
   * Set the people dao instance.
   *
   * @param peopleDao Instance to set.
   */
  public void setPeopleDao(PeopleDao peopleDao) {
    this.peopleDao = peopleDao;
  }

}

A noter la fonction setPeopleDao qui va permettre d'injecter une DAO facilement, et utilisé dans le cadre des tests unitaires par exemple.

La problématique principale est de pouvoir exécuter l'ensemble des closures sur chaque valeur avant transformation, et d'évtier de multiple lecture de la liste des résultats. L'exéction de multiple closure est rendue possible en créant une instance "chaînée" depuis la fonction chainedClosure de la classe ClosureUtils. Ainsi les trois instances seront déclenchées.

    FacetAddClosure<Gender> facetPeopleGender = new FacetPeopleGender();
    FacetAddClosure<String> facetPeopleName = new FacetPeopleName();
    FacetAddClosure<String> facetPeopleSurname = new FacetPeopleSurname();

    Closure<People> closures = ClosureUtils
        .chainedClosure(Arrays.asList(facetPeopleGender, facetPeopleName, facetPeopleSurname));

La deuxième astuce consiste à construire une instance de Transformer à partir de cette nouvelle instance de closure, ce qui est possible avec la fonction asTransformer de la classe TransformerUtils. Ceci permet d'exécuter le "closure" et de retourner l'objet origine sans aucune transformation. Cette instance est utilisée pour construire une implémentation de Iterable exécutant la transformation lors de l'appel de la fonction next.

    Iterable<People> peoplesIterable = IterableUtils
        .transformedIterable(peoples, TransformerUtils.asTransformer(closures));

Enfin la fonction collect de CollectionUtils est exécutée sur cet iterable avec le transformer construisant le tableau depuis les instances de People.

    Collection<String[]> searchResults = CollectionUtils
        .collect(peoplesIterable, PeopleToArrayTransformer.getInstance());

En résumé, lors du parcour de l'iteration, les exécutions sont les suivantes.

  • 1. Récupération d'un élément de l'itération peoplesIterable.
  • 1.1 Appel d'un transformer exécutant la fonction execute d'un closure.
  • 1.2 Appel de tous les closures encapsulé par closures.
  • 2. Transformation de People en String[].
  • 3. Ajout dans la collection résultat.

Enfin, les résultats et les facettes sont placées dans une classe pour encapsuler le tout.