blob: b71fa2687f2f95ac25fa655a49975c275f6093b1 [file] [log] [blame]
<?xml version="1.0"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<appendix xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="JMS-Client-0-8-Appendix-JMS-Extensions">
<title>JMS Extensions</title>
<para>This section illustrates using Qpid specific extensions to JMS for the management of connections, queues,
exchanges and bindings.</para>
<!-- TODO perhaps mention ConnectionListener?-->
<important>
<para>It is not recommended that these extensions are generally used. These interfaces are
subject to change and will not be supported in this form for AMQP 1.0. Instead, the reader is
directed towards the Managment interfaces of the Broker.</para>
</important>
<section xml:id="JMS-Client-0-8-Appendix-JMS-Extensions-Connection">
<title>Connection extensions</title>
<para>Connection extensions allows overriding connection configurations like username or password
in response to some environment changes like account rotation or authentication token expiration.</para>
<para>
The extensions take the form of a BiFunction&lt;Connection, URI, Object&gt; passed into the
ConnectionFactory using the AMQConnectionFactory#setExtension(String, BiFunction).
</para>
<para>A table below lists supported extensions.</para>
<table pgwide="1">
<title>Connection Extensions</title>
<tgroup cols="2">
<thead>
<row>
<entry>Extension Name</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>username</entry>
<entry><para>Allows to hook a custom code for provisioning of user name which would be used in authentication
with a remote host.</para></entry>
</row>
<row>
<entry>password</entry>
<entry><para>Allows to hook a custom code for provisioning of user password which would be used in
authentication with a remote host.</para></entry>
</row>
</tbody>
</tgroup>
</table>
<para>The following example illustrates how expirate OAUTH2 authentication token can be recreated</para>
<example>
<title>Inject password extension</title>
<programlisting>
final String connectionURL = "..."; <co xml:id="ext-token-url" linkends="callout-ext-token-url"/>
final TokenGenerator tokenGenerator = new TokenGenerator(...); <co xml:id="ext-token-generator" linkends="callout-ext-token-generator"/>
final BiFunction&lt;Connection, URI, Object&gt; tokenExtension = new BiFunction&lt;Connection, URI, Object&gt;() <co xml:id="ext-token-extension" linkends="callout-ext-token-extension"/>
{
private volatile String token;
private long currentTokenExpirationTime;
@Override
public Object apply(final Connection connection, final URI uri)
{
long currentTime = System.currentTimeMillis();
if (currentTime &gt; currentTokenExpirationTime) <co xml:id="ext-token-expiration-check" linkends="callout-ext-token-expiration-check"/>
{
this.token = tokenGenerator.generateAccessToken(); <co xml:id="ext-token-generation" linkends="callout-ext-token-generation"/>
this.currentTokenExpirationTime = tokenGenerator.getTokenExpirationTime(token); <co xml:id="ext-token-exp-time" linkends="callout-ext-token-exp-time"/>
}
return this.token;
}
};
final AMQConnectionFactory factory = new AMQConnectionFactory(connectionURL); <co xml:id="ext-token-connection-factory" linkends="callout-ext-token-connection-factory"/>
factory.setExtension(ConnectionExtension.PASSWORD_OVERRIDE.name(), tokenExtension); <co xml:id="ext-token-override" linkends="callout-ext-token-override"/>
final Connection connection = factory.createConnection(); <co xml:id="ext-token-connection" linkends="callout-ext-token-connection"/>
</programlisting>
</example><calloutlist>
<callout xml:id="callout-ext-token-url" arearefs="ext-token-url">
<para>Connection URL</para>
</callout>
<callout xml:id="callout-ext-token-generator" arearefs="ext-token-generator">
<para>Helper object to generate access token for a specific OAUTH2 implementation </para>
</callout>
<callout xml:id="callout-ext-token-extension" arearefs="ext-token-extension">
<para>Password extension for token renewal</para>
</callout>
<callout xml:id="callout-ext-token-expiration-check" arearefs="ext-token-expiration-check">
<para>Check token expiration</para>
</callout>
<callout xml:id="callout-ext-token-generation" arearefs="ext-token-generation">
<para>Get new token</para>
</callout>
<callout xml:id="callout-ext-token-exp-time" arearefs="ext-token-exp-time">
<para>Preserve token expiration time</para>
</callout>
<callout xml:id="callout-ext-token-connection-factory" arearefs="ext-token-connection-factory">
<para>Create connection factory</para>
</callout>
<callout xml:id="callout-ext-token-override" arearefs="ext-token-override">
<para>Register password extension for token regeneration</para>
</callout>
<callout xml:id="callout-ext-token-connection" arearefs="ext-token-connection">
<para>Open connection</para>
</callout>
</calloutlist>
<para>In the snippet above an implementation of BiFunction&lt;Connection, URI, Object&gt; is created at (3) for
access token provisioning. The function implementation checks the token expiration at (4) and regenerate
the token at (5) using a helper object (2) implementing calls to OAUTH2 specific API.
The token expiration time is preserved at (6) for the following reconnects attempts.
An instance of AMQConnectionFactory is created at (7) for a given connection URL (1).
A password extension is registered at (8). JMS connection is open at (9). The example uses a hypothetical
class TokenGenerator invoking underlying OAUTH2 API to generate/renew access token and get token expiration time.</para>
</section>
<section xml:id="JMS-Client-0-8-Appendix-JMS-Extensions-ConnectAttemptListener">
<title>ConnectAttemptListener</title>
<para>An implementation of <emphasis>ConnectAttemptListener</emphasis> can be set on <emphasis>AMQConnectionFactory</emphasis>
or <emphasis>PooledConnectionFactory</emphasis> in order to notify messaging application about every successful
and unsuccessful connectivity attempt.
</para>
<para>
The failed attempt notification can be used as a mechanism to rotate expired credentials,
if those are set as connection extensions.
The implementation can examine the error code reported as part of JMSException, and, if the error code corresponds
to authentication failure codes ("530" is reported by AMQP 0-8..0-91, "320" is reported by AMQP 0-10), the
credentials could be swapped with new ones using the connection extension mechanism.
See <xref linkend="JMS-Client-0-8-Appendix-JMS-Extensions-Connection"/> for details.
</para>
<para>The following <emphasis>ConnectAttemptListener</emphasis> illustrate the idea</para>
<example>
<title>Inject password extension</title>
<programlisting>
class CredentialsRotatingListener implements ConnectAttemptListener
{
@Override
public boolean connectAttemptFailed(final URI brokerURI, final JMSException e)
{
boolean reattempt = "530".equals(e.getErrorCode()) || "320".equals(e.getErrorCode());
if (reattempt)
{
rotateCredentials(brokerURI);
}
return reattempt;
}
@Override
public void connectAttemptSucceeded(final URI brokerURI)
{
credentialsRotatedSuccessfully(brokerURI);
}
private void rotateCredentials(inal URI brokerURI)
{
// credential rotating logic
}
private void credentialsRotatedSuccessfully(final URI brokerURI)
{
// notify that credentials have been rotated successfully
}
}
</programlisting>
</example>
<para>
The method <emphasis>connectAttemptFailed</emphasis> can return true, if connection attempt needs to be repeated
to the same broker immediately and without incrementing a failover re-try counter.
Otherwise, the connection would be attempted as per failover settings.
</para>
</section>
<section xml:id="JMS-Client-0-8-Appendix-JMS-Extensions-Queue">
<title>Queue Management</title>
<para>These extensions allow queues to be created or removed.</para>
<section xml:id="JMS-Client-0-8-Appendix-JMS-Extensions-Queue-Creation">
<title>Queue creation</title>
<para>The following example illustrates the creation of the a LVQ queue from a
javax.jms.Session object. Note that this utilises a Qpid specific extension to JMS and
involves casting the session object back to its Qpid base-class.</para>
<example>
<title>Creation of an LVQ using the Qpid extension to JMS</title>
<programlisting>Map&lt;String,Object&gt; arguments = new HashMap&lt;String, Object&gt;();
arguments.put("qpid.last_value_queue_key","ISIN");
AMQDestination amqQueue = (AMQDestination) context.lookup("myqueue");
((AMQSession&lt;?,?&gt;) session).createQueue(
AMQShortString.valueOf(amqQueue.getQueueName()),
amqQueue.isAutoDelete(),
amqQueue.isDurable(),
amqQueue.isExclusive(),
arguments);
</programlisting>
</example>
</section>
</section>
<section xml:id="JMS-Client-0-8-Appendix-JMS-Extensions-Binding">
<title>Binding Management</title>
<para>These extensions allow bindings to be created or removed.</para>
<section xml:id="JMS-Client-0-8-Appendix-JMS-Extensions-Binding-Creation">
<title>Binding creation</title>
<para>The following example illustrates the creation of queue binding to topic exchange with
JMS client.</para>
<example>
<title>Binding a queue using JMS</title>
<programlisting>ConnectionFactory connectionFactory = ...
Connection connection = connectionFactory.createConnection();
AMQSession&lt;?, ?&gt; session = (AMQSession&lt;?,?&gt;)connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
...
AMQShortString queueName = new AMQShortString("testQueue");
AMQShortString routingKey = new AMQShortString("testRoutingKey");
AMQDestination destination = (AMQDestination) session.createQueue(queueName.asString());
...
// binding arguments
Map&lt;String, Object&gt; arguments = new HashMap&lt;String, Object&gt;();
arguments.put("x-filter-jms-selector", "application='app1'");
// create binding
session.bindQueue(queueName, routingKey, FieldTable.convertToFieldTable(arguments),
new AMQShortString("amq.topic"), destination);</programlisting>
</example>
</section>
</section>
</appendix>