lunes, 9 de junio de 2014

Change IBM Integration Toolkit 9 language to english

By default, IBM Integration Toolkit 9 (from IBM Integration Bus) UI language is determined by the computer locale. My OS is installed and configured to use Spanish, but I prefer to use the development tools in English (error messages, tutorials, etc.)

To force IBM Integration Toolkit 9 to use English, add the following line

-nl en_US

to INSTALLATION_PATH\mb.ini (for example, C:\Program Files\IBM\IntegrationToolkit90\mb.ini)


viernes, 7 de marzo de 2014

Notes about Oracle AQ default exception queues


  • All AQ Queues have a default associated exception queue where messages that "may not be consumed within given constraints" are moved.
  • By default the exception queue name is called AQ$_[ORIGINAL_QUEUE_NAME]_E. For example, if the original normal queue is called MYQUEUE, its default exception queue is called AQ$_MYQUEUE_E. I notice that for example, in SQL Developer, I can't see the exception queue in the "Queues" tree (but it "exists"). Also by default, the default exception queues are not enabled for dequeuing. Yoy can use this instruction to enable dequeuing: dbms_aqadm.start_queue(queue_name=>'MYQUEUE', enqueue=>FALSE,dequeue=>TRUE). You can't enable an exception queue for enqueuing. Here is additional information about the exception queues.
  • By default, both queues (the normal queue and the exception queue), are supported by the same queue table. You can check it by directly querying the queue table (MYQUEUETABLE) and looking the Q_NAME column or by querying the queue table AQ view (AQ$MYQUEUETABLE) and looking the QUEUE column (this is the prefered way). The value for the "expired" messages is AQ$_MYQUEUE_E and the value for the normal messages is MYQUEUE.
  • You can use an "user-defined exception queue", but it is set through a message header; that is, at message level and not at queue level. At the message enqueue time, you can set the exception queue for that message through a header (JMS_OracleExcpQ); if that message expires, it will be moved to that user-defined exception queue. Here, you can check some good instructions to use it in PL/SQL or Java JMS.
Here are some references in My Oracle Support:

Using JMS message selectors in a message consumer using an Oracle AQ (exception) queue

I was testing JMS selectors in a Message Consumer based on an Oracle AQ (exception) queue; I needed to dequeue only the messages with certain message ID's; at first, I tried with this syntax, but found an unexpected behavior:

"JMSMessageID='ID:XXX' OR JMSMessageID='ID:YYY'"

For example, let's say we have this messages in the queue (this specific messages are in the queue's default exception queue):


If I use the following selector,

String selector = 
  "JMSMessageID='ID:4A3966F3C0604B6DAED6D6E7FC62705A' OR 
   JMSMessageID='ID:01AF9624372C4D8A88CFAFD37D4395E7' OR
   JMSMessageID='ID:42C32FA9BBCB48ABA074EB69C94D1A82' OR
   JMSMessageID='ID:4973147F8AC54BD1A5DA2CE81F9622CD'";

the first receive() works OK, returning one of the matching messages (consistently, the first in the selector):

[TestServlet][doGet]selector=JMSMessageID='ID:4A3966F3C0604B6DAED6D6E7FC62705A' OR JMSMessageID='ID:01AF9624372C4D8A88CFAFD37D4395E7' OR JMSMessageID='ID:42C32FA9BBCB48ABA074EB69C94D1A82' OR JMSMessageID='ID:4973147F8AC54BD1A5DA2CE81F9622CD
[TestServlet][doGet]messageID=ID:4A3966F3C0604B6DAED6D6E7FC62705A

But, from the second, and so on, the receive() method behaves as if there aren't messages matching the selector. In the end, just one message got processed:


I enabled the jdbc and jms debug on the managed server (I'm working on Weblogic 12c), and found this, starting from the second receive(): "java.sql.SQLException: ORA-25263: no message in queue PRUEBAS.AQ$_MYQUEUETABLE_E with message ID 4A3966F3C0604B6DAED6D6E7FC62705A".

[[oracle.jdbc.driver.T4CXAConnection@29cccbf, owner=[weblogic.jdbc.wrapper.JTAConnection_weblogic_jdbc_wrapper_XAConnection_oracle_jdbc_driver_LogicalConnection-myQueuePruebas_DS-17, oracle.jdbc.driver.LogicalConnection@29d1486], rmConn=oracle.jdbc.driver.LogicalConnection@29d1486]] dequeue(unknown) throws: java.sql.SQLException: ORA-25263: no message in queue PRUEBAS.AQ$_MYQUEUETABLE_E with message ID 4A3966F3C0604B6DAED6D6E7FC62705A
        at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:445)
        at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:389)
        at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:382)
        at oracle.jdbc.driver.T4Caqdq.processError(T4Caqdq.java:488)
        at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:450)
        at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:192)
        at oracle.jdbc.driver.T4Caqdq.doOAQDQ(T4Caqdq.java:180)
        at oracle.jdbc.driver.T4CConnection.doDequeue(T4CConnection.java:3976)
        at oracle.jdbc.driver.PhysicalConnection.dequeue(PhysicalConnection.java:10106)
        at oracle.jdbc.driver.PhysicalConnection.dequeue(PhysicalConnection.java:10153)
        at oracle.jdbc.OracleConnectionWrapper.dequeue(OracleConnectionWrapper.java:1525)
        at weblogic.jdbc.wrapper.XAConnection_oracle_jdbc_driver_LogicalConnection.dequeue(Unknown Source)
        at weblogic.jdbc.wrapper.JTAConnection_weblogic_jdbc_wrapper_XAConnection_oracle_jdbc_driver_LogicalConnection.dequeue(Unknown Source)
        at oracle.jms.AQjmsConsumer.jdbcConnectionDequeueCheck25242(AQjmsConsumer.java:1713)
        at oracle.jms.AQjmsConsumer.jdbcDequeue(AQjmsConsumer.java:1295)
        at oracle.jms.AQjmsConsumer.receiveFromAQ(AQjmsConsumer.java:1035)
        at oracle.jms.AQjmsConsumer.receiveFromAQ(AQjmsConsumer.java:960)
        at oracle.jms.AQjmsConsumer.receiveFromAQ(AQjmsConsumer.java:938)
        at oracle.jms.AQjmsConsumer.receive(AQjmsConsumer.java:790)
        at test.PruebaConsumerSelectorServlet.doGet(PruebaConsumerSelectorServlet.java:55)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:731)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:844)
        at weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:242)
        at weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:216)
        at weblogic.servlet.internal.StubSecurityHelper.invokeServlet(StubSecurityHelper.java:132)
        at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:338)
        at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:221)
        at weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.wrapRun(WebAppServletContext.java:3284)
        at weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.run(WebAppServletContext.java:3254)
        at weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:321)
        at weblogic.security.service.SecurityManager.runAs(SecurityManager.java:120)
        at weblogic.servlet.provider.WlsSubjectHandle.run(WlsSubjectHandle.java:57)
        at weblogic.servlet.internal.WebAppServletContext.doSecuredExecute(WebAppServletContext.java:2163)
        at weblogic.servlet.internal.WebAppServletContext.securedExecute(WebAppServletContext.java:2089)
        at weblogic.servlet.internal.WebAppServletContext.execute(WebAppServletContext.java:2074)
        at weblogic.servlet.internal.ServletRequestImpl.run(ServletRequestImpl.java:1513)
        at weblogic.servlet.provider.ContainerSupportProviderImpl$WlsRequestExecutor.run(ContainerSupportProviderImpl.java:254)
        at weblogic.work.ExecuteThread.execute(ExecuteThread.java:256)
        at weblogic.work.ExecuteThread.run(ExecuteThread.java:221)

Am I doing somethig wrong? is this a bug? my test code is pretty straighfoward; something like this:

String selector = 
  "JMSMessageID='ID:4A3966F3C0604B6DAED6D6E7FC62705A' OR 
   JMSMessageID='ID:01AF9624372C4D8A88CFAFD37D4395E7' OR
   JMSMessageID='ID:42C32FA9BBCB48ABA074EB69C94D1A82' OR
   JMSMessageID='ID:4973147F8AC54BD1A5DA2CE81F9622CD'";
messageConsumer = session.createConsumer(queue, selector);
Message msg = null;
System.out.println("[TestServlet][doGet]selector=" + messageConsumer.getMessageSelector());
while((msg = messageConsumer.receive(10000)) != null) {          
if(msg != null) {
System.out.println("[TestServlet][doGet]messageID=" + msg.getJMSMessageID());
}
}

Fortunately, trying with other selector syntax, the test worked OK. The syntax is:

"JMSMessageID IN ('ID:XXX', 'ID:YYY')"

For example, having this messages in the queue


And this selector

String selector = 
  "JMSMessageID IN (
    'ID:ED976B4A41F241F594F570A8F2250D26'
'ID:724794DE2C4648AF9EF7EB1CE3B0BC22'
'ID:33CF68A695E04F6B89F1024F7DFE5925'
'ID:3E7059E3489C472096C58A833434893C')"

The test works as expected:

[TestServlet][doGet]messageID=ID:724794DE2C4648AF9EF7EB1CE3B0BC22
[TestServlet][doGet]messageID=ID:3E7059E3489C472096C58A833434893C
[TestServlet][doGet]messageID=ID:33CF68A695E04F6B89F1024F7DFE5925
[TestServlet][doGet]messageID=ID:ED976B4A41F241F594F570A8F2250D26

The final result in the table is:


I found some (un)related cases in My Oracle Support.
I found also an Hermes bug report discusing the same case:

martes, 4 de marzo de 2014

Weblogic: setRollbackOnly called on transaction

Exception: weblogic.transaction.internal.AppSetRollbackOnlyException: setRollbackOnly called on transaction.

This exception can happen when you invoke transactional methods after calling MessageDrivenContext.setRollbackonly():

For example, given this (default: container, required) transactional MDB:

@MessageDriven
public class PruebaMdbA implements MessageListener {
   
    @EJB
    private PruebaSessionNoIntA pruebaSessionNoIntA;

    @Resource
    MessageDrivenContext mdc;

    public void onMessage(Message message) {
        //...
    mdc.setRollbackOnly();
        //...
     pruebaSessionNoIntA.testTx();
    }

//...
}

And this (default: container, required) transactional session EJB:

@Stateless
@LocalBean
public class PruebaSessionNoIntA {
    public void testTx() {
     //...
    }
}

If you invoke the onMessage() MDB method, it starts a transaction; when you invoke the session EJB method (pruebaSessionNoIntA.testTx()) after marking the transaction as rollbak only, you get the exception:

... <BEA-010080> <An error occurred while attempting to process a message inside a Message-Driven Bean: javax.ejb.EJBTransactionRolledbackException: EJB Exception: 
Nested exception: javax.ejb.TransactionRolledbackLocalException: EJB Exception: 
Nested exception: weblogic.transaction.internal.AppSetRollbackOnlyException: setRollbackOnly called on transaction
Exception is : javax.ejb.EJBTransactionRolledbackException: EJB Exception: 
at weblogic.ejb.container.internal.BaseLocalObject.handleSystemException(BaseLocalObject.java:453)
at weblogic.ejb.container.internal.BaseLocalObject.getBeanInstance(BaseLocalObject.java:162)
at weblogic.ejb.container.internal.BaseLocalObject.preInvoke(BaseLocalObject.java:103)
at weblogic.ejb.container.internal.BaseLocalObject.__WL_preInvoke(BaseLocalObject.java:67)
at weblogic.ejb.container.internal.SessionLocalMethodInvoker.invoke(SessionLocalMethodInvoker.java:20)
at test.PruebaSessionNoIntA_uae4n4_NoIntfViewImpl.testTx(Unknown Source)
at test.PruebaMdbA.onMessage(PruebaMdbA.java:40)
at weblogic.ejb.container.internal.MDListener.execute(MDListener.java:585)
at weblogic.ejb.container.internal.MDListener.transactionalOnMessage(MDListener.java:488)
at weblogic.ejb.container.internal.NewJMSMessagePoller.processOneMessage(NewJMSMessagePoller.java:290)
at weblogic.ejb.container.internal.NewJMSMessagePoller.run(NewJMSMessagePoller.java:126)
at weblogic.work.SelfTuningWorkManagerImpl$WorkAdapterImpl.run(SelfTuningWorkManagerImpl.java:545)
at weblogic.work.ExecuteThread.execute(ExecuteThread.java:256)
at weblogic.work.ExecuteThread.run(ExecuteThread.java:221)
Caused by: javax.ejb.TransactionRolledbackLocalException: EJB Exception: 
at weblogic.ejb.container.internal.EJBRuntimeUtils.throwTransactionRolledbackLocal(EJBRuntimeUtils.java:236)
at weblogic.ejb.container.internal.EJBRuntimeUtils.throwWrappedTransactionRolledbackLocal(EJBRuntimeUtils.java:255)
at weblogic.ejb.container.manager.BaseEJBManager.handleSystemException(BaseEJBManager.java:348)
at weblogic.ejb.container.manager.BaseEJBManager.setupTxListener(BaseEJBManager.java:276)
at weblogic.ejb.container.manager.StatelessManager.preInvoke(StatelessManager.java:171)
at weblogic.ejb.container.internal.BaseLocalObject.getBeanInstance(BaseLocalObject.java:146)
... 12 more
Caused by: weblogic.transaction.internal.AppSetRollbackOnlyException: setRollbackOnly called on transaction
at weblogic.transaction.internal.TransactionImpl.setRollbackOnly(TransactionImpl.java:555)
at weblogic.ejb.container.internal.BaseEJBContext.setRollbackOnly(BaseEJBContext.java:242)
at test.PruebaMdbA.onMessage(PruebaMdbA.java:36)
... 7 more







lunes, 3 de marzo de 2014

Maximum redelivery retries and retry delay in AQ backed MDB in Weblogic

When working with AQ backed MDB in Weblogic, the maximum redelivery retry count and retry delay can be configured at AQ queue level. The two related parameters, are:
  • max_retries
  • retry_delay
They can be configured at queue creation time (DBMS_AQADM.CREATE_QUEUE) or can be updated in a existent queue (DBMS_AQADM.ALTER_QUEUE). The default values are 5 for max_retries and 0 for retry (as soon as possible). The complete set of parameters can be found here.

In Weblogic, you can verify the parameter's operation with something like this:

@MessageDriven(
  activationConfig = {
    @ActivationConfigProperty(propertyName = "ConnectionFactoryJndiName", propertyValue = "..."),
    @ActivationConfigProperty(propertyName = "DestinationJndiName", propertyValue = "..."),
    @ActivationConfigProperty(propertyName = "DestinationType", propertyValue = "javax.jms.Queue")
  })
public class TestMyQueueMDB implements MessageListener {
  
  @Resource
  private MessageDrivenContext mdc;
  
  public void onMessage(Message message) {
    try {
      System.out.println("onMessage][START]time=" + new Date());
      System.out.println("JMSMessageID     : " + message.getJMSMessageID());
      System.out.println("JMSRedelivered   : " + message.getJMSRedelivered());
      System.out.println("JMSXDeliveryCount: " + message.getIntProperty("JMSXDeliveryCount"));

      //force retry
      mdc.setRollbackOnly();
    } catch(Exception e) {
      //...
    } finally {
      System.out.println("[onMessage][END]");
      System.out.println();
    }
  }
  //...
}

With an queue created with the default values,

dbms_aqadm.create_queue(
  queue_name=>'myQueue',
  queue_table=>'myQueueTable'
);

it should print something like this:

[onMessage][START]time: Mon Mar 03 13:18:26 COT 2014
JMSMessageID     : ID:205BD8B6042C4348A778FC5D467D2581
JMSRedelivered   : false
JMSXDeliveryCount: 1
[onMessage][END]

[onMessage][START]time: Mon Mar 03 13:18:26 COT 2014
JMSMessageID     : ID:205BD8B6042C4348A778FC5D467D2581
JMSRedelivered   : true
JMSXDeliveryCount: 2
[onMessage][END]

[onMessage][START]time: Mon Mar 03 13:18:26 COT 2014
JMSMessageID     : ID:205BD8B6042C4348A778FC5D467D2581
JMSRedelivered   : true
JMSXDeliveryCount: 3
[onMessage][END]

[onMessage][START]time: Mon Mar 03 13:18:26 COT 2014
JMSMessageID     : ID:205BD8B6042C4348A778FC5D467D2581
JMSRedelivered   : true
JMSXDeliveryCount: 4
[onMessage][END]

[onMessage][START]time: Mon Mar 03 13:18:26 COT 2014
JMSMessageID     : ID:205BD8B6042C4348A778FC5D467D2581
JMSRedelivered   : true
JMSXDeliveryCount: 5
[onMessage][END]

[onMessage][START]time: Mon Mar 03 13:18:26 COT 2014
JMSMessageID     : ID:205BD8B6042C4348A778FC5D467D2581
JMSRedelivered   : true
JMSXDeliveryCount: 6
[onMessage][END]

After doing an update like the following,

BEGIN
DBMS_AQADM.ALTER_QUEUE (
   queue_name => 'MYQUEUE',
   max_retries => 2,
   retry_delay => 10);
END;
/

it should print something like

[onMessage][START]time: Mon Mar 03 13:13:16 COT 2014
JMSMessageID     : ID:61819BE03F7942DE92B2FA196EF13888
JMSRedelivered   : false
JMSXDeliveryCount: 1
[onMessage][END]

[onMessage][START]time: Mon Mar 03 13:13:26 COT 2014
JMSMessageID     : ID:61819BE03F7942DE92B2FA196EF13888
JMSRedelivered   : true
JMSXDeliveryCount: 2
[onMessage][END]

[onMessage][START]time: Mon Mar 03 13:13:37 COT 2014
JMSMessageID     : ID:61819BE03F7942DE92B2FA196EF13888
JMSRedelivered   : true
JMSXDeliveryCount: 3
[onMessage][END]

References:


miércoles, 12 de febrero de 2014

NoClassDefFoundError: org/apache/cxf/jaxrs/impl/MetadataMap in REST invokation from WebLogic with Jersey, CXF

Complete stack trace:

java.lang.NoClassDefFoundError: org/apache/cxf/jaxrs/impl/MetadataMap
        at org.apache.cxf.jaxrs.impl.UriBuilderImpl.(UriBuilderImpl.java:54)
        at org.apache.cxf.jaxrs.impl.RuntimeDelegateImpl.createUriBuilder(RuntimeDelegateImpl.java:87)
        at javax.ws.rs.core.UriBuilder.newInstance(UriBuilder.java:69)
        at javax.ws.rs.core.UriBuilder.fromUri(UriBuilder.java:80)
        at javax.ws.rs.core.UriBuilder.fromUri(UriBuilder.java:99)
        at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:662)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:844)
        at weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:242)
        at weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:216)
        at weblogic.servlet.internal.StubSecurityHelper.invokeServlet(StubSecurityHelper.java:132)
        at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:338)
        at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:221)
        at weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.wrapRun(WebAppServletContext.java:3284)
        at weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.run(WebAppServletContext.java:3254)
        at weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:321)
        at weblogic.security.service.SecurityManager.runAs(SecurityManager.java:120)
        at weblogic.servlet.provider.WlsSubjectHandle.run(WlsSubjectHandle.java:57)
        at weblogic.servlet.internal.WebAppServletContext.doSecuredExecute(WebAppServletContext.java:2163)
        at weblogic.servlet.internal.WebAppServletContext.securedExecute(WebAppServletContext.java:2089)
        at weblogic.servlet.internal.WebAppServletContext.execute(WebAppServletContext.java:2074)
        at weblogic.servlet.internal.ServletRequestImpl.run(ServletRequestImpl.java:1513)
        at weblogic.servlet.provider.ContainerSupportProviderImpl$WlsRequestExecutor.run(ContainerSupportProviderImpl.java:254)
        at weblogic.work.ExecuteThread.execute(ExecuteThread.java:256)
        at weblogic.work.ExecuteThread.run(ExecuteThread.java:221)

The WebLogic environment in which the error is generated contains the big bundle CXF jar (cxf-x.y.z.jar) in the classpath (which can't be removed right now).

As can be seen in the stack trace, the execution jumps from Jersey code to CXF code.

In this Jersey mailing list't thread, a similar problem is discused; one of the proposed solutions worked for me.

I created a ServletContextListener containing the following line in the contextInitialized method:

RuntimeDelegate.setInstance(new com.sun.jersey.server.impl.provider.RuntimeDelegateImpl());

Complete code:

package xyz;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.ws.rs.ext.RuntimeDelegate;

public class JerseyRuntimeDelegateEarlyRegistrationContextListener implements ServletContextListener {

@Override
public void contextDestroyed(ServletContextEvent arg0) {
}

@Override
public void contextInitialized(ServletContextEvent arg0) {
RuntimeDelegate.setInstance(new com.sun.jersey.server.impl.provider.RuntimeDelegateImpl());
}

}

Web.xml registration:

<web-app>
    <listener>
<listener-class>xyz.JerseyRuntimeDelegateEarlyRegistrationContextListener</listener-class>
</listener>






jueves, 21 de noviembre de 2013

"An invalid security token was provided..." invoking CXF Web Services

Error:  An invalid security token was provided (An error happened processing a Username Token "{0}")

Complete SOAP fault response:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <soap:Fault>
      <faultcode xmlns:ns1="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">ns1:InvalidSecurityToken</faultcode>
      <faultstring>An invalid security token was provided (An error happened processing a Username Token "{0}")</faultstring>
    </soap:Fault>
  </soap:Body>
</soap:Envelope>

There are lots of  reasons which can cause this error. As stated by the message, there's a problem in the security token included in the SOAP request.

In my case, the Web service client was not sending the Type attribute for the Password element of the UsernameToken's Security element.

The error was introduced after upgrading the used CXF version (from an very old version to 2.6.1). The underlying project which implements WS Security in CXF is WSS4J. CXF 2.6.1 ships WSS4J 1.6.6. WSS4J 1.6 enforces compliance to the Basic Security Profile 1.1 (BSP) specification by default. The older CXF version shipped an older WSS4J version which did not enforce compliance to the BSP spec.

It seems the Type attribute for the password element is optional in WS-Security but mandatory in the BSP spec.

In this case we choosed not to demand the clients to include the attribute but to configure the service to not to be compliant with the BSP spec. We use Spring configuration for CXF; this can be easily done in the XML configuration file:

<jaxws:inInterceptors>
    <bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
        <constructor-arg>
            <map>
                 ...
                 <entry key="isBSPCompliant" value="false"/>  
                 ...
            </map>
        </constructor-arg>
    </bean>
</jaxws:inInterceptors&gt

This error also usually happens with the EncodingType attribute.

This conversation proposes an alternative configuration parameter: ws-security.is-bsp-compliant.

References: