martedì 14 novembre 2017

Inviare allegati a un web service con MTOM. (parte due... con IBMi)

Nella prima parte abbiamo visto come scrivere un servizio web che accetti file inviati con MTOM, un sistema standard per la trasmissione di allegati non codificati in Base64, ora vediamo come trasferire il tutto su IBMi.

Dividiamo i passaggi sempre tra producer e consumer, prima quindi vediamo come installare il servizio sul server delle applicazioni integrato sulla macchina, poi come creare un client scritto in RPG che interroghi il servizio.

IL PRODUCER.


Di cosa abbiamo bisogno:

  • Una macchina IBMi con IAS versione 8.1 (io ho usato la versione del sistema operativo V7R1).

Ricordate il war prodotto da Maven nel progetto Eclipse? Nella cartella target... MTOMService.war, proprio quello. Bene, prendiamolo e trasferiamolo sull'IFS del nostro sistema, in qualunque modo e in qualunque cartella vogliate.

Ad esempio io uso FileZilla e ho caricato il file su /tmp

Ora dovremmo collegarci al server amministrativo della nostra macchina, prima però verifichiamo che sia operativo, altrimenti avviamolo con:
STRTCPSVR SERVER(*HTTP) HTTPSVR(*ADMIN)

Quindi apriamo il browser e indirizziamolo verso:

http://<host_nostra_macchina_IBMi>:2001/HTTPAdmin

e accediamo con un utente amministrativo.

Ci viene mostrata la lista dei server presenti nel sistema, dobbiamo creare un server delle applicazioni (come Tomcat) che ospiti la nostra servlet, per cui clicchiamo su "Crea server delle applicazioni":

Questo link è molto più semplice, rispetto a quello del DCM... valli a capire...

Scegliamo di usare la versione 8.1 del server:


e andiamo avanti accettando i default che ci vengono proposti, se non avevamo creato altri server delle applicazioni dovremmo avere il server INTAPPSVR in ascolto sulla porta 10000 della macchina.

Fermiamolo. Così com'è ora non riuscirebbe a instanziare la nostra servlet CXF, questo perché CXF non è compatibile con l'implementazione JAX-WS presente sul server, vanno usate le librerie che forniamo noi, per cui:

Stop. Dopo lo riavviamo con il tasto play.

Spostiamoci su terminale, dobbiamo modificare una configurazione prima di farlo ripartire, modifichiamo questo file:
EDTF STMF('/www/INTAPPSVR/lwi/conf/overrides/i5javaopts.javaopt')
Inserendo questa riga:
-Dcom.ibm.websphere.webservices.DisableIBMJAXWSEngine=true
In questo modo istruiamo la JVM a non far partire il server con il supporto nativo JAX-WS.

Ora possiamo ritornare alla gestione del server e farlo ripartire (vedi sopra).

Clicchiamo sul link "Gestisci applicazioni installate", vediamo che per ora è presente una fantomatica quanto solinga "Guida di Eclipse", premiamo sul tasto Installa per aggiungere la nostra applicazione e chiediamo di importare il nostro war caricato prima:


Accettiamo tutti i default, infine vedremo la nostra applicazione fare compagnia alla "Guida di Eclipse" (?).

Facciamo click sul nome della nostra applicazione per interrogarla, si apre una nuova finestra del browser e... errore 404! Vi ricordate che va aggiusto services alla fine del link, vero?

La lista dei servizi messi a disposizione!

Possiamo copiare il link al nostro WSDL per creare un ambiente di test nel solito SoapUI, ma penso che sia arrivato il momento di fare da soli e creare un client in RPG.

IL CONSUMER.


Cosa ci occorre:
  • HTTPAPI di S. Klement (versione usata 1.37)
  • WSDL2RPG di T. Raddatz (la versione 1.16.5, che ha introdotto il supporto a MTOM, seppur sperimentale)

 

Preparativi.


Installiamo le due utility, magari con il supporto a HTTPS, e iniziamo con il creare un libreria e un file di sorgenti:
CRTLIB LIB(ZENEXMPL) TEXT('Example programs')

CRTSRCPF FILE(ZENUTILS/QWSDL) RCDLEN(112) TEXT('WSDL 2 RPG Stubs')
e mettiamo tutto in lista librerie:
ADDLIBLE LIB(LIBHTTP)

ADDLIBLE LIB(WSDL2RPG)

ADDLIBLE LIB(ZENEXMPL)
WSDL2RPG è un utility che crea le procedure necessarie ad assemblare la SOAP Envelope ed effettua la chiamata tramite HTTPAPI. I moduli creati vanno compilati ed assemblati in un service program che andrà collegato ai nostri programmi.
Per creare i sorgenti necessari usiamo il comando:
WSDL2RPG URL('http://localhost:10000/MTOMService/services/archiveServer?wsdl') SRCFILE(ZENEXMPL/QWSDL) SRCMBR(MTOM01) TYPE(*STUB) STRLEN(256) ATTACHMENT(*YES) PARMSTRUCT(*STMF) STRUCTSTMF('/tmp/MTOM01.log' *YES)
e selezioniamo con 1 tutti i metodi esposti dal servizio web: stiamo dicendo all'utility di creare il necessario per l'interrogazione di entrambi i metodi, con il supporto per l'invio di allegati.
Vengono creati i seguenti sorgenti nel file creato prima:
MTOM01      RPGLE       Web Service: ArchiveServerPort
MTOM01001   RPGLE       Web Service: archiveFile()
MTOM01002   RPGLE       Web Service: getFile()
Da questi sorgenti vanno creati i moduli, ma prima sono necessarie delle modifiche per attivare la ricezione MTOM, dato che - come dicevo prima - è sperimentale. Apriamo il sorgente MTOM01001:
STRSEU SRCFILE(ZENEXMPL/QWSDL) SRCMBR(MTOM01001)
(ma potete anche usare RDi.)

Dobbiamo cercare l'istruzione:
g_hMsgCtx = MessageContext_new();
e modificarla così:
g_hMsgCtx = MessageContext_new(cTrue);
(tutto qui.)

La stessa cosa va fatta anche per il sorgente MTOM01002.

Ora possiamo compilare:
CRTRPGMOD MODULE(ZENEXMPL/MTOM01) SRCFILE(ZENEXMPL/QWSDL) SRCMBR(MTOM01)

CRTRPGMOD MODULE(ZENEXMPL/MTOM01001) SRCFILE(ZENEXMPL/QWSDL) SRCMBR(MTOM01001)

CRTRPGMOD MODULE(ZENEXMPL/MTOM01002) SRCFILE(ZENEXMPL/QWSDL) SRCMBR(MTOM01002)
e creare il service program.
CRTSRVPGM SRVPGM(ZENEXMPL/MTOM01) MODULE(ZENEXMPL/MTOM01 ZENEXMPL/MTOM01001 ZENEXMPL/MTOM01002) EXPORT(*ALL) TEXT('MTOM Service Example') BNDSRVPGM((*LIBL/WSDL2RPGRT) (*LIBL/MIME) (*LIBL/HTTPMIME) (*LIBL/BASICS1)) BNDDIR(QC2LE)

 

Implementiamo.


L'utility ha creato le procedure necessarie per interrogare il nostro web service, e noi abbiamo compilato il programma di servizio per utilizzarle, in particolare la procedura:

 ArchiveServerPort_archiveFile 

effettua la chiamata al servizio archiveFile della nostra applicazione e se andiamo a indagare la sua definizione vediamo che richiede in ingresso:
  • una struttura dati i_tns_archiveFile di tipo tns_archiveFileRnmd_t : sono i parametri da passare al web service;
  • una struttura dati o_msg di tipo wsdl_errText_t : contiene eventuali messaggi di errore;

in uscita invece popola una struttura dati di tipo tns_archiveFileResponse_t che contiene la risposta del web service.

NB: le strutture dati tns_archiveFileRnmd_t e tns_archiveFileResponse_t sono modellate dall'utility secondo quanto è stato dichiarato nel WSDL, rispecchiano quindi la stessa struttura dello schema XML!

NB2: l'utility rinomina le procedure e le strutture dati secondo i nomi del nostro web service e i suoi metodi, normalmente quindi le procedure descritte sopra cambiano sempre nome per ogni web service di cui facciamo lo stub, ad esempio il template per i nomi visti prima sono:
  • procedura principale: <Nome_Web_Service>_<Nome_Metodo> ;
  • struttura dati ingresso: tns_<Nome_Metodo>Rnmd_t ;
  • struttura dati uscita: tns_<Nome_Metodo>Response_t .

Se non abbiamo idea di come utilizzare questa procedura e non vogliamo partire da zero, niente paura! WSDL2RPG ci permette di costruire in automatico un programma di esempio con il comando:
WSDL2RPG URL('http://localhost:10000/MTOMService/services/archiveServer?wsdl') SRCFILE(ZENEXMPL/QWSDL) SRCMBR(MTOM01001T) TYPE(*PGM) STUB(MTOM01001)
e selezionando il metodo archiveFile e ancora:
WSDL2RPG URL('http://localhost:10000/MTOMService/services/archiveServer?wsdl') SRCFILE(ZENEXMPL/QWSDL) SRCMBR(MTOM01002T) TYPE(*PGM) STUB(MTOM01002)
e selezionando il metodo getFile.

Ora abbiamo due sorgenti di due programmi che testano le due procedure, dobbiamo solo modificare qualche riga per poter fargli fare quello che vogliamo. Iniziamo dal programma per fare l'upload: MTOM01001T.
STRSEU SRCFILE(ZENEXMPL/QWSDL) SRCMBR(MTOM01001T)
(come prima, potete usare RDi)

Prima di tutto inseriamo dei parametri di ingresso, in modo che il programma possa essere utilizzato in maniera variabile (al momento i parametri sono fissi) tramite un comando, cerchiamo:
D MTOM01001T...
D                 PR
e
D MTOM01001T...
D                 PI
e sotto inseriamo:
D description                         like(
D                                     tns_archiveRequest_t.description)
D fileLoad                            like(
D                                     tns_archiveRequest_t.fileLoad)
D fileName                            like(
D                                     tns_archiveRequest_t.fileName)
D fileType                            like(
D                                     tns_archiveRequest_t.fileType)
D key                                 like(
D                                     tns_archiveRequest_t.key)
Quindi spostiamo i parametri in ingresso nella struttura dati che contiene i parametri della Request, che diventa:
parameters.request.fileName = fileName;
parameters.request.fileType = 'application/pdf';
parameters.request.key = key;
parameters.request.description = description;
alla fine aggiungiamo il nostro allegato:
parameters.request.fileLoad = 'cid:'+
     ArchiveServerPort_archiveFile_Attachments_addFile(
        filePath: parameters.request.fileType);
NB: la procedura ArchiveServerPort_archiveFile_Attachments_addFile serve per aggiungere un allegato, richiede il percorso del file sull'IFS e il suo MIME-Type e ritorna l'identificativo della sezione http dove verrà inserito, come al solito il nome può cambiare e il template è:
<Nome_Web_Service>_<Nome_Metodo>_Attachments_addFile.

Ora tocca al programma per fare il download: MTOM01002T.
STRSEU SRCFILE(ZENEXMPL/QWSDL) SRCMBR(MTOM01002T)
In maniera simile a prima aggiungiamo i parametri, che è uno solo: la chiave del documento registrata in upload. Per cui cerchiamo:
D MTOM01002T...
D                 PR
e
D MTOM01002T...
D                 PI
e sotto inseriamo:
D key                                 like(
D                                     tns_getFile_t.key)
E poi facciamo sempre in modo che il nostro input finisca nei parametri della Request:
parameters.key = key;

Ora possiamo compilare:
CRTRPGMOD MODULE(ZENEXMPL/MTOM01001T) SRCFILE(*LIBL/QWSDL) SRCMBR(MTOM01001T) DBGVIEW(*LIST) TRUNCNBR(*NO)

CRTPGM PGM(ZENEXMPL/MTOM01001T) MODULE(ZENEXMPL/MTOM01001T) TEXT('MTOM Service Example - Test Upload') BNDSRVPGM((ZENEXMPL/MTOM01)) BNDDIR(QC2LE) ACTGRP(*NEW)

CRTRPGMOD MODULE(ZENEXMPL/MTOM01002T) SRCFILE(*LIBL/QWSDL) SRCMBR(MTOM01002T) DBGVIEW(*LIST) TRUNCNBR(*NO)

CRTPGM PGM(ZENEXMPL/MTOM01002T) MODULE(ZENEXMPL/MTOM01002T) TEXT('MTOM Service Example - Test Download') BNDSRVPGM((ZENEXMPL/MTOM01)) BNDDIR(QC2LE) ACTGRP(*NEW)

Abbiamo i nostri due programmi di test, ma potrebbe essere utile fare due comandi per poterli utilizzare, iniziamo creando il comando per inviare:
STRSEU SRCFILE(ZENEXMPL/QWSDL) SRCMBR(SNDMTOM) TYPE(CMD) TEXT('Web Service: archiveFile() - Upload Comand')
e modifichiamo il sorgente SNDMTOM così:
CMD        PROMPT('MTOM Service Upload')

PARM       KWD(DESC)              +
           MIN(1)                 +
           TYPE(*CHAR)            +
           VARY(*YES *INT2)       +
           LEN(128)               +
           CASE(*MIXED)           +
           PROMPT('File description' 1)

PARM       KWD(PATH)              +
           MIN(1)                 +
           TYPE(*CHAR)            +
           VARY(*YES *INT2)       +
           LEN(128)               +
           CASE(*MIXED)           +
           PROMPT('File path' 2)

PARM       KWD(NAME)              +
           MIN(1)                 +
           TYPE(*CHAR)            +
           VARY(*YES *INT2)       +  
           LEN(128)               +
           CASE(*MIXED)           +
           PROMPT('File name' 3)

PARM       KWD(TYPE)              +
           MIN(1)                 +
           TYPE(*CHAR)            +
           VARY(*YES *INT2)       +
           LEN(128)               +
           CASE(*MIXED)           +
           PROMPT('File type' 4)

PARM       KWD(KEY)              +
           MIN(1)                 +
           TYPE(*CHAR)            +
           VARY(*YES *INT2)       +
           LEN(20)                +
           CASE(*MIXED)           +
           PROMPT('Key' 5)
Stessa cosa per il comando per ricevere: RCVMTOM.
STRSEU SRCFILE(ZENEXMPL/QWSDL) SRCMBR(RCVMTOM) TYPE(CMD) TEXT('Web Service: archiveFile() - Download Comand')
e quindi:
CMD        PROMPT('MTOM Service Download')

PARM       KWD(KEY)              +
           MIN(1)                 +
           TYPE(*CHAR)            +
           VARY(*YES *INT2)       +
           LEN(20)                +
           CASE(*MIXED)           +
           PROMPT('Key' 1)
Infine non ci resta che creare anche questi oggetti:
CRTCMD CMD(ZENEXMPL/SNDMTOM) PGM(ZENEXMPL/MTOM01001T) SRCFILE(ZENEXMPL/QWSDL) SRCMBR(SNDMTOM)

CRTCMD CMD(ZENEXMPL/RCVMTOM) PGM(ZENEXMPL/MTOM01002T) SRCFILE(ZENEXMPL/QWSDL) SRCMBR(RCVMTOM)

Ora possiamo testare i nostri programmi! Assicuriamoci di avere un file PDF da utilizzare per i nostri scopi salvato su IFS, per comodità io mi riferirò a un ipotetico file testfile.pdf presente in /tmp, iniziamo inviando il file:
ZENEXMPL/SNDMTOM DESC('Test file.') PATH('/tmp/testfile.pdf') NAME(renamed.pdf) TYPE('application/pdf') KEY(TEST1)
(lo salviamo sotto altro nome, giusto per provare.)

Il messaggio *** Success *** in fondo al terminale ci indica che la trasmissione è avvenuta, ma dove è stato salvato il file? Ricordate che l'applicazione stampa in output il path di archiviazione? Però questo output non è interattivo, è salvato in un file apposito nel percorso dell'istanza del server:
DSPF STMF('/www/INTAPPSVR/lwi/logs/lwistdout.txt')
dall'output notiamo che i file temporanei sono gestiti per singola istanza, per cui troviamo il file salvato qui:
DSPF STMF('/www/INTAPPSVR/lwi/temp/uploaded/renamed.pdf')
Ora quindi proviamo a recuperare il file appena archiviato:
ZENEXMPL/RCVMTOM KEY(TEST1)
Ancora, il messaggio *** Success *** ci indica che tutto è ok, questa volta però spetta alla nostra procedura salvare il file, che di default salva tutti gli allegati in /tmp/attachments:
WRKLNK OBJ('/tmp/attachments')

Considerazioni.


La funzione MTOM dell'utility è un'aggiunta interessante ed è stato divertente approcciarsi a questa metodologia e farla funzionare su IBMi, ricordo però che è ancora in fase sperimentale, e non è quindi esente da mancanze e bug che ne potrebbero limitare l'usabilità, nel caso vi invito a contattare l'autore per segnalare eventuali problemi che potrebbero essere corretti in una futura release!

Di seguito alcuni link utili:
  • il sito di Raddatz ha una serie di manuali che spiegano più ampiamente l'utility e le sue procedure;
  • la mailing list di Klement: è per avere aiuto per HTTPAPI, però viene data risposta anche a domande su WSDL2RPG;
  • Il repository dove ho rilasciato i sorgenti di questo esempio: i sorgenti vanno importati poi a mano nella libreria ZENEXMPL, oppure ho preparato un SAVF che può essere ripristinato sulla macchina (salvato come V7R1M0);
  • Configurazioni particolari per fa funzionare CXF su WebSphere;
  • Manuale dello IAS.

mercoledì 25 ottobre 2017

Inviare allegati a un web service con MTOM. (parte uno... per ora senza IBMi)

Se pensate a come allegare un grosso file in una SOAP envelope immaginerete di creare un campo BLOB codificato in base64. La codifica è utile per rispettare le specifiche (senza infilare nell'XML caratteri speciali), ma ha la brutta abitudine di diventare enorme rispetto al contenuto effettivo del file. MTOM è un meccanismo semplice e soprattutto standard per migliorare questa trasmissione e sfrutta un altro standard, XOP: in pratica, il file viene serializzato come parte della chiamata http, come se fosse un normale form html.

AGGIORNAMENTO 14/11/2017: e stata pubblicata la seconda parte qui.

(Vi apettavate una barra di sapone? Spiacente, uso quello liquido)

Di seguito vediamo come implementare un semplice sistema di archiviazione che sfrutta questa serializzazione. Il producer riceverà i file e li salverà associandoli a una determinata chiave, e sarà in grado di inviarli alla richiesta della stessa chiave, il consumer invierà e richiederà i file al servizio.

In questa prima parte il producer verrà fatto girare in locale su Tomcat, mentre il ruolo del consumer viene giocato da SoapUI che costruirà da solo il necessario per fare la chiamata e testare il tutto, nella seconda parte vedremo come implementare un consumer con RPG e di far girare tutto, compreso il producer, su IBMi.

IL PRODUCER.


Prepariamo il necessario.


Di cosa abbiamo bisogno:

  • Java JDK 6 (bisogna registrarsi);
  • Eclipse Oxygen for JEE Developer (ho utilizzato la versione 4.7.1a);
  • Maven (ho utilizzato la versione 3.2.5);
  • Tomcat (ho utilizzato la versione 8.5), per provare il progetto in locale;

NB: le versioni delle librerie utilizzate sono compatibili con Java 6, questo per poter poi installare il tutto sulla versione 8.1 del server delle applicazioni integrato dell' IBMi, come vedremo in seguito.

Installate tutto quanto, in particolare Maven e Tomcat andranno estratti ognuno in una sua cartella alle quali, per comodità, mi riferirò come $MAVEN_HOME e $TOMCAT_HOME, mentre la cartella di installazione di Java sarà $JAVA_HOME.
Per prima cosa impostiamo Eclipse:

  • per Java: dal menu Windows > Java > Installed JREs > Add selezioniamo "Standard VM" e andiamo avanti, poi "Directory..." e andiamo a cercare $JAVA_HOME, alla fine impostiamo come JRE di default;
  • per Maven: dal menu Windows > Preferences > Maven > Installations > Add selezioniamo "External" e poi "Directory..." e andiamo a cercare $MAVEN_HOME;
  • per Tomcat: dal menu Windows > Preferences > Server > Runtime Environments > Add  selezioniamo "Apache Tomcat 8.5" e andiamo avanti, quindi "Browse..." e andiamo a cercare $TOMCAT_HOME, viene proposto di lavorare con la JRE di default definita prima.

Ok, procediamo. Creiamo un nuovo progetto Maven, dal menu File > New > Maven Project, chiediamo di non utilizzare un archetipo e andiamo avanti in questo modo:


Diamo almeno un nome gruppo e un nome artefatto al nostro progetto, il packaging da utilizzare invece è war:


e finalizziamo.

Come prima cosa Eclipse ci avverte che nel nuovo progetto manca il descrittore web.xml, per cui creiamone uno cliccando con il tasto destro sul progetto e quindi Java EE Tools > Generate Deployment Descriptor Stub.

Come seconda cosa dobbiamo inserire un po' di dipendenze e plugin nel progetto, fortuna che Maven si occupa di reperire tutte le librerie necessarie! A patto ovviamente di farglielo sapere indicandolo nel suo descrittore, apriamo quindi il file pom.xml e inseriamo questo dentro al tag <project>:
<build>
 <pluginManagement>
  <plugins>
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.6.1</version>
    <configuration>
     <source>1.6</source>
     <target>1.6</target>
    </configuration>
   </plugin>
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-war-plugin</artifactId>
    <version>3.0.0</version>
    <configuration>
     <warSourceDirectory>src/main/webapp</warSourceDirectory>
     <webXml>src/main/webapp/WEB-INF/web.xml</webXml>
     <warName>MTOMService</warName>
    </configuration>
   </plugin>
  </plugins>
 </pluginManagement>
</build>
In questo modo aggiungiamo qualche plugin necessario a compilare il progetto, istruito per creare un file di nome MTOMService.war .

Sempre nello stesso tag <project> aggiungiamo anche qualche dipendenza:
<dependencies>
 <dependency>
  <groupId>org.apache.cxf</groupId>
  <artifactId>cxf-rt-frontend-jaxws</artifactId>
  <version>2.5.2</version>
 </dependency>
 <dependency>
  <groupId>org.apache.cxf</groupId>
  <artifactId>cxf-rt-transports-http</artifactId>
  <version>2.5.2</version>
 </dependency>
 <dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-core</artifactId>
  <version>3.0.6.RELEASE</version>
 </dependency>
 <dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-web</artifactId>
  <version>3.0.6.RELEASE</version>
 </dependency>
 <dependency>
  <groupId>org.apache.servicemix.bundles</groupId>
  <artifactId>org.apache.servicemix.bundles.saaj-impl</artifactId>
  <version>1.3.18_1</version>
 </dependency>
</dependencies>
Useremo Spring Framework per instanziare una servlet CXF, inoltre introduciamo una dipendenza a SAAJ implementato da Apache, in quanto l'implementazione di IBM integrata con il server delle applicazioni non è completamente compatibile con CXF.

NB: le versioni utilizzate sopra sono sempre quelle compatibili con Java 6, il motivo è sempre lo stesso.

Ultime configurazioni e poi implementiamo, giuro, per ora apriamo il Deployment Descriptor:


e sostituiamolo completamente in questo modo:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
  <display-name>MTOMService</display-name>
  <servlet>
    <servlet-name>cxfservlet</servlet-name>
    <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>cxfservlet</servlet-name>
    <url-pattern>/services/*</url-pattern>
  </servlet-mapping>
</web-app>

Implementiamo il sistema.


Mettiamo le mani in pasta, finalmente, essendo questo un esempio molto semplice, abbiamo bisogno di scrivere solo tre classi e per comodità mettiamole quindi sotto un unico package che io ho chiamato it.zenovalle.examples.

Per prima creiamo una semplice classe di proprietà (POJO) che descrive il documento che verrà scambiato, oltre al contenuto del documento stesso.

ArchiveRequest.java
package it.zenovalle.examples;

import javax.activation.DataHandler;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlMimeType;
import javax.xml.bind.annotation.XmlType;

@XmlType
@XmlAccessorType(XmlAccessType.FIELD)
public class ArchiveRequest {
 
 @XmlMimeType("application/octet-stream")
 protected DataHandler fileLoad;
 protected String fileName;
 protected String fileType;
 protected String key;
 protected String description;
 
 public DataHandler getFileLoad() {
  return fileLoad;
 }
 
 public void setFileLoad(DataHandler fileLoad) {
  this.fileLoad = fileLoad;
 }
 
 public String getFileName() {
  return fileName;
 }
 
 public void setFileName(String fileName) {
  this.fileName = fileName;
 }
 
 public String getFileType() {
  return fileType;
 }
 
 public void setFileType(String fileType) {
  this.fileType = fileType;
 }
 
 public String getKey() {
  return key;
 }
 
 public void setKey(String key) {
  this.key = key;
 }
 
 public String getDescription() {
  return description;
 }
 
 public void setDescription(String description) {
  this.description = description;
 }
 
}
Vi faccio solo notare che:
  • l'intera classe deve essere annotata come XmlType e con tipo di accesso FIELD;
  • la proprietà che contiene i dati del file è di tipo DataHandler invece di byte[], questo ci permette di trattare meglio i dati, mantenendo il meccanismo di conversione automatico;
  • la conversione automatica viene effettuata marchiando la proprietà con un'annotazione del tipo @XmlMimeType("application/octet-stream") che spiega in che formato attendersi i dati (binario, per l'appunto).
Ora scriviamo l'interfaccia con cui si presenta il Web Service.

ArchiveServer.java
package it.zenovalle.examples;

import javax.activation.DataHandler;
import javax.jws.WebParam;
import javax.jws.WebService;

@WebService
public interface ArchiveServer {

 public String archiveFile(@WebParam(name="request") ArchiveRequest request);
 
 public DataHandler getFile(@WebParam(name="key") String key);

}
Anche in questo caso utilizziamo le annotazioni per far capire al sistema che l'interfaccia è un @WebService e anche per rinominare i parametri dei vari metodi con @WebParam(name="***").

Infine implementiamo l'interfaccia con una classe vera e propria.

ArchiveServerImpl.java
package it.zenovalle.examples;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.LinkedHashMap;
import java.util.Map;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.jws.WebService;
import javax.xml.bind.annotation.XmlMimeType;
import javax.xml.ws.WebServiceException;

@WebService(endpointInterface = "it.zenovalle.examples.ArchiveServer",
            serviceName = "ArchiveServer")
public class ArchiveServerImpl implements ArchiveServer{

 Map<String, File> files = new LinkedHashMap<String, File>();
 private File tmpdir = new File(System.getProperty("java.io.tmpdir"),"uploaded");

 @Override
 public String archiveFile(ArchiveRequest request) {

  tmpdir.mkdir();

  if(request==null){
   throw new WebServiceException("Upload Failed");
  }

  File file = new File(tmpdir, request.getFileName());

  try {
   InputStream is = request.getFileLoad().getInputStream();
   OutputStream os = new FileOutputStream(file);

   file.createNewFile();

   byte[] b = new byte[100000];
   int bytesRead = 0;
   while ((bytesRead = is.read(b)) != -1) {
    os.write(b, 0, bytesRead);
   }
   
   is.close();
   os.flush();
   os.close();

   files.put(request.getKey(), file);
   
   System.out.println("File "+ request.getFileType() +" archived in " + file.getPath() + " - " +request.getKey() +" "+request.getDescription());

  } catch (IOException e) {
   e.printStackTrace();
   return "Upload Failed";
  }

  return "Upload Complete";
 }

 @Override
 public
 @XmlMimeType("application/octet-stream") DataHandler getFile(String key) {

  File file = files.get(key);
  DataSource dataSource = new FileDataSource(file);
  System.out.println("Returning " + key + " : "+ file.getPath());
  return new DataHandler(dataSource);
 }
}
Ancora l'annotazione @WebService, questa volta però con alcune informazioni in più su quale interfaccia viene implementata (che viene utilizzata come endpoint, definisce quindi il contratto WSDL) e su come si chiama il servizio. Viene anche annotato il tipo di ritorno DataHandler del metodo getFile in maniera che possa essere serializzato anch'esso con MTOM.

L'implementazione riceve il DataHandler e ne ricava l'InputStream da cui leggere i dati che sono poi scritti in un File creato nella sotto-cartella uploaded della cartella temporanea di sistema. La chiave viene associata alla posizione tramite una Map che risiede in memoria, se viene richiesto il download il web service interroga la Map per sapere che File aprire.

Ok, manca solo di chiedere a Spring di farci il favore di instanziare la servlet, creiamo il file cxf-servlet.xml sotto src > main > webapp > WEB-INF descritto in questo modo:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws"
 xmlns:cxf="http://cxf.apache.org/core" xmlns:soap="http://cxf.apache.org/bindings/soap"
 xsi:schemaLocation="http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://cxf.apache.org/bindings/soap http://cxf.apache.org/schemas/configuration/soap.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">

 <jaxws:server id="archiveServer"
  serviceClass="it.zenovalle.examples.ArchiveServer" address="/archiveServer">
  <jaxws:serviceBean>
   <bean class="it.zenovalle.examples.ArchiveServerImpl" />
  </jaxws:serviceBean>
  <jaxws:properties>
   <entry key="mtom-enabled" value="true" />
   <entry key="attachment-directory" value="/tmp/"/>
   <entry key="attachment-memory-threshold" value="4000000"/>
  </jaxws:properties>
 </jaxws:server>

</beans>
La proprietà più importante è mtom-enabled che deve essere messa a true per far funzionare tutto il meccanismo, il resto delle proprietà è per indicare di trattare in memoria gli allegati fino a 4 MB, altrimenti li salva prima nella cartella temporanea.

Ora abbiamo veramente tutto, quindi...

Compiliamo e avviamo


Con il tasto destro del mouse sul nostro progetto cerchiamo Run As... e poi Maven install, se tutto ok Maven assemblerà il file MTOMService.war nella cartella target.


Bene, ora facciamo partire tutto in locale, sempre tasto destro sul progetto, sempre Run As... , questa volta scegliamo Run on Server e scegliamo il runtime Tomcat configurato prima. Comparirà il browser interno a Eclipse che cercherà di interrogare la servlet, inizialmente con scarso successo:


E sufficiente aggiungere il suffisso services all'URL per ottenere un risultato migliore:

La lista dei servizi offerti dalla servlet.

In particolare il link che la pagina ci invita a cliccare (cliccate!) non è nient'altro che il WSDL messo a disposizione dal Web Service: copiate questo link negli appunti, ci sarà utile a breve.

IL CONSUMER.


Di cosa abbiamo bisogno:
  • SoapUI (ho usato la versione 5.3.0).

SoapUI è un benchmark molto ben fatto ed OpenSource che ci permette di testare i nostri Web Service, può essere usato sia per SOAP che per REST, e permette anche di inviare allegati con MTOM (con qualche accorgimento, come vedremo). Può ricoprire benissimo il ruolo del nostro consumer in modo da avere finalmente la soddisfazione di vedere funzionare il tutto.

Una volta installato apriamolo e chiediamo subito di creare un nuovo progetto SOAP, con il tastone posizionato in alto, diamo un nome al progetto e incolliamo il link al WSDL copiato prima:


E diamo l'ok, SoapUI penserà ad analizzare il WSDL e creerà l'ambiente necessario per testare i vari metodi messi a disposizione del web service.

Espandiamo il nodo corrispondente al metodo archiveFile, il programma ha già creato una Request di default che possiamo rimaneggiare per fare un test. Normalmente la schermata è separata in due, in una parte possiamo modificare la Request, nell'altra vedremo la risposta data dal web service, per prima cosa abilitiamo MTOM nelle proprietà della Request:

Enable MTOM = true

Poi inseriamo dei valori di prova nei vari tag della SOAP Envelope, tranne per ora fileLoad. Per inserire un allegato facciamo click sul tab Attachments sotto la Request e poi sul tasto + :

(Nell'esempio ipotizzo che venga scelto un file pdf)

NB: il programma ci chiede di inserire in cache il documento, vi direi di rispondere di sì, in questo modo viene inglobato nel progetto SoapUI che poi potrete salvare.

Ora modifichiamo il tag fileLoad inserendo il nome del file scelto, ma preservando il prefisso cid: (nel mio esempio: cid:testfile.pdf ), torniamo in Attachments, scegliamo Part e vediamo che il nostro cid viene elencato tra quelli assegnabili all'allegato:

Il Type viene modificato automaticamente in XOP, proprio quello che ci serve per MTOM.

Ora è tutto pronto, clicchiamo sulla freccettina verde sopra alla Request per far partire la richiesta e, se ok, nella Response dovrebbe comparire "Upload Complete":

Sento scatenarsi le endorfine...

Spostiamoci sulla console di Eclipse, sul quale attendeva silenzioso Tomcat, e dovremmo trovare un output generato dal nostro servizio:

File application/pdf archived in C:\Users\<user>\AppData\Local\Temp\uploaded\testfile.pdf - 1234567890 This is a test file

Che sono proprio i dati che sono stati passati! Come controprova, interroghiamo la posizione e cerchiamo il file, ed eccolo qui:


Rimane solo da verificare il download! Come prima espandiamo getFile, abilitiamo MTOM e modifichiamo il tag key con la stessa chiave inserita prima (nell'esempio: 1234567890) e avviamo:


Troviamo sempre l'allegato in Attachments, ma questa volta sotto la Response, con doppio click possiamo esaminarlo: è proprio lui e come maggior conferma che sia quello caricato prima, vediamo la console di Eclipse:

Returning 1234567890 : C:\Users\<user>\AppData\Local\Temp\uploaded\testfile.pdf

Questo è tutto, per semplicità ho inserito un file di salvataggio del progetto SoapUI (MTOMService-soapui-project.xml) nel repository apposito che contiene tutti i sorgenti mostrati nel post, nel caso potete importare tutto come progetto Eclipse usando EGit.

Nella prossima parte vedremo come prendere tutto e portarlo su IBMi.

PS: dopo tutta questa spiegazione, non vi è rimasta la curiosità di capire com'è fatta in fin dei conti una trasmissione MTOM? Potete vederlo facilmente da SoapUI, nella Request di archiveFile, dopo aver fatto partire la richiesta e cliccando sul tag Raw poco sotto:

No BLOB, No Base64, baby.


Aggiungo qualche link per approfondire l'argomento:

Aggiornamento 25/10/2017 h 22:35 : Aggiunta bibliografia.

lunedì 2 ottobre 2017

Tu (in batch) non puoi passare! O forse sì...

SFTP è un protocollo per lo scambio file in maniera sicura, in quanto usa ssh come layer di trasporto di dati crittografati, è molto, molto più sicuro di un normale FTP e viene quindi spesso richiesto come metodo di scambio file. Sul nostro IBM i è facilmente utilizzabile a patto di avere installato i programmi su licenza 5733SC1 opzione *BASE - IBM Portable Utilities for i e 5733SC1 opzione 1 - OpenSSH, OpenSSL, zlib e chiaramente il PASE (qui su come verificare).

Una volta installate le opzioni collegarsi a un server sftp è semplice, poniamo ad esempio di doverci collegare a host.ext, e ci sono stati dati utente e password:
CALL QP2TERM
sftp utente@host.ext
al primo collegamento con host.ext verrà richiesto se inserire l'indirizzo tra quelli affidabili a cui collegarsi, per cui digitate yes e poi verrà richiesta la password, quindi ora potete inserire i comandi per lo scambio di file.

Ok... ma se dovete farlo in batch? Il comando sftp permette di lanciare dei comandi contenuti in uno script apposito... abbiamo ad esempio il nostro file scambio.txt salvato sulla root dell'IFS e deve essere inviato sulla cartella remota /scambiofile, potremmo scrivere la lista dei comandi necessari e salvarla in un altro file chiamato script.txt (per comodità salvato sempre sulla root), per cui:
EDTF STMF('/script.txt')
e inseriamo
cd /scambiofile
put /scambio.txt
il comando quindi diventa:
sftp -b/script.txt utente@host.ext
questa volta i comandi vengono eseguiti in automatico, il problema è che chiede comunque la password! Chiedere a qualcuno di effettuare l'autenticazione ogni volta potrebbe non essere una soluzione praticabile, l'ideale è che il processo parta completamente in automatico e non interattivo, di seguito illustro un paio di soluzioni, partendo dalla più sicura.

AUTENTICAZIONE TRAMITE CERTIFICATI


Si tratta di creare una coppia di chiavi di crittografia, una privata e una pubblica, i certificati generati sono personali e vi identificano in maniera univoca, per cui possono essere usati per l'autenticazione.
Dovete consegnare la parte pubblica al gestore del servizio SFTP affinché l'aggiunga alla lista delle chiavi accettate, sempre che sia possibile utilizzare questa tipologia da parte loro.

Prima di tutto dovreste collegarvi con l'utente che intendete usare per effettuare il collegamento batch, e se non l'avete già fatto collegatevi manualmente a host.ext con un comando sftp o ssh, lo scopo è di salvare il certificato del sito tra quelli riconosciuti e creare i certificati nella home dell'utente corretto.

Preferisco le mie chiavi come preferisco il caffè: lunghe, per crearle digitiamo a terminale:
CALL QP2TERM

ssh-keygen -b 4096
(crea una chiave lunga 4096, lo standard è invece 2048)

Accettate il nome di default delle chiavi (id_rsa per la chiave privata, id_rsa.pub per la chiave pubblica) e non inserite nessuna passphrase.

A questo punto dovreste consegnare al provider il file con la chiave pubblica id_rsa.pub affinché lo importino nel loro server, oppure potreste provare a farlo da soli sfruttando lo stesso collegamento ssh:
CALL QP2TERM

scp ~/.ssh/id_rsa.pub utente@host.ext:~/.ssh/authorized_keys
ed inserite la password, per questa volta.
Se ok, se provate a ricollegarvi via sftp o ssh, non dovrebbe chiedervi password ma dirvi che è stato accettato il vostro certificato ("Authenticating with public key"), ora non vi resta che mettere in piedi il vostro servizio di scambio in batch senza che resti bloccato in attesa di interattività.


INSTALLARE SSHPASS


Sshpass è una utility che simula il comportamento dell'utente e inserisce la password quando richiesta e, soprattutto, in batch. Andrà compilato e installato da sorgente, per cui per prima cosa verificate di avere il necessario per compilare sorgenti C - sto parlando di gcc e di make - altrimenti vi rimando alla mia guida pubblicata qui, in particolare fate attenzione ad inserire le variabili d'ambiente PASE_PATH e PATH: saranno utili dopo per trovare il comando sshpass.

Innanzitutto scarichiamo l'ultima release dei sorgenti compattati con in formato tar, al momento in cui scrivo è la 1.06; se avete seguito la guida citata prima dovreste avere la cartella /QOpensys/download e visto che mi piace l'ordine creiamo qui una sotto cartella dove tenere il file tar e una cartella dove inserire i file scompattati:
CALL QP2TERM

mkdir /Qopensys/download/tar

mkdir /QOpensys/opt/sshpass
Possiamo scaricare il file da pc e poi caricarlo via ftp su /Qopensys/download/tar e poi da terminale:
cd /Qopensys/download/tar

gtar -xvf sshpass-1.06.tar.gz -C /QOpensys/opt/sshpass
Spostiamoci sulla cartella appena scompattata e iniziamo il primo passo per lo compilazione, chi è pratico di sistemi unix/linux riconoscerà la famosa triade (./configure, make e make install), ma per compilare su IBM i sono necessarie alcune variazioni:
cd /QOpensys/opt/sshpass/sshpass-1.06

./configure
Completato il passo di configurazione viene creato il file config.h, questo file definisce un alias rpl_malloc che però su IBM i non trova, dobbiamo toglierlo manualmente per cui (da terminale normale):
EDTF STMF('/QOpenSys/opt/sshpass/sshpass-1.06/config.h')
trovate la riga:
#define malloc rpl_malloc
e commentatela:
/* #define malloc rpl_malloc */
ora possiamo proseguire:
gmake

gmake install
se digitate il comando sshpass dovrebbe comparire l'help:

(tutto ok)

Non resta che mettere in piedi il servizio batch, solo che a questo punto il comando per accedere via sftp diventa:
sshpass -p "password" sftp -oBatchMode=no -b/script.txt utente@host.ext

NB: sshpass vi costringe ad avere la password scritta da qualche parte, ed è quindi la soluzione giudicata come meno sicura. Dovrebbe essere utilizzata come ultima spiaggia in caso di impossibilità ad utilizzare i certificati.

(Mia reazione all'affermazione: "non siamo in grado di utilizzare le chiavi" ... successo veramente...)

mercoledì 5 luglio 2017

Un tunnel per la posta

Il server SMTP integrato con la nostra macchina è in grado di funzionare da relay di posta utilizzando un servizio esterno, ma molti provider offrono possibilità di crittografia e autenticazione limitate, spesso utilizzano semplicemente SSL su porta 465. IBMi, come altri altri server SMTP (vedi postfix), non supporta questo tipo di crittografia, e per la verità, hanno ragione loro.

Ciononostante rimane la necessità di utilizzare il servizio e instaurare un collegamento, vediamo di seguito come utilizzare un "trucchetto", installare sulla macchina un servizio di tunnelling che utilizza OpenSSL.

0. Ripasso: come configurare il server SMTP per il relay su altro servizio.


Le istruzioni sono ben specificate qui, non è intenzione di questo post spiegare nel dettaglio questi passaggi, ma li riepilogo solamente per capire come cambieranno in seguito:
  • se non si è già in possesso di un certificato rilasciato da qualche autorità, è possibile utilizzarne uno creato "in proprio", per cui prendiamo carta e penna e ... no apriamo il browser puntando sull' "IBM Navigator for i" e da lì cerchiamo il DCM, dobbiamo creare una autorità di certificazione locale (local CA). Creata la CA, creiamo il nostro certificato come se fosse rilasciato dalla nostra CA.
  • Assegniamo il certificato al client di posta, occhio che sia il client: dobbiamo far collegare il nostro server a un altro.
  • Già che ci siamo verifichiamo invece che l'authority che ha rilasciato il certificato utilizzato dal nostro provider sia tra quelle considerate affidabili, altrimenti dobbiamo farci dare il certificato principale della CA, importarlo e poi aggiungerlo alla trust list.
I prossimi passi possono essere eseguiti su "IBM Navigator for i", basta andare a cercare le impostazioni del server SMTP, oppure da riga di comando, di seguito scriverò solo i comandi su terminale (per comodità):
  • Modifichiamo le impostazione del server di posta per utilizzare il relay:
CHGSMTPA ALWAUTH(*RELAY) FWDHUBSVR('<host_server_di_posta_del_provider>')
  • Se diversa da 25, cambiamo anche la porta alla quale collegarsi, questo si configura come variabile d'ambiente:
ADDENVVAR ENVVAR('QIBM_SMTP_SERVER_PORT') VALUE('465') LEVEL(*SYS)
  • Aggiungiamo le informazioni di login:
ADDSMTPLE TYPE(*HOSTAUTH) HOSTNAME('<host_server_di_posta_del_provider>') USERNAME('<username>') PASSWORD('<password>')
  • riavviamo il server smtp
ENDTCPSVR *SMTP
STRTCPSVR *SMTP

Queste impostazioni abilitano la comunicazione via TLS, vediamo come impostare il tunnel.

1. Installate stunnel


Stunnel è un'utility per instaurare un servizio di tunnelling che utilizza OpenSSL per la crittografia, è possibile installarlo utilizzando i pacchetti messi a disposizione da Perzl, come descritto qui.

NB: vi consiglio di installare l'ultima versione, e per questo dovrete aggiornare l'utility di installazione, per questo consultate la guida in linea:
CALL QP2TERM
cd /QOpenSys/download
./wwwperzl.sh aix61 admin
Una volta aggiornato (ci mette un po') date i comandi:
./wwwperzl.sh aix61 list stunnel
lista i pacchetti disponibili tra cui:
...
stunnel-5.36-1.aix5.2.ppc.deps
...
e quindi:
./wwwperzl.sh aix61 wget stunnel-5.36-1.aix5.2.ppc

./wwwperzl.sh aix61 rpm stunnel-5.36-1.aix5.2.ppc

2. Configurate stunnel


Create un nuovo file nella posizione indicata sotto (da terminale AS):
EDTF STMF('/qopensys/opt/freeware/etc/stunnel/stunnel.conf')
e inserite i seguenti dati:
client = yes
foreground = no
fips = no

[smtps-wrapper]
accept = 11125
connect = <host_server_di_posta_del_provider>:465
Chiaramente '<host_server_di_posta_del_provider>' è l'host del server di posta del vostro provider (ve lo devo scrivere?)

Questo file di configurazione dice a stunnel di creare un servizio chiamato "stmps-wrapper" che ascolta in locale sulla porta 11125 e rimbalza la connessione sull'indirizzo remoto indicato alla porta 465.

Ora possiamo far partire il servizio:
CALL QP2TERM

stunnel
tutto qua, con WRKACTJOB potete vedere un lavoro in batch lanciato con il vostro utente con funzione "PGM-stunnel", oppure per maggiore controllo potete vedere il lavoro in ascolto sulla porta 11125 (NETSTAT e opzione 3)

3. Cambiate impostazione del server SMTP


Che non deve più inviare a '<host_server_di_posta_del_provider>' ma al nostro servizio, per cui:
CHGSMTPA ALWAUTH (*RELAY) FWDHUBSVR('localhost')

CHGENVVAR ENVVAR('QIBM_SMTP_SERVER_PORT') VALUE('11125') LEVEL(*SYS)

ADDSMTPLE TYPE(*HOSTAUTH) HOSTNAME('localhost') USERNAME('<username>') PASSWORD('<password>')
username e password sono sempre quelli del server remoto

e riavviate il server SMTP:
ENDTCPSVR *SMTP

STRTCPSVR *SMTP

Infine potete testare utilizzando i vostri comandi di posta, io utilizzo MMAIL, per cui:
MMAIL/EMLPTUMSG SUBJECT('test subject') FROMADDR('sender@yourmaildomain.com') TOADDR('receiver@othermaildomain.com') MSGTXT('test message')

4. Impostate il servizio automatico


Qui lascio libera scelta di come e quando far partire il servizio, tengo solo a precisare che è possibile utilizzare il comando:
QSH CMD('stunnel')

ed è tutto.

AGGIORNAMENTO 20/09/2017: aggiunto LEVEL(*SYS) ai comandi ADDENVVAR e CHGENVVAR, in questo modo la modifica è valida sempre.

AGGIORNAMENTO 20/09/2017: descrivo di seguito i passi per indagare il log di invio del servizio di posta, nel caso ci siano problemi (e visto che è un supplizio):
DSPJRN JRN(QZMF) OUTPUT(*OUTFILE) OUTFILE(QTEMP/LOG) OUTMBR(LOG) ENTDTALEN(512)

salva in QTEMP il file LOG che contiene le voci del giornale, e quindi:
RUNQRY *N QTEMP/LOG

per ispezionarlo, visto che non si capisce niente, vi rimando qua:
https://www.ibm.com/support/knowledgecenter/en/ssw_i5_54/rzair/rzairjournal.htm#rzairjournal__smtpclient
Vi serve vedere soprattutto i sottocodici che trovate nel campo:
JOESD      Dati specifici voce - Contenuti variabili

del file di log, se tutto è ok dovreste trovare i codici 88 e 82.

AGGIORNAMENTO 25/09/2018: modificati i comandi di installazione di stunnel per refuso


martedì 16 maggio 2017

Installiamo GhostPDL!

GhostPDL è la "versione completa" del più noto convertitore Ghostscript, permette la traduzione di file scritti in PCL, XPS e SVG, oltre a incorporare Ghostscript stesso, in questo post vediamo come installarlo su PASE utilizzando GCC.

1) Assicuratevi di avere tutto tutto.

AGGIORNAMENTO 25/03/2019: qui potete trovare un nuovo metodo di installazione delle utility supportato da IBM, se volete usare questo metodo, potete saltare al punto 2) e ricordarvi di utilizzare make al posto di gmake.

Chiaramente dovete avere il gcc, potete installare l'ultima versione che trovate disponibile come rpm da installare sul PASE, come descritto precedentemente qui. Dovete installare anche le utility consigliate, in particolare:
  • zip
  • unzip
  • curl
  • zlib
  • make
  • autoconf
  • tar
  • gzip
  • wget (nuova versione)
Deve esistere la directory /QOpenSys/usr/local , altrimenti createla:
CALL QP2TERM

mkdir /QOpenSys/usr/local
Se non l'avete già fatto aggiungete un link a questa directory:
ln -s /usr/local /QOpenSys/usr/local
Assicuratevi anche di aver impostato nel path il percorso in cui i programmi verranno installati:
WRKENVVAR LEVEL(*SYS)
dobbiamo verificare che non esistano le variabili d'ambiente PATH e PASE_PATH. Se non sono presenti si possono aggiungere:
ADDENVVAR ENVVAR(PASE_PATH)
VALUE('/QOpenSys/usr/local/bin:/usr/local/bin:/QOpenSys/usr/bin:
/usr/ccs/bin:/QOpenSys/usr/bin/X11:/usr/sbin:.:/usr/bin') LEVEL(*SYS)

ADDENVVAR ENVVAR(PATH)
VALUE('/usr/local/bin:/usr/bin:.:/QOpenSys/usr/local/bin:
/QOpenSys/usr/bin') LEVEL(*SYS)
se le variabili esistono vanno modificate aggiungendo :/QOpensys/usr/local/bin e :/usr/local/bin alla lista delle directory.
Se avete modificato le variabili d'ambiente è consigliabile rientrare come nuovo lavoro affinché le modifiche vengano recepite.
Infine assicuratevi di avere installata questa opzione dell'ambiente operativo:
5770SS1    13      Include apertura del sistema
per sapere come installare le opzioni di sistema, qui.

Ora avete tutto, andiamo avanti.

2) Scaricate l'ultima versione dal sito.

Per sapere l'ultima versione potete andare sul sito di Artifex, al momento in cui scrivo l'ultima versione disponibile è la 9.21, per cui la guida si riferirà a questa versione, ma di solito la procedura non cambia, visto che si basa sui soliti strumenti utilizzati per i sistemi UNIX , la famosa triade:

./configure 
make
make install

Prima spostiamoci nell'area download e poi scarichiamo:
cd /QOpenSys/download

wget --no-check-certificate https://github.com/ArtifexSoftware/
 ghostpdl-downloads/releases/download/gs921/ghostpdl-9.21.tar.gz
ora avete il file ghostpdl-9.21.tar.gz che sarà da scompattare, io preferisco tenere i programmi installati "manualmente" nella directory /opt , per cui magari create una directory per GhostPDL e poi scompattate:
mkdir /QOpenSys/opt/ghostpdl

gtar -xvf /QOpenSys/download/ghostpdl-9.21.tar.gz
 -C /QOpenSys/opt/ghostpdl
Dovrebbe essere stato creato il percorso /QOpenSys/opt/ghostpdl/ghostpdl-9.21/

3) Compilate e installate.

Prima spostatevi nel percorso creato:
cd /QOpenSys/opt/ghostpdl/ghostpdl-9.21/
E qui inserite la triade:
./configure
Per configurare l'installazione sul vostro sistema, poi:
gmake
(pausa caffè lunga)
per compilare i programmi, e infine:
gmake install
installa i programmi nella posizione.

N.B.: utilizzo gmake perché make cerca di compilare utilizzando il compilatore di sistema invece che il gcc.

4) Provate

Ora dovreste poter lanciare i programmi da ogni posizione, provate a spostarvi su root e lanciamo il programma per convertire dal PCL:
cd /
gpcl6
se ok dovrebbe mostrare la versione:
Usage: gpcl6 [option* file]+...
Options: -dNOPAUSE -E[#] -h -L<PCL|PCLXL> -K<maxK> -l<PCL5C|PCL5E|RTL> -Z...
         -sDEVICE=<dev> -g<W>x<H> -r<X>[x<Y>] -d{First|Last}Page=<#>
         -H<l>x<b>x<r>x<t> -dNOCACHE
         -sOutputFile=<file> (-s<option>=<string> | -d<option>[=<value>])*
         -J<PJL commands>
Version: 9.21
Build date: Tue May 16 09:51:06 2017
(ok)
PS: la medesima procedura può essere utilizzata anche per installare il solo Ghostscript, trovate i sorgenti qui.

AGGIORNAMENTO 25/03/2019: introdotta nuova metodologia di installazione delle utility mediante repository yum.

mercoledì 8 giugno 2016

Come installare utility per il PASE

Forse non tutti sanno che... la nostra amata macchina può anche essere gestita come partizione AIX sufficientemente emulata dall'applicazione PASE, installabile come opzione software su licenza
(5770SS1, opzione 3: Portable App Solutions Environment, qui la guida per l'installazione delle opzioni).

1. PASE e i suoi comandi

Certo, la sola installazione del PASE non ci permette di fare molto, se non la possibilità di lanciare qualche comando basilare, ad esempio lanciamo il terminale:
CALL QP2TERM
E scriviamo questo comando:
ps aux
Possiamo vedere i processi attivi. "E capirai", direste voi, "anche WRKACTJOB lo fa, e pure meglio", ma con un'occhiata più attenta noteremo che l'output mostra i processi all'interno del PASE, si comporta quindi come macchina a sé.
Come fare colloquiare le parti (i5/OS e PASE)? Oltre al già citato comando per aprire il terminale, ne esistono altri due per poter lanciare un comando su PASE e poi far tornare il controllo al chiamante:
CALL PGM(QP2SHELL) PARM('/path/to/cmd' 'parm1' 'parm2' ...)
CALL PGM(QP2SHELL2) PARM('/path/to/cmd' 'parm1' 'parm2' ...)
La differenza tra i due è che il primo viene eseguito in un nuovo gruppo d'attivazione tutto suo, mentre il secondo in quello del chiamante; il primo parametro è il percorso completo al comando shell, i seguenti sono i parametri del comando stesso.
Se non trovate proprio comodi questi comandi, sappiate che le utility installate per PASE sono utiilzzabili anche da QSHELL, passando a QSH il comando da lanciare, ad esempio:
QSH CMD('echo hello world')
io mi trovo meglio.

2. Preparare l'ambiente

Ok, dopo questa breve introduzione, possiamo passare a installare qualche altra funzione che amplia il PASE e rende più interessante il giochino.

IBM mette a disposizione un archivio di pacchetti rpm installabili su AIX (AIX Toolbox), e anche un file archivio che permette di avere il gestore di pacchetti RPM (appunto). Purtroppo questo archivio non è installabile tramite installp, visto che il comando non è presente nel PASE, si dovrebbe quindi estrarre con:
restore -xvqf rpm.rte
e poi configurare i vari collegamenti.
La via dell'installazione manuale non è proprio comoda, inoltre gli rpm vanno scaricati dal sito e caricati sulla macchina, per non parlare del fatto che le versioni delle utility sono piuttosto datate... .

Ci viene in aiuto il sito Young i Professional, che mette a disposizione degli script per il l'installazione di RPM e scaricamento dei pacchetti (tramite wget). I files, inoltre, non vengono scaricati da IBM ma da PERZL, un sito che raccoglie pacchetti più recenti, per i quali viene anche risolta una fastidiosa dipendenza con la libreria libiconv: qui la pagina con le istruzioni in inglese e i download.

Riporto qui le istruzioni in breve, i file da scaricare sono questi:
- download-2.0.tar.zip -> il contenuto dello zip va caricato su /QOpenSys, il file è un .tar che va a sua volta scompattato così:
CALL QP2TERM

cd /QOpenSys

tar -xf download-2.0.tar
viene creata quindi la directory /QOpenSys/download, il file .tar può essere anche cancellato;
- setup2.sh -> sostituisce quello in /QOpenSys/download;
- wwwperzl.sh ->  sostituisce quello in /QOpenSys/download;

quindi:
cd /QOpenSys/download
./setup2.sh

ATTENZIONE! - Aggiornamento (10/05/2017) : esistono aggiornamenti degli script di installazione, leggete in fondo al post prima di proseguire.

Ora si possono installare le utility usando lo script wwwperzl.sh, per sapere come funziona si può stampare un help:
CALL QP2TERM

cd /QOpenSys/download

./wwwperzl.sh help
Prima di procedere: tramite questo script è possibile installare gcc, con il quale a sua volta è possibile compilare altre utility scritte in C, partendo dal codice sorgente che viene messo a disposizione (un esempio: ghostPDL). I binari ottenuti da questi sorgenti vengono spesso installati in una directory specifica che in ambienti unix di solito è /usr/local/bin e che va quindi creata e inserita nel PATH:
CALL QP2TERM

mkdir /usr/local

mkdir /usr/local/bin
aggiungiamo anche un link a questa directory nella partizione del PASE:
ln -s /usr/local /QOpenSys/usr/local
usciamo dal terminale PASE e da riga di comando lanciamo:
WRKENVVAR LEVEL(*SYS)
dobbiamo verificare che non esistano le variabili d'ambiente PATH e PASE_PATH. Se non sono presenti si possono aggiungere:
ADDENVVAR ENVVAR(PASE_PATH)
VALUE('/QOpenSys/usr/local/bin:/usr/local/bin:/QOpenSys/usr/bin:
/usr/ccs/bin:/QOpenSys/usr/bin/X11:/usr/sbin:.:/usr/bin') LEVEL(*SYS)

ADDENVVAR ENVVAR(PATH)
VALUE('/usr/local/bin:/usr/bin:.:/QOpenSys/usr/local/bin:
/QOpenSys/usr/bin') LEVEL(*SYS)
se le variabili esistono vanno modificate aggiungendo :/usr/local/bin alla lista delle directory.
Se avete modificato le variabili d'ambiente è consigliabile rientrare come nuovo lavoro affinché le modifiche vengano recepite.

3. Installiamo cose

NB: le procedure descritte di seguito sono per il sistema operativo i5/OS versione V7R1, il quale PASE riesce ad emulare fino ad AIX 6.1. Per la corrispondenza tra versioni rimando alla pagina di Young i Professional oppure sui siti IBM, per l'installazione di versioni differenti, rimando invece all'help dello script: /wwwperzl.sh help.

Rientriamo nel PASE e posizioniamoci nella directory degli script:
CALL QP2TERM

cd /QOpenSys/download
Perzl mette a disposizione un sistema per risolvere il problema delle dipendenze tra i vari pacchetti, in pratica sono file di testo che contengono la lista di pacchetti da scaricare per una determinata versione. Questi files hanno estensione .deps e sono gestiti dallo script che interroga una lista salvata in locale.

Proviamo ad esempio a cercare il gcc, visto che l'abbiamo nominato prima:
./wwwperzl.sh aix61 list gcc
dovrebbe generare un output simile:
gcc-4.7.2-1.aix6.1.ppc.deps
gcc-4.8.0-1.aix6.1.ppc.deps
gcc-4.8.0-2.aix6.1.ppc.deps
gcc-4.8.1-1.aix6.1.ppc.deps
gcc-4.8.2-1.aix6.1.ppc.deps
...
l'ultima versione è quindi la 4.8.2-1, quindi:
./wwwperzl.sh aix61 wget gcc-4.8.2-1.aix6.1.ppc
senza .deps finale

verranno scaricati tutti i pacchetti che vanno poi installati con:
./wwwperzl.sh aix61 rpm gcc-4.8.2-1.aix6.1.ppc
per verificare l'installazione potete lanciare il comando:
gcc -v
tra le altre informazione stampa anche la versione del gcc:
...

gcc version 4.8.2 (GCC)
In maniera simile possiamo installare altre utility, io vi consiglio almeno queste:

ZIP
./wwwperzl.sh aix61 list zip

zip-3.0-1.aix5.1.ppc.deps

./wwwperzl.sh aix61 wget zip-3.0-1.aix5.1.ppc

./wwwperzl.sh aix61 rpm zip-3.0-1.aix5.1.ppc

UNZIP
./wwwperzl.sh aix61 list unzip

unzip-6.0-2.aix5.1.ppc.deps

./wwwperzl.sh aix61 wget unzip-6.0-2.aix5.1.ppc

./wwwperzl.sh aix61 rpm unzip-6.0-2.aix5.1.ppc

CURL
./wwwperzl.sh aix61 list curl

curl-7.27.0-1.aix5.1.ppc.deps

./wwwperzl.sh aix61 wget curl-7.27.0-1.aix5.1.ppc

./wwwperzl.sh aix61 rpm curl-7.27.0-1.aix5.1.ppc

IMAGEMAGICK
./wwwperzl.sh aix61 list ImageMagick

...
ImageMagick-Q16-6.8.1.10-1.aix5.1.ppc.deps
...

./wwwperzl.sh aix61 wget ImageMagick-Q16-6.8.1.10-1.aix5.1.ppc

./wwwperzl.sh aix61 rpm ImageMagick-Q16-6.8.1.10-1.aix5.1.ppc
(oppure potete scegliere la versione Q8)


ZLIB
./wwwperzl.sh aix61 list zlib

zlib-1.2.3-4.aix5.2.ppc.deps

./wwwperzl.sh aix61 wget zlib-1.2.3-4.aix5.2.ppc

./wwwperzl.sh aix61 rpm zlib-1.2.3-4.aix5.2.ppc
MAKE
./wwwperzl.sh aix61 list make

make-4.0-1.aix5.3.ppc.deps

./wwwperzl.sh aix61 wget make-4.0-1.aix5.3.ppc

./wwwperzl.sh aix61 rpm make-4.0-1.aix5.3.ppc
AUTOCONF
./wwwperzl.sh aix61 list autoconf

autoconf-2.69-1.aix5.1.ppc.deps

./wwwperzl.sh aix61 wget autoconf-2.69-1.aix5.1.ppc

./wwwperzl.sh aix61 rpm autoconf-2.69-1.aix5.1.ppc
TAR
./wwwperzl.sh aix61 list tar

tar-1.27-1.aix5.1.ppc.deps

./wwwperzl.sh aix61 wget tar-1.27-1.aix5.1.ppc

./wwwperzl.sh aix61 rpm tar-1.27-1.aix5.1.ppc
GZIP
./wwwperzl.sh aix61 list gzip

...
gzip-1.5-1.aix5.1.ppc.deps

./wwwperzl.sh aix61 wget gzip-1.5-1.aix5.1.ppc

./wwwperzl.sh aix61 rpm gzip-1.5-1.aix5.1.ppc

4. Conclusioni

Divertitevi? No davvero ora potete installare un sacco di utility, magari non tutte funzioneranno al primo colpo dopo l'installazione, ma sono comunque un sacco. Inoltre avendo installato gcc potete tentare di compilare altri programmi dei quali potete trovare il codice sorgente (dicevamo ghostPDL?): dipende solo dal vostro tempo a disposizione. Veramente, divertitevi.

5. Ho fatto casino, come disinstallo tutto?
CALL QP2TERM

/QOpenSys/download/wwwclean.sh

ln -s /usr/local /QOpenSys/usr/local

rm -r /usr/local 

(occhio a eventuali utility che avete installato a mano in /usr/local)

Poi dovete eliminare le variabile d'ambiente:
RMVENVVAR ENVVAR(PATH) LEVEL(*SYS)

RMVENVVAR ENVVAR(PASE_PATH) LEVEL(*SYS)

Aggiornamento (28/11/2016)

Modificati i percorsi delle variabili d'ambiente $PATH e $PASE_PATH per far risultare prima i comandi nelle librerie .../usr/local/bin .

Aggiornamento (10/05/2017)

Aaron Bartell ha rilasciato una versione aggiornata degli script, per installare il tutto il file da scaricare è qui sull'apposito repository su BitBucket. La procedura quindi diventa:
CALL QP2TERM

mkdir /QOpenSys/download
Quindi scompattare lo zip e caricare i file nella cartella appena creata, lo script di installazione è setup.sh e non setup2.sh :
cd /QOpenSys/download

./setup.sh
Poi procedere normalmente. Se avete già installato la versione precedente dei files, è sufficiente copiare il file wwwperzl.sh , la nuova versione risolve un bug che pregiudicava l'aggiornamento della lista dei file .deps e quindi non si potevano avere le ultime versioni delle utility dal sito Perzl.org.
Per sapere come aggiornare i .deps rimando all'apposito help:
cd /QOpenSys/download

./wwwperzl.sh aix61 admin

PS: esiste anche un metodo di installazione basato su chroot , opzione installabile con queste ptf, non ho avuto modo di installarle ancora (spero presto) per cui non conosco la procedura, la metodologia migliore per chi non ha gli ultimi aggiornamenti rimane quella illustrata in questo post.

Aggiornamento (22/06/2017)

Alcuni utenti hanno avuto problemi con l'ultima versione di make disponibile su Perzl (la versione del pacchetto make-4.1-1), in pratica se lanciate gmake dà poco simpatici errori, il problema risiede più che altro nel terminale del PASE (QP2TERM), mentre non ha dato problemi se lanciato da un terminale ssh. L'alternativa è piuttosto quella di rimanere sulla versione meno recente (pacchetto make-4.0-1), almeno per adesso; se avete installato la nuova versione e volete fare il downgrade dovrete fare manualmente, per cui:
CALL QP2TERM

cd /qopensys/download

rpm --ignoreos --ignorearch --nodeps --replacepkgs
    --oldpackage --replacefiles -hUv make-4.0-1.aix5.3.ppc.rpm

Aggiornamento (25/03/2019) 

Aggiunta sezione per disintallazione e pulizia.

lunedì 6 giugno 2016

Come installare software su licenza per i5/OS

Lascio qui questa breve, brevissima guida come riferimento agli articoli, potrebbe essere necessario collegarsi con QSECOFR o altro utente di alto profilo per lanciare i comandi descritti sotto, e comunque sicuramente per lanciare l'installazione.

Poniamo il caso che il programma da installare sia questo:
- codice: 5770SS1;
- opzione: 13;
- descrizione: Include apertura del sistema.

Per prima cosa dobbiamo verificare che il programma non sia già installato, dalla riga di comando digitare
GO LICPGM
e poi
10. Visualizzare i programmi su licenza installati
premere poi
F11=Vis.rel.
e ancora
F11=Vis. opz.
l'opzione risulta ora visibile nella lista:


In questo caso il programma è installato, se invece non compare dobbiamo procurarci il cd di installazione del sistema operativo che contiene il software, inserirlo nel lettore ottico e poi
GO LICPGM
11. Installazione dei programmi su licenza
cercare il codice e l'opzione giusti e
1=Installazione

La procedura prosegue al rilevamento del cd e installazione.