seamless 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/bin/seamless +36 -0
  2. data/lib/seamless.rb +70 -0
  3. data/lib/seamless/active_record_extension.rb +11 -0
  4. data/lib/seamless/dispatcher.rb +45 -0
  5. data/lib/seamless/message_broker.rb +12 -0
  6. data/lib/seamless/message_broker_controller.rb +11 -0
  7. data/lib/seamless/messagebroker/inmemory_message_broker.rb +45 -0
  8. data/lib/seamless/migration.rb +8 -0
  9. data/lib/seamless/model.rb +94 -0
  10. data/lib/seamless/service.rb +186 -0
  11. data/seamless/seamless_generator.rb +181 -0
  12. data/seamless/templates/README +24 -0
  13. data/seamless/templates/admin.html +166 -0
  14. data/seamless/templates/application.rb +7 -0
  15. data/seamless/templates/css/global.css +333 -0
  16. data/seamless/templates/css/seamless-admin.css +281 -0
  17. data/seamless/templates/css/seamless.css +632 -0
  18. data/seamless/templates/environment.rb +65 -0
  19. data/seamless/templates/generate +25 -0
  20. data/seamless/templates/images/arch.png +0 -0
  21. data/seamless/templates/images/arrow_undo.png +0 -0
  22. data/seamless/templates/images/cell_phone.png +0 -0
  23. data/seamless/templates/images/confirm.png +0 -0
  24. data/seamless/templates/images/deny.png +0 -0
  25. data/seamless/templates/images/dialog_close.gif +0 -0
  26. data/seamless/templates/images/dialog_close_hover.gif +0 -0
  27. data/seamless/templates/images/email.png +0 -0
  28. data/seamless/templates/images/exclamation.png +0 -0
  29. data/seamless/templates/images/indicator.gif +0 -0
  30. data/seamless/templates/images/indicator2.gif +0 -0
  31. data/seamless/templates/images/seamless.gif +0 -0
  32. data/seamless/templates/images/shadow.gif +0 -0
  33. data/seamless/templates/images/shadowAlpha.png +0 -0
  34. data/seamless/templates/images/small_star.png +0 -0
  35. data/seamless/templates/images/table_add.png +0 -0
  36. data/seamless/templates/images/table_delete.png +0 -0
  37. data/seamless/templates/images/table_edit.png +0 -0
  38. data/seamless/templates/images/table_go.png +0 -0
  39. data/seamless/templates/images/table_refresh.png +0 -0
  40. data/seamless/templates/images/tag.png +0 -0
  41. data/seamless/templates/images/warning.png +0 -0
  42. data/seamless/templates/images/wizard.gif +0 -0
  43. data/seamless/templates/images/work_phone.png +0 -0
  44. data/seamless/templates/index.html +171 -0
  45. data/seamless/templates/js/seamless-admin.js +255 -0
  46. data/seamless/templates/js/seamless.js +2755 -0
  47. data/seamless/templates/message_broker.rb +6 -0
  48. data/seamless/templates/message_broker_helper.rb +2 -0
  49. data/seamless/templates/robots.txt +2 -0
  50. data/seamless/templates/routes.rb +18 -0
  51. data/seamless/templates/seamless.xml +5 -0
  52. data/seamless/templates/server +4 -0
  53. data/seamless/templates/test_service.rb +13 -0
  54. data/service/USAGE +21 -0
  55. data/service/service_generator.rb +59 -0
  56. data/service/templates/service.rb +14 -0
  57. metadata +122 -0
data/bin/seamless ADDED
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ freeze = ARGV.any? { |option| %w(--freeze -f).include?(option) }
4
+ app_path = ARGV.first
5
+
6
+ require 'rubygems'
7
+ gem 'rails', '>= 1.2.3'
8
+
9
+ # find the path to RAILS
10
+ rails_gem = Gem.cache.search('rails').last
11
+ RAILS = rails_gem.full_gem_path
12
+
13
+ require RAILS + '/lib/ruby_version_check'
14
+ Signal.trap("INT") { puts; exit }
15
+
16
+ require RAILS + '/lib/rails/version'
17
+ if %w(--version -v).include? ARGV.first
18
+ puts "Rails #{Rails::VERSION::STRING}"
19
+ exit(0)
20
+ end
21
+
22
+
23
+ require RAILS + '/lib/rails_generator'
24
+ require 'rails_generator/scripts/generate'
25
+
26
+ Rails::Generator::Base.use_application_sources!
27
+
28
+ path = File.dirname(__FILE__) + '/../'
29
+
30
+ Rails::Generator::Base.sources << Rails::Generator::PathSource.new('', path)
31
+
32
+ # execute the seamless generator
33
+ Rails::Generator::Scripts::Generate.new.run(ARGV, {:generator => 'seamless', :source => path + 'seamless', :source_root => path + 'seamless', 'railspath'=>'../../rails-'+rails_gem.version.to_s})
34
+
35
+ Dir.chdir(app_path) { `rake rails:freeze:gems`; puts "froze" } if freeze
36
+
data/lib/seamless.rb ADDED
@@ -0,0 +1,70 @@
1
+ require 'json'
2
+ require 'action_controller'
3
+ require 'active_record'
4
+ require 'md5'
5
+
6
+ unless defined?(SEAMLESS_VERSION)
7
+
8
+ SEAMLESS_VERSION = '1.0.RC4.127'
9
+ SEAMLESS_SERVICES = Array.new
10
+
11
+ def model_name(name)
12
+ name.to_s.gsub(/[::]{2}/,'.').split('.').collect {|token| token.underscore }.join('.')
13
+ end
14
+
15
+ def silence_warnings
16
+ old_verbose, $VERBOSE = $VERBOSE, nil
17
+ yield
18
+ ensure
19
+ $VERBOSE = old_verbose
20
+ end
21
+
22
+ #
23
+ # OK, I realize this might be a little bad, but we include a few dependencies
24
+ # which print out stupid warnings on boot - we're going to suppress that
25
+ #
26
+ silence_warnings do
27
+ #
28
+ # require dependencies
29
+ #
30
+ Dir[File.dirname(__FILE__)+'/seamless/*.rb'].sort.each do |file|
31
+ require file[0..-4]
32
+ end
33
+ end
34
+
35
+ #
36
+ # trick to allow changing ActiveRecord logging stream
37
+ #
38
+ def log_to(stream)
39
+ ActiveRecord::Base.logger = Logger.new(stream)
40
+ ActiveRecord::Base.clear_active_connections!
41
+ end
42
+
43
+ #
44
+ # shortcut to turn on logging to stdout
45
+ #
46
+ def log_to_stdout
47
+ log_to STDOUT
48
+ end
49
+
50
+ #
51
+ # load any services
52
+ #
53
+ Dir[RAILS_ROOT + '/app/services/*_service.rb'].each do |file|
54
+ require file[0..-4]
55
+ name = Inflector.camelize(File.basename(file).chomp('_service.rb'))
56
+ klass = eval(name + 'Service')
57
+ mh = klass.instance
58
+ end
59
+
60
+ #
61
+ # register a message broker listener for admin seamless models
62
+ #
63
+ sam_proc = Proc.new do |req,type,obj|
64
+ resp = {'success'=>true, 'models'=> SEAMLESS_SERVICES}
65
+ Seamless::Dispatcher.instance.outgoing(req,'seamless.admin.models.response',resp)
66
+ end
67
+ Seamless::MessageBroker.register_listener('seamless.admin.models.request',sam_proc)
68
+
69
+ puts "=> Seam(less) on Rails #{SEAMLESS_VERSION}"
70
+ end
@@ -0,0 +1,11 @@
1
+ class ActiveRecord::Base
2
+ # Converts the ActiveRecord object to a JSON string. Only columns with simple
3
+ # types are included in this object so no recursion problems can occur.
4
+ def to_json(*a)
5
+ result = Hash.new
6
+ self.class.columns.each do |column|
7
+ result[column.name.to_sym] = self.send(column.name)
8
+ end
9
+ result.to_json(*a)
10
+ end
11
+ end
@@ -0,0 +1,45 @@
1
+ module Seamless
2
+ class Dispatcher
3
+ include Singleton
4
+
5
+ def serialize(session,body)
6
+ queue = session['_seamless_mq']
7
+ body << '<?xml version="1.0"?>'
8
+ body << "<messages version='1.0' sessionid='#{session.session_id}'>"
9
+ if queue
10
+ queue.each do |msg|
11
+ body << msg
12
+ end
13
+ session['_seamless_mq']=[]
14
+ end
15
+ body << '</messages>'
16
+ end
17
+
18
+ def outgoing(obj,type,message)
19
+ queue = obj['session']['_seamless_mq']
20
+ if !queue
21
+ queue = [];
22
+ obj['session']['_seamless_mq'] = queue
23
+ end
24
+ #TODO requestid
25
+ queue << "<message requestid='' direction='OUTGOING' datatype='JSON' type='#{type}'><![CDATA["
26
+ queue << message.to_json
27
+ queue << ']]></message>'
28
+ end
29
+
30
+ def incoming(session,request,response)
31
+ body = request.env['RAW_POST_DATA']
32
+ if body
33
+ node = REXML::Document.new(body)
34
+ node.root.each_element('//message') do |message|
35
+ requestid = message.attributes['requestid']
36
+ type = message.attributes['type']
37
+ text = message.text
38
+ msg = JSON.parse(text)
39
+ req = {'requestid'=>requestid, 'session'=>session}
40
+ MessageBroker.send(req,type,msg)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,12 @@
1
+ #
2
+ # require dependencies
3
+ #
4
+ Dir[File.dirname(__FILE__)+'/messagebroker/*.rb'].sort.each do |file|
5
+ require file[0..-4]
6
+ end
7
+
8
+
9
+ #
10
+ # set the default message broker to the basic in memory message broker
11
+ #
12
+ Seamless::MessageBroker = Seamless::InmemoryMessageBroker.instance
@@ -0,0 +1,11 @@
1
+
2
+ class MessageBrokerController < ActionController::Base
3
+ def process(request, response)
4
+ response.headers['Content-Type'] = 'text/xml'
5
+ session = request.session
6
+ Seamless::Dispatcher.instance.incoming(session,request,response)
7
+ Seamless::Dispatcher.instance.serialize(session,response.body)
8
+ session.close
9
+ response
10
+ end
11
+ end
@@ -0,0 +1,45 @@
1
+ #
2
+ # implementation of the message broker which only dispatches
3
+ # messages to internally registered listeners
4
+ #
5
+ #
6
+ module Seamless
7
+ class InmemoryMessageBroker
8
+ include Singleton
9
+
10
+ def initialize
11
+ @listeners={}
12
+ end
13
+
14
+ def register_listener(type,listener)
15
+ array = @listeners[type]
16
+ if array==nil
17
+ array=[]
18
+ @listeners[type]=array
19
+ end
20
+ array << listener
21
+ end
22
+
23
+ def unregister_listener(type,listener)
24
+ array = @listeners[type]
25
+ array.delete_if { |a| a == listener } if array
26
+ end
27
+
28
+
29
+ def send (req,type,obj)
30
+ array = @listeners[type]
31
+ if array
32
+ array.each do |listener|
33
+ begin
34
+ listener.call(req,type,obj)
35
+ rescue => error
36
+ puts $!
37
+ raise error
38
+ end
39
+ end
40
+ return true
41
+ end
42
+ false
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,8 @@
1
+ module ActiveRecord
2
+ class Migration
3
+ def self.add_foreign_key(from_table, from_column, to_table)
4
+ constraint_name = "fk_#{from_table}_#{from_column}"
5
+ execute %{ALTER TABLE #{from_table} ADD CONSTRAINT #{constraint_name} FOREIGN KEY (#{from_column}) REFERENCES #{to_table}(id)}
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,94 @@
1
+ begin
2
+ require 'cached_model'
3
+ end
4
+
5
+ module Seamless
6
+ class Model < ActiveRecord::Base
7
+ self.abstract_class = true
8
+
9
+ def Model.models
10
+ SEAMLESS_MODELS
11
+ end
12
+
13
+ def update
14
+ super
15
+ delete_cache if defined?(Cache)
16
+ end
17
+
18
+ def destroy
19
+ super
20
+ delete_cache if defined?(Cache)
21
+ end
22
+
23
+ private
24
+ def delete_cache
25
+ # delete the PK entry
26
+ Cache.delete "#{self.class.name}:id:#{quoted_id}"
27
+ end
28
+
29
+ class << self
30
+
31
+ def log(msg)
32
+ if logger && logger.level == Logger::DEBUG
33
+ logger.add(Logger::DEBUG, "Seamless::Model - #{msg}")
34
+ end
35
+ end
36
+
37
+ def find_by_sql(*args)
38
+
39
+ return super(args) if not defined?(Cache)
40
+ return super(args) unless CachedModel.use_memcache? or CachedModel.use_local_cache?
41
+
42
+ # don't cache result of :all
43
+ return super(args) if args.first =~ /^SELECT \* FROM #{table_name}$/
44
+
45
+ # don't do more complex finds
46
+ case args.first
47
+ when Array then
48
+ return super(args)
49
+ end
50
+
51
+ # some SQL ends with space
52
+ sql = args.first.chomp!(" ")
53
+ log "SQL: #{sql}"
54
+
55
+ if @model_regex==nil
56
+ @model_regex = /^SELECT \* FROM #{table_name} WHERE \(#{table_name}\.`#{primary_key}` = (\d+)\)$/
57
+ end
58
+
59
+ # see if this is a single query for the primary key
60
+ match = @model_regex.match(sql)
61
+ pk = nil
62
+
63
+ if match
64
+ # extract the primary key
65
+ pk = match[1]
66
+
67
+ # check to see if the record is in the DB with PK
68
+ if CachedModel.use_memcache?
69
+ result = Cache.get "#{name}:id:#{pk}"
70
+ if result
71
+ log "cache hit: #{name}.#{primary_key} -> #{pk}"
72
+ result.instance_variable_set(:@new_record,false)
73
+ return [result]
74
+ else
75
+ log "cache miss: #{name}.#{primary_key} -> #{pk}"
76
+ end
77
+ end
78
+ end
79
+
80
+ records = super(args)
81
+
82
+ if CachedModel.use_memcache?
83
+ # store in cache
84
+ if pk
85
+ Cache.put "#{name}:id:#{pk}", records.first, CachedModel.ttl
86
+ log "cache set: #{name}.#{primary_key} -> #{pk}"
87
+ end
88
+ end
89
+
90
+ records
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,186 @@
1
+
2
+ module Seamless
3
+ class Service
4
+ include Singleton
5
+
6
+ def Service.handles_message(messagetype,handler,responsetype)
7
+ name = messagetype.gsub(/[:]|[\.\s-]/,'_')
8
+ self.class_eval <<-end_eval
9
+ alias_method :_initialize_#{name}, :initialize
10
+ def initialize
11
+ _initialize_#{name}
12
+ register("#{messagetype}","#{handler}","#{responsetype}")
13
+ end
14
+ end_eval
15
+ end
16
+
17
+ def Service.service_scaffold(model)
18
+ servicename = model_name(model)
19
+ self.class_eval <<-end_eval
20
+ alias_method :_initialize_#{name}, :initialize
21
+ def initialize
22
+ _initialize_#{name}
23
+ @model_class = eval("#{model.to_s.camelize}")
24
+ %w( create retrieve update delete list assembly search).each do |operation|
25
+ messagetype = "seamless.#{servicename}." + operation
26
+ request = messagetype + ".request"
27
+ response = messagetype + ".response"
28
+ register(request,operation,response)
29
+ SEAMLESS_SERVICES << {'name'=>name, 'model'=>servicename}
30
+ end
31
+ end
32
+
33
+ def assembly(request,message)
34
+ if self.respond_to?(:before_assembly)
35
+ request,message = send(:before_assembly,request,message)
36
+ end
37
+ data = Array.new
38
+ @model_class.columns.each do |column|
39
+ data << {'name'=>column.name,'type'=>column.type, 'limit'=>column.limit,
40
+ 'nullable'=>column.null, 'primary'=>column.primary,
41
+ 'default'=>column.default}
42
+ end
43
+ response = {'success'=>true,'columns'=>data}
44
+ if self.respond_to?(:after_assembly)
45
+ response = send(:after_assembly,response)
46
+ end
47
+ response
48
+ end
49
+
50
+ def create(request,message)
51
+ if self.respond_to?(:before_create)
52
+ request,message = send(:before_create,request,message)
53
+ end
54
+ o = @model_class.new(message)
55
+ o.save
56
+ response = {'success'=>true,'id'=>o.id}
57
+ if self.respond_to?(:after_create)
58
+ response = send(:after_create,response)
59
+ end
60
+ response
61
+ end
62
+
63
+ def retrieve(request,message)
64
+ if self.respond_to?(:before_retrieve)
65
+ request,message = send(:before_retrieve,request,message)
66
+ end
67
+ o = @model_class.find(message['id'])
68
+ response = {'success'=>true}
69
+ o.attributes.each do |key,val|
70
+ response[key]=val
71
+ end
72
+ response
73
+ if self.respond_to?(:after_retrieve)
74
+ response = send(:after_retrieve,response)
75
+ end
76
+ response
77
+ end
78
+
79
+ def update(request,message)
80
+ if self.respond_to?(:before_update)
81
+ request,message = send(:before_update,request,message)
82
+ end
83
+ o = @model_class.find(message['id'])
84
+ return {'success'=>false,'message'=>'record does not exist'} unless o
85
+ message.each do |key,value|
86
+ if o.attributes.has_key?(key)
87
+ o[key]=true if value=='true'
88
+ o[key]=false if value=='false'
89
+ o[key]=value if o[key]!=true && o[key]!=false
90
+ end
91
+ end
92
+ o.save!
93
+ response = {'success'=>true,'id'=>o.id}
94
+ if self.respond_to?(:after_update)
95
+ response = send(:after_update,response)
96
+ end
97
+ response
98
+ end
99
+
100
+ def delete(request,message)
101
+ if self.respond_to?(:before_delete)
102
+ request,message = send(:before_delete,request,message)
103
+ end
104
+ @model_class.delete(message['id'])
105
+ response = {'success'=>true,'id'=>message['id']}
106
+ if self.respond_to?(:after_delete)
107
+ response = send(:after_delete,response)
108
+ end
109
+ response
110
+ end
111
+
112
+ def search(request,message)
113
+ if self.respond_to?(:before_search)
114
+ request,message = send(:before_search,request,message)
115
+ end
116
+ query = '%%' + message['query'] + '%%'
117
+ conditions_str = Array.new
118
+ @model_class.content_columns.each do |col|
119
+ name = col.name + ' like ?'
120
+ conditions_str << name if col.type == :string
121
+ end
122
+
123
+ conditions = Array.new
124
+ conditions << conditions_str.join(' OR ')
125
+ conditions_str.length.times {|e| conditions<<query}
126
+
127
+ results = @model_class.find(:all,:conditions=>conditions)
128
+ response = {'success'=>true, 'rows'=>results, 'total'=>@model_class.count, 'count'=>results.length}
129
+
130
+ if self.respond_to?(:after_search)
131
+ response = send(:after_search,response)
132
+ end
133
+ response
134
+ end
135
+
136
+ def list(request,message)
137
+ pk = message['id']
138
+ limit = message['limit'] || 100
139
+ if pk
140
+ objs = [@model_class.find_by_id(pk)]
141
+ else
142
+ objs = @model_class.find(:all,:limit=>limit)
143
+ end
144
+ response = {'success'=>true,'rows'=>objs, 'total'=>@model_class.count}
145
+ if self.respond_to?(:after_list)
146
+ response = send(:after_list,response)
147
+ end
148
+ response
149
+ end
150
+
151
+ end_eval
152
+ end
153
+
154
+ private
155
+
156
+ def register(type,methodname,responsetype = nil)
157
+ handler = self.method(methodname)
158
+ args = handler.arity
159
+ p = Proc.new do |req,type,obj|
160
+
161
+ #
162
+ # try and be smart about sending in the request to the
163
+ # service based on the arguments he supports
164
+ #
165
+ case args
166
+ when 2
167
+ resp = handler.call(req,obj)
168
+ when 3
169
+ resp = handler.call(req,obj,req['session'])
170
+ when 4
171
+ resp = handler.call(req,obj,req['session'],req['session']['username'])
172
+ end
173
+
174
+ if responsetype
175
+ Dispatcher.instance.outgoing(req,responsetype,resp||{})
176
+ end
177
+ end
178
+ MessageBroker.register_listener(type,p)
179
+ end
180
+
181
+ def send_message(req,type,message)
182
+ MessageBroker.send(req,type,message)
183
+ end
184
+
185
+ end
186
+ end