Un exercici amb el Framework Seam

Des de fa un temps que estic involucrat en el manteniment d’una aplicació desenvolupada sobre el framework Seam. Fins dates recents el manteniment s’ha limitat a modificacions menors, però fa poc ens ha entrat un canvi en profunditat.Ha estat l’oportunitat que estava esperant per a poder aprendre una mica més sobre aquest potent framework.

Primer de tot, què és Seam? La Viquipèdia ens dona una definició breu, senzilla i entenedora: “Seam és un Framework per a aplicacions Web desenvolupat per JBoss, una divisió de Red Hat.”

Hi han molts frameworks per a aplicacions web.  Un framework és  la sistematització de l’experiència acumulada en el desenvolupament de moltes aplicacions web. Aquesta sistematització pren la forma d’arquitectures, patrons, llibreries, bones pràctiques i eines de diferents tipus. En adoptar un framework per a desenvolupar una aplicació web el que estem fent, doncs, es manifestar la voluntat de “fer bé les coses”, repetint models coneguts que han funcionat.  De forma pràctica, l’adopció d’un framework es tradueix en l’ús de certes llibreries, metodologies i eines de desenvolupament

Spring ve a ser l’estàndar de facto de la indústria o, si més no, el framework java per a aplicacions web més popular. Si coneixem Spring, doncs, ja sabem que podem esperar de Seam.  Tanmateix, Seam té les seves pròpies característiques. En aquest enllaç podeu trobar una anàlisi comparativa entre els frameworks Seam i Spring.

Per aproximar-me a Seam provaré de fer “alguna cosa” amb el framework. Per exemple un gestor de biblioteca molt senzill.

El que vull fer és una aplicació web que em permeti donar d’alta i de baixa  llibres, classificar-los segons autor, temàtica i tipus de suport físic, i que em permeti saber a quin lloc de la casa paren.

Doncs bé, resulta que això puc fer-ho sense escriure una línia de codi java. Seam es capaç de generar una aplicació web de manteniment sense més que proporcionar-li un esquema de dades. La veritat és que és força espectacular. No cal dir que aquests superpoders no són pas els únics dels que disposa Seam, però per a una primera aproximació -res més pretenc en aquest post- és més que suficient.

Comencem: aplicacions. El framework Seam es pot fer servir amb qualsevol Servidor d’Aplicacions que suporti EJB3.0, ara bé, Seam és un producte de JBoss i, com es pot esperar s’integra fàcilment amb aquest servidor. El primer que faré, doncs, és descarregar les darreres versions de  JBoss AS i del Seam framework. Proveu els següents enllaços:

Seam 3.1.0 http://sourceforge.net/projects/jboss/files/Seam/3/3.1.0.Final/

jBoss AS 7.1.1 Final http://download.jboss.org/jbossas/7.1/jboss-as-7.1.1.Final/jboss-as-7.1.1.Final.zip

Ens caldrà una versió de JDK igual o superior a la 1.6 (feu un cop d’ull a http://www.oracle.com/technetwork/java/javase/downloads/index.html).

Si per alguna raó no poguéssim disposar almenys de JDK1.6, aleshores hauríem de fer servir versions anteriors de JBoss AS i de SEAM. Podria ser aquest el cas d’haver d’ocupar-nos del manteniment d’una aplicació legacy.

La següent configuració funciona amb JDK 1.5 (evidentment, funciona amb JDK superiors):

Seam 2.3.0 GA http://sourceforge.net/projects/jboss/files/JBoss%20Seam/2.3.0.Final/

JBoss AS 5.1.0.GA http://sourceforge.net/projects/jboss/files/JBoss/JBoss-5.1.0.GA

La instal·lació del servidor JBoss AS 1.7 i del Seam Framework 3.1.0 no presenta cap dificultat, es tracta, simplement de descomprimir el paquet zip corresponent. he creat una carpeta seam-jboss i els he descomprimit allà.

Si tinc versions diverses de JDK, puc modificar l’script d’arrencada de JBoss bin/standalone.sh per a indicar-hi explícitament quin JDK vull fer servir, només em cal donar un valor inicial a JAVA_HOME per a que apunti a la carpeta del JDK triat, per exemple:

JAVA_HOME=/home/albert/jdk1.6.0_30

El següent pas és crear les taules i relacions de la base de dades. Faré servir el servidor MySQL que em ve amb el XAMPP que tinc instal·lat de posts anteriors, i el phpMyAdmin com interfase de la base de dades.

Només vull fer una petita demostració, o sigui que no entraré en detalls: la idea és crear una petita bd de biblioteca. Tindré
– una taula per guardar els llibres (id, llibre, isbn, id_ubicació, id_format);
– una taula d’autors (id, autor);
– una taula de relació m-n entre llibres i autors (un autor pot haver escrit molts llibres, un llibre pot ser escrit per molts autors. Per tant: id, id_autor, id_llibre);
– una taula de relació m-n entre llibres i temàtiques (un llibre pot tractar diversos temes; un tema pot ser tractat per molts llibres. Per tant, id, id_llibre, id_tematica);
– lògicament, doncs, una taula de temàtiques (id, tematica).
– una taula d’ubicacions (id, lloc) ja que puc tenir llibres per uns quants llocs de la casa, a prestatges, o a DVDs (suposo que tinc els DVDs etiquetats, cosa que algun dia hauré de fer, però, en fi, això només pretén ser un exemple 😉
– una taula de formats: “paper”, “pdf”, “epub”, “cbr”… (id, format).

Dit i fet. Primer de tot creo la base de dades “prova_seam”, i les taules “llibre”, “autor”, “llibre_autor”, “llibre_tematica”, “ubicacio” i “format”.

A continuació, creo els índexos sobre totes les columnes que participaran en les relacions i, finalment, creo les relacions. Cal esmerçar-se en crear correctament els índexos i les relacions. És necessari per a que la màgia de Seam funcioni. Es tracta, simplement, de permetre que les eines de mapeig de model relacional a model d’objectes tinguin tota la informació necessària per a crear una capa de classes java que encapsulin l’accés a les dades.

Després d’una sessió de phpMyAdmin, obtinc un model de dades que m’agrada. L’esquema és el següent:


-- phpMyAdmin SQL Dump
-- version 3.4.5
-- http://www.phpmyadmin.net
--
-- Servidor: localhost
-- Tiempo de generación: 21-11-2012 a las 21:05:25
-- Versión del servidor: 5.5.16
-- Versión de PHP: 5.3.8

SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";


/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;

--
-- Base de datos: `prova_seam`
--

-- --------------------------------------------------------

--
-- Estructura de tabla para la tabla `autor`
--

CREATE TABLE IF NOT EXISTS `autor` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `autor` varchar(100) COLLATE latin1_spanish_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 COLLATE=latin1_spanish_ci AUTO_INCREMENT=2 ;


-- --------------------------------------------------------

--
-- Estructura de tabla para la tabla `format`
--

CREATE TABLE IF NOT EXISTS `format` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `format` varchar(100) COLLATE latin1_spanish_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 COLLATE=latin1_spanish_ci AUTO_INCREMENT=2 ;


-- --------------------------------------------------------

--
-- Estructura de tabla para la tabla `llibre`
--

CREATE TABLE IF NOT EXISTS `llibre` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `llibre` varchar(200) COLLATE latin1_spanish_ci NOT NULL,
  `isbn` varchar(13) COLLATE latin1_spanish_ci NOT NULL,
  `id_ubicacio` int(11) NOT NULL,
  `id_format` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `id_ubicacio` (`id_ubicacio`),
  KEY `id_format` (`id_format`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_spanish_ci AUTO_INCREMENT=1 ;

-- --------------------------------------------------------

--
-- Estructura de tabla para la tabla `llibre_autor`
--

CREATE TABLE IF NOT EXISTS `llibre_autor` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `id_llibre` int(11) NOT NULL,
  `id_autor` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `id_llibre` (`id_llibre`),
  KEY `id_autor` (`id_autor`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_spanish_ci AUTO_INCREMENT=1 ;

-- --------------------------------------------------------

--
-- Estructura de tabla para la tabla `llibre_tematica`
--

CREATE TABLE IF NOT EXISTS `llibre_tematica` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `id_autor` int(11) NOT NULL,
  `id_tematica` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `id_autor` (`id_autor`),
  KEY `id_tematica` (`id_tematica`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_spanish_ci AUTO_INCREMENT=1 ;

-- --------------------------------------------------------

--
-- Estructura de tabla para la tabla `tematica`
--

CREATE TABLE IF NOT EXISTS `tematica` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `tematica` varchar(100) COLLATE latin1_spanish_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_spanish_ci AUTO_INCREMENT=1 ;

-- --------------------------------------------------------

--
-- Estructura de tabla para la tabla `ubicacio`
--

CREATE TABLE IF NOT EXISTS `ubicacio` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `ubicacio` varchar(100) COLLATE latin1_spanish_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 COLLATE=latin1_spanish_ci AUTO_INCREMENT=2 ;

--
-- Volcado de datos para la tabla `ubicacio`
--

INSERT INTO `ubicacio` (`id`, `ubicacio`) VALUES
(1, 'llibreria de l''escriptori');

--
-- Restricciones para tablas volcadas
--

--
-- Filtros para la tabla `llibre`
--
ALTER TABLE `llibre`
  ADD CONSTRAINT `llibre_ibfk_1` FOREIGN KEY (`id_ubicacio`) REFERENCES `ubicacio` (`id`),
  ADD CONSTRAINT `llibre_ibfk_2` FOREIGN KEY (`id_format`) REFERENCES `format` (`id`);

--
-- Filtros para la tabla `llibre_autor`
--
ALTER TABLE `llibre_autor`
  ADD CONSTRAINT `llibre_autor_ibfk_1` FOREIGN KEY (`id_llibre`) REFERENCES `llibre` (`id`),
  ADD CONSTRAINT `llibre_autor_ibfk_2` FOREIGN KEY (`id_autor`) REFERENCES `autor` (`id`);

--
-- Filtros para la tabla `llibre_tematica`
--
ALTER TABLE `llibre_tematica`
  ADD CONSTRAINT `llibre_tematica_ibfk_1` FOREIGN KEY (`id_autor`) REFERENCES `autor` (`id`),
  ADD CONSTRAINT `llibre_tematica_ibfk_2` FOREIGN KEY (`id_tematica`) REFERENCES `tematica` (`id`);

/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;


En aquest punt, deixo el phpMyAdmin i me’n vaig a la carpeta on tinc el seam.

Estic fent servir la versió de Seam 3.2.0. Amb aquesta versió hi han les següents eines disponibles per al desenvolupament d’aplicacions web:
– el JBoss Developer Estudio. que només requereix registre per a poder-lo descarregar lliurement.
– Les JBoss Tools. Que són plugins per Eclipse. Les JBoss Tools permeten crear, directament, aplicacions per al Framework Seam 2.3.0 i, indirectament, a través del plugin de Seam Forge, per al Framework Seam 3.1.0.
Seam Forge. Es tracta d’una shell des de la que es poden crear aplicacions, deployar, crear objectes nous… Aquesta shell s’integra com a plugin a les JBoss Tools o al JBoss Developer Studio. El Seam Forge vindria a ser l’evolució de l’script seam-gen de la versió Seam Framework 2.3.0. A diferència de seam-gen, que formava part del paquet de distribució del Framework, el Seam Forge ha de descarregar-se com una aplicació separada.

Seam Forge es pot extendre amb plugins. A la pàgina de plugins en trobarem alguns que ens caldran per a poder fer l’enginyeria inversa de la base de dades i per automatitzar les tasques d’administració dels desplegaments al JBoss AS. Però n’hi han d’altres. Serà cosa de provar a veure què fan.

Com que el repte és “crear una aplicació sense escriure una línia de codi”, i tenint en compte les explicacions anteriors, descarrego el Seam Forge. És un paquet zip que desplego a la mateixa carpeta de seam-jboss on tinc les carpetes del JBoss AS i del Seam Framework. Em cal indicar-li el JDK que penso fer servir i quina és la seva carpeta home. Per a fer-ho vaig a la carpeta bin del Seam Forge i allà modifico l’script forge per a establir les variables JAVA_HOME i FORGE_HOME.

Seam Forge em permetrà generar (scaffold) una aplicació o, si més no, l’esquelet i alguns òrgans vitals, d’una aplicació web. Tot plegat d’una forma que recorda els Archetypes de Maven. pel que estic veient, Maven és una eina que es fa força imprescindible a l’hora de treballar amb projectes desenvolupats a partir de Forge. En general Maven és una eina molt potent que caldria tenir en compte (almenys considerar-ne l’ús) de forma habitual.

A més, em caldrà tenir a ma el driver jdbc del MySQL per a poder desplegar-lo al JBoss.

És el primer que faig. Per a desplegar el driver jdbc n’hi ha prou amb col·locar-lo a la carpeta JBOSS_HOME/standalone/deployments. Si tenim una finestra oberta amb els logs del servidor veurem com, efectivament, el desplega. A més, a la consola d’administració del JBoss també veurem el nou desplegament:

UN cop tinc el driver disponible al JBoss, passo a la construcció de l’aplicació amb el Forge.

Escric forge al terminal i accedeixo a la shell. Una advertència, no tanqueu la shell del Forge fins completar tots els passos del desplegament de l’aplicació:

Per a poder fer l’enginyeria inversa del model de dades al MySQL i generar les classes java per hibernate em cal, primer de tot, carregar els hibernate-tools. Aquesta part del post està basada en aquest link (http://www.mastertheboss.com/forge/reverse-engineer-your-db-schema-using-jboss-forge).


albert@athena:~/seam-jboss/forge-distribution-1.1.2.Final/bin$ forge
    _____                    
   |  ___|__  _ __ __ _  ___ 
   | |_ / _ \| `__/ _` |/ _ \  \\
   |  _| (_) | | | (_| |  __/  //
   |_|  \___/|_|  \__, |\___| 
                   |___/      

JBoss Forge, version [ 1.1.2.Final ] - JBoss, by Red Hat, Inc. [ http://jboss.org/forge ]
[no project] bin $
[no project] bin $ forge install-plugin hibernate-tools
***INFO*** Preparing to install plugin: hibernate-tools
***INFO*** Checking out plugin source files to [/tmp/forgetemp2102291278366764408/repo] via 'git'
***INFO*** Switching to branch/tag [refs/heads/master]
***INFO*** Invoking build with underlying build system.
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building hibernate-tools-plugin 1.0.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.4.1:clean (default-clean) @ hibernate-tools-plugin ---
[INFO] 
[INFO] --- maven-resources-plugin:2.4.3:resources (default-resources) @ hibernate-tools-plugin ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 3 resources
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:compile (default-compile) @ hibernate-tools-plugin ---
[INFO] Compiling 9 source files to /tmp/forgetemp2102291278366764408/repo/target/classes
[INFO] 
[INFO] --- maven-resources-plugin:2.4.3:testResources (default-testResources) @ hibernate-tools-plugin ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 3 resources
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:testCompile (default-testCompile) @ hibernate-tools-plugin ---
[INFO] Not compiling test sources
[INFO] 
[INFO] --- maven-surefire-plugin:2.7.2:test (default-test) @ hibernate-tools-plugin ---
[INFO] Tests are skipped.
[INFO] 
[INFO] --- maven-jar-plugin:2.3.1:jar (default-jar) @ hibernate-tools-plugin ---
[INFO] Building jar: /tmp/forgetemp2102291278366764408/repo/target/hibernate-tools-plugin-1.0.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 22.651s
[INFO] Finished at: Sun Nov 18 14:29:57 CET 2012
[INFO] Final Memory: 33M/79M
[INFO] ------------------------------------------------------------------------
***INFO*** Installing plugin artifact.
***SUCCESS*** Installed from [https://github.com/forge/plugin-hibernate-tools.git] successfully.
Wrote /home/albert/.forge/httpsrawgithubcomforgepluginrepositorymasterrepositoryyaml.yaml
Wrote /home/albert/.forge/plugins/org/jboss/hibernate/forge/hibernate-tools-plugin/1.0.0-SNAPSHOT-311059c3-c505-46c1-915e-4a3af362ea9a
...

després d’escriure un munt de jars a la carpeta de plugins, finalment, tinc les hibernate tools disponibles al Forge:

Wrote /home/albert/.forge/plugins/org/jboss/hibernate/forge/hibernate-tools-plugin/1.0.0-SNAPSHOT-311059c3-c505-46c1-915e-4a3af362ea9a/slf4j-log4j12-1.5.10.jar
Wrote /home/albert/.forge/plugins/org/jboss/hibernate/forge/hibernate-tools-plugin/1.0.0-SNAPSHOT-311059c3-c505-46c1-915e-4a3af362ea9a/hibernate-tools-plugin.jar
Wrote /home/albert/.forge/plugins/org/jboss/hibernate/forge/hibernate-tools-plugin/1.0.0-SNAPSHOT-311059c3-c505-46c1-915e-4a3af362ea9a/module.xml
    _____                    
   |  ___|__  _ __ __ _  ___ 
   | |_ / _ \| `__/ _` |/ _ \  \\
   |  _| (_) | | | (_| |  __/  //
   |_|  \___/|_|  \__, |\___| 
                   |___/      

JBoss Forge, version [ 1.1.2.Final ] - JBoss, by Red Hat, Inc. [ http://jboss.org/forge ]

Ara creo el nou projecte:

[no project] bin $ new-project
 ? [named=The name of the new project (of type java.lang.String)]: provaseam
 ? Use [/home/albert/seam-jboss/forge-distribution-1.1.2.Final/bin/provaseam] as project directory? [Y/n] n

 ? Where would you like to create the project? [Press ENTER to use the current directory: bin] [ .../bin] /home/albert/workspace/wk-java/provaseam
***SUCCESS*** Created project [provaseam] in new working directory [/home/albert/workspace/wk-java/provaseam]
Wrote /home/albert/workspace/wk-java/provaseam
Wrote /home/albert/workspace/wk-java/provaseam/pom.xml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/java
Wrote /home/albert/workspace/wk-java/provaseam/src/test/java
Wrote /home/albert/workspace/wk-java/provaseam/src/main/resources
Wrote /home/albert/workspace/wk-java/provaseam/src/test/resources
Wrote /home/albert/workspace/wk-java/provaseam/src/main/java/com/example/provaseam
[provaseam] provaseam $

new-project ha creat un fitxer pom.xml (per a compilar i deployar amb maven) i una estructura de carpetes.

Ara cal omplir aquesta estructura de carpetes de contingut. A continuació, configuro paràmetres incials de persistència:

[provaseam] provaseam $ persistence setup
 ? [provider=ARG (of type org.jboss.forge.spec.javaee.jpa.api.JPAProvider)]: org.jboss.forge.spec.javaee.jpa.api.JPAProvider
[provider=ARG (of type org.jboss.forge.spec.javaee.jpa.api.JPAProvider)]: 

  1 - [HIBERNATE]
  2 - [OPENJPA]
  3 - [ECLIPSELINK]
  4 - [INFINISPAN]

 ? Choose an option by typing the number of the selection: 1
 ? [container=ARG (of type org.jboss.forge.spec.javaee.jpa.api.JPAContainer)]: org.jboss.forge.spec.javaee.jpa.api.JPAContainer
[container=ARG (of type org.jboss.forge.spec.javaee.jpa.api.JPAContainer)]: 

  1 - [JBOSS_AS6]
  2 - [JBOSS_AS7]
  3 - [JBOSS_EAP6]
  4 - [GLASSFISH_3]
  5 - [CUSTOM_JDBC]
  6 - [CUSTOM_JTA]
  7 - [CUSTOM_NON_JTA]

L’anterior es podia haver fet en una sola línia així si hagués afegit les opcions provider i container a persistence setup:

persistence setup --provider HIBERNATE --container JBOSS_AS7

Segueix:

 ? Choose an option by typing the number of the selection: 2
Use which version of 'jboss-javaee-6.0' ?

  1 - [org.jboss.spec:jboss-javaee-6.0:pom::1.0.0.Beta4]
  2 - [org.jboss.spec:jboss-javaee-6.0:pom::1.0.0.Beta5]
  3 - [org.jboss.spec:jboss-javaee-6.0:pom::1.0.0.Beta6]
  4 - [org.jboss.spec:jboss-javaee-6.0:pom::1.0.0.Beta7]
  5 - [org.jboss.spec:jboss-javaee-6.0:pom::1.0.0.CR1]
  6 - [org.jboss.spec:jboss-javaee-6.0:pom::1.0.0.Final]
  7 - [org.jboss.spec:jboss-javaee-6.0:pom::2.0.0.Beta1]
  8 - [org.jboss.spec:jboss-javaee-6.0:pom::2.0.0.CR1]
  9 - [org.jboss.spec:jboss-javaee-6.0:pom::2.0.0.Final]
  10 - [org.jboss.spec:jboss-javaee-6.0:pom::2.1.0.Beta1]
  11 - [org.jboss.spec:jboss-javaee-6.0:pom::3.0.0.Beta1]
  12 - [org.jboss.spec:jboss-javaee-6.0:pom::3.0.0.Final]
  13 - [org.jboss.spec:jboss-javaee-6.0:pom::3.0.1.Final]*

 ? Choose an option by typing the number of the selection [*-default]  [0] 
***SUCCESS*** Installed [forge.spec.jpa] successfully.
***INFO*** Setting transaction-type="JTA"
***INFO*** Using example data source [java:jboss/datasources/ExampleDS]
 ? Do you want to install a JPA 2 metamodel generator? [y/N] 
 ? The JPA provider [HIBERNATE], also supplies extended APIs. Install these as well? [y/N] 
***SUCCESS*** Persistence (JPA) is installed.
Wrote /home/albert/workspace/wk-java/provaseam/src/main/resources/META-INF/persistence.xml
Wrote /home/albert/workspace/wk-java/provaseam/pom.xml
[provaseam] provaseam $

Fixem-nos en aquesta línia:

***INFO*** Using example data source [java:jboss/datasources/ExampleDS]

S’ha creat un datasource de prova. El podem trobar a la carpeta /home/albert/workspace/wk-java/provaseam/src/main/resources/META-INF/

El persistence.xml generat:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="forge-default" transaction-type="JTA">
    <description>Forge Persistence Unit</description>
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties>
      <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
      <property name="hibernate.show_sql" value="true"/>
      <property name="hibernate.format_sql" value="true"/>
      <property name="hibernate.transaction.flush_before_completion" value="true"/>
    </properties>
  </persistence-unit>
</persistence>

El que faré serà canviar el persistence.xml que fa servir el datasource d’exemple per un persistence.xml que apunti al datasource que crearé ara.

Creo el datasource. Per això copio un fitxer datasource.xml amb el següent contingut a la carpeta de deployments (JBOSS_HOME/standalone/deployments).

<datasources xmlns="http://www.jboss.org/ironjacamar/schema">
<datasource jndi-name="java:jboss/datasources/mysqldev" pool-name="poolmysqldev">
    <connection-url>jdbc:mysql://localhost/prova_seam</connection-url>
    <driver>mysql-connector-java-5.1.22-bin.jar</driver>
    <pool>
        <max-pool-size>30</max-pool-size>
    </pool>
    <security>
        <user-name>root</user-name>
        <password></password>
    </security>
</datasource>
</datasources>

Com abans amb el driver, si tenim una consola oberta amb els logs de funcionament del JBoss veurem com fa el deploy del datasource. A la consola d’administració podrem vereu el datasource que tot just hem creat i en podrem provar la connexió amb la base de dades.

I ara canvio el persistence.xml per un persistence.xml que apunti al meu datasource. Fixem-nos que les properties canvien. Pot ser interessant consultar la següent taula de Hibernate Dialects. En general, no està de més fer-li un cop d’ull a la documentació d’Hibernate.

Vet aquí el persistence.xml que poso a /home/albert/workspace/wk-java/provaseam/src/main/resources/META-INF/

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
             version="2.0">

    <persistence-unit name="test">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <jta-data-source>java:jboss/datasources/mysqldev</jta-data-source>
        <properties>
            <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
            <property name="hibernate.connection.username" value="root"/>
            <property name="hibernate.connection.password" value=""/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
        </properties>
    </persistence-unit>  
</persistence>

I ara ja puc generar les classes amb les hibernate tools:

[provaseam] provaseam $ generate-entities
 ? Specify the URL for the JDBC connection. [jdbc:h2:tcp://localhost/sakila] jdbc:mysql://localhost/prova_seam
 ? Enter the user name for JDBC connection. [null] root
 ? Enter the password for JDBC connection. 
 ? Enter the dialect to use for the datasource. [org.hibernate.dialect.H2Dialect] org.hibernate.dialect.MySQLDialect
 ? Specify the class name for the JDBC driver for the datasource. [org.h2.Driver] com.mysql.jdbc.Driver
 ? Enter the path in the local file system to the jar file containing the JDBC driver. [null] /home/albert/jdbc-drivers/mysql.jar
 ? In which package you'd like to generate the entities, or enter for default: [com.example.provaseam.model] com.stsoftlliure.provaseam.model
Found 7 tables in datasource
Generated java at /home/albert/workspace/wk-java/provaseam/src/main/java/com/stsoftlliure/provaseam/model/LlibreAutor.java
Generated java at /home/albert/workspace/wk-java/provaseam/src/main/java/com/stsoftlliure/provaseam/model/LlibreTematica.java
Generated java at /home/albert/workspace/wk-java/provaseam/src/main/java/com/stsoftlliure/provaseam/model/Tematica.java
Generated java at /home/albert/workspace/wk-java/provaseam/src/main/java/com/stsoftlliure/provaseam/model/Autor.java
Generated java at /home/albert/workspace/wk-java/provaseam/src/main/java/com/stsoftlliure/provaseam/model/Llibre.java
Generated java at /home/albert/workspace/wk-java/provaseam/src/main/java/com/stsoftlliure/provaseam/model/Ubicacio.java
Generated java at /home/albert/workspace/wk-java/provaseam/src/main/java/com/stsoftlliure/provaseam/model/Format.java
Generated 7 java files.
[provaseam] provaseam $

Ara genero l’esquelet de l’aplicació web:

[provaseam] provaseam $ scaffold setup
 ? No scaffold type was selected, use default [JavaServer Faces]? [Y/n] 
 ? Scaffold provider [faces] is not installed. Install it? [Y/n] 
 ? Facet [forge.maven.WebResourceFacet] requires packaging type(s) [war], but is currently [jar]. Update packaging? (Note: this could deactivate other plugins in your project.) [Y/n] 
***SUCCESS*** Installed [forge.maven.WebResourceFacet] successfully.
***SUCCESS*** Installed [forge.spec.ejb] successfully.
***SUCCESS*** Installed [forge.spec.cdi] successfully.
***SUCCESS*** Installed [forge.spec.servlet] successfully.
***SUCCESS*** Installed [forge.spec.jsf.api] successfully.
***SUCCESS*** Installed [faces] successfully.
 ? Create scaffold in which sub-directory of web-root? (e.g. http://localhost:8080/provaseam/DIR) [/] 
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp
Wrote /home/albert/workspace/wk-java/provaseam/pom.xml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/WEB-INF/beans.xml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/WEB-INF/faces-config.xml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/favicon.ico
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/resources/scaffold/paginator.xhtml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/resources/scaffold/pageTemplate.xhtml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/index.html
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/index.xhtml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/error.xhtml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/resources/add.png
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/resources/bootstrap.css
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/resources/false.png
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/resources/favicon.ico
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/resources/forge-logo.png
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/resources/forge-style.css
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/resources/remove.png
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/resources/search.png
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/resources/true.png
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/resources/jboss-community.png
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/WEB-INF/web.xml
[provaseam] provaseam $

Genero les classes de l’aplicació bàsica, a partir del model. L’aplicació bàsica és del tipus CRUD (Create, Read, Update i Delete), és dir, les quatre opcions mínimes de tot manteniment de taules.

[provaseam] provaseam $ scaffold from-entity com.stsoftlliure.provaseam.model.* --overwrite
***INFO*** Using currently installed scaffold [faces]
***SUCCESS*** Generated UI for [com.stsoftlliure.provaseam.model.Autor]
***SUCCESS*** Generated UI for [com.stsoftlliure.provaseam.model.Format]
***SUCCESS*** Generated UI for [com.stsoftlliure.provaseam.model.Llibre]
***SUCCESS*** Generated UI for [com.stsoftlliure.provaseam.model.LlibreAutor]
***SUCCESS*** Generated UI for [com.stsoftlliure.provaseam.model.LlibreTematica]
***SUCCESS*** Generated UI for [com.stsoftlliure.provaseam.model.Tematica]
***SUCCESS*** Generated UI for [com.stsoftlliure.provaseam.model.Ubicacio]
Wrote /home/albert/workspace/wk-java/provaseam/src/main/java/com/example/provaseam/view/AutorBean.java
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/autor/create.xhtml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/autor/view.xhtml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/autor/search.xhtml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/resources/scaffold/pageTemplate.xhtml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/java/com/example/provaseam/view/ViewUtils.java
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/WEB-INF/classes/META-INF/forge.taglib.xml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/java/com/stsoftlliure/provaseam/model/Autor.java
Wrote /home/albert/workspace/wk-java/provaseam/src/main/java/com/example/provaseam/view/FormatBean.java
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/format/create.xhtml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/format/view.xhtml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/format/search.xhtml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/java/com/stsoftlliure/provaseam/model/Format.java
Wrote /home/albert/workspace/wk-java/provaseam/src/main/java/com/example/provaseam/view/LlibreBean.java
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/llibre/create.xhtml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/llibre/view.xhtml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/llibre/search.xhtml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/java/com/stsoftlliure/provaseam/model/Llibre.java
Wrote /home/albert/workspace/wk-java/provaseam/src/main/java/com/example/provaseam/view/LlibreAutorBean.java
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/llibreAutor/create.xhtml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/llibreAutor/view.xhtml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/llibreAutor/search.xhtml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/java/com/stsoftlliure/provaseam/model/LlibreAutor.java
Wrote /home/albert/workspace/wk-java/provaseam/src/main/java/com/example/provaseam/view/LlibreTematicaBean.java
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/llibreTematica/create.xhtml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/llibreTematica/view.xhtml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/llibreTematica/search.xhtml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/java/com/stsoftlliure/provaseam/model/LlibreTematica.java
Wrote /home/albert/workspace/wk-java/provaseam/src/main/java/com/example/provaseam/view/TematicaBean.java
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/tematica/create.xhtml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/tematica/view.xhtml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/tematica/search.xhtml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/java/com/stsoftlliure/provaseam/model/Tematica.java
Wrote /home/albert/workspace/wk-java/provaseam/src/main/java/com/example/provaseam/view/UbicacioBean.java
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/ubicacio/create.xhtml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/ubicacio/view.xhtml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/webapp/ubicacio/search.xhtml
Wrote /home/albert/workspace/wk-java/provaseam/src/main/java/com/stsoftlliure/provaseam/model/Ubicacio.java
[provaseam] provaseam $

Anem molt bé. En principi, la aplicació ja està llesta per compilar i desplegar. Però en realitat no. Si compilem i despleguem ara, obtindrem l’aplicació, però en provar a introduir valors a les taules obtindré errors. (si no us ho creieu simplement salteu-vos aquest pas i compileu i desplegueu ara). Com anuncia el link http://www.mastertheboss.com/forge/reverse-engineer-your-db-schema-using-jboss-forge alguna cosa no ha acabat d’anar a l’hora. Els errors que obtindreu si decidiu provar amb l’aplicació tal comestà deixen aquesta traça:

22:51:02,392 ERROR [org.apache.catalina.core.ContainerBase.[jboss.web].[default-host].[/provaseam].[FacesServlet]] (http--127.0.0.1-8080-2) Servlet.service() for servlet FacesServlet threw exception: org.hibernate.TypeMismatchException: Provided id of the wrong type for class com.stsoftlliure.provaseam.model.Format. Expected: class java.lang.Integer, got class java.lang.Long

Els ID que ha generat el plugin de les hibernate-tools per a les entitats ha estat del tipus Integer; El codi al que em refereixo és aquest:

A la carpeta

src/main/java/com/stsoftlliure/provaseam/model/
    LlibreAutor.java
    LlibreTematica.java
    Tematica.java
    Autor.java
    Llibre.java
    Ubicacio.java
    Format.java

Però resulta que el codi que han generat el pas d’scaffolding (la línia “scaffold from-entity com.stsoftlliure.provaseam.model.* –overwrite”), que ha generat el següent codi java:

src/main/java/com/example/provaseam/view/
    ViewUtils.java
    AutorBean.java
    FormatBean.java
    LlibreBean.java
    LlibreAutorBean.java
    LlibreTematicaBean.java
    TematicaBean.java 
    UbicacioBean.java

Tenim que els ID que referencien els objectes són del tipus Long.

Per exemple, mirem les classes Autor i AutorBean

Autor

package com.stsoftlliure.provaseam.model;

// Generated 19/11/2012 23:16:54 by Hibernate Tools 3.4.0.CR1

import java.util.HashSet;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import static javax.persistence.GenerationType.IDENTITY;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

/**
 * Autor generated by hbm2java
 */
@Entity
@Table(name = "autor", catalog = "prova_seam")
public class Autor implements java.io.Serializable
{

   private Integer id;
   private String autor;
   private Set llibreAutors = new HashSet(0);
   private Set llibreTematicas = new HashSet(0);

   public Autor()
   {
   }

   public Autor(String autor)
   {
      this.autor = autor;
   }

   public Autor(String autor, Set llibreAutors, Set llibreTematicas)
   {
      this.autor = autor;
      this.llibreAutors = llibreAutors;
      this.llibreTematicas = llibreTematicas;
   }

   @Id
   @GeneratedValue(strategy = IDENTITY)
   @Column(name = "id", unique = true, nullable = false)
   public Integer getId()
   {
      return this.id;
   }

   public void setId(Integer id)
   {
      this.id = id;
   }

...

AutorBean

package com.example.provaseam.view;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.annotation.Resource;
import javax.ejb.SessionContext;
import javax.ejb.Stateful;
import javax.enterprise.context.Conversation;
import javax.enterprise.context.ConversationScoped;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.inject.Inject;
import javax.inject.Named;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import com.stsoftlliure.provaseam.model.Autor;

/**
 * Backing bean for Autor entities.
 *
* This class provides CRUD functionality for all Autor entities. It focuses * purely on Java EE 6 standards (e.g. @ConversationScoped for * state management, PersistenceContext for persistence, * CriteriaBuilder for searches) rather than introducing a CRUD framework or * custom base class. */ @Named @Stateful @ConversationScoped public class AutorBean implements Serializable { private static final long serialVersionUID = 1L; /* * Support creating and retrieving Autor entities */ private Long id; public Long getId() { return this.id; } public void setId(Long id) { this.id = id; } ...

En principi, doncs, i atenent al missatge d’error, tenim dues possibilitats: o corregir les classes generades amb les hibernate tools (canviant integer per long), o corregir les generades amb l’scaffolding (canviant Long per Integer), ara bé, resulta que a les classes generadese amb l’scaffolding, les NomClasseBean, es fa servir l’Id del tipus Long en altres mètodes d’us intern, com el FindById. Per tant, és millor canviar les classes generades amb Hibernate-tools, on l’Id només apareix un cop.

Per tant, canvio Integer per Long a Autor. Així:


(- Ep! Un moment! havies dit que no caldria escriure ni una línia de codi!
– Bé, estrictament no estem escrivint cap línia de codi, només estic modificant-ne alguna…Sí, d’acord… és una mica lleig)

package com.stsoftlliure.provaseam.model;

// Generated 19/11/2012 23:16:54 by Hibernate Tools 3.4.0.CR1

import java.util.HashSet;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import static javax.persistence.GenerationType.IDENTITY;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

/**
 * Autor generated by hbm2java
 */
@Entity
@Table(name = "autor", catalog = "prova_seam")
public class Autor implements java.io.Serializable
{

   private Long id;
   private String autor;
   private Set llibreAutors = new HashSet(0);
   private Set llibreTematicas = new HashSet(0);

   public Autor()
   {
   }

   public Autor(String autor)
   {
      this.autor = autor;
   }

   public Autor(String autor, Set llibreAutors, Set llibreTematicas)
   {
      this.autor = autor;
      this.llibreAutors = llibreAutors;
      this.llibreTematicas = llibreTematicas;
   }

   @Id
   @GeneratedValue(strategy = IDENTITY)
   @Column(name = "id", unique = true, nullable = false)
   public Long getId()
   {
      return this.id;
   }

   public void setId(Long id)
   {
      this.id = id;
   }

...

Un cop he fet els canvis a les classes d’entitat (Autor.java, Format.java, Llibre.java, Tematica.java, Ubicacio.java, LlibreAutor.java i LlibreTematica.java), puc compilar i deployar.

Per a fer-ho, primer instal·laré al forge el plugin de control del JBoss AS 7.1:

[provaseam] provaseam $ forge install-plugin jboss-as-7
Connecting to remote repository [https://raw.github.com/forge/plugin-repository/master/repository.yaml]... connected!
***INFO*** Preparing to install plugin: jboss-as-7
***INFO*** Checking out plugin source files to [/tmp/forgetemp3143468914443124072/repo] via 'git'
***INFO*** Switching to branch/tag [refs/heads/1.0.5.Final]
***INFO*** Invoking build with underlying build system.
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building plugin-jboss-as7 1.0.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-clean-plugin:2.4.1:clean (default-clean) @ plugin-jboss-as7 ---
[INFO] 
[INFO] --- maven-resources-plugin:2.4.3:resources (default-resources) @ plugin-jboss-as7 ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 2 resources
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:compile (default-compile) @ plugin-jboss-as7 ---
[WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent!
[INFO] Compiling 23 source files to /tmp/forgetemp3143468914443124072/repo/target/classes
[INFO] 
[INFO] --- maven-resources-plugin:2.4.3:testResources (default-testResources) @ plugin-jboss-as7 ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory /tmp/forgetemp3143468914443124072/repo/src/test/resources
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:testCompile (default-testCompile) @ plugin-jboss-as7 ---
[INFO] Not compiling test sources
[INFO] 
[INFO] --- maven-surefire-plugin:2.7.2:test (default-test) @ plugin-jboss-as7 ---
[INFO] Tests are skipped.
[INFO] 
[INFO] --- maven-jar-plugin:2.3.1:jar (default-jar) @ plugin-jboss-as7 ---
[INFO] Building jar: /tmp/forgetemp3143468914443124072/repo/target/plugin-jboss-as7.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 10.310s
[INFO] Finished at: Sun Nov 18 16:25:25 CET 2012
[INFO] Final Memory: 47M/113M
[INFO] ------------------------------------------------------------------------
***INFO*** Installing plugin artifact.
***SUCCESS*** Installed from [https://github.com/forge/plugin-jboss-as.git] successfully.
Wrote /home/albert/.forge/httpsrawgithubcomforgepluginrepositorymasterrepositoryyaml.yaml

...

Wrote /home/albert/.forge/plugins/org/jboss/as/plugin-jboss-as7/dependencies/1.0.0-SNAPSHOT-b32f7bdd-fc1e-4d44-91ab-18119749258b/module.xml
    _____                    
   |  ___|__  _ __ __ _  ___ 
   | |_ / _ \| `__/ _` |/ _ \  \\
   |  _| (_) | | | (_| |  __/  //
   |_|  \___/|_|  \__, |\___| 
                   |___/      

JBoss Forge, version [ 1.1.2.Final ] - JBoss, by Red Hat, Inc. [ http://jboss.org/forge ]
The following plugins have been activated: [as7]
[provaseam] provaseam $

Compilo…

[provaseam] provaseam $ build
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building provaseam 1.0.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
Downloading: http://repo1.maven.org/maven2/org/apache/maven/plugins/maven-war-plugin/2.1.1/maven-war-plugin-2.1.1.pom
Downloaded: http://repo1.maven.org/maven2/org/apache/maven/plugins/maven-war-plugin/2.1.1/maven-war-plugin-2.1.1.pom (7 KB at 38.6 KB/sec)
Downloading: http://repo1.maven.org/maven2/org/apache/maven/plugins/maven-war-plugin/2.1.1/maven-war-plugin-2.1.1.jar
Downloaded: http://repo1.maven.org/maven2/org/apache/maven/plugins/maven-war-plugin/2.1.1/maven-war-plugin-2.1.1.jar (76 KB at 657.7 KB/sec)
Downloading: http://repo1.maven.org/maven2/org/apache/maven/plugins/maven-install-plugin/2.3.1/maven-install-plugin-2.3.1.pom
Downloaded: http://repo1.maven.org/maven2/org/apache/maven/plugins/maven-install-plugin/2.3.1/maven-install-plugin-2.3.1.pom (5 KB at 83.7 KB/sec)
Downloading: http://repo1.maven.org/maven2/org/apache/maven/plugins/maven-install-plugin/2.3.1/maven-install-plugin-2.3.1.jar
Downloaded: http://repo1.maven.org/maven2/org/apache/maven/plugins/maven-install-plugin/2.3.1/maven-install-plugin-2.3.1.jar (23 KB at 352.8 KB/sec)
[INFO] 
[INFO] --- maven-resources-plugin:2.4.3:resources (default-resources) @ provaseam ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 2 resources
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:compile (default-compile) @ provaseam ---
[INFO] Compiling 15 source files to /home/albert/workspace/wk-java/provaseam/target/classes
[INFO] 
[INFO] --- maven-resources-plugin:2.4.3:testResources (default-testResources) @ provaseam ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 0 resource
[INFO] 
[INFO] --- maven-compiler-plugin:2.3.2:testCompile (default-testCompile) @ provaseam ---
[INFO] Not compiling test sources
[INFO] 
[INFO] --- maven-surefire-plugin:2.7.2:test (default-test) @ provaseam ---
[INFO] Tests are skipped.
[INFO] 
[INFO] --- maven-war-plugin:2.1.1:war (default-war) @ provaseam ---
Downloading: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-archiver/1.2/plexus-archiver-1.2.pom
Downloaded: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-archiver/1.2/plexus-archiver-1.2.pom (2 KB at 32.2 KB/sec)
Downloading: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-io/1.0.1/plexus-io-1.0.1.pom
Downloaded: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-io/1.0.1/plexus-io-1.0.1.pom (2 KB at 20.5 KB/sec)
Downloading: http://repo1.maven.org/maven2/com/thoughtworks/xstream/xstream/1.3.1/xstream-1.3.1.pom
Downloaded: http://repo1.maven.org/maven2/com/thoughtworks/xstream/xstream/1.3.1/xstream-1.3.1.pom (12 KB at 166.6 KB/sec)
Downloading: http://repo1.maven.org/maven2/com/thoughtworks/xstream/xstream-parent/1.3.1/xstream-parent-1.3.1.pom
Downloaded: http://repo1.maven.org/maven2/com/thoughtworks/xstream/xstream-parent/1.3.1/xstream-parent-1.3.1.pom (14 KB at 210.2 KB/sec)
Downloading: http://repo1.maven.org/maven2/xpp3/xpp3_min/1.1.4c/xpp3_min-1.1.4c.pom
Downloaded: http://repo1.maven.org/maven2/xpp3/xpp3_min/1.1.4c/xpp3_min-1.1.4c.pom (2 KB at 33.5 KB/sec)
Downloading: http://repo1.maven.org/maven2/org/apache/maven/shared/maven-filtering/1.0-beta-2/maven-filtering-1.0-beta-2.pom
Downloaded: http://repo1.maven.org/maven2/org/apache/maven/shared/maven-filtering/1.0-beta-2/maven-filtering-1.0-beta-2.pom (4 KB at 64.5 KB/sec)
Downloading: http://repo1.maven.org/maven2/org/apache/maven/shared/maven-shared-components/10/maven-shared-components-10.pom
Downloaded: http://repo1.maven.org/maven2/org/apache/maven/shared/maven-shared-components/10/maven-shared-components-10.pom (9 KB at 84.9 KB/sec)
Downloading: http://repo1.maven.org/maven2/org/apache/maven/maven-parent/9/maven-parent-9.pom
Downloaded: http://repo1.maven.org/maven2/org/apache/maven/maven-parent/9/maven-parent-9.pom (33 KB at 433.3 KB/sec)
Downloading: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-utils/1.5.6/plexus-utils-1.5.6.pom
Downloaded: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-utils/1.5.6/plexus-utils-1.5.6.pom (6 KB at 101.5 KB/sec)
Downloading: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus/1.0.12/plexus-1.0.12.pom
Downloaded: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus/1.0.12/plexus-1.0.12.pom (10 KB at 106.3 KB/sec)
Downloading: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-interpolation/1.6/plexus-interpolation-1.6.pom
Downloaded: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-interpolation/1.6/plexus-interpolation-1.6.pom (3 KB at 55.7 KB/sec)
Downloading: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-archiver/1.2/plexus-archiver-1.2.jar
Downloading: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-io/1.0.1/plexus-io-1.0.1.jar
Downloading: http://repo1.maven.org/maven2/com/thoughtworks/xstream/xstream/1.3.1/xstream-1.3.1.jar
Downloading: http://repo1.maven.org/maven2/xpp3/xpp3_min/1.1.4c/xpp3_min-1.1.4c.jar
Downloading: http://repo1.maven.org/maven2/org/apache/maven/shared/maven-filtering/1.0-beta-2/maven-filtering-1.0-beta-2.jar
Downloaded: http://repo1.maven.org/maven2/xpp3/xpp3_min/1.1.4c/xpp3_min-1.1.4c.jar (25 KB at 135.4 KB/sec)
Downloaded: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-io/1.0.1/plexus-io-1.0.1.jar (50 KB at 248.4 KB/sec)
Downloaded: http://repo1.maven.org/maven2/org/apache/maven/shared/maven-filtering/1.0-beta-2/maven-filtering-1.0-beta-2.jar (33 KB at 150.7 KB/sec)
Downloaded: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-archiver/1.2/plexus-archiver-1.2.jar (178 KB at 595.9 KB/sec)
Downloaded: http://repo1.maven.org/maven2/com/thoughtworks/xstream/xstream/1.3.1/xstream-1.3.1.jar (422 KB at 934.1 KB/sec)
[INFO] Packaging webapp
[INFO] Assembling webapp [provaseam] in [/home/albert/workspace/wk-java/provaseam/target/provaseam]
[INFO] Processing war project
[INFO] Copying webapp resources [/home/albert/workspace/wk-java/provaseam/src/main/webapp]
[INFO] Webapp assembled in [248 msecs]
[INFO] Building war: /home/albert/workspace/wk-java/provaseam/target/provaseam.war
[INFO] WEB-INF/web.xml already added, skipping
[INFO] 
[INFO] --- maven-install-plugin:2.3.1:install (default-install) @ provaseam ---
Downloading: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-digest/1.0/plexus-digest-1.0.pom
Downloaded: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-digest/1.0/plexus-digest-1.0.pom (2 KB at 19.5 KB/sec)
Downloading: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-components/1.1.7/plexus-components-1.1.7.pom
Downloaded: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-components/1.1.7/plexus-components-1.1.7.pom (5 KB at 71.4 KB/sec)
Downloading: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus/1.0.8/plexus-1.0.8.pom
Downloaded: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus/1.0.8/plexus-1.0.8.pom (8 KB at 117.7 KB/sec)
Downloading: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-container-default/1.0-alpha-8/plexus-container-default-1.0-alpha-8.pom
Downloaded: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-container-default/1.0-alpha-8/plexus-container-default-1.0-alpha-8.pom (8 KB at 150.9 KB/sec)
Downloading: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-digest/1.0/plexus-digest-1.0.jar
Downloaded: http://repo1.maven.org/maven2/org/codehaus/plexus/plexus-digest/1.0/plexus-digest-1.0.jar (12 KB at 160.8 KB/sec)
[INFO] Installing /home/albert/workspace/wk-java/provaseam/target/provaseam.war to /home/albert/.m2/repository/com/example/provaseam/provaseam/1.0.0-SNAPSHOT/provaseam-1.0.0-SNAPSHOT.war
[INFO] Installing /home/albert/workspace/wk-java/provaseam/pom.xml to /home/albert/.m2/repository/com/example/provaseam/provaseam/1.0.0-SNAPSHOT/provaseam-1.0.0-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 10.090s
[INFO] Finished at: Sun Nov 18 16:28:22 CET 2012
[INFO] Final Memory: 65M/155M
[INFO] ------------------------------------------------------------------------
[provaseam] provaseam $

Configuro el plugin per a fer-lo servir amb el meu JBoss local

[provaseam] provaseam $ as7 setup
 ? The Java Home '/home/albert/jdk1.6.0_30' is already set, would you like to override it? [y/N] 
 ? A default version of 7.1.1.Final is already set, would you like to override it? [y/N] 
 ? Enter path for JBoss AS or leave blank to download: /home/albert/jboss-as-7.1.1.Final
***SUCCESS*** Installed [AS7ServerFacet] successfully.
[provaseam] provaseam $

i, finalment, faig el deploy:

[provaseam] provaseam $ as7 deploy
The deployment operation (FORCE_DEPLOY) was successful.
[provaseam] provaseam $

I ja només em queda provar l’aplicació que acabo de deplegar. Per això apunto el navegador a http://localhost:8080/provaseam i, efectivament obtinc una senzilla aplicació de biblioteca. Basada en un manteniment de taules. Com he dit abans, el resultat és força espectacular. L’esforç ha estat en definir i crear correctament les taules, els índexos i les relacions entre les taules (i arreglar alguns petits desajustos que Seam Forge ha deixat).

A la consola d’administració del JBoss podem veure els desplegaments realitzats:

Vet aquí la pantalla inicial de l’aplicació:

Per cert, com a extra, des de la pantalla inicial es pot enviar un tuit (feu click al link Get Excited!) per anunciar al món que ja hem fet la nostra primera aplicació amb Seam 3 i Forge! No m’hi estic pas!

Les relacions entre taules es resolen amb camps desplegables. Entro alguns formats i ubicacions i creo un llibre:

format:

ubicacio:

vaig a crear un llibre:

El llibre creat:

Cal reconèixer que és força lletja la representació dels formats i les ubicacions. Caldria corregir les visualitzacions. En tot cas, podem seguir l’enllaç, per exemple, de les ubicacions per obtenir tots els llibres que estan al prestatge de l’estudi:

En resum, una generació força automatitzada d’una aplicació de manteniment de taules (un CRUD) feta amb l’eina Seam Forge, amb la versió de Framework Seam 3.1. desplegada sobre un JBoss 7.1.

Val a dir que després de provar això mateix amb el seam-gen del framework Seam 2.3.0 un es queda una mica decebut. Fa l’efecte com si s’hagués anat enrere. En realitat, sembla que la qüestió és que s’ha iniciat un camí nou amb el Forge. Encara més, a la mateixa definició de Seam3 que fan a la web www.seamframeworkorg/seam3 han fet desaparèixer la paraula framework per parlar de conjunt de mòduls i eines: “Seam3 is a collection of modules and developer tooling tailored for Java EE 6 application development, with CDI as the central piece.”i encara una mica després, referint-se a Forge el defineixen com “JBoss Forge is an incremental project enhancement API and shell.” De fet, la forma de treballar amb Forge té més a veure amb definir des del forge les entitats i les accions, que no pas fer una enginyeria inversa d’un model existent. O sigui que hem fet servir el Forge no de la forma com “estava prevista” (en canvio seam-gen de Seam2, sí que estava més pensat per a això).

La definició anterior de Seam, a més, feia aparèixer la sigla màgica CDI. CDI, Context and Dependency Injection. CDI i Java EE 6 (HTML | PDF). No és ara el moment per parlar-ne. Però caldrà fer-ho.

Al proper post faré de nou l’aplicació de Biblioteca amb el Seam 2 i el seam-gem. Tot i que es tracta de la versió anterior manté tot l’interès ja que, en cap cas, es pot considerar que Seam 2 sigui obsolet. De fet, com he comentat abans, per una qüestió de compatibilitat amb versions de JDK i Java EE podem ben bé trobar-nos amb que no es pugui fer servir la versió Seam 3, que demana últimes versions.

Anuncis

Com afegir un captcha amb PHP i Securimage

Com fer un captcha amb PHP?

Una de les formes habituals de protegir formularis web de l’acció de robots o programes que fan peticions automàtiques és afegir un captcha.

Els captcha són aquestes paraules generades aleatòriament i que es mostren amb les lletres deformades, ratllades, sobre fons degradats o de trames… de forma que no poden ser reconegudes per programes d’OCR, però sí per operadors humans. Aleshores, per validar el formulari web protegit per un captcha, l’operador humà ha d’encertar la paraula o paraules que “oculta”.

Es tracta d’una prova que permet garantir amb una certesa prou important (si més no, per ara) que qui envia el formulari és una persona i no un robot.

No cal inventar la roda. Per afegir un captcha a les nostres aplicacions web n’hi ha prou amb incorporar alguna de les llibreries que es poden trobar per Internet. Per descomptat, trobarem llibreries de codi obert, i en una varietat de llenguatges.

Amb PHP hem provat aquesta: Securimage. Es pot descarregar de la seva pàgina de download.

A dia d’avui podem descarregar la versió 3. També podem descarregar una pàgina de proves per a determinar si la nostra instal·lació té les versions adequades de PHP i llibreries gràfiques per a suportar-la.

A la web de Securimage (http://www.phpcaptcha.org/) trobarem documentació, FAQ, demos…

A continuació faré un petit exercici per a demostrar com afegir un captcha a una aplicació web.

El plantejament és el següent:

  • Faré un formulari web amb els camps nom, email, i captcha
  • El formulari va a una pàgina de resposta que valida el captcha. Si la validació és correcta, ens donarà un missatge de benvinguda; si la validació no és correcta, ens informarà de captcha erroni.

És dir, dues pàgines php:

  • El formulari web.
  • la pàgina de resposta.

A la QuickStart Guide ens explica com hauran de ser aquestes pàgines.

Estic desenvolupant en un entorn local amb Xampp. He creat una carpeta captcha a l’htdocs. Dins aquesta carpeta he descomprimit la llibreria securimage. Ara tinc, dins de la carpeta captcha, una carpeta securimage amb la llibreria securimage.php i la resta de fitxers auxiliars.

A la carpeta captcha poso la pàgina de formulari. Fixem-nos que és una pàgina php senzilla en la que només hi ha la invocació a la sessió php com a únic codi de servidor. La resta és un formulari web senzill.

El captcha es genera invocant securimage_show.php.al src de l’etiqueta img.

Securimage_show.php amaga una seqüència similar a aquesta:

    require_once 'securimage.php';
    $image = new Securimage();
    $image->show();

El camp d’entrada corresponent al text del captcha és un input type text normal.

En el cas que el text del captcha sigui il·legible, amb el javascript de l’enllaç es torna a invocar securimage_show.php per a generar-ne un de nou. Fixem-nos en l’ús de Math.random() per a evitar problemes de caché..

Vet aquí el codi

<?php session_start(); ?>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" />
<title>Pàgina de prova del formulari</title>
</head>
<body>
<form action="prova-resposta.php" method="post">
Nom: <input type="text" name="sNom" maxlength="20" /><br />
Email: <input type="text" name="sEmail" maxlength="40" /><br />

<!-- el captcha, propiament dit -->
Captcha: <br />
<img id="captcha" src="securimage/securimage_show.php" alt="CAPTCHA Image" /><br />

<!-- el camp d'entrada -->
<input type="text" name="sCaptcha" size="10" maxlength="6" />

<!-- Per si la imatge del captcha no s'entén, es permet la possibilitat de generar-ne un altre-->
<a href="#"
onclick="document.getElementById('captcha').src = 'securimage/securimage_show.php?' + Math.random(); return false">
[ genera un nou captcha ]</a>
<br />
<input type="submit" value="envia"/>
</form>
</body>
</html>

I ara el codi del prova-resposta.php. Aquesta pàgina tindrà com esquelet la següent seqüència de codi:

    require_once 'securimage.php';
    $image = new Securimage();

    if ($image->check($_POST['sCaptcha']) == true) {
      echo "Correcte!";
    } else {
      echo "Codi erroni!";
}

Vet aquí el codi:


<?php session_start(); ?>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" />
<title>Pàgina de resposta del formulari</title>
</head>
<body>
<?php
# carreguem la llibreria
include_once 'securimage/securimage.php';

#instanciem l'objecte de captcha
$securimage = new Securimage();

if ($securimage->check($_POST['sCaptcha']) == true) {
?>
Validació correcte!<br />
paràmetres llegits: <br />
sNom: <?php echo($_POST['sNom']) ?> <br />
sEmail: <?php echo($_POST['sEmail']) ?> <br />
<?php
} else {
?>
Validació incorrecte!<br />
<a href="prova-form.php">Torna a provar</a><br />
<?php
}
?>
</body>
</html>

I un exemple d’execució

i la resposta

Codis QR amb Java i ZXing

Cada cop és més comú trobar arreu codis QR. Els codis QR apareixen en promocions i en publicitat de tots tipus. Sens dubte que les aplicacions per a telèfons mòbils que permeten descodificar aquests  codis de barres han contribuït a popularitzar los i, a la vegada, són un estímul més per a fer-se amb un smartphone que permeti gaudir-ne d’aquesta capacitat. Es tracta de dues tecnologies que es reforcen l’una a l’altre.

Vet aquí el que en diu la Viquipèdia dels codis QR:
“El codi QR (en anglès QR Code) és un codi de barres en 2 dimensions (codi matriu) que pot emmagatzemar fins a 7089 caràcters numèrics, 4296 caràcters alfanumèrics (contràriament al codi de barres “tradicional” que només pot emmagatzemar de 10 a 13 caràcters) o 2953 octets .

Per accedir a la informació continguda o encriptada en un qr-code, és necessari un dispositiu digital de captura d’imatges ( Ex: càmera de fotos d’un mòbil, webcam…) i un software específic lector de qr-codes.

Té l’avantatge de poder emmagatzemar moltes informacions tot i ser petit i ràpid d’escanejar. Així, les sigles «QR» deriven de «Quick Response» ja que el contingut pot ser desxifrat ràpidament.
La informació oculta en el codi pot estar en forma de:

TEXT
URL (ens permetrà visitar una pàgina web concreta amb un sol clic)
SMS ( podrem enviar un sms predeterminat a un número de predeterminat amb un sol clic)
VCARD (targes de presentació)

Les possibilitats i aplicacions d’aquests codis en gestió i màrqueting (especialment en opt-in màrqueting i promocions) són novedoses i interessants ja que mitjançant els codis qr es poden unir dos suports històricament aïllats: paper i internet o tinta i bits.

El codi QR ha estat creat per l’empresa japonesa Denso-Wave el 1994. El codi QR és molt emprat al Japó, on actualment és el codi de dues dimensions més popular.

L’estàndard japonès per als codis QR, JIS X 0510, ha estat publicat el 1999, i la norma ISO corresponent, ISO/IEC 18004, ha estat aprovada el juny de 2000.

Un detall important sobre el codi QR, seria que es tracta de codi obert i els seus drets de patent (propietat de Denso Wave) no són exercits.

Que els codis QR siguin d’alguna manera codi open source tampoc deu ser aliè al seu èxit. 
Però, i com es poden crear codis QQ? i com descodificar-los? Més concretament, com crear o llegir codis QR amb Java ( o amb JVM)? Una cerca ràpida amb Google ens mostra diverses llibreries. Em crida l’atenció la llibreria ZXing.
ZXing és una llibreria per al processat d’imatges de codis de barres de 1D/2D per a clients Android i Java. 
Dit i fet, descarrego la llibreria (versió 1.7) d’aquí: http://code.google.com/p/zxing/downloads/detail?name=ZXing-1.7.zip, i la descomprimeixo.
El paquet té la següent estructura:

albert@athena:~/zxing$ ls
actionscript         androidtest  build.properties  COPYING  csharp  javase  README   zxing.appspot.com
android              AUTHORS      build.xml         core     iphone  jruby   rim      zxingorg
android-integration  bug          CHANGES           cpp      javame  objc    symbian
albert@athena:~/zxing$ 

Observem que no hi han jars. Cal compilar-los. És tan senzill com, en un terminal, moure’s primrer a la carpeta core i executar ant, i després a la carpeta javase i tornar a excutar ant. Cal, evidentment, tenir ant instal·lat.
De moment, seguiré les instruccions que posen al lloc web de ZXing. 
albert@athena:~/zxing$ cd core
albert@athena:~/zxing/core$ ls
build  build.xml  lib  pom.xml  src  test
albert@athena:~/zxing/core$ ant
Buildfile: /home/albert/zxing/core/build.xml

clean:
   [delete] Deleting directory /home/albert/zxing/core/build

build:

init:

compile:
    [mkdir] Created dir: /home/albert/zxing/core/build
    [javac] Compiling 177 source files to /home/albert/zxing/core/build
    [javac] warning: [options] bootstrap class path not set in conjunction with -source 1.3
    [javac] 1 warning
      [jar] Building jar: /home/albert/zxing/core/core.jar

BUILD SUCCESSFUL
Total time: 34 seconds
albert@athena:~/zxing/core$ cd ../javase
albert@athena:~/zxing/javase$ ls
build  build.xml  pom.xml  src
albert@athena:~/zxing/javase$ ant
Buildfile: /home/albert/zxing/javase/build.xml

init:

build:
      [jar] Building jar: /home/albert/zxing/javase/javase.jar

BUILD SUCCESSFUL
Total time: 6 seconds
albert@athena:~/zxing/javase$ 

Si examinem de nou les carpetes core i javase trobarem core.jar i javase.jar a les carpetes de mateix nom:
albert@athena:~/zxing/javase$ ls
build  build.xml  javase.jar  pom.xml  src
albert@athena:~/zxing/javase$ cd ../core
albert@athena:~/zxing/core$ ls
build  build.xml  core.jar  lib  pom.xml  src  test
albert@athena:~/zxing/core$ 
En aquest moment es pot fer una prova. Faré servir la següent  imatge:
I ara, fem la prova executant la classe CommandLineRunner que permet descodificar una imatge:
albert@athena:~/zxing$ java -cp javase/javase.jar:core/core.jar com.google.zxing.client.j2se.CommandLineRunner ./sticipl.png
file:/home/albert/zxing/./sticipl.png (format: QR_CODE, type: TEXT):
Raw result:
Serveis TIC i Programari Lliure
Parsed result:
Serveis TIC i Programari Lliure
Found 4 result points.
  Point 0: (14.0,102.0)
  Point 1: (14.0,14.0)
  Point 2: (102.0,14.0)
  Point 3: (90.0,90.0)
albert@athena:~/zxing$ 
Pel que fa a la documentació, el javadoc de ZXing el podem trobar a http://zxing.org/w/docs/javadoc/index.html
També ens pot ajudar la pàgina de les DeveloperNotes (http://code.google.com/p/zxing/wiki/DeveloperNotes)
El que faré serà un parell de programes d’exemple, un per a crear codis de barra  QR i un altre per a descodificar-los. Un coder i un decoder d’exemple.

Primer de tot, el decoder. L’aplicaré al següent QR:

Vet aquí el codi:

package com.sticipl.prova.zxing;


import com.google.zxing.Reader;
import com.google.zxing.qrcode.QRCodeReader;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.LuminanceSource;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.ResultPoint;
import com.google.zxing.Result;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;






public class ProvaZxing {
    public ProvaZxing() {
    try {
        Reader reader = new QRCodeReader();    
     
        BufferedImage myImage = ImageIO.read(
                                    new File(“/home/albert/Workspace/wk-java/prova-zxing/img/albert.png”)
                                );
        LuminanceSource source = new BufferedImageLuminanceSource(myImage);
        BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
    
        Result result = reader.decode(bitmap);


        String text = result.getText();
        System.out.println(“Text llegit: ” + text);
              
        byte[] rawBytes = result.getRawBytes();
        for (byte byteElem : rawBytes) { 
            System.out.println(“Byte: ” + byteElem);
        }
        
        BarcodeFormat format = result.getBarcodeFormat();
        System.out.println(“Format del codi: ” + format);
        
        ResultPoint[] punts = result.getResultPoints();    
        for (ResultPoint punt : punts) { 
            System.out.println(“Punt.x: ” + punt.getX() + “; Punt.y: ” + punt.getY());
        }
      } catch(Exception e) {
        System.out.println(“Error: ” + e.toString());      
      }  
    }

  public static void main(String args[]) {
        new ProvaZxing();  
    }
}

El programa és directe: instancia un reader del tipus QRCodeReader, crea una BufferedImage a partir de la imatge del  QR. El reader s’aplica al QRReder i, senzillament, el descodifica. El resultat s’obté en diferents formats.

L’aplicació del programa sobre la darrera imatge proporciona la sortida:

Text llegit: Albert Baranguer Codina
Byte: 65
Byte: 116
Byte: 22
Byte: -58
Byte: 38
Byte: 87
Byte: 39
Byte: 66
Byte: 4
Byte: 38
Byte: 23
Byte: 38
Byte: 22
Byte: -26
Byte: 119
Byte: 86
Byte: 87
Byte: 34
Byte: 4
Byte: 54
Byte: -10
Byte: 70
Byte: -106
Byte: -26
Byte: 16
Byte: -20
Byte: 17
Byte: -20
Format del codi: QR_CODE
Punt.x: 14.0; Punt.y: 86.0
Punt.x: 14.0; Punt.y: 14.0
Punt.x: 86.0; Punt.y: 14.0
Punt.x: 74.0; Punt.y: 74.0

La creació de codis QR no té dificultat i segueix un procediment similar al de la descodificació: primer de tot, cal instanciar un QRCodeWriter i amb aquesta instància, directament es codifica la informació. El resultat és una matriu de bits que es fa servir per a crear una BufferedImage. Vet aquí el mètode codifica(String sText), que rep com a argument el text a codificar

    // genera un codi QR per al text passat
    public void codifica(String sCadena) {
      try {      
        Writer writer = new QRCodeWriter();
        BitMatrix bitmat = writer.encode(sCadena, BarcodeFormat.QR_CODE, 200, 200);
      
        // genera la imatge. 
        // Obté ample i alt
        int width = bitmat.getWidth(); 
        int height = bitmat.getHeight(); 
      
        // crea BufferedImage
        BufferedImage imatge = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);


        // estableix els bits de la imatge (true, negre; false, blanc)
        for (int y = 0; y < height; y++) { 
          for (int x = 0; x < width; x++) { 
   boolean boolPunt = bitmat.get(x,y); 
   imatge.setRGB(x, y, (boolPunt ? 0 : 0xFFFFFF));
 }
        }

        // genera el fitxer d’imatge a partir de la BufferedImage
        File file = new File(“/home/albert/Workspace/wk-java/prova-zxing/img/generat.png”);
        FileOutputStream fos = new FileOutputStream(file);
        ImageIO.write(imatge, “png”, fos);
        fos.flush();
        fos.close(); 
      } catch(Exception e) {
System.out.println(“Error: ” + e.toString());      
      }       
    }

Aplicant el mètode amb els argument mostrats:

    public static void main(String args[]) {
        ProvaZxing prova = new ProvaZxing();
        // prova.decode();  
        prova.codifica(“Generat amb Zxing – Serveis TIC i Programari Lliure – Albert Baranguer Codina – accents: àèò éíóú ç ñ …”);
    }

obtinc la imatge:

Per acabar, una altre imatge generada amb el mètode codifica(). Endevineu quin és el text que amaga?

Un "Piulador" amb Twitter4J amb gestió de l’autenticació

En el post anterior vaig fer un “piulador” personal que em permet enviar tweets (o piulades) al compte associat a l’Access Token.

Per obtenir l’Access Token i l’Access Token Secret va caldre demanar-los a la web de Twitter. Els valors que em va proporcionar Twitter  els vaig fer servir posant-los a l’objecte de la classe ConfigurationBuilder que passava com argument al constructor de l’objecte de la classe Twitter que és el que permet l’operació.
Un cop obtinguts l’Access Token i l’Access Token Secretm aquests dos valors els podrà fer servir el Piulador indefinidament. L’usuari pot, però, forçar que se’n generin de nous des de la web de Twitter. 
Per a que el “Piulador” el pugui fer servir tothom que tingui un compte a Twitter cal que sigui el mateix piulador qui el demani i en gestioni l’obtenció. Això és el que faré avui.
El codi del partida és el Piulador del post anterior:
package com.sticipl.proves;

import twitter4j.Status;
import twitter4j.Twitter;
import twitter4j.TwitterException;
import twitter4j.TwitterFactory;
import twitter4j.conf.ConfigurationBuilder;

public final class ProvaTwitter {
    /**
     * Usage: java twitter4j.examples.tweets.UpdateStatus [text]
     *
     * @param args message
     */
    public static void main(String[] args) {
        try {
         ConfigurationBuilder cb = new ConfigurationBuilder();
         cb.setDebugEnabled(true)
             .setOAuthConsumerKey(“[El consumer Key obtingut]”)
             .setOAuthConsumerSecret(“[el consumer secret obtingut]”)
             .setOAuthAccessToken(“[l’access token obtingut]”)
             .setOAuthAccessTokenSecret(“[l’access token secret obtingut]”);
         TwitterFactory tf = new TwitterFactory(cb.build());
         Twitter twitter = tf.getInstance();
            
            Status status = twitter.updateStatus(“Aquest és el missatge enviat des de l’aplicació #Java de prova fent us de #Twitter4J”);
            System.out.println(“Actualitzat status a [” + status.getText() + “].”);
            System.exit(0);
        } catch (TwitterException te) {
            te.printStackTrace();
            System.out.println(“Excepció de Twitter: ” + te.getMessage());
            System.exit(-1);
        }
    }
}
El que cal fer és canviar el bloc de codi del ConfigurationBuilder per un bloc de codi que en gestioni l’obtenció del parell Access Token / Access Token Secret.
Al post anterior vaig dir que el Consumer ha de demanar l’autorització de l’usuari amb un Request Token i que aquest Request Token es bescanvia per l’Access Token.  Com es fa això? A la secció dels exemples de codi de la web Twitter4J expliquen com:
    With OAuth authorization scheme, an application can access the user account without userid/password combination given. You need to register your application athttp://twitter.com/oauth_clients/new to acquire consumer key, and consumer secret in advance. key / secret pair can be set via Twitter#setOAuthConsumer(), or following system properties:

    -Dtwitter4j.oauth.consumerKey=[consumer key]
    -Dtwitter4j.oauth.consumerSecret=[consumer secret]

      Initially, you don’t have a permission to access the user’s account and need to acquire access token by redirecting the user to an authorization URL as follows:

        public static void main(String args[]) thrwos Exception{
      // The factory instance is re-useable and thread safe.
      Twitter twitter = new TwitterFactory().getInstance();
      twitter.setOAuthConsumer("[consumer key]", "[consumer secret]");
      RequestToken requestToken = twitter.getOAuthRequestToken();
      AccessToken accessToken = null;
      BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
      while (null == accessToken) {
      System.out.println("Open the following URL and grant access to your account:");
      System.out.println(requestToken.getAuthorizationURL());
      System.out.print("Enter the PIN(if aviailable) or just hit enter.[PIN]:");
      String pin = br.readLine();
      try{
      if(pin.length() > 0){
      accessToken = twitter.getOAuthAccessToken(requestToken, pin);
      }else{
      accessToken = twitter.getOAuthAccessToken();
      }
      } catch (TwitterException te) {
      if(401 == te.getStatusCode()){
      System.out.println("Unable to get the access token.");
      }else{
      te.printStackTrace();
      }
      }
      }
      //persist to the accessToken for future reference.
      storeAccessToken(twitter.verifyCredentials().getId() , accessToken);
      Status status = twitter.updateStatus(args[0]);
      System.out.println("Successfully updated the status to [" + status.getText() + "].");
      System.exit(0);
      }
      private static void storeAccessToken(int useId, AccessToken accessToken){
      //store accessToken.getToken()
      //store accessToken.getTokenSecret()
      }
    El que faig és adaptar aquest codi al meu piulador:
    package com.sticipl.proves;

    import java.io.BufferedReader;
    import java.io.FileWriter;
    import java.io.InputStreamReader;
    import java.io.PrintWriter;

    import twitter4j.Status;
    import twitter4j.Twitter;
    import twitter4j.TwitterException;
    import twitter4j.TwitterFactory;
    import twitter4j.auth.AccessToken;
    import twitter4j.auth.RequestToken;

    public final class ProvaTwitter {
    private Twitter twitter;
    private BufferedReader br;
        /**
         * Usage: java twitter4j.examples.tweets.UpdateStatus [text]
         *
         * @param args message
         */
        public static void main(String[] args) {
            new ProvaTwitter();
        }
        
    public ProvaTwitter() {
    try {
                twitter = new TwitterFactory().getInstance();
                AutenticaConsumer();
                Status status = twitter.updateStatus(“Aquest és el missatge enviat des de l’aplicació #Java de prova fent us de #Twitter4J. Versió 2”);
                System.out.println(“Actualitzat status a [” + status.getText() + “].”);
                System.exit(0);
            } catch (TwitterException te) {
                te.printStackTrace();
                System.out.println(“Excepció de Twitter: ” + te.getMessage());
                System.exit(-1);
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println(“Excepció general: ” + e.getMessage());
                System.exit(-2);        
            }
    }
    private void AutenticaConsumer() throws Exception{
    twitter.setOAuthConsumer(“[consumer key]”, “[consumer key secret]”);
    RequestToken requestToken = twitter.getOAuthRequestToken();
    AccessToken accessToken = null;
    br = new BufferedReader(new InputStreamReader(System.in));
    while (null == accessToken) {
    System.out.print(“Entra el nom d’aquest perfil d’accés a Twitter:”);
    String sNomPerfil = br.readLine();
    System.out.println(“Obre la següent URL a un navegador, identifica’t amb el teu compte de twitter i autoritza al piulador:”);
    System.out.println(requestToken.getAuthorizationURL());
    System.out.print(“Entra el PIN(si està disponible) i prem enter:”);
    String pin = br.readLine();
    try {
    if(pin.length() > 0) {
    accessToken = twitter.getOAuthAccessToken(requestToken, pin);
    } else {
    accessToken = twitter.getOAuthAccessToken();
    }
        
    //persist to the accessToken for future reference.
    storeAccessToken(sNomPerfil, twitter.verifyCredentials().getId() , accessToken);
        
    } catch (TwitterException te) {
    if (401 == te.getStatusCode()) {
    System.out.println(“No ha pogut obtenir l’Access Token.”);
    } else {
    te.printStackTrace();
    }
    }
    }
    }
        
        private  void storeAccessToken(String sNomPerfil, long l, AccessToken accessToken){
    // guarda un fitxer amb:
        // nom del perfil a guardar
        // twitter.verifyCredentials().getId()
    // guarda accessToken.getToken()
    // guarda accessToken.getTokenSecret()
    FileWriter fitxer = null;
    PrintWriter pw = null;
    try {
    fitxer = new FileWriter(“./twitter-access-token.txt”);
    pw = new PrintWriter(fitxer);
    pw.println(“Nom perfil: ” + sNomPerfil );
    pw.println(“twitter.verifyCredentials().getId(): ” + l );
    pw.println(“Access Token: ” + accessToken.getToken() );
    pw.println(“Access Token Secret: ” + accessToken.getTokenSecret() );
    } catch (Exception e) {
    e.printStackTrace();
    } finally {
    try {
    if (null != fitxer)
    fitxer.close();
        } catch (Exception e2) {
         e2.printStackTrace();
        }
    }      
        }    
    }
    I l’executo:
    Des del main s’invoca al constructor , el qual, primer de tot, va al mètode AutenticaConsumer, que és el que fa la negociació OAuth. 
    A AutenticaConsumer,  s’estableixen el Consumer Key i el Cosumer Key Secret i, a continuació s’obté un Request Token. 
    Entra el nom d’aquest perfil d’accés a Twitter:sticipl
    A continuació demana un nom de perfil d’accés al twitter per a identificar-lo en futures connexions. La idea és que guarda a un fitxer el nom del perfil i l’Access Token i l’Access Token Secret, de forma que per a futures connexions amb aquest perfil,  podria fer servir directament aquests valors emmagatzemats localment, en comptes d’haver de tornar a demanar accés.  En aquesta versió genera el fitxer, però no està implementada la  recuperació per perfil de l’Acces Token. No costaria gaire fer-ho, però potser millor amb una bd HSQLDB que amb un fitxer pla.
    Amb el Request Token obté la URL d’autorització. L’usuari ha d’anar a aquesta URL, identificar-se amb el compte de Twitter amb el que vol fer servir el piulador, i autoritzar al Piulador a poder enviar missatges. 
    Obre la següent URL a un navegador, identifica’t amb el teu compte de twitter i autoritza al piulador:

    http://api.twitter.com/oauth/authorize?oauth_token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


    Dit i fet, copio la URL i l’enganxo a un navegador. Vaig a la web d’Autorització de Twitter. Com que ja estava logat a Twitter amb el compte de sticipl, aquesta és el compte per al que s’autoritzarà el Piulador. Però podria haver sortit i haver-me logat de nou amb un compte diferent :




    Faig click a Authorize App i…
    La pàgina de Twitter em proporciona un PIN. El piulador estava esperant a que li indiqués aquest PIN:
    Entra el PIN(si està disponible) i prem enter:6865240
     
    Un cop autoritzat, el mètode AutenticaConsumer invoca al mètode storeAccessToken que genera el fitxer amb l’Access Token i l’Access Token Key per a usos futurs. El fitxer generat és una cosa com aquesta:
    Nom perfil: sticipl
    twitter.verifyCredentials().getId(): 177894606
    Access Token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    Access Token Secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    AutenticaConsumer acaba i torna al constructor, i el constructor fa la següent piulada:
    Actualitzat status a [Aquest és el missatge enviat des de l’aplicació #Java de prova fent us de #Twitter4J. Versió 2]. 

    Vet aquí el resultat:
    Vet aquí, doncs, una versió del piulador amb autenticació OAuth gestionada des de la mateixa aplicació.
    La web de Twitter4J ens ens proporciona codi d’exemple per a desenvolupar altres funcions del Twitter que aniré explorant en propers posts.