createSender(String address, AmqpSenderOptions options);
99 |
100 | /**
101 | * Creates an anonymous sender.
102 | *
103 | * Unlike "regular" sender, this sender is not associated to a specific address, and each message sent must provide
104 | * an address. This method can be used in request-reply scenarios where you create a sender to send the reply,
105 | * but you don't know the address, as the reply address is passed into the message you are going to receive.
106 | *
107 | * @return a future notifid with the created sender, once opened
108 | */
109 | Future createAnonymousSender();
110 |
111 | /**
112 | * @return whether the connection has been disconnected.
113 | */
114 | boolean isDisconnected();
115 |
116 | /**
117 | * @return a future completed when the connection is closed
118 | */
119 | Future closeFuture();
120 |
121 | /**
122 | * @return the underlying ProtonConnection.
123 | */
124 | @GenIgnore(PERMITTED_TYPE)
125 | ProtonConnection unwrap();
126 |
127 | }
128 |
--------------------------------------------------------------------------------
/src/main/java/io/vertx/amqp/AmqpMessage.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018-2019 The original author or authors
3 | *
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * and Apache License v2.0 which accompanies this distribution.
7 | *
8 | * The Eclipse Public License is available at
9 | * http://www.eclipse.org/legal/epl-v10.html
10 | *
11 | * The Apache License v2.0 is available at
12 | * http://www.opensource.org/licenses/apache2.0.php
13 | *
14 | * You may elect to redistribute this code under either of these licenses.
15 | */
16 | package io.vertx.amqp;
17 |
18 | import io.vertx.amqp.impl.AmqpMessageBuilderImpl;
19 | import io.vertx.codegen.annotations.Fluent;
20 | import io.vertx.codegen.annotations.GenIgnore;
21 | import io.vertx.codegen.annotations.VertxGen;
22 | import io.vertx.core.buffer.Buffer;
23 | import io.vertx.core.json.JsonArray;
24 | import io.vertx.core.json.JsonObject;
25 | import org.apache.qpid.proton.message.Message;
26 |
27 | import java.time.Instant;
28 | import java.util.List;
29 | import java.util.Map;
30 | import java.util.UUID;
31 |
32 | /**
33 | * Represents an AMQP message.
34 | *
35 | * Reference about the different metadata can be found on
36 | * AMQP message properties.
37 | *
38 | * Note that the body is retrieved using {@code body*} method depending on the expected type.
39 | */
40 | @VertxGen
41 | public interface AmqpMessage {
42 |
43 | /**
44 | * @return a builder to create an {@link AmqpMessage}.
45 | */
46 | static AmqpMessageBuilder create() {
47 | return new AmqpMessageBuilderImpl();
48 | }
49 |
50 | /**
51 | * Creates a builder to create a new {@link AmqpMessage} copying the metadata from the passed message.
52 | *
53 | * @param existing an existing message, must not be {@code null}.
54 | * @return a builder to create an {@link AmqpMessage}.
55 | */
56 | static AmqpMessageBuilder create(AmqpMessage existing) {
57 | return new AmqpMessageBuilderImpl(existing);
58 | }
59 |
60 | /**
61 | * Creates a builder to create a new {@link AmqpMessage} copying the metadata from the passed (Proton) message.
62 | *
63 | * @param existing an existing (Proton) message, must not be {@code null}.
64 | * @return a builder to create an {@link AmqpMessage}.
65 | */
66 | @GenIgnore
67 | static AmqpMessageBuilder create(Message existing) {
68 | return new AmqpMessageBuilderImpl(existing);
69 | }
70 |
71 | /**
72 | * @return whether or not the message is durable.
73 | * @see AMQP specification
74 | */
75 | boolean isDurable();
76 |
77 | /**
78 | * @return if {@code true}, then this message has not been acquired by any other link. If {@code false}, then this
79 | * message MAY have previously been acquired by another link or links.
80 | * @see AMQP specification
81 | */
82 | boolean isFirstAcquirer();
83 |
84 | /**
85 | * @return the relative message priority. Higher numbers indicate higher priority messages. Messages with higher
86 | * priorities MAY be delivered before those with lower priorities.
87 | * @see AMQP specification
88 | */
89 | int priority();
90 |
91 | /**
92 | * @return the number of unsuccessful previous attempts to deliver this message. If this value is non-zero it can be
93 | * taken as an indication that the delivery might be a duplicate. On first delivery, the value is zero. It is
94 | * incremented upon an outcome being settled at the sender, according to rules defined for each outcome.
95 | * @see AMQP specification
96 | */
97 | int deliveryCount();
98 |
99 | /**
100 | * @return the duration in milliseconds for which the message is to be considered "live".
101 | * @see AMQP specification
102 | */
103 | long ttl();
104 |
105 | /**
106 | * @return the message id
107 | * @see AMQP specification
108 | */
109 | String id();
110 |
111 | /**
112 | * @return the message address, also named {@code to} field
113 | * @see AMQP specification
114 | */
115 | String address();
116 |
117 | /**
118 | * @return The address of the node to send replies to, if any.
119 | * @see AMQP specification
120 | */
121 | String replyTo();
122 |
123 | /**
124 | * @return The client-specific id that can be used to mark or identify messages between clients.
125 | * @see AMQP specification
126 | */
127 | String correlationId();
128 |
129 | /**
130 | * @return whether the body is {@code null}. This method returns {@code true} is the message does not contain a body or
131 | * if the message contain a {@code null} AMQP value as body.
132 | */
133 | boolean isBodyNull();
134 |
135 | /**
136 | * @return the boolean value contained in the body. The value must be passed as AMQP value.
137 | */
138 | boolean bodyAsBoolean();
139 |
140 | /**
141 | * @return the byte value contained in the body. The value must be passed as AMQP value.
142 | */
143 | byte bodyAsByte();
144 |
145 | /**
146 | * @return the short value contained in the body. The value must be passed as AMQP value.
147 | */
148 | short bodyAsShort();
149 |
150 | /**
151 | * @return the integer value contained in the body. The value must be passed as AMQP value.
152 | */
153 | int bodyAsInteger();
154 |
155 | /**
156 | * @return the long value contained in the body. The value must be passed as AMQP value.
157 | */
158 | long bodyAsLong();
159 |
160 | /**
161 | * @return the float value contained in the body. The value must be passed as AMQP value.
162 | */
163 | float bodyAsFloat();
164 |
165 | /**
166 | * @return the double value contained in the body. The value must be passed as AMQP value.
167 | */
168 | double bodyAsDouble();
169 |
170 | /**
171 | * @return the character value contained in the body. The value must be passed as AMQP value.
172 | */
173 | char bodyAsChar();
174 |
175 | /**
176 | * @return the timestamp value contained in the body. The value must be passed as AMQP value.
177 | */
178 | @GenIgnore(GenIgnore.PERMITTED_TYPE)
179 | Instant bodyAsTimestamp();
180 |
181 | /**
182 | * @return the UUID value contained in the body. The value must be passed as AMQP value.
183 | */
184 | @GenIgnore(GenIgnore.PERMITTED_TYPE)
185 | UUID bodyAsUUID();
186 |
187 | /**
188 | * @return the bytes contained in the body. The value must be passed as AMQP data.
189 | */
190 | Buffer bodyAsBinary();
191 |
192 | /**
193 | * @return the string value contained in the body. The value must be passed as AMQP value.
194 | */
195 | String bodyAsString();
196 |
197 | /**
198 | * @return the symbol value contained in the body. The value must be passed as AMQP value.
199 | */
200 | String bodyAsSymbol();
201 |
202 | /**
203 | * @return the list of values contained in the body. The value must be passed as AMQP value.
204 | */
205 | List bodyAsList();
206 |
207 | /**
208 | * @return the map contained in the body. The value must be passed as AMQP value.
209 | */
210 | @GenIgnore
211 | Map bodyAsMap();
212 |
213 | /**
214 | * @return the JSON object contained in the body. The value must be passed as AMQP data.
215 | */
216 | JsonObject bodyAsJsonObject();
217 |
218 | /**
219 | * @return the JSON array contained in the body. The value must be passed as AMQP data.
220 | */
221 | JsonArray bodyAsJsonArray();
222 |
223 | String subject();
224 |
225 | String contentType();
226 |
227 | String contentEncoding();
228 |
229 | long expiryTime();
230 |
231 | long creationTime();
232 |
233 | String groupId();
234 |
235 | String replyToGroupId();
236 |
237 | long groupSequence();
238 |
239 | /**
240 | * @return the message properties as JSON object.
241 | */
242 | JsonObject applicationProperties();
243 |
244 | @GenIgnore
245 | Message unwrap();
246 |
247 | /**
248 | * When receiving a message, and when auto-acknowledgement is disabled, this method is used to acknowledge
249 | * the incoming message. It marks the message as delivered with the {@code accepted} status.
250 | *
251 | * @return the current {@link AmqpMessage} object
252 | * @throws IllegalStateException is the current message is not a received message.
253 | */
254 | @Fluent
255 | AmqpMessage accepted();
256 |
257 | /**
258 | * When receiving a message, and when auto-acknowledgement is disabled, this method is used to acknowledge
259 | * the incoming message as {@code rejected}.
260 | *
261 | * @return the current {@link AmqpMessage} object
262 | * @throws IllegalStateException is the current message is not a received message.
263 | */
264 | @Fluent
265 | AmqpMessage rejected();
266 |
267 | /**
268 | * When receiving a message, and when auto-acknowledgement is disabled, this method is used to acknowledge
269 | * the incoming message as {@code released}.
270 | *
271 | * @return the current {@link AmqpMessage} object
272 | * @throws IllegalStateException is the current message is not a received message.
273 | */
274 | @Fluent
275 | AmqpMessage released();
276 |
277 | /**
278 | * When receiving a message, and when auto-acknowledgement is disabled, this method is used to acknowledge
279 | * the incoming message as {@code modified}.
280 | *
281 | * @param deliveryFailed pass {@code true} to increase the failed delivery count
282 | * @param undeliverableHere pass {@code true} to prevent re-delivery of this message to the same consumer
283 | * @return the current {@link AmqpMessage} object
284 | * @throws IllegalStateException is the current message is not a received message.
285 | */
286 | @Fluent
287 | AmqpMessage modified(boolean deliveryFailed, boolean undeliverableHere);
288 |
289 |
290 | //TODO What type should we use for delivery annotations and message annotations
291 |
292 | }
293 |
--------------------------------------------------------------------------------
/src/main/java/io/vertx/amqp/AmqpMessageBuilder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018-2019 The original author or authors
3 | *
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * and Apache License v2.0 which accompanies this distribution.
7 | *
8 | * The Eclipse Public License is available at
9 | * http://www.eclipse.org/legal/epl-v10.html
10 | *
11 | * The Apache License v2.0 is available at
12 | * http://www.opensource.org/licenses/apache2.0.php
13 | *
14 | * You may elect to redistribute this code under either of these licenses.
15 | */
16 | package io.vertx.amqp;
17 |
18 | import io.vertx.amqp.impl.AmqpMessageBuilderImpl;
19 | import io.vertx.codegen.annotations.GenIgnore;
20 | import io.vertx.codegen.annotations.VertxGen;
21 | import io.vertx.core.buffer.Buffer;
22 | import io.vertx.core.json.JsonArray;
23 | import io.vertx.core.json.JsonObject;
24 |
25 | import java.time.Instant;
26 | import java.util.List;
27 | import java.util.Map;
28 | import java.util.UUID;
29 |
30 | /**
31 | * Builder to create a new {@link AmqpMessage}.
32 | *
33 | * Reference about the different metadata can be found on
34 | * AMQP message properties.
35 | *
36 | * Note that the body is set using {@code withBodyAs*} method depending on the passed type.
37 | */
38 | @VertxGen
39 | public interface AmqpMessageBuilder {
40 |
41 | /**
42 | * @return a new instance of {@link AmqpMessageBuilder}
43 | */
44 | static AmqpMessageBuilder create() {
45 | return new AmqpMessageBuilderImpl();
46 | }
47 |
48 | /**
49 | * @return the message.
50 | */
51 | AmqpMessage build();
52 |
53 | // Headers
54 |
55 | AmqpMessageBuilder priority(short priority);
56 |
57 | AmqpMessageBuilder durable(boolean durable);
58 |
59 | AmqpMessageBuilder ttl(long ttl);
60 |
61 | AmqpMessageBuilder firstAcquirer(boolean first);
62 |
63 | AmqpMessageBuilder deliveryCount(int count);
64 |
65 | // Properties
66 |
67 | AmqpMessageBuilder id(String id);
68 |
69 | AmqpMessageBuilder address(String address);
70 |
71 | AmqpMessageBuilder replyTo(String replyTo);
72 |
73 | AmqpMessageBuilder correlationId(String correlationId);
74 |
75 | AmqpMessageBuilder withBody(String value);
76 |
77 | AmqpMessageBuilder withSymbolAsBody(String value);
78 |
79 | AmqpMessageBuilder subject(String subject);
80 |
81 | AmqpMessageBuilder contentType(String ct);
82 |
83 | AmqpMessageBuilder contentEncoding(String ct);
84 |
85 | AmqpMessageBuilder expiryTime(long expiry);
86 |
87 | AmqpMessageBuilder creationTime(long ct);
88 |
89 | AmqpMessageBuilder groupId(String gi);
90 |
91 | AmqpMessageBuilder replyToGroupId(String rt);
92 |
93 | AmqpMessageBuilder applicationProperties(JsonObject props);
94 |
95 | AmqpMessageBuilder withBooleanAsBody(boolean v);
96 |
97 | AmqpMessageBuilder withByteAsBody(byte v);
98 |
99 | AmqpMessageBuilder withShortAsBody(short v);
100 |
101 | AmqpMessageBuilder withIntegerAsBody(int v);
102 |
103 | AmqpMessageBuilder withLongAsBody(long v);
104 |
105 | AmqpMessageBuilder withFloatAsBody(float v);
106 |
107 | AmqpMessageBuilder withDoubleAsBody(double v);
108 |
109 | AmqpMessageBuilder withCharAsBody(char c);
110 |
111 | @GenIgnore(GenIgnore.PERMITTED_TYPE)
112 | AmqpMessageBuilder withInstantAsBody(Instant v);
113 |
114 | @GenIgnore(GenIgnore.PERMITTED_TYPE)
115 | AmqpMessageBuilder withUuidAsBody(UUID v);
116 |
117 | @GenIgnore
118 | AmqpMessageBuilder withListAsBody(List list);
119 |
120 | @GenIgnore
121 | AmqpMessageBuilder withMapAsBody(Map map);
122 |
123 | AmqpMessageBuilder withBufferAsBody(Buffer buffer);
124 |
125 | AmqpMessageBuilder withJsonObjectAsBody(JsonObject json);
126 |
127 | AmqpMessageBuilder withJsonArrayAsBody(JsonArray json);
128 | }
129 |
--------------------------------------------------------------------------------
/src/main/java/io/vertx/amqp/AmqpReceiver.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018-2019 The original author or authors
3 | *
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * and Apache License v2.0 which accompanies this distribution.
7 | *
8 | * The Eclipse Public License is available at
9 | * http://www.eclipse.org/legal/epl-v10.html
10 | *
11 | * The Apache License v2.0 is available at
12 | * http://www.opensource.org/licenses/apache2.0.php
13 | *
14 | * You may elect to redistribute this code under either of these licenses.
15 | */
16 | package io.vertx.amqp;
17 |
18 | import io.vertx.codegen.annotations.CacheReturn;
19 | import io.vertx.codegen.annotations.GenIgnore;
20 | import io.vertx.codegen.annotations.Nullable;
21 | import io.vertx.codegen.annotations.VertxGen;
22 | import io.vertx.core.Future;
23 | import io.vertx.core.Handler;
24 | import io.vertx.core.streams.ReadStream;
25 | import io.vertx.proton.ProtonReceiver;
26 |
27 | import static io.vertx.codegen.annotations.GenIgnore.PERMITTED_TYPE;
28 |
29 | /**
30 | * Interface used to consume AMQP message as a stream of message.
31 | * Back pressure is implemented using AMQP credits.
32 | */
33 | @VertxGen
34 | public interface AmqpReceiver extends ReadStream {
35 |
36 | @Override
37 | AmqpReceiver exceptionHandler(Handler handler);
38 |
39 | @Override
40 | AmqpReceiver handler(@Nullable Handler handler);
41 |
42 | @Override
43 | AmqpReceiver pause();
44 |
45 | @Override
46 | AmqpReceiver resume();
47 |
48 | @Override
49 | AmqpReceiver fetch(long amount);
50 |
51 | @Override
52 | AmqpReceiver endHandler(@Nullable Handler endHandler);
53 |
54 | /**
55 | * The listened address.
56 | *
57 | * @return the address, not {@code null}
58 | */
59 | @CacheReturn
60 | String address();
61 |
62 | /**
63 | * Closes the receiver.
64 | *
65 | * @return a future notified when the receiver has been closed
66 | */
67 | Future close();
68 |
69 | /**
70 | * Gets the connection having created the receiver. Cannot be {@code null}
71 | *
72 | * @return the connection having created the receiver.
73 | */
74 | AmqpConnection connection();
75 |
76 | /**
77 | * @return the underlying ProtonReceiver.
78 | */
79 | @GenIgnore(PERMITTED_TYPE)
80 | ProtonReceiver unwrap();
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/io/vertx/amqp/AmqpReceiverOptions.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018-2019 The original author or authors
3 | *
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * and Apache License v2.0 which accompanies this distribution.
7 | *
8 | * The Eclipse Public License is available at
9 | * http://www.eclipse.org/legal/epl-v10.html
10 | *
11 | * The Apache License v2.0 is available at
12 | * http://www.opensource.org/licenses/apache2.0.php
13 | *
14 | * You may elect to redistribute this code under either of these licenses.
15 | */
16 | package io.vertx.amqp;
17 |
18 | import io.vertx.codegen.annotations.DataObject;
19 | import io.vertx.codegen.json.annotations.JsonGen;
20 | import io.vertx.core.json.JsonObject;
21 |
22 | import java.util.ArrayList;
23 | import java.util.List;
24 | import java.util.Objects;
25 |
26 | /**
27 | * Configures the AMQP Receiver.
28 | */
29 | @DataObject
30 | @JsonGen(publicConverter = false)
31 | public class AmqpReceiverOptions {
32 |
33 | private String linkName;
34 | private boolean dynamic;
35 | private String qos;
36 | private List capabilities = new ArrayList<>();
37 | private boolean durable;
38 | private int maxBufferedMessages;
39 | private boolean autoAcknowledgement = true;
40 | private boolean noLocal;
41 | private String selector;
42 |
43 | public AmqpReceiverOptions() {
44 |
45 | }
46 |
47 | public AmqpReceiverOptions(AmqpReceiverOptions other) {
48 | this();
49 | setDynamic(other.isDynamic());
50 | setLinkName(other.getLinkName());
51 | setCapabilities(other.getCapabilities());
52 | setDurable(other.isDurable());
53 | setMaxBufferedMessages(other.maxBufferedMessages);
54 | setNoLocal(other.noLocal);
55 | setSelector(other.selector);
56 | }
57 |
58 | public AmqpReceiverOptions(JsonObject json) {
59 | super();
60 | AmqpReceiverOptionsConverter.fromJson(json, this);
61 | }
62 |
63 | public JsonObject toJson() {
64 | JsonObject json = new JsonObject();
65 | AmqpReceiverOptionsConverter.toJson(this, json);
66 | return json;
67 | }
68 |
69 | public String getLinkName() {
70 | return linkName;
71 | }
72 |
73 | public AmqpReceiverOptions setLinkName(String linkName) {
74 | this.linkName = linkName;
75 | return this;
76 | }
77 |
78 | /**
79 | * @return whether the receiver is using a dynamic address.
80 | */
81 | public boolean isDynamic() {
82 | return dynamic;
83 | }
84 |
85 | /**
86 | * Sets whether the Source terminus to be used should specify it is 'dynamic',
87 | * requesting the peer creates a node and names it with a generated address.
88 | *
89 | * The address provided by the peer can then be inspected using the
90 | * {@link AmqpReceiver#address()} method on the {@link AmqpReceiver} received once opened.
91 | *
92 | * @param dynamic true if the receiver should request dynamic creation of a node and address to consume from
93 | * @return the options
94 | */
95 | public AmqpReceiverOptions setDynamic(boolean dynamic) {
96 | this.dynamic = dynamic;
97 | return this;
98 | }
99 |
100 | /**
101 | * Gets the local QOS config, values can be {@code null}, {@code AT_MOST_ONCE} or {@code AT_LEAST_ONCE}.
102 | *
103 | * @return the local QOS config.
104 | */
105 | public String getQos() {
106 | return qos;
107 | }
108 |
109 | /**
110 | * Sets the local QOS config.
111 | *
112 | * @param qos the local QOS config. Accepted values are: {@code null}, {@code AT_MOST_ONCE} or {@code AT_LEAST_ONCE}.
113 | * @return the options.
114 | */
115 | public AmqpReceiverOptions setQos(String qos) {
116 | this.qos = qos;
117 | return this;
118 | }
119 |
120 | /**
121 | * Gets the list of capabilities to be set on the receiver source terminus.
122 | *
123 | * @return the list of capabilities, empty if none.
124 | */
125 | public List getCapabilities() {
126 | if (capabilities == null) {
127 | return new ArrayList<>();
128 | }
129 | return capabilities;
130 | }
131 |
132 | /**
133 | * Sets the list of capabilities to be set on the receiver source terminus.
134 | *
135 | * @param capabilities the set of source capabilities.
136 | * @return the options
137 | */
138 | public AmqpReceiverOptions setCapabilities(List capabilities) {
139 | this.capabilities = capabilities;
140 | return this;
141 | }
142 |
143 | /**
144 | * Adds a capability to be set on the receiver source terminus.
145 | *
146 | * @param capability the source capability to add, must not be {@code null}
147 | * @return the options
148 | */
149 | public AmqpReceiverOptions addCapability(String capability) {
150 | Objects.requireNonNull(capability, "The capability must not be null");
151 | if (this.capabilities == null) {
152 | this.capabilities = new ArrayList<>();
153 | }
154 | this.capabilities.add(capability);
155 | return this;
156 | }
157 |
158 | /**
159 | * @return if the receiver is durable.
160 | */
161 | public boolean isDurable() {
162 | return durable;
163 | }
164 |
165 | /**
166 | * Sets the durability.
167 | *
168 | * Passing {@code true} sets the expiry policy of the source to {@code NEVER} and the durability of the source
169 | * to {@code UNSETTLED_STATE}.
170 | *
171 | * @param durable whether or not the receiver must indicate it's durable
172 | * @return the options.
173 | */
174 | public AmqpReceiverOptions setDurable(boolean durable) {
175 | this.durable = durable;
176 | return this;
177 | }
178 |
179 | /**
180 | * @return the max buffered messages
181 | */
182 | public int getMaxBufferedMessages() {
183 | return this.maxBufferedMessages;
184 | }
185 |
186 | /**
187 | * Sets the max buffered messages. This message can be used to configure the initial credit of a receiver.
188 | *
189 | * @param maxBufferSize the max buffered size, must be positive. If not set, default credit is used.
190 | * @return the current {@link AmqpReceiverOptions} instance.
191 | */
192 | public AmqpReceiverOptions setMaxBufferedMessages(int maxBufferSize) {
193 | this.maxBufferedMessages = maxBufferSize;
194 | return this;
195 | }
196 |
197 | /**
198 | * @return {@code true} if the auto-acknowledgement is enabled, {@code false} otherwise.
199 | */
200 | public boolean isAutoAcknowledgement() {
201 | return autoAcknowledgement;
202 | }
203 |
204 | /**
205 | * Sets the auto-acknowledgement.
206 | * When enabled (default), the messages are automatically acknowledged. If set to {@code false}, the messages must
207 | * be acknowledged explicitly using {@link AmqpMessage#accepted()}, {@link AmqpMessage#released()} and
208 | * {@link AmqpMessage#rejected()}.
209 | *
210 | * @param auto whether or not the auto-acknowledgement should be enabled.
211 | * @return the current {@link AmqpReceiverOptions} instance.
212 | */
213 | public AmqpReceiverOptions setAutoAcknowledgement(boolean auto) {
214 | this.autoAcknowledgement = auto;
215 | return this;
216 | }
217 |
218 | /**
219 | * @return the message selector String
220 | */
221 | public String getSelector() {
222 | return selector;
223 | }
224 |
225 | /**
226 | * Sets a message selector string.
227 | *
228 | * Used to define an "apache.org:selector-filter:string" filter on the source terminus, using SQL-based syntax to request
229 | * the server filters which messages are delivered to the receiver (if supported by the server in question). Precise
230 | * functionality supported and syntax needed can vary depending on the server.
231 | *
232 | * @param selector the selector string to set.
233 | */
234 | public AmqpReceiverOptions setSelector(final String selector) {
235 | this.selector = selector;
236 | return this;
237 | }
238 |
239 | /**
240 | * @return whether this receiver should not receive messages that were sent on the same underlying connection
241 | */
242 | public boolean isNoLocal() {
243 | return noLocal;
244 | }
245 |
246 | /**
247 | * Sets whether this receiver should not receive messages that were sent using the same underlying connection.
248 | *
249 | * Used to determine whether to define an "apache.org:no-local-filter:list" filter on the source terminus, requesting
250 | * that the server filters which messages are delivered to the receiver so that they do not include messages sent on
251 | * the same underlying connection (if supported by the server in question).
252 | *
253 | * @param noLocal true if this receiver shall not receive messages that were sent on the same underlying connection
254 | */
255 | public AmqpReceiverOptions setNoLocal(final boolean noLocal) {
256 | this.noLocal = noLocal;
257 | return this;
258 | }
259 | }
260 |
--------------------------------------------------------------------------------
/src/main/java/io/vertx/amqp/AmqpSender.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018-2019 The original author or authors
3 | *
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * and Apache License v2.0 which accompanies this distribution.
7 | *
8 | * The Eclipse Public License is available at
9 | * http://www.eclipse.org/legal/epl-v10.html
10 | *
11 | * The Apache License v2.0 is available at
12 | * http://www.opensource.org/licenses/apache2.0.php
13 | *
14 | * You may elect to redistribute this code under either of these licenses.
15 | */
16 | package io.vertx.amqp;
17 |
18 | import io.vertx.codegen.annotations.Fluent;
19 | import io.vertx.codegen.annotations.GenIgnore;
20 | import io.vertx.codegen.annotations.VertxGen;
21 | import io.vertx.core.Future;
22 | import io.vertx.core.Handler;
23 | import io.vertx.core.streams.WriteStream;
24 | import io.vertx.proton.ProtonSender;
25 |
26 | import static io.vertx.codegen.annotations.GenIgnore.PERMITTED_TYPE;
27 |
28 | /**
29 | * AMQP Sender interface used to send messages.
30 | */
31 | @VertxGen
32 | public interface AmqpSender extends WriteStream {
33 |
34 | @Override
35 | AmqpSender exceptionHandler(Handler handler);
36 |
37 | @Override
38 | AmqpSender setWriteQueueMaxSize(int maxSize);
39 |
40 | /**
41 | * Sends an AMQP message. The destination the configured sender address or the address configured in the message.
42 | *
43 | * @param message the message, must not be {@code null}
44 | * @return the current sender
45 | */
46 | @Fluent
47 | AmqpSender send(AmqpMessage message);
48 |
49 | /**
50 | * Sends an AMQP message and waits for an acknowledgement. It returns a future marked as failed if the message
51 | * has been rejected or re-routed. If the message has been accepted, the handler is called with a success.
52 | *
53 | * @param message the message, must not be {@code null}
54 | * @return a future notified with the acknowledgement
55 | * @return the current sender
56 | */
57 | Future sendWithAck(AmqpMessage message);
58 |
59 | /**
60 | * Closes the sender.
61 | *
62 | * @return a future notified when the sender has been closed
63 | */
64 | Future close();
65 |
66 | /**
67 | * @return the configured address.
68 | */
69 | String address();
70 |
71 | /**
72 | * Gets the connection having created the sender. Cannot be {@code null}
73 | *
74 | * @return the connection having created the sender.
75 | */
76 | AmqpConnection connection();
77 |
78 | /**
79 | * @return the remaining credit, 0 is none.
80 | */
81 | long remainingCredits();
82 |
83 | /**
84 | * @return the underlying ProtonSender.
85 | */
86 | @GenIgnore(PERMITTED_TYPE)
87 | ProtonSender unwrap();
88 | }
89 |
--------------------------------------------------------------------------------
/src/main/java/io/vertx/amqp/AmqpSenderOptions.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018-2019 The original author or authors
3 | *
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * and Apache License v2.0 which accompanies this distribution.
7 | *
8 | * The Eclipse Public License is available at
9 | * http://www.eclipse.org/legal/epl-v10.html
10 | *
11 | * The Apache License v2.0 is available at
12 | * http://www.opensource.org/licenses/apache2.0.php
13 | *
14 | * You may elect to redistribute this code under either of these licenses.
15 | */
16 | package io.vertx.amqp;
17 |
18 | import java.util.ArrayList;
19 | import java.util.List;
20 | import java.util.Objects;
21 |
22 | import io.vertx.codegen.annotations.DataObject;
23 | import io.vertx.codegen.json.annotations.JsonGen;
24 | import io.vertx.core.json.JsonObject;
25 |
26 | /**
27 | * Configures the AMQP Sender.
28 | */
29 | @DataObject
30 | @JsonGen(publicConverter = false)
31 | public class AmqpSenderOptions {
32 |
33 | private String linkName;
34 | private boolean dynamic;
35 | private boolean autoDrained = true;
36 | private List capabilities = new ArrayList<>();
37 |
38 | public AmqpSenderOptions() {
39 |
40 | }
41 |
42 | public AmqpSenderOptions(AmqpSenderOptions other) {
43 | this();
44 | setDynamic(other.isDynamic());
45 | setLinkName(other.getLinkName());
46 | setAutoDrained(other.isAutoDrained());
47 | }
48 |
49 | public AmqpSenderOptions(JsonObject json) {
50 | super();
51 | AmqpSenderOptionsConverter.fromJson(json, this);
52 | }
53 |
54 | public JsonObject toJson() {
55 | JsonObject json = new JsonObject();
56 | AmqpSenderOptionsConverter.toJson(this, json);
57 | return json;
58 | }
59 |
60 | public String getLinkName() {
61 | return linkName;
62 | }
63 |
64 | public AmqpSenderOptions setLinkName(String linkName) {
65 | this.linkName = linkName;
66 | return this;
67 | }
68 |
69 | /**
70 | * @return whether the sender is using a dynamic address.
71 | */
72 | public boolean isDynamic() {
73 | return dynamic;
74 | }
75 |
76 | /**
77 | * Sets whether the Target terminus to be used should specify it is 'dynamic',
78 | * requesting the peer creates a node and names it with a generated address.
79 | *
80 | * The address provided by the peer can then be inspected using the
81 | * {@link AmqpSender#address()} method on the {@link AmqpSender} received once opened.
82 | *
83 | * @param dynamic true if the sender should request dynamic creation of a node and address to send to
84 | * @return the options
85 | */
86 | public AmqpSenderOptions setDynamic(boolean dynamic) {
87 | this.dynamic = dynamic;
88 | return this;
89 | }
90 |
91 | /**
92 | * Get whether the link will automatically be marked drained after the send queue drain handler fires in drain mode.
93 | *
94 | * @return whether the link will automatically be drained after the send queue drain handler fires in drain mode
95 | * @see #setAutoDrained(boolean)
96 | */
97 | public boolean isAutoDrained() {
98 | return autoDrained;
99 | }
100 |
101 | /**
102 | * Sets whether the link is automatically marked drained after the send queue drain handler callback
103 | * returns if the receiving peer requested that credit be drained.
104 | *
105 | * {@code true} by default.
106 | *
107 | * @param autoDrained whether the link will automatically be drained after the send queue drain handler fires in drain mode
108 | * @return the options
109 | */
110 | public AmqpSenderOptions setAutoDrained(boolean autoDrained) {
111 | this.autoDrained = autoDrained;
112 | return this;
113 | }
114 |
115 | /**
116 | * Gets the list of capabilities to be set on the sender target terminus.
117 | *
118 | * @return the list of capabilities, empty if none.
119 | */
120 | public List getCapabilities() {
121 | if (capabilities == null) {
122 | return new ArrayList<>();
123 | }
124 | return capabilities;
125 | }
126 |
127 | /**
128 | * Sets the list of capabilities to be set on the sender target terminus.
129 | *
130 | * @param capabilities the set of target capabilities.
131 | * @return the options
132 | */
133 | public AmqpSenderOptions setCapabilities(List capabilities) {
134 | this.capabilities = capabilities;
135 | return this;
136 | }
137 |
138 | /**
139 | * Adds a capability to be set on the sender target terminus.
140 | *
141 | * @param capability the target capability to add, must not be {@code null}
142 | * @return the options
143 | */
144 | public AmqpSenderOptions addCapability(String capability) {
145 | Objects.requireNonNull(capability, "The capability must not be null");
146 | if (this.capabilities == null) {
147 | this.capabilities = new ArrayList<>();
148 | }
149 | this.capabilities.add(capability);
150 | return this;
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/src/main/java/io/vertx/amqp/impl/AmqpClientImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018-2019 The original author or authors
3 | *
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * and Apache License v2.0 which accompanies this distribution.
7 | *
8 | * The Eclipse Public License is available at
9 | * http://www.eclipse.org/legal/epl-v10.html
10 | *
11 | * The Apache License v2.0 is available at
12 | * http://www.opensource.org/licenses/apache2.0.php
13 | *
14 | * You may elect to redistribute this code under either of these licenses.
15 | */
16 | package io.vertx.amqp.impl;
17 |
18 | import io.vertx.amqp.*;
19 | import io.vertx.core.*;
20 | import io.vertx.core.internal.ContextInternal;
21 | import io.vertx.core.internal.PromiseInternal;
22 | import io.vertx.proton.ProtonClient;
23 |
24 | import java.util.ArrayList;
25 | import java.util.List;
26 | import java.util.Objects;
27 | import java.util.concurrent.CopyOnWriteArrayList;
28 |
29 | public class AmqpClientImpl implements AmqpClient {
30 |
31 | private final Vertx vertx;
32 | private final ProtonClient proton;
33 | private final AmqpClientOptions options;
34 |
35 | private final List connections = new CopyOnWriteArrayList<>();
36 | private final boolean mustCloseVertxOnClose;
37 |
38 | public AmqpClientImpl(Vertx vertx, AmqpClientOptions options, boolean mustCloseVertxOnClose) {
39 | this.vertx = vertx;
40 | if (options == null) {
41 | this.options = new AmqpClientOptions();
42 | } else {
43 | this.options = options;
44 | }
45 | this.proton = ProtonClient.create(vertx);
46 | this.mustCloseVertxOnClose = mustCloseVertxOnClose;
47 | }
48 |
49 | public AmqpClient connect(Handler> connectionHandler) {
50 | Objects.requireNonNull(connectionHandler, "Handler must not be null");
51 | connect().onComplete(connectionHandler);
52 | return this;
53 | }
54 |
55 | @Override
56 | public Future connect() {
57 | Objects.requireNonNull(options.getHost(), "Host must be set");
58 | ContextInternal ctx = (ContextInternal) vertx.getOrCreateContext();
59 | PromiseInternal promise = ctx.promise();
60 | new AmqpConnectionImpl(ctx, options, proton, promise);
61 | Future future = promise.future();
62 | future.onSuccess(conn -> {
63 | connections.add(conn);
64 | conn.closeFuture().onComplete(ar -> {
65 | connections.remove(conn);
66 | });
67 | });
68 | return future;
69 | }
70 |
71 | public void close(Completable handler) {
72 | List> actions = new ArrayList<>();
73 | for (AmqpConnection connection : connections) {
74 | actions.add(connection.close());
75 | }
76 |
77 | Future.join(actions).onComplete(done -> {
78 | connections.clear();
79 | if (mustCloseVertxOnClose) {
80 | vertx
81 | .close()
82 | .onComplete(x -> {
83 | if (done.succeeded() && x.succeeded()) {
84 | if (handler != null) {
85 | handler.succeed();
86 | }
87 | } else {
88 | if (handler != null) {
89 | handler.fail(done.failed() ? done.cause() : x.cause());
90 | }
91 | }
92 | });
93 | } else if (handler != null) {
94 | handler.complete(null, done.cause());
95 | }
96 | });
97 | }
98 |
99 | @Override
100 | public Future close() {
101 | Promise promise = Promise.promise();
102 | close(promise);
103 | return promise.future();
104 | }
105 |
106 | public AmqpClient createReceiver(String address,
107 | Completable completionHandler) {
108 | return connect(res -> {
109 | if (res.failed()) {
110 | completionHandler.complete(null, res.cause());
111 | } else {
112 | res.result().createReceiver(address).onComplete(completionHandler);
113 | }
114 | });
115 | }
116 |
117 | @Override
118 | public Future createReceiver(String address) {
119 | Promise promise = Promise.promise();
120 | createReceiver(address, promise);
121 | return promise.future();
122 | }
123 |
124 | public AmqpClient createReceiver(String address, AmqpReceiverOptions receiverOptions, Completable completionHandler) {
125 | return connect(res -> {
126 | if (res.failed()) {
127 | completionHandler.complete(null, res.cause());
128 | } else {
129 | res.result().createReceiver(address, receiverOptions).onComplete(completionHandler);
130 | }
131 | });
132 | }
133 |
134 | @Override
135 | public Future createReceiver(String address, AmqpReceiverOptions receiverOptions) {
136 | Promise promise = Promise.promise();
137 | createReceiver(address, receiverOptions, promise);
138 | return promise.future();
139 | }
140 |
141 | public AmqpClient createSender(String address, Completable completionHandler) {
142 | return connect(res -> {
143 | if (res.failed()) {
144 | completionHandler.complete(null, res.cause());
145 | } else {
146 | res.result().createSender(address).onComplete(completionHandler);
147 | }
148 | });
149 | }
150 |
151 | @Override
152 | public Future createSender(String address) {
153 | Promise promise = Promise.promise();
154 | createSender(address, promise);
155 | return promise.future();
156 | }
157 |
158 | public AmqpClient createSender(String address, AmqpSenderOptions options,
159 | Completable completionHandler) {
160 | return connect(res -> {
161 | if (res.failed()) {
162 | completionHandler.complete(null, res.cause());
163 | } else {
164 | res.result().createSender(address, options).onComplete(completionHandler);
165 | }
166 | });
167 | }
168 |
169 | @Override
170 | public Future createSender(String address, AmqpSenderOptions options) {
171 | Promise promise = Promise.promise();
172 | createSender(address, options, promise);
173 | return promise.future();
174 | }
175 |
176 | public int numConnections() {
177 | return connections.size();
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/src/main/java/io/vertx/amqp/impl/AmqpMessageBuilderImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018-2019 The original author or authors
3 | *
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * and Apache License v2.0 which accompanies this distribution.
7 | *
8 | * The Eclipse Public License is available at
9 | * http://www.eclipse.org/legal/epl-v10.html
10 | *
11 | * The Apache License v2.0 is available at
12 | * http://www.opensource.org/licenses/apache2.0.php
13 | *
14 | * You may elect to redistribute this code under either of these licenses.
15 | */
16 | package io.vertx.amqp.impl;
17 |
18 | import io.vertx.amqp.AmqpMessage;
19 | import io.vertx.amqp.AmqpMessageBuilder;
20 | import io.vertx.codegen.annotations.GenIgnore;
21 | import io.vertx.core.buffer.Buffer;
22 | import io.vertx.core.json.JsonArray;
23 | import io.vertx.core.json.JsonObject;
24 | import org.apache.qpid.proton.amqp.Binary;
25 | import org.apache.qpid.proton.amqp.Symbol;
26 | import org.apache.qpid.proton.amqp.messaging.AmqpValue;
27 | import org.apache.qpid.proton.amqp.messaging.ApplicationProperties;
28 | import org.apache.qpid.proton.amqp.messaging.Data;
29 | import org.apache.qpid.proton.message.Message;
30 |
31 | import java.sql.Date;
32 | import java.time.Instant;
33 | import java.util.List;
34 | import java.util.Map;
35 | import java.util.UUID;
36 |
37 | public class AmqpMessageBuilderImpl implements AmqpMessageBuilder {
38 |
39 | private Message message;
40 |
41 | public AmqpMessageBuilderImpl() {
42 | message = Message.Factory.create();
43 | }
44 |
45 | public AmqpMessageBuilderImpl(AmqpMessage existing) {
46 | message = existing.unwrap();
47 | }
48 |
49 | public AmqpMessageBuilderImpl(Message existing) {
50 | message = existing;
51 | }
52 |
53 | @Override
54 | public AmqpMessage build() {
55 | return new AmqpMessageImpl(message);
56 | }
57 |
58 | @Override
59 | public AmqpMessageBuilderImpl priority(short priority) {
60 | message.setPriority(priority);
61 | return this;
62 | }
63 |
64 | @Override public AmqpMessageBuilder durable(boolean durable) {
65 | message.setDurable(durable);
66 | return this;
67 | }
68 |
69 | @Override
70 | public AmqpMessageBuilderImpl id(String id) {
71 | message.setMessageId(id);
72 | return this;
73 | }
74 |
75 | @Override
76 | public AmqpMessageBuilderImpl address(String address) {
77 | message.setAddress(address);
78 | return this;
79 | }
80 |
81 | @Override
82 | public AmqpMessageBuilderImpl replyTo(String replyTo) {
83 | message.setReplyTo(replyTo);
84 | return this;
85 | }
86 |
87 | @Override
88 | public AmqpMessageBuilderImpl correlationId(String correlationId) {
89 | message.setCorrelationId(correlationId);
90 | return this;
91 | }
92 |
93 | @Override
94 | public AmqpMessageBuilderImpl withBody(String value) {
95 | message.setBody(new AmqpValue(value));
96 | return this;
97 | }
98 |
99 | @Override
100 | public AmqpMessageBuilderImpl withSymbolAsBody(String value) {
101 | message.setBody(new AmqpValue(Symbol.valueOf(value)));
102 | return this;
103 | }
104 |
105 | @Override
106 | public AmqpMessageBuilderImpl subject(String subject) {
107 | message.setSubject(subject);
108 | return this;
109 | }
110 |
111 | @Override
112 | public AmqpMessageBuilderImpl contentType(String ct) {
113 | message.setContentType(ct);
114 | return this;
115 | }
116 |
117 | @Override
118 | public AmqpMessageBuilderImpl contentEncoding(String ct) {
119 | message.setContentEncoding(ct);
120 | return this;
121 | }
122 |
123 | @Override
124 | public AmqpMessageBuilderImpl expiryTime(long expiry) {
125 | message.setExpiryTime(expiry);
126 | return this;
127 | }
128 |
129 | @Override
130 | public AmqpMessageBuilderImpl creationTime(long ct) {
131 | message.setCreationTime(ct);
132 | return this;
133 | }
134 |
135 | @Override
136 | public AmqpMessageBuilderImpl ttl(long ttl) {
137 | message.setTtl(ttl);
138 | return this;
139 | }
140 |
141 | @Override public AmqpMessageBuilder firstAcquirer(boolean first) {
142 | message.setFirstAcquirer(first);
143 | return this;
144 | }
145 |
146 | @Override public AmqpMessageBuilder deliveryCount(int count) {
147 | message.setDeliveryCount(count);
148 | return this;
149 | }
150 |
151 | @Override
152 | public AmqpMessageBuilderImpl groupId(String gi) {
153 | message.setGroupId(gi);
154 | return this;
155 | }
156 |
157 | @Override
158 | public AmqpMessageBuilderImpl replyToGroupId(String rt) {
159 | message.setReplyToGroupId(rt);
160 | return this;
161 | }
162 |
163 | @Override
164 | public AmqpMessageBuilderImpl applicationProperties(JsonObject props) {
165 | ApplicationProperties properties = new ApplicationProperties(props.getMap());
166 | message.setApplicationProperties(properties);
167 | return this;
168 | }
169 |
170 | @Override
171 | public AmqpMessageBuilderImpl withBooleanAsBody(boolean v) {
172 | message.setBody(new AmqpValue(v));
173 | return this;
174 | }
175 |
176 | @Override
177 | public AmqpMessageBuilderImpl withByteAsBody(byte v) {
178 | message.setBody(new AmqpValue(v));
179 | return this;
180 | }
181 |
182 | @Override
183 | public AmqpMessageBuilderImpl withShortAsBody(short v) {
184 | message.setBody(new AmqpValue(v));
185 | return this;
186 | }
187 |
188 | @Override
189 | public AmqpMessageBuilderImpl withIntegerAsBody(int v) {
190 | message.setBody(new AmqpValue(v));
191 | return this;
192 | }
193 |
194 | @Override
195 | public AmqpMessageBuilderImpl withLongAsBody(long v) {
196 | message.setBody(new AmqpValue(v));
197 | return this;
198 | }
199 |
200 | @Override
201 | public AmqpMessageBuilderImpl withFloatAsBody(float v) {
202 | message.setBody(new AmqpValue(v));
203 | return this;
204 | }
205 |
206 | @Override
207 | public AmqpMessageBuilderImpl withDoubleAsBody(double v) {
208 | message.setBody(new AmqpValue(v));
209 | return this;
210 | }
211 |
212 | @Override
213 | public AmqpMessageBuilderImpl withCharAsBody(char c) {
214 | message.setBody(new AmqpValue(c));
215 | return this;
216 | }
217 |
218 | @Override
219 | @GenIgnore(GenIgnore.PERMITTED_TYPE)
220 | public AmqpMessageBuilderImpl withInstantAsBody(Instant v) {
221 | message.setBody(new AmqpValue(Date.from(v)));
222 | return this;
223 | }
224 |
225 | @Override
226 | @GenIgnore(GenIgnore.PERMITTED_TYPE)
227 | public AmqpMessageBuilderImpl withUuidAsBody(UUID v) {
228 | message.setBody(new AmqpValue(v));
229 | return this;
230 | }
231 |
232 | @Override
233 | public AmqpMessageBuilderImpl withListAsBody(List list) {
234 | message.setBody(new AmqpValue(list));
235 | return this;
236 | }
237 |
238 | @Override
239 | @GenIgnore(GenIgnore.PERMITTED_TYPE)
240 | public AmqpMessageBuilderImpl withMapAsBody(Map map) {
241 | message.setBody(new AmqpValue(map));
242 | return this;
243 | }
244 |
245 | @Override
246 | public AmqpMessageBuilderImpl withBufferAsBody(Buffer buffer) {
247 | message.setBody(new Data(new Binary(buffer.getBytes())));
248 | return this;
249 | }
250 |
251 | @Override
252 | public AmqpMessageBuilderImpl withJsonObjectAsBody(JsonObject json) {
253 | return contentType("application/json")
254 | .withBufferAsBody(json.toBuffer());
255 | }
256 |
257 | @Override
258 | public AmqpMessageBuilderImpl withJsonArrayAsBody(JsonArray json) {
259 | return contentType("application/json")
260 | .withBufferAsBody(json.toBuffer());
261 | }
262 | }
263 |
--------------------------------------------------------------------------------
/src/main/java/io/vertx/amqp/impl/AmqpMessageImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018-2019 The original author or authors
3 | *
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * and Apache License v2.0 which accompanies this distribution.
7 | *
8 | * The Eclipse Public License is available at
9 | * http://www.eclipse.org/legal/epl-v10.html
10 | *
11 | * The Apache License v2.0 is available at
12 | * http://www.opensource.org/licenses/apache2.0.php
13 | *
14 | * You may elect to redistribute this code under either of these licenses.
15 | */
16 | package io.vertx.amqp.impl;
17 |
18 | import io.vertx.amqp.AmqpMessage;
19 | import io.vertx.core.buffer.Buffer;
20 | import io.vertx.core.json.JsonArray;
21 | import io.vertx.core.json.JsonObject;
22 | import io.vertx.proton.ProtonDelivery;
23 | import io.vertx.proton.ProtonHelper;
24 | import org.apache.qpid.proton.amqp.Symbol;
25 | import org.apache.qpid.proton.amqp.messaging.*;
26 | import org.apache.qpid.proton.message.Message;
27 |
28 | import java.time.Instant;
29 | import java.util.Date;
30 | import java.util.List;
31 | import java.util.Map;
32 | import java.util.UUID;
33 |
34 | public class AmqpMessageImpl implements AmqpMessage {
35 | private final Message message;
36 | private final ProtonDelivery delivery;
37 | private AmqpConnectionImpl connection;
38 |
39 | public AmqpMessageImpl(Message message, ProtonDelivery delivery, AmqpConnectionImpl connection) {
40 | this.message = message;
41 | this.delivery = delivery;
42 | this.connection = connection;
43 | }
44 |
45 | public AmqpMessageImpl(Message message) {
46 | this.message = message;
47 | this.delivery = null;
48 | }
49 |
50 | @Override
51 | public boolean isDurable() {
52 | return message.isDurable();
53 | }
54 |
55 | @Override
56 | public boolean isFirstAcquirer() {
57 | return message.isFirstAcquirer();
58 | }
59 |
60 | @Override
61 | public int priority() {
62 | return message.getPriority();
63 | }
64 |
65 | @Override
66 | public String id() {
67 | Object id = message.getMessageId();
68 | if (id != null) {
69 | return id.toString();
70 | }
71 | return null;
72 | }
73 |
74 | @Override
75 | public String address() {
76 | return message.getAddress();
77 | }
78 |
79 | @Override
80 | public String replyTo() {
81 | return message.getReplyTo();
82 | }
83 |
84 | @Override
85 | public String correlationId() {
86 | Object id = message.getCorrelationId();
87 | if (id != null) {
88 | return id.toString();
89 | }
90 | return null;
91 | }
92 |
93 | private boolean isBodyAmqpValue() {
94 | final Section body = message.getBody();
95 | return body != null && body.getType() == Section.SectionType.AmqpValue;
96 | }
97 |
98 | @Override
99 | public boolean isBodyNull() {
100 | final Section body = message.getBody();
101 | return body == null || isBodyAmqpValue() && ((AmqpValue) body).getValue() == null;
102 | }
103 |
104 | private Object getAmqpValue() {
105 | if (!isBodyAmqpValue()) {
106 | throw new IllegalStateException("The body is not an AMQP Value");
107 | }
108 | return ((AmqpValue) message.getBody()).getValue();
109 | }
110 |
111 | @Override
112 | public boolean bodyAsBoolean() {
113 | return (boolean) getAmqpValue();
114 | }
115 |
116 | @Override
117 | public byte bodyAsByte() {
118 | return (byte) getAmqpValue();
119 | }
120 |
121 | @Override
122 | public short bodyAsShort() {
123 | return (short) getAmqpValue();
124 | }
125 |
126 | @Override
127 | public int bodyAsInteger() {
128 | return (int) getAmqpValue();
129 | }
130 |
131 | @Override
132 | public long bodyAsLong() {
133 | return (long) getAmqpValue();
134 | }
135 |
136 | @Override
137 | public float bodyAsFloat() {
138 | return (float) getAmqpValue();
139 | }
140 |
141 | @Override
142 | public double bodyAsDouble() {
143 | return (double) getAmqpValue();
144 | }
145 |
146 | @Override
147 | public char bodyAsChar() {
148 | return (char) getAmqpValue();
149 | }
150 |
151 | @Override
152 | public Instant bodyAsTimestamp() {
153 | Object value = getAmqpValue();
154 | if (!(value instanceof Date)) {
155 | throw new IllegalStateException("Expecting a Date object, got a " + value);
156 | }
157 | return ((Date) value).toInstant();
158 | }
159 |
160 | @Override
161 | public UUID bodyAsUUID() {
162 | return (UUID) getAmqpValue();
163 | }
164 |
165 | @Override
166 | public Buffer bodyAsBinary() {
167 | Section body = message.getBody();
168 | if (body.getType() != Section.SectionType.Data) {
169 | throw new IllegalStateException("The body is not of type 'data'");
170 | }
171 | byte[] bytes = ((Data) message.getBody()).getValue().getArray();
172 | return Buffer.buffer(bytes);
173 | }
174 |
175 | @Override
176 | public String bodyAsString() {
177 | return (String) getAmqpValue();
178 | }
179 |
180 | @Override
181 | public String bodyAsSymbol() {
182 | Object value = getAmqpValue();
183 | if (value instanceof Symbol) {
184 | return ((Symbol) value).toString();
185 | }
186 | throw new IllegalStateException("Expected a Symbol, got a " + value.getClass());
187 | }
188 |
189 | @Override
190 | @SuppressWarnings("unchecked")
191 | public List bodyAsList() {
192 | Section body = message.getBody();
193 | if (body.getType() == Section.SectionType.AmqpSequence) {
194 | return (List) ((AmqpSequence) message.getBody()).getValue();
195 | } else {
196 | Object value = getAmqpValue();
197 | if (value instanceof List) {
198 | return (List) value;
199 | }
200 | throw new IllegalStateException("Cannot extract a list from the message body");
201 | }
202 | }
203 |
204 | @Override
205 | @SuppressWarnings("unchecked")
206 | public Map bodyAsMap() {
207 | Object value = getAmqpValue();
208 | if (value instanceof Map) {
209 | return (Map) value;
210 | }
211 | throw new IllegalStateException("Cannot extract a map from the message body");
212 | }
213 |
214 | @Override
215 | public JsonObject bodyAsJsonObject() {
216 | return bodyAsBinary().toJsonObject();
217 | }
218 |
219 | @Override
220 | public JsonArray bodyAsJsonArray() {
221 | return bodyAsBinary().toJsonArray();
222 | }
223 |
224 | @Override
225 | public String subject() {
226 | return message.getSubject();
227 | }
228 |
229 | @Override
230 | public String contentType() {
231 | return message.getContentType();
232 | }
233 |
234 | @Override
235 | public String contentEncoding() {
236 | return message.getContentEncoding();
237 | }
238 |
239 | @Override
240 | public long expiryTime() {
241 | return message.getExpiryTime();
242 | }
243 |
244 | @Override
245 | public long creationTime() {
246 | return message.getCreationTime();
247 | }
248 |
249 | @Override
250 | public long ttl() {
251 | return message.getTtl();
252 | }
253 |
254 | @Override
255 | public int deliveryCount() {
256 | return (int) message.getDeliveryCount();
257 | }
258 |
259 | @Override
260 | public String groupId() {
261 | return message.getGroupId();
262 | }
263 |
264 | @Override
265 | public String replyToGroupId() {
266 | return message.getReplyToGroupId();
267 | }
268 |
269 | @Override
270 | public long groupSequence() {
271 | return message.getGroupSequence();
272 | }
273 |
274 | @Override
275 | public JsonObject applicationProperties() {
276 | ApplicationProperties properties = message.getApplicationProperties();
277 | if (properties == null) {
278 | return null;
279 | }
280 | return new JsonObject(properties.getValue());
281 | }
282 |
283 | @Override
284 | public Message unwrap() {
285 | return message;
286 | }
287 |
288 | @Override
289 | public AmqpMessage accepted() {
290 | if (delivery != null) {
291 | connection.runWithTrampoline(v -> ProtonHelper.accepted(delivery, true));
292 | } else {
293 | throw new IllegalStateException("The message is not a received message");
294 | }
295 | return this;
296 | }
297 |
298 | @Override
299 | public AmqpMessage rejected() {
300 | if (delivery != null) {
301 | connection.runWithTrampoline(v -> ProtonHelper.rejected(delivery, true));
302 | } else {
303 | throw new IllegalStateException("The message is not a received message");
304 | }
305 | return this;
306 | }
307 |
308 | @Override
309 | public AmqpMessage released() {
310 | if (delivery != null) {
311 | connection.runWithTrampoline(v -> ProtonHelper.released(delivery, true));
312 | } else {
313 | throw new IllegalStateException("The message is not a received message");
314 | }
315 | return this;
316 | }
317 |
318 | @Override
319 | public AmqpMessage modified(boolean deliveryFailed, boolean undeliverableHere) {
320 | if (delivery != null) {
321 | connection.runWithTrampoline(v -> ProtonHelper.modified(delivery, true, deliveryFailed, undeliverableHere));
322 | } else {
323 | throw new IllegalStateException("The message is not a received message");
324 | }
325 | return this;
326 | }
327 | }
328 |
--------------------------------------------------------------------------------
/src/main/java/io/vertx/amqp/impl/AmqpReceiverImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018-2019 The original author or authors
3 | *
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * and Apache License v2.0 which accompanies this distribution.
7 | *
8 | * The Eclipse Public License is available at
9 | * http://www.eclipse.org/legal/epl-v10.html
10 | *
11 | * The Apache License v2.0 is available at
12 | * http://www.opensource.org/licenses/apache2.0.php
13 | *
14 | * You may elect to redistribute this code under either of these licenses.
15 | */
16 | package io.vertx.amqp.impl;
17 |
18 | import io.vertx.amqp.AmqpConnection;
19 | import io.vertx.amqp.AmqpMessage;
20 | import io.vertx.amqp.AmqpReceiver;
21 | import io.vertx.amqp.AmqpReceiverOptions;
22 | import io.vertx.codegen.annotations.Nullable;
23 | import io.vertx.core.*;
24 | import io.vertx.core.internal.logging.Logger;
25 | import io.vertx.core.internal.logging.LoggerFactory;
26 | import io.vertx.proton.ProtonReceiver;
27 |
28 | import java.util.ArrayDeque;
29 | import java.util.Queue;
30 |
31 | public class AmqpReceiverImpl implements AmqpReceiver {
32 |
33 | private static final Logger LOGGER = LoggerFactory.getLogger(AmqpReceiverImpl.class);
34 | private final ProtonReceiver receiver;
35 | private final AmqpConnectionImpl connection;
36 | private final Queue buffered = new ArrayDeque<>();
37 | private final boolean durable;
38 | private final boolean autoAck;
39 | /**
40 | * The address.
41 | * Not final because for dynamic link the address is set when the createReceiver is opened.
42 | */
43 | private String address;
44 | private Handler handler;
45 | private long demand = Long.MAX_VALUE;
46 | private boolean closed;
47 | private Handler endHandler;
48 | private Handler exceptionHandler;
49 | private boolean initialCreditGiven;
50 | private int initialCredit = 1000;
51 |
52 | /**
53 | * Creates a new instance of {@link AmqpReceiverImpl}.
54 | * This method must be called on the connection context.
55 | *
56 | * @param address the address, may be {@code null} for dynamic links
57 | * @param connection the connection
58 | * @param options the receiver options, must not be {@code null}
59 | * @param receiver the underlying proton createReceiver
60 | * @param completionHandler called when the createReceiver is opened
61 | */
62 | AmqpReceiverImpl(
63 | String address,
64 | AmqpConnectionImpl connection,
65 | AmqpReceiverOptions options,
66 | ProtonReceiver receiver,
67 | Completable completionHandler) {
68 | this.address = address;
69 | this.receiver = receiver;
70 | this.connection = connection;
71 | this.durable = options.isDurable();
72 | this.autoAck = options.isAutoAcknowledgement();
73 | int maxBufferedMessages = options.getMaxBufferedMessages();
74 | if (maxBufferedMessages > 0) {
75 | this.initialCredit = maxBufferedMessages;
76 | }
77 |
78 | // Disable auto-accept and automated prefetch, we manage disposition and credit
79 | // manually to allow for delayed handler registration and pause/resume functionality.
80 | this.receiver
81 | .setAutoAccept(false)
82 | .setPrefetch(0);
83 |
84 | this.receiver.handler((delivery, message) -> handleMessage(new AmqpMessageImpl(message, delivery, connection)));
85 |
86 | this.receiver.closeHandler(res -> {
87 | onClose(address, receiver, res, false);
88 | })
89 | .detachHandler(res -> {
90 | onClose(address, receiver, res, true);
91 | });
92 |
93 | this.receiver
94 | .openHandler(res -> {
95 | if (res.failed()) {
96 | completionHandler.complete(null, res.cause());
97 | } else {
98 | this.connection.register(this);
99 | synchronized (this) {
100 | if (this.address == null) {
101 | this.address = res.result().getRemoteAddress();
102 | }
103 | }
104 | completionHandler.succeed(this);
105 | }
106 | });
107 |
108 | this.receiver.open();
109 | }
110 |
111 | private void onClose(String address, ProtonReceiver receiver, AsyncResult res, boolean detach) {
112 | Handler endh = null;
113 | Handler exh = null;
114 | boolean closeReceiver = false;
115 |
116 | synchronized (AmqpReceiverImpl.this) {
117 | if (!closed && endHandler != null) {
118 | endh = endHandler;
119 | } else if (!closed && exceptionHandler != null) {
120 | exh = exceptionHandler;
121 | }
122 |
123 | if (!closed) {
124 | closed = true;
125 | closeReceiver = true;
126 | }
127 | }
128 |
129 | if (endh != null) {
130 | endh.handle(null);
131 | } else if (exh != null) {
132 | if (res.succeeded()) {
133 | exh.handle(new VertxException("Consumer closed remotely"));
134 | } else {
135 | exh.handle(new VertxException("Consumer closed remotely with error", res.cause()));
136 | }
137 | } else {
138 | if (res.succeeded()) {
139 | LOGGER.warn("Consumer for address " + address + " unexpectedly closed remotely");
140 | } else {
141 | LOGGER.warn("Consumer for address " + address + " unexpectedly closed remotely with error", res.cause());
142 | }
143 | }
144 |
145 | if (closeReceiver) {
146 | if (detach) {
147 | receiver.detach();
148 | } else {
149 | receiver.close();
150 | }
151 | }
152 | }
153 |
154 | private void handleMessage(AmqpMessageImpl message) {
155 | boolean schedule = false;
156 |
157 | Handler h;
158 | synchronized (this) {
159 | h = handler;
160 | if(h == null || demand == 0L) {
161 | // Buffer message until we aren't paused
162 | buffered.add(message);
163 | return;
164 | }
165 |
166 | if(!buffered.isEmpty()) {
167 | // Buffered messages present, deliver the oldest of those instead
168 | buffered.add(message);
169 | message = buffered.poll();
170 | // Schedule a delivery for the next buffered message
171 | schedule = true;
172 | }
173 | if (demand != Long.MAX_VALUE) {
174 | demand--;
175 | }
176 | }
177 | deliverMessageToHandler(h, message);
178 |
179 | // schedule next delivery if appropriate, after earlier delivery to allow chance to pause etc.
180 | if(schedule) {
181 | scheduleBufferedMessageDelivery();
182 | }
183 | }
184 |
185 | @Override
186 | public synchronized AmqpReceiver exceptionHandler(Handler handler) {
187 | exceptionHandler = handler;
188 | return this;
189 | }
190 |
191 | @Override
192 | public AmqpReceiver handler(@Nullable Handler handler) {
193 | int creditToFlow = 0;
194 | boolean schedule = false;
195 |
196 | synchronized (this) {
197 | this.handler = handler;
198 | if (handler != null) {
199 | schedule = true;
200 |
201 | // Flow initial credit if needed
202 | if (!initialCreditGiven) {
203 | initialCreditGiven = true;
204 | creditToFlow = initialCredit;
205 | }
206 | }
207 | }
208 |
209 | if (creditToFlow > 0) {
210 | final int c = creditToFlow;
211 | connection.runWithTrampoline(v -> receiver.flow(c));
212 | }
213 |
214 | if (schedule) {
215 | scheduleBufferedMessageDelivery();
216 | }
217 |
218 | return this;
219 | }
220 |
221 | @Override
222 | public synchronized AmqpReceiverImpl pause() {
223 | demand = 0L;
224 | return this;
225 | }
226 |
227 | @Override
228 | public synchronized AmqpReceiverImpl fetch(long amount) {
229 | if (amount > 0) {
230 | demand += amount;
231 | if (demand < 0L) {
232 | demand = Long.MAX_VALUE;
233 | }
234 | scheduleBufferedMessageDelivery();
235 | }
236 | return this;
237 | }
238 |
239 | @Override
240 | public synchronized AmqpReceiverImpl resume() {
241 | return fetch(Long.MAX_VALUE);
242 | }
243 |
244 | @Override
245 | public synchronized AmqpReceiverImpl endHandler(Handler endHandler) {
246 | this.endHandler = endHandler;
247 | return this;
248 | }
249 |
250 | private void deliverMessageToHandler(Handler h, AmqpMessageImpl message) {
251 | try {
252 | h.handle(message);
253 | if (autoAck) {
254 | message.accepted();
255 | }
256 | } catch (Exception e) {
257 | LOGGER.error("Unable to dispatch the AMQP message", e);
258 | if (autoAck) {
259 | message.rejected();
260 | }
261 | }
262 |
263 | this.receiver.flow(1);
264 | }
265 |
266 | private void scheduleBufferedMessageDelivery() {
267 | boolean schedule;
268 |
269 | synchronized (this) {
270 | schedule = !buffered.isEmpty() && demand > 0L;
271 | }
272 |
273 | if (schedule) {
274 | connection.runOnContext(v -> {
275 | Handler h;
276 | AmqpMessageImpl message = null;
277 |
278 | synchronized (this) {
279 | h = handler;
280 | if (h != null && demand > 0L) {
281 | message = buffered.poll();
282 | if (demand != Long.MAX_VALUE && message != null) {
283 | demand--;
284 | }
285 | }
286 | }
287 |
288 | if (message != null) {
289 | // Delivering outside the synchronized block
290 | deliverMessageToHandler(h, message);
291 |
292 | // Schedule a delivery for a further buffered message if any
293 | scheduleBufferedMessageDelivery();
294 | }
295 | });
296 | }
297 | }
298 |
299 | @Override
300 | public synchronized String address() {
301 | return address;
302 | }
303 |
304 | @Override
305 | public AmqpConnection connection() {
306 | return connection;
307 | }
308 |
309 | public void close(Completable handler) {
310 | Completable actualHandler;
311 | if (handler == null) {
312 | actualHandler = (res, err) -> { /* NOOP */ };
313 | } else {
314 | actualHandler = handler;
315 | }
316 |
317 | synchronized (this) {
318 | if (closed) {
319 | actualHandler.succeed();
320 | return;
321 | }
322 | closed = true;
323 | }
324 |
325 | connection.unregister(this);
326 | connection.runWithTrampoline(x -> {
327 | if (this.receiver.isOpen()) {
328 | try {
329 | if (isDurable()) {
330 | receiver.detachHandler(done -> actualHandler.complete(null, done.cause()))
331 | .detach();
332 | } else {
333 | receiver
334 | .closeHandler(done -> actualHandler.complete(null, done.cause()))
335 | .close();
336 | }
337 | } catch (Exception e) {
338 | // Somehow closed remotely
339 | actualHandler.fail(e);
340 | }
341 | } else {
342 | actualHandler.succeed();
343 | }
344 | });
345 |
346 | }
347 |
348 | @Override
349 | public Future close() {
350 | Promise promise = Promise.promise();
351 | close(promise);
352 | return promise.future();
353 | }
354 |
355 | @Override
356 | public ProtonReceiver unwrap() {
357 | return receiver;
358 | }
359 |
360 | private synchronized boolean isDurable() {
361 | return durable;
362 | }
363 | }
364 |
--------------------------------------------------------------------------------
/src/main/java/io/vertx/amqp/impl/AmqpSenderImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018-2019 The original author or authors
3 | *
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * and Apache License v2.0 which accompanies this distribution.
7 | *
8 | * The Eclipse Public License is available at
9 | * http://www.eclipse.org/legal/epl-v10.html
10 | *
11 | * The Apache License v2.0 is available at
12 | * http://www.opensource.org/licenses/apache2.0.php
13 | *
14 | * You may elect to redistribute this code under either of these licenses.
15 | */
16 | package io.vertx.amqp.impl;
17 |
18 | import io.vertx.amqp.AmqpConnection;
19 | import io.vertx.amqp.AmqpMessage;
20 | import io.vertx.amqp.AmqpSender;
21 | import io.vertx.core.*;
22 | import io.vertx.core.internal.logging.Logger;
23 | import io.vertx.core.internal.logging.LoggerFactory;
24 | import io.vertx.proton.ProtonDelivery;
25 | import io.vertx.proton.ProtonSender;
26 | import org.apache.qpid.proton.amqp.messaging.Rejected;
27 | import org.apache.qpid.proton.amqp.transport.DeliveryState;
28 |
29 | public class AmqpSenderImpl implements AmqpSender {
30 | private static final Logger LOGGER = LoggerFactory.getLogger(AmqpSender.class);
31 | private final ProtonSender sender;
32 | private final AmqpConnectionImpl connection;
33 | private boolean closed;
34 | private Handler exceptionHandler;
35 | private Handler drainHandler;
36 | private long remoteCredit = 0;
37 |
38 | private AmqpSenderImpl(ProtonSender sender, AmqpConnectionImpl connection,
39 | Completable completionHandler) {
40 | this.sender = sender;
41 | this.connection = connection;
42 |
43 | sender
44 | .closeHandler(res -> onClose(sender, res, false))
45 | .detachHandler(res -> onClose(sender, res, true));
46 |
47 | sender.sendQueueDrainHandler(s -> {
48 | Handler dh = null;
49 | synchronized (AmqpSenderImpl.this) {
50 | // Update current state of remote credit
51 | remoteCredit = sender.getRemoteCredit();
52 |
53 | // check the user drain handler, fire it outside synchronized block if not null
54 | if (drainHandler != null) {
55 | dh = drainHandler;
56 | }
57 | }
58 |
59 | if (dh != null) {
60 | dh.handle(null);
61 | }
62 | });
63 |
64 | sender.openHandler(done -> {
65 | if (done.failed()) {
66 | completionHandler.complete(null, done.cause());
67 | } else {
68 | connection.register(this);
69 | completionHandler.succeed(this);
70 | }
71 | });
72 |
73 | sender.open();
74 | }
75 |
76 | /**
77 | * Creates a new instance of {@link AmqpSenderImpl}. The created sender is passed into the {@code completionHandler}
78 | * once opened. This method must be called on the connection context.
79 | *
80 | * @param sender the underlying proton sender
81 | * @param connection the connection
82 | * @param completionHandler the completion handler
83 | */
84 | static void create(ProtonSender sender, AmqpConnectionImpl connection,
85 | Completable completionHandler) {
86 | new AmqpSenderImpl(sender, connection, completionHandler);
87 | }
88 |
89 | private void onClose(ProtonSender sender, AsyncResult res, boolean detach) {
90 | Handler eh = null;
91 | boolean closeSender = false;
92 |
93 | synchronized (AmqpSenderImpl.this) {
94 | if (!closed && exceptionHandler != null) {
95 | eh = exceptionHandler;
96 | }
97 |
98 | if (!closed) {
99 | closed = true;
100 | closeSender = true;
101 | }
102 | }
103 |
104 | if (eh != null) {
105 | if (res.succeeded()) {
106 | eh.handle(new Exception("Sender closed remotely"));
107 | } else {
108 | eh.handle(new Exception("Sender closed remotely with error", res.cause()));
109 | }
110 | }
111 |
112 | if (closeSender) {
113 | if (detach) {
114 | sender.detach();
115 | } else {
116 | sender.close();
117 | }
118 | }
119 | }
120 |
121 | @Override
122 | public synchronized boolean writeQueueFull() {
123 | return remoteCredit <= 0;
124 | }
125 |
126 | @Override
127 | public AmqpConnection connection() {
128 | return connection;
129 | }
130 |
131 | @Override
132 | public AmqpSender send(AmqpMessage message) {
133 | return doSend(message, null);
134 | }
135 |
136 | private AmqpSender doSend(AmqpMessage message, Completable acknowledgmentHandler) {
137 | Handler ack = delivery -> {
138 | DeliveryState remoteState = delivery.getRemoteState();
139 |
140 | Completable handler = acknowledgmentHandler;
141 | if (acknowledgmentHandler == null) {
142 | handler = (res, err) -> {
143 | if (err != null) {
144 | LOGGER.warn("Message rejected by remote peer", err);
145 | }
146 | };
147 | }
148 |
149 | if (remoteState == null) {
150 | handler.fail("Unknown message state");
151 | return;
152 | }
153 |
154 | switch (remoteState.getType()) {
155 | case Rejected:
156 | handler.fail("message rejected (REJECTED): " + ((Rejected) remoteState).getError());
157 | break;
158 | case Modified:
159 | handler.fail("message rejected (MODIFIED)");
160 | break;
161 | case Released:
162 | handler.fail("message rejected (RELEASED)");
163 | break;
164 | case Accepted:
165 | handler.succeed();
166 | break;
167 | default:
168 | handler.fail("Unsupported delivery type: " + remoteState.getType());
169 | }
170 | };
171 |
172 | synchronized (AmqpSenderImpl.this) {
173 | // Update the credit tracking. We only need to adjust this here because the sends etc may not be on the context
174 | // thread and if that is the case we can't use the ProtonSender sendQueueFull method to check that credit has been
175 | // exhausted following this doSend call since we will have only scheduled the actual send for later.
176 | remoteCredit--;
177 | }
178 |
179 | connection.runWithTrampoline(x -> {
180 | AmqpMessage updated;
181 | if (message.address() == null) {
182 | updated = AmqpMessage.create(message).address(address()).build();
183 | } else {
184 | updated = message;
185 | }
186 |
187 | sender.send(updated.unwrap(), ack);
188 |
189 | synchronized (AmqpSenderImpl.this) {
190 | // Update the credit tracking *again*. We need to reinitialise it here in case the doSend call was performed on
191 | // a thread other than the client context, to ensure we didn't fall foul of a race between the above pre-send
192 | // update on that thread, the above send on the context thread, and the sendQueueDrainHandler based updates on
193 | // the context thread.
194 | remoteCredit = sender.getRemoteCredit();
195 | }
196 | });
197 | return this;
198 | }
199 |
200 | @Override
201 | public synchronized AmqpSender exceptionHandler(Handler handler) {
202 | exceptionHandler = handler;
203 | return this;
204 | }
205 |
206 | @Override
207 | public Future write(AmqpMessage data) {
208 | Promise promise = Promise.promise();
209 | doSend(data, promise);
210 | return promise.future();
211 | }
212 |
213 | @Override
214 | public AmqpSender setWriteQueueMaxSize(int maxSize) {
215 | // No-op, available sending credit is controlled by recipient peer in AMQP 1.0.
216 | return this;
217 | }
218 |
219 | @Override
220 | public Future end() {
221 | Promise promise = Promise.promise();
222 | close(promise);
223 | return promise.future();
224 | }
225 |
226 | @Override
227 | public synchronized AmqpSender drainHandler(Handler handler) {
228 | drainHandler = handler;
229 | return this;
230 | }
231 |
232 | public AmqpSender sendWithAck(AmqpMessage message, Promise acknowledgementHandler) {
233 | return doSend(message, acknowledgementHandler);
234 | }
235 |
236 | @Override
237 | public Future sendWithAck(AmqpMessage message) {
238 | Promise promise = Promise.promise();
239 | sendWithAck(message, promise);
240 | return promise.future();
241 | }
242 |
243 | public void close(Completable handler) {
244 | Completable actualHandler;
245 | if (handler == null) {
246 | actualHandler = (res, err) -> { /* NOOP */ };
247 | } else {
248 | actualHandler = handler;
249 | }
250 |
251 | synchronized (this) {
252 | if (closed) {
253 | actualHandler.succeed();
254 | return;
255 | }
256 | closed = true;
257 | }
258 |
259 | connection.unregister(this);
260 | connection.runWithTrampoline(x -> {
261 | if (sender.isOpen()) {
262 | try {
263 | sender
264 | .closeHandler(v -> actualHandler.complete(null, v.cause()))
265 | .close();
266 | } catch (Exception e) {
267 | // Somehow closed remotely
268 | actualHandler.fail(e);
269 | }
270 | } else {
271 | actualHandler.succeed();
272 | }
273 | });
274 | }
275 |
276 | @Override
277 | public Future close() {
278 | Promise promise = Promise.promise();
279 | close(promise);
280 | return promise.future();
281 | }
282 |
283 | @Override
284 | public String address() {
285 | return sender.getRemoteAddress();
286 | }
287 |
288 | @Override
289 | public long remainingCredits() {
290 | return sender.getRemoteCredit();
291 | }
292 |
293 | @Override
294 | public ProtonSender unwrap() {
295 | return sender;
296 | }
297 | }
298 |
--------------------------------------------------------------------------------
/src/main/java/io/vertx/amqp/package-info.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018-2019 The original author or authors
3 | *
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * and Apache License v2.0 which accompanies this distribution.
7 | *
8 | * The Eclipse Public License is available at
9 | * http://www.eclipse.org/legal/epl-v10.html
10 | *
11 | * The Apache License v2.0 is available at
12 | * http://www.opensource.org/licenses/apache2.0.php
13 | *
14 | * You may elect to redistribute this code under either of these licenses.
15 | */
16 | @ModuleGen(name = "vertx-amqp-client", groupPackage = "io.vertx")
17 | package io.vertx.amqp;
18 |
19 | import io.vertx.codegen.annotations.ModuleGen;
20 |
--------------------------------------------------------------------------------
/src/main/java/module-info.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018-2019 The original author or authors
3 | *
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * and Apache License v2.0 which accompanies this distribution.
7 | *
8 | * The Eclipse Public License is available at
9 | * http://www.eclipse.org/legal/epl-v10.html
10 | *
11 | * The Apache License v2.0 is available at
12 | * http://www.opensource.org/licenses/apache2.0.php
13 | *
14 | * You may elect to redistribute this code under either of these licenses.
15 | */
16 | module io.vertx.amqp.client {
17 |
18 | requires static io.vertx.codegen.api;
19 | requires static io.vertx.codegen.json;
20 | requires static io.vertx.docgen;
21 |
22 | requires io.vertx.core;
23 | requires io.vertx.core.logging;
24 | requires io.vertx.proton;
25 | requires java.sql;
26 | requires org.apache.qpid.proton.j;
27 | requires static org.reactivestreams;
28 |
29 | exports io.vertx.amqp;
30 | exports io.vertx.amqp.impl to io.vertx.amqp.client.tests;
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/test/java/io/vertx/amqp/tests/BareTestBase.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018-2019 The original author or authors
3 | *
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * and Apache License v2.0 which accompanies this distribution.
7 | *
8 | * The Eclipse Public License is available at
9 | * http://www.eclipse.org/legal/epl-v10.html
10 | *
11 | * The Apache License v2.0 is available at
12 | * http://www.opensource.org/licenses/apache2.0.php
13 | *
14 | * You may elect to redistribute this code under either of these licenses.
15 | */
16 | package io.vertx.amqp.tests;
17 |
18 | import io.vertx.amqp.AmqpClient;
19 | import io.vertx.core.Vertx;
20 | import io.vertx.ext.unit.junit.VertxUnitRunner;
21 | import org.junit.After;
22 | import org.junit.Before;
23 | import org.junit.Rule;
24 | import org.junit.Test;
25 | import org.junit.rules.TestName;
26 | import org.junit.runner.RunWith;
27 |
28 | import java.util.concurrent.CountDownLatch;
29 | import java.util.concurrent.TimeUnit;
30 |
31 | @RunWith(VertxUnitRunner.class)
32 | public class BareTestBase {
33 |
34 | @Rule
35 | public TestName name = new TestName();
36 |
37 | protected AmqpClient client;
38 |
39 | protected Vertx vertx;
40 |
41 | @Before
42 | public void setUp() {
43 | vertx = Vertx.vertx();
44 | }
45 |
46 | @After
47 | public void tearDown() throws InterruptedException {
48 | CountDownLatch latchForClient = new CountDownLatch(1);
49 | CountDownLatch latchForVertx = new CountDownLatch(1);
50 | if (client != null) {
51 | client.close().onComplete(x -> latchForClient.countDown());
52 | latchForClient.await(10, TimeUnit.SECONDS);
53 | }
54 | vertx.close().onComplete(x -> latchForVertx.countDown());
55 | latchForVertx.await(10, TimeUnit.SECONDS);
56 | }
57 |
58 | @Test
59 | public void justToAvoidTheIdeToFail() {
60 |
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/test/java/io/vertx/amqp/tests/ConnectionMetadataTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018-2019 The original author or authors
3 | *
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * and Apache License v2.0 which accompanies this distribution.
7 | *
8 | * The Eclipse Public License is available at
9 | * http://www.eclipse.org/legal/epl-v10.html
10 | *
11 | * The Apache License v2.0 is available at
12 | * http://www.opensource.org/licenses/apache2.0.php
13 | *
14 | * You may elect to redistribute this code under either of these licenses.
15 | */
16 | package io.vertx.amqp.tests;
17 |
18 | import io.vertx.amqp.AmqpClient;
19 | import io.vertx.amqp.AmqpClientOptions;
20 | import io.vertx.amqp.AmqpConnection;
21 | import io.vertx.amqp.impl.AmqpConnectionImpl;
22 | import io.vertx.core.Vertx;
23 | import io.vertx.ext.unit.Async;
24 | import io.vertx.ext.unit.TestContext;
25 | import io.vertx.ext.unit.junit.VertxUnitRunner;
26 | import org.apache.qpid.proton.amqp.Symbol;
27 | import org.junit.After;
28 | import org.junit.Before;
29 | import org.junit.Test;
30 | import org.junit.runner.RunWith;
31 |
32 | import java.util.Map;
33 | import java.util.concurrent.ExecutionException;
34 | import java.util.concurrent.atomic.AtomicBoolean;
35 |
36 | @RunWith(VertxUnitRunner.class)
37 | public class ConnectionMetadataTest {
38 |
39 | private Vertx vertx;
40 | private MockServer server;
41 |
42 | @Before
43 | public void setUp() {
44 | vertx = Vertx.vertx();
45 | }
46 |
47 | @After
48 | public void tearDown() {
49 | if (server != null) {
50 | server.close();
51 | }
52 | if (vertx != null) {
53 | vertx.close();
54 | }
55 | }
56 |
57 | @Test(timeout = 20000)
58 | public void testMetadata(TestContext context) throws ExecutionException, InterruptedException {
59 | Async asyncMetaData = context.async();
60 | Async asyncShutdown = context.async();
61 |
62 | server = new MockServer(vertx, serverConnection -> {
63 | serverConnection.closeHandler(x -> serverConnection.close());
64 |
65 | serverConnection.openHandler(x -> {
66 | // Open the connection.
67 | serverConnection.open();
68 |
69 | // Validate the properties separately.
70 | Map properties = serverConnection.getRemoteProperties();
71 |
72 | context.assertNotNull(properties, "connection properties not present");
73 |
74 | context.assertTrue(properties.containsKey(AmqpConnectionImpl.PRODUCT_KEY),
75 | "product property key not present");
76 | context.assertEquals(AmqpConnectionImpl.PRODUCT, properties.get(AmqpConnectionImpl.PRODUCT_KEY),
77 | "unexpected product property value");
78 |
79 | asyncMetaData.complete();
80 | });
81 | });
82 |
83 | AmqpClient.create(new AmqpClientOptions().setHost("localhost").setPort(server.actualPort()))
84 | .connect()
85 | .compose(AmqpConnection::close)
86 | .onComplete(context.asyncAssertSuccess(x -> {
87 | asyncShutdown.complete();
88 | }));
89 | }
90 |
91 | @Test(timeout = 20000)
92 | public void testConnectionHostnameAndContainerID(TestContext context) throws Exception {
93 | doConnectionHostnameAndContainerIDTestImpl(context, true);
94 | doConnectionHostnameAndContainerIDTestImpl(context, false);
95 | }
96 |
97 | private void doConnectionHostnameAndContainerIDTestImpl(TestContext context, boolean customValues) throws Exception {
98 | String tcpConnectionHostname = "localhost";
99 | String containerId = "myCustomContainer";
100 | String vhost = "myCustomVhost";
101 |
102 | Async asyncShutdown = context.async();
103 | AtomicBoolean linkOpened = new AtomicBoolean();
104 |
105 | MockServer server = new MockServer(vertx, serverConnection -> {
106 | serverConnection.openHandler(x -> {
107 | if (customValues) {
108 | context.assertEquals(vhost, serverConnection.getRemoteHostname());
109 | context.assertFalse(tcpConnectionHostname.equals(serverConnection.getRemoteHostname()));
110 |
111 | context.assertEquals(containerId, serverConnection.getRemoteContainer());
112 | } else {
113 | context.assertEquals(tcpConnectionHostname, serverConnection.getRemoteHostname());
114 | context.assertNotNull(containerId, serverConnection.getRemoteContainer());
115 | }
116 | serverConnection.open();
117 | });
118 | serverConnection.closeHandler(x -> {
119 | serverConnection.close();
120 | });
121 | });
122 |
123 | AmqpClientOptions opts = new AmqpClientOptions()
124 | .setHost(tcpConnectionHostname).setPort(server.actualPort());
125 | if (customValues) {
126 | opts.setContainerId(containerId).setVirtualHost(vhost);
127 | }
128 |
129 | AmqpClient.create(opts)
130 | .connect().onComplete(context.asyncAssertSuccess(conn -> {
131 | conn.close().onComplete(context.asyncAssertSuccess(shutdownRes -> {
132 | asyncShutdown.complete();
133 | }));
134 | }));
135 |
136 | try {
137 | asyncShutdown.awaitSuccess();
138 | } finally {
139 | server.close();
140 | }
141 |
142 | context.assertFalse(linkOpened.get());
143 | }
144 |
145 | }
146 |
--------------------------------------------------------------------------------
/src/test/java/io/vertx/amqp/tests/ConnectionTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018-2019 The original author or authors
3 | *
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * and Apache License v2.0 which accompanies this distribution.
7 | *
8 | * The Eclipse Public License is available at
9 | * http://www.eclipse.org/legal/epl-v10.html
10 | *
11 | * The Apache License v2.0 is available at
12 | * http://www.opensource.org/licenses/apache2.0.php
13 | *
14 | * You may elect to redistribute this code under either of these licenses.
15 | */
16 | package io.vertx.amqp.tests;
17 |
18 | import io.vertx.amqp.AmqpClient;
19 | import io.vertx.amqp.AmqpClientOptions;
20 | import org.apache.qpid.proton.engine.Sasl;
21 | import org.apache.qpid.proton.engine.Transport;
22 | import org.apache.qpid.proton.engine.Sasl.SaslOutcome;
23 | import org.junit.Test;
24 |
25 | import io.vertx.core.Handler;
26 | import io.vertx.core.net.NetSocket;
27 | import io.vertx.proton.ProtonConnection;
28 | import io.vertx.proton.sasl.ProtonSaslAuthenticator;
29 |
30 | import java.nio.charset.StandardCharsets;
31 | import java.util.Objects;
32 | import java.util.concurrent.CountDownLatch;
33 | import java.util.concurrent.TimeUnit;
34 | import java.util.concurrent.atomic.AtomicBoolean;
35 | import java.util.concurrent.atomic.AtomicReference;
36 |
37 | import static org.assertj.core.api.Assertions.assertThat;
38 |
39 | public class ConnectionTest extends BareTestBase {
40 |
41 | private static final String USER = "MY_USER";
42 | private static final String PASSWD = "PASSWD_VALUE";
43 | private static final String BAD_PASSWD = "WRONG_VALUE";
44 |
45 | private static final String PLAIN = "PLAIN";
46 |
47 | @Test(timeout = 10000)
48 | public void testConnectionSuccessWithDetailsPassedInOptions() throws Exception {
49 | doConnectionWithDetailsPassedInOptionsTestImpl(true);
50 | }
51 |
52 | @Test(timeout = 10000)
53 | public void testConnectionFailureWithDetailsPassedInOptions() throws Exception {
54 | doConnectionWithDetailsPassedInOptionsTestImpl(false);
55 | }
56 |
57 | private void doConnectionWithDetailsPassedInOptionsTestImpl(boolean succeed) throws Exception {
58 | CountDownLatch done = new CountDownLatch(1);
59 | String password = succeed ? PASSWD : BAD_PASSWD;
60 | AtomicBoolean serverConnectionOpen = new AtomicBoolean();
61 |
62 | MockServer server = new MockServer(vertx, serverConnection -> {
63 | serverConnection.openHandler(serverSender -> {
64 | serverConnectionOpen.set(true);
65 | serverConnection.closeHandler(x -> serverConnection.close());
66 | serverConnection.open();
67 | });
68 | });
69 |
70 | TestAuthenticator authenticator = new TestAuthenticator(PLAIN, succeed);
71 | server.getProtonServer().saslAuthenticatorFactory(() -> authenticator);
72 |
73 | try {
74 | client = AmqpClient.create(vertx, new AmqpClientOptions()
75 | .setHost("localhost")
76 | .setPort(server.actualPort())
77 | .setUsername(USER)
78 | .setPassword(password));
79 |
80 | client.connect().onComplete(ar -> {
81 | if (ar.failed() && succeed) {
82 | ar.cause().printStackTrace();
83 | } else {
84 | done.countDown();
85 | }
86 | });
87 |
88 | assertThat(done.await(6, TimeUnit.SECONDS)).isTrue();
89 | assertThat(serverConnectionOpen.get()).isEqualTo(succeed);
90 | } finally {
91 | server.close();
92 | }
93 |
94 | assertThat(authenticator.getChosenMech()).isEqualTo(PLAIN);
95 | assertThat(authenticator.getInitialResponse()).isEqualTo(getPlainInitialResponse(USER, password));
96 | }
97 |
98 | @Test(timeout = 10000)
99 | public void testConnectionSuccessWithDetailsPassedAsSystemVariables() throws Exception {
100 | doConnectionWithDetailsPassedAsSystemVariablesTestImpl(true);
101 | }
102 |
103 | @Test(timeout = 10000)
104 | public void testConnectionFailureWithDetailsPassedAsSystemVariables() throws Exception {
105 | doConnectionWithDetailsPassedAsSystemVariablesTestImpl(false);
106 | }
107 |
108 | private void doConnectionWithDetailsPassedAsSystemVariablesTestImpl(boolean succeed) throws Exception {
109 | CountDownLatch done = new CountDownLatch(1);
110 | AtomicBoolean serverConnectionOpen = new AtomicBoolean();
111 | String password = succeed ? PASSWD : BAD_PASSWD;
112 |
113 | MockServer server = new MockServer(vertx, serverConnection -> {
114 | // Expect a connection
115 | serverConnection.openHandler(serverSender -> {
116 | serverConnectionOpen.set(true);
117 | serverConnection.closeHandler(x -> serverConnection.close());
118 | serverConnection.open();
119 | });
120 | });
121 |
122 | TestAuthenticator authenticator = new TestAuthenticator(PLAIN, succeed);
123 | server.getProtonServer().saslAuthenticatorFactory(() -> authenticator);
124 |
125 | System.setProperty("amqp-client-host", "localhost");
126 | System.setProperty("amqp-client-port", Integer.toString(server.actualPort()));
127 | System.setProperty("amqp-client-username", USER);
128 | System.setProperty("amqp-client-password", password);
129 | try {
130 | client = AmqpClient.create(vertx, new AmqpClientOptions());
131 |
132 | client.connect().onComplete(ar -> {
133 | if (ar.failed() && succeed) {
134 | ar.cause().printStackTrace();
135 | } else {
136 | done.countDown();
137 | }
138 | });
139 |
140 | assertThat(done.await(6, TimeUnit.SECONDS)).isTrue();
141 | assertThat(serverConnectionOpen.get()).isEqualTo(succeed);
142 | }
143 | finally {
144 | System.clearProperty("amqp-client-host");
145 | System.clearProperty("amqp-client-port");
146 | System.clearProperty("amqp-client-username");
147 | System.clearProperty("amqp-client-password");
148 | server.close();
149 | }
150 | }
151 |
152 | @Test(timeout = 10000)
153 | public void testConnectionFailedBecauseOfBadHost() throws Exception {
154 | CountDownLatch done = new CountDownLatch(1);
155 | AtomicBoolean serverConnectionOpen = new AtomicBoolean();
156 |
157 | MockServer server = new MockServer(vertx, serverConnection -> {
158 | // [Dont] expect a connection
159 | serverConnection.openHandler(serverSender -> {
160 | serverConnectionOpen.set(true);
161 | serverConnection.closeHandler(x -> serverConnection.close());
162 | serverConnection.open();
163 | });
164 | });
165 |
166 | try {
167 | AtomicReference failure = new AtomicReference<>();
168 | client = AmqpClient.create(vertx, new AmqpClientOptions()
169 | .setHost("org.acme")
170 | .setPort(server.actualPort()));
171 |
172 | client.connect().onComplete(ar -> {
173 | failure.set(ar.cause());
174 | done.countDown();
175 | });
176 |
177 | assertThat(done.await(6, TimeUnit.SECONDS)).isTrue();
178 | assertThat(failure.get()).isNotNull();
179 | assertThat(serverConnectionOpen.get()).isFalse();
180 | } finally {
181 | server.close();
182 | }
183 | }
184 |
185 | @Test(timeout = 10000)
186 | public void testConnectionWithAmqpOpenHostnameOverride() throws Exception {
187 | CountDownLatch done = new CountDownLatch(1);
188 | AtomicReference serverConnectionRef = new AtomicReference<>();
189 |
190 | MockServer server = new MockServer(vertx, serverConnection -> {
191 | serverConnection.openHandler(x -> {
192 | serverConnectionRef.set(serverConnection);
193 | serverConnection.closeHandler(y -> serverConnection.close());
194 | serverConnection.open();
195 | });
196 | });
197 |
198 | String serverDnsHost = "localhost";
199 | String connectionHostname = "some-other-hostname";
200 |
201 | assertThat(serverDnsHost).isNotEqualTo(connectionHostname);
202 |
203 | try {
204 | AmqpClientOptions options = new AmqpClientOptions()
205 | .setHost(serverDnsHost)
206 | .setPort(server.actualPort())
207 | .setConnectionHostname(connectionHostname);
208 |
209 | client = AmqpClient.create(vertx, options);
210 |
211 | client.connect().onComplete(ar -> {
212 | if (ar.failed()) {
213 | ar.cause().printStackTrace();
214 | } else {
215 | done.countDown();
216 | }
217 | });
218 |
219 | assertThat(done.await(5, TimeUnit.SECONDS)).isTrue();
220 | ProtonConnection serverConnection = serverConnectionRef.get();
221 |
222 | assertThat(serverConnection).isNotNull();
223 | assertThat(serverConnection.getRemoteHostname()).isEqualTo(connectionHostname);
224 | } finally {
225 | server.close();
226 | }
227 | }
228 |
229 | private static byte[] getPlainInitialResponse(String username, String password) {
230 | Objects.requireNonNull(username);
231 | Objects.requireNonNull(password);
232 |
233 | byte[] usernameBytes = username.getBytes(StandardCharsets.UTF_8);
234 | byte[] passwordBytes = password.getBytes(StandardCharsets.UTF_8);
235 |
236 | byte[] data = new byte[usernameBytes.length + passwordBytes.length + 2];
237 | System.arraycopy(usernameBytes, 0, data, 1, usernameBytes.length);
238 | System.arraycopy(passwordBytes, 0, data, 2 + usernameBytes.length, passwordBytes.length);
239 |
240 | return data;
241 | }
242 |
243 | private static final class TestAuthenticator implements ProtonSaslAuthenticator {
244 | private Sasl sasl;
245 | private boolean succeed;
246 | private String offeredMech;
247 | String chosenMech = null;
248 | byte[] initialResponse = null;
249 |
250 | public TestAuthenticator(String offeredMech, boolean succeed){
251 | this.offeredMech = offeredMech;
252 | this.succeed = succeed;
253 | }
254 |
255 | @Override
256 | public void init(NetSocket socket, ProtonConnection protonConnection, Transport transport) {
257 | this.sasl = transport.sasl();
258 | sasl.server();
259 | sasl.allowSkip(false);
260 | sasl.setMechanisms(offeredMech);
261 | }
262 |
263 | @Override
264 | public void process(Handler processComplete) {
265 | boolean done = false;
266 | String[] remoteMechanisms = sasl.getRemoteMechanisms();
267 | if (remoteMechanisms.length > 0) {
268 | chosenMech = remoteMechanisms[0];
269 |
270 | initialResponse = new byte[sasl.pending()];
271 | sasl.recv(initialResponse, 0, initialResponse.length);
272 |
273 | if (succeed) {
274 | sasl.done(SaslOutcome.PN_SASL_OK);
275 | } else {
276 | sasl.done(SaslOutcome.PN_SASL_AUTH);
277 | }
278 |
279 | done = true;
280 | }
281 |
282 | processComplete.handle(done);
283 | }
284 |
285 | @Override
286 | public boolean succeeded() {
287 | return succeed;
288 | }
289 |
290 | public String getChosenMech() {
291 | return chosenMech;
292 | }
293 |
294 | public byte[] getInitialResponse() {
295 | return initialResponse;
296 | }
297 | }
298 | }
299 |
--------------------------------------------------------------------------------
/src/test/java/io/vertx/amqp/tests/DisabledAnonymousLinkTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018-2019 The original author or authors
3 | *
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * and Apache License v2.0 which accompanies this distribution.
7 | *
8 | * The Eclipse Public License is available at
9 | * http://www.eclipse.org/legal/epl-v10.html
10 | *
11 | * The Apache License v2.0 is available at
12 | * http://www.opensource.org/licenses/apache2.0.php
13 | *
14 | * You may elect to redistribute this code under either of these licenses.
15 | */
16 | package io.vertx.amqp.tests;
17 |
18 | import io.vertx.amqp.AmqpClient;
19 | import io.vertx.amqp.AmqpClientOptions;
20 | import io.vertx.ext.unit.Async;
21 | import io.vertx.ext.unit.TestContext;
22 | import io.vertx.proton.ProtonHelper;
23 | import io.vertx.proton.ProtonSession;
24 | import org.apache.qpid.proton.amqp.transport.AmqpError;
25 | import org.junit.Test;
26 |
27 | import java.util.concurrent.atomic.AtomicBoolean;
28 |
29 | public class DisabledAnonymousLinkTest extends BareTestBase {
30 |
31 | @Test(timeout = 20000)
32 | public void testConnectionToServerWithoutAnonymousSenderLinkSupport(TestContext context) throws Exception {
33 | Async asyncShutdown = context.async();
34 | AtomicBoolean linkOpened = new AtomicBoolean();
35 |
36 | MockServer server = new MockServer(vertx, serverConnection -> {
37 | serverConnection.openHandler(x -> serverConnection.open());
38 | serverConnection.closeHandler(x -> serverConnection.close());
39 | serverConnection.sessionOpenHandler(ProtonSession::open);
40 | serverConnection.receiverOpenHandler(serverReceiver -> {
41 | linkOpened.set(true);
42 | serverReceiver.setCondition(ProtonHelper.condition(AmqpError.PRECONDITION_FAILED, "Expected no links"));
43 | serverReceiver.close();
44 | });
45 | serverConnection.senderOpenHandler(serverSender -> {
46 | linkOpened.set(true);
47 | serverSender.setCondition(ProtonHelper.condition(AmqpError.PRECONDITION_FAILED, "Expected no links"));
48 | serverSender.close();
49 | });
50 | });
51 | server.getProtonServer().setAdvertiseAnonymousRelayCapability(false);
52 |
53 | AmqpClientOptions options = new AmqpClientOptions()
54 | .setHost("localhost")
55 | .setPort(server.actualPort());
56 |
57 | this.client = AmqpClient.create(vertx, options);
58 | client.connect().onComplete(context.asyncAssertSuccess(res -> {
59 | res.close().onComplete(context.asyncAssertSuccess(shutdownRes -> {
60 | asyncShutdown.complete();
61 | }));
62 | }));
63 |
64 | try {
65 | asyncShutdown.awaitSuccess();
66 | } finally {
67 | server.close();
68 | }
69 |
70 | context.assertFalse(linkOpened.get());
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/test/java/io/vertx/amqp/tests/DisconnectTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018-2019 The original author or authors
3 | *
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * and Apache License v2.0 which accompanies this distribution.
7 | *
8 | * The Eclipse Public License is available at
9 | * http://www.eclipse.org/legal/epl-v10.html
10 | *
11 | * The Apache License v2.0 is available at
12 | * http://www.opensource.org/licenses/apache2.0.php
13 | *
14 | * You may elect to redistribute this code under either of these licenses.
15 | */
16 | package io.vertx.amqp.tests;
17 |
18 | import java.util.UUID;
19 |
20 | import io.vertx.amqp.AmqpClient;
21 | import io.vertx.amqp.AmqpClientOptions;
22 | import io.vertx.amqp.AmqpReceiverOptions;
23 | import io.vertx.amqp.impl.AmqpClientImpl;
24 | import org.junit.Test;
25 | import org.junit.runner.RunWith;
26 |
27 | import io.vertx.ext.unit.Async;
28 | import io.vertx.ext.unit.TestContext;
29 | import io.vertx.ext.unit.junit.VertxUnitRunner;
30 |
31 | @RunWith(VertxUnitRunner.class)
32 | public class DisconnectTest extends BareTestBase {
33 |
34 | @Test(timeout = 20000)
35 | public void testUseConnectionAfterDisconnect(TestContext ctx) throws Exception {
36 | MockServer server = new MockServer(vertx, serverConnection -> {
37 | // Expect a connection
38 | serverConnection.openHandler(serverSender -> {
39 | serverConnection.closeHandler(x -> serverConnection.close());
40 | serverConnection.open();
41 | });
42 | });
43 |
44 | String queue = UUID.randomUUID().toString();
45 | client = AmqpClient.create(vertx, new AmqpClientOptions()
46 | .setHost("localhost")
47 | .setPort(server.actualPort()));
48 |
49 | Async handlerFired = ctx.async();
50 | client.connect().onComplete(ctx.asyncAssertSuccess(conn -> {
51 | conn.exceptionHandler(err -> {
52 | conn.createSender(queue).onComplete(ctx.asyncAssertFailure(sender -> {
53 | }));
54 | conn.createAnonymousSender().onComplete(ctx.asyncAssertFailure(sender -> {
55 | }));
56 | conn.createReceiver("some-address").onComplete(ctx.asyncAssertFailure(sender -> {
57 | }));
58 | conn.createReceiver("some-address", new AmqpReceiverOptions()).onComplete(ctx.asyncAssertFailure(sender -> {
59 | }));
60 | conn.createDynamicReceiver().onComplete(ctx.asyncAssertFailure(sender -> {
61 | }));
62 |
63 | handlerFired.complete();
64 | });
65 |
66 | server.close();
67 | }));
68 |
69 | handlerFired.awaitSuccess();
70 | }
71 |
72 | @Test(timeout = 20000)
73 | public void testConnectionsCleanupOnDisconnect(TestContext ctx) throws Exception {
74 | MockServer server = new MockServer(vertx, serverConnection -> {
75 | // Expect a connection
76 | serverConnection.openHandler(serverSender -> {
77 | serverConnection.open();
78 | });
79 | });
80 |
81 | client = AmqpClient.create(vertx, new AmqpClientOptions()
82 | .setHost("localhost")
83 | .setPort(server.actualPort()));
84 |
85 | Async handlerFired = ctx.async();
86 | client.connect().onComplete(ctx.asyncAssertSuccess(conn -> {
87 | conn.closeFuture().onComplete(ar -> {
88 | ctx.assertEquals(0, ((AmqpClientImpl)client).numConnections());
89 | handlerFired.complete();
90 | });
91 | server.close();
92 | }));
93 |
94 | handlerFired.awaitSuccess();
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/test/java/io/vertx/amqp/tests/FutureHandler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018-2019 The original author or authors
3 | *
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * and Apache License v2.0 which accompanies this distribution.
7 | *
8 | * The Eclipse Public License is available at
9 | * http://www.eclipse.org/legal/epl-v10.html
10 | *
11 | * The Apache License v2.0 is available at
12 | * http://www.opensource.org/licenses/apache2.0.php
13 | *
14 | * You may elect to redistribute this code under either of these licenses.
15 | */
16 | package io.vertx.amqp.tests;
17 |
18 | import io.vertx.core.AsyncResult;
19 | import io.vertx.core.Handler;
20 |
21 | import java.util.concurrent.*;
22 |
23 | abstract public class FutureHandler implements Future, Handler {
24 |
25 | protected ExecutionException exception;
26 | protected T result;
27 | protected CountDownLatch latch = new CountDownLatch(1);
28 |
29 | public static FutureHandler simple() {
30 | return new FutureHandler() {
31 | @Override
32 | synchronized public void handle(T t) {
33 | result = t;
34 | latch.countDown();
35 | }
36 | };
37 | }
38 |
39 | public static FutureHandler> asyncResult() {
40 | return new FutureHandler>() {
41 | @Override
42 | synchronized public void handle(AsyncResult t) {
43 | if (t.succeeded()) {
44 | result = t.result();
45 | } else {
46 | exception = new ExecutionException(t.cause());
47 | }
48 | latch.countDown();
49 | }
50 | };
51 | }
52 |
53 | @Override
54 | abstract public void handle(X t);
55 |
56 | public T get() throws InterruptedException, ExecutionException {
57 | latch.await();
58 | return result();
59 | }
60 |
61 | private T result() throws ExecutionException {
62 | synchronized (this) {
63 | if (exception != null) {
64 | throw exception;
65 | }
66 | return result;
67 | }
68 | }
69 |
70 | public T get(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException, ExecutionException {
71 | if (latch.await(timeout, unit)) {
72 | return result();
73 | } else {
74 | throw new TimeoutException();
75 | }
76 | }
77 |
78 | @Override
79 | public boolean cancel(boolean mayInterruptIfRunning) {
80 | return false;
81 | }
82 |
83 | @Override
84 | public boolean isCancelled() {
85 | return false;
86 | }
87 |
88 | @Override
89 | public boolean isDone() {
90 | return false;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/test/java/io/vertx/amqp/tests/MockServer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018-2019 The original author or authors
3 | *
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * and Apache License v2.0 which accompanies this distribution.
7 | *
8 | * The Eclipse Public License is available at
9 | * http://www.eclipse.org/legal/epl-v10.html
10 | *
11 | * The Apache License v2.0 is available at
12 | * http://www.opensource.org/licenses/apache2.0.php
13 | *
14 | * You may elect to redistribute this code under either of these licenses.
15 | */
16 | package io.vertx.amqp.tests;
17 |
18 | import io.vertx.core.AsyncResult;
19 | import io.vertx.core.Handler;
20 | import io.vertx.core.Vertx;
21 | import io.vertx.proton.ProtonConnection;
22 | import io.vertx.proton.ProtonServer;
23 | import io.vertx.proton.ProtonServerOptions;
24 |
25 | import java.util.concurrent.ExecutionException;
26 |
27 | public class MockServer {
28 | private ProtonServer server;
29 |
30 | // Toggle to (re)use a fixed port, e.g for capture.
31 | private int bindPort = 0;
32 | private boolean reuseAddress = false;
33 |
34 | public MockServer(Vertx vertx, Handler connectionHandler)
35 | throws ExecutionException, InterruptedException {
36 | this(vertx, connectionHandler, null);
37 | }
38 |
39 | public MockServer(Vertx vertx, Handler connectionHandler, ProtonServerOptions protonServerOptions)
40 | throws ExecutionException, InterruptedException {
41 | if (protonServerOptions == null) {
42 | protonServerOptions = new ProtonServerOptions();
43 | }
44 |
45 | protonServerOptions.setReuseAddress(reuseAddress);
46 | server = ProtonServer.create(vertx, protonServerOptions);
47 | server.connectHandler(connectionHandler);
48 |
49 | FutureHandler> handler = FutureHandler.asyncResult();
50 | server.listen(bindPort, handler);
51 | handler.get();
52 | }
53 |
54 | public int actualPort() {
55 | return server.actualPort();
56 | }
57 |
58 | public void close() {
59 | server.close();
60 | }
61 |
62 | ProtonServer getProtonServer() {
63 | return server;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/test/java/io/vertx/amqp/tests/ReceiverCreditTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018-2019 The original author or authors
3 | *
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * and Apache License v2.0 which accompanies this distribution.
7 | *
8 | * The Eclipse Public License is available at
9 | * http://www.eclipse.org/legal/epl-v10.html
10 | *
11 | * The Apache License v2.0 is available at
12 | * http://www.opensource.org/licenses/apache2.0.php
13 | *
14 | * You may elect to redistribute this code under either of these licenses.
15 | */
16 | package io.vertx.amqp.tests;
17 |
18 | import io.vertx.amqp.AmqpClient;
19 | import io.vertx.amqp.AmqpClientOptions;
20 | import io.vertx.amqp.AmqpReceiverOptions;
21 | import io.vertx.ext.unit.Async;
22 | import io.vertx.ext.unit.TestContext;
23 | import io.vertx.proton.ProtonSession;
24 | import org.apache.qpid.proton.Proton;
25 | import org.apache.qpid.proton.amqp.messaging.AmqpValue;
26 | import org.apache.qpid.proton.amqp.messaging.Source;
27 | import org.junit.Test;
28 |
29 | import java.util.UUID;
30 | import java.util.concurrent.ExecutionException;
31 | import java.util.concurrent.atomic.AtomicBoolean;
32 |
33 | public class ReceiverCreditTest extends BareTestBase {
34 |
35 | private MockServer server;
36 |
37 | @Override
38 | public void tearDown() throws InterruptedException {
39 | super.tearDown();
40 | if (server != null) {
41 | server.close();
42 | }
43 | }
44 |
45 | @Test(timeout = 20000)
46 | public void testInitialCredit(TestContext context) throws Exception {
47 | doConsumerInitialCreditTestImpl(context, false, 1000);
48 | }
49 |
50 | @Test(timeout = 20000)
51 | public void testInitialCreditInfluencedByConsumerBufferSize(TestContext context) throws Exception {
52 | doConsumerInitialCreditTestImpl(context, true, 42);
53 | }
54 |
55 | private void doConsumerInitialCreditTestImpl(TestContext context, boolean setMaxBuffered,
56 | int initialCredit) throws Exception {
57 | final String testName = name.getMethodName();
58 | final String sentContent = "myMessageContent-" + testName;
59 |
60 | final AtomicBoolean firstSendQDrainHandlerCall = new AtomicBoolean();
61 | final Async asyncInitialCredit = context.async();
62 | final Async asyncCompletion = context.async();
63 |
64 | // === Server handling ====
65 |
66 | server = new MockServer(vertx, serverConnection -> {
67 | // Expect a connection
68 | serverConnection.openHandler(serverSender -> {
69 | // Add a close handler
70 | serverConnection.closeHandler(x -> serverConnection.close());
71 | serverConnection.open();
72 | });
73 |
74 | // Expect a session to open, when the receiver is created
75 | serverConnection.sessionOpenHandler(ProtonSession::open);
76 |
77 | // Expect a sender link open for the receiver
78 | serverConnection.senderOpenHandler(serverSender -> {
79 | Source remoteSource = (Source) serverSender.getRemoteSource();
80 | context.assertNotNull(remoteSource, "source should not be null");
81 | context.assertEquals(testName, remoteSource.getAddress(), "expected given address");
82 | // Naive test-only handling
83 | serverSender.setSource(remoteSource.copy());
84 |
85 | serverSender.sendQueueDrainHandler(s -> {
86 | // Verify the initial credit when the handler is first called and send a message
87 | if (firstSendQDrainHandlerCall.compareAndSet(false, true)) {
88 | context.assertEquals(initialCredit, s.getCredit(), "unexpected initial credit");
89 | context.assertFalse(s.sendQueueFull(), "expected send queue not to be full");
90 |
91 | asyncInitialCredit.complete();
92 |
93 | // send message
94 | org.apache.qpid.proton.message.Message protonMsg = Proton.message();
95 | protonMsg.setBody(new AmqpValue(sentContent));
96 |
97 | serverSender.send(protonMsg);
98 | }
99 | });
100 |
101 | serverSender.open();
102 | });
103 | });
104 |
105 | // === Client consumer handling ====
106 |
107 | AmqpClientOptions options = new AmqpClientOptions().setHost("localhost")
108 | .setPort(server.actualPort());
109 |
110 | client = AmqpClient.create(vertx, options);
111 | client.connect().onComplete(context.asyncAssertSuccess(res -> {
112 | AmqpReceiverOptions recOpts = new AmqpReceiverOptions();
113 | if (setMaxBuffered) {
114 | recOpts.setMaxBufferedMessages(initialCredit);
115 | }
116 | res.createReceiver(testName, recOpts).onComplete(context.asyncAssertSuccess(consumer -> {
117 | consumer.handler(msg -> {
118 | context.assertNotNull(msg.bodyAsString(), "amqp message body content was null");
119 | context.assertEquals(sentContent, msg.bodyAsString(), "amqp message body not as expected");
120 | asyncCompletion.complete();
121 | });
122 | }));
123 | }));
124 |
125 | asyncInitialCredit.awaitSuccess();
126 | asyncCompletion.awaitSuccess();
127 | }
128 |
129 | @Test(timeout = 20000)
130 | public void testDynamicReceiver(TestContext context) throws ExecutionException, InterruptedException {
131 | String address = UUID.randomUUID().toString();
132 | Async serverLinkOpenAsync = context.async();
133 |
134 | server = new MockServer(vertx, serverConnection -> {
135 | serverConnection.openHandler(result -> serverConnection.open());
136 |
137 | serverConnection.sessionOpenHandler(ProtonSession::open);
138 | serverConnection.closeHandler(conn -> serverConnection.close());
139 | serverConnection.senderOpenHandler(serverReceiver -> {
140 | serverReceiver.closeHandler(res -> serverReceiver.close());
141 |
142 | // Verify the remote terminus details used were as expected
143 | context.assertNotNull(serverReceiver.getRemoteSource(), "source should not be null");
144 | org.apache.qpid.proton.amqp.messaging.Source remoteSource =
145 | (org.apache.qpid.proton.amqp.messaging.Source) serverReceiver.getRemoteSource();
146 | context.assertTrue(remoteSource.getDynamic(), "expected dynamic source to be requested");
147 | context.assertNull(remoteSource.getAddress(), "expected no source address to be set");
148 |
149 | // Set the local terminus details
150 | org.apache.qpid.proton.amqp.messaging.Source target =
151 | (org.apache.qpid.proton.amqp.messaging.Source) remoteSource.copy();
152 | target.setAddress(address);
153 | serverReceiver.setSource(target);
154 |
155 | serverReceiver.open();
156 |
157 | serverLinkOpenAsync.complete();
158 | });
159 | });
160 |
161 | client = AmqpClient.create(vertx,
162 | new AmqpClientOptions().setHost("localhost").setPort(server.actualPort()));
163 |
164 | client.connect().onComplete(context.asyncAssertSuccess(res -> {
165 | res.createDynamicReceiver().onComplete(context.asyncAssertSuccess(rec -> {
166 | context.assertEquals(rec.address(), address);
167 | }));
168 | }));
169 |
170 | serverLinkOpenAsync.awaitSuccess();
171 |
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/src/test/java/io/vertx/amqp/tests/ReceptionTypeTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018-2019 The original author or authors
3 | *
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * and Apache License v2.0 which accompanies this distribution.
7 | *
8 | * The Eclipse Public License is available at
9 | * http://www.eclipse.org/legal/epl-v10.html
10 | *
11 | * The Apache License v2.0 is available at
12 | * http://www.opensource.org/licenses/apache2.0.php
13 | *
14 | * You may elect to redistribute this code under either of these licenses.
15 | */
16 | package io.vertx.amqp.tests;
17 |
18 | import io.vertx.amqp.AmqpClient;
19 | import io.vertx.amqp.AmqpClientOptions;
20 | import io.vertx.amqp.AmqpConnection;
21 | import io.vertx.amqp.AmqpMessage;
22 | import io.vertx.core.buffer.Buffer;
23 | import io.vertx.core.json.JsonArray;
24 | import io.vertx.core.json.JsonObject;
25 | import org.apache.qpid.proton.amqp.Binary;
26 | import org.apache.qpid.proton.amqp.Symbol;
27 | import org.apache.qpid.proton.amqp.messaging.AmqpSequence;
28 | import org.apache.qpid.proton.amqp.messaging.AmqpValue;
29 | import org.apache.qpid.proton.amqp.messaging.Data;
30 | import org.apache.qpid.proton.amqp.messaging.Section;
31 | import org.apache.qpid.proton.message.Message;
32 | import org.junit.After;
33 | import org.junit.Before;
34 | import org.junit.Test;
35 |
36 | import java.time.Instant;
37 | import java.util.*;
38 | import java.util.concurrent.CopyOnWriteArrayList;
39 | import java.util.concurrent.CountDownLatch;
40 | import java.util.concurrent.TimeUnit;
41 | import java.util.concurrent.atomic.AtomicReference;
42 | import java.util.function.Function;
43 |
44 | import static org.assertj.core.api.Assertions.assertThat;
45 |
46 | public class ReceptionTypeTest extends BareTestBase {
47 |
48 | private AmqpConnection connection;
49 | private AtomicReference