caruby-core 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. data/History.txt +4 -0
  2. data/LEGAL +5 -0
  3. data/LICENSE +22 -0
  4. data/README.md +51 -0
  5. data/doc/website/css/site.css +1 -5
  6. data/doc/website/images/avatar.png +0 -0
  7. data/doc/website/images/favicon.ico +0 -0
  8. data/doc/website/images/logo.png +0 -0
  9. data/doc/website/index.html +82 -0
  10. data/doc/website/install.html +87 -0
  11. data/doc/website/quick_start.html +87 -0
  12. data/doc/website/tissue.html +85 -0
  13. data/doc/website/uom.html +10 -0
  14. data/lib/caruby.rb +3 -0
  15. data/lib/caruby/active_support/README.txt +2 -0
  16. data/lib/caruby/active_support/core_ext/string.rb +7 -0
  17. data/lib/caruby/active_support/core_ext/string/inflections.rb +167 -0
  18. data/lib/caruby/active_support/inflections.rb +55 -0
  19. data/lib/caruby/active_support/inflector.rb +398 -0
  20. data/lib/caruby/cli/application.rb +36 -0
  21. data/lib/caruby/cli/command.rb +169 -0
  22. data/lib/caruby/csv/csv_mapper.rb +157 -0
  23. data/lib/caruby/csv/csvio.rb +185 -0
  24. data/lib/caruby/database.rb +252 -0
  25. data/lib/caruby/database/fetched_matcher.rb +66 -0
  26. data/lib/caruby/database/persistable.rb +432 -0
  27. data/lib/caruby/database/persistence_service.rb +162 -0
  28. data/lib/caruby/database/reader.rb +599 -0
  29. data/lib/caruby/database/saved_merger.rb +131 -0
  30. data/lib/caruby/database/search_template_builder.rb +59 -0
  31. data/lib/caruby/database/sql_executor.rb +75 -0
  32. data/lib/caruby/database/store_template_builder.rb +200 -0
  33. data/lib/caruby/database/writer.rb +469 -0
  34. data/lib/caruby/domain/annotatable.rb +25 -0
  35. data/lib/caruby/domain/annotation.rb +23 -0
  36. data/lib/caruby/domain/attribute_metadata.rb +447 -0
  37. data/lib/caruby/domain/java_attribute_metadata.rb +160 -0
  38. data/lib/caruby/domain/merge.rb +91 -0
  39. data/lib/caruby/domain/properties.rb +95 -0
  40. data/lib/caruby/domain/reference_visitor.rb +289 -0
  41. data/lib/caruby/domain/resource_attributes.rb +528 -0
  42. data/lib/caruby/domain/resource_dependency.rb +205 -0
  43. data/lib/caruby/domain/resource_introspection.rb +159 -0
  44. data/lib/caruby/domain/resource_metadata.rb +117 -0
  45. data/lib/caruby/domain/resource_module.rb +285 -0
  46. data/lib/caruby/domain/uniquify.rb +38 -0
  47. data/lib/caruby/import/annotatable_class.rb +28 -0
  48. data/lib/caruby/import/annotation_class.rb +27 -0
  49. data/lib/caruby/import/annotation_module.rb +67 -0
  50. data/lib/caruby/import/java.rb +338 -0
  51. data/lib/caruby/migration/migratable.rb +167 -0
  52. data/lib/caruby/migration/migrator.rb +533 -0
  53. data/lib/caruby/migration/resource.rb +8 -0
  54. data/lib/caruby/migration/resource_module.rb +11 -0
  55. data/lib/caruby/migration/uniquify.rb +20 -0
  56. data/lib/caruby/resource.rb +969 -0
  57. data/lib/caruby/util/attribute_path.rb +46 -0
  58. data/lib/caruby/util/cache.rb +53 -0
  59. data/lib/caruby/util/class.rb +99 -0
  60. data/lib/caruby/util/collection.rb +1053 -0
  61. data/lib/caruby/util/controlled_value.rb +35 -0
  62. data/lib/caruby/util/coordinate.rb +75 -0
  63. data/lib/caruby/util/domain_extent.rb +49 -0
  64. data/lib/caruby/util/file_separator.rb +65 -0
  65. data/lib/caruby/util/inflector.rb +20 -0
  66. data/lib/caruby/util/log.rb +95 -0
  67. data/lib/caruby/util/math.rb +12 -0
  68. data/lib/caruby/util/merge.rb +59 -0
  69. data/lib/caruby/util/module.rb +34 -0
  70. data/lib/caruby/util/options.rb +92 -0
  71. data/lib/caruby/util/partial_order.rb +36 -0
  72. data/lib/caruby/util/person.rb +119 -0
  73. data/lib/caruby/util/pretty_print.rb +184 -0
  74. data/lib/caruby/util/properties.rb +112 -0
  75. data/lib/caruby/util/stopwatch.rb +66 -0
  76. data/lib/caruby/util/topological_sync_enumerator.rb +53 -0
  77. data/lib/caruby/util/transitive_closure.rb +45 -0
  78. data/lib/caruby/util/tree.rb +48 -0
  79. data/lib/caruby/util/trie.rb +37 -0
  80. data/lib/caruby/util/uniquifier.rb +30 -0
  81. data/lib/caruby/util/validation.rb +48 -0
  82. data/lib/caruby/util/version.rb +56 -0
  83. data/lib/caruby/util/visitor.rb +351 -0
  84. data/lib/caruby/util/weak_hash.rb +36 -0
  85. data/lib/caruby/version.rb +3 -0
  86. metadata +186 -0
@@ -0,0 +1,285 @@
1
+ require 'fileutils'
2
+ require 'caruby/util/collection'
3
+ require 'caruby/util/log'
4
+
5
+ module CaRuby
6
+ class JavaIncludeError < StandardError; end;
7
+
8
+ # The application and database connection access command line options.
9
+ ACCESS_OPTS = [
10
+ [:user, "--user USER", "the application login user"],
11
+ [:password, "--password PSWD", "the application login password"],
12
+ [:host, "--host HOST", "the application host name"],
13
+ [:database_host, "--database_host HOST", "the database host name"],
14
+ [:database_type, "--database_type TYPE", "the database type (mysql or oracle)"],
15
+ [:database_driver, "--database_driver DRIVER", "the database driver"],
16
+ [:database_port, "--database_port PORT", Integer, "the database port number"],
17
+ [:database, "--database NAME", "the database name"],
18
+ [:database_user, "--database_user USER", "the database login user"],
19
+ [:database_password, "--database_password PSWD", "the database login password"]
20
+ ]
21
+
22
+ # Importable extends a Module with Java class support.
23
+ # The calling module must implement the following instance variables:
24
+ # * +*java_package+ - the module Java package name
25
+ # * +@mixin+ - the module which scopes the domain objects
26
+ # See the ClinicalTrials module for an example.
27
+ #
28
+ # The application properties hash specifies the startup information,
29
+ # including the user, password and application Java jar library path.
30
+ # The properties are read from a property file. See CaRuby::Properties for
31
+ # more information.
32
+ #
33
+ # A Java class is imported into Ruby either by directly calling the
34
+ # extended module {#java_import} method or on demand.
35
+ # Import on demand is induced by a reference to the class, e.g.:
36
+ # module ClinicalTrials
37
+ # extend Importable
38
+ #
39
+ # def java_package
40
+ # 'org.nci.ctms'
41
+ # end
42
+ # ...
43
+ # enables references by name to a +ClinicalTrials+ Ruby class wrapper of a
44
+ # +org.nci.ctms+ Java class without an import statement, e.g.:
45
+ # ClinicalTrials::Participant.new
46
+ # without defining the +Participant+ Ruby class.
47
+ module ResourceModule
48
+ # Adds the given klass to this ResourceModule. The class is extended with ResourceMetadata methods.
49
+ def add_class(klass)
50
+ @resource_module__loaded ||= Set.new
51
+ # add superclass if necessary
52
+ unless @resource_module__loaded.include?(klass.superclass) or klass.superclass == Java::JavaLang::Object then
53
+ # the domain module includes the superclass on demand
54
+ const_get(klass.superclass.name[/\w+$/].to_sym)
55
+ end
56
+ ResourceMetadata.extend_class(klass, self)
57
+ @resource_module__loaded << klass
58
+ end
59
+
60
+ # Reinclude the mixin in all loaded classes.
61
+ def mixin_changed
62
+ @resource_module__loaded.each { |klass| klass.instance_eval { include CaRuby::Resource } }
63
+ end
64
+
65
+ def access_properties
66
+ @resource_module__props ||= load_access_properties
67
+ end
68
+
69
+ # Loads the application start-up properties in the given file path.
70
+ # The default file path is a period followed by the lower-case application name in the home directory,
71
+ # e.g. +~/.clincaltrials+.
72
+ #
73
+ # The property file format is a series of property definitions in the form _property_ +=+ _value_.
74
+ # The supported properties include the following:
75
+ # * +path+ - the application client Java jar file directories
76
+ # * +user+ - the application service login
77
+ # * +password+ - the application service password
78
+ # * +database+ - the application database name
79
+ # * +database_user+ - the application database connection userid
80
+ # * +database_password+ - the application database connection password
81
+ # * :database_host - the application database connection host (default +localhost+)
82
+ # * +database_type+ - the application database type, + mysql+ or +oracle+ (default +mysql+)
83
+ # * +database_driver+ - the application database connection driver (default is the database type default)
84
+ # * +database_port+ - the application database connection port
85
+ # # The +path+ value is one or more directories separated by a semi-colon(;) or colon (:)
86
+ # # The jar files in the +path+ directory are added to the caRuby execution Java classpath.
87
+ def load_access_properties(file=nil)
88
+ # If a file was specified, then it must exist.
89
+ if file and not File.exists?(file) then
90
+ raise ArgumentError.new("Application access properties file does not exist: #{file}")
91
+ end
92
+ # the access properties
93
+ @resource_module__props ||= {}
94
+ # If no file was specified, then try the default.
95
+ # If the default does not exist, then use the empty properties hash.
96
+ # It is not an error to omit access properties, since the application domain classes
97
+ # can still be used but not queried or saved.
98
+ file ||= default_properties_file || return
99
+
100
+ logger.info("Loading application properties from #{file}...")
101
+ File.open(file).map do |line|
102
+ # match the tolerant property definition
103
+ match = PROP_DEF_REGEX.match(line.chomp) || next
104
+ # the property [name, value] tokens
105
+ tokens = match.captures
106
+ name = tokens.first.to_sym
107
+ value = tokens.last
108
+ # capture the property
109
+ @resource_module__props[name] = value
110
+ end
111
+
112
+ # Look for environment overrides preceded by the uppercase module name, e.g. CATISSUE
113
+ # for the CaTissue module.
114
+ env_prefix = name[/\w+$/].upcase
115
+ ACCESS_OPTS.each do |spec|
116
+ # the access option symbol is the first specification item
117
+ opt = spec[0]
118
+ # the envvar, e.g. CATISSUE_USER
119
+ ev = "#{env_prefix}_#{opt.to_s.upcase}"
120
+ # the envvar value
121
+ value = ENV[ev] || next
122
+ # override the file property with the envar value
123
+ @resource_module__props[opt] = value
124
+ logger.info("Set application property #{opt} from environment variable #{ev}.")
125
+ end
126
+
127
+ # load the Java application jar path
128
+ path_ev = "#{env_prefix}_PATH"
129
+ path = ENV[path_ev] || @resource_module__props[:path]
130
+ Java.add_path(path) if path
131
+
132
+ @resource_module__props
133
+ end
134
+
135
+ # Loads the Ruby source files in the given directory.
136
+ def load_dir(dir)
137
+ # load the properties on demand
138
+ load_access_properties if @resource_module__props.nil?
139
+ # the domain class definitions
140
+ sources = Dir.glob(File.join(dir, "*.rb"))
141
+
142
+ # autoload the domain classes to ensure that definitions are picked up on demand in class hierarchy order
143
+ sym_file_hash = {}
144
+ sources.each do |file|
145
+ base_name = File.basename(file, ".rb")
146
+ sym = base_name.camelize.to_sym
147
+ sym_file_hash[sym] = file
148
+ autoload(sym, file)
149
+ end
150
+
151
+ # load the domain class definitions
152
+ sym_file_hash.each do |sym, file|
153
+ require file
154
+ end
155
+
156
+ # print the loaded classes to the log
157
+ sym_file_hash.each_key do |sym|
158
+ # it is not an error if the inferred class name is not loaded, since only the Java application classes
159
+ # are required to be the camel-case form of the file names.
160
+ klass = const_get(sym) rescue next
161
+ logger.info("#{klass.pp_s}")
162
+ end
163
+ end
164
+
165
+ # Extends the mod module with Java class support. See the class documentation for details.
166
+ def const_missing(symbol)
167
+ autoload?(symbol) ? super : import_domain_class(symbol)
168
+ end
169
+
170
+ # Imports the domain Java class with specified class name_or_sym.
171
+ # This method enables the domain class extensions for storing and retrieving domain objects.
172
+ # The class is added to this module.
173
+ #
174
+ # The optional block overrides the native Java property access wrappers.
175
+ # For example:
176
+ # ClinicalTrials.java_import('edu.wustl.catissuecore.domain.Study') do
177
+ # def study_code=(value)
178
+ # value = value.to_s if Integer === value
179
+ # setStudyCode(value)
180
+ # end
181
+ # end
182
+ # imports the ClinicalTrials Study class as ClinicalTrials::Study and overrides the
183
+ # +study_code+ setter method.
184
+ #
185
+ # Convenience aliases are added to the imported class, e.g. attribute +studyParticipantCollection+
186
+ # is aliased as +study_participants+. Specifically, each attribute reader and writer is aliased with
187
+ # the lower-case, underscore equivalent and a name ending in 'Collection' is changed to plural.
188
+ # Pluralization is smart, e.g. +studyCollection+ is aliased to +studies+ rather than +studys+.
189
+ #
190
+ # The optional aliases argument consists of additional alias => standard attribute associations.
191
+ # The optional owner_attr argument is a non-Java annotation owner attribute.
192
+ def import_domain_class(name_or_sym)
193
+ name = name_or_sym.to_s
194
+ if name.include?('.') then
195
+ symbol = name[/[A-Z]\w*$/].to_sym
196
+ else
197
+ symbol = name_or_sym.to_sym
198
+ name = [@java_package, name].join('.')
199
+ end
200
+ # check if the class is already defined
201
+ if const_defined?(symbol) then
202
+ klass = const_get(symbol)
203
+ if domain?(klass) then
204
+ logger.warn("Attempt to import #{self.qp} class twice: #{symbol}.") and return klass
205
+ end
206
+ end
207
+
208
+ # import the Java class
209
+ logger.debug { "Importing #{qp} Java class #{symbol}..." }
210
+ begin
211
+ java_import(name)
212
+ rescue Exception => e
213
+ raise JavaIncludeError.new("#{symbol} is not a #{qp} Java class - #{e.message}")
214
+ end
215
+
216
+ # the imported Java class is registered as a constant in this module
217
+ klass = const_get(symbol)
218
+ # the Resource import stack
219
+ @import_stack ||= []
220
+ @import_stack.push klass
221
+ # include the Resource mixin into the imported class
222
+ mixin = @mixin
223
+ klass.instance_eval { include mixin }
224
+ logger.info("Imported #{qp} class #{klass.qp}")
225
+ # if we are back to top of the stack, then print the imported Resources
226
+ if klass == @import_stack.first then
227
+ # a referenced class could be imported on demand in the course of printing a referencing class;
228
+ # the referenced class is then pushed onto the stack. thus, the stack can grow during the
229
+ # course of printing, but each imported class is consumed and printed in the end.
230
+ until @import_stack.empty? do
231
+ ref = @import_stack.pop
232
+ logger.debug { ref.pp_s }
233
+ end
234
+ end
235
+ klass
236
+ end
237
+
238
+ # Returns the domain class for class_name, or nil if none in this module.
239
+ def domain_type_with_name(class_name)
240
+ pkg, base = split_class_name(class_name)
241
+ return unless pkg.nil? or pkg == @java_package
242
+ begin
243
+ type = const_get(base)
244
+ rescue JavaIncludeError
245
+ # no such domain type; not an error.
246
+ # other exceptions indicate that there was a domain type but could not be loaded; these exceptions propagate up the call stack
247
+ logger.debug("#{base} is not a #{qp} Java class.")
248
+ return
249
+ end
250
+ type if type < Resource
251
+ end
252
+
253
+ private
254
+
255
+ # The property/value matcher, e.g.:
256
+ # host: jacardi
257
+ # host = jacardi
258
+ # host=jacardi
259
+ # name: J. Edgar Hoover
260
+ # but not:
261
+ # # host: jacardi
262
+ # The captures are the trimmed property and value
263
+ PROP_DEF_REGEX = /^(\w+)(?:\s*[:=]\s*)([^#]+)/
264
+
265
+ def default_properties_file
266
+ home = ENV['HOME'] || '~'
267
+ file = File.expand_path("#{home}/.#{name[/\w+$/].downcase}")
268
+ if File.exists?(file) then
269
+ file
270
+ else
271
+ logger.warn { "Default application property file not found: #{file}." }
272
+ nil
273
+ end
274
+ end
275
+
276
+ # Returns the [package prefix, base class symbol] pair.
277
+ def split_class_name(class_name)
278
+ # the package prefix, including the period
279
+ package = Java.java_package_name(class_name)
280
+ # remove the package and base class name
281
+ base = package.nil? ? class_name : class_name[package.length + 1..-1]
282
+ [package, base.to_sym]
283
+ end
284
+ end
285
+ end
@@ -0,0 +1,38 @@
1
+ require 'singleton'
2
+ require 'caruby/util/uniquifier'
3
+ require 'caruby/util/collection'
4
+
5
+ module CaRuby
6
+ module Resource
7
+ # The Unique mix-in makes values unique within the scope of a Resource class.
8
+ module Unique
9
+ # Makes the given String value unique in the context of this object's class.
10
+ # @return nil if value is nil
11
+ # Raises TypeError if value is neither nil nor a String.
12
+ def uniquify_value(value)
13
+ unless String === value or value.nil? then
14
+ raise TypeError.new("Cannot uniquify #{qp} non-String value #{value}")
15
+ end
16
+ ResourceUniquifier.instance.uniquify(self, value)
17
+ end
18
+ end
19
+ end
20
+
21
+ # The ResourceUniquifier singleton makes Resource instance attribute values unique.
22
+ class ResourceUniquifier
23
+ include Singleton
24
+
25
+ def initialize
26
+ @cache = LazyHash.new { Hash.new }
27
+ end
28
+
29
+ # Makes the obj attribute value unique, or returns nil if value is nil.
30
+ def uniquify(obj, value)
31
+ @cache[obj.class][value] ||= value.uniquify if value
32
+ end
33
+
34
+ def clear
35
+ @cache.clear
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,28 @@
1
+ require 'caruby/util/class'
2
+ require 'caruby/util/collection'
3
+ require 'caruby/import/annotation_module'
4
+
5
+ module JavaImport
6
+ # AnnotatableClass adds Annotation modules to an annotation anchor's domain module.
7
+ module AnnotatableClass
8
+
9
+ def self.extended(klass)
10
+ klass.class_eval { include CaRuby::Annotatable }
11
+ end
12
+
13
+ def annotation_modules
14
+ @annotation_modules ||= []
15
+ end
16
+
17
+ # Creates a new AnnotationModule anchored by this class for the given package and database service.
18
+ def create_annotation_module(package, service)
19
+ annotation_modules << AnnotationModule.create_annotation_module(self, package, service)
20
+ end
21
+
22
+ # Returns the class with the given unqualified name in one of this AnnotationClass's JavaImport::AnnotationModule
23
+ # modules, or nil if no such class.
24
+ def annotation_class(name)
25
+ annotation_modules.detect_value { |mod| annotation_modules.const_get(name) rescue nil }
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,27 @@
1
+ module JavaImport
2
+ # AnnotationClass extends a domain class for an annotation class scoped by an annotation module.
3
+ # The extended class must contain the +@anchor_class+ and +@annotation_module+ instance variables.
4
+ module AnnotationClass
5
+
6
+ # Dynamically creates a new reference attribute if the given attribute symbol is the underscore
7
+ # form of this Annotation's anchor class.
8
+ #
9
+ #@param [Symbol] attribute the missing attribute
10
+ def attribute_missing(attribute)
11
+ # delegate to super to print an error if no class
12
+ super unless attribute.to_s == anchor_class.qp.underscore
13
+ # add the annotation attribute to the anchor class and the anchor attribute to this class
14
+ anchor_class.add_annotation(self)
15
+ end
16
+
17
+ # @return [Class] the annotated class
18
+ def anchor_class
19
+ @anchor_class
20
+ end
21
+
22
+ # @return [Module] the module which scopes this AnnotationClass
23
+ def annotation_module
24
+ @annotation_module
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,67 @@
1
+ require 'caruby/util/class'
2
+ require 'caruby/util/collection'
3
+ require 'caruby/import/importable'
4
+ require 'caruby/import/annotation_class'
5
+
6
+ module JavaImport
7
+ # AnnotationModule creates annotation package modules within a domain module as a context for importing annotation classes.
8
+ module AnnotationModule
9
+ include Importable
10
+
11
+ # Creates a new AnnotationModule in the given domain_module from the given annotation package and service.
12
+ def self.create_annotation_module(anchor_class, package, service)
13
+ domain_module = anchor_class.domain_module
14
+ # make parent modules for each package component
15
+ mod = package.split('.').inject(anchor_class.domain_module) do |parent, name|
16
+ mod_id = name.camelize.to_sym
17
+ if parent.const_defined?(mod_id) then
18
+ parent.const_get(mod_id)
19
+ else
20
+ parent.const_set(mod_id, Module.new)
21
+ end
22
+ end
23
+ # the terminal module is the AnnotationModule
24
+ mod.extend(self)
25
+ # initialize the java_package, service and empty access_properties
26
+ mod.module_eval do
27
+ @anchor_class = anchor_class
28
+ @java_package = package
29
+ @service = service
30
+ @resource_module__props = CaRuby::Domain::Properties.new
31
+ end
32
+ mod_name = mod.name[anchor_class.domain_module.name.length..-1]
33
+ logger.debug { "Created annotation module #{mod}." }
34
+ mod
35
+ end
36
+
37
+ def java_package
38
+ @java_package
39
+ end
40
+
41
+ def service
42
+ @service
43
+ end
44
+
45
+ def anchor_class
46
+ @anchor_class
47
+ end
48
+
49
+ def access_properties
50
+ @resource_module__props
51
+ end
52
+
53
+ # Imports the domain Java class with specified class_name by delegating to Importable and
54
+ # augmenting the importer to include CaRuby::Annotation in each imported class.
55
+ def import_domain_class(class_name)
56
+ anchor_class = @anchor_class
57
+ ann_mod = self
58
+ klass = super(class_name) do
59
+ include CaRuby::Annotation
60
+ @anchor_class = anchor_class
61
+ @annotation_module = ann_mod
62
+ yield if block_given?
63
+ end
64
+ klass.extend(AnnotationClass)
65
+ end
66
+ end
67
+ end