Prefazione

CUBA Platform è un framework open source, che si prefigge di semplificare il processo di sviluppo delle applicazioni aziendali. Unisce una architettura affidabile, componenti di qualità enterprise pronti all'uso, e una serie di strumenti di produttività, consentendo la creazione di applicazioni web in tempi record.

In questo quick start affronteremo le basi di CUBA, sviluppando un'applicazione semplice ma perfettamente funzionante. Mostrerà i tre aspetti principali per la creazione di applicazioni web: come sviluppare il modello dati, come manipolare i dati, come creare la logica di business e, infine, come creare l'interfaccia utente. L'esempio che svilupperemo è un'applicazione per la pianificazione delle sessioni in una conferenza. Alla fine di questo tutorial sarete già in grado di sviluppare il vostro primo progetto CUBA.
Per seguire questo tutorial dovete scaricare CUBA Studio, quindi installatelo e accettate l'abbonamento di prova per avere accesso agli editor visuali.

Repository di esempio: https://github.com/cuba-platform/sample-session-planner.

Creazione di un nuovo progetto

Creiamo un progetto CUBA vuoto e chiamiamolo SessionPlanner, usando la voce di menù apposita in IntelliJ IDEA. Useremo Java 8 come JDK di default.

Creazione del Modello dei Dati

Il primo compito - creare le entità. Il modello di dominio prevede solo due classi (chiamate anche Entità): Speaker e Session. La relazione tra di loro è di tipo uno a molti. Un relatore può presentare più sessioni.

Come prima cosa, dobbiamo creare l'entità Speaker. Per farlo, possiamo cliccare su un collegamento nella pagina di benvenuto:

Oppure fare tasto destro sul nodo "Data Model" del progetto CUBA, posto nella parte sinistra dello schermo, e selezionare "New -> Entity"

Inseriamo il nome dell'entità (Speaker) e creiamo gli attributi in base alle nostre specifiche:

NomeTipoObbligatorio?Altri vincoli
firstNameString (255)
lastNameString (255)
emailString (1024)Validatore “Email”

CUBA si appoggia sulle entità JPA standard, e per crearle possiamo utilizzare sia l'editor di codice, sia il designer visuale. Clicchiamo sull'icona "+" e aggiungiamo gli attributi all'entità, e CUBA Studio genererà i membri della classe per noi.

In CUBA è possibile specificare una stringa di formato per visualizzare le entità nella UI - l'Instance Name. Per lo Speaker, selezioniamo gli attributi firstName e lastName.

Se clicchiamo sulla scheda "Text" in fondo all'editor di entità, possiamo vedere che è stata generata una normale classe Java con annotazioni JPA standard. Il codice generato può essere modificato a piacere, e l'editor visuale verrà aggiornato in automatico quando clicchiamo nuovamente sulla scheda "Designer".

Proseguiamo creando l'entità Session e collegandola alla nostra classe Speaker. I campi necessari sono riportati nella tabella sottostante. L'ora di fine sessione sarà un campo calcolato (un'ora dall'inizio della sessione).

NomeTipoObbligatorio?
topicString (255)
startDateDateTime
endDateDateTime
speakerAssociazione all'entità “Speaker”, cardinalità molti-a-uno
descriptionString (2000)

La procedura è quella che abbiamo già visto per l'altra entità, ma questa volta vogliamo che l'attributo "speaker" venga visualizzato nella UI come casella a discesa, quindi specifichiamo "lookup" nella sezione "Lookup Actions" e "Dropdown" come tipo di Lookup. Alla fine la definizione dei campi sarà come segue:

Aggiunta di un Campo Calcolato

In CUBA, possiamo aggiungere una logica di calcolo per un campo, utilizzando dei metodi di callback legati al ciclo di vita dell'entità. Dobbiamo creare un metodo e aggiungere l'annotazione Java corretta. Il metodo verrà eseguito in automatico. Aggiungiamo quindi un metodo che verrà invocato durante la fase "PrePersist" del ciclo di vita dell'entità. Per farlo, clicchiamo sul pulsante "Lifecycle Callbacks" posto in alto nell'editor di entità, e selezioniamo la fase desiderata.

Chiamiamo il metodo "updateEndDate" e aggiungiamo entrambe le annotazioni @PreUpdate e @PrePersist. Per favorire il riuso del codice, creiamo un metodo separato per il calcolo della data e ora di fine sessione.

public static Date calculateEndDate(Date startDate) {
  return Date.from(startDate.toInstant().plus(1, ChronoUnit.HOURS));
}

Ora richiamiamo questo nuovo metodo dal metodo updateEndDate.:

@PrePersist
@PreUpdate
public void updateEndDate() {
  endDate = calculateEndDate(startDate);
}

Ecco fatto. Ora il nostro modello dati è completo.

Creazione del Database

Di default CUBA Studio usa il database HSQL durante lo sviluppo, e genera gli script SQL necessari per questo RDBMS. Per generare gli script che servono a creare il database, dobbiamo selezionare il menù "CUBA -> Generate Database Scripts". Possiamo verificare gli script generati nella finestra di dialogo, prima di salvarli come file di progetto. Ricordate che questi script sono parte integrante del progetto, e li potete trovare nel nodo "Main Data Store" nell'albero di progetto a sinistra.

Gli script possono essere modificati se si desidera aggiungere altri oggetti nel database, come indici o istruzioni di insert per i dati iniziali.

Premiamo il pulsante "Save and close" per salvare gli script. Per applicare questi script e creare il database, è sufficiente selezionare la voce di menù "CUBA -> Create Database". Oltre alle tabelle applicative, CUBA crea anche delle tabelle di sistema dove memorizza informazioni quali gli utenti, i ruoli, le attività schedulate, ecc.

Ora che il database è stato creato, passiamo a creare una semplice UI per le operazioni CRUD sui dati.

Creazione delle Schermate CRUD

CUBA Studio rende disponibile un wizard per la creazione di interfacce utente di base:

  • Browser - per mostrare la lista di entità in griglia
  • Editor - per modificare una singola istanza di entità con un form generato automaticamente

Ora procediamo a creare le schermate che gestiranno i relatori. Dato che questa entità è molto semplice, possiamo semplicemente accettare i parametri di default che ci propone il wizard di creazione. Avviamo il wizard cliccando sulla voce di menù "Screen -> Create Screen" che troviamo in cima all'editor di entità.

Possiamo accedere allo stesso wizard facendo tasto destro su una entità nell'albero di progetto a sinistra, e selezionando la voce di menù "New -> Screen".

Per l'entità Speaker vogliamo una schermata di browse ed una di edit. Selezioniamo quindi "Browser and Editor" nella scheda "Screen Templates" del wizard, e premiamo "Next"

Lasciamo i parametri di default proposti e clicchiamo nuovamente "Next".

Nel passaggio successivo personalizziamo i titoli delle schermate e il nome del menù applicativo. Quindi clicchiamo "Finish" per procedere alla creazione delle schermate.

Come possiamo vedere da questi primi esempi, ogni schermata consiste di due parti: il controller, scritto in Java, che contiene la logica interna della schermata e il codice che gestisce gli eventi, e un file XML che definisce l'aspetto dell'interfaccia utente. In questo caso la schermata di browse consiste dei file “speaker-browse.xml” e “SpeakerBrowse.java”, mentre quella di edit dei file “speaker-edit.xml” e “SpeakerEdit.java”. Possiamo trovare i sorgenti delle schermate sotto la voce “Generic UI -> Screens” nell'albero di progetto CUBA.

Prestate attenzione alla sezione "DATA" nell'XML - definisce il modo in cui i dati vengono letti dal database.

<data readOnly="true">
   <collection id="speakersDc"
               class="com.company.sessionplanner.entity.Speaker"
               view="_local">
       <loader id="speakersDl">
           <query>
               <![CDATA[select e from sessionplanner_Speaker e]]>
           </query>
       </loader>
   </collection>
</data>

Possiamo navigare facilmente tra le schermate e le entità collegate all'interno di CUBA Studio, usando i pulsanti sopra la finestra dell'editor:

Creazione schermate di browse ed edit per le sessioni

Avviamo nuovamente il wizard di generazione schermate, e selezioniamo l'opzione “Entity browser and editor screens” quindi clicchiamo su “Next”.
Nel passaggio successivo creeremo le viste di "Browse" e di "Edit" per le nostre schermate. In CUBA, una "Entity View" (Vista di Entità) definisce quali campi saranno letti dal database. Questa informazione viene usata sia durante la creazione delle schermate, sia nelle chiamate API di gestione dei dati.

Creiamo una vista per l'entità "Session" da utilizzare per la schermata di browse, che includa anche il riferimento al relatore. Nella casella a discesa "Browse View" selezioniamo “Create view…”

Nella finestra di popup inseriamo il valore "session-browse-view" nel campo "Name", e selezioniamo l'attributo "speaker" nell'albero di selezione in basso. La configurazione finale dovrà essere la seguente:

Selezionando l'attributo "speaker" stiamo chiedendo a CUBA di leggere i dati dalla tabella "Speaker", per visualizzare il nome e cognome del relatore nella finestra di browse.
Clicchiamo "OK" per salvare la nuova vista.

Per l'entità Session, non permettermo la modifica della data di fine, dato che è generata in automatico. Creaiamo una vista chiamata "session-edit-view" per la schermata di Edit, selezionamo "_minimal" nella casella a discesa "Extends" e spuntiamo tutti gli attributi ad eccezione di "endDate". In questo modo chiediamo a CUBA di non leggere questo campo dal database, ma sarà ugualmente salvato durante le fasi "pre-persist" e "pre-update". Fate riferimento alla sezione "Aggiunta di un Campo Calcolato" per ulteriori informazioni. In aggiunta a ciò, in questo modo il wizard di generazione schermate non creerà un controllo per questo attributo. A questo punto la vista di edit si dovrebbe presentare così:

Clicchiamo "OK" per salvare la nuova vista.

Adesso possiamo completare la generazione della nostra schermata. Clicchiamo "Next", e modifichiamo i titoli se necessario. Adesso dobbiamo fare delle piccole modifiche alla schermata appena generata. Se apriamo il file XML della schermata di edit, vedremo che il campo "description" è stato generato come campo di testo semplice.

Convertiamolo in un campo multi-linea. Il modo più semplice per farlo è di aprire il descrittore XML, e cambiare il tag "textField" in "textArea" per l'elemento con id="descriptionField"

<form id="form" dataContainer="sessionDc">
   <column width="250px">
       <textField id="topicField" property="topic"/>
       <dateField id="startDateField" property="startDate"/>
       <lookupPickerField id="speakerField" optionsContainer="speakersDc" property="speaker">
           <actions>
               <action id="lookup" type="picker_lookup"/>
           </actions>
       </lookupPickerField>
       <textArea id="descriptionField" property="description"/>
   </column>
</form>

Come potete vedere, il campo "endDate" non è stato aggiunto alla schermata di modifica, ma è presente in quella di browse.

Eseguire l'Applicazione in Modalità Sviluppo

Per eseguire l'applicazione CUBA è sufficiente cliccare sul pulsante "Run" nella barra degli strumenti dell'IDE

Oppure selezionare “CUBA -> Start Application Server” dal menù principale.

Dopo la compilazione, potremo accedere all'applicazione nel browser. L'URL corretto verrà visualizzato nella finestra degli strumenti "Run" di IDEA. Facciamo click sull'URL visualizzato per aprirlo nel browser predefinito, e accediamo all'applicazione usando "admin" come nome utente. La password di default è "admin". Le voci di menù, per accedere alle schermate che abbiamo creato, si trovano nel menù "Application".
Ora proviamo a scrivere un po' di dati nel database: un paio di relatori e due sessioni schedulate per il resto della settimana. Proviamo a inserire una email non valida per un relatore, per assicurarci che la validazione funzioni come previsto.

La generazione automatica delle schermate è ottima per le operazioni di base, ma nella realtà le interfaccia sono più complesse.

Personalizzare l'Interfaccia Utente

Ora proviamo ad aggiungere un'interfaccia per visualizzare le sessioni in un calendario, in aggiunta alla griglia. Aggiungeremo un controllo a schede nella schermata di browse, ci metteremo un controllo calendario, e implementeremo la logica per modificare e rischedulare le sessioni usando questo controllo:

Apriamo il file session-browse.xml e individuiamo il container “Tab Sheet” nell'elenco dei controlli.

Trasciniamo il TabSheet sotto il componente "filter" nell'albero dei componenti in alto a destra, e inseriamo l'id "sessionsTab" nella scheda "Properties" in basso a destra.

E' consigliabile assegnare sempre un id ai controlli, soprattutto se prevediamo di utilizzarli nel codice del controller. A questo punto l'albero dei componenti dovrebbe apparire come segue:

Trasciniamo due componenti "Tab" sotto "sessionsTabs" e assegniamo gli id "calendarTab" e "tableTab". Quindi impostiamo i titoli di questi tab come "Sessions Calendar" e "Sessions Table".

A questo punto chiudiamo il ramo del componente "sessionsTable" cliccando su "-", e trasciniamolo su "tableTab".

Vedrete che il layout presenterà un errore dopo le nostre modifiche.

Per correggerlo, dovremo impostare un componente in grado di potersi espandere nell'area vuota della schermata. Selezioniamo il componente "layout" e impostiamo la proprietà "expand" su "sessionTabs" invece di "sessionsTable", così che il controllo a schede occupi l'intera schermata.

Adesso cerchiamo il controllo "Calendar" nell'elenco dei componenti e trasciniamolo sul componente "calendarTab". Assegniamo a questo nuovo controllo l'id "sessionsCalendar".

Impostiamo la proprietà "expand" della scheda "tableTab" su "sessionsTable", e quella della scheda "calendarTab" su "sessionsCalendar".

In CUBA, i componenti di interfaccia sono collegati alle entità e alle loro proprietà.

Collegheremo il calendario alla collezione di dati caricata nella schermata. Per questo basta assegnare la sua proprietà "dataContainer" su "sessionsDc". Inoltre collegheremo le seguenti proprietà:

  • startDateProperty al campo startDate di una Session
  • endDateProperty al campo endDate di una Session
  • captionProperty al campo topic di una Session
  • e infine descriptionProperty al campo description di una Session

Per fare pratica con l'editor XML modifichiamo il descrittore della schermata, in modo che il calendario visualizzi solo le ore lavorative, e mostri i pulsanti di navigazione. Mentre apportate le modifiche, notate come l'editor fornisca l'auto-completamento degli attributi corretti, così da evitare gli errori di digitazione. Dopo le modifiche, il tag del calendario dovrebbe presentarsi come il seguente:

<calendar id="sessionsCalendar" dataContainer="sessionsDc" startDateProperty="startDate"
          endDateProperty="endDate" captionProperty="topic" descriptionProperty="description"
          firstVisibleHourOfDay="8" lastVisibleHourOfDay="18" navigationButtonsVisible="true"/>

Per vedere le modifiche apportate in azione, non è necessario riavviare l'applicazione, ma è sufficiente chiudere e riaprire la schermata modificata. Il framework CUBA framework supporta l'hot reload delle schermate dopo ogni modifica. Adesso possiamo vedere le sessioni visualizzate all'interno del calendario.

Usare le Screen API

Quando un utente interagisce con l'interfaccia, vengono generati degli eventi. Possiamo rispondere a questi eventi gestendoli da codice. Ora gestiamo il click su un evento del calendario per aprire una sessione in modifica. Per la gestione delle schermate, CUBA fornisce un API che ora andremo ad utilizzare.

Nel controller SessionBrowse facciamo click sul pulsante "Subscribe to Event" in alto

e selezioniamo CalendarEventClickEvent nella finestra di popup, quindi clicchiamo OK.

Verrà generato un metodo vuoto nel controller:

@Subscribe("sessionsCalendar")
private void onSessionsCalendarCalendarEventClick(Calendar.CalendarEventClickEvent event) {

}

Ora aggiungiamo l'interfaccia ScreenBuilders al controller. Clicchiamo sul pulsante "Inject" in alto, e selezioniamo il servizio screenBuilders sotto la voce "Screen API" nella finestra di popup.

Lo stesso risultato si può ottenere usando la combinazione Alt+Insert nell'editor di codice, e selezionando la voce "Inject" nel menù contestuale:

Per cercare il servizio desiderato, possiamo digitare le iniziali del suo nome e usare le frecce su/giù per navigare tra i servizi disponibili.

Dopo aver aggiunto il servizio al controller, per aprire una schermata dobbiamo invocare una catena di metodi specifici.

Vogliamo un editor per la classe Session, usando "this" come schermata contenitore. Quindi vogliamo modificare l'entità Session contenuta nell'oggetto di evento ricevuto. La schermata sarà aperta in modalità finestra di dialogo. Quando la schermata verrà chiusa, dobbiamo ricaricare tutti i dati nella schermata di browse. Alla fine dobbiamo visualizzare la schermata che abbiamo appena creato. Il codice finale sarà il seguente:

@Subscribe("sessionsCalendar")
private void onSessionsCalendarCalendarEventClick(Calendar.CalendarEventClickEvent event) {
   Screen screen = screenBuilders.editor(Session.class, this)
           .editEntity((Session) event.getEntity())
           .withLaunchMode(OpenMode.DIALOG).build();
   screen.addAfterCloseListener(afterCloseEvent -> {
       getScreenData().loadAll();
   });
   screen.show();
}

Ora proviamo a riaprire la schermata nel browser e aprire l'editor cliccando su una sessione nel calendario.

Come potete vedere, dobbiamo ritoccare l'aspetto della finestra di modifica, aggiustando la larghezza e l'altezza. Apriamo il descrittore XML della schermata, e impostiamo entrambi gli attributi "width" e "height" del tag "dialogMode" come "auto".

Torniamo all'applicazione nel browser, e proviamo a chiudere l'editor e riaprirlo, cliccando nuovamente su una sessione nel calendario. Ok, ora si presenta meglio.

Sviluppo della Logica di Business

In questa sezione useremo CUBA Studio per creare un servizio che implementerà la logica di business, e useremo questo servizio in una schermata. Il servizio fornirà una funzionalità per rischedulare una sessione, in modo che un relatore non possa avere più di una sessione allo stesso tempo.

Facciamo tasto destro sul nodo Service nel progetto CUBA e selezioniamo "New -> Service". Nella finestra di dialogo inseriamo "SessionService" come nome di interfaccia, e la classe di implementazione verrà chiamata in automatico "SessionServiceBean".

Creiamo un metodo “rescheduleSession” nel file di interfaccia, come riportato di seguito:

public interface SessionService {
   String NAME = "sessionplanner_SessionService";

   boolean rescheduleSession(Session session, Date newStartDate);
}

Il metodo accetterà in ingresso una sessione e una nuova data e ora per la stessa. Se il metodo sarà in grado di rischedulare la sessione senza conflitti, la salveremo con la nuova data e ora di inizio, mentre in caso contrario - restituiremo semplicemente "false" come risultato del metodo.

Per implementare il metodo, apriamo l'editor di codice per la classe SessionServiceBean, che troviamo sotto il nodo "Middleware - Services” nell'albero di progetto CUBA:

La classe è vuota e presenta errori:

Premiamo Alt+Insert nel corpo della classe e selezioniamo "Implement methods" nel menù contestuale:

Ora selezioniamo il metodo “rescheduleSession”, e l'IDE genererà il codice del metodo (vuoto) per noi:

@Service(SessionService.NAME)
public class SessionServiceBean implements SessionService {

   @Override
   public boolean rescheduleSession(Session session, Date newStartDate) {
       return false;
   }
}

Useremo le API di CUBA per l'accesso ai dati - nello specifico la classe DataManager. Con il DataManager useremo una query JPQL, per controllare se ci sono già delle sessioni schedulate per il relatore in un certo intervallo di tempo, e aggiungiamo i valori per i parametri alla chiamata. Quindi controlliamo il risultato della query e, in base ad esso, decidiamo se aggiornare la sessione con una nuova data di inizio, o uscire dal metodo con un risultato "false".

Iniettiamo il DataManager nel servizio premendo Alt+Insert nel corpo della classe, e selezionando “Inject” nel menù contestuale:

Selezioniamo DataManager nella finestra di dialogo:

Ed ecco qui di seguito l'implementazione completa del metodo:

@Override
public boolean rescheduleSession(Session session, Date newStartDate) {

   Date newEndDate = Session.calculateEndDate(newStartDate);

   int sessionsSameTime = dataManager.load(Session.class)
           .query("select s from sessionplanner_Session s where " +
                   "s.startDate < :newEndDate and s.endDate > :newStartDate " +
                   "and s.speaker = :speaker " +
                   "and s.id <> :sessionId")
           .parameter("newStartDate", newStartDate)
           .parameter("newEndDate", newEndDate)
           .parameter("speaker", session.getSpeaker())
           .parameter("sessionId", session.getId())
           .list().size();

   if (sessionsSameTime == 0) {
       session.setStartDate(newStartDate);
       dataManager.commit(session);
       return true;
   }

   return false;
}

Come potete vedere abbiamo riutilizzato il metodo creato in precedenza nella sezione "Aggiunta di un Campo Calcolato":

Date newEndDate = Session.calculateEndDate(newStartDate);

Il servizio ora è pronto, non ci resta che richiamarlo dalla schermata di browse delle sessioni. Sarà invocato quando l'utente farà drag-and-drop di un evento all'interno del calendario. Se la sessione non può essere rischedulata, allora mostreremo una notifica usando le API di notifica di CUBA, che andiamo ad aggiungere al controller.

Ora passiamo al controller dello schermo, e iniettiamo il nostro servizio nella classe come abbiamo fatto in precedenza. Allo stesso modo, iniettiamo anche l'interfaccia "Notifications". E infine, sottoscriviamo l'evento "CalendarEventMove" del componente calendario, come abbiamo fatto in precedenza per l'evento Click.

Ora inseriamo il codice seguente nel metodo di gestione dell'evento:

@Subscribe("sessionsCalendar")
private void onSessionsCalendarCalendarEventMove(Calendar.CalendarEventMoveEvent event) {

   Session session = ((EntityCalendarEvent<Session>)event.getCalendarEvent()).getEntity();

   if (!sessionService.rescheduleSession(session, event.getNewStart())) {
       notifications.create(Notifications.NotificationType.WARNING)
               .withCaption("Session "+session.getTopic()+" cannot be rescheduled to "+event.getNewStart()+" due to a conflict")
               .show();
   }

   getScreenData().loadAll();
}

Per poter utilizzare il nuovo servizio, dobbiamo riavviare l'applicazione. Possiamo usare i pulsanti "Stop" e "Run" nella barra degli strumenti di IDEA, oppure il pulsante "Restart".

Dopo averla riavviata possiamo aprire di nuovo il calendario delle sessioni - e voilà! Il drag-and-drop degli eventi nel calendario è funzionante! Vediamo se funziona davvero.. Aggiungiamo una nuova sessione e proviamo a rischedularla.

Branding

Possiamo personalizzare la nostra applicazione CUBA modificando le etichette di tutte le schermate, comprese la finestra principale e quella di login.
You can customize CUBA applications by changing default text in standard screens like main screen or login screen. Modifichiamo quindi i testi per il nostro dominio di business - la pianificazione delle conferenze.

Apriamo il file di messaggi principale (messages.properties) situato sotto il nodo “Generic UI - Main Message Pack” nell'albero di progetto CUBA.

E' un file .properties standard, quindi potete modificarlo liberamente con l'editor di testo, aggiungendo nuove chiavi (messaggi) e modificando quelle esistenti. Aggiorniamo quindi i messaggi per riflettere lo scopo della nostra applicazione. Il risultato finale è il seguente:

application.caption = CUBA Session Planner
application.logoImage = branding/app-icon-menu.png
application.logoLabel = Session Planner

loginWindow.caption = Login
loginWindow.welcomeLabel = Welcome to Session Planner!
loginWindow.logoImage = branding/app-icon-login.png

menu-config.application-sessionplanner = Planner
menu-config.sessionplanner_Speaker.browse=Speakers
menu-config.sessionplanner_Session.browse=Sessions

Grazie alla funzionalità "Hot Deploy" di CUBA, tutto quello che dobbiamo fare è loggarci nuovamente nell'applicazione, e le nostre modifiche saranno già applicate.

Marketplace

Il framework dispone anche di un marketplace che contiene molti componenti pronti all'uso, che possono essere aggiunti alle nostre applicazioni, fornendo funzionalità quali grafici o mappe. Possiamo installare questi componenti, senza lasciare CUBA Studio, utilizzando il menù "CUBA -> Marketplace".

Conclusioni

Il framework CUBA fornisce una vasta gamma di API per aiutarci a creare applicazioni aziendali molto velocemente. Questo quickstart ha mostrato solo le funzionalità di base del framework e di CUBA Studio. Stiamo lavorando per aggiungere sempre più risorse in italiano, ma se non trovate quello che state cercando su questo sito, provate gli esempi e i tutorials sul nostro sito web inglese: cuba-platform.com.

Grazie per il vostro interesse nella piattaforma CUBA. Confidiamo che lavorare con CUBA si dimostrerà produttivo e piacevole allo stesso tempo!