diff --git a/curriculumsearch/pom.xml b/curriculumsearch/pom.xml index 6d8230f..40c2bd1 100644 --- a/curriculumsearch/pom.xml +++ b/curriculumsearch/pom.xml @@ -47,6 +47,22 @@ spring-boot-devtools true + + + org.springframework.boot + spring-boot-starter-validation + + + org.hibernate.validator + hibernate-validator + 6.0.13.Final + + + + javax.servlet + jstl + 1.2 + diff --git a/curriculumsearch/src/main/java/com/roshka/CurriculumsearchApplication.java b/curriculumsearch/src/main/java/com/roshka/CurriculumsearchApplication.java index 5bdff5e..95f39d9 100644 --- a/curriculumsearch/src/main/java/com/roshka/CurriculumsearchApplication.java +++ b/curriculumsearch/src/main/java/com/roshka/CurriculumsearchApplication.java @@ -14,4 +14,5 @@ public class CurriculumsearchApplication { SpringApplication.run(CurriculumsearchApplication.class, args); } + } diff --git a/curriculumsearch/src/main/java/com/roshka/controller/PostulanteController.java b/curriculumsearch/src/main/java/com/roshka/controller/PostulanteController.java index fcadc92..7e4eec7 100644 --- a/curriculumsearch/src/main/java/com/roshka/controller/PostulanteController.java +++ b/curriculumsearch/src/main/java/com/roshka/controller/PostulanteController.java @@ -1,41 +1,67 @@ package com.roshka.controller; -import java.text.SimpleDateFormat; -import java.util.Date; -import javax.servlet.http.HttpServletRequest; +import javax.validation.ConstraintViolationException; + -import com.roshka.modelo.Experiencia; import com.roshka.modelo.Postulante; import com.roshka.repositorio.PostulanteRepository; +import com.roshka.repositorio.TecnologiaRepository; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.ui.Model; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.*; -@Controller +@Controller public class PostulanteController { @Autowired PostulanteRepository post; + @Autowired + TecnologiaRepository tecRepo; + @RequestMapping("/") public String index() { return "index"; } @RequestMapping("/postulante") - public String getFormPostulante(){ - + public String getFormPostulante(Model model){ + model.addAttribute("tecnologias", tecRepo.findAll()); return "postulante-form"; } @PostMapping(value = "/postulante",consumes = "application/json") public String guardarPostulante(@RequestBody Postulante postulante){ + //se obtiene referencia de todas las tecnologias existentes + postulante.getTecnologias().stream().filter( + tec -> tec.getTecnologia().getId() != 0 + ).forEach( + tec -> tec.setTecnologia(tecRepo.getById(tec.getTecnologia().getId())) + ); post.save(postulante); return "redirect:/"; } + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler({MethodArgumentNotValidException.class}) + public ResponseEntity handleValidationExceptions( + MethodArgumentNotValidException ex) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ex.getMessage()); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler({ConstraintViolationException.class}) + public ResponseEntity handleValidationExceptions2( + ConstraintViolationException ex) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ex.getMessage()); + } + } diff --git a/curriculumsearch/src/main/java/com/roshka/modelo/Estudio.java b/curriculumsearch/src/main/java/com/roshka/modelo/Estudio.java index 818fbc9..f578259 100644 --- a/curriculumsearch/src/main/java/com/roshka/modelo/Estudio.java +++ b/curriculumsearch/src/main/java/com/roshka/modelo/Estudio.java @@ -3,6 +3,7 @@ package com.roshka.modelo; import java.util.Date; import java.util.List; +import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; @@ -12,27 +13,39 @@ import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import javax.persistence.Table; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Past; import com.fasterxml.jackson.annotation.JsonBackReference; import com.fasterxml.jackson.annotation.JsonManagedReference; +import com.roshka.utils.Helper; @Entity @Table(name="estudio") public class Estudio { @Id - @GeneratedValue(strategy=GenerationType.IDENTITY) + @GeneratedValue(strategy=GenerationType.AUTO) @Column(name="id") private long id; + @Column(name="tipo_de_studio") + @NotBlank(message = "Este campo no puede estar vacio") private String tipoDeEstudio; + @Column(name="institucion") + @NotBlank(message = "Este campo no puede estar vacio") private String institucion; + @Column(name="fecha_desde") + @NotNull(message = "Este campo no puede estar vacio") + @Past(message = "Este campo no puede estar en el futuro") private Date fechaDesde; + @Column(name="fecha_hasta") private Date fechaHasta; - @Column(name="referencias") - private String referencias; + @Column(name="titulo") + @NotBlank(message = "Este campo no puede estar vacio") private String titulo; @ManyToOne @@ -41,7 +54,7 @@ public class Estudio { private Postulante postulante; @JsonManagedReference - @OneToMany(mappedBy = "estudio") + @OneToMany(mappedBy = "estudio",cascade = CascadeType.ALL) private List estudioReconocimiento; @@ -62,6 +75,13 @@ public class Estudio { this.postulante = postulante; } + public void setFechaDesde(String fechaDesde) { + this.fechaDesde = Helper.convertirFecha(fechaDesde); + } + public void setFechaHasta(String fechaHasta) { + this.fechaHasta = Helper.convertirFecha(fechaHasta); + } + public long getId() { @@ -105,14 +125,6 @@ public class Estudio { this.fechaHasta = fechaHasta; } - public String getReferencias() { - return this.referencias; - } - - public void setReferencias(String referencias) { - this.referencias = referencias; - } - public String getTitulo() { return this.titulo; } diff --git a/curriculumsearch/src/main/java/com/roshka/modelo/EstudioReconocimiento.java b/curriculumsearch/src/main/java/com/roshka/modelo/EstudioReconocimiento.java index 4ed255b..9ff67fe 100644 --- a/curriculumsearch/src/main/java/com/roshka/modelo/EstudioReconocimiento.java +++ b/curriculumsearch/src/main/java/com/roshka/modelo/EstudioReconocimiento.java @@ -9,6 +9,7 @@ import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; +import javax.validation.constraints.NotBlank; import com.fasterxml.jackson.annotation.JsonBackReference; @@ -19,10 +20,15 @@ public class EstudioReconocimiento { @GeneratedValue(strategy=GenerationType.AUTO) @Column(name="id") private Long id; + @Column(name="nombre") + @NotBlank(message = "Este campo no puede estar vacio") private String nombre; + @Column(name="certificado") + @NotBlank(message = "Este campo no puede estar vacio") private String certificado; + @JsonBackReference @ManyToOne @JoinColumn diff --git a/curriculumsearch/src/main/java/com/roshka/modelo/Experiencia.java b/curriculumsearch/src/main/java/com/roshka/modelo/Experiencia.java index e57e9df..f8d93ce 100644 --- a/curriculumsearch/src/main/java/com/roshka/modelo/Experiencia.java +++ b/curriculumsearch/src/main/java/com/roshka/modelo/Experiencia.java @@ -12,8 +12,12 @@ import javax.persistence.GenerationType; import com.fasterxml.jackson.annotation.JsonBackReference; import com.fasterxml.jackson.annotation.JsonManagedReference; +import com.roshka.utils.Helper; import javax.persistence.*; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Past; @Entity @Table(name = "experiencia") @@ -21,18 +25,29 @@ public class Experiencia { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; + @Column(name = "institucion") + @NotBlank(message = "Este campo no puede estar vacio") private String institucion; + @Column(name = "fecha_desde") + @Past(message = "Este campo no puede estar en el futuro") + @NotNull(message = "Este campo no puede estar vacio") private Date fechaDesde; + @Column(name = "fecha_hasta") private Date fechaHasta; - @Column(name = "referencias") - private String referencias; + @Column(name = "nombre_referencia") + private String nombreReferencia; + @Column(name = "telefono_referencia") + private String telefonoReferencia; @Column(name = "cargo") + @NotBlank(message = "Este campo no puede estar vacio") private String cargo; + @Column(name = "descripcion") private String descripcion; + @JsonBackReference @ManyToOne(optional = false) @JoinColumn @@ -55,14 +70,7 @@ public class Experiencia { this.institucion = institucion; } public void setFechaDesde(String fechaDesde) { - if(fechaDesde==null || fechaDesde.isEmpty()) return; - - try { - this.fechaDesde = new SimpleDateFormat("yyyy-mm-dd").parse(fechaDesde); - } catch (ParseException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } + this.fechaDesde = Helper.convertirFecha(fechaDesde); } public Date getFechaHasta() { return fechaHasta; @@ -71,20 +79,19 @@ public class Experiencia { this.fechaHasta = fechaHasta; } public void setFechaHasta(String fechaHasta) { - if(fechaHasta==null || fechaHasta.isEmpty()) return; - - try { - this.fechaHasta = new SimpleDateFormat("yyyy-mm-dd").parse(fechaHasta); - } catch (ParseException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - public String getReferencias() { - return referencias; - } - public void setReferencias(String referencias) { - this.referencias = referencias; + this.fechaHasta = Helper.convertirFecha(fechaHasta); + } + public String getNombreReferencia() { + return nombreReferencia; + } + public String getTelefonoReferencia() { + return telefonoReferencia; + } + public void setNombreReferencia(String nombreReferencia) { + this.nombreReferencia = nombreReferencia; + } + public void setTelefonoReferencia(String telefonoReferencia) { + this.telefonoReferencia = telefonoReferencia; } public String getCargo() { return cargo; diff --git a/curriculumsearch/src/main/java/com/roshka/modelo/Postulante.java b/curriculumsearch/src/main/java/com/roshka/modelo/Postulante.java index b8b0cc8..60bde6d 100644 --- a/curriculumsearch/src/main/java/com/roshka/modelo/Postulante.java +++ b/curriculumsearch/src/main/java/com/roshka/modelo/Postulante.java @@ -1,8 +1,10 @@ package com.roshka.modelo; import javax.persistence.*; +import javax.validation.constraints.*; import com.fasterxml.jackson.annotation.JsonManagedReference; +import com.roshka.utils.Helper; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -20,36 +22,52 @@ public class Postulante { private long id; @Column(name="nombre") + @NotBlank(message = "Este campo no puede estar vacio") + @Size(max = 255) private String nombre; @Column(name = "apellido") + @NotBlank(message = "Este campo no puede estar vacio") + @Size(max = 255) private String apellido; @Column(name = "ci") + @NotBlank(message = "Este campo no puede estar vacio") + @Size(max = 120) private String ci; @Column(name = "correo") + @NotBlank(message = "Este campo no puede estar vacio") + @Email(message = "Formato incorrecto de correo") private String correo; @Column(name = "ciudad") + @NotBlank(message = "Este campo no puede estar vacio") + @Size(max = 120) private String ciudad; @Column(name = "telefono") + @NotBlank(message = "Este campo no puede estar vacio") private String telefono; @Column(name = "fecha_nacimiento") + @NotNull(message = "Este campo no puede estar vacio") + @Past(message = "Este campo no puede estar en el futuro") private Date fechaNacimiento; @Column(name = "resumen") private String resumen; @Column(name = "nivel_ingles") + @Min(value = 1) + @Max(value = 5) private long nivelIngles; @Column(name = "curriculum") private String curriculum; @Column(name = "modalidad") + @NotNull private String modalidad; @Column(name = "disponibilidad") @@ -133,15 +151,7 @@ public class Postulante { } public void setFechaNacimiento(String fechaNacimiento) { - if(fechaNacimiento==null || fechaNacimiento.isEmpty()) return; - try { - - this.fechaNacimiento = new SimpleDateFormat("yyyy-mm-dd").parse(fechaNacimiento); - } catch (ParseException e) { - // TODO Auto-generated catch block - System.err.println("Error al parsear"); - e.printStackTrace(); - } + this.fechaNacimiento = Helper.convertirFecha(fechaNacimiento); } public String getResumen() { diff --git a/curriculumsearch/src/main/java/com/roshka/modelo/PostulanteTecnologia.java b/curriculumsearch/src/main/java/com/roshka/modelo/PostulanteTecnologia.java index 21aa0ff..bed7f3c 100644 --- a/curriculumsearch/src/main/java/com/roshka/modelo/PostulanteTecnologia.java +++ b/curriculumsearch/src/main/java/com/roshka/modelo/PostulanteTecnologia.java @@ -1,5 +1,6 @@ package com.roshka.modelo; +import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; @@ -8,49 +9,58 @@ import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; import com.fasterxml.jackson.annotation.JsonBackReference; @Entity -@Table(name="postulante_tecnologia") +@Table(name="postulante_tecnologia", +uniqueConstraints=@UniqueConstraint(columnNames={"postulante_id", "tecnologia_id"})) public class PostulanteTecnologia { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name="id") private long id ; + @Column(name="nivel") + @Min(value = 1) + @Max(value = 5) private Long nivel; - @ManyToOne() + @ManyToOne(cascade = {CascadeType.PERSIST,CascadeType.MERGE},optional = false) @JoinColumn private Tecnologia tecnologia; - @ManyToOne() + + @ManyToOne() @JoinColumn @JsonBackReference - private Postulante postulante; -public long getId() { - return id; -} -public void setId(long id) { - this.id = id; -} -public Long getNivel() { - return nivel; -} -public void setNivel(Long nivel) { - this.nivel = nivel; -} -public Tecnologia getTecnologia() { - return tecnologia; -} -public void setTecnologia(Tecnologia tecnologia) { - this.tecnologia = tecnologia; -} -public Postulante getPostulante() { - return postulante; -} -public void setPostulante(Postulante postulante) { - this.postulante = postulante; -} + private Postulante postulante; + public long getId() { + return id; + } + public void setId(long id) { + this.id = id; + } + public Long getNivel() { + return nivel; + } + public void setNivel(Long nivel) { + this.nivel = nivel; + } + public Tecnologia getTecnologia() { + return tecnologia; + } + public void setTecnologia(Tecnologia tecnologia) { + this.tecnologia = tecnologia; + } + public Postulante getPostulante() { + return postulante; + } + public void setPostulante(Postulante postulante) { + this.postulante = postulante; + } diff --git a/curriculumsearch/src/main/java/com/roshka/modelo/Tecnologia.java b/curriculumsearch/src/main/java/com/roshka/modelo/Tecnologia.java index a870a28..ce8e758 100644 --- a/curriculumsearch/src/main/java/com/roshka/modelo/Tecnologia.java +++ b/curriculumsearch/src/main/java/com/roshka/modelo/Tecnologia.java @@ -6,6 +6,7 @@ import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; +import javax.validation.constraints.NotBlank; @Entity @Table(name="tecnologia") @@ -16,6 +17,7 @@ public class Tecnologia { private long id; @Column(name="nombre") + @NotBlank(message = "Este campo no puede estar vacio") private String nombre; diff --git a/curriculumsearch/src/main/java/com/roshka/utils/Helper.java b/curriculumsearch/src/main/java/com/roshka/utils/Helper.java new file mode 100644 index 0000000..40f586f --- /dev/null +++ b/curriculumsearch/src/main/java/com/roshka/utils/Helper.java @@ -0,0 +1,24 @@ +package com.roshka.utils; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +public class Helper { + /** + * Se espera fecha en el formato yyyy-MM-dd + * @param fecha + * @return retorna fecha correcta o nulo si no es posible convertir + */ + public static Date convertirFecha(String fecha) { + try { + + return new SimpleDateFormat("yyyy-MM-dd").parse(fecha); + } catch (ParseException e) { + // TODO Auto-generated catch block + System.err.println("Error al parsear"); + e.printStackTrace(); + return null; + } + } +} diff --git a/curriculumsearch/src/main/resources/application.properties b/curriculumsearch/src/main/resources/application.properties index d6c6325..192b323 100644 --- a/curriculumsearch/src/main/resources/application.properties +++ b/curriculumsearch/src/main/resources/application.properties @@ -1,7 +1,10 @@ spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect - +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.format_sql=true +logging.level.org.hibernate.SQL=DEBUG +logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE spring.sql.init.mode=always spring.sql.init.platform=postgres diff --git a/curriculumsearch/src/main/resources/static/main.js b/curriculumsearch/src/main/resources/static/main.js index b95f448..d55c9af 100644 --- a/curriculumsearch/src/main/resources/static/main.js +++ b/curriculumsearch/src/main/resources/static/main.js @@ -4,13 +4,51 @@ let cont_tecnologia = 0; const experiencias = []; const estudios = []; const tecnologias = []; -function agregarFieldExpierncia(){ + +const formValidator = function () { + 'use strict' + + // Fetch all the forms we want to apply custom Bootstrap validation styles to + var forms = document.querySelectorAll('.needs-validation') + var expForm = document.querySelector('#agregar-exp') + + + // Loop over them and prevent submission + Array.prototype.slice.call(forms) + .forEach(function (form) { + form.addEventListener('submit', function (event) { + if (!form.checkValidity()) { + event.preventDefault() + event.stopPropagation() + } + + form.classList.add('was-validated') + }, false) + }) +} + +function agregarFieldExpierncia(event){ //recoger del form const pairs = {}; const formexp = document.querySelector("[name=experiencia-form]"); const formData = new FormData(formexp); const reconocimientos = [{},{},{}]; let pos_rec; + let returnFlag = false; + + formData.forEach((value, key)=>{ + if((key === "institucion" || key === "cargo" || key === "fechaDesde") + && value==="" && returnFlag == false){ + console.log(key, value) + returnFlag = true; + } + }); + + if(returnFlag===true){ + alert("Rellene Institucion, Fechas y Cargo como minimo"); + return; + } + for (const [name, value] of formData){ pos_rec = name.split("-");//rec-nombre-index if (pos_rec.length > 1) { @@ -28,7 +66,6 @@ function agregarFieldExpierncia(){ const div = document.querySelector("#experiencias") const div1 = document.createElement('div'); let content='
    ' - for (let index = 0; index < experiencias.length; index++) { const exp = experiencias[index]; if(exp==null) continue; @@ -45,15 +82,62 @@ function agregarFieldExpierncia(){ div.innerHTML = ''; div.appendChild(div1); cont_experiencia++; +} +/*--------------------------------------------------------------------*/ +function agregarFieldTecnologia(){ + //recoger del form + const pairs = {}; + const formtecn = document.querySelector("[name=tecnologia-form]"); + const formData = new FormData(formtecn); + for (const [name, value] of formData){ + pairs[name] = value + } + tecnologias[cont_tecnologia]={} + tecnologias[cont_tecnologia]["tecnologia"]=pairs["tecnologia-id"]=="-1"?{nombre: pairs["tecnologia-nombre"]}:{id: pairs["tecnologia-id"],nombre:document.querySelector('option[value="'+pairs["tecnologia-id"]+'"]').innerHTML} + tecnologias[cont_tecnologia]["nivel"]=pairs.nivel + //tecnologias[cont_tecnologia] = pairs; + formtecn.reset(); + document.querySelector("#tecnologia-nombre").classList.add('d-none') + //imprimir lista actualizada + const div = document.querySelector("#tecnologias") + const div1 = document.createElement('div'); + console.log(tecnologias[0]) + let content1='
      ' + for (let index = 0; index < tecnologias.length; index++) { + const tecn = tecnologias[index]; + if(tecn==null) continue; + content1 += ` +
    • + ${tecn.tecnologia.nombre} + +
    • + + ` + } + content1 += "
    " + div1.innerHTML = content1 + div.innerHTML = ''; + div.appendChild(div1); + cont_tecnologia++; } + +/*--------------------------------------------------------------------*/ function eliminarExperiencia(event) { //eliminar del array experiencias[event.target.parentElement.id.split("-")[1]]=null //eliminar en html event.target.parentElement.remove() } +/*----------------------------------------------------------------- */ +function eliminarTecnologia(event) { + //eliminar del array + tecnologias[event.target.parentElement.id.split("-")[1]]=null + //eliminar en html + event.target.parentElement.remove() +} +/*----------------------------------------------------------------- */ function serializeJSON (form) { // Create a new FormData object const formData = new FormData(form); @@ -90,13 +174,85 @@ async function postData(url = '', data = {}) { }); return response; // parses JSON response into native JavaScript objects } - +formValidator() form = document.querySelector("form"); form.addEventListener("submit",(evt)=>{ - + // if (!form.checkValidity()) { + // evt.preventDefault() + // evt.stopPropagation() + // } + // form.classList.add('was-validated') postData('postulante', serializeJSON(form)) .then(response => { - location.replace(response.url); + if(response.status==200 || response.status==302){ + location.replace(response.url); + }else{ + console.log(response.text().then(value => console.log(value))) + } }); evt.preventDefault(); -} ); \ No newline at end of file +} ); + +document.querySelector("#btn-new-tech").addEventListener('click',()=>{document.querySelector("#tecnologia-nombre").classList.remove('d-none')}) + + +//Metodos para Estudios + + + +function agregarFieldEstudio(){ + //recoger del form + const pairs = {}; + const formest = document.querySelector("[name=estudio-form"); + const formData = new FormData(formest); + const estudioReconocimiento = [{},{},{}]; + let pos_rec; + for (const [name, value] of formData){ + pos_rec = name.split("-");//rec-nombre-index + if (pos_rec.length > 1) { + estudioReconocimiento[pos_rec[2]][pos_rec[1]] = value + } + else{ + pairs[name] = value + } + + } + pairs["estudioReconocimiento"] = estudioReconocimiento.filter(rec => rec.nombre); + estudios[cont_estudios] = pairs; + formest.reset(); + //imprimir lista actualizada + const div = document.querySelector("#estudios") + const div1 = document.createElement('div'); + let content='
      ' + + for (let index = 0; index < estudios.length; index++) { + const est = estudios[index]; + if(est==null) continue; + content += ` +
    • + ${est.institucion} + +
    • + + ` + } + content += "
    " + div1.innerHTML = content + div.innerHTML = ''; + div.appendChild(div1); + cont_estudios++; + +} + +function eliminarEstudio(event) { + //eliminar del array + estudios[event.target.parentElement.id.split("-")[1]]=null + //eliminar en html + event.target.parentElement.remove() +} + + + + + + diff --git a/curriculumsearch/src/main/webapp/jsp/postulante-form.jsp b/curriculumsearch/src/main/webapp/jsp/postulante-form.jsp index c6fd829..fbb8853 100644 --- a/curriculumsearch/src/main/webapp/jsp/postulante-form.jsp +++ b/curriculumsearch/src/main/webapp/jsp/postulante-form.jsp @@ -1,3 +1,6 @@ +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> +<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> +<%@ page contentType="text/html;charset=UTF-8" language="java" %> @@ -8,7 +11,7 @@ Hello, world! - +