diff --git a/src/main/java/DNAnalyzer/DNAnalyzerApplication.java b/src/main/java/DNAnalyzer/DNAnalyzerApplication.java index f7769945..c4ccf0ee 100644 --- a/src/main/java/DNAnalyzer/DNAnalyzerApplication.java +++ b/src/main/java/DNAnalyzer/DNAnalyzerApplication.java @@ -1,50 +1,52 @@ package DNAnalyzer; +import java.util.Map; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.boot.web.servlet.error.DefaultErrorAttributes; import org.springframework.boot.web.servlet.error.ErrorAttributes; +import org.springframework.context.annotation.Bean; import org.springframework.web.context.request.WebRequest; -import java.util.Map; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** - * Spring Boot Application configuration for DNAnalyzer web server. - * Handles web-specific configuration like CORS settings. + * Spring Boot Application configuration for DNAnalyzer web server. Handles web-specific + * configuration like CORS settings. */ @SpringBootApplication public class DNAnalyzerApplication { - /** - * Configure CORS for the web server. - * Allows web interface to communicate with the local analyzer. - */ - @Bean - public WebMvcConfigurer corsConfigurer() { - return new WebMvcConfigurer() { - @Override - public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/api/**") - .allowedOrigins("*") - .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") - .allowedHeaders("*") - .maxAge(3600); - } - }; - } + /** + * Configure CORS for the web server. Allows web interface to communicate with the local analyzer. + */ + @Bean + public WebMvcConfigurer corsConfigurer() { + return new WebMvcConfigurer() { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry + .addMapping("/api/**") + .allowedOrigins("*") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*") + .maxAge(3600); + } + }; + } - @Bean - public ErrorAttributes errorAttributes() { - return new DefaultErrorAttributes() { - @Override - public Map getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { - Map errorAttributes = super.getErrorAttributes(webRequest, includeStackTrace); - errorAttributes.put("status", errorAttributes.get("status")); - errorAttributes.put("error", errorAttributes.get("error")); - errorAttributes.put("message", errorAttributes.get("message")); - return errorAttributes; - } - }; - } + @Bean + public ErrorAttributes errorAttributes() { + return new DefaultErrorAttributes() { + @Override + public Map getErrorAttributes( + WebRequest webRequest, boolean includeStackTrace) { + Map errorAttributes = + super.getErrorAttributes(webRequest, includeStackTrace); + errorAttributes.put("status", errorAttributes.get("status")); + errorAttributes.put("error", errorAttributes.get("error")); + errorAttributes.put("message", errorAttributes.get("message")); + return errorAttributes; + } + }; + } } diff --git a/src/main/java/DNAnalyzer/web/AnalyzerController.java b/src/main/java/DNAnalyzer/web/AnalyzerController.java index 792777f7..fce767df 100644 --- a/src/main/java/DNAnalyzer/web/AnalyzerController.java +++ b/src/main/java/DNAnalyzer/web/AnalyzerController.java @@ -2,261 +2,262 @@ import DNAnalyzer.core.DNAAnalysis; import DNAnalyzer.utils.core.DNATools; +import java.io.BufferedReader; import java.io.ByteArrayOutputStream; +import java.io.FileReader; import java.io.PrintStream; import java.nio.file.Files; import java.nio.file.Path; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; -import java.util.Map; -import java.io.BufferedReader; -import java.io.FileReader; -import java.util.HashMap; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import org.json.JSONObject; -import org.json.JSONArray; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; @RestController @RequestMapping("/api") @CrossOrigin(origins = "*") public class AnalyzerController { - @PostMapping(value = "/analyze", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity analyzeDNA( - @RequestParam("dnaFile") MultipartFile dnaFile, - @RequestParam(value = "amino", defaultValue = "M") String amino, - @RequestParam(value = "minCount", defaultValue = "1") int minCount, - @RequestParam(value = "maxCount", defaultValue = "100") int maxCount, - @RequestParam(value = "reverse", defaultValue = "false") boolean reverse, - @RequestParam(value = "rcomplement", defaultValue = "false") boolean rcomplement, - @RequestParam(value = "codons", defaultValue = "false") boolean codons, - @RequestParam(value = "coverage", defaultValue = "false") boolean coverage, - @RequestParam(value = "longest", defaultValue = "false") boolean longest, - @RequestParam(value = "format", defaultValue = "text") String format) { - - try { - // Validate file - if (dnaFile.isEmpty()) { - return ResponseEntity.badRequest().body(Map.of( + @PostMapping(value = "/analyze", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity analyzeDNA( + @RequestParam("dnaFile") MultipartFile dnaFile, + @RequestParam(value = "amino", defaultValue = "M") String amino, + @RequestParam(value = "minCount", defaultValue = "1") int minCount, + @RequestParam(value = "maxCount", defaultValue = "100") int maxCount, + @RequestParam(value = "reverse", defaultValue = "false") boolean reverse, + @RequestParam(value = "rcomplement", defaultValue = "false") boolean rcomplement, + @RequestParam(value = "codons", defaultValue = "false") boolean codons, + @RequestParam(value = "coverage", defaultValue = "false") boolean coverage, + @RequestParam(value = "longest", defaultValue = "false") boolean longest, + @RequestParam(value = "format", defaultValue = "text") String format) { + + try { + // Validate file + if (dnaFile.isEmpty()) { + return ResponseEntity.badRequest() + .body( + Map.of( "error", "File is empty", - "message", "Please provide a valid DNA sequence file" - )); - } - - // Validate file size - long maxSize = 50 * 1024 * 1024; // 50MB - if (dnaFile.getSize() > maxSize) { - return ResponseEntity.badRequest().body(Map.of( + "message", "Please provide a valid DNA sequence file")); + } + + // Validate file size + long maxSize = 50 * 1024 * 1024; // 50MB + if (dnaFile.getSize() > maxSize) { + return ResponseEntity.badRequest() + .body( + Map.of( "error", "File too large", - "message", "File size must be less than 50MB" - )); - } - - // Create temporary file - Path tempFile = Files.createTempFile("dna-", ".fa"); - dnaFile.transferTo(tempFile.toFile()); - - // Capture console output - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - PrintStream ps = new PrintStream(baos); - PrintStream old = System.out; - System.setOut(ps); - - try { - // Read DNA sequence - String dnaSequence = new String(Files.readAllBytes(tempFile)); - - // Validate DNA sequence - if (!isValidDNASequence(dnaSequence)) { - return ResponseEntity.badRequest().body(Map.of( - "error", "Invalid sequence", - "message", "File contains invalid DNA sequence characters" - )); - } - - // Run analysis - DNAAnalysis analysis = new DNAAnalysis(new DNATools(dnaSequence), null, amino); - - if (reverse) { - analysis = analysis.reverseDna(); - } - if (rcomplement) { - analysis = analysis.reverseComplement(); - } - - if (codons) { - analysis.outPutCodons(minCount, maxCount, System.out); - } - if (coverage) { - analysis.printHighCoverageRegions(System.out); - } - if (longest) { - analysis.printLongestProtein(System.out); - } - - // Get output - String output = baos.toString(); - - // Format output based on request - if ("json".equals(format)) { - output = formatAsJson(output); - } else if ("csv".equals(format)) { - output = formatAsCsv(output); - } - - return ResponseEntity.ok() - .contentType("json".equals(format) ? MediaType.APPLICATION_JSON : - "csv".equals(format) ? MediaType.TEXT_PLAIN : - MediaType.TEXT_PLAIN) - .body(output); - - } finally { - // Restore console output and cleanup - System.setOut(old); - Files.deleteIfExists(tempFile); - } - - } catch (Exception e) { - return ResponseEntity.badRequest().body(Map.of( - "error", "Analysis failed", - "message", e.getMessage() - )); + "message", "File size must be less than 50MB")); + } + + // Create temporary file + Path tempFile = Files.createTempFile("dna-", ".fa"); + dnaFile.transferTo(tempFile.toFile()); + + // Capture console output + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream ps = new PrintStream(baos); + PrintStream old = System.out; + System.setOut(ps); + + try { + // Read DNA sequence + String dnaSequence = new String(Files.readAllBytes(tempFile)); + + // Validate DNA sequence + if (!isValidDNASequence(dnaSequence)) { + return ResponseEntity.badRequest() + .body( + Map.of( + "error", "Invalid sequence", + "message", "File contains invalid DNA sequence characters")); } - } - private boolean isValidDNASequence(String sequence) { - // Remove whitespace and newlines - sequence = sequence.replaceAll("\\s+", "").toUpperCase(); - // Check if sequence contains only valid DNA characters - return sequence.matches("^[ATCG]+$"); - } + // Run analysis + DNAAnalysis analysis = new DNAAnalysis(new DNATools(dnaSequence), null, amino); + + if (reverse) { + analysis = analysis.reverseDna(); + } + if (rcomplement) { + analysis = analysis.reverseComplement(); + } + + if (codons) { + analysis.outPutCodons(minCount, maxCount, System.out); + } + if (coverage) { + analysis.printHighCoverageRegions(System.out); + } + if (longest) { + analysis.printLongestProtein(System.out); + } - private String formatAsJson(String output) { - StringBuilder json = new StringBuilder(); - json.append("{\"results\": ["); + // Get output + String output = baos.toString(); - String[] lines = output.split("\n"); - for (int i = 0; i < lines.length; i++) { - json.append("\"").append(lines[i].replace("\"", "\\\"")).append("\""); - if (i < lines.length - 1) { - json.append(","); - } + // Format output based on request + if ("json".equals(format)) { + output = formatAsJson(output); + } else if ("csv".equals(format)) { + output = formatAsCsv(output); } - json.append("]}"); - return json.toString(); + return ResponseEntity.ok() + .contentType( + "json".equals(format) + ? MediaType.APPLICATION_JSON + : "csv".equals(format) ? MediaType.TEXT_PLAIN : MediaType.TEXT_PLAIN) + .body(output); + + } finally { + // Restore console output and cleanup + System.setOut(old); + Files.deleteIfExists(tempFile); + } + + } catch (Exception e) { + return ResponseEntity.badRequest() + .body(Map.of("error", "Analysis failed", "message", e.getMessage())); } + } - private String formatAsCsv(String output) { - StringBuilder csv = new StringBuilder(); - String[] lines = output.split("\n"); - - for (String line : lines) { - // Replace any commas in the data with semicolons - line = line.replace(",", ";"); - // Add quotes around fields containing semicolons - if (line.contains(";")) { - line = "\"" + line + "\""; - } - csv.append(line).append("\n"); - } + private boolean isValidDNASequence(String sequence) { + // Remove whitespace and newlines + sequence = sequence.replaceAll("\\s+", "").toUpperCase(); + // Check if sequence contains only valid DNA characters + return sequence.matches("^[ATCG]+$"); + } + + private String formatAsJson(String output) { + StringBuilder json = new StringBuilder(); + json.append("{\"results\": ["); - return csv.toString(); + String[] lines = output.split("\n"); + for (int i = 0; i < lines.length; i++) { + json.append("\"").append(lines[i].replace("\"", "\\\"")).append("\""); + if (i < lines.length - 1) { + json.append(","); + } } - @PostMapping(value = "/analyze-genetic", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity analyzeGeneticData( - @RequestParam("geneticFile") MultipartFile geneticFile, - @RequestParam(value = "snpAnalysis", defaultValue = "false") boolean snpAnalysis) { - - try { - // Validate file - if (geneticFile.isEmpty()) { - return ResponseEntity.badRequest().body(Map.of( + json.append("]}"); + return json.toString(); + } + + private String formatAsCsv(String output) { + StringBuilder csv = new StringBuilder(); + String[] lines = output.split("\n"); + + for (String line : lines) { + // Replace any commas in the data with semicolons + line = line.replace(",", ";"); + // Add quotes around fields containing semicolons + if (line.contains(";")) { + line = "\"" + line + "\""; + } + csv.append(line).append("\n"); + } + + return csv.toString(); + } + + @PostMapping(value = "/analyze-genetic", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity analyzeGeneticData( + @RequestParam("geneticFile") MultipartFile geneticFile, + @RequestParam(value = "snpAnalysis", defaultValue = "false") boolean snpAnalysis) { + + try { + // Validate file + if (geneticFile.isEmpty()) { + return ResponseEntity.badRequest() + .body( + Map.of( "error", "File is empty", - "message", "Please provide a valid genetic testing data file" - )); - } - - // Validate file size - long maxSize = 50 * 1024 * 1024; // 50MB - if (geneticFile.getSize() > maxSize) { - return ResponseEntity.badRequest().body(Map.of( + "message", "Please provide a valid genetic testing data file")); + } + + // Validate file size + long maxSize = 50 * 1024 * 1024; // 50MB + if (geneticFile.getSize() > maxSize) { + return ResponseEntity.badRequest() + .body( + Map.of( "error", "File too large", - "message", "File size must be less than 50MB" - )); - } - - // Create temporary file - Path tempFile = Files.createTempFile("genetic-", ".txt"); - geneticFile.transferTo(tempFile.toFile()); - - try { - // Process genetic data - Map results = processGeneticData(tempFile, snpAnalysis); - return ResponseEntity.ok() - .contentType(MediaType.APPLICATION_JSON) - .body(new JSONObject(results).toString()); - - } finally { - Files.deleteIfExists(tempFile); - } - - } catch (Exception e) { - return ResponseEntity.badRequest().body(Map.of( - "error", "Analysis failed", - "message", e.getMessage() - )); - } + "message", "File size must be less than 50MB")); + } + + // Create temporary file + Path tempFile = Files.createTempFile("genetic-", ".txt"); + geneticFile.transferTo(tempFile.toFile()); + + try { + // Process genetic data + Map results = processGeneticData(tempFile, snpAnalysis); + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_JSON) + .body(new JSONObject(results).toString()); + + } finally { + Files.deleteIfExists(tempFile); + } + + } catch (Exception e) { + return ResponseEntity.badRequest() + .body(Map.of("error", "Analysis failed", "message", e.getMessage())); } + } + + private Map processGeneticData(Path filePath, boolean includeSnpAnalysis) + throws IOException { + Map results = new HashMap<>(); + List> snps = new ArrayList<>(); + Map chromosomeDistribution = new HashMap<>(); + + try (BufferedReader reader = new BufferedReader(new FileReader(filePath.toFile()))) { + String line; + int totalSnps = 0; - private Map processGeneticData(Path filePath, boolean includeSnpAnalysis) throws IOException { - Map results = new HashMap<>(); - List> snps = new ArrayList<>(); - Map chromosomeDistribution = new HashMap<>(); - - try (BufferedReader reader = new BufferedReader(new FileReader(filePath.toFile()))) { - String line; - int totalSnps = 0; - - while ((line = reader.readLine()) != null) { - // Skip comments and headers - if (line.startsWith("#") || line.trim().isEmpty()) { - continue; - } - - String[] parts = line.split("\t"); - if (parts.length >= 4) { - totalSnps++; - - // Count chromosome distribution - String chromosome = parts[1]; - chromosomeDistribution.merge(chromosome, 1, Integer::sum); - - // Collect SNP data if requested - if (includeSnpAnalysis) { - Map snp = new HashMap<>(); - snp.put("rsid", parts[0]); - snp.put("chromosome", parts[1]); - snp.put("position", parts[2]); - snp.put("genotype", parts[3]); - snps.add(snp); - } - } - } - - // Build results - results.put("totalSnps", totalSnps); - results.put("chromosomeDistribution", chromosomeDistribution); - if (includeSnpAnalysis) { - results.put("snps", snps); - } + while ((line = reader.readLine()) != null) { + // Skip comments and headers + if (line.startsWith("#") || line.trim().isEmpty()) { + continue; } - - return results; + + String[] parts = line.split("\t"); + if (parts.length >= 4) { + totalSnps++; + + // Count chromosome distribution + String chromosome = parts[1]; + chromosomeDistribution.merge(chromosome, 1, Integer::sum); + + // Collect SNP data if requested + if (includeSnpAnalysis) { + Map snp = new HashMap<>(); + snp.put("rsid", parts[0]); + snp.put("chromosome", parts[1]); + snp.put("position", parts[2]); + snp.put("genotype", parts[3]); + snps.add(snp); + } + } + } + + // Build results + results.put("totalSnps", totalSnps); + results.put("chromosomeDistribution", chromosomeDistribution); + if (includeSnpAnalysis) { + results.put("snps", snps); + } } + + return results; + } }