Subir a maven central una librería propia

Ahora que ya acabas de construir una librería interesante en Java, la has hecho pública (en github, por ejemplo) y quieres que todo el mundo la use… Queda una tarea pendiente, subirla a un repositorio maven para ponerla a disposición de los que utilicen este sistema (o gradle, que hoy en día ya son casi todos).

Vamos a verlo con un ejemplo que he subido esta mañana… Hay cosas que todavía no entiendo del todo, pero el resultado ha sido bueno, por lo que, al menos, podremos usar esta receta como guía para próximas veces.

El código que intento subir es una librería simple que tengo alojada en github con su pom.xml básico y que si te descargas el proyecto podrías compilar e instalar en tu maven con mvn install. La dirección es esta:

https://github.com/yoprogramo/nomorepass-java/

Ahora, para que todo el mundo pueda descargárselo como dependencia y no tenga que hacer el mvn install del proyecto, tenemos que subirlo a un repositorio público, podemos ver una guía en esta página: Guide to Public Maven Repositories. Tal como explican en la página, lo más sencillo para publicar en Maven Central es usar el repositorio Sonatype. Dicho y hecho… Lo intentamos por aquí.

Lo primero es crear una cuenta en el Jira de Sonatype aquí. Lo siguiente, y esto es un poco «tricky» es crear un ticket solicitando un nuevo id de grupo en esta dirección. No se puede pedir cualquier id de grupo (en mi caso quería pedir com.nomorepass) y generalmente se pedirá alguna prueba de que el dominio es tuyo. En mi caso este es el ticket que creé: https://issues.sonatype.org/browse/OSSRH-49426, para demostrar que el dominio era mío cambié el DNS e incluí una entrada TXT con el identificador del ticket:

Una vez autorizado (tarda un poco, es un proceso manual) hay que modificar nuestro código y prepararlo para la subida, pero, antes de eso, tenemos que generar nuestras claves gpg para poder firmar el código. eso se hace con este comando:

gpg --gen-key

Una vez generada podremos acceder a la lista de claves con el comando:

gpg --list-keys

Toma nota del id de la clave y recuerda la contraseña que usaste para generarla, porque tendrás que recordarla. Además, tendrás que publicarla en algún servidor de claves públicas para que pueda ser comprobada.

gpg --keyserver hkp://keys.gnupg.net --send-keys <el-id-de-la-clave>

Ahora empezamos a modificar el pom.xml para que cumpla con los requisitos para el repositorio Maven Central. En nuestro caso pusimos esto:

<groupId>com.nomorepass</groupId>
  <artifactId>nomorepass</artifactId>
  <version>1.0</version>
  <packaging>jar</packaging>

  <name>Nomorepass java library</name>
  <description>NoMorePass protocol 2 implemented on Java.</description>
  <url>https://nomorepass.com</url>

  <licenses>
    <license>
      <name>Apache License, Version 2.0</name>
      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
      <distribution>repo</distribution>
    </license>
  </licenses>

  <developers>
    <developer>
      <name>Jose Antonio Espinosa</name>
      <email>espinosa@yoprogramo.com</email>
      <organization>Nomorepass</organization>
      <organizationUrl>https://nomorepass.com</organizationUrl>
    </developer>
  </developers>

  <scm>
    <connection>scm:git:git://github.com/yoprogramo/nomorepass-java.git</connection>
    <developerConnection>scm:git:ssh://github.com:yoprogramo/nomorepass-java.git</developerConnection>
    <url>https://github.com/yoprogramo/nomorepass-java/tree/master</url>
</scm>

Y, una vez informado de todo esto, hay que incluir los plugins que nos permitirán hacer el despliegue directamente. Yo añadí esto:

<distributionManagement>
    <snapshotRepository>
      <id>ossrh</id>
      <url>https://oss.sonatype.org/content/repositories/snapshots</url>
    </snapshotRepository>
    <repository>
      <id>ossrh</id>
      <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
    </repository>
</distributionManagement>

Y puse en mi directorio de maven settings.xml los datos de mi usuario

<settings>
  <servers>
    <server>
      <id>ossrh</id>
      <username>xxxxxxxxxx</username>
      <password>xxxxxxxxxx</password>
    </server>
  </servers>
</settings>

Por último, toda la sección de build (que no tenía) la sustituí por esto:

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-gpg-plugin</artifactId>
        <executions>
          <execution>
            <id>sign-artifacts</id>
            <phase>verify</phase>
            <goals>
              <goal>sign</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.sonatype.plugins</groupId>
        <artifactId>nexus-staging-maven-plugin</artifactId>
        <version>1.6.7</version>
        <extensions>true</extensions>
        <configuration>
          <serverId>ossrh</serverId>
          <nexusUrl>https://oss.sonatype.org/</nexusUrl>
          <autoReleaseAfterClose>true</autoReleaseAfterClose>
        </configuration>
      </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-source-plugin</artifactId>
          <version>2.2.1</version>
          <executions>
            <execution>
              <id>attach-sources</id>
              <goals>
                <goal>jar-no-fork</goal>
              </goals>
            </execution>
          </executions>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-javadoc-plugin</artifactId>
          <version>2.9.1</version>
          <executions>
            <execution>
              <id>attach-javadocs</id>
              <goals>
                <goal>jar</goal>
              </goals>
            </execution>
          </executions>
        </plugin>
    </plugins>
</build>

Y ya, finalmente, pude ejecutar el mágico:

mvn clean deploy

Si todo ha ido bien, el artefacto estará subido a un repositorio que tendremos que promocionar a «Release» para que se sincronice con el repositorio central… Pero al final ya lo tendremos disponible para todo el mundo…

Aquí podéis encontrar lo que acabo de subir: https://search.maven.org/artifact/com.nomorepass/nomorepass/1.0/jar

Páginas estáticas multilingues y con plantilla tiles en struts2

Si, es un título un poco largo, pero he tardado un poco en encontrar una solución a este problema y quiero compartirlo para todos aquellos que os encontréis en la misma tesitura.

Primero, un pequeño resumen de la situación:

  • Aplicación java con struts2+tiles+urlrewrite
  • internacionalización mediante i18n con archivos de properties
  • Necesidad de un número no trivial de páginas «estáticas» con textos largos poco susceptibles de entrar como property.

Los problemas:

  1. No queremos tener una entrada en el tiles.xml por cada página estática pero queremos utilizar las plantillas existentes.
  2. No queremos generar una acción nueva por cada página
  3. No queremos tener que hacer una nueva entrada urlrewrite por cada página
  4. Queremos poder generar páginas de error si no encontramos una página en un idioma determinado.
  5. Queremos poder generar el texto completo en cada idioma como una página html, no como properties.

Si todavía no os habéis hecho una idea, no os preocupeis, es algo normal. Esta situación no se da todos los días.

La solución

O mejor dicho, mi solución…

Voy a crear una única acción que se encargue de determinar el jsp a cargar dentro de la plantilla tiles y voy a modificar una plantilla existente para inyectarle esa nueva página. Además, como el título de la página estará en la plantilla, voy a encargarme de generar el texto en el idioma adecuado. Además, voy a crear un prefijo para el urlrewrite que permita que todo esto quede bonito para google…

Paso a paso:

Modificación del urlrewite.xml:

<rule>
    	<from>^/web/(.*)$</from>
    	<to>/Estaticas.action?pagina=$1.jsp</to>
    </rule>

Con esta regla le decimos que siempre que tengamos una url con la forma /web/mipagina llamaremos a la acción Estaticas y le pasaremos como parámetro mipagina.jsp

Modificacion en struts.xml:

<action name="Estaticas" 
           class="com.yoprogramo.web.action.EstaticasAction">
  <result>/estatica.jsp</result>
</action>

Con esa acción lo que hacemos es llamar a EstaticasAction.java (luego lo vemos) y redirigir a estatica.jsp que tiene este contenido (quitando las cabeceras):

<tiles:definition name="estatica.modif" extends="estatica">
  <tiles:putAttribute name="body" value="${pagina}" />
</tiles:definition>
<tiles:insertDefinition name="estatica.modif" />

Lo que estamos indicando es que se modifique la plantilla con nombre estatica, definida en el tiles.xml y que ponga como atributo body el valor que la acción nos ha devuelto en pagina, de esta manera estaremos utilizando la plantilla definida en el tiles.xml, pero pasándose un jsp distinto. Esta forma de utilizar tiles es lo que se denomina «mutable», para poder utilizarla hay que incluir en el archivo web.xml:

  <context-param>
        <param-name>org.apache.tiles.factory.TilesContainerFactory.MUTABLE</param-name>
        <param-value>true</param-value>
  </context-param>

Ahora que tenemos todo en su sitio, solo tenemos que crear una estructura donde guardar las páginas de cada idioma y poder localizarlas facilmente. Por ejemplo, yo he creado una con esta estructrura:

Arbol de páginas estáticas

Bajo «es» pondré las páginas en español, en «en» las páginas en inglés y en «multi» las páginas multilingues que utilizan el packages.properties para traducir sus claves.

Lo único que nos queda ahora es programar la acción EstaticasAction.java .. Os dejo el código del execute:

		// Eliminamos ruta de lenguaje
		int idx=pagina.lastIndexOf("/");
		String page_name = pagina;
		
		if (idx>0)
			page_name = pagina.substring(idx+1);
		
		idx = page_name.lastIndexOf(".");
		if (idx>0)
			page_name=page_name.substring(0,idx);
		
		//Parte multilingue.
		//Vamos a crear una página para cada idioma, excepto para las
		//que sean muy simples y esas estarán en el directorio /multi
		if (!pagina.contains("/multi")) {
			String lang = getText("locale.language");
			if (lang==null)
				lang="en";
			pagina = "/"+lang+"/"+pagina;
		}
		
		// Ahora comprobamos si existe la página y si no existe redirigimos
		// a una página de error del tipo multi
		String servletContext = 
                     ServletActionContext.getServletContext()
                                                 .getRealPath("/estaticas");
		String filePagina = servletContext+pagina;
		File f = new File (filePagina);
		if (f.exists())
			pagina="/estaticas"+pagina;
		else
			pagina="/estaticas/multi/noexiste.jsp";
		
		// Ahora ponemos el título, que deberá estar como un texto 
		// en package con la forma web.<nombre_pagina>.titulo
		String key = "web."+page_name+".titulo";
		titulo = getText(key);
		
		return SUCCESS;

Una vez todo puesto en su sitio la mecánica para crear páginas estáticas y usarlas en nuestra aplicación es bastante simple:

  1. Crear un jsp con el texto para cada idioma y colocarlos bajo el directorio correcto (p.je. es/mipagina.jsp y en/mipagina.jsp)
  2. Crear una entrada en el package_en.properties y package_es.properties con la clave web.mipagina.titulo indicando el título de la página en cada idioma. (Recordemos que el titulo está en el head de la página y eso suele estar en la plantilla, no en el jsp que estamos modificando).
  3. Ya podemos acceder a /web/mipagina y ver cómo queda dentro de nuestra plantilla.

Igual inicialmente parece mucho trabajo, pero una vez hecho esto podréis hacer tantas páginas estáticas como queráis sin ningún esfuerzo y ligadas a las plantillas del resto de vuestra aplicación.