107 |
Welcome to Grails
108 |
Congratulations, you have successfully started your first Grails application! At the moment
109 | this is the default page, feel free to modify it to either redirect to a controller or display whatever
110 | content you may choose. Below is a list of controllers that are currently deployed in this application,
111 | click on each to execute its default action:
112 |
113 |
114 |
Available Controllers:
115 |
116 |
117 | - ${c.fullName}
118 |
119 |
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/src/test/groovy/grails/plugins/rabbitmq/AutoQueueMessageListenerContainerSpec.groovy:
--------------------------------------------------------------------------------
1 | package grails.plugins.rabbitmq
2 |
3 | import com.rabbitmq.client.AMQP.Queue.DeclareOk
4 | import com.rabbitmq.client.Channel
5 | import com.rabbitmq.client.Consumer
6 | import org.grails.support.MockApplicationContext
7 | import org.springframework.amqp.core.DirectExchange
8 | import org.springframework.amqp.core.FanoutExchange
9 | import org.springframework.amqp.core.Queue
10 | import org.springframework.amqp.core.TopicExchange
11 | import org.springframework.amqp.rabbit.connection.Connection
12 | import org.springframework.amqp.rabbit.connection.ConnectionFactory
13 | import org.springframework.context.ApplicationContext
14 | import spock.lang.Shared
15 | import spock.lang.Specification
16 |
17 | class AutoQueueMessageListenerContainerSpec extends Specification {
18 | @Shared ApplicationContext mockContext = new MockApplicationContext()
19 | @Shared def mockAdminBean = new Expando()
20 | @Shared AutoQueueMessageListenerContainer testContainer = new AutoQueueMessageListenerContainer()
21 |
22 | void setupSpec() {
23 | mockContext = new MockApplicationContext()
24 | mockAdminBean = new Expando()
25 | testContainer = new AutoQueueMessageListenerContainer()
26 | }
27 |
28 | void setup() {
29 | mockContext.registerMockBean("adm", mockAdminBean)
30 | testContainer.applicationContext = mockContext
31 | testContainer.connectionFactory = [
32 | createConnection: {-> [
33 | createChannel: { boolean transactional -> [
34 | basicQos: {int qos -> },
35 | queueDeclarePassive: {String s -> [:] as DeclareOk },
36 | basicConsume: {String s, boolean b, Consumer c -> "Test" }
37 | ] as Channel },
38 | close: {-> /* Do nothing */ }
39 | ] as Connection }
40 | ] as ConnectionFactory
41 | }
42 |
43 | /**
44 | * Make sure that a temporary queue is created and that it is bound to the
45 | * topic exchange with the given name.
46 | */
47 | void testDoStartWithTopicExchangeName() {
48 | when:
49 | def declareBindingCalled = false
50 | def tempQueueName = "dummy-1234"
51 | def exchangeName = "my.topic"
52 |
53 | mockAdminBean.declareQueue = {-> return new Queue(tempQueueName) }
54 | mockAdminBean.declareBinding = { binding ->
55 | assert binding.exchange == exchangeName
56 | assert binding.destination == tempQueueName
57 | assert binding.routingKey == '#'
58 | declareBindingCalled = true
59 | }
60 |
61 | mockContext.registerMockBean(exchangeName, new TopicExchange(exchangeName))
62 |
63 | testContainer.exchangeBeanName = exchangeName
64 | testContainer.doStart()
65 |
66 | then: "declareBinding() not called"
67 | declareBindingCalled
68 | }
69 |
70 |
71 | /**
72 | * Make sure that a temporary queue is created and that it is bound to the
73 | * topic exchange with the given name and the given routing key.
74 | */
75 | void testDoStartWithTopicExchangeAndRoutingKey() {
76 | when:
77 | def declareBindingCalled = false
78 | def tempQueueName = "dummy-1235"
79 | def exchangeName = "another.topic"
80 | def routingKey = "my.routing.#"
81 |
82 | mockAdminBean.declareQueue = {-> return new Queue(tempQueueName) }
83 | mockAdminBean.declareBinding = { binding ->
84 | assert binding.exchange == exchangeName
85 | assert binding.destination == tempQueueName
86 | assert binding.routingKey == routingKey
87 | declareBindingCalled = true
88 | }
89 |
90 | mockContext.registerMockBean(exchangeName, new TopicExchange(exchangeName))
91 |
92 | testContainer.exchangeBeanName = exchangeName
93 | testContainer.routingKey = routingKey
94 | testContainer.doStart()
95 |
96 | then: "declareBinding() not called"
97 | declareBindingCalled
98 | }
99 |
100 | /**
101 | * Make sure that a temporary queue is created and that it is bound to the
102 | * fanout exchange with the given name. The routing key should not be set.
103 | */
104 | void testDoStartWithFanoutExchangeName() {
105 | when:
106 | def declareBindingCalled = false
107 | def tempQueueName = "dummy-1234"
108 | def exchangeName = "my.fanout"
109 |
110 | mockAdminBean.declareQueue = {-> return new Queue(tempQueueName) }
111 | mockAdminBean.declareBinding = { binding ->
112 | assert binding.exchange == exchangeName
113 | assert binding.destination == tempQueueName
114 | assert !binding.routingKey
115 | declareBindingCalled = true
116 | }
117 |
118 | mockContext.registerMockBean(exchangeName, new FanoutExchange(exchangeName))
119 |
120 | testContainer.exchangeBeanName = exchangeName
121 | testContainer.doStart()
122 |
123 | then: "declareBinding() not called"
124 | declareBindingCalled
125 | }
126 |
127 | /**
128 | * Make sure that a temporary queue is created and that it is bound to the
129 | * fanout exchange with the given name. Even if a routing key is given, it
130 | * should be ignored.
131 | */
132 | void testDoStartWithFanoutExchangeAndRoutingKey() {
133 | when:
134 | def declareBindingCalled = false
135 | def tempQueueName = "dummy-1235"
136 | def exchangeName = "another.fanout"
137 | def routingKey = "my.routing.#"
138 |
139 | mockAdminBean.declareQueue = {-> return new Queue(tempQueueName) }
140 | mockAdminBean.declareBinding = { binding ->
141 | assert binding.exchange == exchangeName
142 | assert binding.destination == tempQueueName
143 | assert !binding.routingKey
144 | declareBindingCalled = true
145 | }
146 |
147 | mockContext.registerMockBean(exchangeName, new FanoutExchange(exchangeName))
148 |
149 | testContainer.exchangeBeanName = exchangeName
150 | testContainer.routingKey = routingKey
151 | testContainer.doStart()
152 |
153 | then: "declareBinding() not called"
154 | declareBindingCalled
155 | }
156 |
157 | /**
158 | * No binding should be declared if the exchange is not a fanout or topic.
159 | */
160 | void testDoStartWithDirectExchangeName() {
161 | when:
162 | def declareBindingCalled = false
163 | def tempQueueName = "dummy-1234"
164 | def exchangeName = "my.direct"
165 |
166 | mockAdminBean.declareQueue = {-> return new Queue(tempQueueName) }
167 | mockAdminBean.declareBinding = { binding ->
168 | assert binding.exchange == exchangeName
169 | assert binding.destination == tempQueueName
170 | declareBindingCalled = true
171 | }
172 |
173 | mockContext.registerMockBean(exchangeName, new DirectExchange(exchangeName))
174 |
175 | testContainer.exchangeBeanName = exchangeName
176 | testContainer.doStart()
177 |
178 | then:"declareBinding() was not called"
179 | declareBindingCalled
180 | }
181 |
182 | }
183 |
--------------------------------------------------------------------------------
/src/test/groovy/grails/plugins/rabbitmq/RabbitGrailsPluginSpec.groovy:
--------------------------------------------------------------------------------
1 | package grails.plugins.rabbitmq
2 |
3 | import grails.core.GrailsApplication
4 | import grails.spring.BeanBuilder
5 | import grails.test.mixin.TestMixin
6 | import grails.test.mixin.support.GrailsUnitTestMixin
7 | import org.springframework.amqp.core.Binding
8 | import org.springframework.amqp.core.Binding.DestinationType
9 | import org.springframework.amqp.core.Queue
10 | import org.springframework.amqp.core.TopicExchange
11 | import org.springframework.beans.factory.NoSuchBeanDefinitionException
12 | import spock.lang.Specification
13 |
14 | /**
15 | * Test cases for main plugin file setup.
16 | */
17 | @TestMixin(GrailsUnitTestMixin)
18 | class RabbitGrailsPluginSpec extends Specification {
19 |
20 | def createPluginFileInstance(application = [:]) {
21 | def pluginClass = Class.forName('grails.plugins.rabbitmq.RabbitmqGrailsPlugin')
22 | def pluginInstance = pluginClass.newInstance()
23 | pluginInstance.metaClass.grailsApplication = application
24 | return pluginInstance
25 | }
26 |
27 | void testQueueAndExchangeSetup() {
28 | when:
29 | // mock up test configuration
30 | GrailsApplication application = grailsApplication
31 |
32 | application.metaClass.getServiceClasses = {
33 | return []
34 | }
35 | application.config = new ConfigSlurper().parse("""
36 | rabbitmq {
37 | connectionfactory {
38 | username = 'guest'
39 | password = 'guest'
40 | hostname = 'localhost'
41 | }
42 |
43 | queues = {
44 | exchange name: 'it_topic', durable: true, type: topic, autoDelete: false, {
45 | it_q1 autoDelete: false, durable: true, binding: '#', arguments: ['x-ha-policy' : 'all']
46 | }
47 | }
48 | }
49 | """)
50 | RabbitmqGrailsPlugin base = createPluginFileInstance(application)
51 |
52 | // run a spring builder to create context
53 | def bb = new BeanBuilder()
54 | bb.beans base.doWithSpring()
55 | def ctx = bb.createApplicationContext()
56 |
57 | // test topic
58 | def itTopic = ctx.getBean("grails.rabbit.exchange.it_topic")
59 |
60 | then:
61 | itTopic.getClass() == TopicExchange.class
62 | itTopic.durable
63 | !itTopic.autoDelete
64 | itTopic.name == 'it_topic'
65 |
66 | when:
67 | def itQ1 = ctx.getBean("grails.rabbit.queue.it_q1")
68 |
69 | then:
70 | itQ1.getClass() == Queue.class
71 | itQ1.name == "it_q1"
72 | itQ1.durable == true
73 | itQ1.autoDelete == false
74 | itQ1.arguments['x-ha-policy'] == 'all'
75 |
76 | // test binding
77 | when:
78 | def ibBind = ctx.getBean("grails.rabbit.binding.it_topic.it_q1")
79 |
80 | then:
81 | ibBind.getClass() == Binding.class
82 | ibBind.destination == 'it_q1'
83 | ibBind.exchange == 'it_topic'
84 | ibBind.routingKey == '#'
85 | ibBind.destinationType == DestinationType.QUEUE
86 | }
87 |
88 | void testServiceDisabling() {
89 | when:
90 | def mockBlueService = new MockQueueService(propertyName: 'blueService')
91 | def mockPinkService = new MockQueueService(propertyName: 'pinkService')
92 | def mockRedService = new MockSubscribeService(propertyName: 'redService')
93 | def mockTealService = new MockSubscribeService(propertyName: 'tealService')
94 |
95 | GrailsApplication application = grailsApplication
96 | application.metaClass.getServiceClasses = { -> [mockBlueService, mockRedService, mockPinkService, mockTealService] }
97 |
98 | application.config = new ConfigSlurper().parse("""
99 | rabbitmq {
100 | connectionfactory {
101 | username = 'guest'
102 | password = 'guest'
103 | hostname = 'localhost'
104 | }
105 | services {
106 | blueService {
107 | concurrentConsumers = 5
108 | disableListening = false
109 | }
110 | redService {
111 | concurrentConsumers = 4
112 | disableListening = true
113 | }
114 | }
115 | }
116 | """)
117 |
118 | RabbitmqGrailsPlugin base = createPluginFileInstance(application)
119 |
120 | def bb = new BeanBuilder()
121 | bb.beans {
122 | pinkService(MockQueueService) {
123 | propertyName = 'pinkService'
124 | }
125 | blueService(MockQueueService) {
126 | propertyName = 'blueService'
127 | }
128 | tealService(MockQueueService) {
129 | propertyName = 'tealService'
130 | }
131 | }
132 | bb.beans(base.doWithSpring())
133 | def ctx = bb.createApplicationContext()
134 |
135 | then:
136 | ctx.getBean('blueService_MessageListenerContainer').concurrentConsumers == 5
137 | ctx.getBean('pinkService_MessageListenerContainer').concurrentConsumers == 1
138 |
139 | when:
140 | shouldFail(NoSuchBeanDefinitionException) {
141 | ctx.getBean('redService_MessageListenerContainer')
142 | }
143 |
144 | then:
145 | ctx.getBean('tealService_MessageListenerContainer')
146 | }
147 |
148 | void testQueueFailureWhenMultipleListenersHaveSameName() {
149 | when:
150 | def blueService1 = new MockQueueService(propertyName: 'blueService')
151 | def blueService2 = new MockQueueService(propertyName: 'blueService')
152 | def redService1 = new MockSubscribeService(propertyName: 'redService')
153 | def redService2 = new MockSubscribeService(propertyName: 'redService')
154 |
155 | GrailsApplication application = grailsApplication
156 | application.metaClass.getServiceClasses = {
157 | return [blueService1, blueService2]
158 | }
159 |
160 | application.metaClass.config = new ConfigSlurper().parse("""
161 | rabbitmq {
162 | connectionfactory {
163 | username = 'guest'
164 | password = 'guest'
165 | hostname = 'localhost'
166 | }
167 | }
168 | """)
169 |
170 | RabbitmqGrailsPlugin queueServices = createPluginFileInstance(application)
171 | def msg = shouldFail(IllegalArgumentException) {
172 | new BeanBuilder().beans queueServices.doWithSpring()
173 | }
174 |
175 | then:
176 | msg.contains('blueService')
177 |
178 | when:
179 | application.metaClass.getServiceClasses = {
180 | return [redService1, redService2]
181 | }
182 |
183 | RabbitmqGrailsPlugin subscribeServices = createPluginFileInstance(application)
184 | msg = shouldFail(IllegalArgumentException) {
185 | new BeanBuilder().beans subscribeServices.doWithSpring()
186 | }
187 |
188 | then:
189 | msg.contains('redService')
190 | }
191 | }
192 |
193 | class MockSubscribeService {
194 | static rabbitSubscribe = 'blueExchange'
195 | static transactional = false
196 | def propertyName
197 | def clazz = MockSubscribeService.class
198 | }
199 |
200 | class MockQueueService {
201 | static rabbitQueue = 'blueQueue'
202 | static transactional = false
203 | def propertyName
204 | def clazz = MockQueueService.class
205 | }
206 |
--------------------------------------------------------------------------------
/src/main/groovy/grails.plugins.rabbitmq/RabbitServiceConfigurer.groovy:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2012 the original author or authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package grails.plugins.rabbitmq
17 |
18 | import grails.util.GrailsClassUtils as GCU
19 | import org.slf4j.LoggerFactory
20 | import org.springframework.amqp.core.AcknowledgeMode
21 |
22 | import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer
23 | import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter
24 |
25 | /**
26 | * Configures the Spring beans required to set up a service as an AMQP
27 | * listener. The service must have either a static rabbitQueue
28 | * property declared or a static rabbitSubscribe one (but not
29 | * both). If neither of these exist, nothing is configured. Otherwise,
30 | * it will configure a {@link SimpleMessageListenerContainer} and an
31 | * associated {@link MessageListenerAdapter}.
32 | */
33 | class RabbitServiceConfigurer {
34 |
35 | private final static log = LoggerFactory.getLogger(RabbitServiceConfigurer)
36 |
37 | public static final String LISTENER_CONTAINER_SUFFIX = "_MessageListenerContainer"
38 | public static final String MESSAGE_CONVERTER_OPTION = "messageConverterBean"
39 |
40 | private static final String QUEUE = "rabbitQueue"
41 | private static final String SUBSCRIBE = "rabbitSubscribe"
42 | private static final Set ADAPTER_OPTIONS = [
43 | MESSAGE_CONVERTER_OPTION,
44 | "defaultListenerMethod",
45 | "encoding",
46 | "immediatePublish",
47 | "mandatoryPublish",
48 | "responseExchange",
49 | "responseRoutingKey" ]
50 |
51 | private serviceGrailsClass
52 | private rabbitConfig
53 | private type
54 | private options = [:]
55 | private beanBuilder
56 |
57 | /**
58 | * Fairly cheap configurer construction. Create new ones as needed. If
59 | * the given service isn't declared as an AMPQ/Rabbit listener, then
60 | * the object is successfully created, but {@link #isListener()} will
61 | * return