You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 70 Next »


La Baselib è una libreria usata dal team di sviluppo della Direzione Sistema Informativo, sulla quale sono costruiti tutti i nuovi applicativi di backend.
Si basa a sua volta su Spring Boot e la sua implementazione è stata necessaria per standardizzare i processi di autenticazione, autorizzazione, i flussi di lavoro e la relativa documentazioni delle API REST.

Base configuration

Quella che segue è un esempio di configurazione application.yml 

Configurazione di base
management:
  endpoints:
    web:
      exposure:
        include: "*"

server:
  port: 8080

http:
  client:
    ssl:
		#these file are embedded in library
      trust-store: /service.jts
      trust-store-password: service
it:
  infn:
    sisinfo:
      microservice:
		#microservices discovery strategy
        token:
          prefix: Bearer
          header: Authorization
		oidc:
          client:
            realm: ${OIDC_REALM:aai}
            auth-server: ${OIDC_SERVER:https://idp-test.app.infn.it/auth}
            client-id: ${SERVICE_ID:id}
            client-secret: ${SERVICE_SECRET:secret}
            enable: true

Service discovery

La Baselib ha un suo client interno basato su una personalizzazione del RestTemplare di Spring.
Tale client permette di chiamare una URL esterna oppure un alias.
Gli alias sono risolti in modo statico nella configurazione dell'applicazione, mediante le configurazioni sotto riportate:


Service Discovery
it:
  infn:
    sisinfo:
      microservice:        
		url-discovery-strategy: static
        url-discovery-map:
          (alias): (url)
		  ...

Per poter comunicare con altri servizi, o URL esterne, chiamando API REST, occorre fare l'autowire della classe "ApiCommunication".
Questa espone vari metodi, che vanno dall'esecuzione di API REST all'upload di file:

ApiCommunication
package it.infn.sisinfo.microservice.corelib.abstraction.api;

import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.scheduling.annotation.Async;

import java.io.InputStream;
import java.util.concurrent.CompletableFuture;

public interface ApiCommunication {
    /**
     * Enumeration for token type identification
     */
    public enum TokenType {
        TokenTypeApplication,
        TokenTypeUser
    }

    /**
     * Call and http rest api using the service registry
     *
     * @param method    is the http dialect used for call the api
     * @param tokenType is the token that need to be used for authenticate the api (Application or Logged user)
     * @param service   is the name of the service as registered in service registry
     * @param uri       is the api uri within the service
     * @param payload   is the payload to send to the api
     * @param retType   is the result type of the api(need to be mapped exactly with the return of the remote api)
     * @param <T>       the type of the class result of the oration
     * @return The answer of the api
     */
    public <T> T callAPI(HttpMethod method,
                         ApiCommunication.TokenType tokenType,
                         String service,
                         String uri,
                         Object payload,
                         ParameterizedTypeReference<T> retType,
                         Object... uriVariables) throws Exception;

    /**
     * Call and http rest api using the service registry
     *
     * @param method      is the http dialect used for call the api
     * @param tokenType   is the token that need to be used for authenticate the api (Application or Logged user)
     * @param service     is the name of the service as registered in service registry
     * @param uri         is the api uri within the service
     * @param payload     is the payload to send to the api
     * @param contentType is the payload content type
     * @param retType     is the result type of the api(need to be mapped exactly with the return of the remote api)
     * @param <T>         the type of the class result of the oration
     * @return The answer of the api
     */
    public <T> T callAPI(HttpMethod method,
                         ApiCommunication.TokenType tokenType,
                         String service,
                         String uri,
                         Object payload,
                         MediaType contentType,
                         ParameterizedTypeReference<T> retType,
                         Object... uriVariables) throws Exception;

    /**
     * Upload a file via input stream to a remote service api
     *
     * @param tokenType       is the token that need to be used for authenticate the api (Application or Logged user)
     * @param service         is the name of the service as registered in service registry
     * @param uri             is the api uri within the service
     * @param fileInputStream is the input file stream for the file
     * @param fileName        is the name eof the file
     * @param retType         is the result type of the api(need to be mapped exactly with the return of the remote api)
     * @param <T>             the type of the class result of the oration
     * @return The answer of the api
     * @throws Exception
     */
    public <T> T uploadToAPI(HttpMethod method,
                             ApiCommunication.TokenType tokenType,
                             String service,
                             String uri,
                             InputStream fileInputStream,
                             String fileName,
                             ParameterizedTypeReference<T> retType,
                             Object... uriVariables) throws Exception;

    /**
     * Call and http rest api using the service registry, is an async way
     *
     * @param method    is the http dialect used for call the api
     * @param tokenType is the token that need to be used for authenticate the api (Application or Logged user)
     * @param service   is the name of the service as registered in service registry
     * @param uri       is the api uri within the service
     * @param payload   is the payload to send to the api
     * @param retType   is the result type of the api(need to be mapped exactly with the return of the remote api)
     * @param <T>       the type of the class result of the oration
     * @return The answer of the api
     */
    @Async("restTemplateAsyncExecutor")
    public <T> CompletableFuture<T> callAPIAsync(HttpMethod method,
                                                 ApiCommunication.TokenType tokenType,
                                                 String service,
                                                 String uri,
                                                 Object payload,
                                                 ParameterizedTypeReference<T> retType,
                                                 Object... uriVariables) throws Exception;

    /**
     * Call and http rest api using the service registry, is an async way
     *
     * @param method      is the http dialect used for call the api
     * @param tokenType   is the token that need to be used for authenticate the api (Application or Logged user)
     * @param service     is the name of the service as registered in service registry
     * @param uri         is the api uri within the service
     * @param payload     is the payload to send to the api
     * @param contentType is the payload content type
     * @param retType     is the result type of the api(need to be mapped exactly with the return of the remote api)
     * @param <T>         the type of the class result of the oration
     * @return The answer of the api
     */
    @Async("restTemplateAsyncExecutor")
    public <T> CompletableFuture<T> callAPIAsync(HttpMethod method,
                                                 ApiCommunication.TokenType tokenType,
                                                 String service,
                                                 String uri,
                                                 Object payload,
                                                 MediaType contentType,
                                                 ParameterizedTypeReference<T> retType,
                                                 Object... uriVariables) throws Exception;

    /**
     * Call and http rest api using a service that is not registered on service registry
     *
     * @param method    is the http dialect used for call the api
     * @param tokenType is the token that need to be used for authenticate the api (Application or Logged user)
     * @param url       is the api url within the service
     * @param payload   is the payload to send to the api
     * @param retType   is the result type of the api(need to be mapped exactly with the return of the remote api)
     * @param <T>       the type of the class result of the oration
     * @return The answer of the api
     */
    public <T> T callExternalAPI(HttpMethod method,
                                 ApiCommunication.TokenType tokenType,
                                 String url,
                                 Object payload,
                                 ParameterizedTypeReference<T> retType,
                                 Object... uriVariables) throws Exception;

    /**
     * Call and http rest api using a service that is not registered on service registry
     *
     * @param method      is the http dialect used for call the api
     * @param tokenType   is the token that need to be used for authenticate the api (Application or Logged user)
     * @param url         is the api url within the service
     * @param payload     is the payload to send to the api
     * @param contentType is the payload mediaType
     * @param retType     is the result type of the api(need to be mapped exactly with the return of the remote api)
     * @param <T>         the type of the class result of the oration
     * @return The answer of the api
     */
    public <T> T callExternalAPI(HttpMethod method,
                                 ApiCommunication.TokenType tokenType,
                                 String url,
                                 Object payload,
                                 MediaType contentType,
                                 ParameterizedTypeReference<T> retType,
                                 Object... uriVariables) throws Exception;

    /**
     * Upload a file via input stream to a remote url
     *
     * @param tokenType       is the token that need to be used for authenticate the api (Application or Logged user)
     * @param fileInputStream is the input file stream for the file
     * @param fileName        is the name eof the file
     * @param retType         is the result type of the api(need to be mapped exactly with the return of the remote api)
     * @param <T>             the type of the class result of the oration
     * @return The answer of the api
     * @throws Exception
     */
    public <T> T uploadToExternalAPI(HttpMethod method,
                                     ApiCommunication.TokenType tokenType,
                                     String url,
                                     InputStream fileInputStream,
                                     String fileName,
                                     ParameterizedTypeReference<T> retType,
                                     Object... uriVariables) throws Exception;

    /**
     * Call and http rest api using a service that is not registered on service registry, is an async way
     *
     * @param method    is the http dialect used for call the api
     * @param tokenType is the token that need to be used for authenticate the api (Application or Logged user)
     * @param url       is the api url within the service
     * @param payload   is the payload to send to the api
     * @param retType   is the result type of the api(need to be mapped exactly with the return of the remote api)
     * @param <T>       the type of the class result of the oration
     * @return The answer of the api
     */
    @Async("restTemplateAsyncExecutor")
    public <T> CompletableFuture<T> callExternalAPIAsync(HttpMethod method,
                                                         ApiCommunication.TokenType tokenType,
                                                         String url, Object payload,
                                                         ParameterizedTypeReference<T> retType,
                                                         Object... uriVariables) throws Exception;

    /**
     * Call and http rest api using a service that is not registered on service registry, is an async way
     *
     * @param method       is the http dialect used for call the api
     * @param tokenType    is the token that need to be used for authenticate the api (Application or Logged user)
     * @param url          is the api url within the service
     * @param payload      is the payload to send to the api
     * @param contentType  is the payload content type
     * @param retType      is the result type of the api(need to be mapped exactly with the return of the remote api)
     * @param <T>          the type of the class result of the oration
     * @return The answer of the api
     */
    @Async("restTemplateAsyncExecutor")
    public <T> CompletableFuture<T> callExternalAPIAsync(HttpMethod method,
                                                         ApiCommunication.TokenType tokenType,
                                                         String url, Object payload,
                                                         MediaType contentType,
                                                         ParameterizedTypeReference<T> retType,
                                                         Object... uriVariables) throws Exception;
}

Un elemento fondamentale in questi metodi e' il tipo di token definito dal tipo java "TokenType", avente due diversi valori che determinato quale token usare per l'autenticazione:

  • TokenTypeApplication: la chiamata viene eseguita associando il token del servizio
  • TokenTypeUser: la chiamata viene eseguita facendo il forward del token (in caso sia presente) dell'identità che correntemente autenticata.


È importante notare che le chiamate REST sono stateless. Ogni chiamata potrà quindi avere identità differenti e pertanto l'identità corrente non deve mai essere memorizzata nel controller o nel service, ma deve essere sempre richiesta alla libreria.


Cache

La Baselib permette di abilitare ed usare la cache per i dati richiesti più frequentemente.
Nello specifico vengono messe in cache le autorizzazioni della singola identità, laddove un API ne richiede il caricamento.
Per implementazione di backend viene usato Redis.


Cache Configuration
it:
  infn:
    sisinfo:
      microservice:
		cache:
			enabled: ${CACHE_ENABLED}
			expiration: ${CACHE_EXPIRATION}
			nodes:
				- hostname_1:port
				- hostname_2:port


La configurazione permette di associare uno o più nodi.
Nel caso risulti un solo nodo, viene configurata la cache in modalità stand-alone; in caso di nodi multipli, invece, avviene la configurazione in cluster.

Distribuited Queue Support

La Baselib implementa funzionalità di esecuzione in background di processi mediante l'utilizzo di Kafka.
Kafka permette l'esecuzione di tali processi tra più istanze dell'applicazione e ne garantisce l'alta affidabilità (es: in caso di failure il job viene rieseguito).
E' possibile attivare il supporto ai messaggi tramite le seguenti opzioni:

Queue configuration
it:
  infn:
    sisinfo:
      microservice:
        queue:
			enabled: true
        	server-address: ${QUEUE_SERVER_URL}
        	client-id: ${QUEUE_CLIENT_ID}
        	group-id: ${QUEUE_SERVER_ID}

Distributed Queue Job

E' possibile attivare anche l'esecuzione di batch job usando la seguente configurazione:

Queue Job Configuration
it:
  infn:
    sisinfo:
      microservice:
 		queue-job-processing:
			enabled:true
 			concurrency: ${QUEUE_JOB_PROCESSING_CONCURRENCY}
			topic: ${QUEUE_JOB_PROCESSING_QUEUE}

Distributed Behaviour

I job vengono schedulati su tutte le istanze del servizio con le stesse configurazioni


 Search Utility Support 

A volte e' necessario effettuare delle ricerche "globali" su più di un campo. Una best practice a riguardo è quella di far confluire su un unico campo tutti gli altri campi su cui si intende effettuare la ricerca.
In questo modo è possibile usare un unico indice e varie ulteriori funzionalità messe a disposizione dal database usato. A tale scopo la librerie mette a disposizione due annotation ed un "processore" di annotazioni contenute nel pacchetto "it.infn.sisinfo.microservice.corelib.search":

  • @AggregatedSearchToken: Annotazione per indicare il field della classe destinazione degli aggregati.
  • @AggregatedSearchTokenSource: annotazione che definisce i field della class da aggregare.
  • @AggregatedSearchTokenProcessor: classe che espone il method public statico che permette di processare la classe annotata

Il funzionamento è di seguito esposto, prendendo ad esempio la classe:

public class Model {
    @AggregatedSearchTokenSource
    private String name = "John";
    @AggregatedSearchTokenSource
    private String description = "Smith";
    @AggregateSearchToken
    private String textSearch;
}

Applicando ora ad un'istanza di tale classe il metodo del processore:

public class Model {
    @AggregatedSearchTokenSource
    private String name = "John";
    @AggregatedSearchTokenSource
    private String description = "Smith";
    @AggregateSearchToken
    private String textSearch;
}

private Model m = new Model();
AggregateSearchTokenProcessor.process(m, AggregateSearchTokenProcessor.AggregationType.SUBSTRING_TOKEN);

Sono previste le seguenti tipologie di aggregazione delle stringhe:

  • SUBSTRING_TOKEN (default)
  • SIMPLE_TOKEN

La SUBSTRING_TOKEN estrae tutte le possibili substring di dimensione >= 3, a meno che la stringa non sia più corta, nel qual caso viene considerata interamente.
Nell'esempio qui sopra, ottengo che il filed "textSearch" verrà popolato nel modo seguente: textSearch → "Joh John ohn Smi Smit Smith mit mith ith".

La SIMPLE_TOKEN prende i token di tutti i field annotati con AggregatedSearchTokenSource (senza duplicazioni) e li aggiunge al field di destinazione, identificato con AggregateSearchToken.

Autenticazione OpenID 

La Baselib supporta l'autenticazione OpenID Connect (OIDC) tramite un server Keycloak 

Queue configuration
it:
  infn:
    sisinfo:
      microservice:
        oidc:
          client:
            enable: true
            realm: ${REALM}
            auth-server: ${KEYCLOACK_HOST}
            client-id: ${SERVICE_ID}
            client-secret: ${SERVICE_SECRET}

Autorizzazione

La Baselib permette di gestire in maniera semplice le autorizzazioni per l'accesso alle API. Inoltre consente di analizzare i parametri di un entitlement al fine di filtrare i dati di un resultset in base ai privilegi dell'identità autenticata.
Il controllo dell'autorizzazione viene eseguito prima ancora di arrivare all'esecuzione del metodo del controller.

I parametri di un entitlement possono essere descritti con la seguente modalità

urn:mace:infn.it:ent_with_parameter@param1:value1,param2:value2

seguono il carattere '@' e sono separati dalla virgola ','  . Possono essere concatenati ad altri entitlement dello stesso tipo.

La baselib mette a disposizione utility per interrogare i parametri.

Security Access

Nella base lib ci sono due livelli di autorizzazione per eseguire una metodo del controller questi due livelli sono gestiti dall'annotaione @SecurityGuard. Permette di specificare tre parametri:

  • accessLevel: permette di specificare se l'api identificata dal metodo è accessibile da tutti o solo previa autenticazione, tramite due costanti:
    • AccessLevelOpen, eseguibile in modo autenticato e non autenticato

    • AccessLevelToken, eseguibile solo se autenticati

  • accessEntitlement (Opzionale): permette di associare un entitlement al metodo del rest controller
  • authorizationGuard (Opzionale): permette di specificare una o più guardia da controllar prima che il metodo possa essere chiamato

Sotto viene riportato un esempio di una api pubblica, è sufficiente il token jwt per accedere all'API.:

	@SecurityAccess(
		accessLevel = AccessLevelToken
	)
    @PostMapping(path = "/find/by/tags")
    @Operation(summary = "Find all apps visible to the user with selected tags")
    public ApiResultResponse<List<ApplicationROInfoDTO>> findByTags(
            @Parameter(description = "List of tags")
            @RequestBody List<String> tags
    ) {
        return new ApiResultResponse<>(applicationService.findByTags(tags));
    }

C'è la possibilità di dare accesso in base agli edupersonentitlement di AAI utilizzando il parametro accessEntitlement :

   	@SecurityAccess(
		accessLevel = AccessLevelToken, 
		accessEntitlement = "appman:app_management_read"
	)
   	@GetMapping(path = "/get/management/data/by/{appID}")
   	@Operation(summary = "Return the app management data")
   	public ApiResultResponse<ApplicationManagementDTO> getAppManagementData(
            @Parameter(description = "Is unique id of application")
            @PathVariable String appID) {
   		return new ApiResultResponse<>(applicationService.getAppManagementData(appID));
   	}

In questo caso è concesso l'accesso solo agli utenti che hanno il valore

urn:mace:infn.it:appman:app_management_read

Attenzione, se si usa la guardia di default (descritto sotto), inserire l'entitlement nell'annotation senza il prefisso urn:mace:infn.it: , perchè viene automaticante aggiunto dalla guardia in fase di controllo

Guardie

Le guardie possono essere associate sia al controller che ai metodi dei controller. Nel primo caso sono gestiti tutti i metodi che non specificano alcuna guardia. I metodi con le guardi sono gestiti solamente dal quelle a loro assegnati. Possono essere usate uno o più guardie il controllo viene eseguito dalla prima all'ultima. Accesso e' garantito quando una guardia ritorna 'true' ed il controllo termina senza eseguire le successive.

Di seguito un esempio di guardie globali o locali ai metodi:

Guarda globale
@RestController
@RequestMapping("/")
@SecurityGuard={CustomGuard1.class, CustomGuard1.class, ...}
public class DummyController {
    ....
}
Metodo con guardia custom
@GetMapping("/custom/guard/api")
    @SecurityAccess(
            accessLevel = SecurityAccess.AccessLevel.AccessLevelToken,
            authorizationGuards = {CustomGuard1.class, CustomGuard1.class, ...}
    )
    public String customGuardRestApi()

Codificare una guardia

La guardia deve essere definita come un componente di Spring, in quanto la creazione viene gestita come uno spring Bean. Questo significa che e' possibile usare l'autowire di servizi e repository secondo le regole standard di Spring. La codifica consiste in una classe che estende la classe astratta 'AbstractAuthorizationGuard' che esporta il nome della guardia e il metodo astratto 'eval'.

Abstract Guard Class
abstract public class AbstractAuthorizationGuard {
    final private String name;

    public AbstractAuthorizationGuard(String name) {
        this.name = name;
    }
    /**
     * Is the abstract method for evaluate the guard class
     * @param entitlement is the entitlement associated to the method, if it has been set
     * @param restControllerName is the name of the controller in which the method belong
     * @param restMethodName is the name of the method that need to be checked
     * @param methodParameter are the method parameters
     * @return return true if the method is accessible for the guard instance
     * @throws Exception
     */
    abstract boolean eval(String entitlement,
                          String restControllerName,
                          String restMethodName,
                          MethodParameter[] methodParameter) throws Exception;
}

il metodo eval ha tutti i parametri necessari per identificare il metodo di cui si deve decidere se l'identità corrente può o meno accedervi. Lo sviluppatore può usare le classi AuthorizationAccess e AuthorizationUtilService tramite l'autowire, per ottenere gli entitlement e i parametri dell'identità corrente e determinare in questo modo se il metodo può o meno essere autorizzato.

Esempio di guadia custom
@Component
public class CustomGuard extends AbstractAuthorizationGuard {
    private AuthorizationAccess authorizationAccess;
	private AuthorizationUtilService authorizationUtilService;

    public CustomGuard() {
        super("CustomGuard");
        this.authorizationAccess = authorizationAccess;
    }

    @Override
    boolean eval(
            String entitlement,
            String restControllerName,
            String restMethodName,
            MethodParameter[] methodParameter) throws Exception {
		Authorization auth = authorizationAccess.getEntitlementForInfnUUID(authorizationAccess.getInfnUUID());
			
		//YOUR CODE

        return false;
    }
}

Log Enhancement

La baseline extend il log di spring aggiungendo due variabili che possono essere usate nel pattern di log, esse sono:

  • x-b3-traceid: e' l'istanza dell'applicazione generata a startup
  • x-b3-spanid: is the transaction id, that is an unique id created when a rest call is initiated
logging.pattern.level: "...app.inst.id=%X{x-b3-traceid} app.tx.id=%X{x-b3-spanid}..."

Mocking Authentication & Authorization

la base lib offre funzionalità per eseguire il mock dell'autenticazione e autorizzazione. Usando il profile "mock-auth" si abilita modifica il normale funzionamento del layer di autorizzazione delle rest e del servizio AuthorizationAccess per il controllo delle autorizzazioni dell'identità corrente. Sia l'autenticazione che le autorizzazioni sono prese direttamente dal token JWT create direttamente dalla base lib.

All'avvio dell'applicazione la baselib eseguire la configurazione della classe "MockIdentityConfig" la quale va a cercare il file "moked-identity.yml" per caricare instanze dell'oggetto MockedIdentity.

- infnUUID: uuid_1
  issuer: issuer_1
  label: "Utente 1"
  authentication:
      isMemberOf:
        - group_11
        - group_12
      schacUserStatus:
      eduPersonEntitlement:
        - ent_11
        - ent_22
      edupersonassurance:
- infnUUID: uuid_2
  issuer: issuer_2
  label: "Utente 2"
  authentication:
    isMemberOf:
      - group_21
      - group_22
    schacUserStatus:
    eduPersonEntitlement:
      - ent_21
      - ent_22
    edupersonassurance:


public class MockIdentity {
    private String infnUUID;
    private String username;
    private String label;
    private Authorization authentication;
    private String issuer;
    private String accessToken;
}

Inoltre viene aggiunto un nuovo controller che permette di interagire con le mocked identity. Per ora e' disponibile una sola api che permette di ritornare tutte le mocked identity "http://localhost:8080/v1/mock/auth/findAll", di seguito il risultato per il file di configurazione sopra riportato.

{
    "errorCode": 0,
    "payload": [
        {
            "accessToken": "eyJ...",
            "authentication": {
                "eduPersonEntitlement": [
                    "ent_11",
                    "ent_22"
                ],
                "isMemberOf": [
                    "group_11",
                    "group_12"
                ]
            },
            "infnUUID": "uuid_1",
            "issuer": "issuer_1",
            "label": "Utente 1",
            "username": null
        },
        {
            "accessToken": "eyJ...",
            "authentication": {
                "eduPersonEntitlement": [
                    "ent_21",
                    "ent_22"
                ],
                "isMemberOf": [
                    "group_21",
                    "group_22"
                ]
            },
            "infnUUID": "uuid_2",
            "issuer": "issuer_2",
            "label": "Utente 2",
            "username": null
        }
    ]
}

Mocking Async Action

A volte, durante lo sviluppo di una UI o dei test per il backend si ha la necessita di simulare eventi asynchorni che in un qualche modo vanno a processare gli stati dei documenti su cui si tassa lavorando. Per questa necessita si possono usare le "Mocked Action".

Per usare questa funzionalità bisogna impostare il profilo "mocked-action". Anche in questo caso la libreria andrà a cercare il file di configurazione "mocked-action.yml", il quale e' cosi definito:

- label: Action 1
  type: job
  data:
    id: id
    jobImplementation: it.infn.microservice.Class
    jobParameter:
      id_conferenza: ${document_id}
      doc_id: value_2
- label: Action 2
  type: job
  data:
    id: id
    jobImplementation: it.infn.microservice.Class
    jobParameter:
      doc_id: ${document_id}

Attualmente esiste una sola "azione" possible che e' quella di avviare Job asincroni di sotto la descrizione del costrutto yaml che descrive una singola azione:

label: Action 1 //e' lal abel che viene mostrata nella UI per identificare l'operazione che esegue l-azione
	type: job	// descrive il tipo di azione "job" per ora 'e l'unico valore possibile
  	data:		// contiene la descriozne dell'azione di tipo job
		
		// il job Nell baseline e' definito da tre chiavi id, jobImplementation, jobParamter
    	id: id 											// identificativo custom per controllo dei log
    	jobImplementation: it.infn.microservice.Class	// @Component di spring che identifica la casse da eseguire per il job
   		jobParameter:									// parametri che sono passati al job, che consiste in una mappa chiave valore
      		id_conferenza: ${document_id}				// in questo caso la chiave e' "id_conferenza" e cove valore e' 
														// sta identificate una variabile ${document_id} descritta in seguito
      		chiave_2: valore_2							// ulteriori chiavi possono essere aggiunte


Uso delle variabile

 All'interno del file di descrizione un costrutto del tipo ${} viene definito come variabile. Questo tipo di costrutto verra riconosciuto dall INFN shell per creare le UI e verra usato per richiedere all'utente di inserire un valore da poter sostituire alla variabile stessa al momento del submit. Il contenuto delle variabile definire la label da usare a livello UI quando verra' richiesto di inserire un valore da associare alla variabile: es ${document_id} → document_id sarà usato come label per la form di richiesta dei valori delle variabili.

Astrazione per la gestione dell'autenticazione e delle autorizzazioni in chiamate REST

La baselib si basa su due interfacce per la gestione dello stato di autenticazione ed authorizzazion durante la chiamate di api rest: AuthenticationAccess e AuthorizationAccess ed IdentityAccess:

Authentication Access
public interface AuthenticationAccess {
    /**
     * check the authentication status of current api call
     * @return
     */
    boolean authenticated();
    /**
     * Return the infn uuid
     * @return
     */
    String getInfnUUID();
}
Authentication Access
public interface AuthorizationAccess {
    /**
     * return the authorization of the current identity
     * @param infnUUID
     * @return
     */
    Authorization getEntitlementForInfnUUID(String infnUUID) throws Exception;
    /**
     *
     * @return
     * @throws Exception
     */
    Authorization getEntitlementForLoggedIdentity() throws Exception;
}
/**
 * Abstraction for the base identity data access
 */
public interface IdentityAccess {

    /**
     * Return the identity default information for a specific infn uuid
     * @return
     */
    Identity getIdentity(String infnUUID);

    /**
     * Return the identity default information for logged identity
     * @return
     */
    Identity getIdentity();
}


La baselib offre differenti implementazioni delle tre, una per quando si usa il profile "mock-auth", l'altra quando non lo si usa. Usando il profile di mock tutte le implementazioni usano il JWT di mock, o le informazioni nel file di configurazione iml, per erogare le informazioni necessarie e nel caso non si usa il mock, la baselib mette a disposizione la sola implementazione, che usa il JWT , per l'AuthenticationAccess. Le applicazioni dovranno usare o un plugin o gestire autonomamente l'implementazione con component che implementa l'AuthorizationAccess e IdentityAccess.

Gestione delle autorizzazioni tramite identity-backend

E' stato realizzato il modulo identity-baselib-authorization che implementa l'interfaccia AuthorizationAccess usando le api rest di identity.

Async Controller

La baselib permette di definire una classe con l'annotazione @AsyncController in modo da poter ricevere chiamate, come nel caso di @RestController ma dal sottolivello di code invece che via REST.

Configurazione

La configurazione è la seguente:

Async Controller Configuration
      microservice:
        queue:
          enabled: true
        queue-job-processing:
          enabled: true
        queue-message-processing:
          enabled: true
          concurrency: 1
          reply-topic: 'reply-queue-name'
          queue-mapping:
            main-queue: 'queue-name'
          message-client:
            client-1:
              send-topic: 'queue-name'


il servizio di queue.enable e  queue-job-processing.enable devono essere abilitati, i messaggi asincroni usano la sottoporre di Kafka per l'inoltro. di seguito la descrizione di ogni parametro:

  • reply-topic: il nome della coda su cui il servizio remoto che riceve il messaggio asincrono deve rispondere. La risposta invaga sarà il risultato del metodo, non void, della classe
  • queue-mapping: definisce una mappatura tra alias, da usare nel sorgente, e una coda di Kafka, gli alias sono usati negli asini controller per definire le code da cui prendere i messaggi
  • message-client: definisce I client da usare, il primo livello dopo questo parametro definisce il nome del client, nei successivi livelli sono valorizzati i parametri di creazione dei client
    • send-topic: definisce la topic su cui il client scrive

Definizione di un AsynController

Definizione async controller
@AsyncController(
        eventType = "eventTypeTest",
        queueAlias = "main-queue"
)
public class AsyncController {
    @AsyncEvent(eventName = "voidTestEvent")
    public void voidTestEvent() throws InterruptedException {
        EventTest.eventResultQueue.put("voidTestEvent");
    }
    @AsyncEvent(eventName = "voidTestParameterEvent")
    public void voidTestParameterEvent(EventTestParameter eventTestParameter) throws InterruptedException {
        EventTest.eventResultQueue.put(eventTestParameter);
    }
    @AsyncEvent(eventName = "replyTestParameterEvent")
    public EventTestParameter replyTestParameterEvent(EventTestParameter eventTestParameter) throws InterruptedException {
        EventTest.eventResultQueue.put(eventTestParameter);
        return eventTestParameter;
    }
}

L'esempio sopra riportato definisce un AsyncController con le seguenti proprietà:

  • eventType: é il tipo di evento a cui il controller deve rispondere
  • queueAlias: è la cosa su cui attendere i messaggi di arrivo

Ogni method della classe che deve rispondere ad un certo event devono essere taggati con l'annotazione @AsyncEvent

  • eventName: definisce il nome dell'evento, correlato al tipo definito in @AsyncController, da cui ne segue l'esecuzione del metodo

Il metodo può essere di tipo void o ritornare un risultato. IL risultato a sua volta può venir usato come reply al messaggio di esecuzione dell'evento

Client

La base lib definisce un spring service 'EventClientRegistry' che permette il fetch dei client definiti nella configurazione, ogni cliente mette a disposizione api per l'invio di chiamate asincrone agli AsyncController

EventClient
public class EventMessageClient {
    private String name;
    private String topic;
    private String replyTopic;
    private KafkaTemplate<String, EventMessage> kafkaTemplate;

    public void send(String key, EventMessage request) throws ExecutionException, InterruptedException {
        kafkaTemplate.executeInTransaction(
                t -> t.send(topic, key,request)
        );
    }

    /**
     * Push message and in the same transaction execute the inTransactionCallable lambda
     * @param key
     * @param eventType
     * @param eventName
     * @param data
     * @param inTransactionCallable
     * @param replyEventType
     * @param replyEventName
     * @return
     * @param <T>
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public <T> T send(
            String key,
            String eventType,
            String eventName,
            List<Object> data,
            Callable<T> inTransactionCallable,
            String replyEventType,
            String  replyEventName) throws ExecutionException, InterruptedException {
        T res = kafkaTemplate.executeInTransaction(
                t -> {
                    t.send(
                            topic,
                            key,
                            EventMessage
                                    .builder()
                                    .eventName(eventName)
                                    .eventType(eventType)
                                    .data(data)
                                    .replyQueue(replyTopic)
                                    .replyEventType(replyEventType)
                                    .replyEventName(replyEventName)
                                    .build()
                    );
                    try {
                        return inTransactionCallable.call();
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
        );
        return res;
    }
}


Entrambi i metodo si basano sull'oggetto EventMessage i cui campi sono:

  • eventType: il tipo di messaggio da inviare
  • eventName: il nome del messaggio che è correlato al metodo da eseguire
  • data: una List<Object> che definisce i parametri del metodo (se il match non viene soddisfatto va in error la chiamata lato remoto)
  • replyQueue: e la cosa su cio si vogliono attendere i reply(impostata in automatico dal client)
  • replyEventType: definisce il tipo di evento su cui si vuole la risposta
  • replyEventName: si definisce il nome dell'evento che seleziona il metodo da eseguire per il reply

Message Replay

Per il reply ad un messaggio i parametri replyEventType e replyEventName devono corrispondere ad un eventType ed eventName definiti da un @AsyncController definito nell'applicazione chiamate. 

il method di reply deve avere come parametro il risultato del metodo remoto eseguito

@AsyncController(eventType = "asyncReply", queueAlias = "reply-topic")
public class AsyncControllerReply {

    @AsyncEvent(eventName = "asyncReplyOne")
    public void reply(EventTestParameter eventTestParameter) throws InterruptedException {
        eventTestParameter.testData = eventTestParameter.testData+"reply";
        EventTest.eventResultQueue.put(eventTestParameter);
    }
}




  • No labels