activerdf_net7 1.6.11
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +79 -0
- data/LICENSE +504 -0
- data/README.rdoc +34 -0
- data/activerdf-jena/CHANGELOG +14 -0
- data/activerdf-jena/LICENSE +504 -0
- data/activerdf-jena/README +57 -0
- data/activerdf-jena/Rakefile +87 -0
- data/activerdf-jena/TODO +18 -0
- data/activerdf-jena/VERSION +1 -0
- data/activerdf-jena/ext/antlr-2.7.5.jar +0 -0
- data/activerdf-jena/ext/arq-extra.jar +0 -0
- data/activerdf-jena/ext/arq.jar +0 -0
- data/activerdf-jena/ext/aterm-java-1.6.jar +0 -0
- data/activerdf-jena/ext/commons-logging-1.1.jar +0 -0
- data/activerdf-jena/ext/concurrent.jar +0 -0
- data/activerdf-jena/ext/icu4j_3_4.jar +0 -0
- data/activerdf-jena/ext/iri.jar +0 -0
- data/activerdf-jena/ext/jena.jar +0 -0
- data/activerdf-jena/ext/jenatest.jar +0 -0
- data/activerdf-jena/ext/json.jar +0 -0
- data/activerdf-jena/ext/junit.jar +0 -0
- data/activerdf-jena/ext/log4j-1.2.12.jar +0 -0
- data/activerdf-jena/ext/lucene-core-2.0.0.jar +0 -0
- data/activerdf-jena/ext/ng4j.jar +0 -0
- data/activerdf-jena/ext/pellet.jar +0 -0
- data/activerdf-jena/ext/relaxngDatatype.jar +0 -0
- data/activerdf-jena/ext/stax-api-1.0.jar +0 -0
- data/activerdf-jena/ext/wstx-asl-3.0.0.jar +0 -0
- data/activerdf-jena/ext/xercesImpl.jar +0 -0
- data/activerdf-jena/ext/xml-apis.jar +0 -0
- data/activerdf-jena/ext/xsdlib.jar +0 -0
- data/activerdf-jena/lib/activerdf_jena/init.rb +26 -0
- data/activerdf-jena/lib/activerdf_jena/jena.rb +59 -0
- data/activerdf-jena/lib/activerdf_jena/jena_adapter.rb +515 -0
- data/activerdf-jena/lib/activerdf_jena/lucene.rb +22 -0
- data/activerdf-jena/lib/activerdf_jena/ng4j.rb +50 -0
- data/activerdf-jena/lib/activerdf_jena/ng4j_adapter.rb +379 -0
- data/activerdf-jena/lib/activerdf_jena/pellet.rb +25 -0
- data/activerdf-jena/test/bnode_org_rss.rdf +793 -0
- data/activerdf-jena/test/eyal-foaf.nt +39 -0
- data/activerdf-jena/test/fun_with_bnodes.nt +2 -0
- data/activerdf-jena/test/s1.n3 +18 -0
- data/activerdf-jena/test/test_data.nt +32 -0
- data/activerdf-jena/test/test_jena_adapter.rb +451 -0
- data/activerdf-jena/test/test_ng4j_adapter.rb +354 -0
- data/activerdf-rdflite/CHANGELOG +31 -0
- data/activerdf-rdflite/LICENSE +504 -0
- data/activerdf-rdflite/README +16 -0
- data/activerdf-rdflite/Rakefile +73 -0
- data/activerdf-rdflite/VERSION +1 -0
- data/activerdf-rdflite/lib/activerdf_rdflite/fetching.rb +34 -0
- data/activerdf-rdflite/lib/activerdf_rdflite/init.rb +13 -0
- data/activerdf-rdflite/lib/activerdf_rdflite/rdflite.rb +582 -0
- data/activerdf-rdflite/lib/activerdf_rdflite/suggesting.rb +87 -0
- data/activerdf-rdflite/test/test_bnode_data.nt +5 -0
- data/activerdf-rdflite/test/test_data.nt +32 -0
- data/activerdf-rdflite/test/test_escaped_data.nt +2 -0
- data/activerdf-rdflite/test/test_fetching.rb +33 -0
- data/activerdf-rdflite/test/test_rdflite.rb +277 -0
- data/activerdf-redland/CHANGELOG +12 -0
- data/activerdf-redland/LICENSE +504 -0
- data/activerdf-redland/README +9 -0
- data/activerdf-redland/Rakefile +72 -0
- data/activerdf-redland/VERSION +1 -0
- data/activerdf-redland/lib/activerdf_redland/init.rb +10 -0
- data/activerdf-redland/lib/activerdf_redland/redland.rb +362 -0
- data/activerdf-redland/test/test_person_data.nt +42 -0
- data/activerdf-redland/test/test_redland_adapter.rb +242 -0
- data/activerdf-sesame/CHANGELOG +6 -0
- data/activerdf-sesame/LICENSE +10 -0
- data/activerdf-sesame/LICENSE-aduna +10 -0
- data/activerdf-sesame/LICENSE-lgpl +504 -0
- data/activerdf-sesame/README +33 -0
- data/activerdf-sesame/Rakefile +77 -0
- data/activerdf-sesame/VERSION +1 -0
- data/activerdf-sesame/ext/commons-codec-1.3.jar +0 -0
- data/activerdf-sesame/ext/commons-dbcp-1.2.2.jar +0 -0
- data/activerdf-sesame/ext/commons-httpclient-3.1.jar +0 -0
- data/activerdf-sesame/ext/commons-logging-1.1.1.jar +0 -0
- data/activerdf-sesame/ext/commons-pool-1.3.jar +0 -0
- data/activerdf-sesame/ext/commons-pool-1.5.2.jar +0 -0
- data/activerdf-sesame/ext/junit-3.8.2.jar +0 -0
- data/activerdf-sesame/ext/openrdf-sesame-2.0-onejar.jar +0 -0
- data/activerdf-sesame/ext/openrdf-sesame-2.3-pr1-onejar.jar +0 -0
- data/activerdf-sesame/ext/slf4j-api-1.4.3.jar +0 -0
- data/activerdf-sesame/ext/slf4j-nop-1.4.3.jar +0 -0
- data/activerdf-sesame/ext/wrapper-sesame2.jar +0 -0
- data/activerdf-sesame/java/build.number +3 -0
- data/activerdf-sesame/java/build.xml +313 -0
- data/activerdf-sesame/java/javadoc/allclasses-frame.html +31 -0
- data/activerdf-sesame/java/javadoc/allclasses-noframe.html +31 -0
- data/activerdf-sesame/java/javadoc/constant-values.html +146 -0
- data/activerdf-sesame/java/javadoc/deprecated-list.html +146 -0
- data/activerdf-sesame/java/javadoc/help-doc.html +223 -0
- data/activerdf-sesame/java/javadoc/index-files/index-1.html +150 -0
- data/activerdf-sesame/java/javadoc/index-files/index-10.html +145 -0
- data/activerdf-sesame/java/javadoc/index-files/index-2.html +157 -0
- data/activerdf-sesame/java/javadoc/index-files/index-3.html +146 -0
- data/activerdf-sesame/java/javadoc/index-files/index-4.html +145 -0
- data/activerdf-sesame/java/javadoc/index-files/index-5.html +145 -0
- data/activerdf-sesame/java/javadoc/index-files/index-6.html +142 -0
- data/activerdf-sesame/java/javadoc/index-files/index-7.html +145 -0
- data/activerdf-sesame/java/javadoc/index-files/index-8.html +152 -0
- data/activerdf-sesame/java/javadoc/index-files/index-9.html +146 -0
- data/activerdf-sesame/java/javadoc/index.html +36 -0
- data/activerdf-sesame/java/javadoc/org/activerdf/wrapper/sesame2/WrapperForSesame2.html +665 -0
- data/activerdf-sesame/java/javadoc/org/activerdf/wrapper/sesame2/class-use/WrapperForSesame2.html +144 -0
- data/activerdf-sesame/java/javadoc/org/activerdf/wrapper/sesame2/package-frame.html +32 -0
- data/activerdf-sesame/java/javadoc/org/activerdf/wrapper/sesame2/package-summary.html +157 -0
- data/activerdf-sesame/java/javadoc/org/activerdf/wrapper/sesame2/package-tree.html +150 -0
- data/activerdf-sesame/java/javadoc/org/activerdf/wrapper/sesame2/package-use.html +144 -0
- data/activerdf-sesame/java/javadoc/overview-summary.html +156 -0
- data/activerdf-sesame/java/javadoc/overview-tree.html +152 -0
- data/activerdf-sesame/java/javadoc/package-list +1 -0
- data/activerdf-sesame/java/javadoc/resources/inherit.gif +0 -0
- data/activerdf-sesame/java/javadoc/stylesheet.css +29 -0
- data/activerdf-sesame/java/lib/commons-codec-1.3.jar +0 -0
- data/activerdf-sesame/java/lib/commons-dbcp-1.2.2.jar +0 -0
- data/activerdf-sesame/java/lib/commons-httpclient-3.1.jar +0 -0
- data/activerdf-sesame/java/lib/commons-logging-1.1.1.jar +0 -0
- data/activerdf-sesame/java/lib/commons-pool-1.3.jar +0 -0
- data/activerdf-sesame/java/lib/commons-pool-1.5.2.jar +0 -0
- data/activerdf-sesame/java/lib/junit-3.8.2.jar +0 -0
- data/activerdf-sesame/java/lib/openrdf-sesame-2.0-onejar.jar +0 -0
- data/activerdf-sesame/java/lib/openrdf-sesame-2.3-pr1-onejar.jar +0 -0
- data/activerdf-sesame/java/lib/slf4j-api-1.4.3.jar +0 -0
- data/activerdf-sesame/java/lib/slf4j-nop-1.4.3.jar +0 -0
- data/activerdf-sesame/java/manifest.mf +3 -0
- data/activerdf-sesame/java/settings.xml +135 -0
- data/activerdf-sesame/java/src/org/activerdf/wrapper/sesame2/WrapperForSesame2.java +145 -0
- data/activerdf-sesame/java/test-src/org/activerdf/wrapper/sesame2/TestWrapperForSesame2.java +41 -0
- data/activerdf-sesame/lib/activerdf_sesame/init.rb +11 -0
- data/activerdf-sesame/lib/activerdf_sesame/sesame.rb +400 -0
- data/activerdf-sesame/test/eyal-foaf.nt +39 -0
- data/activerdf-sesame/test/eyal-foaf.rdf +65 -0
- data/activerdf-sesame/test/test_sesame_adapter.rb +341 -0
- data/activerdf-sparql/CHANGELOG +35 -0
- data/activerdf-sparql/LICENSE +504 -0
- data/activerdf-sparql/README +10 -0
- data/activerdf-sparql/Rakefile +78 -0
- data/activerdf-sparql/VERSION +1 -0
- data/activerdf-sparql/lib/activerdf_sparql/init.rb +10 -0
- data/activerdf-sparql/lib/activerdf_sparql/sparql.rb +212 -0
- data/activerdf-sparql/lib/activerdf_sparql/sparql_result_parser.rb +55 -0
- data/activerdf-sparql/test/test_sparql_adapter.rb +108 -0
- data/activerdf-yars/LICENSE +504 -0
- data/activerdf-yars/README +10 -0
- data/activerdf-yars/Rakefile +38 -0
- data/activerdf-yars/lib/activerdf_yars/init.rb +10 -0
- data/activerdf-yars/lib/activerdf_yars/jars2.rb +119 -0
- data/lib/active_rdf.rb +85 -0
- data/lib/active_rdf/directaccess/direct_access.rb +49 -0
- data/lib/active_rdf/federation/active_rdf_adapter.rb +47 -0
- data/lib/active_rdf/federation/connection_pool.rb +156 -0
- data/lib/active_rdf/federation/federation_manager.rb +112 -0
- data/lib/active_rdf/instance_exec.rb +13 -0
- data/lib/active_rdf/objectmanager/bnode.rb +7 -0
- data/lib/active_rdf/objectmanager/literal.rb +71 -0
- data/lib/active_rdf/objectmanager/namespace.rb +106 -0
- data/lib/active_rdf/objectmanager/object_manager.rb +119 -0
- data/lib/active_rdf/objectmanager/ordered_set.rb +116 -0
- data/lib/active_rdf/objectmanager/property_list.rb +76 -0
- data/lib/active_rdf/objectmanager/resource.rb +609 -0
- data/lib/active_rdf/objectmanager/resource_like.rb +28 -0
- data/lib/active_rdf/queryengine/ntriples_parser.rb +90 -0
- data/lib/active_rdf/queryengine/query.rb +245 -0
- data/lib/active_rdf/queryengine/query2jars2.rb +22 -0
- data/lib/active_rdf/queryengine/query2sparql.rb +139 -0
- data/lib/active_rdf_helpers.rb +30 -0
- data/lib/active_rdf_log.rb +100 -0
- data/test/common.rb +119 -0
- data/test/directaccess/test_direct_access.rb +64 -0
- data/test/federation/test_connection_pool.rb +86 -0
- data/test/federation/test_federation_manager.rb +145 -0
- data/test/objectmanager/test_literal.rb +52 -0
- data/test/objectmanager/test_namespace.rb +83 -0
- data/test/objectmanager/test_object_manager.rb +96 -0
- data/test/objectmanager/test_ordered_set.rb +110 -0
- data/test/objectmanager/test_resource_reading.rb +150 -0
- data/test/objectmanager/test_resource_writing.rb +39 -0
- data/test/objectmanager/test_talia_syntax.rb +68 -0
- data/test/queryengine/my_external_resource.rb +24 -0
- data/test/queryengine/test_external_resource_class.rb +49 -0
- data/test/queryengine/test_ntriples_parser.rb +71 -0
- data/test/queryengine/test_query.rb +55 -0
- data/test/queryengine/test_query2jars2.rb +51 -0
- data/test/queryengine/test_query2sparql.rb +76 -0
- data/test/queryengine/test_query_engine.rb +52 -0
- data/test/test_adapters.rb +58 -0
- metadata +266 -0
@@ -0,0 +1,57 @@
|
|
1
|
+
This is the ActiveRDF adapter to the Jena RDF library.
|
2
|
+
|
3
|
+
Features:
|
4
|
+
* supports memory, file and database stores; database
|
5
|
+
stores can be configured both with datasources and raw connection
|
6
|
+
parameters. Jena currently supports Oracle, MySQL, HSQLDB,
|
7
|
+
PostgreSQL, MS SQL, and Derby.
|
8
|
+
* supports reasoners, including Pellet and the built-in Jena reasoners
|
9
|
+
* supports Lucene query support in ARQ
|
10
|
+
(since LARQ doesn't allow you to add statements
|
11
|
+
and reindex only those statements, whenever we've add a statement to a
|
12
|
+
triple store, we must rebuild the index at query time)
|
13
|
+
|
14
|
+
Requirements:
|
15
|
+
* JRuby is required to natively execute Jena
|
16
|
+
* the application can only use pure ruby gems
|
17
|
+
|
18
|
+
License:
|
19
|
+
included LGPL license (version 2 or later).
|
20
|
+
|
21
|
+
|
22
|
+
------------
|
23
|
+
|
24
|
+
Installing and running Activerdf with Jena:
|
25
|
+
=============================
|
26
|
+
|
27
|
+
Download the newest JRuby binary distribution from http://jruby.codehaus.org/
|
28
|
+
|
29
|
+
Unpack it somewhere, e.g. /usr/local/jruby
|
30
|
+
|
31
|
+
Then set your environement:
|
32
|
+
|
33
|
+
export JRUBY_HOME=/usr/local/jruby
|
34
|
+
export JAVA_HOM=/path/to/java/home
|
35
|
+
export PATH=$JRUBY_HOME/bin:$PATH
|
36
|
+
|
37
|
+
check if you now have the correct jruby commands in your path:
|
38
|
+
which jruby -> /usr/local/jruby/bin/jruby
|
39
|
+
which gem -> /usr/local/jruby/bin/gem
|
40
|
+
|
41
|
+
Now you can install rails and activerdf:
|
42
|
+
gem install rails --include-dependencies --no-rdoc --no-ri
|
43
|
+
gem install activerdf --include-dependencies
|
44
|
+
gem install activerdf_jena --include-dependencies
|
45
|
+
|
46
|
+
Create an instance of the jena adapter with simple file based persistence, and load some data into it:
|
47
|
+
|
48
|
+
this_dir = File.dirname(File.expand_path(__FILE__))
|
49
|
+
adapter = ConnectionPool.add_data_source(:type => :jena,
|
50
|
+
:model => "superfunky",
|
51
|
+
:file => this_dir + "/jena_persistence")
|
52
|
+
adapter.load("file://" + this_dir + "/test_data.rdf", :format => :rdfxml, :into => :default_model )
|
53
|
+
|
54
|
+
For more information please see the RDoc, the adapter source code and the unit tests. Go to /path/to/jruby/lib/ruby/gems/1.8/gems/activerdf_jena-0.1/test/ for that.
|
55
|
+
|
56
|
+
For more information see
|
57
|
+
http://wiki.activerdf.org/GettingStartedGuide
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'meta_project'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/clean'
|
5
|
+
require 'rake/gempackagetask'
|
6
|
+
require 'rake/contrib/xforge'
|
7
|
+
require '../tools/rakehelp'
|
8
|
+
require 'rubygems'
|
9
|
+
require 'fileutils'
|
10
|
+
include FileUtils
|
11
|
+
|
12
|
+
$version = IO.read('VERSION').strip
|
13
|
+
$name = 'activerdf_jena'
|
14
|
+
$project = MetaProject::Project::XForge::RubyForge.new('activerdf')
|
15
|
+
$distdir = "#$name-#$version"
|
16
|
+
|
17
|
+
# setup tests and rdoc files
|
18
|
+
setup_tests
|
19
|
+
setup_clean ["pkg", "lib/*.bundle", "*.gem", ".config"]
|
20
|
+
|
21
|
+
# default task: install
|
22
|
+
desc 'test and package gem'
|
23
|
+
task :default => :install
|
24
|
+
|
25
|
+
# define package task
|
26
|
+
setup_gem($name, $version) do |spec|
|
27
|
+
spec.summary = 'ActiveRDF adapter to the Jena RDF store'
|
28
|
+
spec.description = spec.summary
|
29
|
+
spec.author = 'Karsten Huneycutt and Benjamin Heitmann'
|
30
|
+
spec.email = 'benjamin.heitmann@deri.org'
|
31
|
+
spec.homepage = 'http://www.activerdf.org'
|
32
|
+
spec.platform = Gem::Platform::RUBY
|
33
|
+
spec.autorequire = 'active_rdf'
|
34
|
+
spec.add_dependency('gem_plugin', '>= 0.2.1')
|
35
|
+
spec.add_dependency('activerdf', '>= 1.6.4')
|
36
|
+
spec.add_dependency('activerdf_sparql', '>= 1.3.3')
|
37
|
+
spec.autorequire = 'init.rb'
|
38
|
+
spec.files += Dir['ext/**/*.jar']
|
39
|
+
end
|
40
|
+
|
41
|
+
begin
|
42
|
+
require 'rcov/rcovtask'
|
43
|
+
Rcov::RcovTask.new do |t|
|
44
|
+
t.test_files = FileList["activerdf-*/test/**/*.rb"]
|
45
|
+
t.verbose = true
|
46
|
+
end
|
47
|
+
rescue LoadError
|
48
|
+
end
|
49
|
+
|
50
|
+
# define test_all task
|
51
|
+
Rake::TestTask.new do |t|
|
52
|
+
t.name = :test_all
|
53
|
+
t.test_files = FileList["test/**/*.rb", "activerdf-*/test/**/*.rb"]
|
54
|
+
end
|
55
|
+
|
56
|
+
task :verify_rubyforge do
|
57
|
+
raise "RUBYFORGE_USER environment variable not set!" unless ENV['RUBYFORGE_USER']
|
58
|
+
raise "RUBYFORGE_PASSWORD environment variable not set!" unless ENV['RUBYFORGE_PASSWORD']
|
59
|
+
end
|
60
|
+
|
61
|
+
desc "release #$name-#$version gem on RubyForge"
|
62
|
+
task :release => [ :clean, :verify_rubyforge, :package ] do
|
63
|
+
release_files = FileList["pkg/#$distdir.gem"]
|
64
|
+
Rake::XForge::Release.new($project) do |release|
|
65
|
+
release.user_name = ENV['RUBYFORGE_USER']
|
66
|
+
release.password = ENV['RUBYFORGE_PASSWORD']
|
67
|
+
release.files = release_files.to_a
|
68
|
+
release.release_name = "#$name #$version"
|
69
|
+
release.package_name = "activerdf"
|
70
|
+
release.release_notes = ""
|
71
|
+
|
72
|
+
changes = []
|
73
|
+
File.open("CHANGELOG") do |file|
|
74
|
+
current = true
|
75
|
+
|
76
|
+
file.each do |line|
|
77
|
+
line.chomp!
|
78
|
+
if current and line =~ /^==/
|
79
|
+
current = false; next
|
80
|
+
end
|
81
|
+
break if line.empty? and not current
|
82
|
+
changes << line
|
83
|
+
end
|
84
|
+
end
|
85
|
+
release.release_changes = changes.join("\n")
|
86
|
+
end
|
87
|
+
end
|
data/activerdf-jena/TODO
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
administrative tasks:
|
2
|
+
=========================
|
3
|
+
* add API documentation to code
|
4
|
+
* update CHANGELOG before release
|
5
|
+
* describe which features work right now, and which should not be used
|
6
|
+
* check what problems eyals rake file is causing for karsten
|
7
|
+
|
8
|
+
|
9
|
+
before initial release:
|
10
|
+
=======================
|
11
|
+
* test persistance to derby and maybe mysql and postgres
|
12
|
+
|
13
|
+
after release:
|
14
|
+
==============
|
15
|
+
* get pellet to working (generally and specially with queries)
|
16
|
+
* test reasoning
|
17
|
+
* quads
|
18
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
0.2
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#
|
2
|
+
# Author: Karsten Huneycutt
|
3
|
+
# Copyright 2007 Valkeir Corporation
|
4
|
+
# License: LGPL
|
5
|
+
#
|
6
|
+
# add the directory in which this file is located to the ruby loadpath
|
7
|
+
file =
|
8
|
+
if File.symlink?(__FILE__)
|
9
|
+
File.readlink(__FILE__)
|
10
|
+
else
|
11
|
+
__FILE__
|
12
|
+
end
|
13
|
+
$: << File.dirname(File.expand_path(file))
|
14
|
+
|
15
|
+
java_dir = File.expand_path(File.join(File.dirname(File.expand_path(file)), "..", "..", "ext"))
|
16
|
+
|
17
|
+
Dir.foreach(java_dir) do |jar|
|
18
|
+
$CLASSPATH << File.join(java_dir, jar) if jar =~ /.jar$/
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'jena'
|
22
|
+
require 'ng4j'
|
23
|
+
require 'pellet'
|
24
|
+
require 'lucene'
|
25
|
+
require 'jena_adapter'
|
26
|
+
require 'ng4j_adapter'
|
@@ -0,0 +1,59 @@
|
|
1
|
+
#
|
2
|
+
# Author: Karsten Huneycutt
|
3
|
+
# Copyright 2007 Valkeir Corporation
|
4
|
+
# License: LGPL
|
5
|
+
#
|
6
|
+
require 'java'
|
7
|
+
|
8
|
+
module Jena
|
9
|
+
|
10
|
+
module Ontology
|
11
|
+
include_package('com.hp.hpl.jena.ontology')
|
12
|
+
end
|
13
|
+
|
14
|
+
module Model
|
15
|
+
include_package('com.hp.hpl.jena.rdf.model')
|
16
|
+
end
|
17
|
+
|
18
|
+
module DB
|
19
|
+
include_package('com.hp.hpl.jena.db')
|
20
|
+
|
21
|
+
# this maps downcased Jena database types into drivers
|
22
|
+
DRIVER_MAP = {
|
23
|
+
'oracle' => 'oracle.jdbc.Driver',
|
24
|
+
'mysql' => 'com.mysql.jdbc.Driver',
|
25
|
+
'derby' => 'org.apache.derby.jdbc.EmbeddedDriver',
|
26
|
+
'postgresql' => 'org.postgresql.Driver',
|
27
|
+
'hsql' => 'org.hsqldb.jdbcDriver',
|
28
|
+
'mssql' => 'com.microsoft.sqlserver.jdbc.SQLServerDriver'
|
29
|
+
}
|
30
|
+
|
31
|
+
DRIVER_MAP.each do |name, driver|
|
32
|
+
av = "#{name}_available"
|
33
|
+
(class << self ; self ; end).send(:bool_accessor, av.to_sym)
|
34
|
+
begin
|
35
|
+
java.lang.Class.forName driver
|
36
|
+
Jena::DB.send("#{av}=", true)
|
37
|
+
rescue
|
38
|
+
Jena::DB.send("#{av}=", false)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module Query
|
44
|
+
include_package('com.hp.hpl.jena.query')
|
45
|
+
end
|
46
|
+
|
47
|
+
module Reasoner
|
48
|
+
include_package('com.hp.hpl.jena.reasoner')
|
49
|
+
end
|
50
|
+
|
51
|
+
module Datatypes
|
52
|
+
include_package('com.hp.hpl.jena.datatypes')
|
53
|
+
end
|
54
|
+
|
55
|
+
module Graph
|
56
|
+
include_package('com.hp.hpl.jena.graph')
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,515 @@
|
|
1
|
+
#
|
2
|
+
# Author: Karsten Huneycutt
|
3
|
+
# Copyright 2007 Valkeir Corporation
|
4
|
+
# License: LGPL
|
5
|
+
#
|
6
|
+
|
7
|
+
class JenaAdapter < ActiveRdfAdapter
|
8
|
+
|
9
|
+
class JenaAdapterConfigurationError < StandardError
|
10
|
+
end
|
11
|
+
|
12
|
+
class DataSourceDBConnection < Jena::DB::DBConnection
|
13
|
+
|
14
|
+
attr_accessor :datasource, :connection
|
15
|
+
|
16
|
+
def initialize(datasource, type)
|
17
|
+
if datasource.kind_of? javax.sql.DataSource
|
18
|
+
self.datasource = datasource
|
19
|
+
else
|
20
|
+
self.datasource = javax.naming.InitialContext.new.lookup(datasource)
|
21
|
+
end
|
22
|
+
self.setDatabaseType(type)
|
23
|
+
end
|
24
|
+
|
25
|
+
def getConnection
|
26
|
+
if !self.connection || !valid_connection?(self.connection)
|
27
|
+
self.connection = self.datasource.getConnection
|
28
|
+
end
|
29
|
+
self.connection
|
30
|
+
end
|
31
|
+
|
32
|
+
def close
|
33
|
+
self.datasource = nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def valid_connection?(cnxn)
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
ConnectionPool.register_adapter(:jena, self)
|
43
|
+
|
44
|
+
bool_accessor :keyword_search, :reasoning
|
45
|
+
bool_accessor :lucene_index_behind
|
46
|
+
attr_accessor :ontology_type, :model_name, :reasoner, :connection
|
47
|
+
attr_accessor :model_maker, :base_model, :model, :lucene_index
|
48
|
+
attr_accessor :root_directory
|
49
|
+
|
50
|
+
# :database
|
51
|
+
# either use :url, :type, :username, AND :password (for a
|
52
|
+
# regular connection) OR :datasource AND :type (for a container
|
53
|
+
# connection), default to memory data store
|
54
|
+
# example for a derby connection:
|
55
|
+
# :database => {:url => "jdbc:derby:superfunky;create=true", :type => "Derby", :username => "", :password => ""}
|
56
|
+
# :file
|
57
|
+
# database wins over this, this wins over memory store. parameter is
|
58
|
+
# a string or file indicating the root directory for all files.
|
59
|
+
# :model
|
60
|
+
# name of model to use, default is jena's default
|
61
|
+
# :ontology
|
62
|
+
# set to language type if this needs to be viewed as an ontology,
|
63
|
+
# default nil, available :owl, :owl_dl, :owl_lite, :rdfs
|
64
|
+
# pellet only supports owl reasoning.
|
65
|
+
# :reasoner
|
66
|
+
# set to reasoner to use -- default nil (none). options: :pellet,
|
67
|
+
# :transitive, :rdfs, :rdfs_simple, :owl_micro, :owl_mini, :owl,
|
68
|
+
# :generic_rule
|
69
|
+
# :lucene
|
70
|
+
# set to true to enable true lucene indexing of this store, default false
|
71
|
+
def initialize(params = {})
|
72
|
+
dbparams = params[:database]
|
73
|
+
self.ontology_type = params[:ontology]
|
74
|
+
self.reasoner = params[:reasoner]
|
75
|
+
self.keyword_search = params[:lucene]
|
76
|
+
|
77
|
+
# if the model name is not provided and file persistence is used, then jena just
|
78
|
+
# creates random files in the tmp dir. not good, as we need to know the model name
|
79
|
+
# to have persistence
|
80
|
+
if params[:model]
|
81
|
+
self.model_name = params[:model]
|
82
|
+
else
|
83
|
+
self.model_name = "default"
|
84
|
+
end
|
85
|
+
|
86
|
+
if params[:file]
|
87
|
+
if params[:file].respond_to? :path
|
88
|
+
self.root_directory = File.expand_path(params[:file].path)
|
89
|
+
else
|
90
|
+
self.root_directory = params[:file]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# do some sanity checking
|
95
|
+
if self.keyword_search? && !LuceneARQ.lucene_available?
|
96
|
+
raise JenaAdapterConfigurationError, "Lucene requested but is not available"
|
97
|
+
end
|
98
|
+
|
99
|
+
if self.reasoner == :pellet && !Pellet.pellet_available?
|
100
|
+
raise JenaAdapterConfigurationError, "Pellet requested but not available"
|
101
|
+
end
|
102
|
+
|
103
|
+
if self.reasoner && !self.ontology_type
|
104
|
+
raise JenaAdapterConfigurationError, "Ontology model needed for reasoner"
|
105
|
+
end
|
106
|
+
|
107
|
+
if dbparams
|
108
|
+
if dbparams[:datasource]
|
109
|
+
self.connection = DataSourceDBConnection.new(dbparams[:datasource],
|
110
|
+
dbparams[:type])
|
111
|
+
else
|
112
|
+
begin
|
113
|
+
if !Jena::DB.send("#{dbparams[:type].downcase}_available?")
|
114
|
+
raise JenaAdapterConfigurationError, "database type #{dbparams[:type]} not available"
|
115
|
+
end
|
116
|
+
rescue NameError
|
117
|
+
raise JenaAdapterConfigurationError, "database type #{dbparams[:type]} not recognized"
|
118
|
+
end
|
119
|
+
|
120
|
+
self.connection = Jena::DB::DBConnection.new(dbparams[:url],
|
121
|
+
dbparams[:username],
|
122
|
+
dbparams[:password],
|
123
|
+
dbparams[:type])
|
124
|
+
end
|
125
|
+
|
126
|
+
self.model_maker = Jena::Model::ModelFactory.createModelRDBMaker(connection)
|
127
|
+
|
128
|
+
elsif self.root_directory
|
129
|
+
self.model_maker = Jena::Model::ModelFactory.createFileModelMaker(self.root_directory)
|
130
|
+
else
|
131
|
+
self.model_maker = Jena::Model::ModelFactory.createMemModelMaker
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
self.base_model = self.model_maker.openModel(model_name)
|
136
|
+
|
137
|
+
if self.ontology_type
|
138
|
+
rf = map_reasoner_factory(self.reasoner)
|
139
|
+
onturi = map_ontology_type(self.ontology_type)
|
140
|
+
|
141
|
+
spec =
|
142
|
+
Jena::Ontology::OntModelSpec.new(self.model_maker,
|
143
|
+
Jena::Ontology::OntDocumentManager.new,
|
144
|
+
rf, onturi)
|
145
|
+
|
146
|
+
self.model = Jena::Model::ModelFactory.
|
147
|
+
createOntologyModel(spec, self.base_model)
|
148
|
+
self.reasoning = true
|
149
|
+
else
|
150
|
+
self.model = self.base_model
|
151
|
+
self.reasoning = false
|
152
|
+
end
|
153
|
+
|
154
|
+
self.reads = true
|
155
|
+
self.writes = true
|
156
|
+
|
157
|
+
self
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
def size
|
162
|
+
self.model.size
|
163
|
+
end
|
164
|
+
|
165
|
+
def dump
|
166
|
+
it = self.model.listStatements
|
167
|
+
res = ""
|
168
|
+
while it.hasNext
|
169
|
+
res += it.nextStatement.asTriple.toString
|
170
|
+
res += " . \n"
|
171
|
+
end
|
172
|
+
res
|
173
|
+
end
|
174
|
+
|
175
|
+
def close
|
176
|
+
ConnectionPool.remove_data_source(self)
|
177
|
+
self.model.close
|
178
|
+
self.connection.close unless self.connection.nil?
|
179
|
+
end
|
180
|
+
|
181
|
+
def clear
|
182
|
+
self.model.removeAll
|
183
|
+
self.model.prepare if self.model.respond_to? :prepare
|
184
|
+
self.model.rebind if self.model.respond_to? :rebind
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
def delete(subject, predicate, object, context = nil)
|
189
|
+
self.lucene_index_behind = true
|
190
|
+
mod = get_model_for_context(context)
|
191
|
+
s = (is_wildcard?(subject) ? nil : build_subject(subject, mod))
|
192
|
+
p = (is_wildcard?(predicate) ? nil : build_predicate(predicate, mod))
|
193
|
+
o = (is_wildcard?(object) ? nil : build_object(object, mod))
|
194
|
+
mod.removeAll(s, p, o)
|
195
|
+
mod.prepare if mod.respond_to? :prepare
|
196
|
+
mod.rebind if mod.respond_to? :rebind
|
197
|
+
end
|
198
|
+
|
199
|
+
def add(subject, predicate, object, context = nil)
|
200
|
+
self.lucene_index_behind = true
|
201
|
+
mod = get_model_for_context(context)
|
202
|
+
mod.add(build_statement(subject, predicate, object))
|
203
|
+
mod.prepare if mod.respond_to? :prepare
|
204
|
+
mod.rebind if mod.respond_to? :rebind
|
205
|
+
end
|
206
|
+
|
207
|
+
def flush
|
208
|
+
# no-op
|
209
|
+
end
|
210
|
+
|
211
|
+
|
212
|
+
# :format
|
213
|
+
# format -- :ntriples, :n3, or :rdfxml, default :rdfxml
|
214
|
+
# :into
|
215
|
+
# either the name of a model, :default_model for the main model, or
|
216
|
+
# :submodel to load into an anonymous memory model, default is :submodel
|
217
|
+
# if this is an ontology, :default_model if it's not.
|
218
|
+
# :rebind
|
219
|
+
# rebind with the inferencer, default true; no effect if no inferencer
|
220
|
+
def load(uri, params = {})
|
221
|
+
into = params[:into] ? params[:into] :
|
222
|
+
(self.ontology_type ? :submodel : :default_model)
|
223
|
+
format = params[:format] ? params[:format] : :rdfxml
|
224
|
+
rebind = params[:rebind] ? params[:rebind] : true
|
225
|
+
|
226
|
+
jena_format =
|
227
|
+
case format
|
228
|
+
when :rdfxml
|
229
|
+
"RDF/XML"
|
230
|
+
when :ntriples
|
231
|
+
"N-TRIPLE"
|
232
|
+
when :n3
|
233
|
+
"N3"
|
234
|
+
end
|
235
|
+
|
236
|
+
case into
|
237
|
+
when :default_model
|
238
|
+
self.model.read(uri, jena_format)
|
239
|
+
|
240
|
+
when :submodel
|
241
|
+
self.model.addSubModel(Jena::Model::ModelFactory.createDefaultModel.read(uri, jena_format))
|
242
|
+
|
243
|
+
else
|
244
|
+
self.model.addSubModel(self.model_maker.createModel(into).read(uri, jena_format))
|
245
|
+
end
|
246
|
+
|
247
|
+
if rebind && self.reasoner && self.model.respond_to?(:rebind)
|
248
|
+
self.model.rebind
|
249
|
+
end
|
250
|
+
|
251
|
+
self.lucene_index_behind = true
|
252
|
+
|
253
|
+
end
|
254
|
+
|
255
|
+
# this method gets called by the ActiveRDF query engine
|
256
|
+
def query(query, params = {})
|
257
|
+
|
258
|
+
if self.keyword_search? && query.keyword?
|
259
|
+
|
260
|
+
# duplicate the query
|
261
|
+
query_with_keywords = query.dup
|
262
|
+
|
263
|
+
# now duplicate the where stuff so we can fiddle with it...
|
264
|
+
# this is GROSS -- fix this if Query ever sprouts a proper
|
265
|
+
# deep copy or a where_clauses setter
|
266
|
+
query_with_keywords.instance_variable_set("@where_clauses", query.where_clauses.dup)
|
267
|
+
|
268
|
+
# now, for each of the keyword clauses, set up the search
|
269
|
+
query.keywords.each do |var, keyword|
|
270
|
+
# use this if activerdf expects the subject to come back and not the
|
271
|
+
# literal and using indexbuilderstring
|
272
|
+
#query.where("lucene_literal_#{var}".to_sym, LuceneARQ::KEYWORD_PREDICATE, keyword)
|
273
|
+
#query.where(var, "lucene_property_#{var}".to_sym, "lucene_literal_#{var}".to_sym)
|
274
|
+
|
275
|
+
# use this if activerdf expects the literal to come back, not the
|
276
|
+
# subject, or if using indexbuildersubject (which makes the subject
|
277
|
+
# come back instead of the literal
|
278
|
+
query_with_keywords.where(var, RDFS::Resource.new(LuceneARQ::KEYWORD_PREDICATE), keyword)
|
279
|
+
|
280
|
+
end
|
281
|
+
|
282
|
+
else
|
283
|
+
query_with_keywords = query
|
284
|
+
end
|
285
|
+
|
286
|
+
# jena knows about lucene, so use the query object that has the keyword
|
287
|
+
# search requests expanded.
|
288
|
+
jena_results = query_jena(query_with_keywords)
|
289
|
+
|
290
|
+
# use the conjunctive query facility in pellet to get additional
|
291
|
+
# answers, if we're using pellet and we don't have a pure keyword
|
292
|
+
# query
|
293
|
+
if self.reasoner == :pellet && query.where_clauses.size > 0
|
294
|
+
# pellet doesn't know about lucene, so we use the original query
|
295
|
+
# object
|
296
|
+
pellet_results = query_pellet(query)
|
297
|
+
results = (jena_results + pellet_results).uniq!
|
298
|
+
else
|
299
|
+
results = jena_results
|
300
|
+
end
|
301
|
+
|
302
|
+
if query.ask?
|
303
|
+
return [[true]] if results.size > 0
|
304
|
+
return [[false]]
|
305
|
+
end
|
306
|
+
|
307
|
+
if query.count?
|
308
|
+
return results.size
|
309
|
+
end
|
310
|
+
|
311
|
+
results
|
312
|
+
|
313
|
+
end
|
314
|
+
|
315
|
+
# ==========================================================================
|
316
|
+
# put private methods here to seperate api methods from the
|
317
|
+
# inner workings of the adapter
|
318
|
+
private
|
319
|
+
|
320
|
+
def map_ontology_type(type)
|
321
|
+
case type
|
322
|
+
when :rdfs
|
323
|
+
'http://www.w3.org/2000/01/rdf-schema#'
|
324
|
+
when :owl
|
325
|
+
'http://www.w3.org/2002/07/owl#'
|
326
|
+
when :owl_dl
|
327
|
+
'http://www.w3.org/TR/owl-features/#term_OWLDL'
|
328
|
+
when :owl_lite
|
329
|
+
'http://www.w3.org/TR/owl-features/#term_OWLLite'
|
330
|
+
else
|
331
|
+
type
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
|
336
|
+
def map_reasoner_factory(type)
|
337
|
+
case type
|
338
|
+
when :pellet
|
339
|
+
Pellet.reasoner_factory
|
340
|
+
|
341
|
+
when :transitive
|
342
|
+
com.hp.hpl.jena.reasoner.transitiveReasoner.TransitiveReasonerFactory.theInstance
|
343
|
+
|
344
|
+
when :rdfs
|
345
|
+
com.hp.hpl.jena.reasoner.rulesys.RDFSFBRuleReasonerFactory.theInstance
|
346
|
+
|
347
|
+
when :rdfs_simple
|
348
|
+
com.hp.hpl.jena.reasoner.rulesys.RDFSRuleReasonerFactory.theInstance
|
349
|
+
|
350
|
+
when :owl_micro
|
351
|
+
com.hp.hpl.jena.reasoner.rulesys.OWLMicroReasonerFactory.theInstance
|
352
|
+
|
353
|
+
when :owl_mini
|
354
|
+
com.hp.hpl.jena.reasoner.rulesys.OWLMiniReasonerFactory.theInstance
|
355
|
+
|
356
|
+
when :owl
|
357
|
+
com.hp.hpl.jena.reasoner.rulesys.OWLFBRuleReasonerFactory.theInstance
|
358
|
+
|
359
|
+
when :generic_rule
|
360
|
+
com.hp.hpl.jena.reasoner.rulesys.GenericRuleReasonerFactory.theInstance
|
361
|
+
|
362
|
+
else
|
363
|
+
type
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
def appropriate_model(submodel)
|
368
|
+
(submodel && (submodel != self.model))? submodel : self.model
|
369
|
+
end
|
370
|
+
|
371
|
+
def build_object(object, submodel = nil)
|
372
|
+
mod = appropriate_model(submodel)
|
373
|
+
if object.respond_to? :uri
|
374
|
+
o = mod.getResource(object.uri)
|
375
|
+
else
|
376
|
+
#xlate to literal
|
377
|
+
if !object.kind_of? Literal
|
378
|
+
objlit = Literal.new object
|
379
|
+
else
|
380
|
+
objlit = object
|
381
|
+
end
|
382
|
+
|
383
|
+
if objlit.type
|
384
|
+
type = Jena::Datatypes::TypeMapper.getInstance.getTypeByName(objlit.type.uri)
|
385
|
+
o = mod.createTypedLiteral(objlit.value, type)
|
386
|
+
elsif objlit.language
|
387
|
+
o = mod.createLiteral(objlit.value, objlit.language)
|
388
|
+
else
|
389
|
+
o = mod.createTypedLiteral(objlit.value, nil)
|
390
|
+
end
|
391
|
+
end
|
392
|
+
return o
|
393
|
+
end
|
394
|
+
|
395
|
+
def build_subject(subject, submodel = nil)
|
396
|
+
# ensure it exists in the parent model
|
397
|
+
self.model.getResource(subject.uri) if submodel
|
398
|
+
appropriate_model(submodel).getResource(subject.uri)
|
399
|
+
end
|
400
|
+
|
401
|
+
def build_predicate(predicate, submodel = nil)
|
402
|
+
appropriate_model(submodel).getProperty(predicate.uri)
|
403
|
+
end
|
404
|
+
|
405
|
+
def build_statement(subject, predicate, object, submodel = nil)
|
406
|
+
s = build_subject(subject, submodel)
|
407
|
+
p = build_predicate(predicate, submodel)
|
408
|
+
o = build_object(object, submodel)
|
409
|
+
mod = submodel ? submodel : self.model
|
410
|
+
mod.createStatement(s, p, o)
|
411
|
+
end
|
412
|
+
|
413
|
+
|
414
|
+
def is_wildcard?(thing)
|
415
|
+
(thing == nil) || thing.kind_of?(Symbol)
|
416
|
+
end
|
417
|
+
|
418
|
+
def get_model_for_context(context)
|
419
|
+
if (context == nil || context == self.model_name)
|
420
|
+
self.model
|
421
|
+
else
|
422
|
+
subm = self.model_maker.openModel(context)
|
423
|
+
self.model.addSubModel(subm)
|
424
|
+
subm
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
def query_jena(query)
|
429
|
+
query_sparql = translate(query)
|
430
|
+
|
431
|
+
qexec = Jena::Query::QueryExecutionFactory.create(query_sparql, self.model)
|
432
|
+
|
433
|
+
# PROBABLY A VERY EXPENSIVE OPERATION (rebuilds lucene index if ANYTHING
|
434
|
+
# changed -- this seems to be the only way, since you have to close
|
435
|
+
# the index after you build it...
|
436
|
+
if query.keyword? && self.keyword_search?
|
437
|
+
LuceneARQ::LARQ.setDefaultIndex(qexec.getContext, retrieve_lucene_index)
|
438
|
+
end
|
439
|
+
|
440
|
+
begin
|
441
|
+
results = perform_query(query, qexec)
|
442
|
+
ensure
|
443
|
+
qexec.close
|
444
|
+
end
|
445
|
+
|
446
|
+
results
|
447
|
+
end
|
448
|
+
|
449
|
+
def query_pellet(query)
|
450
|
+
query_sparql = translate(query)
|
451
|
+
jena_query = Jena::Query::QueryFactory.create(query_sparql)
|
452
|
+
|
453
|
+
# bail if not a select
|
454
|
+
return [] if !jena_query.isSelectType
|
455
|
+
|
456
|
+
qexec = Pellet::Query::PelletQueryExecution.new(jena_query, self.model)
|
457
|
+
|
458
|
+
begin
|
459
|
+
results = perform_query(query, qexec)
|
460
|
+
ensure
|
461
|
+
qexec.close
|
462
|
+
end
|
463
|
+
|
464
|
+
results
|
465
|
+
end
|
466
|
+
|
467
|
+
def perform_query(query, qexec)
|
468
|
+
results = qexec.execSelect
|
469
|
+
arr_results = []
|
470
|
+
|
471
|
+
while results.hasNext
|
472
|
+
row = results.nextSolution
|
473
|
+
res_row = []
|
474
|
+
query.select_clauses.each do |kw|
|
475
|
+
thing = row.get(kw.to_s)
|
476
|
+
if thing.kind_of? Jena::Model::Resource
|
477
|
+
if thing.isAnon
|
478
|
+
res_row << BNode.new(thing.getId.to_s)
|
479
|
+
else
|
480
|
+
res_row << RDFS::Resource.new(thing.to_s)
|
481
|
+
end
|
482
|
+
elsif thing.kind_of? Jena::Model::Literal
|
483
|
+
if thing.getLanguage == "" and thing.getDatatypeURI.nil?
|
484
|
+
# plain literal
|
485
|
+
res_row << thing.getString
|
486
|
+
elsif thing.getLanguage == ""
|
487
|
+
# datatyped literal
|
488
|
+
res_row << Literal.new(thing.getValue, RDFS::Resource.new(thing.getDatatypeURI))
|
489
|
+
elsif thing.getDatatypeURI.nil?
|
490
|
+
# language tagged literal
|
491
|
+
res_row << Literal.new(thing.getLexicalForm, "@" + thing.getLanguage)
|
492
|
+
else
|
493
|
+
raise ActiveRdfError, "Jena Sparql returned a strange literal"
|
494
|
+
end
|
495
|
+
else
|
496
|
+
raise ActiveRdfError, "Returned thing other than resource or literal"
|
497
|
+
end
|
498
|
+
end
|
499
|
+
arr_results << res_row
|
500
|
+
end
|
501
|
+
arr_results
|
502
|
+
end
|
503
|
+
|
504
|
+
def retrieve_lucene_index
|
505
|
+
if self.lucene_index_behind?
|
506
|
+
builder = LuceneARQ::IndexBuilderSubject.new
|
507
|
+
builder.indexStatements(self.model.listStatements)
|
508
|
+
builder.closeForWriting
|
509
|
+
self.lucene_index = builder.getIndex
|
510
|
+
self.lucene_index_behind = false
|
511
|
+
end
|
512
|
+
self.lucene_index
|
513
|
+
end
|
514
|
+
|
515
|
+
end
|