Versions stable (inachevée mais prête à un déploiement en prod)
This commit is contained in:
171
src/main/java/dev/lions/components/ChartComponent.java
Normal file
171
src/main/java/dev/lions/components/ChartComponent.java
Normal file
@@ -0,0 +1,171 @@
|
||||
package dev.lions.components;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.faces.view.ViewScoped;
|
||||
import jakarta.inject.Named;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.primefaces.model.charts.*;
|
||||
import org.primefaces.model.charts.bar.*;
|
||||
import org.primefaces.model.charts.line.*;
|
||||
import org.primefaces.model.charts.pie.*;
|
||||
import org.primefaces.model.charts.optionconfig.title.Title;
|
||||
import org.primefaces.model.charts.optionconfig.legend.Legend;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Composant de gestion des graphiques.
|
||||
* Fournit des modèles pour les graphiques linéaires, en barres et circulaires.
|
||||
*
|
||||
* @author Lions Dev Team
|
||||
* @version 2.1
|
||||
*/
|
||||
@Slf4j
|
||||
@Named
|
||||
@ViewScoped
|
||||
public class ChartComponent implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final List<String> CHART_COLORS = Arrays.asList(
|
||||
"rgba(33, 150, 243, 0.8)",
|
||||
"rgba(255, 64, 129, 0.8)",
|
||||
"rgba(255, 193, 7, 0.8)",
|
||||
"rgba(76, 175, 80, 0.8)",
|
||||
"rgba(156, 39, 176, 0.8)"
|
||||
);
|
||||
|
||||
@Getter @Setter
|
||||
private LineChartModel lineModel;
|
||||
|
||||
@Getter @Setter
|
||||
private BarChartModel barModel;
|
||||
|
||||
@Getter @Setter
|
||||
private PieChartModel pieModel;
|
||||
|
||||
/**
|
||||
* Initialise les modèles de graphiques lors de la construction du composant.
|
||||
*/
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
log.info("Initialisation des modèles de graphiques.");
|
||||
createLineModel();
|
||||
createBarModel();
|
||||
createPieModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée un modèle de graphique linéaire.
|
||||
*/
|
||||
private void createLineModel() {
|
||||
lineModel = new LineChartModel();
|
||||
ChartData data = new ChartData();
|
||||
|
||||
LineChartDataSet dataSet = new LineChartDataSet();
|
||||
dataSet.setLabel("Évolution des ventes");
|
||||
dataSet.setData(new ArrayList<>(generateRandomData(6)));
|
||||
dataSet.setBorderColor(CHART_COLORS.get(0));
|
||||
dataSet.setFill(false);
|
||||
dataSet.setTension(0.4);
|
||||
|
||||
data.addChartDataSet(dataSet);
|
||||
data.setLabels(generateLabels(6, "Mois"));
|
||||
|
||||
lineModel.setData(data);
|
||||
addChartOptions(lineModel, "Évolution temporelle");
|
||||
|
||||
log.debug("Modèle de graphique linéaire créé.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée un modèle de graphique en barres.
|
||||
*/
|
||||
private void createBarModel() {
|
||||
barModel = new BarChartModel();
|
||||
ChartData data = new ChartData();
|
||||
|
||||
BarChartDataSet dataSet = new BarChartDataSet();
|
||||
dataSet.setLabel("Performance par trimestre");
|
||||
dataSet.setData(new ArrayList<>(generateRandomData(4)));
|
||||
dataSet.setBackgroundColor(CHART_COLORS.get(1));
|
||||
|
||||
data.addChartDataSet(dataSet);
|
||||
data.setLabels(generateLabels(4, "T"));
|
||||
|
||||
barModel.setData(data);
|
||||
addChartOptions(barModel, "Performance trimestrielle");
|
||||
|
||||
log.debug("Modèle de graphique en barres créé.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée un modèle de graphique circulaire.
|
||||
*/
|
||||
private void createPieModel() {
|
||||
pieModel = new PieChartModel();
|
||||
ChartData data = new ChartData();
|
||||
|
||||
PieChartDataSet dataSet = new PieChartDataSet();
|
||||
dataSet.setData(Arrays.asList(25, 35, 40));
|
||||
dataSet.setBackgroundColor(CHART_COLORS);
|
||||
|
||||
data.addChartDataSet(dataSet);
|
||||
data.setLabels(Arrays.asList("Développement", "Marketing", "Infrastructure"));
|
||||
|
||||
pieModel.setData(data);
|
||||
addChartOptions(pieModel, "Répartition des activités");
|
||||
|
||||
log.debug("Modèle de graphique circulaire créé.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute des options telles que le titre et la légende aux graphiques.
|
||||
*
|
||||
* @param model Le modèle de graphique.
|
||||
* @param title Titre du graphique.
|
||||
*/
|
||||
private void addChartOptions(ChartModel model, String title) {
|
||||
Title chartTitle = new Title();
|
||||
chartTitle.setDisplay(true);
|
||||
chartTitle.setText(title);
|
||||
|
||||
Legend legend = new Legend();
|
||||
legend.setDisplay(true);
|
||||
legend.setPosition("bottom");
|
||||
|
||||
if (model instanceof LineChartModel) {
|
||||
LineChartModel lineChart = (LineChartModel) model;
|
||||
lineChart.setExtender((String) chartTitle.getText());
|
||||
} else if (model instanceof BarChartModel) {
|
||||
BarChartModel barChart = (BarChartModel) model;
|
||||
barChart.setExtender((String) chartTitle.getText());
|
||||
} else if (model instanceof PieChartModel) {
|
||||
PieChartModel pieChart = (PieChartModel) model;
|
||||
pieChart.setExtender((String) chartTitle.getText());
|
||||
}
|
||||
}
|
||||
|
||||
private List<Number> generateRandomData(int size) {
|
||||
Random random = new Random();
|
||||
List<Number> data = new ArrayList<>();
|
||||
for (int i = 0; i < size; i++) {
|
||||
data.add(random.nextInt(100));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private List<String> generateLabels(int size, String prefix) {
|
||||
List<String> labels = new ArrayList<>();
|
||||
for (int i = 1; i <= size; i++) {
|
||||
labels.add(prefix + " " + i);
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
}
|
||||
4
src/main/java/dev/lions/components/DataTableView.java
Normal file
4
src/main/java/dev/lions/components/DataTableView.java
Normal file
@@ -0,0 +1,4 @@
|
||||
package dev.lions.components;
|
||||
|
||||
public class DataTableView {
|
||||
}
|
||||
295
src/main/java/dev/lions/components/DynamicDataTable.java
Normal file
295
src/main/java/dev/lions/components/DynamicDataTable.java
Normal file
@@ -0,0 +1,295 @@
|
||||
package dev.lions.components;
|
||||
|
||||
import jakarta.enterprise.context.Dependent;
|
||||
import jakarta.faces.view.ViewScoped;
|
||||
import jakarta.inject.Named;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import java.io.Serial;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.primefaces.model.FilterMeta;
|
||||
import org.primefaces.model.LazyDataModel;
|
||||
import org.primefaces.model.SortMeta;
|
||||
import org.primefaces.event.data.PageEvent;
|
||||
|
||||
import dev.lions.utils.Column;
|
||||
import dev.lions.utils.FilterCriteria;
|
||||
import dev.lions.exceptions.DataTableException;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Composant de tableau de données dynamique avec support de pagination, tri et filtrage.
|
||||
* Fournit une interface riche et performante pour l'affichage et la manipulation des données
|
||||
* tabulaires dans l'application.
|
||||
*
|
||||
* @author Lions Dev Team
|
||||
* @version 2.1
|
||||
*/
|
||||
@Named
|
||||
@Dependent
|
||||
@Slf4j
|
||||
public class DynamicDataTable<T> implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final int DEFAULT_PAGE_SIZE = 10;
|
||||
private static final int MAX_PAGE_SIZE = 100;
|
||||
private static final String DEFAULT_EMPTY_MESSAGE = "Aucune donnée disponible";
|
||||
|
||||
@Getter @Setter
|
||||
private List<T> data;
|
||||
|
||||
@Getter @Setter
|
||||
private List<Column> columns;
|
||||
|
||||
@Getter @Setter
|
||||
private String emptyMessage = DEFAULT_EMPTY_MESSAGE;
|
||||
|
||||
@Getter @Setter
|
||||
@Min(1)
|
||||
private int pageSize = DEFAULT_PAGE_SIZE;
|
||||
|
||||
@Getter
|
||||
private LazyDataModel<T> lazyModel;
|
||||
|
||||
@Getter
|
||||
private final Map<String, FilterCriteria> activeFilters = new ConcurrentHashMap<>();
|
||||
|
||||
private final Map<String, Comparator<T>> customSorters = new HashMap<>();
|
||||
private final Map<String, PropertyAccessor<T>> propertyAccessors = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Initialise le tableau avec les données et les colonnes spécifiées.
|
||||
*
|
||||
* @param data Données à afficher
|
||||
* @param columns Configuration des colonnes
|
||||
*/
|
||||
public void initialize(@NotNull List<T> data, @NotNull List<Column> columns) {
|
||||
log.info("Initialisation du tableau dynamique avec {} enregistrements", data.size());
|
||||
|
||||
validateInitializationParameters(data, columns);
|
||||
|
||||
this.data = new ArrayList<>(data);
|
||||
this.columns = new ArrayList<>(columns);
|
||||
|
||||
initializePropertyAccessors();
|
||||
initializeLazyLoading();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure le modèle de chargement paresseux des données.
|
||||
*/
|
||||
private void initializeLazyLoading() {
|
||||
lazyModel = new LazyDataModel<T>() {
|
||||
@Override
|
||||
public List<T> load(int first, int pageSize, Map<String, SortMeta> sortBy, Map<String, FilterMeta> filterBy) {
|
||||
try {
|
||||
return loadDataPage(first, pageSize, sortBy, filterBy);
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors du chargement des données", e);
|
||||
throw new DataTableException("Échec du chargement des données", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count(Map<String, FilterMeta> filterBy) {
|
||||
return data == null ? 0 : data.size();
|
||||
}
|
||||
};
|
||||
lazyModel.setRowCount(data.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Charge une page de données selon les critères spécifiés.
|
||||
*/
|
||||
protected List<T> loadDataPage(int first, int pageSize, Map<String, SortMeta> sortBy, Map<String, FilterMeta> filterBy) {
|
||||
return data.stream()
|
||||
.filter(item -> applyFilters(item, filterBy))
|
||||
.sorted((a, b) -> applySorting(a, b, sortBy))
|
||||
.skip(first)
|
||||
.limit(pageSize)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Applique les filtres sur un élément.
|
||||
*/
|
||||
private boolean applyFilters(T item, Map<String, FilterMeta> filterBy) {
|
||||
if (filterBy == null || filterBy.isEmpty()) return true;
|
||||
|
||||
return filterBy.entrySet().stream().allMatch(entry -> {
|
||||
Object filterValue = entry.getValue().getFilterValue();
|
||||
if (filterValue == null) return true;
|
||||
|
||||
try {
|
||||
Object value = getPropertyValue(item, entry.getKey());
|
||||
return value != null && value.toString().toLowerCase().contains(filterValue.toString().toLowerCase());
|
||||
} catch (Exception e) {
|
||||
log.warn("Erreur lors du filtrage", e);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si un élément correspond à un critère de filtrage.
|
||||
*/
|
||||
private boolean matchesFilter(T item, String property, Object filterValue) {
|
||||
if (filterValue == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
Object value = getPropertyValue(item, property);
|
||||
return compareValues(value, filterValue);
|
||||
} catch (Exception e) {
|
||||
log.warn("Erreur lors du filtrage de la propriété: {}", property, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare deux valeurs pour le filtrage.
|
||||
*/
|
||||
private boolean compareValues(Object value, Object filterValue) {
|
||||
if (value == null) {
|
||||
return filterValue == null;
|
||||
}
|
||||
|
||||
String valueStr = value.toString().toLowerCase();
|
||||
String filterStr = filterValue.toString().toLowerCase();
|
||||
return valueStr.contains(filterStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applique le tri sur les données.
|
||||
*/
|
||||
private int applySorting(T a, T b, Map<String, SortMeta> sortBy) {
|
||||
for (Map.Entry<String, SortMeta> entry : sortBy.entrySet()) {
|
||||
String property = entry.getKey();
|
||||
try {
|
||||
Comparable valueA = (Comparable) getPropertyValue(a, property);
|
||||
Comparable valueB = (Comparable) getPropertyValue(b, property);
|
||||
|
||||
int result = valueA.compareTo(valueB);
|
||||
return entry.getValue().getOrder().isAscending() ? result : -result;
|
||||
} catch (Exception e) {
|
||||
log.warn("Erreur lors du tri", e);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compare les valeurs de deux propriétés pour le tri.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private int compareProperties(T a, T b, String property) {
|
||||
try {
|
||||
Comparable valueA = (Comparable) getPropertyValue(a, property);
|
||||
Comparable valueB = (Comparable) getPropertyValue(b, property);
|
||||
|
||||
if (valueA == null && valueB == null) return 0;
|
||||
if (valueA == null) return -1;
|
||||
if (valueB == null) return 1;
|
||||
|
||||
return valueA.compareTo(valueB);
|
||||
} catch (Exception e) {
|
||||
log.warn("Erreur lors de la comparaison de la propriété: {}", property, e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise les accesseurs de propriétés pour optimiser les performances.
|
||||
*/
|
||||
private void initializePropertyAccessors() {
|
||||
columns.forEach(column -> {
|
||||
String property = column.getField();
|
||||
try {
|
||||
Method getter = findGetter(property);
|
||||
propertyAccessors.put(property, item -> getter.invoke(item));
|
||||
} catch (Exception e) {
|
||||
log.warn("Impossible de créer l'accesseur pour la propriété: {}", property, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve la méthode getter pour une propriété.
|
||||
*/
|
||||
private Method findGetter(String property) throws NoSuchMethodException {
|
||||
String getterName = "get" + property.substring(0, 1).toUpperCase() + property.substring(1);
|
||||
return data.get(0).getClass().getMethod(getterName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface fonctionnelle pour l'accès aux propriétés.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
private interface PropertyAccessor<T> {
|
||||
Object access(T item) throws Exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute un trieur personnalisé pour une colonne.
|
||||
*/
|
||||
public void addCustomSorter(String property, Comparator<T> comparator) {
|
||||
customSorters.put(property, comparator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour le nombre total de lignes.
|
||||
*/
|
||||
private void updateRowCount() {
|
||||
if (lazyModel != null) {
|
||||
lazyModel.setRowCount(data.size());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gère l'événement de changement de page.
|
||||
*/
|
||||
public void onPageChange(PageEvent event) {
|
||||
log.debug("Changement de page: {}", event.getPage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide les paramètres d'initialisation.
|
||||
*/
|
||||
private void validateInitializationParameters(List<T> data, List<Column> columns) {
|
||||
if (data == null || data.isEmpty()) {
|
||||
throw new DataTableException("Les données ne peuvent pas être nulles ou vides");
|
||||
}
|
||||
if (columns == null || columns.isEmpty()) {
|
||||
throw new DataTableException("La configuration des colonnes est requise");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rafraîchit les données du tableau.
|
||||
*/
|
||||
public void refresh() {
|
||||
log.debug("Rafraîchissement du tableau");
|
||||
updateRowCount();
|
||||
}
|
||||
|
||||
private Object getPropertyValue(T item, String property) throws Exception {
|
||||
PropertyAccessor<T> accessor = propertyAccessors.get(property);
|
||||
if (accessor != null) {
|
||||
return accessor.access(item);
|
||||
}
|
||||
throw new NoSuchFieldException("Propriété inaccessible : " + property);
|
||||
}
|
||||
|
||||
}
|
||||
226
src/main/java/dev/lions/components/FileUploadComponent.java
Normal file
226
src/main/java/dev/lions/components/FileUploadComponent.java
Normal file
@@ -0,0 +1,226 @@
|
||||
package dev.lions.components;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import jakarta.faces.application.FacesMessage;
|
||||
import jakarta.faces.context.FacesContext;
|
||||
import jakarta.faces.view.ViewScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import java.io.Serial;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.primefaces.event.FileUploadEvent;
|
||||
import org.primefaces.model.file.UploadedFile;
|
||||
|
||||
import dev.lions.config.ApplicationConfig;
|
||||
import dev.lions.exceptions.FileUploadException;
|
||||
import dev.lions.services.FileStorageService;
|
||||
import dev.lions.utils.FileValidator;
|
||||
import dev.lions.utils.SecurityUtils;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Composant de gestion des téléchargements de fichiers.
|
||||
* Fournit une interface sécurisée et performante pour le téléchargement,
|
||||
* la validation et la gestion des fichiers dans l'application.
|
||||
*
|
||||
* @author Lions Dev Team
|
||||
* @version 2.1
|
||||
*/
|
||||
@Named
|
||||
@ViewScoped
|
||||
@Slf4j
|
||||
public class FileUploadComponent implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final int MAX_FILES = 10; // Limite de fichiers autorisés
|
||||
private static final String TEMP_DIR_PREFIX = "upload_"; // Préfixe pour répertoire temporaire
|
||||
|
||||
@Inject
|
||||
ApplicationConfig appConfig;
|
||||
|
||||
@Inject
|
||||
FileStorageService storageService;
|
||||
|
||||
@Inject
|
||||
FileValidator fileValidator;
|
||||
|
||||
@Inject
|
||||
SecurityUtils securityUtils;
|
||||
|
||||
@Getter
|
||||
private final List<UploadedFileInfo> uploadedFiles = new ArrayList<>();
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private String uploadDirectory;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private boolean multiple = false;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private String acceptedTypes;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private long maxFileSize;
|
||||
|
||||
/**
|
||||
* Initialisation du composant.
|
||||
*/
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
this.maxFileSize = appConfig.getMaxFileSize();
|
||||
this.acceptedTypes = appConfig.getAllowedFileTypes();
|
||||
this.uploadDirectory = createTempUploadDirectory();
|
||||
|
||||
log.info("Composant de téléchargement initialisé. Taille max: {}, Types acceptés: {}",
|
||||
maxFileSize, acceptedTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gère l'événement de téléchargement de fichier.
|
||||
*
|
||||
* @param event L'événement PrimeFaces contenant le fichier téléchargé.
|
||||
*/
|
||||
public void handleFileUpload(@NotNull FileUploadEvent event) {
|
||||
UploadedFile file = event.getFile();
|
||||
log.info("Téléchargement de fichier : {}", file.getFileName());
|
||||
|
||||
try {
|
||||
validateUploadRequest(file);
|
||||
UploadedFileInfo fileInfo = processUploadedFile(file);
|
||||
uploadedFiles.add(fileInfo);
|
||||
|
||||
addSuccessMessage("Fichier téléchargé avec succès : " + fileInfo.getFileName());
|
||||
} catch (FileUploadException e) {
|
||||
log.error("Erreur de validation du fichier : {}", file.getFileName(), e);
|
||||
addErrorMessage(e.getMessage());
|
||||
} catch (IOException e) {
|
||||
log.error("Erreur lors du traitement du fichier : {}", file.getFileName(), e);
|
||||
addErrorMessage("Une erreur est survenue lors du traitement du fichier.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide la requête de téléchargement.
|
||||
*
|
||||
* @param file Le fichier téléchargé.
|
||||
*/
|
||||
private void validateUploadRequest(UploadedFile file) {
|
||||
if (uploadedFiles.size() >= MAX_FILES) {
|
||||
throw new FileUploadException("Vous avez atteint le nombre maximum de fichiers autorisés.");
|
||||
}
|
||||
fileValidator.validateFile(file, acceptedTypes, maxFileSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Traite et stocke le fichier téléchargé.
|
||||
*
|
||||
* @param file Le fichier téléchargé.
|
||||
* @return Les informations du fichier.
|
||||
* @throws IOException En cas d'erreur de stockage.
|
||||
*/
|
||||
private UploadedFileInfo processUploadedFile(UploadedFile file) throws IOException {
|
||||
String secureFileName = generateSecureFileName(file.getFileName());
|
||||
Path destinationPath = storageService.storeFile(file.getInputStream(), uploadDirectory, secureFileName);
|
||||
|
||||
return UploadedFileInfo.builder()
|
||||
.id(UUID.randomUUID().toString())
|
||||
.fileName(file.getFileName())
|
||||
.contentType(file.getContentType())
|
||||
.size(file.getSize())
|
||||
.path(destinationPath)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un nom de fichier sécurisé.
|
||||
*
|
||||
* @param originalFileName Nom original.
|
||||
* @return Nom sécurisé.
|
||||
*/
|
||||
private String generateSecureFileName(String originalFileName) {
|
||||
String extension = getFileExtension(originalFileName);
|
||||
return securityUtils.sanitizeFileName(UUID.randomUUID().toString() + "." + extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère l'extension d'un fichier.
|
||||
*
|
||||
* @param fileName Nom du fichier.
|
||||
* @return Extension.
|
||||
*/
|
||||
private String getFileExtension(String fileName) {
|
||||
return fileName.substring(fileName.lastIndexOf('.') + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée un répertoire temporaire pour les téléchargements.
|
||||
*
|
||||
* @return Le chemin du répertoire temporaire.
|
||||
*/
|
||||
private String createTempUploadDirectory() {
|
||||
return storageService.createTempDirectory(TEMP_DIR_PREFIX + UUID.randomUUID());
|
||||
}
|
||||
|
||||
/**
|
||||
* Nettoie les ressources lors de la destruction du composant.
|
||||
*/
|
||||
@PreDestroy
|
||||
public void cleanup() {
|
||||
try {
|
||||
storageService.deleteDirectory(uploadDirectory);
|
||||
log.info("Répertoire temporaire supprimé : {}", uploadDirectory);
|
||||
} catch (Exception e) {
|
||||
log.error("Erreur lors du nettoyage des ressources : {}", uploadDirectory, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute un message de succès dans l'interface utilisateur.
|
||||
*
|
||||
* @param message Message à afficher.
|
||||
*/
|
||||
private void addSuccessMessage(String message) {
|
||||
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès", message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute un message d'erreur dans l'interface utilisateur.
|
||||
*
|
||||
* @param message Message à afficher.
|
||||
*/
|
||||
private void addErrorMessage(String message) {
|
||||
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Classe interne représentant un fichier téléchargé.
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
public static class UploadedFileInfo {
|
||||
private final String id;
|
||||
private final String fileName;
|
||||
private final String contentType;
|
||||
private final long size;
|
||||
private final Path path;
|
||||
}
|
||||
}
|
||||
225
src/main/java/dev/lions/components/FilterComponent.java
Normal file
225
src/main/java/dev/lions/components/FilterComponent.java
Normal file
@@ -0,0 +1,225 @@
|
||||
package dev.lions.components;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.faces.model.SelectItem;
|
||||
import jakarta.faces.view.ViewScoped;
|
||||
import jakarta.inject.Named;
|
||||
import jakarta.faces.context.FacesContext;
|
||||
import jakarta.faces.application.FacesMessage;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import java.io.Serial;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import dev.lions.utils.FilterOperator;
|
||||
import dev.lions.utils.FilterCriteria;
|
||||
import dev.lions.exceptions.FilterException;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Composant de gestion des filtres dynamiques.
|
||||
* Permet la création, la validation et l'application de filtres
|
||||
* pour des tableaux de données.
|
||||
*
|
||||
* <p>Fonctionnalités incluses :
|
||||
* <ul>
|
||||
* <li>Ajout de filtres avec validation des entrées</li>
|
||||
* <li>Suppression de filtres</li>
|
||||
* <li>Application des filtres sur des listes d'objets</li>
|
||||
* <li>Interface utilisateur avec feedback via les messages JSF</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Lions Dev Team
|
||||
* @version 2.2
|
||||
*/
|
||||
@Slf4j
|
||||
@Named
|
||||
@ViewScoped
|
||||
public class FilterComponent implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final int MAX_FILTERS = 10;
|
||||
|
||||
@Getter @Setter
|
||||
private List<FilterCriteria> criteria = new ArrayList<>();
|
||||
|
||||
@Getter @Setter
|
||||
private String selectedField;
|
||||
|
||||
@Getter @Setter
|
||||
private FilterOperator selectedOperator;
|
||||
|
||||
@Getter @Setter
|
||||
private String filterValue;
|
||||
|
||||
@Getter
|
||||
private List<SelectItem> availableFields;
|
||||
|
||||
@Getter
|
||||
private List<SelectItem> availableOperators;
|
||||
|
||||
private final Map<String, String> fieldConfigurations = new LinkedHashMap<>();
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
log.debug("Initialisation du composant de filtrage");
|
||||
initializeFieldConfigurations();
|
||||
initializeAvailableFields();
|
||||
initializeAvailableOperators();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise la configuration des champs disponibles.
|
||||
*/
|
||||
private void initializeFieldConfigurations() {
|
||||
fieldConfigurations.put("name", "Nom");
|
||||
fieldConfigurations.put("date", "Date");
|
||||
fieldConfigurations.put("status", "Statut");
|
||||
fieldConfigurations.put("category", "Catégorie");
|
||||
fieldConfigurations.put("price", "Prix");
|
||||
log.info("Champs disponibles pour le filtrage : {}", fieldConfigurations.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remplit la liste des champs disponibles.
|
||||
*/
|
||||
private void initializeAvailableFields() {
|
||||
availableFields = new ArrayList<>();
|
||||
fieldConfigurations.forEach((key, value) ->
|
||||
availableFields.add(new SelectItem(key, value))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remplit la liste des opérateurs disponibles.
|
||||
*/
|
||||
private void initializeAvailableOperators() {
|
||||
availableOperators = new ArrayList<>();
|
||||
for (FilterOperator operator : FilterOperator.values()) {
|
||||
availableOperators.add(new SelectItem(operator, operator.getLabel()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute un critère de filtrage après validation.
|
||||
*/
|
||||
public void addFilter() {
|
||||
log.debug("Ajout d'un filtre : Champ = {}, Opérateur = {}, Valeur = {}",
|
||||
selectedField, selectedOperator, filterValue);
|
||||
|
||||
try {
|
||||
validateFilterInput();
|
||||
validateFilterLimit();
|
||||
|
||||
FilterCriteria newCriteria = new FilterCriteria(selectedField, selectedOperator, filterValue);
|
||||
criteria.add(newCriteria);
|
||||
|
||||
addMessage(FacesMessage.SEVERITY_INFO, "Filtre ajouté", "Filtre appliqué avec succès.");
|
||||
log.info("Filtre ajouté avec succès : {}", newCriteria);
|
||||
resetForm();
|
||||
} catch (FilterException e) {
|
||||
log.warn("Erreur de validation du filtre", e);
|
||||
addMessage(FacesMessage.SEVERITY_ERROR, "Erreur", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime un critère de filtrage.
|
||||
*
|
||||
* @param filter Le critère à supprimer.
|
||||
*/
|
||||
public void removeFilter(@NotNull FilterCriteria filter) {
|
||||
log.debug("Suppression du filtre : {}", filter);
|
||||
criteria.remove(filter);
|
||||
addMessage(FacesMessage.SEVERITY_INFO, "Filtre supprimé", "Le filtre a été retiré.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Efface tous les filtres existants.
|
||||
*/
|
||||
public void clearAllFilters() {
|
||||
log.info("Suppression de tous les filtres ({})", criteria.size());
|
||||
criteria.clear();
|
||||
addMessage(FacesMessage.SEVERITY_INFO, "Filtres effacés", "Tous les filtres ont été supprimés.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Applique les filtres sur une liste de données.
|
||||
*
|
||||
* @param data Liste des objets à filtrer.
|
||||
* @return Liste filtrée.
|
||||
*/
|
||||
public List<Object> applyFilters(List<Object> data) {
|
||||
if (criteria.isEmpty()) {
|
||||
return data;
|
||||
}
|
||||
log.debug("Application des filtres sur {} éléments", data.size());
|
||||
return data.stream().filter(this::matchesAllCriteria).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide les entrées du filtre.
|
||||
*/
|
||||
private void validateFilterInput() {
|
||||
if (selectedField == null || selectedOperator == null || filterValue == null) {
|
||||
throw new FilterException("Tous les champs du filtre doivent être remplis.");
|
||||
}
|
||||
if (selectedOperator.isNumericComparison()) {
|
||||
try {
|
||||
Double.parseDouble(filterValue);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new FilterException("La valeur doit être numérique pour cet opérateur.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validateFilterLimit() {
|
||||
if (criteria.size() >= MAX_FILTERS) {
|
||||
throw new FilterException("Nombre maximum de filtres atteint (" + MAX_FILTERS + ")");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean matchesAllCriteria(Object item) {
|
||||
return criteria.stream().allMatch(filter -> matchesCriteria(item, filter));
|
||||
}
|
||||
|
||||
private boolean matchesCriteria(Object item, FilterCriteria filter) {
|
||||
try {
|
||||
Object value = getPropertyValue(item, filter.getField());
|
||||
return filter.getOperator().apply(value, (String) filter.getValue());
|
||||
} catch (Exception e) {
|
||||
log.warn("Erreur d'accès à la propriété : {}", filter.getField(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Object getPropertyValue(Object item, String property) {
|
||||
try {
|
||||
Method getter = item.getClass().getMethod("get" + capitalize(property));
|
||||
return getter.invoke(item);
|
||||
} catch (Exception e) {
|
||||
throw new FilterException("Propriété inaccessible : " + property);
|
||||
}
|
||||
}
|
||||
|
||||
private String capitalize(String str) {
|
||||
return str.substring(0, 1).toUpperCase() + str.substring(1);
|
||||
}
|
||||
|
||||
private void resetForm() {
|
||||
selectedField = null;
|
||||
selectedOperator = null;
|
||||
filterValue = null;
|
||||
}
|
||||
|
||||
private void addMessage(FacesMessage.Severity severity, String summary, String detail) {
|
||||
FacesContext.getCurrentInstance()
|
||||
.addMessage(null, new FacesMessage(severity, summary, detail));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package dev.lions.components;
|
||||
|
||||
import jakarta.faces.application.FacesMessage;
|
||||
import jakarta.faces.context.FacesContext;
|
||||
import jakarta.faces.view.ViewScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
import java.io.Serial;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
/**
|
||||
* Composant gérant l'affichage des notifications dans l'interface utilisateur.
|
||||
*/
|
||||
@Slf4j
|
||||
@Named
|
||||
@ViewScoped
|
||||
public class NotificationComponent implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String MESSAGE_BUNDLE = "messages";
|
||||
|
||||
@Inject
|
||||
FacesContext facesContext;
|
||||
|
||||
@Inject
|
||||
transient ResourceBundle messageBundle;
|
||||
|
||||
/**
|
||||
* Affiche un message de succès.
|
||||
*/
|
||||
public void showSuccess(@NotBlank String key) {
|
||||
log.debug("Affichage message succès: {}", key);
|
||||
addMessage(FacesMessage.SEVERITY_INFO,
|
||||
getMessage(key + ".title", "Succès"),
|
||||
getMessage(key + ".detail"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche un message d'erreur.
|
||||
*/
|
||||
public void showError(@NotBlank String key) {
|
||||
log.debug("Affichage message erreur: {}", key);
|
||||
addMessage(FacesMessage.SEVERITY_ERROR,
|
||||
getMessage(key + ".title", "Erreur"),
|
||||
getMessage(key + ".detail"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche un message d'avertissement.
|
||||
*/
|
||||
public void showWarning(@NotBlank String key) {
|
||||
log.debug("Affichage message avertissement: {}", key);
|
||||
addMessage(FacesMessage.SEVERITY_WARN,
|
||||
getMessage(key + ".title", "Attention"),
|
||||
getMessage(key + ".detail"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère un message localisé avec fallback.
|
||||
*/
|
||||
private String getMessage(String key, String defaultValue) {
|
||||
try {
|
||||
return messageBundle.getString(key);
|
||||
} catch (Exception e) {
|
||||
log.warn("Message non trouvé: {}", key);
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
private String getMessage(String key) {
|
||||
return getMessage(key, key);
|
||||
}
|
||||
|
||||
private void addMessage(FacesMessage.Severity severity, String summary, String detail) {
|
||||
facesContext.addMessage(null, new FacesMessage(severity, summary, detail));
|
||||
log.debug("Message ajouté: {} - {}", summary, detail);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user