Propòsit
El propòsit del connector és proporcionar una interfície java independent del framework Canigó per accedir a la PICA (Plataforma d’Integració i Col·laboració Administrativa).
El connector amb la PICA disposa de dos tipus de comunicació, un d’ells a través de web service síncron, i l’altre, mitjançant web service asíncron.
Instal.lació i Configuració
Instal.lació
Per d’instal-lar el mòdul de PICA, afegir manualment en el pom.xml de l’aplicació la següent dependència:
<dependency>
<groupId>conectores</groupId>
<artifactId>pica</artifactId>
<version>0.0.1</version>
</dependency>
Configuració
Son necessaris els següents arxius de configuració:
- application.yml
- pica.signature.properties
- client.axis2.xml
- modules.list
- rampart-1.3.mar
- certificats.jks
Alguns d’ells es troben en aquest zip adjunt. A continuació es descriu el detall de cadascun.
Ubicació: <PROJECT_ROOT>/src/main/resources/config/props/application.yml
| Propietat | Requerit | Descripció |
|---|---|---|
| pica.modes.passwordType | No | Tipus de password per a la PICA. Per defecte: PasswordText |
| pica.requirer.signatureFile | Sí | Arxiu de configuració de la seguretat |
| pica.requirer.petitionerId | Sí | Identificador del solicitant |
| pica.requirer.transmitionId | Sí | Identificador de la transmissió |
| pica.requirer.petitionerName | Sí | Nom del peticionari |
| pica.requirer.user | Sí | Usuari que vol consumir un producte/modalitat de la PICA |
| pica.requirer.password | Sí | Password de l’usuari que vol consumir un producte/modalitat de la PICA |
| pica.axisdefinition.location | Sí | Localització de l’arxiu d’axisdefinition |
| pica.trustStore.location | No | Localització de l’arxiu de certificats per a la configuració SSL |
| pica.trustStore.type | No | Tipus de keystore |
| pica.trustStore.password | No | Password de la keystore |
En l’exemple configurem el servei de PICA per invocar el producte/modalitat “PADRO_MUNICIPI_RESIDENCIA”.
pica:
axis-definition: classpath:axis2client/
modalitats:
PADRO_MUNICIPI_RESIDENCIA:
signat: false
url-pica: http://preproduccio.pica.intranet.gencat.cat/pica_cataleg/AppJava/services/PADRO_MUNICIPI_RESIDENCIA
cod-certificado: PADRO_MUNICIPI_RESIDENCIA
cod-producto: PADRO
finalidad: PROVES
nif-emisor: Q0801175A
nombre-emisor: CONSORCI AOC
password-type: PasswordText
requeridor:
fitxer-signatura: classpath:config/cert/pica.signature.properties
id-solicitante: ID_SOLICITANTE
id-transmision: ID_TRANSMISION
nombre-solicitante: NOM_SOLICITANTE
password: USER_PASSWORD
user: PASSWORD
requeridor-error:
password: PICAERROR
user: USER_ERROR
modes:
passwordType: PasswordText
requirer:
password: REQUIRER_PASSWORD
petitionerId: PETITIONER_ID
petitionerName: PETITIONER_NAME
signatureFile: classpath:config/cert/pica.signature.properties
transmitionId: TRANSMITION_ID
user: USER
trustStore:
location: classpath:config/cert/certificats.jks
password: PASSWORD_DE_LA_TRUSTSTORE
type: JKS
Ubicació: <PROJECT_ROOT>/src/main/resources/axis2client/conf/client.axis2.xml
Adjunt en el zip.
Ubicació: <PROJECT_ROOT>/src/main/resources/axis2client/conf/rampart-1.3.mar
Adjunt en el zip.
Ubicació: <PROJECT_ROOT>/src/main/resources/axis2client/conf/modules.list
Adjunt en el zip.
Ubicació: <PROJECT_ROOT>/src/main/resources/config/cert/certificats.jks
Ha de ser proporcionat per cada projecte que utilitzi aquest connector, ja que conté les claus i certificats específics necessaris per a la signatura i/o xifrat segons la configuració de seguretat de cada entorn.
Ubicació: <PROJECT_ROOT>/src/main/resources/config/cert/pica.signature.properties
| Propietat | Descripció |
|---|---|
| org.apache.ws.security.crypto.provider | Defineix el proveïdor de seguretat que s’utilitza per gestionar la criptografia |
| org.apache.ws.security.crypto.merlin.file | Ruta del keystore (JKS) que conté el certificat/clau privada. |
| org.apache.ws.security.crypto.merlin.keystore.type | Tipus d’emmagatzematge de claus, pot ser JKS, PKCS12, etc. |
| org.apache.ws.security.crypto.merlin.keystore.password | Contrasenya per accedir al keystore |
| org.apache.ws.security.crypto.merlin.keystore.provider | Proveïdor de Java que gestiona el keystore |
| org.apache.ws.security.crypto.merlin.keystore.alias | Àlies dins del keystore per identificar la clau/certificat |
| org.apache.ws.security.crypto.merlin.alias.password | Contrasenya per accedir a la clau privada associada a l’àlies anterior |
Important
Si l'aplicació ha de ser desplegada en format empaquetat (.war, .ear) caldrà indicar la ubicació de la carpeta de configuració d'axis2 com a Path absolut dins el servidor on desplegarà. Aquest Path s'indicarà sobreescrivint el valor de la propietat pica.axisdefinition.location del fitxer pica.properties amb el Path absolut on s'ha deixat la carpeta d'axis2client als servidors. S'haurà de pactar la ubicació amb els responsables del CPD on desplegarà.
Exemple:
pica.properties:
int.pica.axisdefinition.location=file:/export/AppJavaDades/int/.../axis2client/
pre.pica.axisdefinition.location=file:/export/AppJavaDades/pre/.../axis2client/
pro.pica.axisdefinition.location=file:/export/AppJavaDades/pro/.../axis2client/
Utilització del Mòdul
REST
PicaService.java
Classe Java amb la lògica de les peticions que es realitzin, i la connectivitat amb el mòdul de la PICA.
En aquesta classe es pot visualiztar:
- Injecció del servei de PICA.
- Invocació del producte/modalitat “PADRO_MUNICIPI_RESIDENCIA” definit en el map de modalitats que es troba al fitxer application.yml.
import conectores.pica.PicaServiceWrapperImpl;
import cat.gencat.pica.api.peticio.beans.DadesEspecifiques;
import cat.gencat.pica.api.peticio.beans.Funcionari;
import cat.gencat.pica.api.peticio.beans.Titular;
import cat.gencat.pica.api.peticio.core.IPICAServiceSincron;
import com.generalitat.mp.ws.CridaSincronaResponseDocument;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Service;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@Service
public class PicaService {
private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(PicaService.class);
private final PicaServiceWrapperImpl picaServiceWrapperImpl;
private static final int TIPO_DOC_TITULAR_NIF = 2;
public PicaService(PicaServiceWrapperImpl picaServiceWrapperImpl) {
this.picaServiceWrapperImpl = picaServiceWrapperImpl;
}
public String testPica() throws Exception {
log.info("Executing Pica test...");
String message;
try {
IPICAServiceSincron service = picaServiceWrapperImpl.getPicaWebServiceSincronInstance("PADRO_MUNICIPI_RESIDENCIA");
Funcionari func = getFuncionari();
Titular tit = creaTitular();
List<DadesEspecifiques> datosEspecificosXML = createDadesEspecifiques();
service.setFuncionari(func);
service.setTitular(tit);
service.setDadesEspecifiques(datosEspecificosXML);
// Identificador únic de petició
service.crearPeticio("PROVA_OTCANIGO_" + System.currentTimeMillis());
// fer peticio
CridaSincronaResponseDocument resp = picaServiceWrapperImpl.ferPeticioAlServei(service);
// extreure resultat
List<DadesEspecifiques> resposta = picaServiceWrapperImpl.extreuDadesEspecifiques(service, resp);
Iterator<DadesEspecifiques> it = resposta.iterator();
while (it.hasNext()) {
DadesEspecifiques object = it.next();
String dadesXML = object.getDadesXML();
// Parsejar resposta
parseXML(dadesXML);
}
message = "Test correcte";
} catch (Exception e) {
message = "Test amb errors: " + e.getMessage();
log.error(e.getMessage(), e);
}
return message;
}
/**
* Crea un objecte funcionari return funcionari
**/
private Funcionari getFuncionari() {
Funcionari funcionari = new Funcionari();
funcionari.setNombreFuncionario("Nom Funcionari");
funcionari.setNifFuncionario("55555555A");
funcionari.setEmailFuncionario("prova@gencat.com");
return funcionari;
}
/**
* Crea un objecte titular return titular
**/
private Titular creaTitular() {
Titular tit = new Titular();
tit.setTitularTipoDocumentacion(TIPO_DOC_TITULAR_NIF);
tit.setTitularDocumentacion("NIF_TITULAR"); // Es posa el NIF real
tit.setTitularNombreCompleto("NOM_COMPLET_TITULAR"); // Es posa el nom
// real
return tit;
}
/**
* Crea un objecte dades específiques return DadesEspecifiques
**/
private List<DadesEspecifiques> createDadesEspecifiques() {
List<DadesEspecifiques> dadesEspecifiquesXML = new ArrayList<DadesEspecifiques>();
StringBuffer sb = new StringBuffer("<ns1:request xmlns:ns1=\"http://www.gencat.net/tfn\">");
sb.append("<ns1:simpleparam name=\"NUMTIT\">83746573</ns1:simpleparam>");
sb.append("<ns1:simpleparam name=\"NUMNIF\">111111111H</ns1:simpleparam>");
sb.append("</ns1:request>");
DadesEspecifiques dades = new DadesEspecifiques();
dades.setIdSolicitud("1");
dades.setDadesXML(sb.toString());
dadesEspecifiquesXML.add(dades);
return dadesEspecifiquesXML;
}
/**
* Parsejar la resposta deixant constància en format de traces si la crida
* ha finalitzat o no correctament
**/
private void parseXML(String dadesXML) throws ParserConfigurationException, SAXException, IOException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
InputSource inStream = new InputSource();
boolean error = false;
inStream.setCharacterStream(new StringReader(dadesXML));
Document doc = builder.parse(inStream);
NodeList nodes = doc.getElementsByTagName("error");
if (nodes.getLength() == 0) {
nodes = doc.getElementsByTagName("sol:SolicitudError");
error = true;
}
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
if (!error) {
log.info(dadesXML);
} else {
log.info(element.getTextContent());
}
}
}
}
}
Cal destacar que l’identificador de la petició ha de ser únic, per aquest motiu es concatena un prefix de text qualsevol amb el timestamp del sistema.
Per a més informació respecte l’especificació tècnica i funcional podeu contactar amb l’OT PICA.
En aquest exemple, els mètodes privats crearFuncionari() i crearTitular() (creats simplement per aquest test) s’encarreguen de setejar les propietats del funcionari que fa la petició i titular sobre el que es fa la petició respectivament. El mètode també privat createDadesEspecifiques() conté una llista de les sol.licituds, en format XML, que s’enviaran dins la petició.
Dins una petició (llista de dades específiques) es poden incloure diverses sol·licituds, els identificadors de les quals han de ser únics dins una mateixa petició. Si volguéssim afegir una altra sol- licitud en l’exemple, només caldria instanciar un nou objecte DadesEspecifiques, amb IdSolicitud=“2”, un nou StringBuffer amb les dades i afegir-lo a la llista dadesEspecifiquesXML dins del mètode createDadesEspecifiques() de l’exemple.
PicaController.java
Controller que publica les operacions disponibles per a qui hagi de consumir-les
import io.swagger.annotations.Api;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Api(tags = {"pica-test"})
@RestController
@RequestMapping("/pica")
public class PicaController {
@Autowired
private final PicaService picaService;
public PicaController(PicaService picaService) {
this.picaService = picaService;
}
@PostMapping(produces = {MediaType.APPLICATION_JSON_VALUE})
public String testPica() throws Exception {
return picaService.testPica();
}
}