Laravel – modifiche ad un tracciato dati in una dashboard di dati Covid-19: come gestirle

Laravel vs SARS-Cov-2
Laravel vs SARS-Cov-2
Immagine del virus SARS-Cov-2 al

Nella gestione dei dati della pandemia da Covid-19, nel corso del tempo è successo che i gestori del flusso dati della Protezione Civile per ragioni di miglioramento della interpretazione edlla situazione, integrassero le colonne con nuove dimensioni. Ad esempio dal 18 gennaio 2021 sono stati introdotti dimensioni come il numero di tamponi rapidi, per distinguerli dai tamponi con test molecolare.

Questa modifica impatta nella base dati locale, che si maniene aggiornata quotidianamente, con l’aggiunta di una colonna ulteriore nel tracciato CSV reperibile nel repository pubblico ufficiale della Protezione Civile ospitato su Github, colonna che deve essere ospitata in una tabella relazionale che rispecchia la struttura del CSV.

Come adeguare il modello MVC al nuovo tracciato? Laravel consente di farlo in modo molto veloce.

Modifica al modello Laravel e alla tabella database

Si crea innanziatutto una nuova migrazione vuota:

$ php artisan make:migration tab_dati_nazionali_add_tampone_rapido

Il nome della migrazione è a piacere e deve ricordare l’operazione che svolgerà. Non serve che sia il nome esatto del campo (sono nomi lunghi e preferisco abbreviare).

Un nuovo file per la migrazione verrà quidi creato sotto la directory

app/database/migrations
-rw-rw-r-- 1 marcob marcob 822 gen 22 10:51 2021_01_22_095107_tab_dati_nazionali_add_tampone_rapido.php

Editiamo il file che si presenta così, con una nuova classe che è la versione camelCased del nome del file di migrazione:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class TabDatiNazionaliAddTamponeRapido extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        //
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        //
    }
}

La classe ha due metodi: uno up() per aggiungere il campo e uno che si chiama ovviamente down() per eliminarlo, nel caso in cui fosse necessario un rollback delle operazioni di modifica della struttura.

Dobbiamo quindi aggiungere l’istruzione PHP che verrà convertita in istruzione DDL dall’ORM. Il nome esatto del campo che verrà prelevato dal CSV è tamponi_test_antigenico_rapido ed è modellabile con un integer; lo sisteiamo appena dopo la colonna note_test:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class TabDatiNazionaliAddTamponeRapido extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        //
        Schema::table('dati_nazionali', function (Blueprint $table) {
            //
            $table->integer('tamponi_test_antigenico_rapido', '0')
                ->after('note_test')
                ->default(0)
                ->nullable();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        //
        Schema::table('dati_nazionali', function (Blueprint $table) {
            //
            $table->dropColumn('tamponi_test_antigenico_rapido');
        });
    }
}

Ho creato a suo tempo una tabella dati_nazionali nel database che ospita i dati provenienti dai file CSV. Ora devo solo aggiungere una nuova colonna.

A questo punto è arrivato il momento di lanciare la migrazione

$ php artisan migrate
Migrating: 2021_01_22_095107_tab_dati_nazionali_add_tampone_molecolare
Migrated:  2021_01_22_095107_tab_dati_nazionali_add_tampone_molecolare (0.43 seconds)
$

Si può controllare la presenza del nuovo campo nella tabella dati nazionali:

mysql> desc italia;
+----------------------------------------+-----------------+------+-----+---------+-----------+
| Field                                  | Type            | Null | Key | Default | Extra     |
+----------------------------------------+-----------------+------+-----+---------+-----------+
| id                                     | bigint unsigned | NO   | PRI | NULL    | auto_inc..|
| data                                   | datetime        | NO   | UNI | NULL    |           |
| stato                                  | varchar(255)    | NO   |     | NULL    |           |
| ricoverati_con_sintomi                 | int unsigned    | YES  |     | NULL    |           |
| terapia_intensiva                      | int unsigned    | YES  |     | NULL    |           |
| ingressi_terapia_intensiva             | int             | YES  |     | 0       |           |
| totale_ospedalizzati                   | int unsigned    | YES  |     | NULL    |           |
| isolamento_domiciliare                 | int unsigned    | YES  |     | NULL    |           |
| totale_positivi                        | int unsigned    | YES  |     | NULL    |           |
| variazione_totale_positivi             | int             | YES  |     | NULL    |           |
| nuovi_positivi                         | int unsigned    | YES  |     | NULL    |           |
| dimessi_guariti                        | int unsigned    | YES  |     | NULL    |           |
| deceduti                               | int unsigned    | YES  |     | NULL    |           |
| casi_da_sospetto_diagnostico           | int             | YES  |     | 0       |           |
| casi_da_screening                      | int             | YES  |     | 0       |           |
| totale_casi                            | int unsigned    | YES  |     | NULL    |           |
| tamponi                                | int unsigned    | YES  |     | NULL    |           |
| casi_testati                           | int unsigned    | YES  |     | NULL    |           |
| note                                   | varchar(255)    | YES  |     | NULL    |           |
| note_casi                              | longtext        | YES  |     | NULL    |           |
| note_test                              | longtext        | YES  |     | NULL    |           |
| tamponi_test_antigenico_rapido         | int             | YES  |     | 0       |           |
| tamponi_test_molecolare                | int             | YES  |     | 0       |           |
| totale_positivi_test_antigenico_rapido | int             | YES  |     | 0       |           |
| totale_positivi_test_molecolare        | int             | YES  |     | 0       |           |
| created_at                             | timestamp       | YES  |     | NULL    |           |
| updated_at                             | timestamp       | YES  |     | NULL    |           |
+----------------------------------------+-----------------+------+-----+---------+-----------+
27 rows in set (0.01 sec)

Come si può vedere, il campo tamponi_test_antigenico_rapido è un intero ed è satato inserito dopo il campo note_test come richiesto.

Finito!

E se qualcosa va storto?

Si può sempre tornare indietro facendo un rollback: artisan eseguirà il metodo down() della classe che viene tradotto con un alter table remove column.

$ php artisan migrate:rollback

Gestione della view e dell’importatore Laravel

In questo caso ho scelto di non mostrare nella tabella anche il nuovo dato, ma se si volesse è sufficiente modificare la view

app/resources/views/dati_nazionali/index.blade.php

aggiungendo una colonna nella riga di intestazione

    <table class="table table-hover text-centered">
            <thead>
                <th>data</th>
               ...
                <th>totale</th>
                <th>tamponi molecolari</th>
                <th>tamponi rapidi</th> <-------
               ...

e il segnaposto per il dato nel ciclo

@foreach($array['data'] as $item)
                <tr>
                    <td>{{$item->data}}</a></td>
                    ...
                    <td>{{$item->tamponi_test_molecolare}}</a></td>
                    <td>{{$item->tamponi_test_antigenico_rapido}}</a></td>
                    ...

Nle mio caso non l’ho integrato per pigrizia (mi interessa maggiormanete che il processo di importazione giri senza problemi ogni giorno), ma appena posso lo integro nella vista.

Ultimo step: l’estrattore da CSV

L’ultimo passaggio è l’esecuzione giornaliera dell’importatore: una riga di cron lancia lo script Laravel che si connette via Curl (vedi l’articolo sui problemi della libreria Ixudra che implememta il client cURL http) al repository Github e scrive le nuove righe nelle tabelle dei dati nazionali e regionali.

Questo script non ho dovuto modificarlo perché l’ho già scritto in modo dinamico: il parser estrae i dati leggendo le colonne del csv e cercando le colonne omonime nella tabella, per cui è già tutto ok.

Sito della situazione Covid-19 di betaingegneria.

Grails scaffolding

Questo articolo parla dello scaffolding di Grails, ossia della funzionalità di generare le pagine web standard in base alle tabelle del database. In particolare affrontiamo gli aspetti della generazione dello scaffold e dell’ordine di comparsa dei campi della tabella nella form HTML. La versione di Grails di cui parlo non è nuovissima, ma è quella su cui sto lavorando su comissione.

A TRUE scaffold
Le impalcature servono anche nel software 😉

Cos’è uno scaffold

Lo scaffold è l’impalcatura, cioè una rappresentazione grezza, anche se visavamente decorosa, delle tabelle DBMS come form HTML, che viene generata automaticamente dal framework, assieme alle implementazioni HTTP dei verbi CRUD che definiscono le operazioni su queste tabelle.

Per chi come me ha iniziato a scrivere applicazioni web nel 2000 si ricorderà che si doveva fare tutto a mano: mappare i campi della tabella in opportuni controlli (HTML), scrivere il controllo di omogeneità lato server e lato client (javascript/jQuery) . Ad esempio: range di valori, correttezza dell’input, ecc. E poi implementare le operazioni CRUD attraverso la compilazione di comandi SQL parametrici.

Dopo un po’ ci si è resi conto che questo lavoro era anche terribilmente noioso perché meccanico e ripetitivo, per cui presto si è messo a punto lo strumento dello scaffolding che automatizzava questa parte di lavoro, producendo automaticamente il codice nel linguaggio dell’application server e la parte grafica in HTML/CSS a partire dalla definizione in DDL della tabella DBMS per la realizzazione delle 4 operazioni.

Scaffolding con Grails 1: static vs dynamic

Lo scaffolding con Grails è possibile in due modi.

Dynamic scaffolding.

Il primo veloce ed economico è lo scaffolding dinamico che significa che gli HTML delle pagine che realizzano le operazioni vengono generati al volo dal framework coem anche le funzionalità CRUD del controller e viene tenuto tutto in memoria RAM dell’Application server. È sufficente aggiungere la variabile static scaffolding inizializzata a true nella dichiarazione del controller:

package myapp

class CapitoloController {
    static scaffold = true

//    def index() {
//        render "Elenco dei capitoli"
//    }

}

Come alternativa si può anche inizializzare la variabile scaffold con il nome del dominio (tabella) che si vuole implementare.

Con questa modalità però non ho nessuna azione che venga cablata nel controller, né la produzione di alcuna vista. Se arresto il server perdo tutto.

È un buon punto di partenza ma non offre alcuna flessibilità.

Static scaffolding.

Lo scaffolding statico invece produce codice (controller / view) che poi può essere personalizzato.

In IDE del tipo Groovy/Grails toolsuite esisteva un comando disponibile a menu che produceva lo scaffold per il controller (quindi le azioni standard) e per le viste (le pagine html standard per ogni verbo). Venegoo generati anche i codici Groovy per tutti questi elementi.

Io però uso IntelliJ IDEA di JetBrains che non ha questo comando. Tuttavia è solo un lack della GUI perché da linea di comando si può fare tranquillamente:

$ grails generate-controller rewards.OrderItem

ad esempio genererà il codice per il Controller con i metodi standard index(), show(), creste(), save(), update(), delete(), notFound() e inoltre produrrà una cartella sotto views con il nome del dominio e le viste standard: _form.gsp, create.gsp, edit.gsp, index.gsp e show.gsp.

Grails Scaffolding #2: decidere l’ordine di apparizione dei campi della form HTML

Parte dell’utilizzo dei database nelle applicazioni web è rivolto alla posssibilità di fornire una rappresentazione grafica della tabelle db (assieme alle relazioni) come moduli (form) html. Infatti ciò che si fa è mappare ogni record in una maschera (form) con controlli (input) che ne riportano i valori.

Se si usa la funzionalità di scaffolding di Grails per costtuire le views (le schermate), è possibile decidere in quale ordine debbano essere visualizzati i campi di una form CRUD per la gestione di una tabella DBMS (ovvero di un domain Grails).

Ovviamente se non si usa lo scaffolidng è tutto a carico del progettista, ma è anche possibile dire a Grails come generare gli scaffold.

Nella dichiarazione del domain, nella dichiarazione dei constraints, scriveremo i campi nell’ordine in cui vogliamo che compaiano:

package rewards

class Customer {
    String firstName
    String lastName
    Long phone
    String email
    Integer totalPoints
    static hasMany = [awards: Award, orders: OnlineOrder]

    static constraints = {
        firstName()
        lastName()
        email()
        phone()
        totalPoints()
    }

Nelle dichiarazioni delle variabili, l’ordine non è importante (a questo riguardo). Per quanto riguarda l’ordine nel quale comparranno nella form, è la varibile static costraints a guidare.

Il risultato è rappresentato in figura 1

Come ordinare i campi della form usando lo scaffolding Grails
Oridine dei campi della form

Grails, regolazione fine dell’ambiente di sviluppo integrato con Oracle

Grails, driver Oracle, Groovy

In questo articolo riassumo un po’ di informazioni sulla corretta impostazione di un progetto Groovy on Grails che integra una base dati Oracle, utilizzando la IDE IntelliJ IDEA di JetBrains. Con poco sforzo si può adattare ad altri tipi di DBMS.

Versione di Grails da linea di comando

Mi sono accorto oggi che mentre inizializzavo i miei progetti con la versione 2.3.11 (che è quella che mi ha richiesto il Cliente), in realtà quando eseguivo il comand grails veniva impiegata la versione 4.0.5. L’output era il seguente:

$ grails -version
 Grails version: 4.0.5

Ho in realtà a disposizione diverse versioni di Grails:

$ ll ~/.sdkman/candidates/grails
 drwxr-xr-x  8 marcob marcob 4096 gen  8 11:24 ./
 drwxr-xr-x  6 marcob marcob 4096 ago 13  2019 ../
 drwxr-xr-x 13 marcob marcob 4096 apr 29  2013 2.1.5/
 drwxr-xr-x 13 marcob marcob 4096 giu 21  2013 2.2.3/
 drwxr-xr-x 12 marcob marcob 4096 gen  8 11:23 2.3.11/
 drwxr-xr-x  7 marcob marcob 4096 apr 17  2018 3.2.13/
 drwxr-xr-x  7 marcob marcob 4096 lug 11  2019 4.0.0/
 drwxr-xr-x  7 marcob marcob 4096 ott 25 20:07 4.0.5/
 lrwxrwxrwx  1 marcob marcob    7 gen  8 11:24 current -> 4.0.5/

È sufficiente pertanto modificare il puntamento:

$ unlink ~/.sdkman/candidates/grails/current
$ ln -s ~/.sdkman/candidates/grails/2.3.11 current

A questo punto la versione era quella desiderata:

$ grails -version
 Grails version: 2.3.11

Oracle driver non trovato

La compilazione si arrestava dando questo errore

ClassNotFoundException: oracle.jdbc.driver.OracleDriver

Il driver Oracle dev’essere infatti disponibile nella directory del progetto.

$ echo $ORACLE_HOME 
 /usr/lib/oracle/12.2/client64
$ cd <PROJECT_HOME>/lib
$ cp $ORACLE_HOME/lib/ojdbc8.jar .

È forse più economico linkare il file invece di copiarlo:

$ ln -s $ORACLE_HOME/lib/ojdbc8.jar

Poi occorre aggiungere il path del client anche alle variabili di ambiente; il modo più robusto per farlo è modificare il file ~/.bashrc

$ nano ~/.bashrc
...
export ORACLE_HOME=/usr/lib/oracle/12.2/client64
export PATH=$PATH:$ORACLE_HOME/lib
...

Occorre poi rileggere il file per poter caricare le variabili

$ source ~/.bashrc

A questo punto la mia IDE (Intellij IDEA) compila che è una meraviglia.

Fonti

Altri articoli su Groovy on Grails

Incontri spaziali: la congiunzione tra Giove e Saturno (21/12/2020)

Incontri spaziali. Sembra che Giove e Saturno siano prossimi a scontrarsi ma naturalmente è solo una questione di prospettiva: è la loro proiezione sulla volta celeste a farlo.

Quello che accade realmente è che siamo all’incirca tutti e tre, noi, Giove e Saturno, lungo un’unica linea retta di congiunzione.

Incontri spaziali: Giove e Saturno si incontrano sulla sfera celeste sotto un angolo di 1/5 del disco lunare
Giove e Saturno si incontrano sulla sfera celeste sotto un angolo di 1/5 del disco lunare

Non si tratta in realtà di un allineamento perfetto; più precisamente vediamo entrambi i pianeti sotto un angolo solido pari a circa di 1/5 del disco lunare.

Per esempio questa elaborazione della Nasa mostra cosa si intenda per allineamento tra pianeti. Quello qui rappresentato è la sovrapposizione tra Venere e Giove avvenuta il 30 giugno 2015

Allineamento tra Terra, Venere e Giove del 30 giugno 2015
Allineamento tra Terra, Venere e Giove del 30 giugno 2015

Questo delle congiunzioni o allineamenti è un evento molto raro sulla scala di una vita umana. L’ultima volta che Giove e Saturno si trovarono così vicini sulla volta celeste successe 4 marzo 1226. In quell’anno morirono S. Francesco d’Assisi e Federico II di Hohenstaufen.

Non per la congiunzione astrale, beninteso, è solo una coincidenza!

Anche un’altra volta avvenne, il 16 luglio 1623, però da terra non si vide nulla perché la congiunzione avvenne vicino al disco solare, verso il tramonto.

La congiunzione tra Giove e Saturno in realtà avviene molto più spesso, circa ogni 20 anni, ma non sempre entro un angolo così piccolo e in condizione di oscurità favorevole. Una lista delle congiunzioni tra i due pianeti, nel passato e nel futuro si trova in questa risorsa. Trovate tutte le congiunzioni, più o meno notevoli, dei due pianti dal 1206 al 2398.

Appunto, l’ultima volta i cui entrambe queste condizioni si verificarono, fu nel 1226.

C’è da dire che questa è la prima volta in assoluto che noi umani possiamo goderci lo spettacolo così da vicino, grazie alla Scienza e alla Tecnologia.

Qui trovate l’evento raccontato su The Virtual Telescope Project da Gianluca Masi.

Fonti

PKIX: errore nel creare un’applicazione Grails.

La non disponibiità di un certificato SSL è all'origine del "problema PXIX"
La non disponibiità di un certificato SSL è all’origine del “problema PXIX”

Voglio avvisarvi subito che non esiste in sé un “errore PKIX”. È soltanto un modo breve di descrivere una situazione in cui c’è un problema del client Grails (o a qualunque altro client) ad accedere ad un URL protetto da SSL.

Se vi capita di vedere questa sigla PKIX in un messaggio errore di Grails, si fa riferimento alla Public Key Infrastructure X.509, uno standard usato per definire il formato dei certificati a chiave pubblica (PKC) e delle autorità di certificazione (CA).

Significa che è sorto qualche problema nello stabilire la connessione cifrata SSL con lo storage da cui il client scarica il template dell’applicazione.

Mi è capitato in più di un contesto questo problema (ad esempio nella configurazione di un reverse proxy Apache), però il significato di un simile tipo di diagnostica risulta abbastanza chiaro nello specifico esempio che vi propongo.

Questo problema sorge subito quando si vuole avviare il client Grails:

$ grails

“PKIX path building failed” and “unable to find valid certification path to requested target”

Ciò che succede è che insorge un problema di sicurezza perché non ho tra i certificati SSL installati nella JVM quello di grails.org da cui il client cerca di scaricare il boilerplate dell’applicazione. Da qui l’errore PKIX.

La soluzione consiste nello scaricare e installare manualmente il certificato dal sito con Chrome o Firefox, e importarlo nel keystore della JVM

Passaggio 1

Scaricare il certificato: puntare il browser all’URL

https://repo.grails.org/grails/core

Una volta stabilita la connessione e visulizzato il sito, cliccare sul lucchetto a sinistra nella barra degli indirizzi; cliccare quindi su “Certificato”, su “Dettagli”, su “Esporta” e fare il download in una cartella che conosciamo, ad esempio Downloads. Questo per Chrome.

Per Firefox: cliccare sul lucchetto, quindi su “Connessione Sicura >”, su “Ulteriori informazioni”, su “Visualizza certificato”; quindi sulla tab repo.grails.org cliccare sul link “PEM (certficato) e selezionare l’opzione “Salva con nome”. Un po’ più laborioso.

Passaggio 2

Importare il certificato appena scaricato nella keystore della JVM: individuare dunque la directory della JVM, nel mio caso:

/usr/lib/java/jdk1.8.0_25/jre/lib/security

Il file dei certificati è cacerts

Lanciare l’importazione

$ sudo keytool -import -alias example 
   -keystore /usr/lib/java/jdk1.8.0_25/jre/lib/security/cacerts 
   -file /home/marcob/Downloads/repo.grails.org 

Keytools richiede la password del keystore che al primo utilizzo è changeit

[sudo] password di marcob: 
Immettere la password del keystore:  

Proprietario: CN=repo.grails.org
Emittente: CN=Sectigo RSA Domain Validation Secure Server CA, O=Sectigo Limited, L=Salford, ST=Greater Manchester, C=GB
Numero di serie: 2560fb52bfdc8f6bb2b3366805e3c911
Valido da: Fri Nov 13 01:00:00 CET 2020 a: Wed Dec 15 00:59:59 CET 2021
Impronte digitali certificato:
	 SHA1: D6:24:89:09:18:F1:39:E8:D6:6F:DF:EA:88:68:5C:BD:DB:2C:DF:F3
	 SHA256: 65:39:9F:7D:C3:09:D5:58:ED:E3:C3:82:59:98:BA:22:E2:8F:2D:65:6D:1E:F6:C3:51:55:9B:4F:14:9C:ED:A4
Nome algoritmo firma: SHA256withRSA
Algoritmo di chiave pubblica oggetto: Chiave RSA a 2048 bit
Versione: 3
...
Considerare sicuro questo certificato? [no]:  si
Il certificato è stato aggiunto al keystore

Una volta completato l’nserimento del certificato nel portachiavi si può avviare Grails dalla cartella del progetto:

$ grails create-app com.djamware.gadgethouse
| Application created at /home/marcob/IdeaProjects/java/gadgethouse
| Resolving Dependencies. Please wait…
| Starting interactive mode…
| Enter a command name to run. Use TAB for completion:
grails> run-app
| Running application…
...
Grails application running at http://localhost:8080 in environment: development

Fine.

PS: ho preso l’esempio da questo sito qui

Grails in pillole – come superare i problemi che sorgono con database già fatti

Grails Logo
Grails

In questo articolo parlerò dei problemi che si incontrano nel refactoring di una applicazione. Più precisamente di quanto espresso dal titolo quindi:

Come gestire l’ORM (Object Relationship Mapping) su tabelle già definite.

Devo riscrivere un’applicazione PHP in Groovy on Grails ma il database non deve essere toccato come specifica di progetto.

Le pratiche di convention over configuration di molti framweork MVC (come Groovy on Grails, Laravel o Django) permettono di creare il database semplicemente dalla definizione delle classi, senza scrivere alcuna istruzione DDL.

Tuttavia se il Cliente non vuole che si modifichi il database, abbiamo una serie di strumenti messi a disposizone dal layer di gestione della mappatura tra oggetti e tabelle adottato da Grails (Hibernate) che ci permettono di uscire dallo standard per adeguarci all’esistente.

Importante: se non si vuole sovrascrivere il database, nel file grails-app/conf/dataSource.groovy va impostato il parametro dbCreate come “update“, non come “create-drop” (mi raccomando).

Se c’è già un campo chiave primaria

Ogni tabella ha come standard la colonna ID che di solito è un number(10) (sto usando Oracle). Se nella definizione della classe aggiungiamo la proprietà id, Hibernate mapperà il campo id nella colonna ID senza protestare.

Il campo version

Hibernate aggiunge per ogni tabella anche il campo version per implementare la strategia di “optimistic locking”. Cioè si vuole gestire una storia del record per cui viene di fatto creata una chiave composita che si riferisce ad un particolare record attraverso la fornitura sia dell’id che del numero di versione. In realtà in molte applicazioni a cui ho lavorato questo tipo di meccanismo è delegato al DBMS, nel quale (soprattutto nei database Oracle che ho gestito) viene scritto un trigger che crea un “giornale” della tabella con dentro tutte le oprazioni fatte sul record: dal primo inserimento, alle succesive modifiche fino all’eventuale cancellazione fisica. Per cui, in questo contesto ho ritenuto sovrabbondante l’introduzione dell’oprimistic locking e ho optato per la sua soppressione.

Per evitare di gestire una proprietà version si deve dichiare che non la si vuole nel dictionary mapping dentro alla dichiarazione del domain (il corrispondente Grails del model nel paradiga MVC):

package myapp
class Utente
    ...
    static mapping = {
        version false
    }

Il campo version non verrà aggiunto alla tabella.

Il nome della tabella non segue uno standard

I nomi delle classi domain e nelle tabelle prassi abbastanza diffusa sceglierli in inglese, al singolare; però io ho una tabella Utenti per cui faccio un compromesso:

package myapp
class Utente
    ...
    static mapping = {
        table "utenti"
        version false
    }

Specificando nel dictionary statico mapping il campo table posso scegliere di associare il nome della tabella del database “intoccabile”.

Relazioni uno a molti

Come esempio ho un tabella articoli che è figlia di una tabella capitoli: ad un capitolo possono essere associati più articoli. Ho di fatto una serie di articoli che appartengono ad un capitolo per cui si usa la variabile belongsTo per specifcare questa relazione:

package myapp

class Articolo {
    Integer id
    Float prezzo
    String descrizione
    Integer attivo

    static belongsTo = [capitolo: Capitolo]

In questa dichiarazione, il nome a sinistra del : è il nome della relazione (capitolo in minuscolo), e il nome a destra è il nome dell’oggetto padre (Capitolo).

Il nome delle chiavi esterne non segue uno standard

O, meglio, ne segue uno di suo che non è quello di Grails. Nella tabella Articoli per esempio ho una chiave esterna per realizzare la relazione con i Capitoli: idcapitolo che si riferisce all’id della tabella Capitoli.

Seguendo il suo standard, Hibernate crea il nome <tabella>Id per dichiarare le variabili “chiave”. Sfortunamtamente all’epoca nel database hanno seguito la convenzione inversa IDCAPITOLO (con l’id prima del nome tabella).

Mai paura: si dichiara questa deviazione dallo standard nella creazione del domain:

package myapp

class Articolo {
    Integer id
    Float prezzo
    String descrizione
    Integer attivo

    static belongsTo = [capitolo: Capitolo]
    static mapping = {
        table "articoli"
        capitolo column: "idcapitolo" // <-----
        version false
    }

Si faccia attenzione che per mappare il nome della colonna della tabella database ho utilizzato il nome della relazione definita in belongsTo: non capitoloId (che non è definito da nessuna parte) ma capitolo.

Una tabella ha una chiave primaria composita

Come ultimo caso c’è quello di una tabella con una doppia chiave composta dall’unione di due campi idutente e iddipartimento (dovendo gestire il fatto che un utente può lavorare anche in dipartimenti diversi). È una strategia per gestire una relazione molti-a-molti. Di suo Hibernate vorrebbe che io aggiungessi una chiave artificiale ID ma la consegna data me lo impedisce, posso dichiarare la chiave composita e sopprimere la direttiva di creazione dell’ID:

package myapp

class UtenteDipartimento implements Serializable {
    Integer attivo
    Date dataInizio
    Date dataFine

    static belongsTo = [utente: Utente, dipartimento: Dipartimento]
    static mapping = {
        table "utenti_dipartimenti"
        dataInizio column: "data_inizio"
        dataFine column: "data_fine"
        // relazioni
        utente column: "idutente"
        dipartimento column: "iddipartimento"
        id composite: ['utente', 'dipartimento']

        version false
    }

nel quale sorgente riassumo un po’ tutte le cose dette nell’articolo.

Attenzione: se voglio creare una chiave composita, la classe deve implementare l’interfaccia Serializable.

Risorse

Grails Framework home page

Oracle: alcuni tips utili

Oracle Logo

Oracle tips: in questo post troverete alcuni consigli utili per gestire situazioni anomale che, quindi, normamente non si dovrebbero verificare: la password di system è scaduta oppure Sql*Plus non si avvia.
Aggiungo anche un suggerimento su come creare un nuovo schema e degli utenti di supporto per limitare le operazioni sul database da applicazione.

Problem 1: password di system scaduta

Il problema si presenta tipicamente quando non si accede da molto ad un database. È un’eventualità abbastanza remota se si lavora regolarmente sui sistemi. Nel mio caso avevo un vecchio DBMS Oracle di sviluppo in una macchina Windows 10 che non usavo da un po’ ed era scaduta anche la password di system.

Una semplice procedura consente di risolvere il problema. Da linea di comando si digiti

$ sqlplus system
SQL*Plus: Release 11.2.0.2.0 Production on Mer Nov 25 11:57:12 2020
...
Enter password:
ERROR:
ORA-28001: the password has expired
Changing password for system
New password:
Retype new password:
Password changed

Stessa procedura per qualsiasi altro utente. In sostanza Oracle avvia automaticamente la procedura di cambio password nel caso sia scaduta. È chiaro che il ststema è bene non venga lasciato inattivo per troppo tempo, perché chiunque potrebbe impossessarsi del DBMS con questa procedura!

Problem 2: indisponibilità di SQL*Plus

Nel tentativo di risolvere il problema sopra, ho cozzato contro un altro problema che veniva prima in ordine di priorità, ossia non riuscivo nemmeno ad avviare SQL*Plus, il tool di amministrazione/sviluppo di Oracle da linea di comando.

SQL*Plus è uno strumento molto grezzo ma è potentissimo, consente di lanciare ogni tio di comando di amministrazione (DDL) ed elaborazione dati (SQL).

Generalmente si preferisce utilizzare tool con una GUI (Graphic User Interface) come TOAD o Oracle SQL Developer ma questo tool è davvero equivalente, se non più potente.

Nel mio caso l’errore che si verificava all’avvio del comando era il seguente:

C:\> sqlplus
PSP2-0667: Message file sp1<lang>.msb not found

Il problema all’origine di questo messaggio è la non corretta valorizzazione della variabile di ambiente ORACLE_HOME, ho impostato (nel mio caso) al valore:

ORACLE_HOME=C:\oraclexe\app\oracle\product\11.2.0\server\

Problema rientrato.

Tip 1: come creare uno schema

Ecco invece una breve lista di comandi per creare uno schema (uno schema è un utente Oracle proprietario di oggetti) e utilizzarlo per un’applicazione web (da sviluppare con qualsiasi tecnologia).

  1. CREATE USER rewards IDENTIFIED BY smartPassw0rd;
  2. GRANT CONNECT, RESOURCE TO rewards;
  3. GRANT CREATE SESSION TO rewards;
  4. CREATE USER rewards_rw IDENTIFIED BY P4ssw0rd;

Una volta creati gli oggetti, ad esempio creata una una tabella books, devo stabilire cosa l’utente può fare in questa tabella; devo concedere (GRANT) dei privilegi all’utente sulla tabella, andando dalla sua modifica, alla distruzione, alla gestione dei dati. Per questo utente mi limito alle sole operazioni CRUD:

  1. GRANT SELECT, INSERT, UPDATE, DELETE ON rewards.books TO rewards_rw;

È buona prassi non dare i massimi privilegi sul database all’utente utilizzato dall’applicativo per connettersi a Oracle mediante il client. In genere è sufficiente un profilo abilitato alle sole operazioni CRUD, come realizzato dal comando sopra.

Si evita così la possibilità che in modo malevolo (sempre che l’applicazione lo consenta – e lo consente se non è progettata abbastanza bene da inibire comandi SQL – la cosiddetta SQL injection) un attaccante, per mezzo dell’applicazione, lanci istruzione SQL verso il server Oracle.

CRUD

Un’ultima considerazione: CRUD è l’elenco delle operazioni su database definite però in un ambito più genarale e astratto di storage. In pratica queste quattro operazioni (4 verbi) sono richieste e possibili anche in un file system, per esempio: creazione, lettura, scrittura, cancellazione di file, e non è inteso specificatamente per un DBMS. Tuttavia nel linguaggio SQL le quattro operazioni prendono dei nomi specifici:

  • Create → Insert
  • Read → Select
  • Update → Update
  • Delete → Delete

Links utili

Oracle Database Administration Guide

Mysql Workbench su Ubuntu 20.04

Mysql Workbench su Ubuntu 20.04
Mysql Workbench su Ubuntu 20.04

Ho reinstallato Mysql Workbench versione Community, un tool per l’amministrazione e la gestione di database basati su DBMS MySQL/MariaDB, su Ubuntu 20.04.

Al tentativo di creare una nuova connessione mi sono ritrovato un errore mai comparso prima:

An AppArmor policy prevents this sender from sending this message to this recipient; type="method_call", sender=":1.125"

eccetera.

Il problema (spiegato qui) è che ho dovuto installare MySQL con snap anziché apt, per cui mi sono scontrato con il fatto che i pacchetti installati con snap sono sandboxed, in qualche modo isolati e non possono fare alcune richieste al sistema. Ho dovuto quindi applicare questo comando:

$ sudo snap connect mysql-workbench-community:password-manager-service :password-manager-service

Ora funziona:

Sul modulo kernel Linux Apparmor ho già dedicato questo articolo.

Se vi è servito, lasciatemi un like

Matematica: la congettura di Collatz

È una congettura, cioè una proprietà che si presume vera ma non si è in grado di dimostrarlo rigorosamente. Le congetture affascinano perché funzionano per tutti i tentativi che si fanno per verificarla e la loro non dimostrabilità le avvolge di mistero.

In generale non è dimostrato che non si possono dimostrare ma come disse Paul Erdős, matematico ungherese, a proposito della congettura di Collatz

“La matematica non è ancora pronta per risolvere problemi del genere”

Paul Erdős

La congettura

La congettura di Collatz afferma che la successione così definita:

	x_{n+1} = 
	\begin{cases}
	3x_n + 1, & x_n \mod 2 = 0 \\
	x_n/2 & x_n \mod 2 = 1
	\end{cases}

converge sempre a 1 per n \ge N \in \mathbb{N} . Ricordo che x_n \mod 2 = 0 vuol dire che x_n è pari e x_n \mod 2 = 1 vuol dire che x_n è dispari.

Python: la libreria matplotib

Ora per divertirmi un po’ a vedere come funziona questa successione ho utilizzato Python e la libreria matplotlib che avevo già utilizzato per lo studio dell’apprendimento delle reti neurali.

È semplice installarla come libreria stand-alone per Python 3:

$ git clone git@github.com:matplotlib/matplotlib.git
$ cd matplotlib/
$ python3 setup.py install

Sorgente Python

Dopodiché la uso in un programma che semplicemente calcola la successione e la visualizzaz u un grafico

!/usr/bin/python3
import matplotlib.pyplot as plt

a = input("Dimmi un numero: ")
print("Successione di Collatz a partire da a=",a)
z = int(a)
s = []
s.append(z)

while (z != 1):
   print(z,", ")
   if (z % 2 == 0):
      z /= 2
   else:
      z = 3*z + 1
      
   s.append(z)
# la congettura d Collatz afferma che si esce sempre dal ciclo

# disegna
x = range(len(s))
plt.plot(x, s, label="Successione di Collatz")
plt.xlabel('iterazioni')
plt.ylabel('successione')
plt.title('Congettura di Collatz')
plt.legend() 
plt.grid(True)
plt.show()

Ci si può divertire a vedere il comportamento di questa succcessione che sembra davvero imprevedibile per numeri che non fanno parte della successsione delle potenze di 2. Ad esempio il 103 genera questo andamento

Matematica: Congettura di Collatz: un esempio di successione per punto iniziale x1=103.
Matematica: Successione per x_1= 103

Wireless: come visualizzare la potenza del segnale

Utilizzo il mio smartphone come hotspot wireless per connettermi a Internet con i dispositivi di casa. Stamattina ho voluto controllare quale fosse la potenza del segnale perché volevo trovare la posizione ottimale dell’hotspot rispetto alla mia postazione di lavoro.

Prima mi sono studiato come accedere al dato della potenza del segnale in ingresso dell’antenna (dettagli qui) e poi ho riportato questo dato in funzione del tempo in un grafico animato in modo tale da poter girar per casa e posizionarlo dove ho un segnale più forte, e non è detto che sia accanto al PC 😉

Campionamento della potenza del segnale wireless

La potenza istantanea del segnale si può visualizzare con il comando

$ iwconfig wlp2s0
wlp2s0 IEEE 802.11 ESSID:"HUAWEI Y6 2019"
Mode:Managed Frequency:2.412 GHz Access Point: F2:E4:A2:93:8C:0C
Bit Rate=72.2 Mb/s Tx-Power=22 dBm
Retry short limit:7 RTS thr:off Fragment thr:off
Power Management:on
Link Quality=61/70 Signal level=-49 dBm
Rx invalid nwid:0 Rx invalid crypt:0 Rx invalid frag:0
Tx excessive retries:171 Invalid misc:9250 Missed beacon:0

Come si vede, la linea

Link Quality=61/70 Signal level=-49 dBm

contiene l’informazione desiderata. Se spegniamo l’hotspot l’output del comando è invece:

$ iwconfig wlp2s0
wlp2s0 IEEE 802.11 ESSID:off/any
Mode:Managed Access Point: Not-Associated Tx-Power=22 dBm
Retry short limit:7 RTS thr:off Fragment thr:off
Power Management:on

Quindi un modo se volete un po’ grezzo di accedere velocemente alla potenza del segnale è fare il parsing dell’output di questo comando. L’idea è di lanciarlo con regolarità in modo da costruire un campione di dati da visualizzare in qualche modo in tempo reale.

Il primo programma che ho scritto è questo:

#!/usr/bin/python3
#
# Found in: https://www.calazan.com/how-to-continuously-monitor-your-wi-fis-signal-strength-in-ubuntu/
# Mutatis mutandi
#

import subprocess
import time
import argparse

parser = argparse.ArgumentParser(description='Display WLAN signal strength.')
parser.add_argument(dest='interface', nargs='?', default='wlan0',
                    help='wlan interface (default: wlan0)')
args = parser.parse_args()

print('\n---Press CTRL+Z or CTRL+C to exit---\n')

while True:
    cmd = subprocess.Popen('iwconfig %s' % args.interface, shell=True,
                           stdout=subprocess.PIPE)
                           
    for line in cmd.stdout:
        l = str(line)
        if 'Link Quality' in l:
            print (l),
        elif 'Not-Associated' in l:
            print ('No signal')
    time.sleep(1)

Aspetti importanti di questo script sono il package subprocess che consente a Python di lanciare un comando del sistema operativo, in questo caso

iwconfig wlp2s0

Il package argparser fornisce una serie di utilissime funzioni per maneggiare le linee di comando (definire i parametri di avvio, la loro obbligatorietà, i valori di default e finanche la documentazione di help).

Il package itertools fornisce il metodo count() che realizza un contatore progressivo che ci serve per etichettare i campioni di segnale, campioni che vengono prelevati con regolarità utilizzando le funzione del package time..

In sostanza il metodo subprocess.Popen() lancia il comando e il dato desiderato si trova nell’output del comando che comunque non è una stringa e va convertito per potere fare il parsing. Il parsing si utilizza quando il dato è annegato nella sporcizia, per cui ritengo questo mio metodo un metodo piuttosto sporco.

In questo embrione di programma ho il seguente ouput

—Press CTRL+Z or CTRL+C to stop.—

b' Link Quality=70/70 Signal level=-31 dBm \n'
b' Link Quality=70/70 Signal level=-31 dBm \n'
b' Link Quality=70/70 Signal level=-36 dBm \n'
b' Link Quality=70/70 Signal level=-32 dBm \n'
b' Link Quality=70/70 Signal level=-32 dBm \n'

Il dato interessante si trova in ogni riga dalla posizione 45 alla posizione 48.

Con una piccola modifica ci facciamo stampare solo il dato desiderato

--Press CTRL+Z or CTRL+C to stop.---
-32
-32
-35
-31
-31
-35
-35
-35
-36

Visualizzazione del segnale wireless utilizzando matplotlib

Ora invece di stampare questi valori, li voglio disporre su un grafico. Utilizzo la libreria matplotlib. Il sorgente completo è il seguente

#!/usr/bin/python3
#
# matplotlib @ https://youtu.be/Ercd-Ip5PfQ
# subprocess @ https://bit.ly/3041a3m
# Adopted for reading wifi power signal and plotting it
#
# @author: marco@betaingegneria.it
# @copyright: GPL Affero v.3
# @date 2020-09-25

import subprocess
import argparse
import matplotlib.pyplot as plt
from itertools import count
from matplotlib.animation import FuncAnimation

# init graphics stuff
plt.style.use('seaborn')

x=[] # time series
y=[] # power samples
index = count()

# init wlan signal reader
# parse line command arguments
parser = argparse.ArgumentParser(description='Display WLAN signal strength.')

# interface
parser.add_argument(dest='interface', nargs='?', default='wlp2s0',
                    help='wireless.py [interface=wlp2s0] [delta=800]')

# sampling period in ms
parser.add_argument(dest='delta', nargs='?', default='800',
                    help='wireless.py [interface=wlp2s0] [delta=800]')
                    
args = parser.parse_args()

if (int(args.delta) < 200):
    print('Please, choose a value >= 200 for delta')
    exit()

print('Close the window to exit')

def animate(i):
    """ read the wifi power in dBm and plot """
    x.append(next(index))
    
    cmd = subprocess.Popen('iwconfig %s' % args.interface, shell=True,
                       stdout=subprocess.PIPE)
                       
    for line in cmd.stdout:
        l = str(line)
        if 'Link Quality' in l:
            y.append(int(l[45:48]))
        elif 'Not-Associated' in l:
            y.append(None)

    plt.cla()
    plt.plot(x, y, '-', marker='.', label='WiFi signal strenght in dBm')
    plt.legend(loc='upper left')
    plt.tight_layout()
    
    # ring a bell every sample
    #print('\a')

ani = FuncAnimation(plt.gcf(), animate, interval=args.delta)    

plt.show()

Un po’ di parafrasi: la funzione animate() contiene il codice che legge la potenza e, eseguendo il parsing estraiamo il valore in dBm da accodare alla lista y, mentre nella lista x accodiamo i valori del contatore.

Il plot viene fatto utilizzando come skin lo stile seaborn, che comprende la griglia in negativo (bianco su grigio).

Infine ho utilizzato il metodo matplotlib.animation() che può essere configurato per un aggiornamento ogni delta millisecondi – ci sganciamo quindi dalla libreria time – , variabile che gestiamo come parametro di input.

Il risultato è questo

Andamento della potenza segnale wireless (max -30dB) provando a spostare l'hotspot in giro per la stanza
Andamento della potenza segnale wireless provando a spostare l’hotspot in giro per la stanza

Se si spegne l’hotspot o si esce dal suo range di segnale non verrò plottato alcun dato.