martedì 8 febbraio 2011

UML per NetBeans 6.9.1

In questi giorni ho dovuto creare alcuni diagraammi UML riguardanti un progetto sviluppato con NetBeans 6.9.1. Trattandosi di alcune classi con una gran quantità di metodi, ho deciso di ricorrere ad un plugin per generare i diagrammi in automatico. Ricercando su Google ho trovato alcuni plugins adatti solo a vecchie versioni di NetBeans, ma poi ho finalmente trovato quello che faceva al caso mio: UML Modeler for Netbeans 6.9 (252).
Purtroppo non è esente da alcuni bug di visualizzazione, ma con un po' di pazienza sono riuscita a generare tutti i class diagrams voluti a partire dalle mie classi.
Per poterlo utilizzare non dovete far altro che chiudere NetBeans 6.9, estrarre la cartella uml dallo zip scaricato e inserirla nella cartella di installazione di NetBeans. Se state lavorando sotto Mac, andate nella cartella in cui si trova l'applicazione NetBeans --> mostra contenuto pacchetto --> Contents --> Resources --> NetBeans. Quindi avviate NetBeans e cliccate su Nuovo Progetto. Se nella lista compare anche UML, allora avete fatto tutto correttamente. Annullate pure la creazione del nuovo progetto.
Aprite il progetto per cui volete crere i class diagrams, cliccate con il pulsante destro sul package che vi interessa e scegliete Reverse Engineer. Nella schermata che comparirà create un nuovo progetto UML, se ancora non lo possedete. Ora espandete il nuovo progetto creato e anche la sezione model. Cliccate con il destro sul file che ha il nome del vostro package e scegliete Create Diagram From Selected Elements. Scegliete class diagram e terminate la procedura. Comparirà un alert, confermate. Il vostro diagramma si aprirà immediatamente.
Purtroppo non ho trovato un modo per esportare tali diagrammi. Potete provare a stamparli su pdf, diminuendo lo zoom del diagramma stesso, oppura utilizzate print screen (su Mac cmd+shift+4 per selezionare la porzione di schermo da catturare).

sabato 5 febbraio 2011

Fase 7 : Ultimi ritocchi

Utilizzando JSF e Richfaces ho avuto molte difficoltà a capire come poter ricavare, da un Bean, il percorso assoluto per poter salvare le immagini caricate. Riporto il frammento di codice che ho utilizzato e grazie a cui ho risolto il problema:

FacesContext context = FacesContext.getCurrentInstance();
ServletContext servletContext = (ServletContext)context.getExternalContext().getContext();
String path = servletContext.getRealPath("/img/products/");

"/img/products/" è il percorso relativo alla root della mia web application. Attenzione alla barra finale. Se volete concatenare il nome dell'immagine che verrà salvata, dovrete aggiungere  un'altra barra "/nomeImg.jpg", perchè in "path" la barra finale non c'è.

martedì 1 febbraio 2011

Fase 6 : Problema con le query JPA

Implementando la sezione relativa alle operazioni CRUD sulle entità mi sono dovuta confrontare con le queries JPA. Non è complicato generare una query utilizzando la sintassi adatta perchè si tratta di un linguaggio molto simile all' sql, ma allo stesso tempo con delle importanti differenze che ho dovuto imparare.
Riporto alcuni esempi di queries che ho utilizzato:

Seleziono i prodotti che possegono l'attributo "deleted" = 0 ordinandoli per data di inserimento. Da notare che Product è l'entità, non la tabella.
entityManager.createQuery("SELECT p FROM Product p WHERE p.deleted = 0 ORDER BY p.inserted").getResultList();

Stessa query di prima, ma seleziono solo i primi 10 elementi.
entityManager.createQuery("SELECT p FROM Product p WHERE p.deleted = 0 ORDER BY p.inserted DESC LIMIT 0,10").getResultList();

Posso selezionare anche un solo attributo (in questo caso itemsNum).
entityManager.createQuery("SELECT p.itemsNum FROM Product p WHERE p.productCode = 333").getSingleResult();

Posso fare JOIN tra due entità solo se esiste una relazione tra di esse. In questo caso setto "orderShop" nella query attraverso il metodo "setParameter".
Query q = entityManager.createQuery("SELECT p FROM Product p JOIN p.orderShopList o WHERE o = :orderShop ORDER BY p.inserted");
q.setParameter("orderShop", new OrderShop(orderId));
obj = q.getResultList();


Per salvare un oggetto nel DB non devo far altro che richiamare il metodo "persist" passandogli l'oggetto( ma solo se si tratta di un'entità).
entityManager.persist(product);
Fatto ciò, per vedere il nuovo id assegnato a product, non devo far altro che scrivere product.getProductCode();

Per aggiornare un oggetto.
Product p = em.find(Product.class, productCode);
p.setDeleted(false);

lunedì 31 gennaio 2011

Fase 5 : Problema Stateful Session Bean

Parallelamente al modulo web ho continuato a sviluppare il modulo ejb. Ho deciso di utilizzare dei Session Beans stateless per eseguire azioni atomiche (sulle entità o semplicemente per elaborare alcuni dati). Invece ho preferito utilizzare un Session Bean stateful che fungesse da carrello per l'utente. Fin dal primo istante mi sono accorta che richiamando queste due righe di codice ad ogni reload della pagina mi veniva assegnata ogni volta una nuova istanza dell'EJB.

Context context = new InitialContext();
this.carrelloEJB = (CarrelloSBRemote) context.lookup("WiiShop/CarrelloSB/remote");


Il mio scopo, però, era mantenere un riferimento sempre allo stesso EJB stateful, contenente lo stato del carrello. Allora, dopo molte ricerche, ho scoperto che bastava memorizzare in sessione un reference all'EJB per richiamarlo in caso di bisogno.

this.carrelloEJB = (CarrelloSBRemote) session.getAttribute("shopSFSB");
// se in sessione non c'è un'istanza dell'EJB allora accedo all'EJB e memorizzo un reference nella sessione
if (this.carrelloEJB == null)
 {
    Context context;
    try {
        context = new InitialContext();
        this.carrelloEJB = (CarrelloSBRemote) context.lookup("WiiShop/CarrelloSB/remote");
                    session.setAttribute("shopSFSB", this.carrelloEJB);
            } catch (NamingException ex) {
            }
 }


Se, invece che stateful, CarrelloSBRemote fosse stato stateless e avessi utilizzato la stessa tecnica, avrei sbagliato. Testando il codice potreste avere la sensazione che tutto funzioni, ma un stateless Session Bean può essere cancellato in qualunque istante dal sistema e se voi vi salvate un riferimento ad esso non è detto che quando andate a richiamarlo sia lo stesso di prima.

lunedì 24 gennaio 2011

Fase 4 : Architettura applicazione web e prime fasi di sviluppo

Questa fase è stata molto laboriosa, soprattutto data la mia lentezza nello sviluppare un'interfaccia abbastanza accettabile dal punto di vista grafico. Questo è ciò che sono riuscita ad ottenere prendendo in prestito alcune immagini trovate girovagando tra i siti sulla Wii.


Per quanto riguarda l'architettura, la mia web application si compone di pagine JSP, Servlets e Java Bean (molto utili per JSF e Richfaces). Inoltre ho impostato un filtro (Filter) per controllare gli accessi all'area riservata.
Grazie a Jaxb e alle sue annotazioni ho potuto creare l'rss per il WiiShop, creandone anche una versione Html tramite l'uso di un file xsl e dei Transformers.

martedì 11 gennaio 2011

Fase 3 : Interfaccia Web, JSF e Richfaces

A questo punto ho provato a generare alcuni Session Beans per capirne bene il funzionamento. Facendo uso di EJB3 ho trovato tutto molto semplice e piuttosto intuitivo, anche se ho trascorso parecchio tempo a studiare le differenze tra EJB2 e EJB3.
Prima di procedere con il modulo ejb e le entità ho deciso di dedicarmi all'applicazione web ed in particolare alla sua interfaccia. Ho, quindi, creato un template html+css per capire dove avrei posizionato i vari componenti grafici.
Dopo un po' di studi, la scelta della tecnologia da utilizzare è ricaduta su Java Server Faces e RichFaces. Nonostante fossi già venuta in contatto con queste due tecnologie o riscontrato non pochi problemi.
Innanzitutto tengo a precisare che JBoss 5 già contiene al suo interno JSF 1.2. Se inserite le JSF 1.2 di NetBeans otterrete solo conflitti. Per quanto riguarda RichFaces, ho usato le 3.3.0 perchè sono le uniche che sono riuscita a far funzionare.

mercoledì 5 gennaio 2011

Fase 2 : Configurazione, Entità e prime problematiche

Ora che ero in possesso di una struttura per il mio progetto avevo bisogno di identificare gli "attori" che avrebbero popolato il mio database. Ho deciso, quindi, di creare una piccola applicazione java per costruire il database facendo uso di Hibernate e dei suoi file di mappatura.
Le tabelle di cui avrei avuto bisogno sarebbero state: User, Order, Product.
Ecco la classe che ho utilizzato per caricare i file .hbm.xml e generare così la struttura per il mio database.

public class SchemaGenerator
{
    // System constants for the current platform directory token
    static String fileSep = System.getProperty("file.separator");

    // We use this session factory to create our sessions
    public static SessionFactory sessionFactory;

    static String[] db_dialects = {"Mysql", "org.hibernate.dialect.MySQL5InnoDBDialect",};
  
    public static void main(String[] args)
    {
        initialization();
    }
  
    /**
    * Loads the Hibernate configuration information, sets up the
    * database and the Hibernate session factory.
    */
    public static void initialization()
    {
        System.out.println("initialization");
        try
        {
            Configuration myConfiguration = new Configuration();
  
            /**
             * Insert here your beans classes for which you want to generate the SQL script
             * one for each bean for each table
             **/
            myConfiguration.addClass(main.Product.class);
            myConfiguration.addClass(main.User.class);
            myConfiguration.addClass(main.Order.class);
          
            Properties myProperties = new Properties();

            for (int i = 0; i < db_dialects.length; i = i + 2)
            {
                String dialect_name = db_dialects[i];
                String dialect_class = db_dialects[i + 1];

                String dialect_file = dialect_name.toLowerCase();
                dialect_file = dialect_file.replace(' ', '_');
                dialect_file += (".sql");

                System.out.println("Generating " + dialect_name);

                // Note that this is the only Hibernate property
                // set. In particular, there is no JDBC
                // connectivity data, nor are we specifying a
                // driver!
                myProperties.put("hibernate.dialect", dialect_class);
                try
                {
                    // Load the *.hbm.xml files as set in the
                    // config, and set the dialect.
                    SchemaExport mySchemaExport = new SchemaExport(myConfiguration, myProperties);
                  
                    mySchemaExport.setDelimiter(";");

                    // Despite the name, the generated create
                    // scripts WILL include drop statements at
                    // the top of the script!
                    mySchemaExport.setOutputFile("/...your path.../create_"+ dialect_file);
                    mySchemaExport.create(false, false) ;

                    // Generates DROP statements only
                    mySchemaExport.setOutputFile("/...your path.../drop_"+ dialect_file);
                    mySchemaExport.drop(false, false);

                    System.out.println(dialect_name + " OK.");

                } catch (Exception e)
                {
                    e.printStackTrace();
                    System.out.println(e.getMessage());
                }
            }
        } catch (Exception e)
        {
            e.printStackTrace();
        }
    }  
}

Di seguito un esempio di file .hbm.xml.

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 17-nov-2010 15.44.15 by Hibernate Tools 3.4.0.Beta1 -->
<hibernate-mapping>
    <class name="main.Order" table="orders">
        <id name="orderId" type="java.lang.Long">
            <column name="order_id" />
            <generator class="increment" />
        </id>
        <many-to-one name="userId" class="main.User" cascade="all" lazy="false">
            <column name="user" />
        </many-to-one> 
        <property name="totalCost" type="int">
            <column name="total_cost" />
        </property>
        <property name="date" type="java.lang.String">
            <column name="order_date" />
        </property>
        <set name="products" table="order_product" cascade="all" lazy="false">
            <key column="order_id"/>
            <many-to-many column="product_code" class="main.Product" />
        </set>
        <property name="deleted" type="boolean">
            <column name="deleted" length="0" />
        </property>
    </class>
</hibernate-mapping>

La classe Product altro non è che un semplice bean.

Una volta creato il database MySql ho importato il file generato dall'applicazione.
Attenzione a quando utilizzate parole come "Order" o "date" all'interno dei vostri file di mappatura poichè sono parole riservate per MySql e quando tenterete di importare gli schema generati vi verranno restituiti messaggi d'errore.
Nel mio database ora avevo le tabella Orders, Products, Users e una tabella di join tra Orders e Products. Quindi ho creato una nuova connessione con il database da NetBeans (Servizi --> Database --> New Connection). Finalmente potevo iniziare ad inserire i primi file di configurazione all'interno di NetBeans.
Pulsante destro sul modulo ejb: Nuovo --> Altro --> Persistenza --> Schema Database (ho inserito la connessione al db appena creata). Quindi ho creato anche un'unità di persistenza (stesso percorso di prima). Di seguito come si presenta il mio file di configurazione con una piccola aggiunta per poter utilizzare Hibernate:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" 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_1_0.xsd">
  <persistence-unit name="WiiShop-ejbPU" transaction-type="JTA">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <jta-data-source>java:/wiishopDS</jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties>
         <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect"/>
         <property name="hibernate.connection.isolation" value="RepeatableRead"/>
      </properties>
  </persistence-unit>
</persistence>

Nella stessa posizione ho dovuto copiare a mano il file jboss-ds.xml che riporto di seguito.

<?xml version="1.0" encoding="UTF-8"?>
<datasources>
  <local-tx-datasource>
    <jndi-name>wiishopDS</jndi-name>
    <connection-url>jdbc:mysql://localhost/WiiShop</connection-url>
    <driver-class>com.mysql.jdbc.Driver</driver-class>
    <user-name>root</user-name>
    <password/>
    <min-pool-size>5</min-pool-size>
    <max-pool-size>20</max-pool-size>
    <idle-timeout-minutes>5</idle-timeout-minutes>
    <transaction-isolation>TRANSACTION_REPEATABLE_READ</transaction-isolation>
  </local-tx-datasource>
</datasources>

Purtroppo esso viene generato dentro setup, ma una volta che farete deploy il server non troverà questo file e restituirà eccezione. Ho perso molto tempo per capire il motivo di questo problema che potrebbe essere dovuto al fatto che NetBeans si aspetta di lavorare con JBoss 4 e non 5. Spostandolo dalla cartella setup a src/conf non ho più avuto problemi.

A questo punto ero già in grado di generare le mie entità dal database. Ho creato un package e cliccando con il destro su di esso ho scelto Nuovo --> Classi Entità dal Database. Niente di più semplice per ottenere le entità corrette con tutte le opportune annotazioni.
Un consiglio: fate partire JBoss 5 da shell separatamente da NetBeans. Per fare deploy non dovrete far altro che pulire e costruire l'applicazione JEE. Copiare il file .ear (generato dentro dist) all'interno di JBoss5/server/default/deploy. In un attimo JBoss si accorgerà del file e lo caricherà. Ogni tanto ricordate anche di cancellare un po' di file da JBoss5/server/default/tmp.