diff --git a/Build_jar/Framework.jar b/Build_jar/Framework.jar index aa310fc..188dcb9 100644 Binary files a/Build_jar/Framework.jar and b/Build_jar/Framework.jar differ diff --git a/src/Controller/FrontController.java b/src/Controller/FrontController.java index dd09d1a..9c904b9 100644 --- a/src/Controller/FrontController.java +++ b/src/Controller/FrontController.java @@ -58,8 +58,8 @@ private void handleException(HttpServletRequest req, HttpServletResponse res, Ex ErrorHandler.handleException(req, res, e); } - private void handleRestApiException(HttpServletResponse res, Exception e) throws IOException { - ErrorHandler.handleRestApiException(res, e); + private void handleRestApiException(HttpServletRequest req,HttpServletResponse res, Exception e) throws IOException { + ErrorHandler.handleRestApiException(req,res, e); } @@ -137,6 +137,7 @@ public void process_request(HttpServletRequest req, HttpServletResponse res, Met if (!res.isCommitted()) { // Ensure response isn't committed for JSP// Ensure response isn't committed for JSP if (Validator.verifyErrorRequest(req) && method.isAnnotationPresent(RollBack.class)) { String urlformOrigin = method.getAnnotation(RollBack.class).rollBackUrl(); + System.out.println("urlformOrigin" + urlformOrigin); String rollBackmethod = method.getAnnotation(RollBack.class).method(); HttpServletRequest getRequest = new HttpServletRequestWrapper(req) { @@ -145,14 +146,13 @@ public String getMethod() { return rollBackmethod; } }; - req.getRequestDispatcher(urlformOrigin).forward(getRequest, res); - } else { + System.out.println("return value" +returnValue); if (returnValue instanceof ModelAndView modelView) { handleModelAndView(modelView, req, res); } else if (returnValue instanceof String) { - print.println(returnValue); + res.sendRedirect((String) returnValue); } else { assert returnValue != null; throw new IOException("Return type is not supported or contains error =>" + returnValue.getClass().getSimpleName()); @@ -162,7 +162,7 @@ public String getMethod() { } } catch (Exception e) { if (method.isAnnotationPresent(RestApi.class)) { - handleRestApiException(res, e); + handleRestApiException(req,res, e); } else { handleException(req, res, e); } @@ -175,6 +175,10 @@ private void handleModelAndView(ModelAndView modelView, HttpServletRequest req, for (Map.Entry entry : modelData.entrySet()) { req.setAttribute(entry.getKey(), entry.getValue()); } + if(modelView.getIsRedirect()){ + res.sendRedirect(modelView.getUrl()); + return; + } RequestDispatcher dispatcher = req.getRequestDispatcher(modelView.getUrl()); dispatcher.forward(req, res); } @@ -190,7 +194,7 @@ private void handleFields(HttpServletRequest request, Class controllerClass, Obj } private Object handleMethod(HttpServletRequest request, Method method, Object controllerInstance) throws Exception { - + boolean errorIsForMe = !Validator.verifyErrorRequest(request); Parameter[] parameters = method.getParameters(); if (parameters.length == 0) { return method.invoke(controllerInstance); @@ -200,6 +204,7 @@ private Object handleMethod(HttpServletRequest request, Method method, Object co Paranamer paranamer = new BytecodeReadingParanamer(); String[] paramNames = paranamer.lookupParameterNames(method); + Requestparam requestparam = new Requestparam(request); for (int i = 0; i < parameters.length; i++) { if (parameters[i].getType() == CustomSession.class) { @@ -211,7 +216,7 @@ private Object handleMethod(HttpServletRequest request, Method method, Object co paramValues[i] = requestparam.mappingParam(parameters[i], paramNames[i]); } } - if (Validator.verifyErrorRequest(request)) { + if (Validator.verifyErrorRequest(request) && errorIsForMe) { return null; } // Invoke the method and get the return value @@ -241,7 +246,10 @@ public void init() throws ServletException { } String directory_controller = getServletContext().getInitParameter("controller"); String realPath = getServletContext().getRealPath(directory_controller); - String package_class = directory_controller.split("/")[directory_controller.split("/").length - 1]; + String package_class = directory_controller + .replace("WEB-INF/classes/", "") // Enlever le préfixe WEB-INF/classes/ + .replace("/", ".") // Remplacer / par . + .replace("\\", "."); ArrayList> controllers_list = AccesController.getControllerList(package_class, realPath); for (Class controller : controllers_list) { for (Method method : controller.getMethods()) { @@ -255,7 +263,6 @@ public void init() throws ServletException { Mapping mp = new Mapping(); mp.setClass_name(controller.getName()); mp.getVerbActions().add(verbAction); - road_controller.put(url, mp); } else { if (!controller.getName().equals(mappingCurrent.getClass_name())) { diff --git a/src/Model/ModelAndView.java b/src/Model/ModelAndView.java index 2141beb..5dcfaae 100644 --- a/src/Model/ModelAndView.java +++ b/src/Model/ModelAndView.java @@ -7,8 +7,9 @@ public class ModelAndView { String url; HashMap data; + Boolean isRedirect = false; - public ModelAndView(String url){ + public ModelAndView(String url) { this.setUrl(url); this.setData(new HashMap<>()); } @@ -29,11 +30,29 @@ public String getUrl() { return url; } - public void setUrl(String url) {this.url = url; } + public void setUrl(String url) { + this.url = url; + } + + public Boolean getIsRedirect() { + return isRedirect; + } + + public void setIsRedirect(Boolean redirect) { + isRedirect = redirect; + } + + /** + * Adds a key-value pair to the model's data map. + * + * @param name The key for the data entry + * @param value The value to be associated with the key + * @throws Exception if the view URL is null + */ public void add_data(String name, Object value) throws Exception { - if (this.url == null){ - throw new Exception("url Model and view null"); + if (this.url == null) { + throw new Exception("url of ModelAndview is null"); } this.data.put(name, value); } diff --git a/src/Utils/AccesController.java b/src/Utils/AccesController.java index 46107cc..04ac52f 100644 --- a/src/Utils/AccesController.java +++ b/src/Utils/AccesController.java @@ -5,32 +5,65 @@ import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; +import java.util.List; import java.util.Objects; public class AccesController { - public static ArrayList> getControllerList(String package_class, String directoryPath) throws ServletException { + public static ArrayList> getControllerList(String packageClass, String directoryPath) throws ServletException { ArrayList> classFiles = new ArrayList<>(); - File b = new File(directoryPath); - if (!b.exists()) { - throw new ServletException("Directory of controller not found =>" + directoryPath); + File baseDirectory = new File(directoryPath); + + if (!baseDirectory.exists()) { + throw new ServletException("Directory of controller not found => " + directoryPath); } - if (Objects.requireNonNull(b.listFiles()).length == 0) { - throw new ServletException("Liste de Controller vide =>" + directoryPath); + + if (Objects.requireNonNull(baseDirectory.listFiles()).length == 0) { + throw new ServletException("Liste de Controller vide => " + directoryPath); } - for (File onefile : Objects.requireNonNull(b.listFiles())) { - if (onefile.isFile() && onefile.getName().endsWith(".class")) { - Class clazz; + + try { + List classPaths = new ArrayList<>(); + + // Parcours récursif des fichiers dans le répertoire + Files.walk(baseDirectory.toPath()) + .filter(Files::isRegularFile) + .filter(path -> path.toString().endsWith(".class")) + .forEach(classPaths::add); + + for (Path classPath : classPaths) { + String className = buildClassName(packageClass, baseDirectory, classPath.toFile()); + try { - clazz = Class.forName(package_class + "." + onefile.getName().split(".class")[0]); - if (clazz.isAnnotationPresent(Controller.class)) + Class clazz = Class.forName(className); + if (clazz.isAnnotationPresent(Controller.class)) { classFiles.add(clazz); + } } catch (ClassNotFoundException e) { - e.printStackTrace(); + e.printStackTrace(); // Vous pouvez logger l'erreur ici } } + } catch (IOException e) { + throw new ServletException("Erreur lors du parcours des fichiers : " + e.getMessage()); } + return classFiles; } + + /** + * Construit le nom complet de la classe en fonction du package et du chemin du fichier. + */ + private static String buildClassName(String basePackage, File baseDirectory, File classFile) { + String relativePath = classFile.getAbsolutePath().substring(baseDirectory.getAbsolutePath().length() + 1); + String className = relativePath.replace(File.separator, ".").replace(".class", ""); + + // ✅ Si basePackage est vide, ne pas ajouter un point + return basePackage.isEmpty() ? className : basePackage + (className.isEmpty() ? "" : "." + className); + } + + } diff --git a/src/Utils/Requestparam.java b/src/Utils/Requestparam.java index fce4499..16e647b 100644 --- a/src/Utils/Requestparam.java +++ b/src/Utils/Requestparam.java @@ -70,7 +70,7 @@ private ArrayList collectObjectParameters(Parameter param, String name) if (valueNameParts.length > 1 && valueNameParts[0].equals(name)) { listOfParamObj.add(paramName); - } else if (param.isAnnotationPresent(Param.class) && valueNameParts[0].equals(param.getAnnotation(Param.class).name())) { + } else if (param.isAnnotationPresent(Param.class) && valueNameParts[0].equals(param.getAnnotation(Param.class).name()) && valueNameParts.length > 1) { listOfParamObj.add(paramName); } } @@ -88,6 +88,9 @@ private Method findSetterMethod(Method[] methods, String fieldName) { } private Object castValue(String value, Class paramType) { + if (value == null) { + return null; + } if (value.isEmpty()){ return null; } @@ -114,12 +117,11 @@ private Object castValue(String value, Class paramType) { private Object getParameterValue(Parameter param, String name) { // Check if the parameter exists in the request if (request.getParameter(name) != null) { - return request.getParameter(name); + return castValue(request.getParameter(name),param.getType()); } - // Handle annotated parameter if (param.isAnnotationPresent(Param.class)) { String paramName = param.getAnnotation(Param.class).name(); - return request.getParameter(paramName); + return castValue(request.getParameter(paramName),param.getType()); } return null; diff --git a/src/Utils/UploadFile/UploadFile.java b/src/Utils/UploadFile/UploadFile.java index 6b3deb8..9f5b2fc 100644 --- a/src/Utils/UploadFile/UploadFile.java +++ b/src/Utils/UploadFile/UploadFile.java @@ -50,18 +50,22 @@ public static String extractFileName(Part part) { /** * Saves an uploaded file to the specified directory + * * @param uploadDir The directory to save the file to do not put just / or \ as it will save to the root directory put a name of directory or.. * the racine is tomcat + * @param fileName The name of the file to save you can make it null or empty if you want to keep the original name * @return The path of the saved file * @throws IOException if file operations fail - * @throws Exception if validation fails + * @throws Exception if validation fails */ - public String saveFile(String uploadDir) throws Exception { + public String saveFile(String uploadDir, String fileName) throws Exception { Path rootPath = (uploadDir == null || uploadDir.isEmpty()) ? Paths.get("").toAbsolutePath().resolve("uploads") : Paths.get(uploadDir).toAbsolutePath(); - String fileName = extractFileName(this.getPart()); + if (fileName == null || fileName.isEmpty()) { + fileName = extractFileName(this.getPart()); + } if (fileName == null || fileName.isEmpty()) { throw new Exception("File does not have a name"); } @@ -81,6 +85,7 @@ public String saveFile(String uploadDir) throws Exception { /** * Gets the byte array of an uploaded file + * * @return byte array containing the file data */ public byte[] getBytes() throws IOException { diff --git a/src/error/ErrorHandler.java b/src/error/ErrorHandler.java index 8d5fb91..70e9a23 100644 --- a/src/error/ErrorHandler.java +++ b/src/error/ErrorHandler.java @@ -3,242 +3,335 @@ import com.google.gson.Gson; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; + import java.io.IOException; import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; public class ErrorHandler { private static final Gson gson = new Gson(); - private static String buildErrorPage(Exception e, int statusCode) { - LocalDateTime now = LocalDateTime.now(); - String formattedDate = now.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); - - return """ - - - - - - Error %d - Server Error - - - -
-
-
- ⚠️ -
-

Server Error

-
- -
- Error %d -
- -
- Error Message: %s -
- -
-

Technical Details

-

An unexpected error occurred while processing your request. Our team has been notified and is working to resolve the issue.

-

Error Type: %s

-
- - - -
- Error occurred at: %s -
-
- - - """.formatted( - statusCode, - statusCode, - e.getMessage(), - e.getClass().getSimpleName(), - formattedDate - ); - } - public static void handleException(HttpServletRequest req, HttpServletResponse res, Exception e) throws IOException { int statusCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR; res.setStatus(statusCode); res.setContentType("text/html"); res.setCharacterEncoding("UTF-8"); - String errorPage = buildErrorPage(e, statusCode); + String errorPage = buildErrorPage(req, e, statusCode); res.getWriter().println(errorPage); - e.printStackTrace(res.getWriter()); } - public static void handleRestApiException(HttpServletResponse res, Exception e) throws IOException { + public static void handleRestApiException(HttpServletRequest req, HttpServletResponse res, Exception e) throws IOException { int statusCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR; res.setStatus(statusCode); res.setContentType("application/json"); res.setCharacterEncoding("UTF-8"); Map errorResponse = new HashMap<>(); - Map errorDetails = new HashMap<>(); + errorResponse.put("error", buildErrorDetails(req, e, statusCode)); - // Enhanced error details - errorDetails.put("timestamp", LocalDateTime.now().toString()); - errorDetails.put("status", statusCode); - errorDetails.put("error", "Internal Server Error"); - errorDetails.put("message", e.getMessage()); - errorDetails.put("type", e.getClass().getSimpleName()); - errorDetails.put("path", res.getHeader("X-Original-URL")); + try (PrintWriter out = res.getWriter()) { + out.print(gson.toJson(errorResponse)); + } + } + + private static String buildErrorPage(HttpServletRequest req, Exception e, int statusCode) { + LocalDateTime now = LocalDateTime.now(); + String formattedDate = now.format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss")); + String stackTrace = getFilteredStackTrace(e); + boolean isDevMode = isDevelopmentMode(); + String requestId = generateRequestId(); + + // Définir stackTraceDiv pour afficher ou non la stack trace selon le mode de développement + String stackTraceDiv = isDevMode + ? "

Stack Trace

" + escapeHtml(stackTrace) + "
" + : ""; + + return String.format(""" + + + + + + Erreur %d + + + +
+
+
⚠️
+

Erreur du serveur

+
+ +
+
+ Code : %d +
+
+ Date : %s +
+
+ Requête ID : %s +
+
+ +
+

Message d'erreur

+

%s

+
+ + %s + +
+ Retour à l'accueil + + Copié ! +
+
+ + + + + """, + statusCode, // %d (titre) + statusCode, // %d (bloc meta - Code) + formattedDate, // %s (bloc meta - Date) + requestId, // %s (bloc meta - Requête ID) + escapeHtml(e.getMessage()), // %s (bloc meta - Message d'erreur) + stackTraceDiv, // %s (bloc stack trace) + statusCode, // %d (script - Code d'erreur) + formattedDate, // %s (script - Date) + requestId, // %s (script - ID Requête) + escapeJs(e.getMessage()), // %s (script - Message) + escapeJs(e.getClass().getSimpleName()), // %s (script - Type) + stackTrace // %s (script - StackTrace) + ); + } + + + private static Map buildErrorDetails(HttpServletRequest req, Exception e, int statusCode) { + Map details = new HashMap<>(); + details.put("timestamp", LocalDateTime.now().toString()); + details.put("status", statusCode); + details.put("error", e.getClass().getSimpleName()); + details.put("message", e.getMessage()); + details.put("path", req.getRequestURI()); + details.put("requestId", generateRequestId()); if (isDevelopmentMode()) { - String stackTrace = String.valueOf(java.util.Arrays.stream(e.getStackTrace()) + details.put("stackTrace", Arrays.stream(e.getStackTrace()) .map(StackTraceElement::toString) - .limit(5) - .toList()); - errorDetails.put("stackTrace", stackTrace); + .collect(Collectors.toList())); } - errorResponse.put("error", errorDetails); + return details; + } + + private static String getFilteredStackTrace(Exception e) { + Logger.getLogger(ErrorHandler.class.getName()).log(Level.SEVERE, null, e); + return getFullStackTrace(e); + } + public static String getFullStackTrace(Throwable e) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); // Capture le même output que printStackTrace() + return sw.toString(); + } + + + private static String generateRequestId() { + return Long.toHexString(System.currentTimeMillis()) + "-" + + (int) (Math.random() * 1000); + } + + private static String escapeHtml(String input) { + if (input == null) return ""; + return input.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("\"", """) + .replace("'", "'"); + } - PrintWriter out = res.getWriter(); - out.print(gson.toJson(errorResponse)); - out.flush(); + private static String escapeJs(String input) { + if (input == null) return ""; + return input.replace("\\", "\\\\") + .replace("'", "\\'") + .replace("\"", "\\\"") + .replace("\n", "\\n"); } private static boolean isDevelopmentMode() { - return System.getProperty("app.environment", "development") - .equalsIgnoreCase("development"); + return true; +// return System.getProperty("app.environment", "development") +// .equalsIgnoreCase("development"); } }