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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package ch.gobothegeek.lofidrox.repositories;

import org.apache.deltaspike.data.api.EntityManagerDelegate;
import org.apache.deltaspike.data.api.EntityPersistenceRepository;
import org.apache.deltaspike.data.api.criteria.CriteriaSupport;
import java.io.Serializable;

// describe a generice repository.
// Please note that this repository is NOT annotated with @Repository (otherwise it will be used for queries, leading to errors)
public 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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package ch.gobothegeek.lofidrox.repositories;

import ch.gobothegeek.lofidrox.model.entities.FileDescriptor;
import ch.gobothegeek.lofidrox.model.entities.FileDescriptorPK;
import ch.gobothegeek.lofidrox.services.FileDescriptorService;
import org.apache.deltaspike.data.api.Repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.transaction.Transactional;
import java.util.Date;
import java.util.List;
import java.util.Optional;

// repository used to access files table
@ApplicationScoped
@Repository(forEntity = FileDescriptor.class)
public abstract class FileDescriptorRepository extends LfdRepository<FileDescriptor, FileDescriptorPK> {
	private final Logger logger = LoggerFactory.getLogger(FileDescriptorRepository.class);

	@Inject private FileRecipientRepository fileRecipientRepository;
	@Inject private FileDescriptorService fileDescriptorService;

	// return file by its name
	@Transactional(Transactional.TxType.REQUIRED)
	public abstract Optional<FileDescriptor> findById(Integer id);

	@Transactional(Transactional.TxType.REQUIRED)
	public abstract List<FileDescriptor> findByIdIn(List<Integer> ids);

	// create the required file
	@Transactional(Transactional.TxType.REQUIRED)
	public FileDescriptor createFile(String filename, String path, String source, String type) {
		FileDescriptor file;

		file = new FileDescriptor(null, filename, path, source, new Date(), type);
		file = this.save(file);
		return file;
	}

	// update file path
	@Transactional(Transactional.TxType.REQUIRED)
	public FileDescriptor updateFilePathAndType(Integer id, String path, String type) {
		Optional<FileDescriptor> file;

		file = this.findById(id);
		if (file.isPresent()) {
			file.get().setPath(path);
			file.get().setDataType(type);
			this.save(file.get());
			return file.get();
		}
		return null;
	}

	// delete specified file
	@Transactional(Transactional.TxType.REQUIRED)
	public void deleteFile(Integer id) {
		Optional<FileDescriptor> file;

		file = this.findById(id);
		file.ifPresent(this::remove);
	}
}

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:

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

Repository FileRecipientRepository

Ce repository permet la manipulation des entités FileRecipient.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package ch.gobothegeek.lofidrox.repositories;

import ch.gobothegeek.lofidrox.model.entities.FileRecipient;
import ch.gobothegeek.lofidrox.model.entities.FileRecipientPK;
import org.apache.deltaspike.data.api.Query;
import org.apache.deltaspike.data.api.QueryParam;
import org.apache.deltaspike.data.api.Repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.enterprise.context.ApplicationScoped;
import javax.transaction.Transactional;
import java.util.List;
import java.util.Optional;

// repository used to access files recipients table
@ApplicationScoped
@Repository(forEntity = FileRecipient.class)
public abstract class FileRecipientRepository extends LfdRepository<FileRecipient, FileRecipientPK> {
	private final Logger logger = LoggerFactory.getLogger(FileRecipientRepository.class);

	// return recipient by its id
	@Transactional(Transactional.TxType.REQUIRED)
	public abstract Optional<FileRecipient> findById(Integer id);

	// return recipients for a file
	@Transactional(Transactional.TxType.REQUIRED)
	public abstract List<FileRecipient> findAnyByFileId(Integer id);

	// return recipient for a file and a user
	@Transactional(Transactional.TxType.REQUIRED)
	public abstract Optional<FileRecipient> findAnyByUserToAndFileId(String userTo, Integer id);

	// create the required file
	@Transactional(Transactional.TxType.REQUIRED)
	public FileRecipient createLink(Integer fileId, String userTo, Boolean downloaded) {
		FileRecipient file;

		file = new FileRecipient(null, fileId, userTo, downloaded);
		file = this.save(file);
		return file;
	}

	// return list of files for a specific user
	@Transactional(Transactional.TxType.REQUIRED)
	@Query(value = "SELECT rec FROM FileRecipient rec LEFT JOIN rec.file f WHERE rec.userTo = :uto ORDER BY f.name")
	public abstract List<FileRecipient> listFilesForUser(@QueryParam("uto") String userTo);

	// delete specified file
	@Transactional(Transactional.TxType.REQUIRED)
	public void deleteLink(String user, Integer id) {
		Optional<FileRecipient> recipient;

		recipient = this.findAnyByUserToAndFileId(user, id);
		recipient.ifPresent(this::remove);
	}

	// return file for a specific id and user
	@Transactional(Transactional.TxType.REQUIRED)
	@Query(value = "SELECT rec FROM FileRecipient rec LEFT JOIN rec.file file WHERE rec.userTo = :uto AND file.id = :fid")
	public abstract Optional<FileRecipient> findFileByIdAndUserTo(@QueryParam("fid") Integer id, @QueryParam("uto") String userTo);

	// mark file read
	@Transactional(Transactional.TxType.REQUIRED)
	public FileRecipient markRead(FileRecipient recipient) {
		if (null != recipient) {
			recipient.setDownloaded(true);
			recipient = this.save(recipient);
		}
		return recipient;
	}
}

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package ch.gobothegeek.lofidrox.repositories;

import ch.gobothegeek.lofidrox.model.entities.User;
import ch.gobothegeek.lofidrox.model.entities.UserPK;
import org.apache.deltaspike.data.api.Repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.enterprise.context.ApplicationScoped;
import javax.transaction.Transactional;
import java.util.List;
import java.util.Optional;

// repository used to access users table
@ApplicationScoped
@Repository(forEntity = User.class)
public abstract class UserRepository extends LfdRepository<User, UserPK> {
	private final Logger logger = LoggerFactory.getLogger(UserRepository.class);

	// return user if found by its username
	@Transactional(Transactional.TxType.REQUIRED)
	public abstract Optional<User> findByUsername(String username);

	// with username and encoded password, returns the required user (if found)
	@Transactional(Transactional.TxType.REQUIRED)
	public abstract Optional<User> findByUsernameAndPwd(String username, String pwd);

	// create the required user, with encrpyted password
	@Transactional(Transactional.TxType.REQUIRED)
	public User createUser(String username, String pwd) {
		User user;

		user = new User(username, pwd);
		user = this.save(user);
		return user;
	}

	// return list of users
	@Transactional(Transactional.TxType.REQUIRED)
	public abstract List<User> findAllOrderByUsernameAsc();
}

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package ch.gobothegeek.lofidrox.repositories;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Disposes;
import javax.enterprise.inject.Produces;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceUnit;
import javax.transaction.TransactionScoped;
import java.io.Serializable;

// Produces an entityManager for persistence unit described in persistence.xml file.
// Nothing fancy here. Just keep in mind that persistence unit name is LfdPersistenceUnit
@ApplicationScoped
public class LfdEntityManagerProducer implements Serializable {
    @PersistenceUnit(name = "LfdPersistenceUnit") private EntityManagerFactory entityManagerFactory;

    // return an EntityManager use to access data
    @Produces
    @TransactionScoped
    public EntityManager create() {
        return this.entityManagerFactory.createEntityManager();
    }

    // close current entity manager
    public void close(@Disposes EntityManager em)
    {
        if (em.isOpen()) { em.close(); }
    }

    // getter/setter for EntityManagerFactory
    public EntityManagerFactory getEntityManagerFactory() { return entityManagerFactory; }
    public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) { this.entityManagerFactory = entityManagerFactory; }
}

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