Dans ce quatrième article, nous allons nous intéresser aux repositories de l’application.

Persistance des entités

Dans le troisième article, j’ai présenté les entités de l’application. Apache Deltaspike propose une solution simple (et plutôt élégante) permettant de gérer la persistance des données: le repository. En effet, les méthodes abstraites répondent à une normalisation permettant de ne pas écrire la requête JPQL correspondante.

Repository générique: LfdRepository

Afin de faciliter l’écriture des différents repositories, j’ai ajouté un repository générique: LfdRepository.

    
 1package ch.gobothegeek.lofidrox.repositories;
 2
 3import org.apache.deltaspike.data.api.EntityManagerDelegate;
 4import org.apache.deltaspike.data.api.EntityPersistenceRepository;
 5import org.apache.deltaspike.data.api.criteria.CriteriaSupport;
 6import java.io.Serializable;
 7
 8// describe a generice repository.
 9// Please note that this repository is NOT annotated with @Repository (otherwise it will be used for queries, leading to errors)
10public abstract class LfdRepository<E, P extends Serializable> implements EntityPersistenceRepository<E, P>, EntityManagerDelegate<E>, CriteriaSupport<E> { }

Repository FileDescriptorRepository

Ce repository permet la manipulation des entités FileDescriptor.

    
 1package ch.gobothegeek.lofidrox.repositories;
 2
 3import ch.gobothegeek.lofidrox.model.entities.FileDescriptor;
 4import ch.gobothegeek.lofidrox.model.entities.FileDescriptorPK;
 5import ch.gobothegeek.lofidrox.services.FileDescriptorService;
 6import org.apache.deltaspike.data.api.Repository;
 7import org.slf4j.Logger;
 8import org.slf4j.LoggerFactory;
 9import javax.enterprise.context.ApplicationScoped;
10import javax.inject.Inject;
11import javax.transaction.Transactional;
12import java.util.Date;
13import java.util.List;
14import java.util.Optional;
15
16// repository used to access files table
17@ApplicationScoped
18@Repository(forEntity = FileDescriptor.class)
19public abstract class FileDescriptorRepository extends LfdRepository<FileDescriptor, FileDescriptorPK> {
20	private final Logger logger = LoggerFactory.getLogger(FileDescriptorRepository.class);
21
22	@Inject private FileRecipientRepository fileRecipientRepository;
23	@Inject private FileDescriptorService fileDescriptorService;
24
25	// return file by its name
26	@Transactional(Transactional.TxType.REQUIRED)
27	public abstract Optional<FileDescriptor> findById(Integer id);
28
29	@Transactional(Transactional.TxType.REQUIRED)
30	public abstract List<FileDescriptor> findByIdIn(List<Integer> ids);
31
32	// create the required file
33	@Transactional(Transactional.TxType.REQUIRED)
34	public FileDescriptor createFile(String filename, String path, String source, String type) {
35		FileDescriptor file;
36
37		file = new FileDescriptor(null, filename, path, source, new Date(), type);
38		file = this.save(file);
39		return file;
40	}
41
42	// update file path
43	@Transactional(Transactional.TxType.REQUIRED)
44	public FileDescriptor updateFilePathAndType(Integer id, String path, String type) {
45		Optional<FileDescriptor> file;
46
47		file = this.findById(id);
48		if (file.isPresent()) {
49			file.get().setPath(path);
50			file.get().setDataType(type);
51			this.save(file.get());
52			return file.get();
53		}
54		return null;
55	}
56
57	// delete specified file
58	@Transactional(Transactional.TxType.REQUIRED)
59	public void deleteFile(Integer id) {
60		Optional<FileDescriptor> file;
61
62		file = this.findById(id);
63		file.ifPresent(this::remove);
64	}
65}

Méthode findById

Pas grand chose à en dire: il s’agit d’une recherche par identifiant. Les deux points à noter sont que la méthode est abstract et que son retour est Optional afin d’indiquer que l’entité peut ne pas exister.

Méthode findByIdIn

Il s’agit d’une extension de la méthode précédente puisqu’elle permet la recherche des entités dont l’identifiant figure dans la liste indiquée.

Méthode createFile

Ici la méthode n’est pas abstraite car elle va écrire en base une nouvelle entité.

Méthode updateFilePathAndType

Cette méthode va mettre à jour certains champs de l’entité désignée par son identifiant.

Méthode deleteFile

Cette méthode permet la suppression de l’entité désignée par son identifiant. Point notable: file.ifPresent(this::remove);. Si la méthode findById ne retourne rien, la suppression n’est pas tentée. Cette syntaxe permet d’éviter d’écrire ce code:

    
1if (file.isPresent()) { this.remove(file); }

Repository FileRecipientRepository

Ce repository permet la manipulation des entités FileRecipient.

    
 1package ch.gobothegeek.lofidrox.repositories;
 2
 3import ch.gobothegeek.lofidrox.model.entities.FileRecipient;
 4import ch.gobothegeek.lofidrox.model.entities.FileRecipientPK;
 5import org.apache.deltaspike.data.api.Query;
 6import org.apache.deltaspike.data.api.QueryParam;
 7import org.apache.deltaspike.data.api.Repository;
 8import org.slf4j.Logger;
 9import org.slf4j.LoggerFactory;
10import javax.enterprise.context.ApplicationScoped;
11import javax.transaction.Transactional;
12import java.util.List;
13import java.util.Optional;
14
15// repository used to access files recipients table
16@ApplicationScoped
17@Repository(forEntity = FileRecipient.class)
18public abstract class FileRecipientRepository extends LfdRepository<FileRecipient, FileRecipientPK> {
19	private final Logger logger = LoggerFactory.getLogger(FileRecipientRepository.class);
20
21	// return recipient by its id
22	@Transactional(Transactional.TxType.REQUIRED)
23	public abstract Optional<FileRecipient> findById(Integer id);
24
25	// return recipients for a file
26	@Transactional(Transactional.TxType.REQUIRED)
27	public abstract List<FileRecipient> findAnyByFileId(Integer id);
28
29	// return recipient for a file and a user
30	@Transactional(Transactional.TxType.REQUIRED)
31	public abstract Optional<FileRecipient> findAnyByUserToAndFileId(String userTo, Integer id);
32
33	// create the required file
34	@Transactional(Transactional.TxType.REQUIRED)
35	public FileRecipient createLink(Integer fileId, String userTo, Boolean downloaded) {
36		FileRecipient file;
37
38		file = new FileRecipient(null, fileId, userTo, downloaded);
39		file = this.save(file);
40		return file;
41	}
42
43	// return list of files for a specific user
44	@Transactional(Transactional.TxType.REQUIRED)
45	@Query(value = "SELECT rec FROM FileRecipient rec LEFT JOIN rec.file f WHERE rec.userTo = :uto ORDER BY f.name")
46	public abstract List<FileRecipient> listFilesForUser(@QueryParam("uto") String userTo);
47
48	// delete specified file
49	@Transactional(Transactional.TxType.REQUIRED)
50	public void deleteLink(String user, Integer id) {
51		Optional<FileRecipient> recipient;
52
53		recipient = this.findAnyByUserToAndFileId(user, id);
54		recipient.ifPresent(this::remove);
55	}
56
57	// return file for a specific id and user
58	@Transactional(Transactional.TxType.REQUIRED)
59	@Query(value = "SELECT rec FROM FileRecipient rec LEFT JOIN rec.file file WHERE rec.userTo = :uto AND file.id = :fid")
60	public abstract Optional<FileRecipient> findFileByIdAndUserTo(@QueryParam("fid") Integer id, @QueryParam("uto") String userTo);
61
62	// mark file read
63	@Transactional(Transactional.TxType.REQUIRED)
64	public FileRecipient markRead(FileRecipient recipient) {
65		if (null != recipient) {
66			recipient.setDownloaded(true);
67			recipient = this.save(recipient);
68		}
69		return recipient;
70	}
71}

Méthode findById

Retourne l’entité désignée par son identifiant.

Méthode findAnyByFileId

Cette méthode retourne la liste des entités liées au FileDescriptor dont l’identifiant est indiqué.

Méthode findAnyByUserToAndFileId

Même concept que la méthode précédente mais avec l’identifiant de l’entité User en plus du FileDescriptor.

Permet d’ajouter un lien entre une entité User et une entité FileDescriptor. C’est grâce à cela qu’on peut envoyer le même fichier à plusieurs destinataires.

Méthode listFilesForUser

Cette méthode est particulière: elle est abstraite mais il n’est pas possible de décrire la requête avec les mots clefs proposés par Deltaspike. Il faut alors ajouter cette requête avec l’annotation @Query.

Cette méthode permet d’effacer le lien entre une entité User et une entité FileDescriptor.

Méthode findFileByIdAndUserTo

Elle permet de trouver le FileDescriptor demandé pour le User indiqué. Même remarque que pour listFilesForUser.

Méthode markRead

Ici on va noter que le fichier a été téléchargé par son destinataire.

Repository UserRepository

Ce repository assure l’accès aux comptes utilisateurs (création, connexion) via l’entitét User

    
 1package ch.gobothegeek.lofidrox.repositories;
 2
 3import ch.gobothegeek.lofidrox.model.entities.User;
 4import ch.gobothegeek.lofidrox.model.entities.UserPK;
 5import org.apache.deltaspike.data.api.Repository;
 6import org.slf4j.Logger;
 7import org.slf4j.LoggerFactory;
 8import javax.enterprise.context.ApplicationScoped;
 9import javax.transaction.Transactional;
10import java.util.List;
11import java.util.Optional;
12
13// repository used to access users table
14@ApplicationScoped
15@Repository(forEntity = User.class)
16public abstract class UserRepository extends LfdRepository<User, UserPK> {
17	private final Logger logger = LoggerFactory.getLogger(UserRepository.class);
18
19	// return user if found by its username
20	@Transactional(Transactional.TxType.REQUIRED)
21	public abstract Optional<User> findByUsername(String username);
22
23	// with username and encoded password, returns the required user (if found)
24	@Transactional(Transactional.TxType.REQUIRED)
25	public abstract Optional<User> findByUsernameAndPwd(String username, String pwd);
26
27	// create the required user, with encrpyted password
28	@Transactional(Transactional.TxType.REQUIRED)
29	public User createUser(String username, String pwd) {
30		User user;
31
32		user = new User(username, pwd);
33		user = this.save(user);
34		return user;
35	}
36
37	// return list of users
38	@Transactional(Transactional.TxType.REQUIRED)
39	public abstract List<User> findAllOrderByUsernameAsc();
40}

Méthode findByUsername

Rien de spécial puisque cette méthode retourne un User grâce à son username.

Méthode findByUsernameAndPwd

Cette méthode est utilisée lors de la phase de connexion: elle permet de vérifier que le couple nom d’utilisateur / mot de passe (chiffré!) est le bon.

Méthode createUser

Cette méthode permet d’ajouter un nouvel utilisateur.

Méthode findAllOrderByUsernameAsc

Encore une méthode abstraite. Son objectif est de retourner tous les utilisateurs, classés par nom en ordre croissant.

Classe LfdEntityManagerProducer

    
 1package ch.gobothegeek.lofidrox.repositories;
 2
 3import javax.enterprise.context.ApplicationScoped;
 4import javax.enterprise.inject.Disposes;
 5import javax.enterprise.inject.Produces;
 6import javax.persistence.EntityManager;
 7import javax.persistence.EntityManagerFactory;
 8import javax.persistence.PersistenceUnit;
 9import javax.transaction.TransactionScoped;
10import java.io.Serializable;
11
12// Produces an entityManager for persistence unit described in persistence.xml file.
13// Nothing fancy here. Just keep in mind that persistence unit name is LfdPersistenceUnit
14@ApplicationScoped
15public class LfdEntityManagerProducer implements Serializable {
16    @PersistenceUnit(name = "LfdPersistenceUnit") private EntityManagerFactory entityManagerFactory;
17
18    // return an EntityManager use to access data
19    @Produces
20    @TransactionScoped
21    public EntityManager create() {
22        return this.entityManagerFactory.createEntityManager();
23    }
24
25    // close current entity manager
26    public void close(@Disposes EntityManager em)
27    {
28        if (em.isOpen()) { em.close(); }
29    }
30
31    // getter/setter for EntityManagerFactory
32    public EntityManagerFactory getEntityManagerFactory() { return entityManagerFactory; }
33    public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) { this.entityManagerFactory = entityManagerFactory; }
34}

Cette classe permet la mise à disposition d’un EntityManager (requis par JPA) permettant l’accès à la base de données. La gestion de cet EntityManager est réalisée au travers d’une factory décrite dans le fichier persistence.xml, sous le nom LfdPersistenceUnit.

Conclusion

L’accès aux données proposé par Apache Deltaspike au travers des repositories est simple et efficace, surtout en rapport de ce que propose Hibernate.

Code source

Comme annoncé dans l’article Fuyez GitHub, le code n’est plus disponible sur GitHub. L’intégralité du code est disponible sur mon CodeBerg: https://codeberg.org/GoboTheGeek/LoFiDroX