Getting UserDetails from Active Directory using Java (AD Window Server 2008)

March 7th, 2011

Source Code:ADUserDetails , ActiveDirectoryUtils 

For getting UserDetails we need to get and process the following Ldap attributes:

Active Directory Attributes

User Attributes

userAccountControl

Bit vector that contains several flags about the user.

accountExpires

Date that the account expires (number of 100-nanosecond intervals since January 1, 1601)

pwdLastSet

Date and time that the password was last changed (number of 100-nanosecond intervals since January 1, 1601)

Domain Attribute

maxPwdAge

It contains the maximum password age. Measured in 100-nanosecond intervals.

Getting User details

Details about Account

Account never expires

private static final String ACCOUNT_NEVER_EXPIRE_VALUE = "9223372036854775807";
boolean accountNeverExpire = accountExpires.equals("0") || ACCOUNT_NEVER_EXPIRE_VALUE.equals(accountExpires);

Account disabled

private static final int ACCOUNT_DISABLE = 0x0002;
boolean accountDisabled = (userAccountControl & ACCOUNT_DISABLE) == ACCOUNT_DISABLE;

Account expiration Date

The attribute “accountExpires” is a number (number of 100-nanosecond intervals since January 1, 1601) representing the date at which the account will expire. We have to transform this to appropriate milliseconds number

private final static long DIFF_NET_JAVA_FOR_DATES = 11644473600000L + 24 * 60 * 60 * 1000;

long adAccountExpires= Long.parseLong(accountExpires);
long milliseconds = (adAccountExpires / 10000) - DIFF_NET_JAVA_FOR_DATES;
Date accountExpiresDate= new Date(milliseconds);

Details about Credentials

Credentials has to be changed at first

If it is true then the user has to change its password before login at first time.

boolean credentialsHasToBeChangedAtFirst = pwdLastSet.equals("0");

Credentials never expire

private static final int ADS_UF_DONT_EXPIRE_PASSWD = 0x10000;
boolean credentialsNeverExpire = (userAccountControl & ADS_UF_DONT_EXPIRE_PASSWD) == ADS_UF_DONT_EXPIRE_PASSWD;

Credentials expiration date and time

Getting the date and time at which the password was last changed:

private final static long DIFF_NET_JAVA_FOR_DATE_AND_TIMES = 11644473600000L;
long adDateTime = Long.parseLong(pwdLastSet);
long milliseconds = (adDateTime / 10000) - DIFF_NET_JAVA_FOR_DATE_AND_TIMES;
Date pwdLastSetDate = new Date(milliseconds);
 
Getting the number of max days that could have the password:
private final static int ONE_HUNDRED_NANOSECOND = 10000000;
private final static long SECONDS_IN_DAY = 86400;
long maxPwdAge = Math.abs(Long.parseLong(maxPwdAgeStr));
long maxPwdAgeSecs = maxPwdAge / ONE_HUNDRED_NANOSECOND;
int maxPwdAgeDays = (int) (maxPwdAgeSecs / SECONDS_IN_DAY);
 
Getting the the date and time at which the password will expire
Calendar cal = Calendar.getInstance();
cal.setTime(pwdLastSetDate);
cal.add(Calendar.DATE, maxPwdAgeDays);
Date credentialsExpiresDate = cal.getTime();

Using the Active Directory from Java (AD Window Server 2008)

February 23rd, 2011

Source Code: Java Active Directory Source Code

We will show the needed steps in order to do a java program that interacts with the Active Directory. This program must allow the following:

  • Create user – with password that expired and does not expired.
  • Enable user
  • Disable user
  • Login user
  • Add user to a Group
  • Show all users

After checking these requirements it is clear that we need to do the following task in order to achieve the solution:

Configuration

Configure secure connection (ldaps) in Active Directory

To change anything in the Active Directory we must go via secure connection (ldaps://your.ldap.server:636) so it is needed to enable the “ldaps” that normally listen in the port: “636”. The easiest way to do this is to install the Online Responder (Go to "Deploying Microsoft Online Responder" Section).

After installing use the program “ldp.exe” to check if it is possible to connect to the AD using SSL.

Add the Server Certificate to our Java key Store

If we run our java program we will get the error "unable to find valid certification path to requested target" because our server certificate is not issued by a certification authority.

To solve this we need to add the server certificate to our trusted Java key store. The easiest way is to run the program: InstallCert check more details about how to do this here.

Development

We will use the Spring-Ldap.

Spring Configuration

   1: <?xml version="1.0" encoding="UTF-8"?>

   2: <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">

   3: <beans>

   4:     <bean id="contextSource"

   5:           class="org.springframework.ldap.core.support.LdapContextSource">

   6:         <!--<property name="url" value="ldap://192.168.1.102:389"/>-->

   7:         <property name="url" value="ldaps://192.168.1.102:636"/>

   8:         <property name="base" value="CN=Users,dc=agileworks,dc=com"/>

   9:         <property name="userDn" value="CN=Administrador,CN=Users,DC=agileworks,DC=com"/>

  10:         <property name="password" value="$awpassword191"/>

  11:         <property name="referral" value="follow"/>

  12:         <property name="baseEnvironmentProperties">

  13:             <map>

  14:                 <entry key="java.naming.security.authentication" value="simple"/>

  15:             </map>

  16:         </property>

  17:     </bean>

  18:     <bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate">

  19:         <constructor-arg ref="contextSource"/>

  20:     </bean>

  21:     <bean id="ldapUser" class="com.aw.ad.UserDaoImpl">

  22:         <property name="ldapTemplate" ref="ldapTemplate"/>

  23:     </bean>

  24: </beans>

Line 7: Using “ldaps” because we need to create users and change passwords.

Line 21: Configuring the DAO that will contain all the logic to interact with the AD

Getting reference to the UserDao

Resource resource = new ClassPathResource("springldap.xml");
BeanFactory factory = new XmlBeanFactory(resource);
UserDao userDao = (UserDaoImpl) factory.getBean("ldapUser");

Constants of the UserDao

// Attribute names
private static final String USER_ACCOUNT_CONTROL_ATTR_NAME = "userAccountControl";
private static final String PASSWORD_ATTR_NAME = "unicodepwd";
private static final String DISTINGUISHED_NAME_ATTR_NAME = "distinguishedname";
private static final String MEMBER_ATTR_NAME = "member";

// usercontrol params
private static final int FLAG_TO_DISABLE_USER = 0x2;
private static final int ADS_UF_DONT_EXPIRE_PASSWD = 0x10000;
private static final int USER_CONTROL_NORMAL_USER = 512;

Creating user

With password that expires

String userName = "Test18";
String pwd = "##12345xx";
User newUser = new User();
newUser.setUserName(userName);
newUser.setPassword(pwd);
userDao.createUser(newUser);

With password that does not expire

User newUser = new User();
newUser.setUserName(userName);
newUser.setPassword(pwd);
newUser.setExpirePasswd(false);
userDao.createUser(newUser);

UserDao.createUser method

   1: public void createUser(User user) {

   2:     try {

   3:         Attributes userAttributes = new BasicAttributes();

   4:         userAttributes.put("objectclass", "person");

   5:         userAttributes.put("objectclass", "user");

   6:         userAttributes.put("userPrincipalName", user.getEmailAddress());

   7:         userAttributes.put("sAMAccountName", user.getUserName());

   8:         userAttributes.put("givenName", user.getFirstName());

   9:         userAttributes.put("sn", user.getLastName());

  10:         userAttributes.put("displayName", user.getDisplayName());

  11:         int userAccounControl = getUserAccountControl(user);

  12:         userAttributes.put("userAccountControl", "" + userAccounControl);

  13:         userAttributes.put("unicodepwd", encodePassword(user.getPassword()));

  14:         ldapTemplate.bind(getDnFrom(user.getUserName()), null, userAttributes);

  15:     } catch (NameAlreadyBoundException e) {

  16:         throw new DuplicateUserException("User:[" + user.getUserName() + "] allready exists in AD.", e);

  17:     } catch (OperationNotSupportedException e) {

  18:         throw new PasswordStrengthException("Password:[" + user.getPassword() + "] does not pass the strength password validation.", e);

  19:     } catch (Throwable e) {

  20:         throw new LdapException("Problems creating user.", e);

  21:     }

  22: }

Line 1-13: Setting the user attributes.

Line 11: Getting the value of the userAccountControl that it is used to set different characteristics for the account. In our case we set here the type of the account and if the password will expire or not:

private int getUserAccountControl(User user) {
int userAccounControl = USER_CONTROL_NORMAL_USER;
if (!user.isExpirePasswd()) {
userAccounControl |= ADS_UF_DONT_EXPIRE_PASSWD;
}
return userAccounControl;
}
 
Line 13: The password is decorated in order to comply the AD conditions for passwords
private byte[] encodePassword(String password) throws UnsupportedEncodingException {
String newQuotedPassword = "\"" + password + "\"";
return newQuotedPassword.getBytes("UTF-16LE");
}

Line 14: Creating the user in the AD.

Enable User

userDao.enableUser(userName);

UserDao.enableUser method

In order to enable the user the value of the userAccountControl must be change.

public void enableUser(String userName) {
DirContextOperations userContextOperations = ldapTemplate.lookupContext(getDnFrom(userName));
String userAccountControlStr = userContextOperations.getStringAttribute(USER_ACCOUNT_CONTROL_ATTR_NAME);
int newUserAccountControl = Integer.parseInt(userAccountControlStr) & ~FLAG_TO_DISABLE_USER;
userContextOperations.setAttributeValue(USER_ACCOUNT_CONTROL_ATTR_NAME, "" + newUserAccountControl);
ldapTemplate.modifyAttributes(userContextOperations);
}

Disable user

userDao.disableUser(userName);

UserDao.disableUser method

In order to disable the user the value of the userAccountControl must be change.

public void disableUser(String userName) {
DirContextOperations userContextOperations = ldapTemplate.lookupContext(getDnFrom(userName));
String userAccountControlStr = userContextOperations.getStringAttribute(USER_ACCOUNT_CONTROL_ATTR_NAME);
int newUserAccountControl = Integer.parseInt(userAccountControlStr) | FLAG_TO_DISABLE_USER;
userContextOperations.setAttributeValue(USER_ACCOUNT_CONTROL_ATTR_NAME, "" + newUserAccountControl);
ldapTemplate.modifyAttributes(userContextOperations);
}

Login user

userDao.login(userName, pwd)

UserDao.login method

public boolean login(String userName, String password) {
AndFilter filter = new AndFilter();
filter.and(new EqualsFilter("objectclass", "person")).and(new EqualsFilter("cn", userName));
return ldapTemplate.authenticate(DistinguishedName.EMPTY_PATH, filter.toString(), password);
}

Add user to a Group

userDao.addUserToGroup(userName, "AWGrupo1");

UserDao.addUserToGroup method

In order to assign a user to a specific Group in the AD we need to get a reference to the group and add the new user to its attribute “member”,

   1: public void addUserToGroup(String userName, String group) {

   2:     try {

   3:         DirContextAdapter dirContext = (DirContextAdapter) ldapTemplate.lookup(getDnFrom(userName));

   4:         String dnUserFull = dirContext.getStringAttribute(DISTINGUISHED_NAME_ATTR_NAME);

   5:         DirContextOperations groupContextOperations = ldapTemplate.lookupContext(getDnFrom(group));

   6:         String[] currentMembers = groupContextOperations.getStringAttributes(MEMBER_ATTR_NAME);

   7:         List<String> dnUserFullList = new ArrayList<String>();

   8:         if (currentMembers != null && currentMembers.length > 0) {

   9:             dnUserFullList.addAll(Arrays.asList(currentMembers));

  10:         }

  11:         dnUserFullList.add(dnUserFull);

  12:         groupContextOperations.setAttributeValues(MEMBER_ATTR_NAME, dnUserFullList.toArray(new String[dnUserFullList.size()]));

  13:         ldapTemplate.modifyAttributes(groupContextOperations);

  14:     } catch (Throwable e) {

  15:         throw new LdapException("Problem adding user:[" + userName + "] to Group:[" + group + "]", e);

  16:     }

  17: }

Line 4: Getting the full DN for the user that will be added to the group.

Line 5: Getting a reference to the Group

Line 6: Getting the list of current users of the Group

Line 11: Adding the user to the others users of the Group

Line 12: Setting the attribute “member” with the new list of users

Line 13: Saving the modifications.

Show all users

List<User> allUsers = userDao.getAllUsers();
System.out.println("There are:[" + allUsers.size() + "] users");
for (User user : allUsers) {
System.out.println(user);
}

UserDao.getAllUsers method

We will use a ContextMapper in order to populate the User info with the values that the AD will return.

public List<User> getAllUsers() {
SearchControls controls = new SearchControls();
controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
return ldapTemplate.search("", "(objectclass=person)", controls, new UserContextMapper());
}
 
 
public class UserContextMapper extends AbstractContextMapper {
@Override
protected Object doMapFromContext(DirContextOperations context) {
User user = new User();
user.setUserName(context.getStringAttribute("cn"));
user.setFirstName(context.getStringAttribute("givenName"));
user.setLastName(context.getStringAttribute("sn"));
user.setEmailAddress(context.getStringAttribute("userPrincipalName"));
user.setMemberOf(context.getStringAttribute("memberOf"));
user.setDisplayName(context.getStringAttribute("displayName"));
return user;
}
}

DropBox Diet puzzle–Solution

February 12th, 2011

Source Code: DropBox Diet Solution

The problem was taken from:The Dropbox Diet

In order to solve this puzzle we have followed the next steps:

1) Separate the activities in two groups, one of them with positives values of calories and the other with negative values.

2) Sort each of the two groups upwards.

3) Under the algorithm to be used, in the most cases only for one of the lists all combinations of its values will be calculated. So it’s important to always use the small list for that case.

4) The algorithm used always discarded the unnecessary combinations.

To understand the solution it is needed to known the following:

Class Description
Activity Represents an activity with name and calorie value
ActivityGroup Represents one or more activities 
AbstractActivityGroupProvider Base class that has two implementations: SequentialGroupProvider, CombinationSet.
It is used to generate all of the groups
SequentialGroupProvider Generate all of the possible groups that it can get from the different combinations of the list’s elements.
CombinationSet Generate all of the groups that belong to Combination Set (Check the below table to know what is a combination set)
CombinationSetManager It is used to search the group in all of the Combinations Set that has groups with the same number of activities
CombinationsGenerator Generate all of the possible combinations of the list’s indexes. For instance for three elements exist the following combinations: [0],[1],[2],[0, 1],[0, 2],[1, 2],[0, 1, 2]
ActivityCollector Provide all of the activities that have to be processed. 

In order to know the logic of the combinations see the following example with all of the combinations for 4 elements:

Comb of 1 Comb of 2 Comb of 3 Comb of 4
3 03 13 23 013 023 123 0123
2 02 12   012      
1 01            
0              

CombinationSet is each one of the columns.

Also is important to know the following characteristics of the groups that are in the combinations set:
- The groups are sorted upwards
- The first group of one combination set is greater or equal than the first group of the previous combination set.

The following is the method that has the core part of the searching:

   1: public List<ActivityGroup> getActivityGroupsThatSumZeroCalories() {

   2:     SequentialGroupProvider actGroupProvider = new SequentialGroupProvider(smallList);

   3:     List<ActivityGroup> activityGroups = new ArrayList();

   4:     ActivityGroup activityGroupToFind = actGroupProvider.getNextGroup();

   5:     CombinationSet combSetInitial = null;

   6:     while (activityGroupToFind != null) {

   7:         SearchResult searchResult = search(activityGroupToFind, bigList, combSetInitial);

   8:         if (searchResult.equivalentGroupWasFound()) {

   9:             ActivityGroup equivalentGroup = searchResult.getEquivalentGroup();

  10:             activityGroups.add(ActivityGroup.union(activityGroupToFind, equivalentGroup));

  11:             smallList.removeAll(activityGroupToFind.getActivities());

  12:             bigList.removeAll(equivalentGroup.getActivities());

  13:             recalculateSmallAndBigList();

  14:             actGroupProvider.refresh(smallList);

  15:             combSetInitial = null;

  16:             activityGroupToFind = actGroupProvider.getNextGroup();

  17:         } else {

  18:             int lastValue = activityGroupToFind.getValue();

  19:             if (searchResult.groupToFindWasGreaterThanAllElements()) {

  20:                 activityGroupToFind = actGroupProvider.getNextGroupWithValueLessThan(lastValue);

  21:             } else {

  22:                 activityGroupToFind = actGroupProvider.getNextGroup();

  23:                 combSetInitial = searchResult.getFirstCombSetGreater();

  24:                 if (combSetInitial != null && activityGroupToFind != null) {

  25:                     if (activityGroupToFind.getValue() < lastValue) {

  26:                         combSetInitial = null;

  27:                     }

  28:                 }

  29:             }

  30:         }

  31:     }

  32:     return activityGroups;

  33: }

Line 2: It is defined the object that will travel the short list

Line 7: The ActivityGroup is searched in the BigList

Line 8 –16 : If an equivalent group was found then the both groups are added to the solutions and all of their activities are removed from small and big list and then both are recalculated.

Line 18 – 29 : If equivalent group was not found then the next group to be find will be obtained based on the results of the previous searching.

Jugar Mario Bross con los ojos

August 4th, 2010

Fuente: http://trippletech.com/news/1691 

Un grupo de ingenieros lograron controlar un Nintendo con el movimiento de los ojos.

La idea fue colocar electrodos alrededor de los ojos para monitorear el movimiento de los mismos y luego hacer que el Nintendo acepte estos movimientos como datos de entrada.

Prototipo de un “Mouse Invisible” – MIT

July 9th, 2010

Fuente: http://techland.com/2010/07/09/mits-mouseless-project-is-an-invisible-computer-mouse/

La idea es simular el efecto de usar un mouse pero sin la necesidad de tener un mouse real.

Para esto se usa dos cámaras infrarrojas capaces de identificar los detalles y la posición de la mano.

El usuario mueve la mano como si estuviera usando un mouse real y las cámaras y el programa capturan estos movimientos, los cuales luego son interpretados para generar los cambios de posición y funcionamiento del cursor de la pantalla.

Encontrando las validaciones configuradas en un bean

July 6th, 2010

Código Fuente: Ejemplo 3

A continuación vamos a revisar las clases y los métodos que nos permiten obtener información de las distintas validaciones que han sido configuradas en un bean.

Para tener acceso a las clases necesarias necesitamos usar la referencia del objeto Validator. Recordar que una manera simple de obtener esta referencia es hacer lo siguiente:

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();

BeanDescriptor

Esta es la clase principal desde la cual podemos tener acceso a la información referente a las validaciones configuradas de un bean.

Para tener acceso a este objeto necesitamos llamar al método: “getConstraintsForClass” del “validator” enviando como parámetro la clase del bean del que deseamos obtener la información.

BeanDescriptor puertaBeanDescriptor = validator.getConstraintsForClass(Puerta.class);
 
Ya teniendo la referencia al “BeanDescriptor” podemos usar su métodos:

isBeanConstrained()

Este método nos indica si el bean tiene o no configurado alguna validación. Por ejemplo, teniendo en cuenta la definición de la clase Puerta

public class Puerta {
}

el método va a devolver false.

BeanDescriptor puertaBeanDescriptor = validator.getConstraintsForClass(Puerta.class);
System.out.println("Puerta tiene algún constraint:" + puertaBeanDescriptor.isBeanConstrained());

hasConstraints()

Este método nos indica si existen “constraints” a nivel de clase. Por ejemplo asumiendo la siguiente definición de la clase Carro:

public class Carro {
private String codigo;
@NotNull(groups=GrupoBasico.class)
private String numeroPlaca;
@NotNull
private String anio;
@Valid
private Motor motor;

}

el método va a devolver false:

BeanDescriptor carroBeanDescriptor = validator.getConstraintsForClass(Carro.class);
System.out.println("Carro tiene algún constraint de Clase:" + carroBeanDescriptor.hasConstraints());

getConstraintDescriptors()

Este método nos devuelve todos los constraints a nivel de clase que existen, para el caso de la clase Carro nos devolverá un Set de tamaño CERO.

getConstrainedProperties()

Nos devuelve todos los constraints aplicados a los atributos que existen en un bean. Por ejemplo para la clase Carro el tamaño del siguiente Set será 3.

Set<PropertyDescriptor> constraintProperties = carroBeanDescriptor.getConstrainedProperties();
System.out.println("Carro tiene constraints a nivel de Atributos Nro:" + constraintProperties.size());

getConstraintsForProperty(String property)

Nos devuelve un PropertyDescriptor el cual es un objeto que puede tener uno o más ConstraintDescriptors. Por ejemplo para la clase Carro tendríamos lo siguiente:

Para el atributo “codigo”, este método devolverá null, ya que este atributo no tiene ninguna anotación de validación.

PropertyDescriptor codigoPropertyDescriptor = carroBeanDescriptor.getConstraintsForProperty("codigo");
System.out.println("Código property descriptor:" + codigoPropertyDescriptor);

Pero para el atributo "numeroPlaca" si devolverá un objeto con toda la información de validación de ese atributo.

PropertyDescriptor nroPlacaPropertyDescriptor = carroBeanDescriptor.getConstraintsForProperty("numeroPlaca");
System.out.println("Número de Placa property descriptor:" + nroPlacaPropertyDescriptor);

PropertyDescriptor

Contiene toda la información de validación de un atributo de un bean. Comparte varios métodos con el “BeanDescriptor”, ya que ambos implementan la interface:”ElementDescriptor”.

getConstraintDescriptors()

Devuelve todos los ContraintDescriptors asociados a un atributo del bean. Por ejemplo, en el siguiente caso devuelve un Set con un ConstraintDescritpor.

PropertyDescriptor nroPlacaPropertyDescriptor = carroBeanDescriptor.getConstraintsForProperty("numeroPlaca");
System.out.println("Número de Placa property descriptor:" + nroPlacaPropertyDescriptor);
System.out.println("Número de Placa property constraint descriptors:" + nroPlacaPropertyDescriptor.getConstraintDescriptors());

ConstraintDescriptor

Es la clase que tiene toda la información del constraint.

Por ejemplo para el atributo “numeroPlaca” podemos obtener este objeto de la siguiente manera:

PropertyDescriptor nroPlacaPropertyDescriptor = carroBeanDescriptor.getConstraintsForProperty("numeroPlaca");
System.out.println("Número de Placa property descriptor:" + nroPlacaPropertyDescriptor);
ConstraintDescriptor nroPlacaConstraintDescriptor = nroPlacaPropertyDescriptor.getConstraintDescriptors().iterator().next();

Entre sus principales métodos tenemos:

getAnnotation()

Nos devuelve la anotación que usa este constraint.

getAttributes()

Devuelve un Map con todos los atributos seteados en la anotación del constraint.

getGroups()

Devuelve los grupos a los que pertenece el constraint.

getPayload()

Devuelve el payload asociado al constraint.

A tener en Cuenta

Para poder encontrar un constraint teniendo en cuenta algunos filtros podemos usar el método “findConstraints()”, tanto en el “BeanDescriptor” como en el “PropertyDescriptor”

Por ejemplo en el siguiente código estamos buscando todos los constraints que estén configurados en el atributo “numeroPlaca”

Set<ConstraintDescriptor<?>> nroPlacaConstraintDescriptors = 
nroPlacaPropertyDescriptor.findConstraints().
declaredOn(ElementType.FIELD).
getConstraintDescriptors();
System.out.println("Número de Placa Constraint Descriptors:"+nroPlacaConstraintDescriptors);

Creando custom constraints

July 4th, 2010

Código Fuente: Ejemplo 1

Hibernate Validator viene con una serie de constraints ya creados, estos incluyen a los constraints que son parte del jsr303. En el siguiente link podemos encontrar una descripción de estos Constraints.

Por otro lado, nosotros también podemos crear nuestros propios constraints, los pasos necesarios para hacer esto, serán descritos a continuación.

Creando constraints simples

Vamos a crear dos constraints, uno a nivel de atributo y otro a nivel de clase.

Constraint a nivel de atributo

Vamos a crear un constraint que valide que un atributo de tipo String sólo tenga letras. Este constraint también debería poderse colocar al “getter” del atributo.

Necesitamos hacer lo siguiente:

- Crear la anotación que será usada para las validaciones.
   1: @Target({METHOD, FIELD, ANNOTATION_TYPE})

   2: @Retention(RUNTIME)

   3: @Constraint(validatedBy = OnlyLettersValidator.class)

   4: @Documented

   5: public @interface OnlyLetters {

   6:     String message() default "com.aw.jsr303.ejemplo001.constraints.onlyletters";

   7:     Class<?>[] groups() default {};

   8:     Class<? extends Payload>[] payload() default {};

   9: }

Línea 1: Se indica que la anotación puede usarse tanto en atributos, métodos y anotaciones (esto es necesario para crear constraint compuestos).

Línea 3:  Se define que la anotación será un constraint y que la clase que se encargará de realizar la validación será: “OnlyLettersValidator”

Línea 6: Se define el mensaje por default que se usará cuando falle esta validación.

Línea 7: Se define los grupos a los cuales puede pertenecer este constraint.

Línea 8:  Se define los “payload” que es una manera de agregar mayor información al Constraint, por ejemplo podríamos definir algunas interfaces que indiquen el nivel de severidad del constraint y enviar esta información como payload.

- Crear la clase que se encargue de ejecutar la validación.
   1: public class OnlyLettersValidator implements ConstraintValidator<OnlyLetters, String> {

   2:     public void initialize(OnlyLetters annotation) {

   3:     }

   4:     public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {

   5:         return allAreLetters(value);

   6:     }

   7:     private boolean allAreLetters(String str) {

   8:         if (str == null) {

   9:             return false;

  10:         }

  11:         int stringSize = str.length();

  12:         for (int i = 0; i < stringSize; i++) {

  13:             if (Character.isLetter(str.charAt(i)) == false) {

  14:                 return false;

  15:             }

  16:         }

  17:         return true;

  18:     }

  19: }

Línea 1: La clase que finalmente realizará la validación debe implementar la interface “ConstraintValidator” y debe poner como tipos, la anotación que usará para el constraint “OnlyLetters” y el tipo de dato que será validado “String” (ConstraintValidator)

Línea 2: Este método será llamado al inicio para obtener la información que se necesite de la anotación.

Línea 4: Este método es el que realiza la validación, en este caso va a recibir como parámetro el valor del atributo a ser validado.

Modo de uso del constraint
public class ComprobanteDePago {
@OnlyLetters
private String codigoInterno;

Constraint a nivel de clase

Vamos a crear un constraint a nivel de clase. Se validará que la clase ComprobanteDePago tenga obligatoriamente seteado el RUC siempre y cuando el tipo de documento sea FACTURA.

Necesitamos hacer lo siguiente:

- Crear la anotación que será usada para las validaciones.
   1: @Target({TYPE})

   2: @Retention(RUNTIME)

   3: @Constraint(validatedBy = RucInfoValidator.class)

   4: @Documented

   5: public @interface RucInfoChecker {

   6:     String message() default "com.aw.jsr303.ejemplo001.constraint.rucinfochecker";

   7:     Class<?>[] groups() default {};

   8:     Class<? extends Payload>[] payload() default {};

   9: }

Línea 1: Se indica que la anotación sólo se puede usar a nivel de clase.

Línea 3:  Se define que la anotación será un constraint y que la clase que se encargará de realizar la validación será: “RucInfoValidator”

- Crear la clase que se encargue de ejecutar la validación.
   1: public class RucInfoValidator implements ConstraintValidator<RucInfoChecker, ComprobanteDePago> {

   2:     public void initialize(RucInfoChecker annotation) {

   3:     }

   4:     public boolean isValid(ComprobanteDePago comprobanteDePago, ConstraintValidatorContext constraintValidatorContext) {

   5:         if (comprobanteDePago.getTipo() == ComprobanteTipo.FACTURA){

   6:             String ruc =comprobanteDePago.getRuc();

   7:             return ruc != null && !"".equals(ruc);

   8:         }

   9:         return true;

  10:     }

  11: }

Línea 1: La clase que finalmente realizará la validación debe implementar la interface “ConstraintValidator” y debe poner como tipos, la anotación que usará para el constraint “RucInfoChecker” y el tipo de dato que será validado “ComprobanteDePago” (ConstraintValidator)

Línea 2: Este método será llamado al inicio para obtener la información que se necesite de la anotación.

Línea 4: Este método es el que realiza la validación, en este caso va a recibir como parámetro la instancia de la clase DocumentoDePago.

Modo de uso del constraint
@RucInfoChecker
public class ComprobanteDePago {

Creando constraints compuestos

Los constraints compuestos son aquellos que agrupan más de un constraint. A continuación vamos a crear un constraint que agrupe @NotNull y @OnlyLetters. En estos casos sólo es necesario crear la anotación para el nuevo constraint.

   1: @NotNull

   2: @OnlyLetters

   3: @Target({METHOD, FIELD})

   4: @Retention(RUNTIME)

   5: @Constraint(validatedBy = {})

   6: @Documented

   7: @ReportAsSingleViolation

   8: public @interface OnlyLettersNotNull {

   9:     String message() default "com.aw.jsr303.ejemplo001.constraints.onlylettersnotnull";

  10:     Class<?>[] groups() default {};

  11:     Class<? extends Payload>[] payload() default {};

  12: }

Línea 1: Se indica que se incluirá el constraint @NotNull

Línea 2: Se indica que se incluirá el constraint @OnlyLetters

Línea 5:  Se define que la anotación será un constraint, no se indica la clase que será encargada de la validación, ya que es un constraint compuesto que en esencia sólo agrupa otros constraints.

Línea 7: Usando la anotación “@ReportAsSingleViolation” podemos indicar que por más que muchos constraints – que forman parte de este constraint compuesto – fallen, un sólo error debe ser reportado, sin esta anotación se reportarán todos los errores individuales que se encuentren.

Modo de uso del constraint

public class Item {
@OnlyLettersNotNull
private String coProducto;

Validar Beans – Agrupación de Validaciones

July 3rd, 2010

Código Fuente: Ejemplo 2

Para poder separar las validaciones de acuerdo a algún criterio debemos usar los grupos (“groups”)

Declaración de Grupos de validación

Normalmente para definir un grupo se usa una interface sin métodos, pero también se pueden usar clases. A continuación declararemos tres grupos de validación.

public interface TodasLasValidaciones {
}
public interface ValidacionesPrimerNivel {
}
public interface ValidacionesSegundoNivel {
}

Uso de los grupos de validación

Una vez definidos los grupos de validación los podemos usar en cualquier anotación. Por ejemplo:

   1: public class Ciudadano {

   2:     @NotNull(message = "Se debe setear el estado civil", groups = ValidacionesPrimerNivel.class)

   3:     private EstadoCivil estadoCivil;

   4:     @Min(value = 18, groups = ValidacionesSegundoNivel.class)

   5:     private int edad;

   6:     @NotNull

   7:     private String documentoDeIdentidad;

   8: }

Línea 2-3 : Acá estamos indicando que la validación de @NotNull pertenece al grupo “ValidacionesPrimerNivel”.

Línea 4-5 : Acá estamos indicando que la validación de @Min pertenece al grupo “ValidacionesSegundoNivel”.

Línea 6-7 : Acá estamos indicando que la validación de @NotNull pertenece al grupo “Default”, que es un grupo especial al cual pertenecen todas las validaciones cuyo grupo no ha sido seteado.

También se debe tener en cuenta que el valor de “groups” puede ser un arreglo con varios grupos.

Validar usando grupos

Los grupos se pueden usar en cualquiera de los métodos:”validate”, “validateProperty” o “validateValue” de la clase Validator.

Ejecutando las validaciones por defecto

   1: Validator validator = validatorFactory.getValidator();

   2: Ciudadano ciudadano = new Ciudadano();

   3: Set<ConstraintViolation<Ciudadano>> errors = validator.validate(ciudadano);

   4: System.out.println("Errores: Nro:<" + errors.size()+">" + errors);

   5: Set<ConstraintViolation<Ciudadano>> errorsDefault = validator.validate(ciudadano, Default.class);

   6: System.out.println("Errores usando Default:Nro:<" +  errorsDefault.size() +">" +errorsDefault);

Línea 3 : Se llama al método “validate” sin indicar ningún grupo.

Línea 5 : Se llama al método “validate” enviando como parámetro el grupo “Default”.

Se debe notar que la línea 3 y la línea 5 son equivalente en cuanto realizan la validación de todas las anotaciones que no tienen seteado explícitamente el atributo “groups”

Ejecutando las validaciones del primer nivel

Acá se van a ejecutar sólo las validaciones que pertenecen al grupo “ValidacionesPrimerNivel”.

Set<ConstraintViolation<Ciudadano>> errorsPrimerNivel = validator.validate(ciudadano,ValidacionesPrimerNivel.class);
System.out.println("Errores Primer Nivel:Nro:<" + errorsPrimerNivel.size()+">"+errorsPrimerNivel);

Podemos ver en el extracto anterior como enviamos el grupo como un parámetro más del método “validate”.

Ejecutando las validaciones del segundo nivel

Acá se van a ejecutar sólo las validaciones que pertenecen al grupo “ValidacionesSegundoNivel”.

Set<ConstraintViolation<Ciudadano>> errorsSegundoNivel = validator.validate(ciudadano,ValidacionesSegundoNivel.class);
System.out.println("Errores Segundo Nivel:Nro:<" + errorsSegundoNivel.size()+">"+errorsSegundoNivel);

Al igual que en caso anterior tenemos que enviar el grupo como parámetro al método “validate”

Ejecutar todas las validaciones

Para ejecutar todas las validaciones necesitamos enviar como parámetros del método “validate” todo los grupos que se han usado, incluyendo el “Default”, como se puede ver en el siguiente extracto:

Set<ConstraintViolation<Ciudadano>> errorsTodasLasValidaciones = validator.validate(ciudadano,Default.class,ValidacionesPrimerNivel.class,ValidacionesSegundoNivel.class);
System.out.println("Todos los Errores:Nro:<" + errorsTodasLasValidaciones.size()+">"+errorsTodasLasValidaciones);

Claro que  también podemos crear un grupo que extienda todos los grupos.

public interface SuperGrupo extends Default,
ValidacionesPrimerNivel,
ValidacionesSegundoNivel {
}

y ejecutar la validación usando este nuevo grupo.

Set<ConstraintViolation<Ciudadano>> errorsSuperGrupo = validator.validate(ciudadano,SuperGrupo.class);
System.out.println("Todas las Validaciones :Nro:<" + errorsSuperGrupo.size()+">"+errorsSuperGrupo);

Ejecutar los grupos de validaciones en secuencia

Acá lo que se desea hacer, es poder ejecutar las validaciones teniendo en cuenta un orden entre los grupos que se desean validar, además de eso se necesita que si un grupo genera errores de validación ya no se ejecute el siguiente grupo de validaciones.

Para poder hacer lo anterior usaremos la anotación: “@GroupSequence”. Veamos el siguiente ejemplo:

@GroupSequence({Default.class,ValidacionesPrimerNivel.class,ValidacionesSegundoNivel.class})
public interface TodasLasValidaciones {
}

Acá estamos definiendo el grupo: “TodasLasValidaciones” y como estamos usando la anotación “@GroupSequence” podemos decir que:

-  Este grupo incluye a los grupos: Default, ValidacionesPrimerNivel, ValidacionesSegundoNivel.

- Si fallan las validaciones de uno de los grupos, las validaciones de los siguientes grupos no serán ejecutadas. Por ejemplo si las validaciones del grupo Default fallan, las validaciones de los otros 2 grupos no serán ejecutadas.

Para ejecutar las validaciones de este grupo “TodasLasValidaciones” llamamos al método “validate” enviando como parámetro el grupo.

Set<ConstraintViolation<Ciudadano>> errorsTodasLasValidacionesEnSecuencia = validator.validate(ciudadano,TodasLasValidaciones.class);
System.out.println("Validación en secuencia:Nro:<" + errorsTodasLasValidacionesEnSecuencia.size()+">"+errorsTodasLasValidacionesEnSecuencia);

Modificando la secuencia de grupos de validación por defecto de una clase.

Para entender esto veamos el siguiente ejemplo:

Teniendo en cuenta la siguiente definición:

public class Carro {
@NotNull
private String numeroPlaca;

@NotNull(groups = ValidacionesPrimerNivel.class)
private Integer numeroAsientos;

Si ejecutamos lo siguiente:

Carro carro  = new  Carro();
Set<ConstraintViolation<Carro>> erroresEnCarro = validator.validate(carro);
System.out.println("Carro Errores: Nro:<" + erroresEnCarro.size()+">" + erroresEnCarro);

Solamente se ejecutará la validación del atributo “numeroPlaca” ya que tiene la anotación @NotNull  sin ningún grupo, por lo cual se asume que pertenece al grupo “Default”. La validación sobre “numeroAsientos” no será ejecutada ya que pertenece al grupo “ValidacionPrimerNivel”.

Ahora lo que deseamos es que al llamar a “validator.validate(carro)” se ejecuten tanto las validaciones del grupo “Default” como las del grupo “ValidacionPrimerNivel”. Para poder hacer esto debemos agregar la anotación “@GroupSequence” a la clase Carro, la cual quedaría así:

@GroupSequence({Carro.class, ValidacionesPrimerNivel.class})
public class Carro {

De esta manera al llamar a: “validator.validate(carro)”, primero se ejecutarían las validaciones por defecto, y si no hay errores luego se ejecutarían las validaciones del grupo “ValidacionPrimerNivel”.

Se debe notar que en este caso en la anotación “@GroupSequence” se está usando “Carro.class” para indicar el grupo “Default”.

Validar Beans

July 2nd, 2010

Código Fuente: Ejemplo 1

Esta especificación considera una sola clase para realizar todas las validaciones. Esta clase es: “Validator” y la manera más simple de tener una referencia a este objeto es hacer lo siguiente:

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();

Ahora bien, teniendo en cuenta la siguiente declaración:

public class ComprobanteDePago {
private Date fechaDeEntrega;
@Future
public Date getFechaDeEntrega() {
return fechaDeEntrega;
}
}

y teniendo la referencia al objeto Validator podemos llevar a cabo las siguientes validaciones:

Validar el valor de un atributo

Si sólo deseamos ejecutar la validación @Future que está marcando el “getter” del atributo “fechaDeEntrega” tendríamos que hacer lo siguiente:

Set<ConstraintViolation<ComprobanteDePago>> errorsProperty =  validator.validateProperty(comprobante,"fechaDeEntrega");
System.out.println("Errores del atributo <fechaDeEntrega>:"+ errorsProperty);

Acá estamos llamando al método “validateProperty” del validator, enviándole como parámetros la instancia del objeto y el nombre del atributo que se desea validar.

Validar si un valor cumple con las restricciones de un atributo

Si sólo deseamos validar que cierto valor cumple con las restricciones de un atributo, sin tener que setear el valor a la instancia del objeto debemos hacer lo siguiente:

Set<ConstraintViolation<ComprobanteDePago>> errorsPropertyValue =  validator.validateValue(ComprobanteDePago.class,"fechaDeEntrega",new Date());
System.out.println("Errores del valor que se quiere setear al atributo <fechaDeEntrega>:"+ errorsPropertyValue);

Acá estamos llamando al método “validateValue” del validator, enviándole como parámetro la clase, el nombre del atributo y el valor que se desea validar.

Validar todo el objeto

Para validar todas las restricciones asociadas a un objeto debemos hacer lo siguiente:

ComprobanteDePago comprobante = new ComprobanteDePago();
Set<ConstraintViolation<ComprobanteDePago>> errors = validator.validate(comprobante);
System.out.println("Errores:"+ errors);

Acá estamos llamando al método “validate” del validator y le estamos enviando la instancia del objeto. De esta manera obtendremos todos los errores luego de que todas las restricciones hayan sido revisadas.

A tener en cuenta

La anotación “@Valid” no es tomada en cuenta cuando se usa cualquiera de los métodos: “validateProperty” o “validateValue”.

JSR 303 – Bean Validation

July 1st, 2010

Código Fuente: Ejemplo 1

Esta especificación define el API para poder validar JavaBeans tanto en Java EE como en Java SE.

El objetivo de esta especificación es proveer los mecanismos necesarios para poder declarar y validar Beans, así como también proveer de un repositorio de los constraints y un API para hacer las consultas acerca de los constraints existentes.

Implementaciones

Actualmente existe 2 implementaciones:

- Hibernate Validator, que es la primera implementación y

- Apache Bean Validation tiene soporte para la validación de métodos, lo cual no es parte de la especificación 303 pero es algo que está incluido en los anexos de la misma, lo de malo es que recién esta en su versión: 0.1-incubating.

Para ver los detalles usaremos la implementación: Hibernate Validator.

Declaración de constraints

Los constraints se pueden declarar usando xml o anotaciones, nosotros nos limitaremos a mostrar el uso utilizando anotaciones.

Se pueden dar los siguientes casos:

A nivel de atributo

Sirve para validar que el valor del atributo cumpla ciertos requisitos. Se debe tener en cuenta que el atributo puede tener cualquier nivel de visibilidad (public, prívate, etc), pero no debe ser estático (static).

public class ComprobanteDePago {
    @OnlyLetters
    private String codigoInterno;
    @NotNull
    private ComprobanteTipo tipo;

 

A nivel de getters

Sirve para validar que la propiedad relacionada al “getter” cumpla ciertos requisitos. Al validar se toma en cuenta el valor que retorna el método “getter”.

@Future
public Date getFechaDeEntrega() {
    return fechaDeEntrega;
}

 

A nivel de clase o interface

Sirve para hacer validaciones que incluyan más de un campo, en este caso se tiene la referencia a todos los datos de la instancia. Y se puede validar todas las posibles combinaciones del estado actual de la instancia.

@RucInfoChecker
public class ComprobanteDePago {

 

Validación de “graphs of object”.

Esto nos permite validar objetos de manera recursiva. Para esto se debe usar la anotación @Valid. Veamos el siguiente ejemplo para clarificar la idea:

public class Cliente {
    @NotNull
    private String nombre;
}

public class ComprobanteDePago {
    @Valid
    @NotNull
    private Cliente cliente;
}

Acá podemos ver que la clase “ComprobanteDePago” tiene como atributo la clase “Cliente”, además podemos ver que este atributo tiene 2 anotaciones @Valid, @NotNull, teniendo en cuenta esta configuración al momento de validar la clase: “ComprobanteDePago” también se ejecutará la validación de la clase “Cliente”, por tanto se verificará que el atributo “nombre” sea distinto de null.

Este comportamiento también funciona cuando usamos colecciones, arreglos y en general cualquier campo que implemente “Iterable”. En estos casos se ejecuta la validación de cada uno de los elementos de las colecciones.

Veamos el siguiente ejemplo:

public class ComprobanteDePago {
    @Valid
    @Size(min=1, max=20)
    private List items = new ArrayList();
 
Acá @Valid está siendo usado con una lista (List), esto nos indica que cuando se valide la clase “ComprobanteDePago” se ejecutará también la validación de todos los objetos “Item” que estén en la lista.  

A tener en cuenta:

a) Los constraints puedes ser usados tanto en clases como en interfaces.

b) Los constraints se heredan, tanto si se usa extend o implement.

c) En caso de que se sobreescriba un método que ya tenía constraints al final se juntan los constraints del método sobreescrito y el método actual.