Ir al contenido principal

Cómo crear un custom mediator y asociarle un tag propio en WSO2

WSO2 pone a nuestra disposición múltiples herramientas para realizar nuestras APIs ya sea a través del API Manager o el Enterprise Integrator. Además nos permite realizar flujos de mediación o secuencias, a la entrada o salida de las mismas. En estas secuencias, WSO2 también pone a nuestra disposición múltiples mediators, los cuales nos permiten realizar operaciones sobre el mensaje.

Aunque hay veces en las cuales, esos mediators no realizan todo aquello que necesitamos. Como puede ser crear una clave HMAC en base a un dato concreto, leer el contenido de un fichero adjunto o realizar un tratamiento concreto sobre las cabeceras de transporte. Para ello tenemos los custom mediators, otra herramienta que pone a nuestra disposición WSO2 para extender su funcionalidad y el tratamiento que realizamos sobre los mensajes.

Creación de un Custom Mediator: Ejemplo

Para verlos vamos a crear un ejemplo basándonos en la siguiente premisa:

Una de las buenas prácticas al realizar una llamada desde una secuencia a un sistema externo es borrar las cabeceras de transporte y establecer tú mismo aquellas necesarias para el nuevo sistema. De esta forma evitamos el envío de información redundante o errónea.

En realidad esto lo podríamos hacer a través del property mediator, de la siguiente forma, pero nosotros queremos ir un poquito más lejos:

<property name="TRANSPORT_HEADERS" action="remove" scope="axis2"/>

Es posible que el conjunto de cabeceras en transporte sean cinco en total, pero nosotros sepamos que hay dos que son iguales a las que necesitamos y queremos mantenerlas. Con nuestro mediator podremos indicar cuales queremos mantener y cuales queremos borrar.

AbstractMediator

Deberemos crear un proyecto Java que debe de tener dos particularidades:

  • Debe ser denominado como proyecto bundle y generar un JAR una vez empaquetado. Maven puede ayudarte a realizar esto.
  • Debe tener como dependencia la librería org.apache.synapse.synapse-core

Dentro del proyecto Java, incluiremos las clases que nos permitan realizar la lógica que queramos. Y tenemos que tener en cuenta que la clase principal, aquella que invocamos más adelante desde la secuencia, debe de implementar la clase AbstractMediator.

public class RemoveTrpHeadersMediator extends AbstractMediator {
    private String exclude;
    public boolean mediate(final MessageContext context) {
        org.apache.axis2.context.MessageContext mc = ((org.apache.synapse.core.axis2.Axis2MessageContext) context).getAxis2MessageContext();
        Map<String, String> transportHeaders = (Map<String, String>) mc.getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);
        if (StringUtils.isNotEmpty(exclude)) {
            List<String> headersToMaintain = Arrays.stream(exclude.split(",")).map(String::trim).collect(Collectors.toList());
            transportHeaders.keySet().retainAll(headersToMaintain);
        } else {
            transportHeaders.clear();
        }
        return true;
    }
    // getters and setters
}

El siguiente paso será, incluir el empaquetado JAR en la carpeta física lib de nuestro producto WSO2 y reiniciar el mismo.

-Quizás te interese: Check list – Qué debo saber antes de transformar mensajes –

Class mediator

Ya tenemos todo correctamente configurado. Ya solo nos queda usar nuestro custom mediator en la secuencia. Si seguimos con el ejemplo, vamos a querer borrar todas las cabeceras de transporte, menos Content-Type y Accept. Para ello debemos realizar la siguiente invocación:

<class name="com.chakray.mediator.trpHeadersMgmt.RemoveTrpHeadersMediator" >
<property name="exclude" value="Accept, Content-Type" />
</class>

Como veis, para usar nuestro custom mediator tendremos que indicar el nombre y paquete exacto. Y además hemos tenido que definir una lógica propia para admitir un listado de valores asociados a un atributo concreto.

Estos dos detalles podremos solucionarlos a través del siguiente apartado, generando nuestro propio mediator, con etiqueta concreta incluida.

AbstractMediatorFactory y AbstractMediatorSerializer

A través de estas clases de utilidad podremos implementar las clases que nos permitan crear un mediator y gestionar su configuración a través de XML.

A través de AbstractMediatorFactory nos permite crear la instancia de un mediator a través del código XML que lo identifica. Indicaremos la que será nuestra etiqueta: removeTrpHeaders.

public class RemoveTrpHeadersMediatorFactory extends AbstractMediatorFactory {
    public static final QName RemoveTrpHeadersMediator_Q = new QName(XMLConfigConstants.SYNAPSE_NAMESPACE, "removeTrpHeaders");
    public static final QName EXCLUDE_Q = new QName(XMLConfigConstants.SYNAPSE_NAMESPACE, "exclude");
    public QName getTagQName() {
        return RemoveTrpHeadersMediator_Q;
    }
    protected Mediator createSpecificMediator(final OMElement elem, final Properties properties) {
        RemoveTrpHeadersMediator removeTrpHeadersMediator = new RemoveTrpHeadersMediator();
        processAuditStatus(removeTrpHeadersMediator, elem);
        Iterator<OMElement> iter = elem.getChildrenWithName(EXCLUDE_Q);
        while (iter.hasNext()) {
            OMElement excludeElem = iter.next();
            OMAttribute name = excludeElem.getAttribute(ATT_NAME);
            if (name == null) {
                String msg = "The 'name' attribute is required for 'exclude' definition";
                throw new SynapseException(msg);
            } 
            removeTrpHeadersMediator.getHeadersToExclude().add(name.getAttributeValue());
        }
        addAllCommentChildrenToList(elem, removeTrpHeadersMediator.getCommentsList());
        return removeTrpHeadersMediator;
    }
}

A través de AbstractMediatorSerializer realizaremos el paso contrario al anterior.

public class RemoveTrpHeadersMediatorSerializer extends AbstractMediatorSerializer {
    public String getMediatorClassName() {
        return RemoveTrpHeadersMediator.class.getName();
    }
    protected OMElement serializeSpecificMediator(final Mediator m) {
        if (!(m instanceof RemoveTrpHeadersMediator)) {
            handleException("Unsupported mediator passed in for serialization : " + m.getType());
        }
        RemoveTrpHeadersMediator mediator = (RemoveTrpHeadersMediator) m;
        OMElement removeTrpHeadersMed = fac.createOMElement(MediatorConstants.RemoveTrpHeadersMediator_Q);
        saveTracingState(removeTrpHeadersMed, mediator);
        for (String nameHeaderToExclude : mediator.getHeadersToExclude()) {
            OMElement excludeElem = fac.createOMElement(MediatorConstants.EXCLUDE_Q);
            if (nameHeaderToExclude != null) {
                excludeElem.addAttribute(fac.createOMAttribute("name", nullNS, nameHeaderToExclude));
            } else {
                handleException("Invalid header to exclude. Name required");
            }
            removeTrpHeadersMed.addChild(excludeElem);
        }
        serializeComments(removeTrpHeadersMed, mediator.getCommentsList());
        return removeTrpHeadersMed;
    }
}

Para que los mediators sean tenidos en cuenta al ser desplegados nuevamente en nuestro producto WSO2 (y reiniciado), debemos de realizar un nuevo paso de configuración. Este paso se basa en la característica SPI incluida dentro de Java, la cual nos permite cargar y utilizar dinámicamente implementaciones de determinados servicios en nuestras aplicaciones, en este caso los mediators.

Para conseguirlo deberemos crear dos ficheros, denominados org.apache.synapse.config.xml.MediatorFactory/MediatorSerializer. Los cuales tendrán incluidos los nombres completos de las clases que hemos creado durante este paso y que implementan cada una de estas interfaces y serán almacenados dentro de la carpeta META-INF de nuestra librería.

Custom Mediator

Ahora para indicar una cabecera a mantener, usaremos la etiqueta ‘exclude’. Y además podremos repetirla tantas veces como queramos. Por tanto como mejora en nuestra clase RemoveTrpHeadersMediator, podremos almacenar en un listado de cadenas o string dichas cabeceras a excluir del borrado, sin necesidad de almacenarlas en un único atributo separado por comas, como hacíamos en el paso anterior.

Por tanto la invocación de nuestro custom mediator, tras estas remodelaciones quedaría de la siguiente forma:

<removeTrpHeaders >
<exclude name="Accept" />
<exclude name="Content-Type" />
</removeTrpHeaders>