1.1.14.4.2.5. fejezet, Hibernate annotáció és validáció
Annotáció alapú ellenőrzéshez a több nyelvű hibaüzeneteket az alábbi parancsal készíthetjük el:
native2ascii -encoding UTF-8 ValidationMessages_hu_HU-utf8.properties ValidationMessages_hu_HU.properties
Ennek a körülményességnek az oka, hogy egyenlőre kizárólag ISO8859-1 karakterkészletű properties fájlokat kezel a Hibernate Validator. A native2ascii paranccsal ASCII formába konvertálhatók az amúgy UTF-8 kódolású üzenetek, és Unicode vezérlőkarakterekkel válnak a beállított nyelvnek megfelelően ékezetes karakterekké. Ez a kis program az aktuális JDK bin könyvtárában található. A böngészőben beállított nyelven jelennek meg a hibaüzenetek, amik interpolációval készülnek. Ehhez egy interpolátor osztályt kell létrehoznunk.
package com.integrity.i18n; import java.util.Locale; import javax.validation.MessageInterpolator; public class ClientLocaleMessageInterpolator implements MessageInterpolator { private final MessageInterpolator delegate; private Locale locale = null; public ClientLocaleMessageInterpolator(MessageInterpolator delegate) { this.delegate = delegate; } @Override public String interpolate(String message, Context context) { return this.interpolate(message, context, locale); } @Override public String interpolate(String message, Context context, Locale locale) { return delegate.interpolate(message, context, locale); } public void setLocale(Locale locale){ this.locale = locale; } }
A kiválasztott nyelvet egy session bean tárolja.
package com.integrity.i18n; import java.io.Serializable; import java.util.Locale; import javax.faces.bean.ManagedBean; import javax.faces.bean.SessionScoped; import javax.faces.component.UIViewRoot; import javax.faces.context.FacesContext; @ManagedBean @SessionScoped public class LocaleBean implements Serializable { private static final long serialVersionUID = 5503640713110816880L; private Locale locale; public static final String DEFAULT_LOCALE="hu"; public LocaleBean() { FacesContext context = FacesContext.getCurrentInstance(); UIViewRoot root = context.getViewRoot(); if (root != null) { locale = root.getLocale(); } else { locale = new Locale(DEFAULT_LOCALE); } } public Locale getLocale() { return locale; } public String getLanguage() { return locale.getLanguage(); } public void setLanguage(String language) { locale = new Locale(language); FacesContext.getCurrentInstance().getViewRoot().setLocale(locale); } }
A standard.xhtml-ben (alapértelmezett sablon) a
<f:view contentType="text/html" locale="#{localeBean.locale}">
érvényesíti a beállítást.
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui"> <f:view contentType="text/html" locale="#{localeBean.locale}"> <h:head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>JSF 2, Spring Web Flow, and PrimeFaces Showcase</title> <link rel="stylesheet" href="${request.contextPath}/app/resources/styles/blueprint/screen.css" type="text/css" media="screen, projection" /> <link rel="stylesheet" href="${request.contextPath}/app/resources/styles/blueprint/print.css" type="text/css" media="print" /> <ui:insert name="headIncludes"/> </h:head> <h:body> <div class="container"> <h:outputLink value="${request.contextPath}/app/service-request" > <h:outputText value="Service request home" /> </h:outputLink><br/> <h:outputLink value="${request.contextPath}/app/home" > <h:outputText value="Home" /> </h:outputLink><br/> <div> <h1>JSF 2, PrimeFaces, and Spring Web Flow</h1> <h3 class="alt"> <ui:insert name="title"/> </h3> <hr/> </div> <div> <ui:insert name="notes"/> </div> <div> <ui:insert name="content"/> </div> </div> </h:body> </f:view> </html>
A programban a validáció és a hibajelzés az alábbiak szerint működik
public class RequestDAOImpl implements RequestDAO { private LocaleBean localeBean; public void setMyLocale(LocaleBean localeBean){ this.localeBean = localeBean; } public boolean validate(Request request) throws ConstraintViolationException { Configuration config = Validation.byDefaultProvider().configure(); ClientLocaleMessageInterpolator interpolator = new ClientLocaleMessageInterpolator(config.getDefaultMessageInterpolator()); interpolator.setLocale(localeBean.getLocale()); config = config.messageInterpolator(interpolator); Validator localValidator = config.buildValidatorFactory().getValidator(); Set constraintViolations = localValidator.validate(request); for(ConstraintViolation<Request> constraint : (Set<ConstraintViolation<Request>>)constraintViolations) { log.error(constraint.getMessage()); FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR,constraint.getMessage(), "")); } return constraintViolations.isEmpty(); }
Itt a Request egy saját osztály, tehát nem a HttpRequest-ből örökített. Saját annotációi közül az összetett osztály szintű annotációk szúrhatnak szemet első látásra (FieldMatch)
package com.integrity.domain; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; import javax.persistence.Transient; import javax.validation.constraints.NotNull; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; /* import org.hibernate.validator.Email; import org.hibernate.validator.Length; import org.hibernate.validator.NotEmpty; import org.hibernate.validator.NotNull; */ import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.FieldMatch; import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.NotEmpty; /* packages for validateEdit method import org.springframework.binding.message.MessageBuilder; import org.springframework.binding.message.MessageContext; import org.springframework.binding.validation.ValidationContext; //*/ @Entity @Table(name="request") @XmlRootElement //* @FieldMatch.List({ @FieldMatch(first = "discount", second = "discountReason", message = "{com.integrity.validation.discountReason.isEmpty.message}"), @FieldMatch(first = "operationType", second = "serviceId", message = "{com.integrity.validation.serviceid.isEmpty.message}") }) //*/ public class Request implements Serializable { private static final long serialVersionUID = -3828086455549895099L; private Long id; private String name; private String phone; private String email; private String organization; private String subject; private String operationType; @Transient private String operatoinTypeLabel; private String serviceId; private String homepage; private String applicantType; @Transient private String applicantTypeLabel; private String startTime; @Transient private String startTimeLabel; private Boolean discount; private String discountReason; private String comment; public static String CONTRACTION_YES = "I"; public static String CONTRACTION_NO = "N"; public static String CONTRACTION_TRUE = "true"; public static String CONTRACTION_FALSE = "false"; public static String UNIFIED_YES_HU = "Igen"; public static String UNIFIED_NO_HU = "Nem"; public static String SERVICE_GROW = "B"; public static String SERVICE_SWITCH = "L"; public static String SERVICE_NEW = "U"; /** * @return the id */ @Id @GeneratedValue @Column(name="ID") @XmlAttribute public Long getId() { return id; } /** * @param id the id to set */ public void setId(Long id) { this.id = id; } /** * @return the nev */ @Column(name="name") @Length(max = 64) @XmlElement @NotNull @NotEmpty public String getName() { return name; } /** * @param name the nev to set */ public void setName(String name) { this.name = name; } /** * @return the telefonszam */ @Column(name="phone") @XmlElement @NotNull @NotEmpty public String getPhone() { return phone; } /** * @param phone the telefonszam to set */ public void setPhone(String phone) { this.phone = phone; } /** * @return the email */ @Email @Column(name="email") @XmlElement @NotNull @NotEmpty public String getEmail() { return email; } /** * @param email the email to set */ public void setEmail(String email) { this.email = email; } /** * @return the szervezetnev */ @Column(name="organization") @XmlElement @NotNull @NotEmpty public String getOrganization() { return organization; } /** * @param szervezetnev the szervezetnev to set */ public void setOrganization(String organization) { this.organization = organization; } /** * @return the targy */ @Column(name="subject") @XmlElement public String getSubject() { return subject; } /** * @param subject the targy to set */ public void setSubject(String subject) { this.subject = subject; } /** * @return the muveletjelleg */ @Column(name="operation_type") @XmlElement @NotNull @NotEmpty public String getOperationType() { return operationType; } /** * @param operationType the muveletjelleg to set */ public void setOperationType(String operationType) { this.operationType = operationType; } /** * @return the serviceID */ @Column(name="service_id") @XmlElement public String getServiceId() { return serviceId; } /** * @param serviceId the serviceId to set */ public void setServiceId(String service_id) { this.serviceId = service_id; } /** * @return the honlap */ @Column(name="homepage") @XmlElement public String getHomepage() { return homepage; } /** * @param homepage the honlap to set */ public void setHomepage(String homepage) { this.homepage = homepage; } /** * @return the kerelmezojelleg */ @Column(name="applicant_type") @XmlElement @NotNull @NotEmpty public String getApplicantType() { return applicantType; } /** * @param applicantType the kerelmezojelleg to set */ public void setApplicantType(String applicantType) { this.applicantType = applicantType; } /** * @return the szolgaltatasido */ @Column(name="start_time") @XmlElement @NotEmpty @NotNull public String getStartTime() { return startTime; } /** * @param startTime the szolgaltatasido to set */ public void setStartTime(String startTime) { this.startTime = startTime; } /** * @return the kedvezmeny */ @Column(name="discount") @XmlElement public Boolean getDiscount() { return discount; } public void setDiscount(Boolean discount) { this.discount = discount; } /** * @return the kedvezmenyoka */ @Column(name="discount_reason") @XmlElement public String getDiscountReason() { return discountReason; } /** * @param discountReason the kedvezmenyoka to set */ public void setDiscountReason(String discountReason) { this.discountReason = discountReason; } /** * @return the megjegyzes */ @Column(name="comment") @XmlElement public String getComment() { return comment; } /** * @param comment the megjegyzes to set */ public void setComment(String comment) { this.comment = comment; } @XmlElement @Transient public String getOperationTypeLabel() { return operatoinTypeLabel; } public void setOperationTypeLabel(String muveletjellegLabel) { this.operatoinTypeLabel = muveletjellegLabel; } @XmlElement @Transient public String getApplicantTypeLabel() { return applicantTypeLabel; } public void setApplicantTypeLabel(String applicantTypeLabel) { this.applicantTypeLabel = applicantTypeLabel; } @XmlElement @Transient public String getStartTimeLabel() { return startTimeLabel; } public void setStartTimeLabel(String startTimeLabel) { this.startTimeLabel = startTimeLabel; } @XmlElement @Transient public String getDiscountLabel() { return ((discount!=null) && discount ? UNIFIED_YES_HU : UNIFIED_NO_HU); } /* NotACleanCode + uses Messages.properties public void validateEdit(ValidationContext validationContext) { if ((discount!=null) && discount && discountReason.isEmpty()) { MessageContext messageContext = validationContext.getMessageContext(); messageContext.addMessage(new MessageBuilder().error().code("discountReason.isEmpty").build()); } if ((operationType.equals(SERVICE_GROW) || operationType.equals(SERVICE_SWITCH)) && serviceId.isEmpty()) { MessageContext messageContext = validationContext.getMessageContext(); messageContext.addMessage(new MessageBuilder().error().code("serviceid.isEmpty").build()); } } //*/ }
A validateEdit magával hozta a Messages_xx_XX.properties kezelésének lehetőségét, ami UTF-8 karakterkészlet használatát is eredményezte. A Spring servlet-context.xml-ben konfiguráltam is, de ez igen távol áll az igazi annotációs érték ellenőrzéstől.
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basename" value="classpath:Messages"/> <property name="defaultEncoding" value="UTF-8"/> </bean>
A validateEdit funkció már közelített a megoldáshoz, de még mindig külön fájlból emelte be a hibaüzenetet, és a tiszta kód szabályai szerint a validáció nem implementálható közvetlenül a domain-be.
A FieldMatch annotáció osztálya pedig az alábbi (interface majd az implementáció)
package org.hibernate.validator.constraints; import javax.validation.Constraint; import javax.validation.Payload; import org.hibernate.validator.constraints.FieldMatchValidator; import java.lang.annotation.Documented; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; import static java.lang.annotation.ElementType.TYPE; import java.lang.annotation.Retention; import static java.lang.annotation.RetentionPolicy.RUNTIME; import java.lang.annotation.Target; @Target({TYPE, ANNOTATION_TYPE}) @Retention(RUNTIME) @Constraint(validatedBy = FieldMatchValidator.class) @Documented public @interface FieldMatch { String message() default "{constraints.fieldmatch}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; String first(); String second(); @Target({TYPE, ANNOTATION_TYPE}) @Retention(RUNTIME) @Documented @interface List { FieldMatch[] value(); } }
package org.hibernate.validator.constraints; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import org.apache.commons.beanutils.BeanUtils; import com.integrity.domain.Request; public class FieldMatchValidator implements ConstraintValidator<FieldMatch, Object> { private String firstFieldName; private String secondFieldName; @Override public void initialize(final FieldMatch constraintAnnotation) { firstFieldName = constraintAnnotation.first(); secondFieldName = constraintAnnotation.second(); } @Override public boolean isValid(final Object value, final ConstraintValidatorContext context) { try { final Object firstObj = BeanUtils.getProperty(value, firstFieldName); final Object secondObj = BeanUtils.getProperty(value, secondFieldName); if (firstObj == null || secondObj == null){ return false; } if (firstFieldName.equalsIgnoreCase("operationType")){ return firstObj.equals(Request.SERVICE_NEW) || (!secondObj.toString().isEmpty() && (firstObj.equals(Request.SERVICE_SWITCH) || firstObj.equals(Request.SERVICE_GROW))); } else if (firstFieldName.equalsIgnoreCase("discount")) { return firstObj.toString().equalsIgnoreCase("false") || (!secondObj.toString().isEmpty() && firstObj.toString().equalsIgnoreCase("true")); } } catch (final Exception ignore) { // ignore } return true; } }
Nem túl szerencsés, hogy két feltételt is a mezőnevek alapján vizsgál, de kísérleti megoldásról van szó, ezért talán elnézhető a lazaság.
- A hozzászóláshoz be kell jelentkezni