Sixième article sur LoFiDroX: les services.
Objectif des services
Les services permettent d’établir le lien entre les contrôleurs et les repositories. Pour rappel, un contrôleur est responsable de gérer les requêtes et les réponses, le repository est lui responsable de l’accès aux données. Le service est alors chargé d’implémenter la logique de chaque traitement.
Service FileDescriptorService
Ce service gère le stockage sur disque et les liens avec les utilisateurs.
1package ch.gobothegeek.lofidrox.services;
2
3import ch.gobothegeek.lofidrox.exceptions.LfdException;
4import ch.gobothegeek.lofidrox.model.entities.FileDescriptor;
5import ch.gobothegeek.lofidrox.model.entities.FileRecipient;
6import ch.gobothegeek.lofidrox.model.json.file.JsonFileDownload;
7import ch.gobothegeek.lofidrox.repositories.FileDescriptorRepository;
8import ch.gobothegeek.lofidrox.repositories.FileRecipientRepository;
9import org.apache.commons.codec.binary.Base64;
10import org.apache.commons.io.FileUtils;
11import org.apache.deltaspike.core.api.config.ConfigResolver;
12import org.slf4j.Logger;
13import org.slf4j.LoggerFactory;
14import javax.enterprise.context.ApplicationScoped;
15import javax.inject.Inject;
16import javax.transaction.Transactional;
17import java.io.File;
18import java.io.IOException;
19import java.util.List;
20import java.util.UUID;
21
22@ApplicationScoped
23@Transactional
24public class FileDescriptorService {
25 private static final Logger logger = LoggerFactory.getLogger(FileDescriptorService.class);
26 private static final String BASE64_DATA = "data:";
27 private static final String BASE64_TAG = ";base64,";
28
29 @Inject private FileDescriptorRepository fileDescriptorRepository;
30 @Inject private FileRecipientService fileRecipientService;
31 @Inject private FileRecipientRepository fileRecipientRepository;
32
33 @Transactional(Transactional.TxType.REQUIRED)
34 public FileDescriptor addFile(String filename, String userSrc) {
35
36 if ((null != filename) && (null != userSrc)) {
37 return this.fileDescriptorRepository.createFile(filename, null, userSrc, null);
38 }
39 return null;
40 }
41
42 // upload a file to multiples receivers
43 @Transactional(Transactional.TxType.REQUIRED)
44 public boolean uploadFileToUsers(String[] users, String filename, String base64Data, String userPost) {
45 FileDescriptor file;
46 String folder;
47 File fileOut;
48 String base64Head;
49 byte[] content;
50
51 if ((null != users) && (0 < users.length) && (null != filename) && (null != base64Data)) {
52 // prepare upload: add file name to database
53 file = this.addFile(filename, userPost);
54 try {
55 // compute destination folder name
56 folder = ConfigResolver.getPropertyValue("application.storage.base");
57 folder = folder.replace("{source}", userPost);
58 // compute filename
59 fileOut = new File(folder, UUID.randomUUID().toString());
60 // ensure this path exists
61 FileUtils.forceMkdir(new File(folder));
62 // extract base64 header before decoding
63 base64Head = base64Data.substring(BASE64_DATA.length(), base64Data.indexOf(BASE64_TAG));
64 // get Base64 content
65 content = Base64.decodeBase64(base64Data.substring(base64Data.indexOf(BASE64_TAG) + BASE64_TAG.length()));
66 FileUtils.writeByteArrayToFile(fileOut, content);
67 // update file in DB
68 if (!this.updateFilePathAndType(file.getId(), fileOut.getAbsolutePath(), base64Head)) {
69 logger.error("Unable to update file [" + file.getId() + "]");
70 throw new LfdException("Unable to update file [" + file.getId() + "]");
71 }
72 return (null != this.fileRecipientService.addRecipients(file.getId(), users));
73 } catch (Exception e) {
74 System.out.println("Unable to write file [" + filename + "]");
75 e.printStackTrace();
76 // delete file from DB
77 this.deleteFile(file);
78 }
79 }
80 logger.error("Missing parameters to upload file [users: " + ((null != users) && (0 < users.length)) + "], [filename=" + (null != filename) + "], " +
81 "[data=" + (null != base64Data) + "]");
82 return false;
83 }
84
85 @Transactional(Transactional.TxType.REQUIRED)
86 public Boolean updateFilePathAndType(Integer fileId, String path, String type) {
87 FileDescriptor file;
88
89 if ((null != fileId) && (null != path)) {
90 file = this.fileDescriptorRepository.updateFilePathAndType(fileId, path, type);
91 return (null != file);
92 }
93 return Boolean.FALSE;
94 }
95
96 @Transactional(Transactional.TxType.REQUIRED)
97 public void deleteFile(FileDescriptor file) {
98 if (null != file) {
99 FileUtils.deleteQuietly(new File(file.getPath()));
100 this.fileDescriptorRepository.deleteFile(file.getId());
101 }
102 }
103
104 @Transactional(Transactional.TxType.REQUIRED)
105 public JsonFileDownload downloadFile(Integer fileId, String user) {
106 FileRecipient rec;
107 JsonFileDownload jsonDL;
108 StringBuilder content;
109
110 jsonDL = new JsonFileDownload();
111 // start by finding file in database
112 rec = this.fileRecipientService.findFileByIdAndOwner(fileId, user);
113 if ((null != rec) && null != rec.getFile()) {
114 // mark file as read
115 this.fileRecipientService.markRead(rec);
116 // add filename (without path)
117 jsonDL.setFilename(rec.getFile().getName());
118 try {
119 content = new StringBuilder();
120 // add Base64 header
121 content.append(BASE64_DATA);
122 content.append(rec.getFile().getDataType());
123 content.append(BASE64_TAG);
124 // add Base64 file content
125 content.append(Base64.encodeBase64String(FileUtils.readFileToByteArray(new File(rec.getFile().getPath()))));
126 jsonDL.setType(rec.getFile().getDataType());
127 jsonDL.setData(content.toString());
128 return jsonDL;
129 } catch (IOException e) {
130 logger.error("Unable to read file [" + rec.getFile().getPath() + "]", e);
131 e.printStackTrace();
132 }
133 } else {
134 logger.error("Unable to fetch file with id [" + fileId + "] for user [" + user + "]");
135 }
136 return null;
137 }
138
139 @Transactional(Transactional.TxType.REQUIRED)
140 public int deleteFiles(List<Integer> files, String user) {
141 List<FileDescriptor> filesDesc;
142 List< FileRecipient> recips;
143
144 if ((null != files) && (0 < files.size()) && (null != user)) {
145 // find files by id
146 filesDesc = this.fileDescriptorRepository.findByIdIn(files);
147 if (filesDesc.size() == files.size()) { // we got all required files
148 for (FileDescriptor fileDesc : filesDesc) {
149 // break link between recipient and file
150 this.fileRecipientRepository.deleteLink(user, fileDesc.getId());
151 // check if a user hold this file
152 recips = this.fileRecipientRepository.findAnyByFileId(fileDesc.getId());
153 if ((null == recips) || (0 == recips.size())) {
154 // no one hold the file, remove it
155 this.deleteFile(fileDesc);
156 }
157 }
158 return filesDesc.size();
159 }
160 }
161 return -1;
162 }
163}
Méthode | But |
---|---|
addFile | Etablit le lien entre un fichier et un utilisateur. Afin d’économiser de l’espace disque, chaque fichier est stocké de manière unique puis mis à disposition des destinataires. |
uploadFileToUsers | Créer un dossier de dépôt pour l’émetteur (si besoin) puis transformer le contenu Base64 en fichier et le stocker sur disque et finalement mettre à disposition des destinataires le fichier stocké (méthode FileRecipientService.addRecipients). |
updateFilePathAndType | Stocker en base l’identifiant du fichier, son chemin d’accès (relatif) et son type MIME (utile pour déclencher un téléchargement correct). |
deleteFile | Efface le fichier désigné. |
downloadFile | lit le fichier désigné, le transforme en Base64 et prépare la réponse JSON pour le navigateur |
deleteFiles | A partir des identifiants des fichiers, trouve les fichiers en base de données, supprimer les liens vers l’utilisateur ayant demandé la suppression et finalement effacer le fichier, pour autant qu’il n’ait plus de destinataire. |
Service FileRecipientService
Ce service a pour but de gérer les destinataires de fichiers.
1package ch.gobothegeek.lofidrox.services;
2
3import ch.gobothegeek.lofidrox.model.entities.FileRecipient;
4import ch.gobothegeek.lofidrox.repositories.FileDescriptorRepository;
5import ch.gobothegeek.lofidrox.repositories.FileRecipientRepository;
6import org.slf4j.Logger;
7import org.slf4j.LoggerFactory;
8import javax.enterprise.context.ApplicationScoped;
9import javax.inject.Inject;
10import javax.transaction.Transactional;
11import java.util.ArrayList;
12import java.util.List;
13import java.util.Optional;
14
15@ApplicationScoped
16@Transactional
17public class FileRecipientService {
18 private static final Logger logger = LoggerFactory.getLogger(FileRecipientService.class);
19
20 @Inject private FileDescriptorRepository fileDescriptorRepository;
21 @Inject private FileRecipientRepository fileRecipientRepository;
22
23 // add recipients for a file descriptor
24 @Transactional(Transactional.TxType.REQUIRED)
25 public List<FileRecipient> addRecipients(Integer fileId, String[] users) {
26 List<FileRecipient> recipients;
27 FileRecipient rec;
28
29 if ((null != fileId) && (null != users) && (0 < users.length)) {
30 recipients = new ArrayList<>();
31 for (String userTo : users) {
32 rec = this.fileRecipientRepository.createLink(fileId, userTo, false);
33 recipients.add(rec);
34 }
35 return recipients;
36 }
37 logger.error("Missing parameters to add recipients [fileId=" + (null != fileId) + "], [users=" + ((null != users) && (0 < users.length)) + "]");
38 return null;
39 }
40
41 // return list of files for a specific user
42 @Transactional(Transactional.TxType.REQUIRED)
43 public List<FileRecipient> listFilesForUser(String owner) {
44 return this.fileRecipientRepository.listFilesForUser(owner);
45 }
46
47 @Transactional(Transactional.TxType.REQUIRED)
48 public FileRecipient findFileByIdAndOwner(Integer id, String owner) {
49 Optional<FileRecipient> file;
50
51 file = this.fileRecipientRepository.findFileByIdAndUserTo(id, owner);
52 return (file.orElse(null));
53 }
54
55 @Transactional(Transactional.TxType.REQUIRED)
56 public FileRecipient markRead(FileRecipient recipient) {
57 recipient = this.fileRecipientRepository.markRead(recipient);
58 return recipient;
59 }
60}
Méthode | But |
---|---|
addRecipients | Génére les liens entre un fichier donné et les utilisateurs concernés. |
listFilesForUser | Retourne la liste des fichiers reçus par un utilisateur. |
findFileByIdAndOwner | Permet de vérifier si un fichier donné est bien lié à l’utilisateur indiqué. |
markRead | Note que le fichier a été téléchargé par l’utilisateur. |
Service RequestService
Ce service possède un seul objectif: trouver l’adresse IP de l’appelant via la méthode getRemoteAddress
.
1package ch.gobothegeek.lofidrox.services;
2
3import org.slf4j.Logger;
4import org.slf4j.LoggerFactory;
5import javax.enterprise.context.RequestScoped;
6import javax.inject.Inject;
7import javax.servlet.http.HttpServletRequest;
8
9@RequestScoped
10public class RequestService {
11 private final Logger logger = LoggerFactory.getLogger(RequestService.class);
12
13 @Inject private HttpServletRequest request;
14
15 public String getRemoteAddress() {
16 return request.getRemoteAddr();
17 }
18}
Service SessionService
Ici on va gérer les sessions utilisateurs: création, récupération et suppression.
1package ch.gobothegeek.lofidrox.services;
2
3import ch.gobothegeek.lofidrox.exceptions.LfdSessionException;
4import ch.gobothegeek.lofidrox.security.sessions.LfdSession;
5import org.slf4j.Logger;
6import org.slf4j.LoggerFactory;
7import javax.enterprise.context.ApplicationScoped;
8import java.util.HashMap;
9
10@ApplicationScoped
11public class SessionService {
12 private static final Logger logger = LoggerFactory.getLogger(SessionService.class);
13 private HashMap<String, LfdSession> sessions;
14
15 public SessionService() {
16 this.sessions = new HashMap<>();
17 }
18
19 public void create(String username) {
20 if (!this.sessions.containsKey(username)) {
21 this.sessions.put(username, new LfdSession());
22 }
23 }
24
25 public void add(String username, String key, Object obj) throws LfdSessionException {
26 if (this.sessions.containsKey(username)) {
27 this.sessions.get(username).add(key, obj);
28 return;
29 }
30 logger.error("No session for [" + username + "]");
31 throw new LfdSessionException("No session for this user (" + username + ")");
32 }
33
34 public Object get(String username, String key) throws LfdSessionException {
35 if (this.sessions.containsKey(username)) {
36 return this.sessions.get(username).get(key);
37 }
38 logger.error("No session for [" + username + "]");
39 throw new LfdSessionException("No session for this user (" + username + ")");
40 }
41
42 public void delete(String username) {
43 if (this.sessions.containsKey(username)) {
44 this.sessions.get(username).destroy();
45 this.sessions.remove(username);
46 }
47 }
48
49 public boolean hasSession(String username) {
50 return this.sessions.containsKey(username);
51 }
52}
Méthode | But |
---|---|
create | Créer une nouvelle session pour l’utilisateur, s’il n’en possède pas encore. |
add | Ajoute un objet à la session de l’utilisateur. |
get | Retourne un objet de la session de l’utilisateur. |
delete | Supprime la session de l’utilisateur. |
hasSession | Indique si l’utilisateur possède une session. |
Service UserService
Ce service permet le pilotage des utilisateurs: enregistrement, connexion, déconnexion.
1package ch.gobothegeek.lofidrox.services;
2
3import ch.gobothegeek.lofidrox.exceptions.LfdException;
4import ch.gobothegeek.lofidrox.model.entities.User;
5import ch.gobothegeek.lofidrox.repositories.UserRepository;
6import ch.gobothegeek.lofidrox.security.LfdUserBean;
7import ch.gobothegeek.lofidrox.utils.GtgPasswordUtils;
8import org.apache.commons.codec.DecoderException;
9import org.apache.commons.codec.binary.Hex;
10import org.apache.deltaspike.core.api.config.ConfigResolver;
11import org.slf4j.Logger;
12import org.slf4j.LoggerFactory;
13import javax.enterprise.context.ApplicationScoped;
14import javax.inject.Inject;
15import javax.transaction.Transactional;
16import java.util.ArrayList;
17import java.util.List;
18import java.util.Optional;
19
20@ApplicationScoped
21@Transactional
22public class UserService {
23 private static final Logger logger = LoggerFactory.getLogger(UserService.class);
24 @Inject private UserRepository userRepository;
25 @Inject private SessionService sessionService;
26 @Inject private LfdUserBean lfdUserBean;
27
28 @Transactional(Transactional.TxType.REQUIRED)
29 public Boolean loginUser(String username, String pwd) {
30 Optional<User> user;
31 String encPwd;
32
33 try {
34 encPwd = GtgPasswordUtils.hashPassword(pwd, Hex.decodeHex(ConfigResolver.getPropertyValue("application.password.salt")),
35 ConfigResolver.getPropertyValue("application.password.algorithm"),
36 Integer.parseInt(ConfigResolver.getPropertyValue("application.password.iteration")),
37 Integer.parseInt(ConfigResolver.getPropertyValue("application.password.keylength")));
38 user = this.userRepository.findByUsernameAndPwd(username, encPwd);
39 if (user.isPresent()) {
40 this.sessionService.create(username);
41 //this.lfdUserLoggedEvent.fire(new LfdUserLoggedEvent());
42 this.lfdUserBean.setUser(new ch.gobothegeek.lofidrox.security.sessions.LfdUser(username));
43 return Boolean.TRUE;
44 }
45 logger.error("User not found or wrong password [" + username + "]");
46 return Boolean.FALSE;
47 } catch (DecoderException e) {
48 System.out.println("Unable to decode salt [" + ConfigResolver.getPropertyValue("application.password.salt") + "]");
49 e.printStackTrace();
50 logger.error("Unable to decode salt [" + ConfigResolver.getPropertyValue("application.password.salt") + "]", e);
51 }
52 return Boolean.FALSE;
53 }
54
55 @Transactional(Transactional.TxType.REQUIRED)
56 public User createUser(String username, String pwd) throws LfdException {
57 Optional<User> userReg;
58 String encPwd;
59
60 if (null != pwd) {
61 userReg = this.userRepository.findByUsername(username);
62 if (userReg.isEmpty()) {
63 try {
64 encPwd = GtgPasswordUtils.hashPassword(pwd, Hex.decodeHex(ConfigResolver.getPropertyValue("application.password.salt")), ConfigResolver.getPropertyValue("application.password.algorithm"), Integer.parseInt(ConfigResolver.getPropertyValue("application.password.iteration")), Integer.parseInt(ConfigResolver.getPropertyValue("application.password.keylength")));
65 return this.userRepository.createUser(username, encPwd);
66 } catch (DecoderException e) {
67 logger.error("Unable to decode salt [" + ConfigResolver.getPropertyValue("application.password.salt") + "]", e);
68 throw new LfdException("Unable to hash password");
69 }
70 }
71 }
72 return null;
73 }
74
75 @Transactional(Transactional.TxType.REQUIRED)
76 public void logoffUser(String username) {
77 if (this.sessionService.hasSession(username)) {
78 this.sessionService.delete(username);
79 }
80 }
81
82 @Transactional(Transactional.TxType.REQUIRED)
83 public List<User> listUsers() {
84 return this.userRepository.findAllOrderByUsernameAsc();
85 }
86
87 @Transactional(Transactional.TxType.REQUIRED)
88 public List<String> listUsernames() {
89 List<User> users = this.userRepository.findAllOrderByUsernameAsc();
90 List<String> usernames;
91
92 if (null != users) {
93 usernames = new ArrayList<>();
94 if (0 < users.size()) {
95 for (User usr : users) { usernames.add(usr.getUsername()); }
96 }
97 return usernames;
98 }
99 return null;
100 }
101}
Méthode | But |
---|---|
loginUser | Réalise la connexion de l’utilisateur |
createUser | Enregistre un nouvel utilisateur |
logoffUser | Déconnecte l’utilisateur |
listUsers | Retourne la liste des utilisateurs |
listUsernames | Retourne la liste des noms des utilisateurs |
Conclusion
L’écriture des services avec Apache Deltaspike est intuitive et leur injection est vraiment simple (pas de déclaration XML comme Spring par exemple).
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