Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Icefaces con JBoss Developer Studio: moduli EJB3 e JPA

Continuiamo lo sviluppo di una semplice applicazione IceFaces compelta su JBoss Development Studio: in questo articolo aggiungiamo la parte JPA ed EJB
Continuiamo lo sviluppo di una semplice applicazione IceFaces compelta su JBoss Development Studio: in questo articolo aggiungiamo la parte JPA ed EJB
Link copiato negli appunti

Continuiamo la serie di articoli per la nostra applicazione IceFaces, iniziata con .

Al termine di questo articolo avremo un progetto abbastanza completo nelle sue prime parti, sviluppato in JBoss Developer Studio con IceFaces, EJB 3 e JPA 2.0.

In questo articolo realizziamo la prima funzionalità del nostro sistema: la gestione degli studenti. Vogliamo concentrarci sulla operazioni CRUD inserimento/modifica che ci permetteranno, partendo dalla lista studenti, di inserirne di nuovi o di modificare quelli esistenti.

Modello e Business Logic

Ogni applicazione J2EE è dotata di uno strato responsabile delle logica applicativa e di uno responsabile dell'accesso al database (DAO).

Nell'ambito del nostro applicativo ci affidiamo alla tecnologia EJB3 per lo strato di business e a quella JPA per quello DAO.

I componenti EJB ci offrono la possibilità di incapsulare la nostra logica avendo a disposizione un solido ambiente per la gestione delle transazioni e davvero tanto altro.

Con JPA 2.0 (Java persistence api) abbiamo invece un framework che semplifica notevolmente la gestione della base dati e che, utilizzato in modo congiunto ad EJB 3, ci permette di realizzare solide applicazioni gestionali in tempi molto brevi. Il codice è davvero molto semplice ed ha come obiettivo quello di rendere l'applicazione più interessante, inoltre può rappresentare il punto di partenza per approfondire lo sviluppo in ambito J2EE partendo da IceFaces e scendendo verso i livelli più bassi.

IL Progetto JPA

Partiamo dalla definizione del modello: dobbiamo gestire informazioni che riguardano studenti e appelli di esame. La prima cosa che facciamo è quindi quella di modellare queste entità attraverso classi Entity JPA che si legheranno al database grazie all'uso delle annotazioni. Queste classi rappresentano oggetti persistenti, possiamo pensare alle loro istanze come a record di database. In genere una classe entity ed una tabella di database sono in relazione 1:1.

Creiamo attraverso il wizard un progetto JPA come mostrato in figura:

Il wizard per la parte JPA

Il wizard per la parte JPA

proseguiamo dando un nome (PrenotazioneEsamiJPA) è configurando il progetto per il runtime JBOSS e versione 2.0 per JPA:

configurazioni nel wizard per JPA

configurazioni nel wizard per JPA

terminiamo il wizard con i successivi semplici step. Al termine dovremmo avere lo scheletro di una applicazione JPA vuoto che ci permetterà di salvare i dati del modello all'interno del database h2 già presente è configurato in JBOSS.

All'interno del progetto creiamo il package it.html.jpa e definiamo le classi del modello per l'entità studente e appello di esame.

Le classi JPA

Se abbiamo familiarità con l'ambiente eclipse e JPA, possiamo utilizzare il wizard per la creazione delle entities, altrimenti fate riferimento direttamente al codice delle classi qui esposto (prodotto comunque attraverso il wizard, e di cui potete consultare il codice completo nell'alegato):

Studente.java

package it.html.jpa;
/**
 * Entity implementation class for Entity: Studente
 */
@Entity
public class Studente implements Serializable {
	private int id;
	private String nome;
	private String cognome;
	private String matricola;
	private static final long serialVersionUID = 1L;
	private List<AppelloEsame> esami;
	public Studente() {
		super();
	}   
	// GETTERS / SETTERS	
	@ManyToMany
	public List<AppelloEsame> getEsami() {
		return esami;
	}
	public void setEsami(List<AppelloEsame> esami) {
		this.esami = esami;
	}
	@Id @GeneratedValue
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
}

AppelloEsame.java

package it.html.jpa;
/**
 * Entity implementation class for Entity: AppelloEsame
 */
@Entity
public class AppelloEsame implements Serializable {
	private int cdEsame;
	private String corso;
	private String corsoLaurea;
	private Date data;
	private String ora;
	private static final long serialVersionUID = 1L;
	private List<Studente> studenti;
	public AppelloEsame() {
		super();
	}
	// GETTERS / SETTERS
	@Id
	public int getCdEsame() {
		return cdEsame;
	}
	public void setCdEsame(int cdEsame) {
		this.cdEsame = cdEsame;
	}
	@ManyToMany(mappedBy="esami")
	public List<Studente> getStudenti() {
		return studenti;
	}
	public void setStudenti(List<Studente> studenti) {
		this.studenti = studenti;
	}
}

Come potete vedere le classi sono volutamente mantenute semplici, con pochi campi e l'uso essenziale delle annotazioni. Vediamo rapidamente il significato delle annotazioni utilizzate.

Le annotazioni

@Entity

Diciamo che la nostra classe è mappata su una tabella del database, in questo non specificando l'attributo Table per l'annotation, si indica che la tabella ha lo stesso nome della classe.

@Id

Indica la chiave primaria che identifica univocamente un'istanza dell'entità e quindi un record del database.

@GeneratedValue

Generazione automatica dei valori per la chiave primaria.

@ManyToMany

Con questa annotazione realizziamo una relazione molti a molti tra Studente e AppelloEsame: uno studente può essere iscritto a 0,1 o più appelli; ad un AppelloEsame di esame possono essere iscritti 0,1 o più studenti. Sul database una relazione di questo tipo viene realizzata attraverso una tabella che collega, con riferimenti alle chiavi primarie, le tabelle Studente e AppelloEsame.

configurazione di persistence.xml

Per poter utilizzare il database interno di JBOSS per il progetto JPA in modo tale che a partire dalle entity vengano generate le tabelle, dobbiamo configurare il file persistence.xml:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="...">
	<persistence-unit name="PrenotazioneEsamiJPA">
      <jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
      <properties>
       <property name="hibernate.hbm2ddl.auto" value="create" />
       <property name="jboss.entity.manager.factory.jndi.name"
                         value="java:app/PrenotazioneEsamiJPA"/>
      </properties>
	</persistence-unit>
</persistence>

Notiamo il collegamento al datasource definito su JBOSS (jta-data-source). Potete individuarlo accedendo alla console di amministrazione ed entrando nella sezione datasource. Notiamo inoltre le proprietà che ci permettono di definire un entity manager factory dal quale, a livello applicativo, è possibile ottenere un entity manager che rappresenta l'oggetto per eseguire le operazioni di persistenza. Particolarmente interessante è infine la proprietà con name hibernate.hbm2ddl.auto e value “create”: essa permette la creazione delle tabelle partendo dal modello durante la fase di deploy.

Attraverso questo file possiamo collegare il progetto a qualsiasi database configurato su JBOSS (oracle, postgres, mysql, etc).

Il nostro progetto per l'accesso alla base dati è completo, nella prossima parte possiamo proseguire con un progetto EJB per logica di business e spingerci infine sullo strato applicativo con IceFaces.

Possiamo utilizzare nuovamente il wizard, per la creazione di un progetto EJB 3:

il wizard per EJB

il wizard per EJB

nella pagina successiva diamo il nome al progetto (PrenotazioneEsamiEJB) ed impostiamo come versione EJB la 3.1 e runtime il runtime server già definito.

configurazione per EJB

configurazione per EJB

Completiamo quindi la procedura di creazione con i vari bean

Realizziamo un session bean di tipo stateless che conterrà i metodi di business per la gestione di uno studente. Un session bean stateless consta di una classe di interfaccia per l'esposizione dei metodi e di una classe che implementa questi metodi. Non mantiene uno stato conversazionale con il client, questo significa che chiamate successive allo stesso metodo o a metodi diversi del session bean non verranno in genere servite dalla stessa istanza. L'application server crea infatti un pool di session beans e ad ogni chiamata ne viene selezionato uno tra quelli presenti nel pool per servire la richiesta. Non siamo noi a creare istanze dei session bean ma è il server ad occuparsi di tutto. L'interfaccia può essere annotata con diverse annotations @Local, @Remote, @WebService a seconda del tipo di esposizione: visibile solo localmente nel server all'interno della stessa JVM, visibile anche da remoto ovvero invocabile da applicazioni esterne, oppure esporre la propria interfaccia ad applicazioni remote come web service.

Nella prossima parte ci dedicheremo alle classi.

Creiamo all'interno del progetto EJB in ejbModule il package it.html.ejb e definiamo la seguente interfaccia

GestioneStudentiLocal.java

package it.html.ejb;
@Local
public interface GestioneStudentiLocal {
	void aggiungi(Studente studente);
	void aggiorna(Studente studente);
	List<Studente> listaStudenti();
    	Studente  getEsami(Studente studente);
}

Non abbiamo bisogno di esporre l'ejb all'esterno poiché sarà soltanto il nostro strato applicativo a farne uso, utilizziamo quindi l'annotazione @Local.

Aggiungiamo all'interfaccia tutti i metodi di cui abbiamo bisogno per la gestione di uno studente:

  • aggiungere uno studente
  • modificarne uno esistente
  • listare tutti gli studenti
  • ricavare la lista di esami ai quali è correntemente iscritto

Nello stesso package inseriamo la classe che implementa questa interfaccia:

GestioneStudenti.java

package it.html.ejb;
/**
 * Session Bean implementation class GestioneStudenti
 */
@Stateless(mappedName = "GestioneStudenti")
public class GestioneStudenti implements GestioneStudentiLocal {
	@PersistenceContext
	EntityManager entityManager;
	@Override
	public void aggiungi(Studente studente) {
		entityManager.persist(studente);
	}
	@Override
	public void aggiorna(Studente studente) {
		entityManager.merge(studente);
	}
	@Override
	public List listaStudenti() {
		Query query = entityManager.createQuery("select c from Studente c");
		return (List)query.getResultList();
	}
	@Override
	public Studente getEsami(Studente studente) {
		Studente studenteAttached= 										entityManager.find(Studente.class,studente.getMatricola());
		studenteAttached.getEsami();
		return studenteAttached;
	}
}

ancora sulle annotazioni

In questa classe troviamo diverse annotazioni interessanti:

@Stateless annotation

Ci dice che il session bean è di tipo Stateless ( possiamo avere anche session bean di tipo StateFul o Message driven)

@PersistenceContext annotations

Ci permette di ottenere un'istanza dell'entity manager attraverso la dependency injection

Implementazione dei metodi

Possiamo notare l'implementazione dei metodi dell'interfaccia e l'uso dell'entity manager per la gestione di uno studente.

JPQL

Mentre è abbastanza semplice intuire cosa facciano i metodi persist (inserimento di un nuovo record) e merge(modifica di uno esistente), non proprio intuitiva è la query che recupera la lista studenti. Infatti quando con JPA si fa uno di un linguaggio il cui nome è JPQL, simile all'SQL ma con la visione verso il modello delle classi entity, potete trovare tantissima documentazione a riguardo.

collegamento tra il progetto JPA e quello EJB

Affinchè si possano usare le classi entity all'interno del progetto EJB, dobbiamo fare in modo che il progetto JPA sia visibile al progetto EJB, per far ciò entrate nelle proprietà del progetto EJB,selezionate java build path ed aggiungete il progetto JPA nella tab Projects:

java build path per il progetto EJB

java build path per il progetto EJB

Con la creazione del progetto EJB abbiamo concluso con gli strati logici e di accesso al database necessari per il nostro strato applicativo: nella prossima parte proseguiremo con la definizione delle interfacce e dei managed bean che, utilizzando IceFaces ci permetteranno di vedere subito all'opera il nostro applicativo.

In questa parte finalmente ci dedicheremo allo sviluppo dell'applicazione IceFaces vera e propria.

Il progetto IceFaces (gestione esami universitari)

Diamo subito una vista di insieme all'interfaccia finale che visualizzeremo. Abbiamo un menù di sinistra per le funzionalità, ed un'area centrale nella quale visualizzeremo liste ed interfaccie. Completiamo il layout grafico con un header.

interfaccia finale del progetto IceFaces

interfaccia finale del progetto IceFaces

Prima di proseguire non dimenticate di aggiungere nel java build path del progetto PrenotazioneEsami i progetti EJB e JPA come fatto precedentemente in modo tale da renderli visibili anche al progetto JavaServer faces. Infine definite un'applicazione EAR, PrenotazioneEsamiEAR ed aggiungete durante la creazione tutti e tre i progetti, è il progetto EAR che installeremo su JBOSS attraverso il deploy.

Il template

Utilizziamo le facelets per dare una fisionomia al layout, questo si traduce nella realizzazione della pagina di template template.xhtml, situata in WebContent all'interno del progetto iceFaces.:

<html xmlns="...">
		<h:head>
		<title><ui:insert name="pageTitle">Page Title</ui:insert></title>
		<style type="text/css">
			body {
				  font-family: Verdana, Arial, Helvetica, sans-serif;
				  font-size: 14px;
			}
			.header {
				  font-family: Verdana, Arial, Helvetica, sans-serif;
				  font-size: 18px;
			}
			.bottom {
				  font-family: Verdana, Arial, Helvetica, sans-serif;
				  font-size: 9px;
				  text-align: center;
				  vertical-align: middle;
				  color: #8E969D;
			}
		</style>
		</h:head>
<h:body>
<h:panelGrid style="width:100%">
<ui:insert name="pageHeader">Page Header</ui:insert>
<h:panelGrid columns="2" style="width:100%;" >
<ui:insert name="pageMenu">Menu</ui:insert>
<ui:insert name="pageBody">Page Body</ui:insert>
</h:panelGrid>
</h:panelGrid>
</h:body>
</html>

Abbiamo definito 3 aree, una per ospitare l'header, una per il menu ed un'altra per la parte centrale.
Proseguiamo con il definere la pagina di index che utilizza questo template per visualizzare la pagina iniziale di accesso:

<html xmlns="...">
<h:head>
	<ice:outputStyle href="./xmlhttp/css/rime/rime.css" rel="stylesheet"
		type="text/css" />
	<title><ui:insert name="title">Sistema prenotazione esami</ui:insert></title>
</h:head>
<h:body>
<ui:composition template="template.xhtml">
<ui:define name="pageHeader">
<h:panelGrid columns="2" style="border:1px solid black;width:100%">
<h:graphicImage value="/resources/header.png" />
<h2 style="margin:auto;text-align:center">ICE FACES - SISTEMA PRENOTAZIONE ESAMI</h2>
</h:panelGrid>
</ui:define>
<ui:define name="pageMenu">
         <h:form>
          <ace:menu type="plain" style="width:300px">
            <ace:submenu label="Area studenti">
              <ace:menuItem  value="Prenotazione esami" >
                 <ace:ajax event="activate" execute="@this"  />
               </ace:menuItem>
             </ace:submenu>
          <ace:submenu label="Area amministrazione" >
              <ace:menuItem value="Gestione esami" >
                  <ace:ajax event="activate" execute="@this"  />
              </ace:menuItem>
              <ace:menuItem   action="#{menuManagedBean.gestioneStudenti}"
                                        value="Gestione studenti">
                  <ace:ajax event="activate" execute="@this"  />
              </ace:menuItem>
         </ace:submenu>
        </ace:menu>
       </h:form>
</ui:define>
<ui:define name="pageBody">
</ui:define>
</ui:composition>
</h:body>
</html>

Abbiamo utilizzato il componente IceFaces ace:menu per la realizzazione del menu di sinistra. Per il momento l'unica voce attiva è quella relativa alla gestione degli studenti, un click su questa voce provocherà l'esecuzione del metodo gestioneStudenti del managed bean StudentiManagedBean, che vedremo successivamente e che visualizzerà la lista. Dalla pagina della lista avremo la possibilità di navigare verso la pagina di edit di un nuvo studente o di uno esistente. Le pagine della lista e di edit hanno i seguenti nomi : edit.xhtml e listaStudenti.xhtml.

La navigazione, con faces-config.xml

Tutte le pagine sono a livello di WebContent. Possiamo editare direttamente il faces-config.xml per la gestione di queste navigazioni oppure utilizzare la tab di design attiva nel momento in cui apriamo il faces-config.xml:

progettazione grafica della navigazione con faces-config.xml

progettazione grafica della navigazione con faces-config.xml

Mentre se volessimo visualizzare il codice di faces-config.xml:

<?xml version="1.0" encoding="UTF-8"?>
<faces-config version="2.0" xmlns="...">
<navigation-rule>
	<from-view-id>/index.xhtml</from-view-id>
	<navigation-case>
		<from-outcome>gestioneStudenti</from-outcome>
		<to-view-id>/listaStudenti.xhtml</to-view-id>
	</navigation-case>
</navigation-rule>
<navigation-rule>
	<from-view-id>/listaStudenti.xhtml</from-view-id>
	<navigation-case>
		<from-outcome>edit</from-outcome>
		<to-view-id>/edit.xhtml</to-view-id>
	</navigation-case>
</navigation-rule>
<navigation-rule>
	<from-view-id>/edit.xhtml</from-view-id>
	<navigation-case>
		<from-outcome>annulla-salva</from-outcome>
		<to-view-id>/listaStudenti.xhtml</to-view-id>
	</navigation-case>
</navigation-rule>
</faces-config>

pagina di update del singolo studente

Attraverso queste regole di navigazione abbiamo definito tutto il flusso per la gestione degli studenti: partiamo da una lista visualizzata attraverso il menu, entriamo in editing , salviamo o annulliamo e ritorniamo in ogni caso alla lista principale. Analizziamo questo flusso partendo dalla pagina della lista studenti, in essa faremo sempre uso del template definito ma qui visualizziamo solo la parte relativa al pageBody:

<ui:define name="pageBody">
 <h:form>
 <h:panelGrid style="width:900px">
            <style type="text/css">
                /* Important required because row */
                .ui-datatable-odd {background-color:lightgray !important;}
                .wijmo-wijmenu{width:80%;}
            </style>
<h:panelGrid  columns="2">
  <ice:commandButton value="Nuovo studente" action="#{studentiManagedBean.editPage}" />
  <ice:commandButton value="Aggiorna studente"
                                            action="#{studentiManagedBean.editPage}"/>
   </h:panelGrid>
           <ace:dataTable id="studenti"
                          value="#{studentiManagedBean.studenti}"
                          var="studente"
                          paginator="true"
                          paginatorPosition="bottom"
                          rows="10"
                          emptyMessage="Nessuno studente presente nel database"
                          style="width:100%"
                          selectionMode="single">
                <ace:column headerText="Nome">
                    <h:outputText value="#{studente.nome}"/>
                </ace:column>
                <ace:column headerText="Cognome">
                    <h:outputText value="#{studente.cognome}"/>
                </ace:column>
                <ace:column headerText="Matricola">
                    <h:outputText value="#{studente.matricola}"/>
                </ace:column>
            </ace:dataTable>
 </h:panelGrid>
</h:form>
</ui:define>

La pagina è collegata al managed-bean StudentiManagedBean che definiamo attraverso la relativa classe nel package it.html.jsf, che andiamo a creare. Partendo dal componente ice:commandButton vediamo che la classe è dotata del metodo editPage il cui compito è quello di far navigare verso la pagina di editing.

Il componente relativo alla dataTable definisce diverse cose interessanti: oltre alla lista di oggetti entity Studente, collegata al campo studenti del managed-bean, abbiamo l'impostazione del paginatore attiva (paginator=true), il numero di record per pagina(rows=10), un messaggio da visualizzare quando non ci sono record (emptyMessage), la posizione del paginatore (paginatorPosition=bottom), ed infine la modalità di selezione (selectionMode=single) impostata su selezione singola sul click del record.

Quando selezioniamo un elemento della tabella, l'oggetto selezionato viene inserito nella variabile di tipo Studente con nome studente all'interno managed-bean, questo avviene grazie al collegamento con l'attributo var che definisce non solo la variabile di iterazione ma anche la variabile nel quale porre l'elemento selezionato. Vediamo il codice:

package it.html.jsf;
public class StudentiManagedBean {
	@EJB
	private GestioneStudentiLocal gestioneStudenti;
	private List<Studente> studenti;
	private Studente studente;
	@PostConstruct
	private void initialize(){
		studenti=gestioneStudenti.listaStudenti();
	}
	public String editPage(){
		return "edit";
	}
	public String salva(){
		if(studente.getId()==0){
		 gestioneStudenti.aggiungi(studente);
		}else{
		 gestioneStudenti.aggiorna(studente);
		}
	   studenti=gestioneStudenti.listaStudenti();
	   return "annulla-salva";
	}
	public List<Studente> getStudenti() {
		return studenti;
	}
	public void setStudenti(List<Studente> studenti) {
		this.studenti = studenti;
	}
	public Studente getStudente() {
		if(studente==null){
			studente=new Studente();
		}
		return studente;
	}
	public void setStudente(Studente studente) {
		this.studente = studente;
	}
}

Come potete notare dal codice, attraverso l'annotazione @EJB riusciamo ad avere tramite dependency injection un'istanza dell' EJB del componente EJB definito precedentemente. Questa istanza viene usata nel metodo initialize (invocato subito dopo la creazione del managed-bean, notare l'annotazione @PostConstruct) per popolare la lista da fornire alla dataTable e nel metodo salva per salvare i dati di uno studente. Il managed-bean ha scope session poiché vogliamo che rimanga in vita in seguito a successive fasi di navigazione:

<managed-bean>
  <managed-bean-name>studentiManagedBean</managed-bean-name>
  <managed-bean-class>it.html.jsf.StudentiManagedBean</managed-bean-class>
  <managed-bean-scope>session</managed-bean-scope>
 </managed-bean>

Vediamo adesso la pagina di editing nella sua parte essenziale:

<ui:define name="pageBody">
<h:form>
<h:panelGrid style="width:900px;padding:10px;border:1px solid black">
 <h:outputLabel value="Nome" /><ice:message for="nome"/>
 <ice:inputText  value="#{studentiManagedBean.studente.nome}" style="width:400px" required="true" id="nome"/>
 <h:outputLabel value="Cognome" /><ice:message for="cognome" />
 <ice:inputText value="#{studentiManagedBean.studente.cognome}" style="width:400px" required="true" id="cognome"/>
 <h:outputLabel value="Matricola" /><ice:message for="matricola"  />
 <ice:inputText value="#{studentiManagedBean.studente.matricola}" style="width:400px" required="true" id="matricola"/>
 <h:panelGrid columns="2">
  <ice:commandButton value="Salva" action="#{studentiManagedBean.salva}"/>
  <ice:commandButton value="Annulla" action="annulla-salva" immediate="true"/>
 </h:panelGrid>
</h:panelGrid>
</h:form>
</ui:define>
</ui:composition>
</h:body>
</html>

Quando dalla pagina della lista facciamo click sul pulsante Nuovo studente o Aggiorna studente, navighiamo su questa pagina:

IceFaces - sistema di prenotazione esami universitari

IceFaces - sistema di prenotazione esami universitari

Come possiamo notare i campi del form sono collegati al campo studente del managed-bean, campo che nel caso di selezione sulla dataTable contiene l'elemento selezionato. Inoltre tali campi sono obbligatori e per la visualizzazione dei messaggi di validazione abbiamo fatto uso del componente ice:message. Per inserire o aggiornare un nuovo studente si utilizza un ice:commandButton che invoca il metodo salva del managed-bean:

public String salva(){
	if(studente.getId()==0){
		gestioneStudenti.aggiungi(studente);
	}else{
		gestioneStudenti.aggiorna(studente);
	}
	studenti=gestioneStudenti.listaStudenti();
	return "annulla-salva";
}

se l'oggetto studente ha il suo id a 0, significa che non vi è stata alcuna selezione, quindi l'operazione da effettuare è un nuvo inserimento, altrimenti abbiamo un elemento da aggiornare.

Al termine dell'operazione richiesta al componente EJB, dobbiamo aggiornare la lista totale e navigare sulla pagina ad essa associata. Abbiamo a questo punto ultimato il nostro lavoro per questa funzionalità e possiamo effettuare il deploy del nostro EAR per vedere quindi l'applicazione all'opera.

conclusioni: cosa c'è ancora da fare?

Abbiamo completato la prima parte del nostro sistema, proseguiremo nei successivi articoli con l'implementazione delle restanti funzionalità e con la realizzazione di un piccolo modulo di autenticazione che faccia uso di un listener JSF. Si sono introdotte diverse tecnologie con l'obiettivo di una trattazione che non si fermasse ad una semplice esposizione su IceFaces, ma su come sia piuttosto possibile usarlo in maniera più realistica insieme ad altre tecnologie, per la realizzazione di soluzioni complesse in tempi rapidi.

Ti consigliamo anche