torquebox-messaging 1.1.1-java → 2.0.0.beta1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/lib/gem_hook.rb +26 -3
  2. data/lib/torquebox-messaging.jar +0 -0
  3. data/lib/torquebox-messaging.rb +2 -32
  4. data/lib/torquebox/messaging.rb +16 -0
  5. data/lib/torquebox/messaging/backgroundable.rb +32 -15
  6. data/lib/torquebox/messaging/backgroundable_processor.rb +32 -0
  7. data/lib/torquebox/messaging/connection.rb +127 -0
  8. data/lib/torquebox/messaging/connection_factory.rb +70 -0
  9. data/lib/torquebox/messaging/const_missing.rb +28 -0
  10. data/lib/torquebox/messaging/core.rb +27 -0
  11. data/lib/torquebox/messaging/destination.rb +68 -111
  12. data/lib/torquebox/messaging/ext/javax_jms_queue_browser.rb +1 -3
  13. data/lib/torquebox/messaging/future_responder.rb +10 -7
  14. data/lib/torquebox/messaging/json_message.rb +50 -0
  15. data/lib/torquebox/messaging/marshal_base64_message.rb +44 -0
  16. data/lib/torquebox/messaging/marshal_message.rb +43 -0
  17. data/lib/torquebox/messaging/message.rb +123 -0
  18. data/lib/torquebox/messaging/message_processor.rb +47 -0
  19. data/lib/torquebox/messaging/processor_wrapper.rb +38 -0
  20. data/lib/torquebox/messaging/queue.rb +61 -0
  21. data/lib/torquebox/messaging/session.rb +168 -0
  22. data/lib/torquebox/messaging/task.rb +7 -6
  23. data/lib/torquebox/messaging/text_message.rb +36 -0
  24. data/lib/torquebox/messaging/topic.rb +54 -0
  25. data/lib/torquebox/messaging/xa_connection.rb +58 -0
  26. data/lib/torquebox/messaging/xa_connection_factory.rb +59 -0
  27. data/lib/torquebox/messaging/xa_session.rb +30 -0
  28. data/licenses/lgpl-2.1.txt +7 -9
  29. data/spec/backgroundable_spec.rb +17 -24
  30. data/spec/destination_spec.rb +31 -318
  31. data/spec/future_responder_spec.rb +1 -1
  32. data/spec/json_message_spec.rb +50 -0
  33. data/spec/message_processor_spec.rb +54 -0
  34. data/spec/message_spec.rb +114 -0
  35. data/spec/task_spec.rb +11 -5
  36. metadata +38 -77
  37. data/lib/acl-spi-3.0.0.CR2.jar +0 -0
  38. data/lib/activation-1.1.jar +0 -0
  39. data/lib/authorization-spi-3.0.0.CR2.jar +0 -0
  40. data/lib/dtdparser-1.21.jar +0 -0
  41. data/lib/hornetq-core-2.1.2.Final.jar +0 -0
  42. data/lib/hornetq-jms-client-2.1.2.Final.jar +0 -0
  43. data/lib/hornetq-logging-2.1.2.Final.jar +0 -0
  44. data/lib/identity-spi-3.0.0.CR2.jar +0 -0
  45. data/lib/jaxb-api-2.1.9.jar +0 -0
  46. data/lib/jboss-common-core-2.2.17.GA.jar +0 -0
  47. data/lib/jboss-jms-api_1.1_spec-1.0.0.Final.jar +0 -0
  48. data/lib/jboss-logging-3.0.0.Beta4.jar +0 -0
  49. data/lib/jboss-reflect-2.2.0.GA.jar +0 -0
  50. data/lib/jbosssx-bare-3.0.0.CR2.jar +0 -0
  51. data/lib/jbossxacml-2.0.5.CR1.jar +0 -0
  52. data/lib/jbossxb-2.0.3.GA.jar +0 -0
  53. data/lib/jnp-client-5.0.5.Final.jar +0 -0
  54. data/lib/jruby-complete-1.6.3.jar +0 -0
  55. data/lib/netty-3.2.1.Final.jar +0 -0
  56. data/lib/picketbox-acl-impl-3.0.0.CR2.jar +0 -0
  57. data/lib/picketbox-bare-3.0.0.CR2.jar +0 -0
  58. data/lib/picketbox-identity-impl-3.0.0.CR2.jar +0 -0
  59. data/lib/picketbox-spi-bare-3.0.0.CR2.jar +0 -0
  60. data/lib/torquebox-base-core.jar +0 -0
  61. data/lib/torquebox-base-metadata.jar +0 -0
  62. data/lib/torquebox-base-spi.jar +0 -0
  63. data/lib/torquebox-mc-support.jar +0 -0
  64. data/lib/torquebox-messaging-core.jar +0 -0
  65. data/lib/torquebox/messaging/client.rb +0 -75
  66. data/lib/torquebox/messaging/ext/javax_jms_session.rb +0 -153
  67. data/lib/xercesImpl-2.9.1.jar +0 -0
  68. data/lib/xml-apis-1.3.04.jar +0 -0
  69. data/spec/client_spec.rb +0 -116
  70. data/spec/dispatcher-queues.yml +0 -4
  71. data/spec/dispatcher_not_running.rb +0 -54
  72. data/spec/ext/java_jmx_session_spec.rb +0 -71
  73. data/spec/queues.yml +0 -6
data/lib/gem_hook.rb CHANGED
@@ -15,9 +15,32 @@
15
15
  # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
16
16
  # 02110-1301 USA, or see the FSF site: http://www.fsf.org.
17
17
 
18
- require 'torquebox/messaging/client'
19
- require 'torquebox/messaging/task'
18
+ begin
19
+ javax.jms::Session
20
+ rescue
21
+ # $stderr.puts "Will not load torquebox-messaging: javax.jms.* cannot be loaded"
22
+ return
23
+ end
24
+
25
+ require 'torquebox/messaging/connection_factory'
26
+ require 'torquebox/messaging/connection'
27
+ require 'torquebox/messaging/session'
28
+
29
+ require 'torquebox/messaging/message'
30
+ require 'torquebox/messaging/json_message'
31
+ require 'torquebox/messaging/text_message'
32
+ require 'torquebox/messaging/marshal_base64_message'
33
+ require 'torquebox/messaging/marshal_message'
34
+
20
35
  require 'torquebox/messaging/destination'
36
+ require 'torquebox/messaging/queue'
37
+ require 'torquebox/messaging/topic'
38
+
39
+ require 'torquebox/messaging/xa_connection_factory'
40
+ require 'torquebox/messaging/xa_connection'
41
+ require 'torquebox/messaging/xa_session'
42
+
21
43
  require 'torquebox/messaging/message_processor'
44
+ require 'torquebox/messaging/task'
22
45
  require 'torquebox/messaging/backgroundable'
23
- require 'torquebox/messaging/web_sockets_processor'
46
+ require 'torquebox/messaging/processor_wrapper'
Binary file
@@ -1,40 +1,10 @@
1
1
  module TorqueboxMessaging
2
- VERSION = '1.1.1'
3
- MAVEN_VERSION = '1.1.1'
2
+ VERSION = '2.0.0.beta1'
3
+ MAVEN_VERSION = '2.0.0.beta1'
4
4
  end
5
5
  begin
6
6
  require 'java'
7
7
  require File.dirname(__FILE__) + '/torquebox-messaging.jar'
8
- require File.dirname(__FILE__) + '/jboss-jms-api_1.1_spec-1.0.0.Final.jar'
9
- require File.dirname(__FILE__) + '/hornetq-core-2.1.2.Final.jar'
10
- require File.dirname(__FILE__) + '/hornetq-jms-client-2.1.2.Final.jar'
11
- require File.dirname(__FILE__) + '/netty-3.2.1.Final.jar'
12
- require File.dirname(__FILE__) + '/hornetq-logging-2.1.2.Final.jar'
13
- require File.dirname(__FILE__) + '/jboss-logging-3.0.0.Beta4.jar'
14
- require File.dirname(__FILE__) + '/jnp-client-5.0.5.Final.jar'
15
- require File.dirname(__FILE__) + '/jboss-common-core-2.2.17.GA.jar'
16
- require File.dirname(__FILE__) + '/jboss-reflect-2.2.0.GA.jar'
17
- require File.dirname(__FILE__) + '/jbossxb-2.0.3.GA.jar'
18
- require File.dirname(__FILE__) + '/xml-apis-1.3.04.jar'
19
- require File.dirname(__FILE__) + '/xercesImpl-2.9.1.jar'
20
- require File.dirname(__FILE__) + '/dtdparser-1.21.jar'
21
- require File.dirname(__FILE__) + '/activation-1.1.jar'
22
- require File.dirname(__FILE__) + '/jaxb-api-2.1.9.jar'
23
- require File.dirname(__FILE__) + '/torquebox-base-spi.jar'
24
- require File.dirname(__FILE__) + '/jruby-complete-1.6.3.jar'
25
- require File.dirname(__FILE__) + '/torquebox-base-metadata.jar'
26
- require File.dirname(__FILE__) + '/torquebox-base-core.jar'
27
- require File.dirname(__FILE__) + '/picketbox-bare-3.0.0.CR2.jar'
28
- require File.dirname(__FILE__) + '/identity-spi-3.0.0.CR2.jar'
29
- require File.dirname(__FILE__) + '/authorization-spi-3.0.0.CR2.jar'
30
- require File.dirname(__FILE__) + '/acl-spi-3.0.0.CR2.jar'
31
- require File.dirname(__FILE__) + '/jbosssx-bare-3.0.0.CR2.jar'
32
- require File.dirname(__FILE__) + '/picketbox-identity-impl-3.0.0.CR2.jar'
33
- require File.dirname(__FILE__) + '/picketbox-spi-bare-3.0.0.CR2.jar'
34
- require File.dirname(__FILE__) + '/picketbox-acl-impl-3.0.0.CR2.jar'
35
- require File.dirname(__FILE__) + '/jbossxacml-2.0.5.CR1.jar'
36
- require File.dirname(__FILE__) + '/torquebox-mc-support.jar'
37
- require File.dirname(__FILE__) + '/torquebox-messaging-core.jar'
38
8
  rescue LoadError
39
9
  puts 'JAR-based gems require JRuby to load. Please visit www.jruby.org.'
40
10
  raise
@@ -1,3 +1,19 @@
1
+ # Copyright 2008-2011 Red Hat, Inc, and individual contributors.
2
+ #
3
+ # This is free software; you can redistribute it and/or modify it
4
+ # under the terms of the GNU Lesser General Public License as
5
+ # published by the Free Software Foundation; either version 2.1 of
6
+ # the License, or (at your option) any later version.
7
+ #
8
+ # This software is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
+ # Lesser General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU Lesser General Public
14
+ # License along with this software; if not, write to the Free
15
+ # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
16
+ # 02110-1301 USA, or see the FSF site: http://www.fsf.org.
1
17
 
2
18
  begin
3
19
  require 'torquebox-messaging'
@@ -1,38 +1,53 @@
1
1
  # Copyright 2008-2011 Red Hat, Inc, and individual contributors.
2
- #
2
+ #
3
3
  # This is free software; you can redistribute it and/or modify it
4
4
  # under the terms of the GNU Lesser General Public License as
5
5
  # published by the Free Software Foundation; either version 2.1 of
6
6
  # the License, or (at your option) any later version.
7
- #
7
+ #
8
8
  # This software is distributed in the hope that it will be useful,
9
9
  # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
11
  # Lesser General Public License for more details.
12
- #
12
+ #
13
13
  # You should have received a copy of the GNU Lesser General Public
14
14
  # License along with this software; if not, write to the Free
15
15
  # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
16
16
  # 02110-1301 USA, or see the FSF site: http://www.fsf.org.
17
17
 
18
- require 'torquebox/messaging/destination'
18
+ require 'torquebox/messaging/queue'
19
19
  require 'torquebox/messaging/future'
20
+ require 'torquebox/messaging/task'
20
21
  require 'torquebox/messaging/future_status'
22
+ require 'torquebox/injectors'
21
23
 
22
24
  module TorqueBox
23
25
  module Messaging
26
+
27
+ # Backgroundable provides mechanism for executing an object's
28
+ # methods asynchronously.
24
29
  module Backgroundable
25
30
  def self.included(base)
26
31
  base.extend(ClassMethods)
27
32
  base.send(:include, FutureStatus)
28
33
  end
29
34
 
35
+ # Allows you to background any method that has not been marked
36
+ # as a backgrounded method via {ClassMethods#always_background}.
37
+ # @param [Hash] options that are passed through to
38
+ # {TorqueBox::Messaging::Destination#publish}
39
+ # @return [Future]
30
40
  def background(options = { })
31
41
  BackgroundProxy.new(self, options)
32
42
  end
33
43
 
34
44
  module ClassMethods
35
45
 
46
+ # Marks methods to always be backgrounded. Takes one or more
47
+ # method symbols, and an optional options hash as the final
48
+ # argument. The options allow you to set publish options for
49
+ # each call.
50
+ # see TorqueBox::Messaging::Destination#publish
36
51
  def always_background(*methods)
37
52
  options = methods.last.is_a?(Hash) ? methods.pop : {}
38
53
  @__backgroundable_methods ||= {}
@@ -103,21 +118,23 @@ module TorqueBox
103
118
  end
104
119
 
105
120
  module Util
106
- QUEUE_NAME = "/queues/torquebox/#{ENV['TORQUEBOX_APP_NAME']}/backgroundable"
107
-
121
+ extend TorqueBox::Injectors
122
+
108
123
  class << self
109
124
  def publish_message(receiver, method, args, options = { })
110
- queue = Queue.new(QUEUE_NAME)
125
+ queue_name = Task.queue_name( "torquebox_backgroundable" )
126
+ queue = Queue.new( queue_name )
111
127
  future = Future.new( queue )
112
- queue.publish({:receiver => receiver,
113
- :future_id => future.correlation_id,
114
- :future_queue => QUEUE_NAME,
115
- :method => method,
116
- :args => args}, options)
117
-
128
+ options[:encoding] = :marshal
129
+ queue.publish( {:receiver => receiver,
130
+ :future_id => future.correlation_id,
131
+ :future_queue => queue_name,
132
+ :method => method,
133
+ :args => args}, options )
134
+
118
135
  future
119
- rescue javax.naming.NameNotFoundException => ex
120
- raise RuntimeError.new("The backgroundable queue is not available. Did you disable it by setting its concurrency to 0?")
136
+ rescue javax.jms.InvalidDestinationException => ex
137
+ raise RuntimeError.new("The Backgroundable queue is not available. Did you disable it by setting its concurrency to 0?")
121
138
  end
122
139
 
123
140
  def instance_methods_include?(klass, method)
@@ -0,0 +1,32 @@
1
+ # Copyright 2008-2011 Red Hat, Inc, and individual contributors.
2
+ #
3
+ # This is free software; you can redistribute it and/or modify it
4
+ # under the terms of the GNU Lesser General Public License as
5
+ # published by the Free Software Foundation; either version 2.1 of
6
+ # the License, or (at your option) any later version.
7
+ #
8
+ # This software is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
+ # Lesser General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU Lesser General Public
14
+ # License along with this software; if not, write to the Free
15
+ # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
16
+ # 02110-1301 USA, or see the FSF site: http://www.fsf.org.
17
+
18
+ require 'torquebox/messaging/message_processor'
19
+ require 'torquebox/messaging/const_missing'
20
+ require 'torquebox/messaging/future_responder'
21
+
22
+ module TorqueBox
23
+ module Messaging
24
+ class BackgroundableProcessor < MessageProcessor
25
+ def on_message(hash)
26
+ FutureResponder.new( Queue.new( hash[:future_queue] ), hash[:future_id] ).respond do
27
+ hash[:receiver].send(hash[:method], *hash[:args])
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,127 @@
1
+ # Copyright 2008-2011 Red Hat, Inc, and individual contributors.
2
+ #
3
+ # This is free software; you can redistribute it and/or modify it
4
+ # under the terms of the GNU Lesser General Public License as
5
+ # published by the Free Software Foundation; either version 2.1 of
6
+ # the License, or (at your option) any later version.
7
+ #
8
+ # This software is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
+ # Lesser General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU Lesser General Public
14
+ # License along with this software; if not, write to the Free
15
+ # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
16
+ # 02110-1301 USA, or see the FSF site: http://www.fsf.org.
17
+
18
+ require 'torquebox/injectors'
19
+ require 'torquebox/messaging/session'
20
+
21
+ module TorqueBox
22
+ module Messaging
23
+ class Connection
24
+ include TorqueBox::Injectors
25
+
26
+ def initialize(jms_connection)
27
+ @jms_connection = jms_connection
28
+ @tm = inject('transaction-manager')
29
+ end
30
+
31
+ def start
32
+ @jms_connection.start
33
+ end
34
+
35
+ def close
36
+ @jms_connection.close
37
+ end
38
+
39
+ def client_id
40
+ @jms_connection.client_id
41
+ end
42
+
43
+ def client_id=(client_id)
44
+ @jms_connection.client_id = client_id
45
+ end
46
+
47
+ def with_session(enlist_tx = true)
48
+ if !enlist_tx || (current.nil? && !transaction)
49
+ begin
50
+ yield activate( create_session )
51
+ ensure
52
+ deactivate
53
+ end
54
+ elsif transaction && (!current.respond_to?(:transaction) || current.transaction != transaction)
55
+ yield activate( create_xa_session )
56
+ else
57
+ yield( current )
58
+ end
59
+ end
60
+
61
+ def sessions
62
+ Thread.current[:session] ||= []
63
+ end
64
+ def current
65
+ sessions.last
66
+ end
67
+ def activate(session)
68
+ sessions.push(session) && current
69
+ end
70
+ def deactivate
71
+ sessions.pop.close
72
+ end
73
+
74
+ def transaction
75
+ @tm && @tm.transaction
76
+ end
77
+
78
+ def create_xa_session
79
+ jms_session = @jms_connection.create_xa_session()
80
+ transaction.enlist_resource( jms_session.xa_resource )
81
+ session = TransactedSession.new( jms_session, transaction, self )
82
+ transaction.registerSynchronization( session )
83
+ session
84
+ end
85
+
86
+ def create_session
87
+ Session.new( @jms_connection.create_session( false, Session::AUTO_ACK ) )
88
+ end
89
+
90
+ end
91
+
92
+ class TransactedSession < Session
93
+ include javax.transaction.Synchronization
94
+ attr_reader :transaction
95
+
96
+ def initialize( jms_session, transaction, connection )
97
+ super( jms_session )
98
+ @transaction = transaction
99
+ @connection = connection.extend(TransactedConnection)
100
+ end
101
+
102
+ def close
103
+ # eat the close, until tx completes
104
+ end
105
+
106
+ def beforeCompletion
107
+ # required interface
108
+ end
109
+
110
+ def afterCompletion(status)
111
+ @connection.deactivate
112
+ @connection.complete!
113
+ end
114
+ end
115
+
116
+ module TransactedConnection
117
+ def close
118
+ super if @complete
119
+ end
120
+ def complete!
121
+ @complete = true
122
+ close
123
+ end
124
+ end
125
+
126
+ end
127
+ end
@@ -0,0 +1,70 @@
1
+ # Copyright 2008-2011 Red Hat, Inc, and individual contributors.
2
+ #
3
+ # This is free software; you can redistribute it and/or modify it
4
+ # under the terms of the GNU Lesser General Public License as
5
+ # published by the Free Software Foundation; either version 2.1 of
6
+ # the License, or (at your option) any later version.
7
+ #
8
+ # This software is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
+ # Lesser General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU Lesser General Public
14
+ # License along with this software; if not, write to the Free
15
+ # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
16
+ # 02110-1301 USA, or see the FSF site: http://www.fsf.org.
17
+
18
+ require 'java'
19
+ require 'torquebox/messaging/connection'
20
+
21
+ module TorqueBox
22
+ module Messaging
23
+ class ConnectionFactory
24
+
25
+ attr_reader :internal_connection_factory
26
+
27
+ def self.new(internal_connection_factory = nil)
28
+ return internal_connection_factory if internal_connection_factory.is_a?( ConnectionFactory )
29
+ super
30
+ end
31
+
32
+ def initialize(internal_connection_factory = nil)
33
+ @internal_connection_factory = internal_connection_factory
34
+ end
35
+
36
+ def with_new_connection(client_id = nil, &block)
37
+ connection = create_connection
38
+ connection.client_id = client_id
39
+ connection.start
40
+ begin
41
+ result = block.call( connection )
42
+ ensure
43
+ connection.close
44
+ end
45
+ return result
46
+ end
47
+
48
+ def create_connection()
49
+ if !@internal_connection_factory
50
+ # try to connect to HornetQ directly - this currently
51
+ # assumes localhost, and the default AS7 HQ Netty port of 5445
52
+ connect_opts = { org.hornetq.core.remoting.impl.netty.TransportConstants::PORT_PROP_NAME => 5445.to_java( java.lang.Integer ) }
53
+ transport_config =
54
+ org.hornetq.api.core.TransportConfiguration.new("org.hornetq.core.remoting.impl.netty.NettyConnectorFactory",
55
+ connect_opts)
56
+ @internal_connection_factory =
57
+ org.hornetq.api.jms.HornetQJMSClient.createConnectionFactoryWithoutHA( org.hornetq.api.jms::JMSFactoryType::CF, transport_config )
58
+ end
59
+
60
+ Connection.new( @internal_connection_factory.create_connection )
61
+ end
62
+
63
+
64
+ def to_s
65
+ "[ConnectionFactory: internal_connection_factory=#{internal_connection_factory}]"
66
+ end
67
+
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,28 @@
1
+ # Copyright 2008-2011 Red Hat, Inc, and individual contributors.
2
+ #
3
+ # This is free software; you can redistribute it and/or modify it
4
+ # under the terms of the GNU Lesser General Public License as
5
+ # published by the Free Software Foundation; either version 2.1 of
6
+ # the License, or (at your option) any later version.
7
+ #
8
+ # This software is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
+ # Lesser General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU Lesser General Public
14
+ # License along with this software; if not, write to the Free
15
+ # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
16
+ # 02110-1301 USA, or see the FSF site: http://www.fsf.org.
17
+
18
+ unless defined?(ActiveSupport)
19
+ $stderr.puts "WARN: Defining const_missing"
20
+
21
+ def Object.const_missing(name)
22
+ file = org.torquebox.core.util.StringUtils.underscore(name)
23
+ require file
24
+ result = const_get(name)
25
+ return result if result
26
+ raise "Class not found: #{name}"
27
+ end
28
+ end