| <?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<Connection, URI, Object> 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<Connection, URI, Object> tokenExtension = new BiFunction<Connection, URI, Object>() <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 > 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<Connection, URI, Object> 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<String,Object> arguments = new HashMap<String, Object>(); |
| arguments.put("qpid.last_value_queue_key","ISIN"); |
| AMQDestination amqQueue = (AMQDestination) context.lookup("myqueue"); |
| ((AMQSession<?,?>) 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<?, ?> session = (AMQSession<?,?>)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<String, Object> arguments = new HashMap<String, Object>(); |
| 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> |