Skip to content

Conversation

@benbennett
Copy link

Just added route class, to reduce duplication of code and prepare spring-integration-amqp . Also inspired by the rabbitmq describing routes as entity.
Basically I added route and binding elements to the spring-integration-amqp schema.
A route is a generic class to allow reuse in other binding elements and outbound channel adapters.
So it becomes

<amqp:route exchange="si.test.exchange" id="my.route.id" routing-key="inbound.from.si"/>

It can be referenced in both a <amqp:binding > element or amqp:outbound-channel-adapter
Examples:
<amqp:binding queue="inbound.from.si.queue" route="my.route.id"/>
And also
<amqp:outbound-channel-adapter route-ref="my.route.id" channel="toRabbit" amqp-template="amqpTemplate"/>

I also made changes to spring-integration-amqp that it so you can reference queue, exchanges , route and binding elements from the outbound-channel-adapter and inbound-channel adapter.

Made Binding extend from Route.
Change method String getExchange () to String getExchangeName
Change method String getQueue () to String getQueueName
@dsyer
Copy link
Member

dsyer commented Nov 28, 2010

Looks interesting. Can you fill in the contributor agreement form at https://support.springsource.com/spring_committer_signup please? Then we can have a proper chat about it.

@benbennett
Copy link
Author

I did
Individual Contributor Agreement
Thank you Benjamin Bennett

Thank you for registering to be a SpringSource Community Contributor, we appreciate your dedication to the SpringSource community.

Your confirmation number is XXXXXXXXX

please save this number for your records.

Please contact your project lead for information regarding repository access to the project, project roadmaps, etc..

I didn't know what project lead they wanted so I put Mark Fisher, I might have spelled his name incorrectly.

@dsyer
Copy link
Member

dsyer commented Nov 29, 2010

Thanks. Mark Fisher is correct, but you can consider you have made contact. One last thing: is there a JIRA issue associated with this change (I vaguely remember discussing something similar, but it might have been on the rabbitmq mailing list)?

@benbennett
Copy link
Author

I don't know, I just started coding from the spring-amqp and spring-integration-amqp. The example spring-integration-amqp looked like it was a work in progress and I just wanted to make it such that it used references to beans instead of text strings.

@dsyer
Copy link
Member

dsyer commented Nov 30, 2010

Ben,

There's only one commit and three files are .java, so I didn't see any changes to the XSD or namespace parsers. Did you forget to push something?

A couple of housekeeping requests (none of which are urgent), can you:

  • Change the new .java files to have a simple Javadoc class comment with just an @author tag identifying you (not the full monty that you put in which looks autogenerated)
  • Add the ASF license header comment to all new .java files
  • Add yourself as an @author to the other .java files that you modify substantially (all of them in this case).
  • Add some Javadocs and XSD doc elements (I know Spring AMQP is not stellar in this area yet, but it will have to come up to scratch one day, so you will be helping a lot).
  • A few unit tests would help a lot as well - someone has to do it.

I'll add this stuff to the fork instructions on the main repository page (you are a guinea pig here).

Also, if no-one else is using your branch, could you rebase it against the current master in spring-amqp?

@benbennett
Copy link
Author

The other changes are in the spring-integration-amqp repo. I was trying to minimize the changes in the spring-amqp repo. I will make the rest of the changes tonight, have to get to work.

http://git.springsource.org/spring-integration/sandbox/trees/master/spring-integration-amqp

@benbennett
Copy link
Author

Here is a full example that was extended from the test case in spring-integration-amqp
<beans:bean id="inbound.from.rb.queue" parent="durableQTemplate">
<beans:constructor-arg index="0" value="nd.inbound.from.rb.queue"/>
/beans:bean
<beans:bean id="inbound.from.si.queue" parent="durableQTemplate">
<beans:constructor-arg index="0" value="nd.inbound.from.si.queue"/>
/beans:bean
<beans:bean id="si.test.exchange" parent="directTemplate">
<beans:constructor-arg index="0" value="nd.si.test.exchange"/>
/beans:bean
<amqp:binding id="rb.inbound" queue="inbound.from.rb.queue" exchange="si.test.exchange" routing-key="inbound.from.rb"/>

<amqp:binding id="si.outbound" queue="inbound.from.si.queue" exchange="si.test.exchange" routing-key="inbound.from.si"/>
<amqp:route exchange="si.test.exchange" id="si.route" routing-key="inbound.from.si"/>
<amqp:binding queue="inbound.from.si.queue" route="si.route"/>
<amqp:inbound-channel-adapter queue-ref="inbound.from.rb.queue" channel="fromRabbit" connection-factory="connectionFactoryBean"/>
<publish-subscribe-channel id="fromRabbit"/>
<publish-subscribe-channel id="fromRabbit1"/>
<publish-subscribe-channel id="fromRabbit10"/>
<publish-subscribe-channel id="fromRabbit2"/>
<console:stdout-channel-adapter  channel="fromRabbit1" append-newline="true" />
<console:stdout-channel-adapter  channel="fromRabbit2" append-newline="true" />

<service-activator input-channel="fromRabbit1" ref="rxMsgServ" />
<service-activator input-channel="fromRabbit2" ref="rxMsgServ"/>
<beans:bean id="rxMsgServ" class="org.springframework.integration.amqp.config.EchoServiceActivator"/>

<recipient-list-router id="splitter" input-channel="fromRabbit"
            timeout="5000"
            ignore-send-failures="false"
            apply-sequence="true">
<recipient channel="toRabbit"/>
<recipient channel="fromRabbit1"/>
</recipient-list-router>
<publish-subscribe-channel id="toRabbit"/>
<amqp:outbound-channel-adapter  binding-ref="si.outbound" channel="toRabbit"  amqp-template="amqpTemplate"/>

<amqp:inbound-channel-adapter queue-ref="inbound.from.si.queue" channel="fromRabbit2" connection-factory="connectionFactoryBean"/>

@dsyer
Copy link
Member

dsyer commented Nov 30, 2010

The route and binding declarations belong in the Spring AMQP namespace (as distinct from the Spring Integration AMQP one). Can we port those changes over as part of this pull request, or would you rather do another one?

The spring-rabbit-1.0.xsd we already have in the repo has those elements declared - it just doesn't implement the parser. If you've done that, then let's put it in the right place.

If you want to do the two things separately we can handle that - just tidy up the .java and make an independent request from another branch for the namespace changes.

@benbennett
Copy link
Author

I am not a xml guru. Ahh yes that is better idea. Let me look at it tonight. I was playing around with spring-integration and trying to make rabbit the back end between different systems(.NET talking to JAVA app). We have a bunch of code at work uses spring-integration but now we are adding .NET 3rd party portions and want rabbit to be the broker.
I will write some test cases too, I will try to get it done to tonight .

@markpollack
Copy link
Member

Hi,
I created the namespace in Spring AMQP more as a POC/first-pass type thing so don't take it to be authoritative. I patterned it after http://www.rabbitmq.com/rabbitmq-bql.html rather strictly. The use of the word 'route' concept is going to cause confusing when using SI and doesn't match the fluent API well

@bean
public Binding marketDataBinding() {
return BindingBuilder.from(
marketDataQueue()).to(marketDataExchange()).with(marketDataRoutingKey);
}

no mention of 'routes'. What about we get rid of route and have the binding XSD element take queue, exchange, and optional routing key.

@dsyer
Copy link
Member

dsyer commented Nov 30, 2010

I feel like this discussion should move to JIRA, but I'm happy to be having it.

Mark: I think there are problems with the fluent API and the XML namespace from a Rabbit angle. The broker team are not convinced by it and we should listen to what they say. One of the points is that "exchange" and "queue" as names for the "source" and "destination" of a binding are restrictive (Rabbit supports bindings from Exchange to Exchange as well, even though AMQP doesn't). They prefer "source" and "destination" (for the message) as names, and I agree, but that possibly makes the strongly typed fluent API harder to implement (don't know I haven't tried it - was going to look after 1.0.0.M2).

I don't see why (with other changes in the offing) the fluent API couldn't use a new abstraction with Route as well. The next thing I was going to ask Ben to think about once we got the first draft committed was how to extend it to support binding to an Exchange, and from there to a mult-step route (e.g. e-e-e-e-q).

@benbennett
Copy link
Author

Don't know what your are trying to say. In that you don't want things in spring-integration-amqp(example route, binding, queue)? Is that correct. No problem with that if that is what you said.
And you don't want route elements anywhere?If that is the case here is my reasoning behind routes elements.

First I was inspired by the rabbit-mq documentation example "What happens to messages when a network outage removes the route between an exchange and a bound queue? " The documentation goes and describes routes as the link between an exchange and queue, it is more of an abstract idea , where the binding is the more physical component. I thought the repetition of the term route is was important enough to get its own element.
Secondly I wanted reduce duplication. Maybe I missing something but I want to make it simple such that if someone want to route messages with a routing key from a exchange it was simple to repeat the information such to send it to multiple queues. Example
<amqp:route exchange="si.test.exchange" id="si.route" routing-key="inbound.from.si"/>
<amqp:binding queue="inbound.from.si.queue" route="si.route"/>
<amqp:binding queue="inbound.from.si.queue1" route="si.route"/>
<amqp:binding queue="inbound.from.si.queue2" route="si.route"/>

Now the route only has be declared once, and it can be reused to send the messages to multiple queues.
I might be missing something or don't understand rabbit enough, but I trying to make the routing of messages to queues easier without having to repeat the same information over and over.

@benbennett
Copy link
Author

Jira issue sounds fine, I will work on it in my spare time. I am really just trying to make the spring-amqp / spring-integration-amqp easier to use. I am introducing spring-amqp and spring-integration-amqp at work and I don't want my developers making mistakes. The hard coding of the routes, bindings, and exchanges in the spring-integration-amqp will be error prone and I don't want to have to waste a bunch of time when they make mistakes. Anyways that is that , have to get to back to work work. Is there a way to send a private message. I would rather send some of these things by email, but I don't want all my email being spammed across the Internet.

@markpollack
Copy link
Member

I mentioned it so that we can be consistent - didn't even know about the exchange-to-exchange support in Rabbit. That does require then for some or all the fluent API to move out of spring-amqp and into spring-rabbit.

It concerns me that Route in the way we are discussing it being used is not even a first class term in the AMQP specs. Is the use case you show Ben that common as it seems to me to introduce a new term, though it is used in the Rabbit docs and BQL.

@dsyer
Copy link
Member

dsyer commented Nov 30, 2010

Ben: may email is dsyer AT vmware DOT com. The rabbitmq-discuss group is also a good place to chat about design. Mark and I are possibly talking at cross purposes because I think of spring-amqp as meaning spring-rabbit (whereas in fact they are separated at birth, even though there is no other implementation of the APIs right now).

I think Route is a useful addition, and I suppose if it has to it could even go in spring-amqp as opposed to spring-rabbit, but happy to let it fall in either for now, and we can refactor later. It's not that we don't want contributions to spring-integration-amqp as well, but these features are more generic, so they belong more here.

@benbennett
Copy link
Author

Maybe not , another way might be to have binding that can reference another binding and take its routing information. Does that sound better? Don't know how easy it would be to put it in the code though. Abstract binding class maybe , idk , just trying to throw some things out there.
It is more of a implementation issue, don't want repetition of strings when trying to build up bindings. There has to be other ways. This was just my first attempt at it.

@markfisher
Copy link
Contributor

I apologize for being a bit late to the party. I need to read the RabbitMQ discussion about "routes" to make sure I have the context, but I just want to point out my initial reaction here. I personally do not like the term "route" for this abstraction over bindings. If I understand, the main goal is to provide a single, reusable thing instead of requiring repetitive use of two things. Perhaps a better concept would be "address". In fact, this is something that we have used within the framework a bit (e.g. someExchange://someRoutingKey). My other concern is that this seems to go against the whole goal of decoupling Exchanges from Queues. In fact, if I'm not mistaken, the idea of a "route" as something reusable on both the producer and consumer side would only apply to a Direct Exchange or a Topic Exchange that uses exact-matching rather than routing "patterns".

@benbennett
Copy link
Author

Using address might be a better idea if it fits better within the spring code base. The documentation for rabbit that I was inspired by was the section "Messaging Concepts: Binding and Routing" .
I don't follow the where the coupling of exchanges and queues is coming into play. Route has exchanged tied to it. The queue class hasn't changed , only when you create a binding do they become a coupled entity , which is alright because binding depends on both an exchange and a queue(or exchange in the latest rabbitmq). Also routes could be used with FanoutExchanges also , the routing key is just set to "", which was the default before.

@dsyer
Copy link
Member

dsyer commented Nov 30, 2010

I agree with Ben in that I don't understand the point about coupling queues and exchanges. Also happy to change names. But I take Mark's point about a route (keeping that as a working name for now) having only a fixed routing key if used by a producer, but being empty or a wildcard if used in a binding. That does seem like the design is wrong somewhere.

You will have heard me say this before, but maybe we (i.e. Ben) should elaborate in some more detail about the use case, and we can look at the motivation and not the abstract design.

@benbennett
Copy link
Author

Maybe I haven't been clear. Route was just addition in where multiple bindings needed the same routing information, route isn't need all the time. It is just when I was coding up pub/sub portion of the system I am working on, the usage of having to create bindings for each queue and repeating the same information with no checks when the application started up was error prone.
Example in the system I am working on we are using the messing system to control events. One is event is sync message that tells the different subsystems that a connection is available , go attempt to sync any data you have with the central system. Say the routing key is sync.txready .

The are queues per subsystem, there is only one sync exchange. There is a common source tree that contains exchanges and routes. So the underlying subsystems uses the parents application context to get routing and exchange information without needing to know the exact routing keys or exchanges.

The introduction of the route was also driven by the spring-integration-amqp source. In the current example the strings si.test.exchange and si.test.queue were repeated 3 times. The routing key was repeated twice.
Now the code only has one declaration of important string values, the rest are just references to beans. Thus if there is simple typo in the example code you quickly know why instead of searching for some typo along the way. The code also allows you to change the routing key , queue and exchange for the entire system with the change of 3 strings.

@dsyer
Copy link
Member

dsyer commented Dec 1, 2010

I'm not sure I understood the use case properly. Let's focus on that and leave aside the configuration and abstraction design.

It sounds like a fanout exchange (you want to broadcast the sync message to all interested parties) specific to the message type? Which is why I'm lost because a fanout doesn't require a routing key. What are the producer and consumer semantics - how many messages does a producer send, and how many consumers receive it?

@benbennett
Copy link
Author

I am getting a little confused also. I don't know if you guys want any of the changes, or what you want the changes to be, so I am limbo right now. I don't know what your saying about the exchanges are you not understanding what the different types of exchanges do or the use cases for these changes?
On off-topic , fallacy ridden point , seems the rabbitmq developers are calling things route behind the scenes, their is rabbit_durable_route.DCD file behind the scenes.

@dsyer
Copy link
Member

dsyer commented Dec 1, 2010

It's not a question of not wanting changes. We are having a discussion about the design of your proposal, and you can contribute to the discussion best by keeping it on track. As far as your question goes: I think I understand what a fanout exchange is, but I'm happy to be corrected if you detect a misconception, especially if it's relevant to your use case. I asked if you were using a fanout because I'm still confused about your application. It still seems like the best way to nail the requirement for an enhancement (or if there is one) to get more detail from you about how you would use it.

@benbennett
Copy link
Author

Trying to simplify the use case.
We are not using Fanout Exchanges, only Direct and Topic exchanges at this point. Most are direct.
The use of direct exchanges with route elements is being used to create pub/sub architecture.Either the publisher is 1:1 or 1:N for absolute . I think is very similar to the publish-subscribe-channel element in spring-integration, or generally trying to set up something along the lines of channel that is in spring-integration. I also wanted to leverage spring injection such that the application context fails to load if something isn't right, ie typo , queue not create, exchange doesn't exist , invalid routing key.

@markfisher
Copy link
Contributor

I would say if you are really trying to emulate the behavior of a publish-subscribe-channel, then Fanout Exchange is the closest match. Then, the "routing key" is not even relevant, and you would have what you want: a single "address" - in this case, the exchange name.

However, I could be missing something. Can you elaborate on the limitations of Fanout Exchange that led you down this path of using Direct Exchanges, which exhibit point-to-point semantics, when you are actually aiming for pub-sub semantics?

@benbennett
Copy link
Author

Flexibility, because both direct and fanout exchanges can be simulated using topic exchanges. The performance is O(N) right now which isn't bad because we don't have bunch of queues. I see that fanout is better choice for pub/sub but I want to keep it flexible.
I really wish I could keep the ideas of types of exchanges hidden from the developer. I am really just trying to make is easier to use for my developers. If I throughout you can use these three types exchanges in these different situations, they we become confused and make mistakes. I need to make the use of rabbitmq dead simple to use in spring app just like spring-integration. They have a little problems with spring-integration but they are lost in rabbit, so I am trying to make it easier to use so I don't have to consistently fix their work.

@benbennett
Copy link
Author

The more I think about it.
Is there anyway to either extend spring-integration or somewhere to use the same elements as used in spring-integration but to allow implementation reference. Something where you declare you channel either direct or publish-subscribe and it figures things out for you. You might have to give reference to a rabbitmq connection bean and some addition information.

@markfisher
Copy link
Contributor

If I understand correctly, you are asking about an AMQP-backed MessageChannel option as opposed to the channel-adapters. Is that correct? We are planning to do that, and it would be similar to our JMS-backed channels: http://static.springsource.org/spring-integration/reference/htmlsingle/#jms-channel

@benbennett
Copy link
Author

The jms section would be great down the road. The question is what do to in the mean time. I can see if you don't want the route class I can deal with pulling it out , it makes some steps easier.I just didn't care for the repetition of strings in the unit tests and examples it is just too error prone. I also have pet peeve when writing code. If you end up repeating member names, methods or character string three or more times you should probably re-factor the code.

I could write the code to parse bindings,queues,routingkeys and exchanges to allow for the referencing of beans instead of strings or property variables.

@markfisher
Copy link
Contributor

I agree that duplication of strings is bad. What I'm trying to understand is why duplication of strings is really necessary in your particular case. The idea about AMQP-backed channels was an attempt to respond to your last question (or at least what I thought you were asking). However, it seems to me that the most important thing for right now is to really get to the bottom of why you have duplication in the first place, and I think the rationale for NOT using Fanout Exchanges is a key part of that discussion. Based on your previous comments, you want to make it so that developers are not aware of exchange types, and I understand that. On the broker, a Fanout Exchange "x" can be defined. Then, developers only need to know that they send to "x". They do not need to know that it's a Fanout Exchange.

@benbennett
Copy link
Author

The applies to any case of using the spring-amqp and/or spring-integration-amqp. From the current state of the code in both projects it is difficult to leverage basic spring dependency injection without using variable injections or some other work around(abstract beans etc). Even your own example in spring-integration-amqp I have reduce the string declarations to 3 down from 8, the exchange, queue, route are declare once, the rest just reference those declarations.
Furthermore I can declare queues , exchanges, and bindings but I don't have anyway for them to reference each other if need be nor to methods that use these objects reference them, they all use strings. A binding depends on queue and exchange , but I cannot reference either queue, exchange, routing key object(don't think there is even a routingkey object). The methods that send messages use exchanges, and routing keys but I cannot use routes or exchange objects, I have to use a string, which is little confusing since almost all the strings indirectly are represented as an object in the code base.
I guess I am seeing the use of strings is creating a weakly type methods when I am suggesting more strongly typed methods. I don't know if the use of strings was by design, if so I trying to understand the design.
Whether you want a Route class that has an exchange in it or just a routing key instead and not have the binding class extend from it is fine with me. I just want to get rid of having strings everywhere and so I can put them as beans in my application context and have them reference one another.

As side note my majors are in theoretical math and computer science. I have a tendency to make everything more abstract and it confuses people sometimes. I would rather write everything in set notation , then explain in words.

@markfisher
Copy link
Contributor

Well, I understand the general issue. I guess one issue with using references instead of just names is that it would require someone to create a Queue and/or Exchange instance within the application. The main problem I have with that is that it actually might also require them to know much more about the underlying instance than they really need to. Let me see if I can explain very briefly...

If I want to publish a Message to a Fanout Exchange named "x", then the ONLY thing I need to know is "x" is the name. If OTOH, I need to use a reference for any exchange (e.g. for a outbound-channel-adapter), then I need to create that Exchange or Queue instance. At that point, if the instance has already been declared on the broker (the most common case), then I also need to know all of its properties (durable, exclusive, auto-delete, etc), because a declare command requires all of the details. The "publish" and "consume" commands are much simpler in that regard. I think for the majority of users, they will just want to provide the exchange name on the adapter rather than providing a bean definition that matches all of those properties. I tend to see all of those details as "admin" concerns whereas the end-user just needs to know the names and possibly some set of routing keys.

@benbennett
Copy link
Author

I left in the amqp references to still allow the use of strings for routing keys,exchanges, and routing keys. I didn't pull that out, I added attributes that allow you to reference a queue, binding, route, or exchange but the user can just still use hard coded strings if they would like. I left it in there because of the use case you are describing.

I am having a hard time following your point about the not wanting to have the details behind the scenes, and just having strings. All the examples/test cases in the rabbitlib create queues,exchanges,and bindings, none expect that they are already created. All your own code creates them. It is also very similar to a hibernate property hibernate.hbm2ddl.auto and behind the scenes in rabbit all queues,exchanges ,bindings are being mapped to tables, similar to sql database. A production environment you wouldn't want to mess with making the exchanges etc, but in a testing and coding env , I want them to be made, so I don't have to worry about it. I would also want to test the setup exactly how it would appear in the production system, and not have a miss-match from dev/test to production.

@benbennett
Copy link
Author

What do you want to do guys? I am going the forward and will attempt see how the route class can be substituted for the address class.
I am also trying to take the jms code and use it as a model for the spring-integration-amqp.
Just wondering if this is in work already or if there can be some agreed xml spec so even if I write the underly code, I could use the real release eventually.

@benbennett
Copy link
Author

re submitting sometime later

garyrussell added a commit to garyrussell/spring-amqp that referenced this pull request Jul 19, 2012
Rabbit can piggy-back confirms - for example, if seq 1, 2, 3 are
sent, it is possible to receive ack spring-projects#3 with 'multiple' set. These
means 1, 2, and 3 are acked.

This worked fine with just one listener. However, if two or
more listeners (e.g. rabbit templates) are attached, only the
listener for ack spring-projects#3 is notified (regardless of whether all
the acks belong to it).

The PublisherCallbackChannel maintains two maps: seq-to-listener
and listener-to-map(seq-to-correlation).

This fixes the problem by first finding all the listeners that
have pending confirms at or below the sequence number; and then
uses the second map to send the confirms to the appropriate
listener.
olegz pushed a commit to olegz/spring-amqp that referenced this pull request Jul 19, 2012
Rabbit can piggy-back confirms - for example, if seq 1, 2, 3 are
sent, it is possible to receive ack spring-projects#3 with 'multiple' set. These
means 1, 2, and 3 are acked.

This worked fine with just one listener. However, if two or
more listeners (e.g. rabbit templates) are attached, only the
listener for ack spring-projects#3 is notified (regardless of whether all
the acks belong to it).

The PublisherCallbackChannel maintains two maps: seq-to-listener
and listener-to-map(seq-to-correlation).

This fixes the problem by first finding all the listeners that
have pending confirms at or below the sequence number; and then
uses the second map to send the confirms to the appropriate
listener.

AMQP-255 Polishing

PR Comments
olegz pushed a commit to olegz/spring-amqp that referenced this pull request Jul 19, 2012
Rabbit can piggy-back confirms - for example, if seq 1, 2, 3 are
sent, it is possible to receive ack spring-projects#3 with 'multiple' set. These
means 1, 2, and 3 are acked.

This worked fine with just one listener. However, if two or
more listeners (e.g. rabbit templates) are attached, only the
listener for ack spring-projects#3 is notified (regardless of whether all
the acks belong to it).

The PublisherCallbackChannel maintains two maps: seq-to-listener
and listener-to-map(seq-to-correlation).

This fixes the problem by first finding all the listeners that
have pending confirms at or below the sequence number; and then
uses the second map to send the confirms to the appropriate
listener.

AMQP-255 Polishing

PR Comments
artembilan pushed a commit that referenced this pull request Aug 15, 2024
Bumps [io.micrometer:micrometer-tracing-bom](https://github.com/micrometer-metrics/tracing-commercial) from 1.0.12 to 1.0.14.
- [Commits](https://github.com/micrometer-metrics/tracing-commercial/commits)

---
updated-dependencies:
- dependency-name: io.micrometer:micrometer-tracing-bom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants