[NoSQL] Implementare un Document Store NoSQL con Oracle 12c e le SODA API

Avete intenzione di implementare una schemaless application, memorizzando le informazioni in modo “dinamico” e flessibile, secondo il cosiddetto paradigma “NoSQL style document store”?

Anche Oracle DB, a partire dalla versione 12.1.0.2, fornisce il supporto per la memorizzazione, l’indicizzazione e la ricerca di documenti in formato JSON.

Oracle12c

Più dettagliatamente, Oracle DB 12c permette, senza necessità di installare plugin aggiuntivi, l’implementazione di un Document Store e fornisce il seguente set di API, progettate per garantire il supporto allo sviluppo di schemaless application:

  • SODA for Java: interfaccia di programmazione document-store per sviluppatori Java che usano JDBC per comunicare con il database. SODA for Java consiste di un set di semplici classi che rappresentano il database, una collezione di documenti e un documento. I metodi di queste classi forniscono tutte le funzionalità richieste per gestire ed interrogare documenti e collezioni di documenti memorizzati in un database Oracle;
  • SODA for REST: interfaccia REST-based document-store implementata come Java servlet e distribuita come parte dell’Oracle REST Data Services (ORDS) 3.0. Le applicazioni basate su SODA for REST usano il protocollo HTTP per comunicare con la Java Servlet. SODA for REST Servlet può anche essere eseguito su un HTTP Server nativo del database (esistono versioni “bundle” di Web Server, come TomCat e JBoss opportunamente configurati per accedere al DB Oracle tramite API SODA). I metodi HTTP come PUT, POST, GET e DELETE mappano le operazioni eseguite sui documenti JSON. Fornendo API di tipo REST è possibile integrare la soluzione con web application esterne per esporre i dati memorizzati nella base dati Oracle.

Riferimento: http://www.oracle.com/technetwork/database/appdev-with-soda-2606220.pdf

Modello relazionale VS. Modello No-SQL. Volendo comparare un database relazionale con un DB NoSQL “document-store”, è possibile dire che:

  • Una collezione di documenti è una tabella
  • Un documento è una riga di una tabella
  • Un campo del documento è una colonna della tabella

I documenti in formato JSON vengono memorizzati con un ID univoco all’interno di una collezione. Per ciascun documento è possibile recuperare metadati, come data di creazione, dati di aggiornamento, owner e versione del documento, ecc.

Le funzionalità offerte da un document store includono:

  • Creazione e cancellazione di una collezione
  • Creazione, ricerca, aggiornamento o cancellazione di un singolo documento in base al suo ID
  • Recupero dei documenti in una collezione
  • Ricerca di una collezione, tipicamente utilizzando Query By Example (QBE) metaphor
  • Creazione e cancellazione di indici

Dato questo semplice livello di funzionalità fornito da un document store, l’API diventa semplice, particolarmente quando comparato con le tradizionali API SQL-based come JDBC.

Il DBMS Oracle già forniva dalla versione 9 il supporto alla memorizzazione, alla ricerca e all’indicizzazione di documenti XML. Oracle Database 12c estende tale funzionalità ai documenti JSON, introducendo le due implementazioni dell’interfaccia SODA, denominate, come suddetto, SODA for REST e SODA for JAVA, e ponendosi sul mercato come valida alternativa tra i NoSQL-style Document Store.

Oracle NoSQL-style Document Store Capabilities. In Oracle DB 12c, i documenti vengono memorizzati, indicizzati e ricercati senza che il database ne conosca la struttura (schemaless). Ciò lascia agli sviluppatori la libertà di modificare la struttura dei documenti JSON in base alle esigenze. Non esiste un datatype dedicato per memorizzare i documenti JSON, ma gli stessi vengono memorizzati con i tipi standard VARCHAR2, CLOB e BLOB. Viene introdotto il nuovo constraint “IS JSON”, utilizzato per assicurare che il contenuto di una colonna sia un JSON valido, fornendo pieno supporto al trattamento avanzato dei JSON, come disaster recovery, replication, compression ed encryption.

Inoltre, è possibile eseguire delle query SQL direttamente sulle tabelle di documenti JSON del database utilizzando le JSON Path Expressions. Tali espressioni sono equivalenti al linguaggio xPath in XML e sono sintatticamente simili a JavaScript. Si riportano di seguito degli esempi:

JsonPathExpressions.png

SODA API. SODA fornisce un set di API semplice da utilizzare per lavorare con i documenti memorizzati in un Oracle Database. L’oggetto Database, che è richiesto per interagire con le Collections, viene istanziato usando un database connection con le API SQL standard di Oracle. La versione corrente di SODA adotta una strategia di optimistic locking, ma quella di pessimistic locking èsarà probabilmente disponibile nelle future release.

La specifica SODA definisce un set di metodi che forniscono le seguenti funzionalità:

  • Stabilire una connessione ad un Oracle Database Document Store
  • Creare e cancellare una collezione
  • Creare, ricerca, aggiornare e cancellare un documento
  • Elencare i contenuti di una collezione
  • Ricercare una collezione di documenti che “matchino” una espressione Query By Example (QBE)
  • Operazioni di “bulk insert” in una collezione
  • Creazione e cancellazione di indici

Di seguito, riporto alcune caratteristiche dell’implementazione “SODA for JAVA”, tralasciando “SODA for REST” (utile nel caso ci si voglia interfacciare direttamente con il Document Store con il paradigma REST).

SODA for JAVA. Consiste di un set di semplici classi che rappresentano un database, una collezione di documenti e il documento stesso. I metodi che queste classi forniscono permettono di gestire e ricercare le collezioni e i documenti JSON memorizzati. Utilizza una connessione JDBC standard e SQL*NET per comunicare con il database: ciò significa che le API sono transazionali e una serie di operazioni SODA può generare una singola transazione. Poiché SODA utilizza una connessione JDBC, è possibile utilizzare sia le API di SODA for JAVA che quelle tradizionali JDBC.

Di seguito, si riportano le principali classi di “SODA for JAVA” con relativa descrizione:

Classe Descrizione Commenti
OracleClient Classe client generica SODA. L’entry point di SODA per i JSON.
OracleRDBMSClient La classe Client dell’Oracle Database Usata per recuperare l’oggetto OracleDatabase
OracleDatabase Rappresenta un Document Store, il quale consiste di uno o più collezioni. Usato per accedere alle collezioni.
OracleDatabaseAdmin Usato per creare e cancellare collezioni
OracleCollection Rappresenta una collezione di un Document Store
OracleCollectionAdmin Usato per creare e cancellare indici
OracleDocument Rappresenta un documento in un Document Store Aggiorna (o crea) il documento con un dato ID

Struttura di un documento di una collezione. Di seguito, si riporta la struttura SQL di una collezione rappresentata su una tabella Oracle e contenente il JSON in corrispondenza di una colonna CLOB:

Name                                             Null?   Type

—————————————– ——– —————————-

ID                                              NOT NULL VARCHAR2(255)

CREATED_ON                                      NOT NULL TIMESTAMP(6)

LAST_MODIFIED                                   NOT NULL TIMESTAMP(6)

VERSION                                         NOT NULL VARCHAR2(255)

JSON_DOCUMENT                                     CLOB

Le colonne della tabella rappresentano quanto segue:

 ID ID autogenerato del singolo record
 JSON_DOCUMENT Contenuto del documento in JSON
 CREATED_ON Timestamp (autogenerato) di inserimento del record
 LAST_MODIFIED Timestamp (autogenerato) di modifica del record
 VERSION Versione del documento (incrementato automaticamente quando viene modificato)

Per dettagli sulle API di SODA e sulla potenza espressiva delle Query By Example per la ricerca dei documenti di una collezione: http://docs.oracle.com/cd/E63251_01/doc.12/e58124/soda.htm#ADSDA107

Memorizzazione dei documenti JSON (codifica e datatype). Un documento JSON può essere considerato un dato semi-strutturato, ossia non conforme alla struttura formale dei modelli di dato associato con le basi di dati relazionali. Esso, comunque, contiene etichette o altri marcatori per separare gli elementi semantici e rafforzare le gerarchie di record e campi all’interno del dato. E’ anche conosciuto come “dato senza schema” o “dato con struttura autodescritta”.

Oracle raccomanda di memorizzare tali dati utilizzando datatype di tipo LOB, in quanto la quantità di caratteri può essere elevata (maggiore di 4000 byte e, dunque, della massima capacità di un datatype VARCHAR2). I datatype raccomandati per i contenuti testuali sono Characted Large Object (CLOB) e National Character Large Object (NCLOB).

Il datatype CLOB è raccomandato per la memorizzazione di stringhe o documenti di lunghezza fissa. Invece, il datatype NCLOB è raccomandato per quelli a lunghezza variabile.

Riferimento: https://docs.oracle.com/database/121/ADXDB/json.htm#ADXDB6252

Per quanto riguarda il character encoding, conviene adottare quello AL16UTF16 o AL32UTF8. In particolare, Oracle raccomanda l’uso di AL32UTF8 per memorizzare i dati con caratteri Unicode.

Riferimenti:
https://docs.oracle.com/cd/E11882_01/appdev.112/e18294.pdf
https://docs.oracle.com/database/121/NLSPG/ch2charset.htm#NLSPG179

Sicurezza dei dati. Al fine di salvaguardare la sicurezza e l’integrità dei dati,è possibile sfruttare il meccanismo di Oracle Secure Files, il quale consente anche le compressione dei dati memorizzati all’interno di datatype LOB.

Riferimento: https://docs.oracle.com/database/121/ADLOB/toc.htm

[BigData] ELK Stack: ElasticSearch + Logstash + Kibana

ELK Stack

Nel presente articolo riporto alcune informazioni rilevanti relative all’ ELK Stack, set di tecnologie open-source più diffuse e utilizzate per l’implementazione di una soluzione di Log Management, costituito dai seguenti prodotti:

  • ElasticSearch: server di ricerca basato su Lucene, e, dunque, con capacità “full-text”, e con supporto ad architetture distribuite e su larga scala. Le modalità di interazione e di interrogazione con la base dati proprietaria (su file system) avvengono attraverso interfaccia RESTful. Le informazioni sono memorizzate internamente come documenti JSON; Riferimento: https://www.elastic.co
  • LogStash: progetto Open Source scritto in JRuby, distribuito in formato JAR, la cui funzione principale è quella di fare il pipe di un qualsiasi evento, che può essere un log di sistema, una riga di testo, un tweet, ecc. Logstash può interfacciarsi con numerosi input, elaborarli, filtrarli e passarli ad un motore di ricerca o memorizzazione, come MongoDB, Redis, ElasticSearch e molto altro, configurando (attraverso un opportuno file di configurazione) la pipeline per l’acquisizione, il filtering e l’invio dei dati. L’integrazione con ElasticSearch è quella più potente e veloce per l’implementazione di una soluzione di log managementRiferimento: https://www.elastic.co/products/logstash
  • Kibana: è un tool che permette di visualizzare, grazie a strumenti di data analytics, le informazioni indicizzate ed acquisite da ElasticSearch o altri prodotti. Permette la rappresentazione delle informazioni in tempo reale, attraverso dashboard configurabili con vari tipi di widget (pie chart, istogrammi, grafici cartesiani, ecc.). Riferimento: https://www.elastic.co/products/kibana

La soluzione ELK Stack permette di implementare differenti modelli architetturali di alta scalabilità. Per maggiori dettagli su LogStash, vi consiglio il seguente riferimento: LogStask Book. Si riportano, di seguito, alcuni dei modelli di riferimento dell’ELK stack.

Modelli di riferimento: soluzioni con ELK Stack

Soluzione di base
La soluzione di base proposta da ELK Stack prevede la configurazione di una singola istanza di LogStash in modo da acquisire i dati non strutturati da differenti sorgenti. E’ possibile configurare più istanze di LogStash (Agent Shipper), in modo da acquisire i dati da datasource differenti ed indirizzare le informazioni strutturate (documenti JSON) ad un’unica istanza di ElasticSearch. Quest’ultimo effettua l’indicizzazione e la memorizzazione dei documenti JSON acquisiti.

ELKStack_SoluzioneBase

Tramite un file di configurazione è possibile far puntare l’istanza agent di LogStash ad uno o più datasource (SysLog Server, File System, DBMS, ecc.), grazie all’utilizzo di vari input plugin.

Inoltre, è possibile configurare le destinazioni, a cui inviare i documenti JSON degli eventi acquisiti, utilizzando vari output plugin. Il parsing e il filtering degli eventi acquisiti dai vari datasource può essere configurato grazie a filter plugin.

ELKStack_SoluzioneBase

Di seguito, si riporta un esempio di trasformazione di un evento di log in formato SysLog (formato non strutturato) in un documento JSON (formato strutturato):

SysLog Message:

Dec 17 16:00:35 joker systemd-logind[2113]: New session 31581 of user bob.

JSON Log Event:

{
  "host" : "joker.example.com",
  "priority" : 13,
  "timestamp" : "Dec 17 16:00:35",
  "logsource" : "joker.example.com",
  "program" : "bob",
  "pid" : "23262",
  "severity" : 5,
  "facility" : 1,
  "facility_label" : "user-level",
  "severity_label" : "Notice",
  "@timestamp" : "2012-12-17T16:00:35.000Z",
  "@version" : "1",
  "message" : "New session 31581 of user bob",
  "type" : "syslog"
}

Soluzione con coda di messaggi
Quando i dati inviati alla pipeline di LogStash eccedono l’abilità del cluster di ElasticSearch di poterli prendere in input, conviene utilizzare un message queue come buffer. Prevedendo un message queue nell’architettura si garantisce un livello di protezione per evitare la perdita dei dati. In questo modo si riesce ad evitare la congestione dell’istanza Indexer, la quale “scoda” i messaggi sulla coda uno alla volta e in maniera asincrona.

ELKStack_SoluzioneCodaJMS

Soluzione ad alta affidabilità
ELKStack_SoluzioneAltaAffidabilità

La soluzione su rappresentata è quella più completa dal punto di vista dell’alta affidabilità. Grazie ad un bilanciatore è possibile instradare differenti datasource verso una istanza agent di LogStash attiva ed inviare il messaggio di evento sul message queue. In pratica, ogni istanza agent di LogStash viene configurata su input multipli e l’architettura può essere scalata orizzontalmente. Pipeline separate incrementano, dunque, la reliability del sistema ed eliminano i single points of failure.

Modello per la memorizzazione degli eventi
Nella soluzione con l’ELK Stack, il prodotto ElasticSearch usa Apache Lucene per la creazione degli indici. Ogni indice è un namespace logico che permette di recuperare tutti gli eventi collezionati nella base dati NOSQL di ElasticSearch. Di default, LogStash invia il documento JSON sull’indice che ha nel nome il suffisso del giorno di acquisizione dell’evento, ad esempio: logstash-2015.11.31.

Questo tipo di memorizzazione potrebbe essere preso come riferimento per collezionare le informazioni in tabelle/indici creati su base temporale (ad esempio, mensilmente). In questo modo si potrebbero salvare tutti gli eventi su base temporale, evitando di caricare di troppi record una singola tabella. Tale scelta dipende anche dai tipi di correlazione che occorrerà fornire per la Log Analysis.

Volendo comparare il modello di memorizzazione di ElasticSearch con quello di un database relazionale si ha che:

  • Un index è una tabella
  • Un document è una riga della tabella
  • Un field è una colonna della tabella.

 

Per maggiori dettagli vi rimando ai tutorial su Mokabyte:

 

Protetto: [BigData&NoSQL] Log Management: un caso d’uso di Big Data e di Operational Intelligence

Il contenuto è protetto da password. Per visualizzarlo inserisci di seguito la password:

Service-level requirements: soddisfare i requisiti non funzionali di una architettura software

L’architetto software è la figura professionale che ha l’obiettivo fondamentale di progettare l’architettura di un sistema software nel rispetto dei requisiti non funzionali (NFR)Di seguito, riporto un elenco generale di accorgimenti o best practices che occorre, o conviene, seguire per rispettare i requisiti di base quali performance, availability, reliability, extensibility, manageability, maintainability security.

Il seguente elenco è stato estratto da una lista di quesiti utili alla certificazione di JEE Architect (Oracle) che sto conseguendo; esso non è esaustivo e copre casi piuttosto generali. 

 

Come si può soddisfare la scalabilità del sistema (scalability)?

  • Creando una architettura ridondante, con l’applicazione di tecniche di clustering. I tier dovrebbero essere ridondanti: web tier, application tier, database, LDAP e load balancer, componenti di rete, ecc;
  • Load balancing: è la tecnica che permette di redirigere una richiesta ad uno dei tanti server disponibili in base ad un predeterminato algoritmo di load balancing. Il vantaggio è redistribuire il carico su differenti macchine, con prestazioni non eccessive, invece di avere una singola macchina ad alte prestazioni che gestisca tutte le richieste. In questo modo si risparmia sui costi e si ottimizza l’uso delle risorse computazionali. Per implementare il load balancing occorre scegliere l’appropriato algoritmo in base alle performance e alla disponibilità richieste. Un tipico algoritmo è il round-robin;
  • Riducendo il traffico di rete usando pattern, come DTO e Session Façade, ad esempio;
  • Valutando se scalare verticalmente o orizzontalmente (vertical scaling horizontal scaling). Per scalare un sistema occorre inserire hardware aggiuntivo: scalare verticalmente significa aggiungere risorse addizionali alla macchina, come processori, memoria o dischi; invece, scalare orizzontalmente significa aggiungere altre macchine all’architettura esistente, aumentando la capacità complessiva di sistema. La prima soluzione è più semplice, poiché non ha impatto sull’architettura: infatti, per scalare orizzontalmente l’architettura deve essere predisposta per supportare una cluster-based configuration e progettata in modo che i suoi componenti non dipendano dalla macchina su cui vengono eseguiti. La scelta, comunque, dipende dal costo dell’hardware richiesto per raggiungere i requisiti di performance e di scalabilità fissati col cliente;
  • Disegnando l’architettura in maniera modulare (modularity): conviene separare il sistema in componenti auto-consistente per poter scalare singolarmente le sue parti (web, business, integration tier, ad esempio);
  • Stateless architecture. Conviene utilizzare EJB di tipo stateless: l’utilizzo di stateful session bean comporta una scelta progettuale “povera” dal punto di vista della scalabilità. Inoltre, lo stato conversazionale dovrebbe essere mantenuto lato web (con l’uso di HttpSessionURL rewriting)
  • Usando un connection pooling per le connessioni al database. Inoltre, conviene minimizzare l’utilizzo di long transaction (in generale, l’uso delle transazioni dovrebbe avvenire solo se effettivamente necessario)

 

Come si possono ottimizzare le performance di un sistema?

  •  Attraverso la ridondanza (redundancy). La capacità del sistema può crescere attraverso il vertical scaling e solitamente viene definita height. La ridondanza è la dimensione che viene calcolata in base al numero di istanze configurate per svolgere in modo sincronizzato lo stesso lavoro (ad esempio, attraverso il load balancing): tale dimensione cresce quando si scala orizzontalmente e viene definita width. La ridondanza può migliorare le performance del sistema, oltre all’availability, alla reliability, alla scalability e alla extensibility, ma può comportare un peggioramento della security e della manageability.
  • Introducendo un meccanismo di caching dei dati, in modo da ridurre l’overhead computazionale e limitando il numero di richieste concorrenti. Si potrebbe introdurre un timeout sulle operazioni long-lasting, specialmente quelle che riguardano l’accesso a servizi esterni. In questo modo si migliora il system throughput.
  • Il caching si può introdurre anche grazie all’utilizzo di pattern, come Service Locator e Value List Handler, oppure Session Facade e DTO, che permettono di ridurre il traffico di rete, permettono di esporre servizi “a grana grossa” (course-grained services) che consentono di ridurre il round-trip time all’atto della chiamata ai servizi di business;
  • L’utilizzo delle transazioni deve essere effettuato solo se espressamente richiesto. In generale, peggiorano le performance complessive del sistema, ma ovviamente non si può evitare di utilizzarle, essendo necessarie per garantire la consistenza e l’integrità dei dati. Vanno valutati i singoli casi e fatte delle scelte, trovando il giusto trade-off;
  • Session management. Le specifiche Servlet consigliano di spostare la gestione della sessione lato web, attraverso l’uso della classe HttpSession. La memorizzazione degli oggetti in sessione deve essere ragionata: salvare oggetti onerosi, come collezioni di risultati di ricerca, è un conosciuto problema denominato “performance and scalability anti-pattern”. Conviene ridurre, dunque, i dati in sessione ed effettuare aggiornamenti “a grana grossa” (course grained update).
  • L’utilizzo di un algoritmo di load balancing, come Sticky sessionconsente di avere effetti benefici sulle performance e consente di evitare di avere frequenti sincronizzazioni di dati in sessione. Lo svantaggio è sulla availability: l’utente perde la sessione, ma il failover è più veloce. L’utilizzo di un elastic load balancing e di uno short duration time di sessione avita di avere una perdita di sessione.
  • Utilizzando Java Persistence API (JPA) in modalità lazy, in modo da evitare di caricare dati inutili in modo massivo quando si effettuano le query sui dati;
  • La replicazione passiva (passive replication) consente di avere un sistema poco sovraccarico e di evitare frequenti sincronizzazioni tra i nodi replicati.

 

Come si può soddisfare il requisito della disponibilità del sistema (availability)?

  • Realizzando una architettura ridondante; tutti i tier dovrebbero essere ridondanti (web tier, application tier, database, load balancer, componenti di rete, ecc.). Prevedendo ridondanzafailoverun componente singolo può essere indisponibile e avere un impatto negativo sulla reliability, ma il servizio continua ad essere disponibile grazie alla ridondanza;
  • Dunque, conviene prevedere un clustering con failover usando un hot stand-by server (con capacità extra), ossia un componente secondario che si attiva solo in caso di necessità (soluzione che comporta una spesa aggiuntiva per un componente che potrebbe non essere utilizzato se non in caso di inattività dei componenti principali – occorre valutare se conviene sostenere tale costo, con la garanzia di avere una architettura disponibile in caso di fallimento);
  • Prevedendo una configurazione in clustering e scegliendo un cluster software che permetta di amministrare il server group, rilevando softwarehardware failure e di gestire il failover, restartando i servizi in caso di fallimento. La configurazione two-node clusters (symmetric and asymmetric) è quella più comune: quella simmetrica prevede che entrambi i cluster siano attivi nello stesso istante, invece, quella asimmetrica prevede la presenza di un stand-by failover server.
  • Prevedendo una replicazione passiva (passive replication), dove lo stato dei componenti secondari viene sincronizzato con quello del componente principale, in caso di fallimento. Per il database, si potrebbe optare per una replicazione attiva (active replication), dove i componenti cluster sono tutti principali e si sincronizzano per gestire il carico (in questo modo non si ha una capacità extra inattiva in condizioni normali di funzionamento). Tuttavia, la replicazione attiva è onerosa poiché la sincronizzazione e il coordinamento tra i componenti aggiungono un certo overhead e complessità al sistema;
  • Attraverso la definizione di un piano di Disaster Recovery e di Service-Level Agreements (SLA) per i sistemi esterni da integrare. La definizione delle SLA deve essere concordata con il cliente e fissata in modo chiaro anche per il sistema che si intende progettare.


Come si garantisce il requisito di sicurezza del sistema (security)?

  • Con la ridondanza delle componenti di sistema, diminuisce anche la sicurezza. Questo perché potenzialmente si hanno più punti di vulnerabilità nel sistema;
  • Application security: la sicurezza applicativa si potrebbe soddisfare attraverso il transport level security (come SSL/TLS EAS 256 bit) o attraverso un message level security (come ad esempio, WS-Security). Inoltre, a livello applicativo occorrerebbe prevedere sempre una input validation, filtrando gli input inseriti dagli utenti sulle interfacce web, risolvendo ad esempio il problema del cross site scripting, e prevedendo un packet filtering firewall per evitare attacchi DDoS;
  • Si potrebbe prevedere una zona demilitarizzata (DMZ) per il web e per il business tier, in modo da proteggersi da attacchi esterni, usando il protocollo HTTPS, web server operanti come reverse proxy, una business DMZ accessibile solo attraverso web server “trusted”. Anche al database si dovrebbe accedere soltanto attraverso application server “trusted”.
  • Inoltre, applicando il principle of least privilege, secondo il quale si dovrebbe accedere solo alle informazioni e alle risorse necessarie. Tale principio prevede che utenti, amministratori, programmi, sistemi, ecc. dovrebbero possedere solamente i privilegi necessari per eseguire uno specifico task. Il principio dei privilegi minimi è un importante principio per limitare l’esposizione agli attacchi e per minimizzare i danni;  
  • Per l’integrazione di servizi esterni occorrerebbe utilizzare protocolli come SSL. Ad esempio, per l’accesso a Web Services si potrebbe utilizzare JAX-WS over SSL, in modo da sfruttare il message-level data integrity, grazie all’XML Signature, e il message-level and transport confidentiality, utilizzando l’encryption (WS-Security Specification)
  • Creando una architettura che separi i componenti funzionali o prevedendo delle security zone per ciascun componente: se un componente viene violato, viene compromesso solo tale componente e non tutti gli altri presenti nel sistema (punti di isolamento);
  • Con l’utilizzo di filtri (filter), componenti server-side ospitati dal web container, che permettono di intercettare le richieste in entrata prima che vengano ricevute dai rispettivi componenti target. I filtri sono utili per pre-processare le richieste, permettendo, ad esempio, di loggare eventi o validare le richieste (security checks);
  • Sfruttando la sicurezza messa a disposizione come servizio da un EJB container, facilmente configurabile anche a run-time;
  • Prevedendo un meccanismo di authorization (soltanto un utente valido e autenticato ha gli appropriati diritti per accedere ai dati e alle funzionalità di sistema), confidentiality (assicurando che i dati e le funzionalità di sistema siano protetti da accesso non autorizzati), integrity (assicurando che i dati di sistema non siano modificati o che ci siano interferenze da parte di componenti terzi, malintenzionati e non) e authentication (assicurando che l’identità di un utente che accede al sistema sia valida e corretta e che non possa essere impersonificata o compromessa in alcun modo). Per garantire tali requisiti, si potrebbe sfruttare Java Authentication and Authorization (JAAS);
  • Prevedendo un declarative security model, dove le regole di autorizzazione sono definite attraverso un file XML (deployment descriptor). Nel caso in cui occorra gestire una sicurezza “role-based” particolare, è possibile definire un programmatic security model.

 

Come si garantisce l’affidabilità (reliability) di un sistema?

  • La reliability assicura l’integrità e la consistenza dell’applicazione e di tutte le sue transazioni. Inoltre, definisce l’abilità di un sistema o di un componente di funzionare sotto determinate condizioni per un periodo di tempo specificato. E’ possibile garantire l’affidabilità del sistema utilizzando la ridondanza (redundancy): quando un server non risponde, altri possono lavorare al suo posto;
  • Utilizzando load balancing, failover e clustering;
  • Lo sticky session ha effetti negativi sulla reliability perché l’utente perde la sessione. Per ridurre tale effetto occorre prevedere uno short time session e un elastic load balancing
  • Anche la replicazione passiva ha un effetto negativo sulla reliability, perché ci potrebbe essere un tempo di indisponibilità del sistema (down time) in caso di fallimento e conseguente attivazione dello stand-by server.

 

Come si garantisce l’estensibilità del sistema (extensibility)?

  • Avere un sistema flessibile significa che l’aggiunta di funzionalità o la modifica di quelle esistenti non hanno impatto sull’architettura del sistema. Tale misura dipende da come il sistema è stato progettato e da quanto precisi sono stati i service-level requirements stabiliti con il cliente. I principi base che occorre coprire per garantire la flessibilità del sistema sono: low coupling, interfaces encapsulation.
  • L’applicazione deve essere progettata rispettando il separation of concernsconviene prevedere differenti layer (presentation, business e data access layer). Ogni layer è debolmente accoppiato con gli altri (loose coupling), grazie all’utilizzo di pattern, interfacce, ereditarietà, incapsulamento e best practices della programmazione ad oggetti;
  • Utilizzando pattern per separare l’applicativo in concern e layer: ad esempio, utilizzando DAO, Value List Handler, Front Controller, MVC, Service to Worker;
  • Progettando un object model di alta qualità: basta applicare i principi della programmazione ad oggetti e i design pattern (ad esempio, l’MVC pattern disaccoppia i componenti di user interface dai componenti di business logic);
  • Definendo un service level agreement (SLA) con il cliente e cercando di anticipare le modifiche applicative; attraverso la definizione di SLA è possibile definire uno scope di progetto chiaro e preciso e anticipare inaspettate modifiche al sistema. Occorrerebbe identificare le possibili “changed area” del sistema (ad esempio, la tecnologia di user interface utilizzata) e isolare tali aree in componenti coerenti: in questo modo, si può prevenire che la propagazione degli effetti dei cambiamenti abbia sensibili ripercussioni in tutto il sistema;
  • La ridondanza (redundancy) e la modularità (modularity) consentono di migliorare anche l’estensibilità del sistema;

 

Come si garantisce la manageability del sistema?

  • La manageability è l’abilità di gestire il sistema per assicura di averlo in salute, grazie al monitoraggio dei QoS requirements (scalability, reliability, availability, performance, security). Inoltre, un’alta manageability consente di cambiare la configurazione del sistema per migliorare la QoS dinamicamente senza impattare l’architettura del sistema stesso;
  • La ridondanza ha effetti negativi sulla manageability e non solo sulla sicurezza;
  • Sticky session ha effetti negativi sulla manageability;
  • Utilizzando Java Management Extensions (JMX) e prevedendo differenti livelli di logging per monitorare condizioni di errore, fatali o di warning durante il funzionamento dell’applicativo;
  • Scegliendo un software idoneo di load balancer per il cluster management.

 

Come si garantisce la maintainability del sistema?

  • La maintainability è la capacità di correggere difetti nelle funzionalità esistenti senza impatti su altri componenti del sistema. Tale misura non si valuta a tempo di deploy, ma quando si progetta l’architettura e viene garantita se si rispettano i principi di low coupling, modularity e documentation. Grazie alla modularity, ogni layer è debolmente accoppiato agli altri (loosely coupling), e sfruttando i principi di separation of concerns, i design pattern, le interfacce e le best practices della programmazione ad oggetti, è possibile fare in modo che il sistema sia più manutenibile;
  • Grazie ad una buona documentazione dell’applicativo (architecture diagrams, Interface agreements con i sistemi esterni, class diagrams, sequence diagrams, Java doc, ecc.);
  • Utilizzando lo Standard JEE Stack, con tecnologie come JSF, EJB, JPA, JAAS, JTA in modo da manutenere una soluzione standard e non “specific vendor”. Ad esempio, i componenti JSF si possono riutilizzare e la stessa tecnologia JSF è definita “toolable”, grazie al supporto di IDE maturi come Eclipse, Netbeans e IntelliJ.

[JBoss&EJB] EJB remoti: come effettuare la lookup da client JBoss

In questo articolo, descrivo come effettuare una lookup da un client JBoss verso un servizio EJB (Enterprise Java Bean) remoto, esposto su una macchina differente dal client stesso.

Queste sono le tecnologie utilizzate in questo tutorial:

ejb_lookup

Il nostro EJB remoto di test è il seguente:

@Stateless
@Remote(TestService.class)
@TransactionManagement(TransactionManagementType.BEAN)
public class TestServiceBean implements TestService {

       public methodTest(){

           //code implementation here

       }

}

Il precedente EJB remoto verrà esposto su un application server installato su una macchina differente (che chiamerò destination server). Di seguito, divido le attività di configurazione in due: configurazione destination server e configurazione client server.

 

Configurazione destination server

Sull’application server su cui è deployato l’EJB remoto, occorre semplicemente creare un utente applicativo, per mettere in sicurezza la chiamata remota (security realm). Utilizziamo lo script add-user.sh (o .bat, se siete su Windows), presente in ${JBOSS_HOME}/bin, per creare l’utente ejb (con password test):

add-user JBoss

Dopo aver creato l’utente su JBoss, basta startare l’AS e deployare il servizio remoto.

 

Configurazione client server

Il grosso della configurazione va fatta sul client che deve richiamare l’EJB remoto.

Nel file di configurazione di JBoss, standalone.xml (o domain.xml), occorre eseguire quanto segue:

  • creare il security realm sul client server
   <management>
        <security-realms>
            ...
            <security-realm name="ejb-security-realm">
                <server-identities>
                    <secret value="dGVzdA=="/>
                </server-identities>
            </security-realm>
        </security-realms>

Dove in secret c’è la password (test) codifica in BASE-64.

  • creare l’outbound-socket-binding sul client server
 <socket-binding-group name="standard-sockets" default-interface="public" port-offset="${jboss.socket.binding.port-offset:0}">
        ...
        <outbound-socket-binding name="remote-ejb">
            <remote-destination host="127.0.01" port="4447"/>
        </outbound-socket-binding>
     </socket-binding-group>

Dove al posto dell’indirizzo IP 127.0.0.1 potete inserire quello della macchina remota del destination server (la porta di default del remoting è la 4447, a meno che non l’abbiate cambiata).

  • creare un remote-outbound-connection che usa l’outbound-socket-binding
<subsystem xmlns="urn:jboss:domain:remoting:1.1">
....
            <outbound-connections>
                <remote-outbound-connection name="remote-ejb-connection" outbound-socket-binding-ref="remote-ejb" security-realm="ejb-security-realm" username="ejb">
                    <properties>
                        <property name="SASL_POLICY_NOANONYMOUS" value="false"/>
                        <property name="SSL_ENABLED" value="false"/>
                    </properties>
                </remote-outbound-connection>
            </outbound-connections>
        </subsystem>

Dove in corrispondenza dell’attributo username c’è l’utente di test che abbiamo creato sul destination server.

Lato configurazione del client server abbiamo terminato.

Di seguito, si riportano gli interventi da fare sull’applicazione client (client application), per poter richiamare il servizio remoto:

  • inserire nel classpath dell’applicazione client il file jboss-ejb-client.xml 
<jboss-ejb-client xmlns="urn:jboss:ejb-client:1.0">
    <client-context>
        <ejb-receivers>
            <remoting-ejb-receiver outbound-connection-ref="remote-ejb-connection"/>
        </ejb-receivers>
    </client-context>
</jboss-ejb-client>

Infine, per effettuare la lookup remota in modo programmatico, ecco un esempio di una classe client di test:

import javax.naming.Context;
import java.util.Hashtable;
import javax.naming.InitialContext;
 
...
public void invokeOnBean() {
        try {
            final Hashtable props = new Hashtable();
            // setup the ejb: namespace URL factory
            props.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
            // create the InitialContext
            final Context context = new javax.naming.InitialContext(props);
 
            // Lookup
            final TestService bean = (TestService) context.lookup("ejb:" + "myapp" + "/" + "myejb" + "/" + "" + "/" + "TestService" + "!" + it.francescoficetola.ejb.TestService.class.getName());
 
            // invoke on the bean
            final String greeting = bean.test();
 
 
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
}

Nota. Quando si deploya l’EJB remoto, il JNDI name viene automaticamente registrato dall’application server. Ad esempio, per gli EJB stateless, il JNDI name è come questo:

ejb:jboss-as-ejb-remote-app/TestServiceBean!it.francescoficetola.ejb.TestService

Come si riporta in questo articolo (EJB invocations from a remote client using JNDI), ecco la regola per la generazione del JNDI name:

ejb:<app-name>/<module-name>/<distinct-name>/<bean-name>!<fully-qualified-classname-of-the-remote-interface>

Poiché in app-namemodule-name mi venivano registrati anche i numeri di versione dei moduli applicativi (dopo aver buildato con Maven), ho effettuato un override dei loro valori in questo modo:

  • per settare l’app-name, ho inserito l’application-name nel file application.xml dell’EAR in cui è deployato l’EJB remoto:

<application-name>myapp</application-name>

  • per settare il module-name, occorre inserire nel classpath del modulo EJB (nella directory META-INF) il file ejb-jar.xml:
<ejb-jar xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/ejb-jar_3_2.xsd"
    version="3.2">
    <module-name>myejb</module-name>
</ejb-jar>

 

Riferimenti:

[JMS&JBoss] Configurazione delle code JMS su JBoss 7

hornetQIn questo tutorial condivido la configurazione delle code JMS su JBoss 7, utilizzando HornetQ Servermodulo già presente nelle versioni 7.x di JBoss.

Nel file standalone.xml in ${JBOSS_HOME}/standalone/configuration (o nel file domain.xml, se è questo il file utilizzato per la configurazione di JBoss), aggiungere ciò che segue:

  • Inserire il modulo messaging nelle estensioni di JBoss (se non c’è), ossia all’interno del tag <extensions>.:
<extension module="org.jboss.as.messaging"/>
  • Per configurare l’hornetQ server e registrare i JNDI name delle code (sia queue che topic), occorre aggiungere un subsystem all’interno del tag <profile>:
<subsystem xmlns="urn:jboss:domain:messaging:1.4">
 <hornetq-server>
 <persistence-enabled>true</persistence-enabled>
 <security-enabled>false</security-enabled>
 <journal-file-size>102400</journal-file-size>
 <journal-min-files>2</journal-min-files>

 <connectors>
 <netty-connector name="netty" socket-binding="messaging">
 <param key="host" value="${jboss.bind.address}"/>
 </netty-connector>
 <netty-connector name="netty-throughput" socket-binding="messaging-throughput">
 <param key="host" value="${jboss.bind.address}"/>
 <param key="batch-delay" value="50"/>
 </netty-connector>
 <in-vm-connector name="in-vm" server-id="0"/>
 </connectors>

 <acceptors>
 <netty-acceptor name="netty" socket-binding="messaging"/>
 <netty-acceptor name="netty-throughput" socket-binding="messaging-throughput">
 <param key="batch-delay" value="50"/>
 <param key="direct-deliver" value="false"/>
 </netty-acceptor>
 <in-vm-acceptor name="in-vm" server-id="0"/>
 </acceptors>

 <security-settings>
 <security-setting match="#">
 <permission type="send" roles="guest"/>
 <permission type="consume" roles="guest"/>
 <permission type="createNonDurableQueue" roles="guest"/>
 <permission type="deleteNonDurableQueue" roles="guest"/>
 </security-setting>
 </security-settings>

 <address-settings>
 <address-setting match="jms.queue.testQueue">
 <dead-letter-address>jms.queue.DLQ</dead-letter-address>
 <expiry-address>jms.queue.ExpiryQueue</expiry-address>
 <redelivery-delay>0</redelivery-delay>
 <max-delivery-attempts>5</max-delivery-attempts>
 <max-size-bytes>10485760</max-size-bytes>
 <address-full-policy>BLOCK</address-full-policy>
 <message-counter-history-day-limit>10</message-counter-history-day-limit>
 </address-setting>
 
 <address-setting match="jms.queue.DLQ">
 <max-delivery-attempts>5</max-delivery-attempts>
 <max-size-bytes>10485760</max-size-bytes>
 <address-full-policy>BLOCK</address-full-policy>
 <message-counter-history-day-limit>10</message-counter-history-day-limit>
 </address-setting>
 </address-settings>

 <jms-connection-factories>
 <connection-factory name="InVmConnectionFactory">
 <connectors>
 <connector-ref connector-name="in-vm"/>
 </connectors>
 <entries>
 <entry name="java:/ConnectionFactory"/>
 </entries>
 <client-failure-check-period>2147483646</client-failure-check-period>
 <connection-ttl>-1</connection-ttl>
 <reconnect-attempts>-1</reconnect-attempts>
 </connection-factory>
 <connection-factory name="RemoteConnectionFactory">
 <connectors>
 <connector-ref connector-name="netty"/>
 </connectors>
 <entries>
 <entry name="RemoteConnectionFactory"/>
 <entry name="java:jboss/exported/jms/RemoteConnectionFactory"/>
 </entries>
 <ha>true</ha>
 <block-on-acknowledge>true</block-on-acknowledge>
 <retry-interval>1000</retry-interval>
 <retry-interval-multiplier>1.0</retry-interval-multiplier>
 <reconnect-attempts>-1</reconnect-attempts>
 </connection-factory>
 <pooled-connection-factory name="hornetq-ra">
 <transaction mode="xa"/>
 <connectors>
 <connector-ref connector-name="in-vm"/>
 </connectors>
 <entries>
 <entry name="java:/JmsXA"/>
 </entries>
 <client-failure-check-period>2147483646</client-failure-check-period>
 <connection-ttl>-1</connection-ttl>
 <reconnect-attempts>-1</reconnect-attempts>
 </pooled-connection-factory>
 </jms-connection-factories>

 <jms-destinations>
 <jms-queue name="testQueue">
 <entry name="queue/TestQueue"/>
 <entry name="java:jboss/exported/jms/queues/TestQueue"/>
 </jms-queue>
 <jms-topic name="testTopic">
 <entry name="topic/MyTopic"/>
 </jms-topic>
 </jms-destinations>
 </hornetq-server>
</subsystem>
  • Aggiungere i seguenti messaging port nel tag <socket-binding-group>:
<socket-binding name="messaging" port="5445"/>
<socket-binding name="messaging-throughput" port="5455"/>
  • Immediatamente sotto il tag subsystem <subsystem xmlns=”urn:jboss:domain:ejb3:1.4″> (e prima del tag session-bean) aggiungere quanto segue:
<mdb>
    <resource-adapter-ref resource-adapter-name="hornetq-ra"/>
     <bean-instance-pool-ref pool-name="mdb-strict-max-pool"/>
</mdb>
  • Sempre in <subsystem xmlns=”urn:jboss:domain:ejb3:1.4″>, controllare che in pools ci sia quanto sottolineato di seguito (altrimenti aggiungerlo):
<pools>
    <bean-instance-pools>
          <strict-max-pool name="slsb-strict-max-pool" max-pool-size="20" instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
          <strict-max-pool name="mdb-strict-max-pool" max-pool-size="20" instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
     </bean-instance-pools>
</pools>

NOTA 1: la porta mappata nel socket-binding (nell’esempio , la porta 5445) non deve avere restrizioni di rete.

NOTA 2. Per evitare la seguente eccezione:

11:56:54,753 WARN  [org.hornetq.jms.server] (ServerService Thread Pool — 52) HQ122017: Tried to correct invalid “host” value “0.0.0.0” for “netty” connector, but received an exception.: java.net.UnknownHostException: XXXXXXX: XXXXXXXX

Occorre mappare sul file etc/hosts la seguente regola:

0.0.0.0  HOSTNAME

Dove HOSTNAME è il nome macchina (valore restituito lanciando il comando hostname da terminale).

TEST: per verificare la correttezza della configurazione delle code JMS, basta restartare il server e verificare che non ci sia nessuna eccezione sul log server.log.

 

Configurazione delle code JMS in ambiente clusterizzato (Clustering HornetQ)

hornetq-ha-cluster

Aggiungere alla configurazione precedente quanto segue (solo in ambiente clusterizzato):

<server name="xyz" xmlns="urn:jboss:domain:1.1">
  <profile>
    ...
    <subsystem xmlns="urn:jboss:domain:messaging:1.1">
      <hornetq-server>
        ...
        <connectors>
          <netty-connector name="netty" socket-binding="messaging"/>
          ...
          <netty-connector name="server2-connector" socket-binding="messaging-server2"/>
          <netty-connector name="server3-connector" socket-binding="messaging-server3"/>
        </connectors>
        ...
        <cluster-connections>
          <cluster-connection name="default-cluster-connection">
            <address>
              jms
            </address>
            <connector-ref>
              netty
            </connector-ref>
            <retry-interval>
              500
            </retry-interval>
            <forward-when-no-consumers>
              true
            </forward-when-no-consumers>
            <static-connectors>
              <connector-ref>
                server2-connector
              </connector-ref>
              <connector-ref>
                server3-connector
              </connector-ref>
            </static-connectors>
          </cluster-connection>
        </cluster-connections>
        ...
      </hornetq-server>
    </subsystem>
    ...
  </profile>
  <socket-binding-group name="standard-sockets" default-interface="public">
    ...
    <socket-binding name="messaging" port="5445"/>
      ...
      <outbound-socket-binding name="messaging-server2">
        <remote-destination host=”#Indirizzo_Cluster_1” port="5445"/>
      </outbound-socket-binding>
      <outbound-socket-binding name="messaging-server3">
        <remote-destination host=”#Indirizzo_Cluster_2” port="5445"/>
      </outbound-socket-binding>
  </socket-binding-group>
</server>

NOTA. Sostituire gli indirizzi IP in remote-destination (in outbound-socket-binding) con quello dei nodi cluster JBoss (così come su evidenziato e commentato).

Riferimento: Clustering HornetQ – Red Hat Documentation

[JSON] La specifica JSON-RPC 2.0: alcuni client iOs


masthead-jsonrpc2base
Esiste una specifica alquanto diffusa per la distribuzione di servizi RESTful, con l’ovvio formato di interscambio JSON, nota come JSON-RPC 2.0.

Pe le caratteristiche di dettaglio di questa specifica vi rimando al sito ufficiale: http://www.jsonrpc.org/specification 

In breve, JSON-RPC non è altro che un protocollo per la chiamata di procedure remote, dove è possibile passare al servizio anche oggetto complessi ed avere in output dati multipli. La descrizione del servizio (una sorta di WSDL) è ben definita, come anche i codici di errore e di successo restituiti dalla chiamata. In sintesi, non è altro che un “modo” per la scrittura di servizi RESTful secondo un certo standard.

La cosa interessante è che il servizio remoto viene interrogato tramite i comuni protocolli HTTP o TCP/IP e la serializzazione degli oggetti di output avviene in formato JSON. Una richiesta di servizio è una chiamata a un metodo specifico e che contiene le seguenti proprietà:

  • method – una stringa con il nome del metodo da invocare.
  • params – un array di oggetti da passare come parametri al metodo definito.
  • id un valore di qualsiasi tipo, che viene utilizzato per abbinare la risposta alla richiesta

Esempio:  {“jsonrpc”: “2.0”, “method”: “subtract”, “params”: [42, 23], “id”: 1}

json-rpc2.0 Per poter richiamare questi servizi da un’app mobile iOs, ho provato i seguenti client:

Esiste anche la possibilità di richiamare i servizi JSON-RPC in “modalità nativa”, come descritto da questo tutorial:

[PrimeFaces] Due approcci per customizzare i tag di PrimeFaces

Logo Prime FacesIn un recente post, ho “lodato” PrimeFaces come uno dei migliori framework Java Server Faces (JSF) attualmente in circolazione. In effetti, le componenti grafiche, rispetto ad altre librerie del genere (vedi Rich Faces), sono sicuramente più stabili, complete e graficamente più carine. Tuttavia, può capitare di dover fare delle customizzazioni, specie per renderne alcune davvero accessibili (nonostante da documentazione si dichiari l’aderenza di PrimeFaces alle specifiche Accessibile Rich Internet Applications – WAI-ARIA).

Illustro qui due metodi per customizzare i tag di Prime. Nell’esempio, verrà modificato la componente “PanelGrid” (p:panelGrid), ma la procedura può essere applicata a tutti i tag della libreria “core”.

Entrambi metodi che vi illustrerò, prendono come riferimento la documentazione dei tag di PrimeFaces. Ecco il link alla documentazione dell’attuale versione (4.0): Documentazione PrimeFaces 4.0

Ad esempio, per il tag PanelGrid ecco le informazioni che ci servono:

tagPanelGrid PrimeFaces

 

1° Metodo: Customizzazione della Renderer Class (RenderKit)

Se vi occorre customizzare soltanto il rendering della componente grafica, basta estendere la classe “Renderer” ad essa associata. Come già anticipato sopra, per capire qual è tale classe, occorre leggere la documentazione relativa al tag da customizzare. Nel caso del panelGrid, la classe da estendere è org.primefaces.component.panelgrid.PanelGridRenderer

NOTA. Gli attributi rendererType e rendererClass, che si leggono da documentazione, non coincidono. Quindi, state attenti ad annotarvi queste informazioni, perché occorre configurarle allo stesso modo per la classe “custom” che scriverete, come illustrato di seguito.

Ecco la classe CustomPanelGridRenderer che estende quella “core” PanelGridRenderer:

package it.francescoficetola.prime.web.component;

import java.io.IOException;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

import org.primefaces.component.column.Column;
import org.primefaces.component.panelgrid.PanelGrid;
import org.primefaces.component.panelgrid.PanelGridRenderer;
import org.primefaces.component.row.Row;

public class CustomPanelGridRenderer extends PanelGridRenderer{

	 public CustomPanelGridRenderer() {
                super();
	        System.out.println("costruttore classe custom renderer");
	 }

         @Override
	 public void encodeRow(FacesContext context, Row row, String columnRole, String rowClass, String columnClass) throws IOException {
	        ResponseWriter writer = context.getResponseWriter();

	        writer.startElement("tr", null);
	        if(shouldWriteId(row)) {
	            writer.writeAttribute("id", row.getClientId(context), null);
	        }

	        writer.writeAttribute("class", rowClass, null);
	        writer.writeAttribute("role", "row", null);

	        for(UIComponent child : row.getChildren()) {
	            if(child instanceof Column && child.isRendered()) {
	                Column column = (Column) child;
	                String styleClass = null;
	                String userStyleClass = column.getStyleClass();

	                if(userStyleClass != null && columnClass != null) styleClass = columnClass + " " + userStyleClass;
	                else if(userStyleClass != null && columnClass == null) styleClass = userStyleClass;
	                else if(userStyleClass == null && columnClass != null) styleClass = columnClass;

	                writer.startElement("td", null);
	                if(shouldWriteId(column)) { 
	                    writer.writeAttribute("id", column.getClientId(context), null);
	                }
	                writer.writeAttribute("role", columnRole, null);

	                if(column.getStyle() != null) writer.writeAttribute("style", column.getStyle(), null);
	                if(styleClass != null) writer.writeAttribute("class", styleClass, null);
	                if(column.getColspan() > 1) writer.writeAttribute("colspan", column.getColspan(), null);
	                if(column.getRowspan() > 1) writer.writeAttribute("rowspan", column.getRowspan(), null);

	                column.encodeAll(context);

	                writer.endElement("td");
	            }
	        }

	        writer.endElement("tr");
	    }
}

Nell’esempio precedente, è stato effettuato l’override del metodo encodeRow. In base al comportamento che vorrete customizzare, occorre individuare nella classe “Renderer” della vostra componente, il metodo associato a tale comportamento ed effettuarne l’override, modificandone la logica.

Per poter registrare la vostra classe “custom” di rendering, occorre configurare il faces.config.xml:

<?xml version='1.0' encoding='UTF-8'?>
<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
        version="2.0">

	<render-kit>
            <renderer>
                <component-family>org.primefaces.component</component-family>
                <renderer-type>org.primefaces.component.PanelGridRenderer</renderer-type>
                <renderer-class>it.francescoficetola.prime.web.component.CustomPanelGridRenderer</renderer-class>
            </renderer>
        </render-kit>

</faces-config>

I valori da associare agli attributi component-familyrenderer-type li prendete da documentazione del tag, come suddetto. In questo modo, ogni volta che utilizzate il “core tag” sulle pagine JSF della vostra applicazione (nell’esempio, p:panelGrid), in automatico verrà richiamata la vostra classe “renderer”.

NOTA. Se notate che nulla accade, provate ad inserire nel file web.xml della vostra applicazione le seguenti righe di codice (inserendo anche il file faces-config.xml nella cartella WEB-INF):

    <context-param>
        <param-name>javax.faces.CONFIG_FILES</param-name>
        <param-value>
            /WEB-INF/faces-config.xml, /faces-config.xml
        </param-value>
    </context-param>

 

2° Metodo: Customizzazione del tag (CustomTag)

Il secondo metodo che vi illustro è sicuramente il più completo, perché vi permette di modificare tutto il comportamento del tag associato alla componente grafica, e non solo il suo rendering.

In questo caso, vanno estesi sia il Component Class che il Renderer Class. Nel caso del p:panelGrid di questo esempio:

Renderer class: org.primefaces.component.panelgrid.PanelGridRenderer

Component class: org.primefaces.component.panelgrid.PanelGrid

package it.francescoficetola.prime.web.component;

import javax.faces.component.FacesComponent;
import org.primefaces.component.panelgrid.PanelGrid;

@FacesComponent("it.francescoficetola.prime.component.CustomPanelGrid")
public class CustomPanelGrid extends PanelGrid{

	public CustomPanelGrid() {
		super();
		System.out.println("Costruttore del component class");
	}

	@Override
	public void setColumns(int _columns) {
	    getStateHelper().put(PropertyKeys.columns, _columns);
	}
}

Nell’esempio del PanelGrid, è stato effettuato l’override del metodo setColumns, ma anche qui vale quanto detto nel 1° metodo di customizzazione, ossia che in base al comportamento che vorrete customizzare, occorre individuare nel Component Class il metodo associato per modificandone la logica, attraverso l’override.

Di seguito, vi ripeto la classe di “renderer” custom, che anche per questo metodo va prevista, ma che in più presenta anche delle annotazioni di classe:

package it.francescoficetola.prime.web.component;

import java.io.IOException;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.FacesRenderer;

import org.primefaces.component.column.Column;
import org.primefaces.component.panelgrid.PanelGrid;
import org.primefaces.component.panelgrid.PanelGridRenderer;
import org.primefaces.component.row.Row;

@FacesRenderer(
	    componentFamily=PanelGrid.COMPONENT_FAMILY,
	    rendererType="it.francescoficetola.prime.web.component.CustomPanelGridRenderer"
	)
public class CustomPanelGridRenderer extends PanelGridRenderer{

	public CustomPanelGridRenderer() {
	    System.out.println("costruttore della classe renderer");
	    }

         @Override
	 public void encodeRow(FacesContext context, Row row, String columnRole, String rowClass, String columnClass) throws IOException {
	        ResponseWriter writer = context.getResponseWriter();

	        writer.startElement("tr", null);
	        if(shouldWriteId(row)) {
	            writer.writeAttribute("id", row.getClientId(context), null);
	        }

	        writer.writeAttribute("class", rowClass, null);
	        writer.writeAttribute("role", "row", null);

	        for(UIComponent child : row.getChildren()) {
	            if(child instanceof Column && child.isRendered()) {
	                Column column = (Column) child;
	                String styleClass = null;
	                String userStyleClass = column.getStyleClass();

	                if(userStyleClass != null && columnClass != null) styleClass = columnClass + " " + userStyleClass;
	                else if(userStyleClass != null && columnClass == null) styleClass = userStyleClass;
	                else if(userStyleClass == null && columnClass != null) styleClass = columnClass;

	                writer.startElement("td", null);
	                if(shouldWriteId(column)) { 

	                    writer.writeAttribute("id", column.getClientId(context), null);
	                }
	                writer.writeAttribute("role", columnRole, null);

	                if(column.getStyle() != null) writer.writeAttribute("style", column.getStyle(), null);
	                if(styleClass != null) writer.writeAttribute("class", styleClass, null);
	                if(column.getColspan() > 1) writer.writeAttribute("colspan", column.getColspan(), null);
	                if(column.getRowspan() > 1) writer.writeAttribute("rowspan", column.getRowspan(), null);

	                column.encodeAll(context);

	                writer.endElement("td");
	            }
	        }

	        writer.endElement("tr");
	    }

}

A questo punto, occorre creare la taglib con i propri “custom tag”. Nel mio caso, ho creato un file customtag.taglib.xml, con il seguente contenuto:

<?xml version="1.0" encoding="UTF-8"?>
<facelet-taglib
    xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facelettaglibrary_2_0.xsd"
    version="2.0"
>
    <namespace>http://francescoficetola.it/ui</namespace>

    <tag>
        <tag-name>custompanelgrid</tag-name>
        <component>
            <component-type>it.francescoficetola.prime.web.component.CustomPanelGrid</component-type>
            <renderer-type>it.francescoficetola.prime.CustomPanelGridRenderer</renderer-type>
        </component>
    </tag>
</facelet-taglib>

Nel file della taglib, occorre registrare tutti i “custom tag” che prevedete (nel mio caso, ho per ora solo il custompanelgrid, che vi ricordo estende il tag “core” p:panelgrid di PrimeFaces). Salvate questo file nella directory WEB-INF del vostro progetto.

Infine, occorre registrare la taglib nel file web.xml, come segue:

	<!-- CONTEXT PARAM  -->
	<context-param>
	    <param-name>javax.faces.FACELETS_LIBRARIES</param-name>
	    <param-value>/WEB-INF/customtag.taglib.xml</param-value>
	</context-param>

Ora siamo pronti ad utilizzare il nostro custom tag. Ricordatevi di definire il namespace nella pagina JSF in cui utilizzare la vostra componente custom:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<ui:composition 
	xmlns="http://www.w3.org/1999/xhtml"
	xmlns:c="http://java.sun.com/jsp/jstl/core"
	xmlns:h="http://java.sun.com/jsf/html"
	xmlns:f="http://java.sun.com/jsf/core"
	xmlns:ui="http://java.sun.com/jsf/facelets"
	xmlns:p="http://primefaces.org/ui"
	xmlns:customtag="http://francescoficetola.it/ui"
>

<h:html>

   <h:head>
   </h:head>

   <h:body>

	<h:form id="myForm">

            <p:messages id="messages" /> 

	     <h:panelGroup id="myPanelGroup">

		 <customtag:custompanelgrid>

		        <p:row>
				<p:column>Colonna 1</p:column>
                                <p:column>Colonna 1</p:column>
			</p:row>

	         </customtag:custompanelgrid>

	     </h:panelGroup>

	</h:form>

   </h:body>

</h:html>

</ui:composition>

 

Riferimenti utili:

Nel mercato delle app mobile, a non guadagnare saranno gli sviluppatori

In un post pubblicato a novembre scorso, esprimevo il mio parere circa le potenzialità/possibilità di guadagnare nel mercato delle app mobile:

l Freemium: Le app economiche potrebbero uccidere l’App Store

Stavolta riporto un pò di dati alla mano, citando un articolo pubblicato su Wired di Marzo 2014:

Con le app non si diventa ricchi

Sei un giovane smanettone in cerca di successo? Per fare soldi non ha senso pensare a una nuova app. Secondo un report di Gartner, una delle società leader nella consulenza e nell’analisi del mondo dell’Information Technology, nel 2018 solo lo 0,01% di questi software sarà anche un successo finanziario.

Nella guerra tra i marketplace che si sta consumando questa primavera, con l’offerta di Android Market che dovrebbe toccare quota 1 milione entro giugno superando iTunes Store, a non guadagnare un soldo saranno proprio gli sviluppatori. I download non significano successo finanziario: le previsioni indicano nel 94,5% la quota di applicazioni che nel 2017 saranno scaricabili gratis. Per avere probabilità di successo, sottolinea Gartner, bisogna puntare su grandi spese pubblicitarie o avere un brand già molto forte.

Per quanto riguarda le app a pagamento, già oggi il 90% è scaricato meno di 500 volte al giorno: una soglia che non permette di raggiungere la redditività finanziaria. Le previsioni indicano un ulteriore aumento dell’aggressività di un mercato già definito iperattivo. Le piattaforme per la produzione di app sono, da sole, oltre 200, in gran parte basate su strumenti open. E il loro numero
è destinato a crescere ancora.

[http://www.wired.it/economia/lavoro/2014/04/03/con-le-app-non-si-diventa-ricchi/]

 

L’incubo degli ingegneri: “una linea rossa con inchiostro verde”. Le assurde richieste dei clienti

Vi linko un video eloquente sulle assurde richieste dei clienti, con i soliti personaggi che si trovano ai tavoli di riunione. L’esperto di turno si trova a “rispondere” a domande senza senso e a tener testa non solo ai clienti, ma anche al suo stesso commerciale.