package es.uv.saic.service; import java.io.IOException; import java.io.InputStreamReader; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.sql.rowset.CachedRowSet; import javax.sql.rowset.RowSetFactory; import javax.sql.rowset.RowSetProvider; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.LinkedCaseInsensitiveMap; import es.uv.saic.shared.domain.Datasource; import es.uv.saic.shared.domain.IndicadorEnquestaTmp; import es.uv.saic.shared.domain.Usuari; import es.uv.saic.shared.dto.EmailDTO; import es.uv.saic.shared.dto.IndicadorEnquestaTmpDup; import es.uv.saic.shared.dto.OrganDTO; import es.uv.saic.shared.feign.EmailClient; import es.uv.saic.shared.feign.OrganClient; import jakarta.mail.MessagingException; @Service public class DataService { @Autowired private DatasourceService dss; @Autowired private UsuariService us; @Autowired private IndicadorEnquestaService ies; @Autowired private IndicadorEnquestaTmpService iets; @Autowired private OrganClient oc; @Autowired private EmailClient ec; private HashMap titsMap; private HashMap centresMap; public DataService() { titsMap = new HashMap(); titsMap.put("TRAD", "1099"); centresMap = new HashMap(); centresMap.put("INDV", "121"); centresMap.put("POST", "121"); centresMap.put("EDOC", "62"); } public String doImport(InputStreamReader in, String usuari, String enquesta, String ambit, String estudi, String locale, String delim, String clau, List ignoredColumns) throws IOException, MessagingException { CSVFormat format = CSVFormat.DEFAULT.builder() .setHeader() .setIgnoreHeaderCase(true) .setSkipHeaderRecord(true) .setIgnoreEmptyLines(true) .setIgnoreSurroundingSpaces(true) .setDelimiter(delim) .setRecordSeparator('\n') .build(); CSVParser parser = format.parse(in); List records = parser.getRecords(); List header = parser.getHeaderNames(); List headerInds = new ArrayList<>(header); StringBuilder errors = new StringBuilder(); /* 1) Comprobar columnas obligatorias */ List mandatoryColumns = new ArrayList(); List knownColumns = new ArrayList(); if(clau.equals("ruct")) { this.setColumnsRuct(ambit, knownColumns, mandatoryColumns); } else { this.setColumnsCod(ambit, knownColumns, mandatoryColumns); } if(!this.checkColumns(header, mandatoryColumns, errors, locale)) { return errors.append("[ERROR] " + (locale.equals("es") ? "Se han encontrado errores en el fichero proporcionado. No se ha importado ningún registro. " : "S'han trobat errors al fitxer proporcionat. No s'ha importat cap registre.")).append("
").toString(); } /* 2) Eliminar las columnas obligatorias, conocidas e ignoradas. El resto se consideran indicadores a importar */ knownColumns.addAll(mandatoryColumns); knownColumns.addAll(ignoredColumns); for(String c : knownColumns) { headerInds.removeIf(v->v.equalsIgnoreCase(c)); } /* 3) Comprobar valores y tipos */ if(!this.checkValues(records, ambit, clau, errors, locale)) { return errors.append(("[ERROR] " + (locale.equals("es") ? "Se han encontrado errores en el fichero proporcionado. No se ha importado ningún registro. " : "S'han trobat errors al fitxer proporcionat. No s'ha importat cap registre."))).append("
").toString(); } /* 4) Importar datos */ Integer numRecords = 0; Integer numTuples = 0; for (CSVRecord record : records) { Map recordMap = new LinkedCaseInsensitiveMap<>(); recordMap.putAll(record.toMap()); String c = ambit.equals("T") || ambit.equals("C") ? translateCentre(recordMap.get("centre")) : null; String t = ambit.equals("T") ? translateTitulacio(recordMap.get("titulacio")) : null; String type = recordMap.get("tipus"); Integer curs = Integer.parseInt(recordMap.get("curs").replaceAll("\\D+","")); String cursEnquesta = recordMap.containsKey("curs_enquesta") ? recordMap.get("curs_enquesta") : null; String titulacioOrigen = recordMap.containsKey("titulacio_origen") ? recordMap.get("titulacio_origen") : null; String centreOrigen = recordMap.containsKey("centre_origen") ? recordMap.get("centre_origen") : null; Integer nenq = recordMap.containsKey("nenq") ? Integer.parseInt(recordMap.get("nenq").replaceAll("\\D+","")) : null; String ructStr = recordMap.containsKey("ruct") ? recordMap.get("ruct").replaceAll("\\D+","") : null; String cursd = recordMap.containsKey("cursd") ? recordMap.get("cursd") : null; Integer ruct = null; if(ructStr != null) { if(!(ructStr.isBlank() || ructStr.isEmpty() || ructStr.equals("NULL") || ructStr.equals("null"))) { ruct = Integer.parseInt(ructStr); } } Integer centre; Integer titulacio; if(clau.equals("ruct") && ambit.equals("T") && ruct != null) { OrganDTO o = findOrgByRuct(ruct); if(o == null) { errors.append("[WARNING] " + (locale.equals("es") ? "No se ha encontrado el RUCT "+ructStr+", se omiten los registros de esta entrada." : "No s'ha trobat el RUCT "+ructStr+", s'ometen els registres d'aquesta entrada.")).append("
").toString(); continue; } titulacio = o.getLugar(); centre = o.getLugar2(); } else{ centre = ambit.equals("T") || ambit.equals("C") ? Integer.parseInt(c.replaceAll("\\D+","")) : null; titulacio = ambit.equals("T") ? Integer.parseInt(t) : null; if(!existsOrg("T", titulacio)){ errors.append("[WARNING] " + (locale.equals("es") ? "No se ha encontrado la titulación con código "+titulacio+" pero se importa igualmente." : "No s'ha trobat la titulació amb codi "+titulacio+" però s'importa igualment")).append("
").toString(); } } if(header.contains("cursd")) { cursd = recordMap.get("cursd").replaceAll("\\D+",""); } for(String r : headerInds) { IndicadorEnquestaTmp ie = new IndicadorEnquestaTmp(); ie.setEnquesta(enquesta); ie.setTitulacio(titulacio); ie.setCentre(centre); ie.setCurs(curs); ie.setAmbit(ambit); ie.setEstudi(estudi); ie.setIndicador(r.replace("_min", "").replace("_max", "").toLowerCase()); ie.setNum(null); ie.setCursd(cursd); ie.setTipus(type); ie.setUsuari(usuari); ie.setData(LocalDateTime.now()); ie.setCursEnquesta(cursEnquesta); ie.setTitulacioOrigen(titulacioOrigen); ie.setCentreOrigen(centreOrigen); ie.setNenq(nenq); ie.setRuct(ruct); try { ie.setValor(record.get(r).replace(",", ".")); } catch(Exception ex) { ie.setValor(null); } save(ie); numRecords++; } numTuples++; } /* 5) Notificar administradores y usuario implicado */ this.sendNotificacion(usuari, enquesta, ambit, estudi); Usuari usuario = us.findByUsername(usuari); if(!usuario.isAdmin()){ this.sendConfirmation(usuari, enquesta, ambit, estudi); } errors.append("[INFO] " + (locale.equals("es") ? ("Se han importado "+numRecords.toString()+" nuevos registros para un total de "+numTuples.toString()+" titulaciones") : ("[INFO] S'han importat "+numRecords.toString()+" nous registres per a un total de "+numTuples.toString()+" titulacions"))); return errors.toString(); } public String doDbImport(Integer dbOrigen, String vista, Integer srcCurs, Integer dstCurs, String usuari, String enquesta, String ambit, String estudi, String locale, String clau, List ignoredColumns) throws SQLException, ClassNotFoundException { StringBuilder errors = new StringBuilder(); Datasource source = this.dss.findById(dbOrigen); if(source == null){ return errors.append("[ERROR] " + (locale.equals("es") ? "No se ha proporcionado un origen de datos válido." : "No s'ha proporcionat un origen de dades vàlid.")).append("
").toString(); } String query = "SELECT * FROM "+vista+ " WHERE curs = "+srcCurs; Class.forName(source.getDriver()); Integer numRecords = 0; Integer numTuples = 0; try (Connection connection = DriverManager.getConnection(source.getConn())) { /* 1) Ejecutar consulta */ Statement statement = connection.createStatement(); ResultSet resultset = statement.executeQuery(query); ResultSetMetaData resultMetadata = resultset.getMetaData(); int columns = resultMetadata.getColumnCount(); /* 2) Obtener resultados para procesar online y cerrar conexión */ RowSetFactory factory = RowSetProvider.newFactory(); CachedRowSet result = factory.createCachedRowSet(); result.populate(resultset); connection.close(); /* 3) Comprobar columnas obligatorias */ List mandatoryColumns = new ArrayList(); List knownColumns = new ArrayList(); if(clau.equals("ruct")) { this.setColumnsRuct(ambit, knownColumns, mandatoryColumns); } else { this.setColumnsCod(ambit, knownColumns, mandatoryColumns); } List allColNames = new ArrayList(); for(int i = 1; i <= columns; i++){ allColNames.add(resultMetadata.getColumnLabel(i).toLowerCase()); } List colnames = new ArrayList(allColNames); if(!this.checkColumns(colnames, mandatoryColumns, errors, locale)) { return errors.append("[ERROR] " + (locale.equals("es") ? "Se han encontrado errores al procesar la vista seleccionada. No se ha importado ningún registro. " : "S'han trobat errors al processar la vista seleccionada. No s'ha importat cap registre.")).append("
").toString(); } knownColumns.addAll(mandatoryColumns); knownColumns.addAll(ignoredColumns); for(String c : knownColumns) { colnames.removeIf(v->v.equalsIgnoreCase(c)); } if(ambit.equals("T") && !clau.equals("ruct")){ return errors.append("[ERROR] " + (locale.equals("es") ? "Los datos de titulación deben de importarse por código RUCT" : "Les dades de titulació s'han d'importar per códi RUCT.")).append("
").toString(); } /* 4) Importar datos */ while(result.next()) { Integer ruct = null; Integer titulacio = null; Integer centre = null; String c = ambit.equals("C") ? translateCentre(result.getString("centre")) : null; boolean isValid = true; if(ambit.equals("T")) { String ructStr = result.getString("ruct"); if(ructStr != null) { if(!(ructStr.isBlank() || ructStr.isEmpty() || ructStr.equals("NULL") || ructStr.equals("null"))) { ruct = Integer.parseInt(ructStr); } else{ isValid = false; } } else{ isValid = false; } OrganDTO o = findOrgByRuct(ruct); if(o == null) { errors.append("[WARNING] " + (locale.equals("es") ? "No se ha encontrado el RUCT "+ructStr+", se omiten los registros de esta entrada." : "No s'ha trobat el RUCT "+ructStr+", s'ometen els registres d'aquesta entrada.")).append("
").toString(); continue; } titulacio = o.getLugar(); centre = o.getLugar2(); } else if(ambit.equals("C")) { centre = ambit.equals("T") || ambit.equals("C") ? Integer.parseInt(c.replaceAll("\\D+","")) : null; if(centre == null){ isValid = false; } } if(isValid) { Integer curs = dstCurs == null ? result.getInt("curs") : dstCurs; String tipus = allColNames.contains("tipus") ? result.getString("tipus") : "avg"; String cursEnquesta = allColNames.contains("curs_enquesta") ? result.getString("curs_enquesta") : null; String titulacioOrigen = allColNames.contains("titulacio_origen") ? result.getString("titulacio_origen") : null; String centreOrigen = allColNames.contains("centre_origen") ? result.getString("centre_origen") : null; Integer nenq = allColNames.contains("nenq") ? result.getInt("nenq") : null; String cursd = allColNames.contains("cursd") ? result.getString("cursd") : null; for(String colName : colnames) { IndicadorEnquestaTmp ie = new IndicadorEnquestaTmp(); ie.setEnquesta(enquesta); ie.setTitulacio(titulacio); ie.setCentre(centre); ie.setCurs(curs); ie.setAmbit(ambit); ie.setEstudi(estudi); ie.setIndicador(colName.replace("_min", "").replace("_max", "").toLowerCase()); ie.setNum(null); ie.setCursd(cursd); ie.setTipus(tipus); ie.setUsuari(usuari); ie.setData(LocalDateTime.now()); ie.setCursEnquesta(cursEnquesta); ie.setTitulacioOrigen(titulacioOrigen); ie.setCentreOrigen(centreOrigen); ie.setNenq(nenq); ie.setRuct(ruct); try { ie.setValor(result.getString(colName).replace(",", ".")); } catch(Exception ex) { ie.setValor(null); } save(ie); numRecords++; } numTuples++; } } } /* 5) Notificar administradores y usuario implicado */ this.sendNotificacion(usuari, enquesta, ambit, estudi); Usuari usuario = us.findByUsername(usuari); if(!usuario.isAdmin()){ this.sendConfirmation(usuari, enquesta, ambit, estudi); } errors.append("[INFO] " + (locale.equals("es") ? ("Se han importado "+numRecords.toString()+" nuevos registros para un total de "+numTuples.toString()+" titulaciones") : ("[INFO] S'han importat "+numRecords.toString()+" nous registres per a un total de "+numTuples.toString()+" titulacions"))); return errors.toString(); } public List listTableColumns(Integer dbOrigen, String vista, String locale) throws SQLException, ClassNotFoundException { Datasource source = this.dss.findById(dbOrigen); String query = "SELECT * FROM "+vista+" LIMIT 1;"; Class.forName(source.getDriver()); List colNames = new ArrayList(); try (Connection connection = DriverManager.getConnection(source.getConn())) { Statement statement = connection.createStatement(); ResultSet resultset = statement.executeQuery(query); ResultSetMetaData resultMetadata = resultset.getMetaData(); int columns = resultMetadata.getColumnCount(); for(int i = 1; i <= columns; i++){ colNames.add(resultMetadata.getColumnLabel(i)); } } return colNames; } public List checkDuplicates(String enquesta) { return checkDuplicatesAux(enquesta); } public Integer consolidateByEnquesta(String enquesta) { if(checkDuplicates(enquesta).size() > 0) { return -1; } else { Integer i = ies.consolidateByEnquesta(enquesta); deleteByEnquesta(enquesta); return i; } } public Integer countByEnquesta(String enquesta) { return countByEnquestaAux(enquesta); } public List checkIntegrity(String enquesta){ return iets.checkIntegrity(enquesta); } public Integer deleteFromCurrent(String enquesta){ return iets.deleteFromCurrent(enquesta); } public Integer deleteFromPending(String enquesta){ return iets.deleteFromPending(enquesta); } private void setColumnsCod(String ambit, List knownColumns, List mandatoryColumns) { knownColumns.addAll(Arrays.asList("ruct", "curs_enquesta", "centre_origen", "titulacio_origen", "nenq", "cursd")); if(ambit.equals("U")) { mandatoryColumns.addAll(Arrays.asList("curs", "tipus")); } else if(ambit.equals("C")) { mandatoryColumns.addAll(Arrays.asList("curs", "centre", "tipus")); } else { mandatoryColumns.addAll(Arrays.asList("curs", "titulacio", "centre", "tipus")); } } private void setColumnsRuct(String ambit, List knownColumns, List mandatoryColumns) { knownColumns.addAll(Arrays.asList("curs_enquesta", "centre_origen", "titulacio_origen", "nenq", "cursd")); if(ambit.equals("U")) { mandatoryColumns.addAll(Arrays.asList("curs", "tipus")); } else if(ambit.equals("C")) { mandatoryColumns.addAll(Arrays.asList("curs", "tipus", "ruct")); knownColumns.add("centre"); } else { mandatoryColumns.addAll(Arrays.asList("curs", "tipus", "ruct")); knownColumns.addAll(Arrays.asList("centre", "titulacio")); } } private boolean checkColumns(List header, List mandatoryColumns, StringBuilder errors, String locale) { boolean isvalid = true; for(String c : mandatoryColumns) { if(!header.stream().anyMatch(c::equalsIgnoreCase)) { isvalid = false; errors.append((locale.equals("es") ? "[ERROR] No se ha encontrado la columna " : "[ERROR] No s'ha trobat la columna ") +c).append("
"); } } return isvalid; } private boolean checkValues(List records, String ambit, String clau, StringBuilder errors, String locale) { boolean isvalid = true; Integer numRecords = 0; for (CSVRecord record : records) { Map recordMap = new LinkedCaseInsensitiveMap<>(); recordMap.putAll(record.toMap()); String type = recordMap.get("tipus"); numRecords++; if((ambit.equals("T") || ambit.equals("C")) && !clau.equals("ruct")) { String c = recordMap.get("centre"); c = translateCentre(c); if(!StringUtils.isNumeric(c.replace("C", ""))) { isvalid = false; errors.append("[ERROR] " + (locale.equals("es") ? "En la linea " : "A la línia ") + numRecords.toString() + ": " + (locale.equals("es") ? c+" no es un valor válido para la columna centre." : c+" no es un valor vàlid per la columna centre.")).append("
"); } } if(ambit.equals("T")) { String t = recordMap.get("titulacio"); if(!clau.equals("ruct")) { t = translateTitulacio(t); if(!StringUtils.isNumeric(t)) { isvalid = false; errors.append("[ERROR] " + (locale.equals("es") ? "En la linea " : "A la línia ") + numRecords.toString() + ": " + (locale.equals("es") ? t+" no es un valor válido para la columna titulacio." : t+" no es un valor vàlid per la columna titulacio.")).append("
"); } } else { String ructStr = recordMap.get("ruct"); if(!ructStr.isBlank() && ructStr.isEmpty() && ructStr.equals("NULL") && ructStr.equals("null") && !StringUtils.isNumeric(t)) { isvalid = false; errors.append("[ERROR] " + (locale.equals("es") ? "En la linea " : "A la línia ") + numRecords.toString() + ": " + (locale.equals("es") ? ructStr+" no es un valor válido para la columna ruct." : ructStr+" no es un valor vàlid per la columna ruct.")).append("
"); } } } if(!type.equals("avg") && !type.equals("min") && !type.equals("max")) { isvalid = false; errors.append("[ERROR] " + (locale.equals("es") ? "En la linea " : "A la línia ") + numRecords.toString() + ": " + (locale.equals("es") ? type+" no es un valor válido para la columna tipus." : type+" no es un valor vàlid per la columna tipus.")).append("
"); } } return isvalid; } private void sendNotificacion(String usuari, String enquesta, String ambit, String estudi) { sendMail("saic@uv.es", "[SYS] Datos pendientes de consolidar", "Estimado/a administrador: \n" + "\n" + "El usuario "+usuari+" ha importado nuevos datos referentes a: \n" + "Origen: "+enquesta+"\n" + "Ámbito: "+ambit+"\n" + "Tipo titulación: "+estudi+"\n" + "\n" + "Acceda a https://saic.uv.es y realice las acciones pertinentes para consolidar los datos. "); } private void sendConfirmation(String to, String enquesta, String ambit, String estudi) { sendMail(to, "[SYS] Nuevos datos importados", "Estimado/a usuario: \n" + "\n" + "Se han importado correctamente nuevos datos referentes a: \n" + "Origen: "+enquesta+"\n" + "Ámbito: "+ambit+"\n" + "Tipo titulación: "+estudi+"\n" + "\n" + "Los datos se quedarán en estado pendiente de consolidar hasta que un administrador inicie el proceso de consolidación."); } /* Exepciones en el código de titulación que hay que traducir a código SAIC */ private String translateTitulacio(String t) { if(!StringUtils.isNumeric(t)) { if(this.titsMap.containsKey(t)) { return this.titsMap.get(t); } } return t; } /* Exepciones en el código de centro que hay que traducir a código SAIC */ private String translateCentre(String c) { if(!StringUtils.isNumeric(c)) { if(this.centresMap.containsKey(c)) { return this.centresMap.get(c); } } return c; } private OrganDTO findOrgByRuct(Integer ruct) { return oc.getOrgByRuct(ruct); } private boolean existsOrg(String tlugar, Integer titulacio) { return oc.existsOrg(tlugar, titulacio); } private void deleteByEnquesta(String enquesta) { iets.deleteByEnquesta(enquesta); } private void save(IndicadorEnquestaTmp ie) { iets.save(ie); } private List checkDuplicatesAux(String enquesta) { return iets.checkDuplicates(enquesta); } private Integer countByEnquestaAux(String enquesta) { return iets.countByEnquesta(enquesta); } private void sendMail(String string, String string2, String string3) { EmailDTO email = new EmailDTO(string, string2, string3); ec.sendEmail(email); } }