Enlazar Bugzilla con MavenChangesPlugin

0
7505

Enlazar el Bugzilla con el plugin de cambios maven-changes-plugin

Índice de contenido

Introducción.

En este tutorial «Como generar con Maven un histórico de cambios del proyecto»
podéis encontrar una expliación más completa de qué es el plugin de cambios
de Maven (maven-changes-plugin) y como podemos configurar nuestro proyecto
para utilizarlo. Por lo que resumidamente os cuento que básicamente sirve para
llevar un histórico de los cambios de nuestros proyectos; saber en que versión
se arregló algo, nueva funcionalidad, etc.

Actualmente el plugin «maven-changes-plugin» genera el histórico de cambios
tomando como fuente un fichero XML (changes.xml),
Jira, o Trac;
pero de momento todavía no han sacado una versión del plugin que sea capaz de
sacar el informe de Bugzilla, herramienta
que utilizamos muchos de nosotros para tener un control de los cambios a implementar
en nuestros proyectos.

Esto supone que para generar nuestro informe con el histórico de cambios
debemos ir manualmente viendo los cambios que se han ido implementando en cada
versión para crear nuestro fichero «changes.xml». Como suponéis esto es algo que
podemos solucionar haciendo que el fichero changes.xml, se genere automáticamente
tomando la información que hay en nuestro Bugzilla; pero aún es mejor si hacemos
que esto se haga de forma transparente cada vez que generamos la documentación
de nuestro proyecto con Maven. Por eso en este tutorial os vamos a contar como
crear un plugin de Maven que genere automáticamente el fichero changes.xml,
tomando la información del Bugzilla, para ser utilizado por el plugin maven.changes-plugin
en la generación de su histórico de cambios.

Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portatil Samsung R70 ( Intel(R) Core(TM)2 Duo 2,5Ghz, 2046 MB RAM, 232 Gb HD)

  • Sistema Operativo: Windows Vista Home Premium

  • Máquina Virtual Java: JDK 1.5.0_14 de Sun Microsystems (http://java.sun.com/javase/downloads/index_jdk5.jsp

  • IDE Eclipse 3.3 (Europa) (http://www.eclipse.org/downloads/)

Creación del plugin complementario.

Como hemos explicado en la introducción, la solución que hemos adoptado
es crear un plugin de Maven para que se conecte a nuestro Bugzilla obteniendo
la lista de bugs y genere el fichero changes.xml antes de que lo necesite el
plugin de cambios de Maven para generar el histórico de cambios.

Para ver como empezar a crear nuestro plugin podéis ver este tutorial
«Desarrollo de Plugins para Maven»
que explica como hacer un plugin de Maven sencillo.

En primer lugar identificamos los parámeteros que necesita nuestro plugin
para conectarse al Bugzilla y generar el fichero changes.xml:

  • productName: Nombre del producto en el Bugzilla del que obtenemos la
    lista de bugs.
  • componentName: Nombre del componente en el Bugzilla del que obtendremos
    la lista de bugs.
  • bugzillaUser: Usuario para el login en el Bugzilla.
  • bugzillaPassword: Contraseña del usuario para el login en el Bugzilla.
  • resolution: Estado de resolución de los bugs a tener en cuenta.
    (Por defecto sólo se tienen en cuenta los que tienen estado a fixed)
  • loginPage: Página de login en el Bugzilla.
  • xmlPath: Ruta donde se generará el fichero changes.xml que debe ser la misma que la que definamos en el maven-changes-plugin.
  • loginRequired: Parámetro para indicar si es necesario hacer login en
    el Bugzilla para recuperar la lista de bugs. (por defecto true)
  • parentOnly: Al tener los proyectos de Maven herencia, con este parámetros
    le indicamos que sólo genere la información de cambios en el proyecto padre.
  • fitDevelopers: La información del Bugzilla nos indica quién tiene asignado
    el bug como una dirección de correo. Con este parámetro indicamos que la
    información correspondiente al desarrollador que va a ir en el «changes.xml»
    se ajuste al usuario de la cuenta. (p.e. si tenemos «usuario@dominio.com»
    se quedaría con «usuario»).
  • currentVersionOnly: Debido a que la lista de bugs de todas las versiones
    del producto puede ser muy larga, con este parámetro indicamos que sólo tenga
    en cuenta la versión actual (definida en el POM). Si ya tenemos un fichero
    changes.xml, con este parámetro lo que hacemos es añadir o sustituir los
    cambios de esta versión. Si está a «true» se genera la fecha de la versión
    con la fecha actual, para el resto de casos la fecha de la versión se deja en blanco.
  • versionSuffix: El nombre de la versión es común que tenga un sufijo
    mientras se está desarrollando, por lo que al consultar en el Bugzilla se
    debe eliminar. (por defecto «-SNAPSHOT»).
  • componentNamePrefix2Remove: El nombre de los módulos puede que tengan
    un prefijo común que no están en el nombre del componente en el Bugzilla.
    Con este parámetro indicamos cual es dicho prefijo que será eliminado para
    hacer la consulta en el Bugzilla. (por defecto «»).

Todos estos parámetros deben formar parte de nuestro plugin, por lo que en
nuestro código debemos añadir:

  /**
	 * Bugzilla product name.
	 * 
	 * @parameter expression="${changesMavenPlugin.productName}"
	 * @required
	 */
	private String productName;

	/**
	 * Bugzilla product compnent name.
	 * 
	 * @parameter expression="${changesMavenPlugin.componentName}" default-value="${project.artifactId}"
	 */
	private String componentName;

	/**
	 * Bugzilla user.
	 * 
	 * @parameter expression="${changesMavenPlugin.bugzillaUser}"
	 * @required
	 */
	private String bugzillaUser;

	/**
	 * Bugzilla password for the user.
	 * 
	 * @parameter expression="${changesMavenPlugin.bugzillaPassword}"
	 * @required
	 */
	private String bugzillaPassword;

	/**
	 * Sets the resolution(s) that you want to fetch from Bugzilla. Valid resolutions are: FIXED,
	 * INVALID, WONTFIX, DUPLICATE, WORKSFORME,
	 * MOVED and ---. Multiple values can be separated by commas.
	 * 
	 * @parameter expression="${changesMavenPlugin.resolution}" default-value="FIXED"
	 */
	private String resolution;

	/**
	 * Bugzilla login page.
	 * 
	 * @parameter expression="${changesMavenPlugin.loginPage}" default-value="index.cgi"
	 */
	private String loginPage;

	/**
	 * The path of the changes.xml file that will be generated to maven-changes-plugin. Must be the same path
	 * configured in maven-changes-plugin.
	 * 
	 * @parameter expression="${changesMavenPlugin.xmlPath}" default-value="src/changes/changes.xml"
	 */
	private File xmlPath;

	/**
	 * Bugzilla login required.
	 * 
	 * @parameter expression="${changesMavenPlugin.loginRequired}" default-value="true"
	 */
	private boolean loginRequired;

	/**
	 * Changes report only in parent project.
	 * 
	 * @parameter expression="${changesMavenPlugin.parentOnly}" default-value="true"
	 */
	private boolean parentOnly;

	/**
	 * Fits Bugzilla developers email with Maven developers removing from @ to the end.
	 * @parameter expression="${changesMavenPlugin.fitDevelopers}" default-value="true"
	 */
	private boolean fitDevelopers;

	/**
	 * Changes report only for current version
	 * 
	 * @parameter expression="${changesMavenPlugin.currentVersionOnly}" default-value="true"
	 */
	private boolean currentVersionOnly;

	/**
	 * Version name suffix that is removed from the Bugzilla Http request.
	 * 
	 * @parameter expression="${changesMavenPlugin.versionSuffix}" default-value="-SNAPSHOT"
	 */
	private String versionSuffix;  
	
	/**
	 * Component name prefix that is removed from the Bugzilla Http request. 
	 * 
	 * @parameter expression="${changesMavenPlugin.componentNamePrefix2Remove}" default-value=""
	 */
	private String componentNamePrefix2Remove;
    

El proceso principal de nuestro plugin se realiza en el método «execute()»
que necesariamente debe implementar al heredar de «org.apache.maven.plugin.AbstractMojo».
Los pasos que sigue son:

  • Preprocesamiento de parámetros: Para controlar si seguir con la ejecución
    y preparar los datos necesarios para el resto del proceso.
  • Login en el Bugzilla.
  • Recuperar la lista de bugs del Bugzilla
  • Recuperar el documento XML del Bugzilla con la información de los bugs de la lista.
  • Formar el «changes.xml» transformando el XML del bugzilla.

El código del método «execute()» es:

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.apache.maven.plugin.AbstractMojo#execute()
	 */
	public void execute() throws MojoExecutionException, MojoFailureException {

		this.getLog().debug("======= ENTERING");
		this.getLog().info("component:" + this.componentName);

		// si el informe el solo para el padre y es un hijo salimos
		// si es un hijo y no está definido el component salimos
		if ((this.parentOnly && (this.project.getParent() != null))
				|| (!this.parentOnly && ((this.componentName == null) || this.componentName.equals("")))) {
			return;
		}

		// recuperamos el nombre de la version
		this.versionName = this.project.getVersion();
		final int index = this.versionName.indexOf(this.versionSuffix);
		if (index != -1) {
			// quitamos el sufijo
			this.versionName = this.versionName.substring(0, index);
		}

		// quitamos el prefijo del nombre del componente
		if ((this.componentNamePrefix2Remove != null) && !this.componentNamePrefix2Remove.equals("")) {
			if (this.componentName.startsWith(this.componentNamePrefix2Remove)) {
				this.componentName = this.componentName.substring(this.componentNamePrefix2Remove.length());
			}
		}
		// inicializamos el gestor de peticiones
		this.httpRequest = new HttpRequest(this.getLog());
		// inicializamos la url del Bugzilla
		this.bugzillaURL = this.project.getIssueManagement().getUrl();
		// preparamos el cliente HTTP para las peticiones
		final HttpClient client = new HttpClient();
		final HttpClientParams clientParams = client.getParams();
		clientParams.setBooleanParameter(HttpClientParams.ALLOW_CIRCULAR_REDIRECTS, true);
		final HttpState state = new HttpState();
		final HostConfiguration hc = new HostConfiguration();
		client.setHostConfiguration(hc);
		client.setState(state);
		this.determineProxy(client);

		// hacemos el login si es necesario
		boolean loginSuccess = true;
		if (this.loginRequired) {
			loginSuccess = this.login(client);
		}

		if (loginSuccess) {
			// recuperamos la lista de bugs
			String bugsIds;
			bugsIds = this.getBugList(client);

			// recuperamos el xml de bugs
			Document bugsDocument;
			bugsDocument = this.getBugsDocument(client, bugsIds);

			// formamos el changes.xml
			this.builChangesXML(bugsDocument);

		}
		this.getLog().debug("======= EXITING");
	}   
     

Como se puede ver en el código, después de un procesamiento previo de los
parámetros se empieza con las peticiones al Bugzilla; hacemos el login y realizamos
la petición que nos devuelve la lista de Bugs. Esta petición es del estilo
«https://host/cgi-bin/bugzilla3/buglist.cgi?product=productName&resolution=FIXED&order=target_milestone»
que devuelve una página Web en HTML; que deberemos procesar para poder sacar
los identificadores de los bugs que queremos recuperar. Si vemos el código
HTML de la respuesta podemos ver un formulario como este:

     
......

Si nos fijamos en el código HTML completo, tenemos únicamente dos formularios
con «action=show_bug.cgi», pero los id’s que se envían son siempre los mismos;
por lo que procesando la respuesta HTML recuperaremos todos los id’s que tenemos
que enviar para recuperar la petición. Como la respuesta es HTML, y para poder
procesarla mejor, la convertimos a XHTML utilizando JTidy; para lo que en el
POM.xml debemos añadir la siguiente dependencia:


	org.hibernate
	jtidy-r8
	21122004

Para convertir un String que representa a un HTML en un documento XML lo
hacemos con el siguiente código:

final Tidy tidy = new Tidy();
tidy.setXHTML(true);
tidy.setMakeClean(true);
tidy.setBreakBeforeBR(true);
tidy.setTidyMark(false);
tidy.setQuoteAmpersand(false);
tidy.setQuoteMarks(false);
tidy.setQuoteNbsp(false);
tidy.setRawOut(true);
tidy.setFixComments(true);
tidy.setSmartIndent(true);
tidy.setWraplen(4000);
tidy.setDocType("omit");
tidy.setShowWarnings(false);
tidy.setQuiet(true);
tidy.setIndentAttributes(false);
tidy.setIndentContent(false);
tidy.setSpaces(0);
tidy.setTabsize(0);
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final ByteArrayInputStream bais = new ByteArrayInputStream(response.getBytes());
final Document doc = tidy.parseDOM(bais, baos);

Habiendo procesado el formulario, enviamos la petición al Bugzilla para que nos devuelva el XML
con la lista de bugs. Es muy importante que no nos olvimemos que la petición
la debemos realizar por POST; y que no nos podemos olvidar de los parámetos
«ctype=xml» (indica que queremos la respuesta en formato XML) y «excludefield=attachmentdata»
(para que no nos incluya los fichero adjuntos a los bugs).

Debido a los posible tipos de
codificación que puede haber entre el servidor del Bugzilla y nuestra JVM os
aconsejo que esta respuesta en particular la tratemos siempre que podamos en bytes;
por lo que en la clase que realizamos las peticiones HTTP tendremos el siguiente método:

  /**
	 * Send a GET method request to the given link using the configured HttpClient, possibly following redirects, and returns
	 * the response as String.
	 * 
	 * @param cl the HttpClient
	 * @param link the URL
	 * @throws HttpStatusException
	 * @throws IOException
	 */
	public byte[] sendPostRequest(final HttpClient cl, final String link, final String parameters)
			throws HttpStatusException, IOException {
		try {
			final PostMethod pm = new PostMethod(link);

			if (parameters != null) {
				final String[] params = parameters.split("&");
				for (final String param : params) {
					final String[] pair = param.split("=");
					if (pair.length == 2) {
						pm.addParameter(pair[0], pair[1]);
					}
				}
			}

			this.getLog().info("Downloading from Bugzilla at: " + link);

			cl.executeMethod(pm);

			final StatusLine sl = pm.getStatusLine();

			if (sl == null) {
				this.getLog().error("Unknown error validating link: " + link);

				throw new HttpStatusException("UNKNOWN STATUS");
			}

			// if we get a redirect, throws exception
			if (pm.getStatusCode() == HttpStatus.SC_MOVED_TEMPORARILY) {
				this.getLog().debug("Attempt to redirect ");
				throw new HttpStatusException("Attempt to redirect");
			}

			if (pm.getStatusCode() == HttpStatus.SC_OK) {
				final InputStream is = pm.getResponseBodyAsStream();
				final ByteArrayOutputStream baos = new ByteArrayOutputStream();
				final byte[] buff = new byte[256];
				int readed = is.read(buff);
				while (readed != -1) {
					baos.write(buff, 0, readed);
					readed = is.read(buff);
				}

				this.getLog().debug("Downloading from Bugzilla was successful");
				return baos.toByteArray();
			} else {
				this.getLog().warn("Downloading from Bugzilla failed. Received: [" + pm.getStatusCode() + "]");
				throw new HttpStatusException("WRONG STATUS");
			}
		} catch (final HttpException e) {
			if (this.getLog().isDebugEnabled()) {
				this.getLog().error("Error downloading issues from Bugzilla:", e);
			} else {
				this.getLog().error("Error downloading issues from Bugzilla url: " + e.getLocalizedMessage());

			}
			throw e;
		} catch (final IOException e) {
			if (this.getLog().isDebugEnabled()) {
				this.getLog().error("Error downloading issues from Bugzilla:", e);
			} else {
				this.getLog().error("Error downloading issues from Bugzilla. Cause is " + e.getLocalizedMessage());
			}
			throw e;
		}
	}     

Y en nuestra clase principal, formaremos el documento XML con el siguiente código:

.....
final byte[] response = this.httpRequest.sendPostRequest(client, link, bugsIds);

final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
final DocumentBuilder db = dbf.newDocumentBuilder();
db.setEntityResolver(new EntityResolver() {

	public InputSource resolveEntity(final String publicId, final String systemId) throws SAXException,
			IOException {
		return new InputSource(this.getClass().getClassLoader().getResourceAsStream(
				"bugzilla3/bugzilla.dtd"));
	}
});
final ByteArrayInputStream bais = new ByteArrayInputStream(response);

final Document docBugzilla = db.parse(bais); 
.....    

Donde se puede ver que formamos el documento XML directamente con los bytes
que nos devuelve el servidor del Bugzilla. También cambiamos el «EntityResolver»
para que independientemente del DTD que vaya definido en el XML, que puede no
estar accesible provocando un fallo, lo recupere de nuestros recursos.

Habiendo obtenido el documento XML con los bugs, lo sometemos a un procesamiento
previo que facilite su posterior transformación. Al elemento «target_milestone»
le añadimos 2 atributos que faciliten la ordenación en su transformación.
Por ejemplo, para un <target_milestone>1.13</target_milestone>
se convertiría en <target_milestone version1=»1″ version2=»13″>1.13</target_milestone>.
También aprovechamos para eliminar todos aquellos nodos que no nos van a
hacer falta en la transformación XSL.

Ahora ya tenemos el documento XML del bugzilla con la información de los
bugs que necesitamos, por lo que vamos a transformarlo al formato del «changes.xml».
Para hacer esto utilizamos el siguiente XSL:


	
	
	
		
			
				
					<xsl:value-of select="title" />
				
			
			
				
				
						
				
				
					
					
					
						
					
					
						
						
						
						
						
							
							
								
									
										add
										fix
									
								
								
									
								
								
									
								
								
							
						
					
				
			
		
	

En el XSL se puede observar la particualridad de formar los elementos <release>.
Como los bugs del documento del Bugzilla pueden venir desordenados, al recorrerlos
en el bucle de la línea 18, se indica que sólo se coja un bug de cada versión;
de esta forma obtenemos la información para formar por completo el elemento <release>.
Y recorriendo este bucle ya procesamos todos los bugs de la versión en bucle
de la línea 35; donde formaremos la información específica de cada bug.

Esta información se corresponde con:

  • El título lo pondremos como el título del proyecto, que se añadirá como un elemento más al XML del Bugzilla.
  • El nombre de la versión con el valor del elemento <target_milestone>.
  • El tipo de cada bug será «add» si en el Bugzilla está como mejora, y «fix» en cualquier otro caso.
  • El identificador del bug con el valor del elemento <bug_id>.
  • El desarrollador con el valor del elemento <assigned_to>.
  • La descripción del bug con el valor del elemento <short_desc>.

Por último sólo nos queda hacer que nuestro plugin pueda ser utilizado desde otros proyectos instalandolo en nuestro repositorio local ejecutando:

mvn clean install

Utilizando nuestro plugin.

Como hemos dicho nuestro plugin es un complemento para el plugin de informes
de cambios maven-changes-plugin. Éste plugin de cambios se ejecuta en la fase
de maven «site». Por lo que el plugin que acabamos de crear lo debemos configurar
para que se ejecute justo antes, es decir en la fase «pre-site». De esta forma
en el «pom.xml» de nuestros proyectos será del estilo:


  .....    
  
  	Bugzilla
  	https://host/cgi-bin/bugzilla3/
  
  .....
  
		
      .....
			
			  
				com.autentia.mvn.plugin
				bugzillaChanges
				
        			 *** nombre del producto en el bugzilla *** 
        			${bugzillaUser}
        			${bugzillaPassword}
        			false
				
			  
					
						pre-site
						
							changes-generate
						
					
				
			
		
	
	.....
  
  	
  		.....
  		
  			org.apache.maven.plugins
  			maven-changes-plugin
  			2.0
  			
  				%URL%/show_bug.cgi?id=%ISSUE%
  			
  			
  				
  					
  						changes-report
  					
  				
  			
  		
  		.....
  	
  
  .....
  
  
      
          AutentiaBugzillaMaven-release
          Local Maven repository of releases
          http://bugzillachanges.sourceforge.net/maven-repository
          
              false
          
          
              true
          
      
  
  .....
    

De los parámetros de configuración de nuestro plugin, se pueden considerar
un poco distintos los parámetros «bugzillaUser» y «bugzillaPassword», ya que
es algo particular de cada usuario. Al ser el «pom.xml» un fichero que se suele
compartir, no es aconsejable que aparezcan estos datos; por lo que nos valemos de
las ventajas de Maven para recuperarlos del perfil activo en el fichero «settings.xml»;
de esta forma en nuestro fichero «setting.xml» deberá aparecer:


    
      .....    
      
         identificador del perfil activo 
         .....
      	 
              user
              password
          
       
      .....
    
    .....
   

Conclusiones

Ya sabemos que gracias a Maven tenemos una gestión de nuestros proyectos
más completa, y que con una sola herramienta podemos unificar varias tareas
de la gestión de proyectos; pero a veces no se ajusta exáctamente a lo que
necesitamos. Es aqui donde la extensibilidad de Maven por medio de plugins
nos proporciona esa posibilidad que nos facilita la vida.

Animaros a hacer plugins de Maven, ya que combinando la potencia de varios
plugins al final podemos conseguir lo que buscamos.

Si queréis todo el código fuente de este plugin lo podéis conseguir en
sourceforge.

Documentación extra la podéis encontrar
aqui.

Un saludo.
Borja Lázaro de Rafael.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad