dor-services 2.2.4 → 4.4.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +15 -0
  2. data/bin/dor-indexer +108 -0
  3. data/bin/dor-indexerd +73 -0
  4. data/bin/nokogiri +19 -0
  5. data/bin/rake +19 -0
  6. data/bin/ruby_noexec_wrapper +14 -0
  7. data/bin/solrizer +19 -0
  8. data/bin/solrizerd +19 -0
  9. data/config/certs/README +1 -0
  10. data/config/config_defaults.yml +62 -0
  11. data/config/dev_console_env.rb.example +67 -0
  12. data/config/predicate_mappings.yml +55 -0
  13. data/lib/dor-services.rb +152 -19
  14. data/lib/dor/config.rb +133 -35
  15. data/lib/dor/datastreams/administrative_metadata_ds.rb +84 -0
  16. data/lib/dor/datastreams/content_metadata_ds.rb +337 -0
  17. data/lib/dor/datastreams/datastream_spec_solrizer.rb +18 -0
  18. data/lib/dor/datastreams/default_object_rights_ds.rb +52 -0
  19. data/lib/dor/datastreams/desc_metadata_ds.rb +39 -0
  20. data/lib/{datastreams → dor/datastreams}/embargo_metadata_ds.rb +25 -20
  21. data/lib/{datastreams → dor/datastreams}/events_ds.rb +14 -9
  22. data/lib/dor/datastreams/identity.xsl +8 -0
  23. data/lib/dor/datastreams/identity_metadata_ds.rb +112 -0
  24. data/lib/dor/datastreams/role_metadata_ds.rb +51 -0
  25. data/lib/dor/datastreams/simple_dublin_core_ds.rb +45 -0
  26. data/lib/dor/datastreams/version_metadata_ds.rb +214 -0
  27. data/lib/dor/datastreams/workflow_definition_ds.rb +113 -0
  28. data/lib/dor/datastreams/workflow_ds.rb +103 -0
  29. data/lib/dor/exceptions.rb +0 -1
  30. data/lib/dor/migrations/content_metadata_ds/change_content_type.rb +7 -0
  31. data/lib/dor/migrations/identifiable/assert_adminPolicy.rb +9 -0
  32. data/lib/dor/migrations/identifiable/fix_model_assertions.rb +13 -0
  33. data/lib/dor/migrations/identifiable/record_remediation.rb +18 -0
  34. data/lib/dor/migrations/identifiable/uriify_augmented_contentlocation_refs.rb +18 -0
  35. data/lib/dor/migrations/identifiable/uriify_contentlocation_refs.rb +18 -0
  36. data/lib/dor/migrations/processable/unify_workflows.rb +17 -0
  37. data/lib/dor/migrations/versionable/add_missing_version_md.rb +9 -0
  38. data/lib/dor/models/admin_policy_object.rb +16 -0
  39. data/lib/dor/models/assembleable.rb +14 -0
  40. data/lib/dor/models/collection.rb +14 -0
  41. data/lib/dor/models/contentable.rb +227 -0
  42. data/lib/dor/models/describable.rb +194 -0
  43. data/lib/dor/models/discoverable.rb +66 -0
  44. data/lib/dor/models/editable.rb +267 -0
  45. data/lib/dor/models/embargoable.rb +97 -0
  46. data/lib/dor/models/eventable.rb +12 -0
  47. data/lib/dor/models/governable.rb +162 -0
  48. data/lib/dor/models/identifiable.rb +211 -0
  49. data/lib/dor/models/item.rb +44 -0
  50. data/lib/dor/models/itemizable.rb +66 -0
  51. data/lib/dor/{mods2dc.xslt → models/mods2dc.xslt} +39 -12
  52. data/lib/dor/models/preservable.rb +50 -0
  53. data/lib/dor/models/processable.rb +229 -0
  54. data/lib/dor/models/publishable.rb +74 -0
  55. data/lib/dor/models/set.rb +12 -0
  56. data/lib/dor/models/shelvable.rb +27 -0
  57. data/lib/dor/models/upgradable.rb +74 -0
  58. data/lib/dor/models/versionable.rb +94 -0
  59. data/lib/dor/models/workflow_object.rb +54 -0
  60. data/lib/dor/services/cleanup_service.rb +47 -0
  61. data/lib/dor/services/digital_stacks_service.rb +55 -0
  62. data/lib/dor/services/merge_service.rb +96 -0
  63. data/lib/dor/{metadata_handlers → services/metadata_handlers}/catalog_handler.rb +0 -2
  64. data/lib/dor/{metadata_handlers → services/metadata_handlers}/mdtoolkit_handler.rb +0 -2
  65. data/lib/dor/{metadata_service.rb → services/metadata_service.rb} +1 -3
  66. data/lib/dor/services/registration_service.rb +181 -0
  67. data/lib/dor/services/sdr_ingest_service.rb +181 -0
  68. data/lib/dor/services/search_service.rb +131 -0
  69. data/lib/dor/services/suri_service.rb +32 -0
  70. data/lib/dor/services/technical_metadata_service.rb +226 -0
  71. data/lib/dor/{tei2dc.xslt → services/tei2dc.xslt} +0 -0
  72. data/lib/dor/utils/ng_tidy.rb +37 -0
  73. data/lib/dor/utils/predicate_patch.rb +23 -0
  74. data/lib/dor/utils/solr_doc_helper.rb +9 -0
  75. data/lib/dor/utils/utc_date_field_mapper.rb +7 -0
  76. data/lib/dor/version.rb +3 -0
  77. data/lib/dor/workflow/document.rb +131 -0
  78. data/lib/dor/workflow/graph.rb +166 -0
  79. data/lib/dor/workflow/process.rb +99 -0
  80. data/lib/gsearch/demoFoxmlToSolr.xslt +340 -122
  81. data/lib/tasks/dor.rake +39 -0
  82. metadata +494 -384
  83. data/lib/datastreams/content_metadata_ds.rb +0 -12
  84. data/lib/datastreams/identity_metadata_ds.rb +0 -28
  85. data/lib/datastreams/ng_tidy.rb +0 -19
  86. data/lib/datastreams/simple_dublin_core_ds.rb +0 -23
  87. data/lib/datastreams/workflow_definition_ds.rb +0 -105
  88. data/lib/datastreams/workflow_ds.rb +0 -16
  89. data/lib/dor/admin_policy_object.rb +0 -11
  90. data/lib/dor/base.rb +0 -81
  91. data/lib/dor/cleanup_service.rb +0 -32
  92. data/lib/dor/digital_stacks_service.rb +0 -82
  93. data/lib/dor/druid_utils.rb +0 -41
  94. data/lib/dor/embargo.rb +0 -41
  95. data/lib/dor/item.rb +0 -141
  96. data/lib/dor/provenance_metadata_service.rb +0 -65
  97. data/lib/dor/registration_service.rb +0 -87
  98. data/lib/dor/rsolr.rb +0 -27
  99. data/lib/dor/sdr_ingest_service.rb +0 -117
  100. data/lib/dor/search_service.rb +0 -86
  101. data/lib/dor/suri_service.rb +0 -37
  102. data/lib/dor/workflow_object.rb +0 -13
  103. data/lib/dor/workflow_service.rb +0 -111
  104. data/lib/xml_models/foxml.rb +0 -261
  105. data/lib/xml_models/identity_metadata/dublin_core.rb +0 -119
  106. data/lib/xml_models/identity_metadata/identity_metadata.rb +0 -288
@@ -0,0 +1,37 @@
1
+ class Nokogiri::XML::Text
2
+
3
+ def normalize
4
+ self.content =~ /\S/ ? self.content.gsub(/\s+/,' ').strip : self.content
5
+ end
6
+
7
+ def normalize!
8
+ self.content = self.normalize
9
+ end
10
+
11
+ end
12
+
13
+ class Nokogiri::XML::Node
14
+
15
+ def normalize_text!
16
+ self.xpath('//text()').each { |t| t.normalize! }
17
+ end
18
+
19
+ end
20
+
21
+ class Nokogiri::XML::Document
22
+
23
+ def prettify
24
+ xslt = Nokogiri::XSLT <<-EOC
25
+ <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
26
+ <xsl:output omit-xml-declaration="yes" indent="yes"/>
27
+ <xsl:template match="node()|@*">
28
+ <xsl:copy>
29
+ <xsl:apply-templates select="node()|@*"/>
30
+ </xsl:copy>
31
+ </xsl:template>
32
+ </xsl:stylesheet>
33
+ EOC
34
+ xslt.transform(self).to_xml
35
+ end
36
+
37
+ end
@@ -0,0 +1,23 @@
1
+ # Monkey patch ActiveFedora::RelsExtDatastream.short_predicate to
2
+ # create missing mappings on the fly.
3
+
4
+ module ActiveFedora
5
+ class RelsExtDatastream
6
+ def self.short_predicate(predicate)
7
+ # for this regex to short-circuit correctly, namespaces must be sorted into descending order by length
8
+ if match = /^(#{Predicates.predicate_mappings.keys.sort.reverse.join('|')})(.+)$/.match(predicate.to_str)
9
+ namespace = match[1]
10
+ predicate = match[2]
11
+ ns_mapping = Predicates.predicate_mappings[namespace] ||= {}
12
+ pred = ns_mapping.invert[predicate]
13
+ if pred.nil?
14
+ pred = predicate.underscore.to_sym
15
+ ns_mapping[pred] = predicate
16
+ end
17
+ pred
18
+ else
19
+ raise "Unable to parse predicate: #{predicate}"
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ module SolrDocHelper
2
+
3
+ def add_solr_value(solr_doc, field_name, value, field_type = :default, index_types = [:searchable])
4
+ index_types.each { |index_type|
5
+ ::Solrizer::Extractor.insert_solr_field_value(solr_doc, ::ActiveFedora::SolrService.solr_name(field_name, field_type, index_type), value)
6
+ }
7
+ end
8
+
9
+ end
@@ -0,0 +1,7 @@
1
+ class UtcDateFieldMapper < Solrizer::FieldMapper::Default
2
+ [:searchable,:facetable,:displayable,:sortable,:unstemmed_searchable].each do |index_type|
3
+ index_as index_type do |type|
4
+ type.date { |value| Time.parse(value).utc.xmlschema }
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module Dor
2
+ VERSION = '4.4.10'
3
+ end
@@ -0,0 +1,131 @@
1
+ module Dor
2
+ module Workflow
3
+ class Document
4
+ include SolrDocHelper
5
+ include OM::XML::Document
6
+
7
+ set_terminology do |t|
8
+ t.root(:path => 'workflow')
9
+ t.repository(:path=>{:attribute => "repository"})
10
+ t.workflowId(:path=>{:attribute => "id"})
11
+ t.process {
12
+ t.name_(:path=>{:attribute=>"name"})
13
+ t.status(:path=>{:attribute=>"status"})
14
+ t.timestamp(:path=>{:attribute=>"datetime"})#, :data_type => :date)
15
+ t.elapsed(:path=>{:attribute=>"elapsed"})
16
+ t.lifecycle(:path=>{:attribute=>"lifecycle"})
17
+ t.attempts(:path=>{:attribute=>"attempts"}, :index_as => [:not_searchable])
18
+ }
19
+ end
20
+ @@definitions={}
21
+ def initialize node
22
+ self.ng_xml = Nokogiri::XML(node)
23
+ end
24
+ #is this an incomplete workflow with steps that have a priority > 0
25
+ def expedited?
26
+ processes.any? { |proc| !proc.completed? && proc.priority.to_i > 0 }
27
+ end
28
+
29
+ # @return [Integer] value of the first > 0 priority. Defaults to 0
30
+ def priority
31
+ processes.map {|proc| proc.priority.to_i }.detect(0) {|p| p > 0}
32
+ end
33
+
34
+ def active?
35
+ processes.any? { |proc| !proc.version }
36
+ end
37
+
38
+ def definition
39
+ @definition ||= begin
40
+ if @@definitions.has_key? self.workflowId.first
41
+ @@definitions[self.workflowId.first]
42
+ else
43
+ wfo = Dor::WorkflowObject.find_by_name(self.workflowId.first)
44
+ wf_def=wfo ? wfo.definition : nil
45
+ @@definitions[self.workflowId.first] = wf_def
46
+ wf_def
47
+ end
48
+ end
49
+ end
50
+
51
+ def graph(parent=nil, dir=nil)
52
+ wf_definition = self.definition
53
+ result = wf_definition ? Workflow::Graph.from_processes(wf_definition.repo, wf_definition.name, self.processes, parent) : nil
54
+ unless result.nil?
55
+ result['rankdir'] = dir || 'TB'
56
+ end
57
+ result
58
+ end
59
+
60
+ def [](value)
61
+ self.processes.find { |p| p.name == value }
62
+ end
63
+
64
+ def processes
65
+ #if the workflow service didnt return any processes, dont return any processes from the reified wf
66
+ if ng_xml.search("/workflow/process").length == 0
67
+ return []
68
+ end
69
+ @processes ||= if self.definition
70
+ self.definition.processes.collect do |process|
71
+ node = ng_xml.at("/workflow/process[@name = '#{process.name}']")
72
+ process.update!(node,self) unless node.nil?
73
+ process
74
+ end
75
+ else
76
+ self.find_by_terms(:workflow, :process).collect do |x|
77
+ pnode = Dor::Workflow::Process.new(self.repository, self.workflowId, {})
78
+ pnode.update!(x,self)
79
+ pnode
80
+ end.sort_by(&:datetime)
81
+ end
82
+ end
83
+
84
+ def to_solr(solr_doc=Hash.new, *args)
85
+ wf_name = self.workflowId.first
86
+ repo=self.repository.first
87
+ add_solr_value(solr_doc, 'wf', wf_name, :string, [:facetable])
88
+ add_solr_value(solr_doc, 'wf_wps', wf_name, :string, [:facetable])
89
+ add_solr_value(solr_doc, 'wf_wsp', wf_name, :string, [:facetable])
90
+ status = processes.empty? ? 'empty' : (processes.all?(&:completed?) ? 'completed' : 'active')
91
+ errors = processes.select(&:error?).count
92
+ add_solr_value(solr_doc, 'workflow_status', [wf_name,status,errors,repo].join('|'), :string, [:displayable])
93
+
94
+ processes.each do |process|
95
+ if process.status.present?
96
+ #add a record of the robot having operated on this item, so we can track robot activity
97
+ if process.date_time and process.status and (process.status == 'completed' || process.status == 'error')
98
+ add_solr_value(solr_doc, "wf_#{wf_name}_#{process.name}", process.date_time+'Z', :date)
99
+ end
100
+ add_solr_value(solr_doc, 'wf_error', "#{wf_name}:#{process.name}:#{process.error_message}", :string, [:facetable,:displayable]) if process.error_message #index the error message without the druid so we hopefully get some overlap
101
+ add_solr_value(solr_doc, 'wf_wsp', "#{wf_name}:#{process.status}", :string, [:facetable])
102
+ add_solr_value(solr_doc, 'wf_wsp', "#{wf_name}:#{process.status}:#{process.name}", :string, [:facetable])
103
+ add_solr_value(solr_doc, 'wf_wps', "#{wf_name}:#{process.name}", :string, [:facetable])
104
+ add_solr_value(solr_doc, 'wf_wps', "#{wf_name}:#{process.name}:#{process.status}", :string, [:facetable])
105
+ add_solr_value(solr_doc, 'wf_swp', "#{process.status}", :string, [:facetable])
106
+ add_solr_value(solr_doc, 'wf_swp', "#{process.status}:#{wf_name}", :string, [:facetable])
107
+ add_solr_value(solr_doc, 'wf_swp', "#{process.status}:#{wf_name}:#{process.name}", :string, [:facetable])
108
+ if process.state != process.status
109
+ add_solr_value(solr_doc, 'wf_wsp', "#{wf_name}:#{process.state}:#{process.name}", :string, [:facetable])
110
+ add_solr_value(solr_doc, 'wf_wps', "#{wf_name}:#{process.name}:#{process.state}", :string, [:facetable])
111
+ add_solr_value(solr_doc, 'wf_swp', "#{process.state}", :string, [:facetable])
112
+ add_solr_value(solr_doc, 'wf_swp', "#{process.state}:#{wf_name}", :string, [:facetable])
113
+ add_solr_value(solr_doc, 'wf_swp', "#{process.state}:#{wf_name}:#{process.name}", :string, [:facetable])
114
+ end
115
+ end
116
+ end
117
+
118
+ solr_doc['wf_wps_facet'].uniq! if solr_doc['wf_wps_facet']
119
+ solr_doc['wf_wsp_facet'].uniq! if solr_doc['wf_wsp_facet']
120
+ solr_doc['wf_swp_facet'].uniq! if solr_doc['wf_swp_facet']
121
+ solr_doc['workflow_status'].uniq! if solr_doc['workflow_status']
122
+
123
+ solr_doc
124
+ end
125
+
126
+ def inspect
127
+ "#<#{self.class.name}:#{self.object_id}>"
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,166 @@
1
+ require 'graphviz'
2
+
3
+ module Dor
4
+ module Workflow
5
+ class Graph
6
+
7
+ FILL_COLORS = { 'waiting' => "white", 'ready' => "white", 'error' => "#8B0000", 'blocked' => "white", 'completed' => "darkgreen", 'unknown' => "#CFCFCF" }
8
+ TEXT_COLORS = { 'waiting' => "black", 'ready' => "black", 'error' => "white", 'blocked' => "#8B0000", 'completed' => "white", 'unknown' => "black" }
9
+ PATTERNS = { 'waiting' => "diagonals", 'ready' => "filled", 'error' => "filled", 'blocked' => "diagonals", 'completed' => "filled", 'unknown' => "filled" }
10
+ RESERVED_KEYS = ['repository','name']
11
+
12
+ attr_reader :repo, :name, :processes, :graph, :root
13
+
14
+ def self.from_config(name, config, parent = nil)
15
+ wf = self.new(config['repository'], name, parent)
16
+ config.keys.each { |p| wf.add_process(p.to_s) unless RESERVED_KEYS.include?(p) }
17
+ config.keys.each { |p|
18
+ if wf.processes[p]
19
+ Array(config[p]['prerequisite']).each { |prereq|
20
+ prereq.sub!(/^#{config['repository']}:#{name}:/e,'')
21
+ if wf.processes[prereq]
22
+ wf.processes[p].depends_on(wf.processes[prereq])
23
+ else
24
+ wf.processes[p].depends_on(wf.add_process(prereq).set_status('external'))
25
+ end
26
+ }
27
+ end
28
+ }
29
+ wf.finish
30
+ return wf
31
+ end
32
+
33
+ def self.from_processes(repo, name, processes, parent = nil)
34
+ wf = self.new(repo, name, parent)
35
+ processes.each { |p|
36
+ wf.add_process(p.name).status = p.state || 'unknown'
37
+ }
38
+ processes.each { |p|
39
+ p.prerequisite.each { |prereq|
40
+ prereq.sub!(/^#{repo}:#{name}:/e,'')
41
+ if wf.processes[prereq]
42
+ wf.processes[p.name].depends_on(wf.processes[prereq])
43
+ else
44
+ wf.processes[p.name].depends_on(wf.add_process(prereq).set_status('external'))
45
+ end
46
+ }
47
+ }
48
+ wf.finish
49
+ return wf
50
+ end
51
+
52
+ def initialize(repo, name, parent = nil)
53
+ @repo = repo
54
+ @name = name
55
+ if parent.nil?
56
+ @graph = GraphViz.new(qname)
57
+ @root = self.add_nodes(name)
58
+ else
59
+ @graph = parent.subgraph(qname)
60
+ @root = parent.add_nodes(name)
61
+ end
62
+ @graph[:truecolor => true]
63
+ @root.shape = 'plaintext'
64
+ @processes = {}
65
+ end
66
+
67
+ def qname
68
+ [@repo,@name].join(':')
69
+ end
70
+
71
+ def add_process(name, external = false)
72
+ pqname = name.split(/:/).length == 3 ? name : [qname,name].join(':')
73
+ p = Process.new(self, pqname, name)
74
+ @processes[name] = p
75
+ return p
76
+ end
77
+
78
+ def finish
79
+ @processes.values.each do |process|
80
+ process.node.fontname = 'Helvetica'
81
+ if process.id =~ %r{^#{qname}} and process.prerequisites.length == 0
82
+ (@root << process.node)[:arrowhead => 'none', :arrowtail => 'none', :dir => 'both', :style => 'invisible']
83
+ end
84
+ end
85
+
86
+ @root.fontname = 'Helvetica'
87
+ return self
88
+ end
89
+
90
+ def inspect
91
+ "#{self.to_s[0..-2]} #{repo}:#{name} (#{processes.keys.join(', ')})>"
92
+ end
93
+
94
+ def method_missing(sym,*args)
95
+ if @graph.respond_to?(sym)
96
+ @graph.send(sym,*args)
97
+ else
98
+ super
99
+ end
100
+ end
101
+
102
+ class Process
103
+
104
+ attr_reader :name, :status, :node, :prerequisites
105
+
106
+ def initialize(graph, id, name)
107
+ @name = name
108
+ @graph = graph
109
+ @node = @graph.add_nodes(id)
110
+ @node.shape = 'box'
111
+ @node.label = name
112
+ @prerequisites = []
113
+ self.set_status('unknown')
114
+ end
115
+
116
+ def id
117
+ @node.id
118
+ end
119
+
120
+ def status=(s)
121
+ @status = s
122
+ if s == 'external'
123
+ @node.fillcolor = "gray"
124
+ @node.fontcolor = "black"
125
+ @node.style = "dashed"
126
+ else
127
+ @node.fillcolor = FILL_COLORS[s] || "yellow"
128
+ @node.fontcolor = TEXT_COLORS[s]
129
+ @node.style = PATTERNS[s]
130
+ end
131
+ end
132
+
133
+ def set_status(s)
134
+ self.status = s
135
+ return self
136
+ end
137
+
138
+ def depends_on(*processes)
139
+ wf1 = self.id.split(/:/)[0..1].join(':')
140
+ processes.each { |process|
141
+ wf2 = process.id.split(/:/)[0..1].join(':')
142
+ edge = (process.node << @node)
143
+ edge.dir = 'both'
144
+ edge.arrowhead = 'none'
145
+ edge.arrowtail = 'none'
146
+ if (wf1 != wf2)
147
+ edge.style = 'dashed'
148
+ end
149
+ self.prerequisites << process
150
+ }
151
+ return self
152
+ end
153
+
154
+ def same_as(process)
155
+ @node = process.node
156
+ end
157
+
158
+ def all_prerequisites
159
+ prerequisites.collect { |p| p.all_prerequisites + [p.name] }.flatten.uniq
160
+ end
161
+
162
+ end
163
+
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,99 @@
1
+ module Dor
2
+ module Workflow
3
+ class Process
4
+ attr_reader :owner, :repo, :workflow
5
+
6
+ def initialize(repo, workflow, attrs)
7
+ @workflow = workflow
8
+ @repo = repo
9
+ if attrs.is_a? Nokogiri::XML::Node
10
+ init_from_node(attrs)
11
+ else
12
+ @attrs = attrs
13
+ end
14
+ end
15
+
16
+ def init_from_node(node)
17
+ @attrs = {
18
+ 'name' => node['name'],
19
+ 'sequence' => node['sequence'] ? node['sequence'].to_i : nil,
20
+ 'status' => node['status'], # TODO see how this affects argo
21
+ 'lifecycle' => node['lifecycle'],
22
+ 'label' => node.at_xpath('label/text()').to_s,
23
+ 'batch_limit' => node['batch-limit'] ? node['batch-limit'].to_i : nil,
24
+ 'error_limit' => node['error-limit'] ? node['error-limit'].to_i : nil,
25
+ 'prerequisite' => node.xpath('prereq').collect { |p|
26
+ repo = (p['repository'].nil? or p['repository'] == @repo) ? nil : p['repository']
27
+ wf = (p['workflow'].nil? or p['workflow'] == @workflow) ? nil : p['workflow']
28
+ [repo,wf,p.text.to_s].compact.join(':')
29
+ },
30
+ 'priority' => node['priority'] ? node['priority'].to_i : 0
31
+ }
32
+ end
33
+
34
+ def name ; @attrs['name'] ; end
35
+ def sequence ; @attrs['sequence'] ; end
36
+ def lifecycle ; @attrs['lifecycle'] ; end
37
+ def label ; @attrs['label'] ; end
38
+ def batch_limit ; @attrs['batch_limit'] ; end
39
+ def error_limit ; @attrs['error_limit'] ; end
40
+ def error_message ; @attrs['errorMessage'] ; end
41
+ def prerequisite ; @attrs['prerequisite'] ; end
42
+ def status ; @attrs['status'] ; end
43
+ def note ; @attrs['note'] ; end
44
+ def version ; @attrs['version'] ; end
45
+ def priority ; @attrs['priority'] ; end
46
+ def completed? ; self.status == 'completed' ; end
47
+ def error? ; self.status == 'error' ; end
48
+ def waiting? ; self.status == 'waiting' ; end
49
+ def date_time ;@attrs['datetime'] ; end
50
+
51
+ def archived?
52
+ return true if(@attrs['archived'] =~ /true$/i)
53
+ return false
54
+ end
55
+
56
+ def ready?
57
+ self.waiting? and (not self.prerequisite.nil?) and self.prerequisite.all? { |pr| (prq = self.owner[pr]) && prq.completed? }
58
+ end
59
+
60
+ def blocked?
61
+ self.waiting? and (not self.prerequisite.nil?) and self.prerequisite.any? { |pr| (prq = self.owner[pr]) && (prq.error? or prq.blocked?) }
62
+ end
63
+
64
+ def state
65
+ if blocked?
66
+ 'blocked'
67
+ elsif ready?
68
+ 'ready'
69
+ else
70
+ status
71
+ end
72
+ end
73
+
74
+ def attempts
75
+ @attrs['attempts'].to_i
76
+ end
77
+
78
+ def datetime
79
+ @attrs['datetime'] ? Time.parse(@attrs['datetime']) : nil
80
+ end
81
+
82
+ def elapsed
83
+ @attrs['elapsed'].nil? ? nil : @attrs['elapsed'].to_f
84
+ end
85
+
86
+ def update!(info, new_owner=nil)
87
+ @owner = new_owner unless new_owner.nil?
88
+ if info.is_a? Nokogiri::XML::Node
89
+ info = Hash[info.attributes.collect { |k,v| [k,v.value] }]
90
+ end
91
+ @attrs.merge! info
92
+ end
93
+
94
+ def to_hash
95
+ @attrs.reject { |k,v| v.nil? or v == 0 or (v.respond_to?(:empty?) and v.empty?) }
96
+ end
97
+ end
98
+ end
99
+ end