Instalando Confluence en Ubuntu 12.04 LTS

En esta entrada se instalará la herramienta colaborativa Confluence (la versión actual es la 4.2.6) de Atlassian.

La idea es acabar con un servidor a través del cual podamos acceder a Confluence a través de la URL http://nuestro-servidor/wiki/. Para ello haremos uso de Apache como servidor Web, Tomcat como servidor de aplicaciones (que viene empotrado en el paquete standalone de Confluence) y de MySQL como gestor de bases de datos para mantener la información de la Wiki.

Toda la información aquí expuesta es un resumen adaptado a las necesidades que nos hemos encontrado para instalar nuestro Confluence. En la documentación oficial de Confluence de Atlassian (por supuesto servida desde un Confluence :D) está todo explicado estupendamente en caso de que lo aquí expuesto no sea exactamente el entorno que tengáis pensado montar.

Java

Porque sin Java no hacemos nada, instalaremos la JDK con un apt-get instalaremos la JDK. En este caso es opcional, y es posible sólo con la JRE. Sin embargo, como en nuestro caso instalaremos también un servidor de integración continua, aprovecharemos e instalaremos la JDK:

sudo apt-get install openjdk-6-jdk

Si no está instalado, es un buen momento para hacerse un café porque tardará un rato dependiendo de la conexión.

Instalar MySQL

Esto la verdad es que tiene poco misterio. Se instalará a golpe de apt-get de la siguiente manera:

sudo apt-get install mysql-server

En algún momento pedirá la contraseña para el usuario root del gestor. Viene bien sabérsela, pero para Confluence crearemos un usuario propio. De esta forma no usamos a root para todo y tenemos más controlado el acceso al gestor de bases de datos en caso de que no sea la única aplicación que haga uso de éste. Para ello accederemos a la consola de MySQL de la siguiente manera:

mysql -u root -p

Una vez introducido, nos solicitará la contraseña de root. Esperemos que esta sea la última vez que la usamos (por lo menos para esto :D). Una vez dentro, crearemos la base de datos para confluence y el usuario que accederá a ésta de la siguiente manera:

CREATE DATABASE confluence CHARACTER SET UTF8;
GRANT ALL on confluence.* TO 'confluence'@'localhost' IDENTIFIED BY 'cfpass';
FLUSH PRIVILEGES;
EXIT

Y hasta aquí la instalación de MySQL.

Creación de usuario para confluence

Al igual que en en el gestor de bases de datos queremos tener parcelados a los usuarios para que accedan a sus respectivas bases de datos, en el sistema también viene bien que sea así. Por tanto crearemos un usuario para Confluence en el sistema. Para ello:

sudo useradd -s /sbin/nologin confluence

Instalación de Confluence

En nuestro caso para instalar Confluence haremos uso de la versión standalone. Para ello nos la descargaremos en el servidor con wget, lo descomprimiremos y lo pondremos en el «directorio de instalación» (en nuestro caso /usr/local/confluence):

wget http://www.atlassian.com/software/confluence/downloads/binary/atlassian-confluence-4.2.6.tar.gz
tar -zxvf atlassian-confluence-4.2.6.tar.gz
sudo mv atlassian-confluence-4.2.6 /usr/local/confluence
sudo chown -R confluence /usr/local/confluence/
sudo chgrp -R confluence /usr/local/confluence/

Ahora habrá que configurar el «directorio de trabajo» donde Confluence ficheros de configuración, locks y temporales (entre otros artefactos). Esto se en el fichero confluence-init.properties que en el caso del ejemplo se encuentra en la ruta /usr/local/confluence/confluence/WEB-INF/classes/confluence-init.properties. Nosotros estableceremos la ruta /var/confluence como directorio de trabajo y por tanto añadiremos la siguiente línea al fichero de configuración:

confluence.home=/var/confluence

Y crearemos el directorio al que hacemos referencia:

sudo mkdir /var/confluence
sudo chown -R confluence /var/confluence
sudo chgrp -R confluence /var/confluence

Por último cambiaremos tanto el contexto en el que se despliega la aplicación como el puerto en el que escucha (nos vendrá bien para más adelante). Para ello habrá que modificar el fichero /usr/local/confluence/conf/server.xml y modificar las líneas pertinentes. En nuestro caso desplegaremos la aplicación en el contexto /confluence escuchando por el puerto 9090. Para ello habrá que modificar el atributo port del elemento Server (para que no entre en conflicto con otros posibles servidores que tengamos en el servidor), el elemento port del elemento Connector y el atributo path del elemento Context. Pongo un fragmento del código (desde el comienzo del fichero hasta la modificación del contexto, que es la última) para que se vea cómo ha quedado después de la modificación:

<server port="9000" shutdown="SHUTDOWN" debug="0">
  <service name="Tomcat-Standalone">
      <connector className="org.apache.coyote.tomcat4.CoyoteConnector" port="9090" minProcessors="5" maxProcessors="75" enableLookups="false" redirectPort="8443" acceptCount="10" debug="0" connectionTimeout="20000" useURIValidationHack="false" URIEncoding="UTF-8"/>
      <engine name="Standalone" defaultHost="localhost" debug="0">
        <host name="localhost" debug="0" appBase="webapps" unpackWARs="true" autoDeploy="false">
          <context path="/confluence" docBase="../confluence" debug="0" reloadable="false" useHttpOnly="true">
      ...

Y hasta aquí la configuración de Confluence propiamente dicha. Sin embargo, como queremos que Confluence se arranque cada vez que se arranca la máquina, añadiremos el script de arranque /etc/init.d/confluence con el siguiente contenido:

#!/bin/sh -e
# Confluence startup script

APP=confluence
USER=confluence
CATALINA_HOME=/usr/local/confluence
export JAVA_HOME=/usr/lib/jvm/java-6-openjdk-amd64

case "$1" in
  start)
    echo "Starting $APP"
    sudo /bin/su -m $USER -c "$CATALINA_HOME/bin/startup.sh &amp;> /dev/null"
    ;;
  stop)
    echo "Stopping $APP"
    sudo /bin/su -m $USER -c "$CATALINA_HOME/bin/shutdown.sh &amp;> /dev/null"
    echo "$APP stopped successfully"
    ;;
  restart)
    $0 stop
    sleep 5
    $0 start
    ;;
  *)
    echo "Usage: sudo service $APP {start|restart|stop}"
    exit 1
    ;;
esac

exit 0

Para arrancarlo basta con hacerlo ejecutable y lanzar el script de arranque como cualquier otro script.

sudo chmod u+x /etc/init.d/confluence
sudo service confluence start

Para instalarlo en el arrranque, con el siguiente comando se instala en todos los runlevels:

sudo update-rc.d confluence defaults

Si todo ha ido bien, en http://localhost:9090/confluence deberá aparecer la siguiente pantalla que indica que Confluence está arrancado y necesita la activación para continuar.

Nosotros le introduciremos nuestra clave y seleccionaremos la opción «Product installation». La siguiente pantalla (no la pongo) da a elegir entre base de datos empotrada o base de datos externa. Nosotros que molamos más que los Peta Zetas vamos a usar una base de datos externa de MySQL seleccionando dicha opción en el desplegable. Una vez hecho esto llegaremos a la siguiente pantalla donde elegiremos conectarnos vía JDBC. Una vez hecho esto nos aparecerá la pantalla de configuración

Configuración de la base de datos

Ojo con la cadena de conexión porque si usamos MySQL y queremos usar «non-latin characters» hay que añadir sí o sí el trozo useUnicode=true&characterEncoding=utf8. También en la cadena de conexión se encuentra el nombre de la base de datos, así que si la hemos llamado de otra forma distinta a confluence habrá que modificar la cadena con el nuevo nombre. Una vez le demos a continuar se crearán todas las tablas (tardará un rato) y nos dará a elegir distintas opciones que no merece la pena contar aquí. A partir de este punto tenemos nuestro Confluence instalado y funcionando. Ahora sólo nos queda configurar Apache para servir confluence a través del contexto /confluence.

Configuración de Apache

Configuraremos apache a través del módulo mod_proxy. Para ello instalaremos el módulo con apt-get.

sudo apt-get install libapache2-mod-proxy-html
sudo a2enmod proxy_html
sudo a2enmod proxy_http

Una vez instalado, crearemos un nuevo fichero para el sitio de confluence en los sitios de apache (en nuestro caso se encuentra en /etc/apache2/sites-available) con el siguiente contenido:


  Order deny,allow
  Allow from all

 
ProxyRequests       Off
ProxyPreserveHost On
ProxyPass           /confluence       http://localhost:9090/confluence
ProxyPassReverse    /confluence       http://localhost:9090/confluence

Por último habrá que activar el sitio para que apache lo reconozca y reiniciar apache. Para ello, un par de comandos:

sudo a2ensite confluence
sudo service apache2 restart

Ya debería estar instalado y funcionando en http://localhost/confluence.

Sin embargo, por lo menos en mi caso y seguramente a todo el que use la versión de 64 bits de Ubuntu Server, puede que nos dé un error de librerías. Esto es debido a que en la versión de 64 bits la librería libxml2.so.2 no está en /usr/lib sino que (después de un rato buscándola) resulta que está en /usr/lib/x86_64-linux-gnu/ (vaya por dios :D). Así que la solución en este caso es editar el fichero /etc/apache2/mods-available/proxy_html.load y cambiar la línea donde se hace referencia a la librería por la nueva, es decir, de:

LoadFile /usr/lib/libxml2.so.2
LoadModule proxy_html_module /usr/lib/apache2/modules/mod_proxy_html.so

a:

LoadFile /usr/lib/x86_64-linux-gnu/libxml2.so.2
LoadModule proxy_html_module /usr/lib/apache2/modules/mod_proxy_html.so

Para concluir

Como bonus track de todo esto está el hecho de que otras aplicaciones como Jira o Bamboo siguen el mismo patrón de instalación, y por tanto pueden instalarse con la misma guía casi cambiando únicamente el nombre. Bueno, es cierto que también habría que cambiar puertos y contextos, pero la caso es que es fácilmente reproducible.

Cualquier mejora a este proceso es bienvenida :D. Que paséis un feliz fin de semana!.

Creación de validadores con anotaciones

Este va a ser un ejemplo rápido sobre cómo crear validaciones con anotaciones a medida del estilo:

@NotEmpty
private String name;

No pretende ser una explicación rigurosa de todas las opciones (para eso existen unos manuales y unas referencias técnicas fabulosas.

Usamos la validación de beans Bean Validation JSR 303, en su implementación de Hibernate (Hibernate validator 4.1.0.Final)

Si, por ejemplo, al dar de alta un usuario, queremos comprobar que el usuario es único, y no queremos meternos en engorrosas comprobaciones e if’s anidados para ver si un usuario está o no dado de alta, podemos querer crear la anotación siguiente dentro del bean que nos interese:

@UniqueUser
private String username;

Y si no pasa la validación, podremos dar un error de validación sin necesidad de ensuciar el código.

Para ello primero se crea la anotación propiamente dicha, por ejemplo «UniqueUser.java».

@Target({ FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = UniqueUserConstraintValidator.class)
@Documented
public @interface UniqueUser {
	String message() default "{user.exists}";
	Class<?>[] groups() default {};
	Class<? extends Payload>[] payload() default {};
}

Lo interesante de aquí es:

  • El nombre que sigue a @interface UniqueUser, que será como llamaremos a la anotación, es decir, utilizaremos @UniqueUser cuando queramos usarla.
  • La línea @Target({ FIELD, ANNOTATION_TYPE }) dice que se podrán anotar campos, pero por ejemplo no métodos (que sería tan sencillo como añadir METHOD)
  • @Retention(RUNTIME) indica que la anotación se aplicará en tiempo de ejecución por reflection. Se escapa de este mini-manual explicarlo en profundidad.
  • @Constraint(validatedBy = UniqueUserConstraintValidator.class) nos indica que la anotación se validará con la clase UniqueUserConstraintValidator, que veremos a continuación.
  • El message es el texto del error. Puede estar internacionalizado en messages_i18n.properties, donde i18n es el locale pertinente.
  • Groups y payload se dejan como arrays vacíos.

La clase UniqueUserConstraintValidator tiene que implementar un ConstraintValidator, y quedaría de la siguiente manera:

public class UniqueUserConstraintValidator implements ConstraintValidator<UniqueUser, String> {

	@Inject
	private UserService userService;

	public void initialize(UniqueUser constraintAnnotation) {
		// No hacemos nada
	}

	public boolean isValid(String username,	ConstraintValidatorContext constraintContext) {

		if (userService.existsUserByUsername(username)) {
			return false;
		} else {
			return true;
		}
	}
}

Como puede verse, hemos inyectado (gracias a Spring) el servicio de usuarios que será el que compruebe si ya existe ese nombre de usuario en el sistema. En el método isValid es donde se valida realmente si el campo username cumple o no la validación, devolviendo true si pasa la validación y false en caso contrario.

Y de esta manera ya tendremos nuestra brand-new anotación @UniqueUser lista para comprobar si el usuario es único.

Tasks anotadas. Rápidas y fáciles con Spring 3.1

Hola a todos. Aquí va un artículo píldora para aquellos que deseen crear tareas lanzadas en background (e.g. recalcular índices, borrar datos temporales, etcétera) usando anotaciones con Spring 3.1. Y para ello nada mejor que un ejemplo. Se presupone que el lector sabe configurar un proyecto con Spring 3.1.

Supongamos que queremos una tarea que imprima en los logs de nuestra aplicación «qué día más bonito hace hoy!» porque nos encontramos tremendamente felices y queremos alegrarle el día al administrador de sistemas. De hecho, queremos alegrárselo tanto que se lo queremos imprimir cada medio segundo (seguro que el encanta tener 120 líneas/minuto para revisar en el log). Para ello, crearemos una clase como la siguiente:

[sourcecode language=java]
@Component
public class MakeMyDayTask {
private static final Logger LOGGER = Logger.getLogger(MakeMyDayTask.class.getName());

@Scheduled(fixedDelay = 500)
public void run() {
// Nivel severe porque así seguro que se ven
LOGGER.severe("qué día más bonito hace hoy!");
}
}
[/sourcecode]

Y nada más. Así de fácil. Bueno, en realidad hay un detalle más, pero es tan tonto que casi no merece la pena indicarlo. Salvo para el que escribe, que nunca fue capaz de saber por qué las tareas no se lanzaban hasta que después de mucho tiempo descubrió que es necesario especificar que las anotaciones de las tareas necesitan ser escaneadas (pensaba que de eso se encargaba automágicamente el elemento «component-scan» de la configuración xml, pero evidentemente no). En fin, para que funcionen las anotaciones de las tareas en background es necesario que el fichero xml encargado de montar el contexto (aunque sólo sea para decir «oye, usa anotaciones») contenga el elemento «annotation-driven» de las tasks. A continuación se muestra un ejemplo del xml correspondiente a la configuración de un servlet de un proyecto que usa Spring MVC:

[sourcecode language=xml]
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd">

<context:component-scan base-package="paquetería" />

<!– GRACIAS A ESTO BUSCA ANOTACIONES EN LAS TAREAS –>
<task:annotation-driven />

<mvc:annotation-driven />

<mvc:view-controller path="/" view-name="index"/>

<mvc:resources mapping="/resources/**" location="/resources/" />

<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:/i18n/messages" />
<property name="cacheSeconds" value="0" />
</bean>

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>

</beans>
[/sourcecode]

Con ese elemento en la configuración y las tareas bajo la paquetería a escanear, las anotaciones funcionarán como la seda. Hay que destacar que a las anotaciones se les pueden inyectar otros objetos existentes en el contexto (con @Autowired o con @Inject, según el gusto).

Termino enlazando con la referencia a spring relativa a las tareas, donde se puede ver por ejemplo cómo configurar la tarea con sintaxis cron o cómo hacer uso de la anotación @Async. Un saludo a todos y que paséis un feliz día.

Cosas básicas que [quizá] no sabías sobre Java (III)

En los anteriores posts pudimos ver algunas curiosidades sobre Java básico, ahora toca el turno a los arrays y operadores.

Arrays

Un array se puede declarar de estas dos maneras, aunque la segunda está desaconsejada:
[sourcecode language=»java»]
int[] anArray;
int anArray[];
[/sourcecode]

Operadores

Veamos algunos conceptos sobre operadores en Java.

[sourcecode language=»java»]
int i = 1;
System.out.println(++i);
System.out.println(i++);
System.out.println(i);
[/sourcecode]

¿Qué salida da este código? Esta quizá es una demasiado fácil… ¿No? Pues hay mucha gente que no tiene muy claro el funcionamiento de ese operador unario de suma como prefijo o sufijo, es decir, antes o después de la variable i. La respuesta aquí es 2, 2, 3 (en distintas lineas, claro).

Y por último ¿Cuál es el operador binario de nombre más largo en Java? Esta pregunta hecha así os llevará probablemente a la solución directamente: instanceof. Comprueba si un objeto es de un determinado tipo (clase, subclase o si su clase implementa una determinada interfaz).
[sourcecode language=»java»]
// Declaración de objeto
System.out.println(objeto instanceof Clase);
[/sourcecode]
Devolvería true si el objeto es del tipo Clase, hijo de Clase o implementa la interfaz Clase y false en otro caso. La idea aquí no es ver qué es instanceof, sino aclarar que es un operador binario cosa que, curiosamente, no sabe todo el mundo. Incluso programadores añejos.

¡Hasta el próximo capítulo!

Generics (II) – Clases genéricas

En la anterior entrada hablamos de qué eran las Generics y por qué eran útiles. En ésta veremos cómo se definen clases genéricas para su posterior uso.

Clases genéricas

Para ver cómo definir las clases, vamos a partir de un ejemplo sencillo. Supongamos que en nuestra aplicación tenemos la necesidad de trabajar con pares. En algunos puntos necesitaremos usar pares de manzanas y en otros puntos pares de piedras.

Dado que el cometido de estos pares de elementos dentro de nuestra aplicación va a ser similar, hemos decidido implementar una clase que represente un par de cosas, en lugar de dos clases, la del par de manzanas y la del par de tornillos. Además queremos que sea una clase genérica por las ventajas que nos aportan.

Por tanto, una posible implementación de la clase podría ser la siguiente:

public class Pair<T> {
  private T first;
  private T second;
 
  public T getFirst() {
    return first;
  }
 
  public T getSecond() {
    return second;
  }
 
  public void setFirst(T first) {
    this.first = first;
  }
 
  public void setSecond(T second) {
    this.second = second;
  }
}

NOTA: Como convenio se suele utilizar una letra mayúscula para indicar una clase genérica, siendo T y E las más usadas.

Hemos definido una clase de un tipo genérico. Se puede leer como esto es una clase que representa un par de objetos de la clase T, y dejamos al criterio del usuario de la clase de qué tipo quiere o no usarla.

Definir una clase genérica no tiene más misterio. Se puede hacer todo lo enrevesado que queramos, pero en esencia toda definición se reduce a esto. El punto clave es la T. Le estamos indicando al compilador que a la hora de usar la clase Pair, la clase que pongamos en lugar de la T situada entre los símbolos < y > se sustituirá por todas las T que aparezcan en el resto de la clase.

A continuación una serie de ejemplos con comportamientos que funcionan y comportamientos que dan error:

// ...
Pair<Integer> intPair = new Pair<Integer>();
intPair.setFirst(7); // OK
intPair.setSecond("5"); // ERROR
//...
Pair objPair = new Pair();
objPair.setFirst(7); // OK
objPair.setSecond("5"); // OK
//...

No parece que tengan mucho misterio, ¿no?. Únicamente puntualizar que, si no especificamos las clases, el objeto creado usará Object como clase para el tipo genérico, y no será necesario especificarlo.

Múltiples clases

Si por ejemplo, quisiésemos que el par de elementos fuesen de dos tipos, independietes el uno del otro, podríamos redefinir la clase Pair como:

public class Pair<T, E> {
  private T first;
  private E second;
 
  public T getFirst() {
    return first;
  }
 
  public E getSecond() {
    return second;
  }
 
  public void setFirst(T first) {
    this.first = first;
  }
 
  public void setSecond(E second) {
    this.second = second;
  }
}

Aquí estamos definiendo dos tipos, (T y E). De esta forma, la clase que pongamos en lugar de T se sustituirá en todos los T, y lo mismo con la clase que pongamos en lugar de E. Ejemplo:

// ...
Pair<Integer, String> intPair = new Pair<Integer, String>();
intPair.setFirst(7); // OK
intPair.setSecond("5"); // OK
//...
Pair objPair = new Pair();
objPair.setFirst(7); // OK
objPair.setSecond("5"); // OK
//...

Para finalizar

Poco más hay que contar sobre cómo declarar una clase para que se pueda usar de forma genérica. Así que para finalizar, voy a hacer una pregunta capciosa. Dada la siguiente jerarquía:

Jerarquía de clases

¿cuál de los siguientes ejemplos daría error?

// EJEMPLO 1
Collection<Apple> apples = new ArrayList<Apple>();
Collection<Stones> stones = apples;
// ...
// EJEMPLO 2
Collection<Apple> apples = new ArrayList<Apple>(); 
Collection<Thing> things = apples;
// ...

En efecto, el primer ejemplo da un error, ya que una colección de piedras no es lo mismo que una de manzanas (afortunadamente para los que comemos fruta).

Lo malo es que el segundo ejemplo también es incorrecto. Para Java, una colección de manzanas no es equivalente a una colección de cosas. La respuesta y la solución las veremos en el siguiente post dedicado a las clases comodín (las «Wildcards»). ¡Sed felices!.

Generics (I) – Introducción

Java Generics es una de las características más solicitadas de Java (casi desde la primeras versiones). La implementación vio la luz en el año 2004 (offtopic: año en que desgraciadamente falleció el gran Miguel de Guzmán).

Previo

Me gustaría hacer un inciso sobre los posibles errores que pueden existir en una aplicación. Voy a tomarme la libertad de clasificarlos en tres conjuntos que, aunque totalmente inventados, creo que pueden ser considerados perfectamente válidos:

  1. Errores en tiempo de compilación. Los mejores errores que podemos tener, ya que los detectamos en el momento que compilamos la aplicación. Estos errores son los más fáciles de detectar.
  2. Errores en tiempo de ejecución. Errores que pasan desapercibidos para el compilador. Sólo aparecen mientras el sistema está en funcionamiento, ya sea durante su testeo (todo tipo de pruebas) como durante su explotación (cuando el cliente lo está usando).
    1. Errores en tiempo de ejecución chungos. Dentro de los errores en tiempo de ejecución, me inventaré la categoría «chungos», aquellos errores que por unas causas o por otras los detecta el usuario final en lugar de los desarrolladores (o el equipo de pruebas). Vienen a ser la maldad convertida en bug.

Pero … ¿por qué usar generics?

Supongamos que en algún momento a lo largo de nuestra vida hemos llegado a tener el engendro de clases que se muestra en la figura.

Jerarquía de alimentos

Vemos que tanto verdura como carne son comida y pueden ser cocinadas dando lugar a comida cocinada (menos mal). Por otro lado, vamos a suponer que tenemos una clase que se corresponde con un cocinero de comida vegetariana. Éste tomará una colección de verduras y las devolverá bien hechas para que se las coma el usuario:

class VeganChef {
  // ...
  public Collection cook(Collection vegetables) {
    List cookedFood = new ArrayList();
    for(Object obj : vegetables) {
      Vegetable vegetable = (Vegetable) obj;
      cookedFood.add(vegetable.getCooked());
    }
    return cookedFood;
  }
  // ...
}

(Me he tomado la libertad de usar un bucle de tipo for-each, porque me resultan más cómodos que iteradores a saco. De todas formas, el bucle de la forma for-each no se introdujo hasta la versión 1.5.)

Como podemos ver, el código fuente no parece tener ningún error. El problema es que, a priori el código es completamente correcto y compilará sin problemas. De hecho, normalmente no debería dar problemas en ejecución. Pero ¿qué sucedería si en lugar de…

//...
Set vegetables = new HashSet();
vegetables.add(new Bean());
vegetables.add(new Carrot());
Collection cookedFood = veganChef.cook(vegetables);
//...

… hubiésemos escrito lo siguiente?

// ...
Set integers = new HashSet();
integers.add(new Integer(1));
integers.add(new Integer(2));
// ...
Collection cookedFood = veganChef.cook(integers);
// ...

Lo que ocurriría es un gran error en el punto que hacemos el casting, ya que un entero no es una verdura (de momento):

Vegetable vegetable = (Vegetable) obj; // ERROR!

El problema de este error es que es indetectable en tiempo de compilación. En nuestro caso fallaría en el momento de hacer el casting y quizá sería fácilmente detectable en algún punto durante la fase de pruebas. El verdadero problema es, cuando seis semanas después tenemos que nuestra jerarquía de clases ha sido modificada:

Jerarquía de alimentos

y alguien «sin querer» ha escrito lo siguiente:

// ...
Set food = new HashSet();
food.add(Ternera);
food.add(Cordero);
// ...
Collection cookedFood = veganChef.cook(filets);
// ...

Esto es un problema. El error es prácticamente indetectable hasta que el usuario final (en nuestro caso, el pobre comensal vegetariano) da la voz de alarma. Y que el usuario de un producto se sienta beta-tester cuando no lo es no deja muy buena impresión. A estos errores me refería cuando al principio del post decía «chungos».

Solución

Usando generics, nuestro método de cocinar podría quedar como sigue:

class VeganChef {
// ...
public Collection cook(Collection<Vegetable> vegetables) {
List cookedFood<Vegetable> = new ArrayList<Vegetable>();
for(Vegetable vegetable : vegetables) {
cookedFood.add(vegetable.getCooked());
}
return cookedFood;
}
// ...
}

Estamos indicando que lo que recibimos es una colección de verduras, de tal forma que en los dos ejemplos anteriores nos daría un error de compilación:

Set filets = new HashSet();
filets.add(Ternera);
filets.add(Cordero);
// ...
Collection cookedFood = veganChef.cook(filets); // ERROR
// ...

Esto es una gran ventaja. Hemos bajado un error del nivel de error de ejecución chungo (nos falla en ejecución, y probablemente en un caso que no habíamos contemplado y que se ha «comido» el usuario final) en un error en el nivel de compilación (los más fáciles de arreglar). Hemos conseguido hacer la programación genérica en Java menos propensa a errores.

Para finalizar (de momento)

No obstante, el uso de Generics no está exento de sus peculiaridades. Si las usas, habrás oído decir, con mejores o peores palabras, que las Generics en Java no tienen nada que ver con las Templates de C++. Bueno, en realidad ambas son mecanismos para facilitar el concepto de programación genérica en ambos lenguajes, pero sí es cierto que la forma de comportarse de ambos es bastante diferente.

En los próximos artículos se resaltarán estos aspectos, además de cómo usar clases que se declaran como genéricas e incluso cómo declararlas nosotros como solución a determinados problemas. Espero que os haya gustado. Tanto si sí, como si no, ¡ahí tenéis los comentarios!

PD: Los diagramas han sido generados a partir de la aplicación http://yuml.me/. Es Web, gratuita (aunque existe versión de pago) y permite generar fácil y rápidamente diagramas de secuencia, de clases y de casos de uso.

Cosas básicas que [quizá] no sabías sobre Java (II)

Continuamos la serie de posts sobre cosas que quizá desconocías de Java básico. En la primera entrega hablábamos sobre atributos, variables, convenciones de nombrado y una pequeña introducción a palabras reservadas. En esta segunda entrega nos centramos en las palabras reservadas y literales, haciendo un pequeño inciso sobre Unicode.

En la pregunta que hicimos en el post anterior sobre cuáles eran palabras reservadas de entre las propuestas (if, true, for, goto, select, const), la respuesta correcta era la b) if, for, goto, const. Ahora entenderemos el porqué.

Palabras reservadas y literales

Aunque «const» y «goto» son palabras reservadas de Java, no están implementadas. Y por lo tanto, tampoco pueden usar como identificadores.

Un literal de clase (class literal) se forma poniendo el apéndice «.class» a cualquier nombre de clase existente. Evidentemente tampoco se puede usar como identificador, ya que tendría un «.» (que no está permitido para nombrar).

«true», «false» y «null» no son palabras reservadas, son literales. Y, evidentemente tampoco se pueden usar como identificadores.

Existen 50 palabras reservadas en Java (en versiones superiores de 5.0).

Unicode

Se pueden usar escapes Unicode (como ‘u00F1’ para la ñ) y, si el editor lo permite, también los caracteres especiales (como ñ) para identificadores. ¡Flipa! Podríamos declarar variables año, en lugar de la archiconocida «anio», «anyo» o incluso, la que más me divierte ver en el código: «ano».

Los caracteres Unicode se representan como la cadena ‘u’ seguida de un código hexadecimal de 4 cifras: ‘uFFFF’

Asignaciones

No puede asignar null a una variable primitiva. Da un error de compilación de tipo «Type mismatch».

Es decir, el siguiente extracto de código daría error:

    // Esto da error de compilación 
    int a = null;

Algunas cosas pueden parecer tonterías, o perogrulladas, pero si no se sientan las bases, nunca se puede llegar a dominar un lenguaje de programación. No sería el primer «experto» que falla la pregunta de test hecha más arriba.

Accesos directos

Cosas básicas que [quizá] no sabías sobre Java (I)

Este es el comienzo de una serie de posts sobre conceptos básicos del lenguaje Java. Nos centraremos en Java Standard Edition desde la versión 5.0 en adelante. No es un curso de programación, tampoco es un listado exhaustivo, es más bien un compendio de curiosidades que se nos pueden haber olvidado, o quizá nunca supimos. Comencemos.

Inicialización de atributos y variables

Siempre nos han dicho que es una buena práctica inicializar las variables antes de usarlas. De hecho, lo es, pero no siempre es necesario hacerlo. El compilador de Java inicializa los atributos de una clase, que no han sido previamente inicializados, a su valor por defecto.

Estos son los valores que pone por defecto (tipo/clase: valor por defecto):

  • byte: 0
  • short: 0
  • int: 0
  • long: 0L
  • float: 0.0f
  • double: 0.0d
  • char:‘u0000’
  • boolean: false
  • String (o cualquier objeto): null

Pero las variables locales (variables declaradas dentro de un método) nunca son inicializadas por el compilador a sus valores por defecto.

Restricciones de nombrado

Un nombre de variable/clase puede ser cualquier identificador (salvo palabras reservadas y literales) compuesto por una secuencia «ilimitada» (entendiendo ilimitada como que el lenguaje de programación no pone una restricción explícita) de letras y números Unicode, comenzando por cualquiera de las siguientes:

  • una letra
  • el símbolo del dolar («$«)
  • un guión bajo («_»)

Sin embargo las normas de convención Java no recomiendan usar $, ni _ para comienzos, y el _ usarlo exclusivamente para separar palabras en nombres de constantes.

Palabras reservadas y literales

Si te pregunto ¿cuál de estas palabras son palabras reservadas de Java?

  • if
  • true
  • for
  • goto
  • select
  • const

Sin hacer trampas ¿con cuál de las siguientes opciones te quedarías?
a) goto, select, const
b) if, for, goto, const
c) if, true, for

Probablemente, si programas en java, haciendo memoria sobre las palabras reservadas que usas, tendrás una solución en mente. No te voy a dar la respuesta directamente, sino que voy a dejar que la compruebes tú mismo. También daremos la solución en el próximo post.

Ahora que sabes la respuesta correcta, proseguimos. Existen algunas confusiones con respecto a qué es una palabra reservada y, por esto, también existe confusión sobre qué es un literal. Lo primero que hay que saber es que son excluyentes: un literal no es una palabra reservada, ni una palabra reservada es un literal.

En el próximo post hablaremos de curiosidades sobre palabras reservadas y literales, y otros conceptos interesantes.

Accesos directos