diff --git a/appveyor.yml b/appveyor.yml
index fb77ba7d2..dfdb09941 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,10 +1,15 @@
version: 1.0.{build}
+services:
+- mysql
+- postgresql96
+
init:
- cmd: java -version 2>&1 | find "version"
-clone_script:
-- cmd: git clone --recursive https://github.com/OpenRefine/OpenRefine
+clone_depth: 5
+skip_branch_with_pr: true
+
environment:
ANT_HOME: C:\apache-ant-1.10.1
JAVA_HOME: C:\Program Files (x86)\Java\jdk1.8.0
@@ -20,17 +25,24 @@ cache:
- apache-ant-1.10.1-bin.zip
build: off
-# scripts to run before build
-before_build:
+# scripts to run before test
+before_test:
- cmd: echo Running scripts before build...
+- cmd: |-
+ PATH=C:\Program Files\PostgreSQL\9.6\bin\;C:\Program Files\MySQL\MySQL Server 5.7\bin\;%PATH%
+ SET MYSQL_PWD=Password12!
+ mysql -u root --password=Password12! -e "create database test_db;"
+ mysql -u root test_db --password=Password12! < extensions\database\test\conf\travis-mysql.sql
+ echo "localhost:*:test_db:postgres:Password12!" > C:\Program Files\PostgreSQL\9.6\pgpass.conf
+ echo "localhost:*:test_db:postgres:Password12!" > pgpass.conf
+ echo "localhost:*:test_db:postgres:Password12!" > %userprofile%\pgpass.conf
+ SET PGPASSFILE=C:\Program Files\PostgreSQL\9.6\pgpass.conf
+ SET PGPASSWORD=Password12!
+ SET PGUSER=postgres
+ createdb test_db
+ psql -U postgres test_db < extensions\database\test\conf\travis-pgsql.sql
-build_script:
-# - cmd: >-
-# echo Running refine build...
-#
-# cd OpenRefine
-#
-# refine build
+ copy extensions\database\test\conf\appveyor_tests.xml extensions\database\test\conf\tests.xml
test_script:
- cmd: echo Running test_script...
@@ -39,8 +51,6 @@ test_script:
path
- cd OpenRefine
-
refine server_test
-
+
refine extensions_test
diff --git a/broker/appengine/.classpath b/broker/appengine/.classpath
deleted file mode 100644
index 4f8426eef..000000000
--- a/broker/appengine/.classpath
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/broker/appengine/.project b/broker/appengine/.project
deleted file mode 100644
index 7b4860b54..000000000
--- a/broker/appengine/.project
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
- grefine-appengine-broker
-
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
- org.eclipse.ui.externaltools.ExternalToolBuilder
- full,incremental,
-
-
- LaunchConfigHandle
- <project>/.externalToolBuilders/com.google.gdt.eclipse.core.webAppProjectValidator.launch
-
-
-
-
- com.google.appengine.eclipse.core.enhancerbuilder
-
-
-
-
- org.eclipse.ui.externaltools.ExternalToolBuilder
- full,incremental,
-
-
- LaunchConfigHandle
- <project>/.externalToolBuilders/com.google.appengine.eclipse.core.projectValidator.launch
-
-
-
-
-
- org.eclipse.jdt.core.javanature
- com.google.appengine.eclipse.core.gaeNature
-
-
diff --git a/broker/core/.classpath b/broker/core/.classpath
deleted file mode 100644
index ef2854438..000000000
--- a/broker/core/.classpath
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/broker/core/.project b/broker/core/.project
deleted file mode 100644
index 4538782c6..000000000
--- a/broker/core/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
- grefine-broker
-
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
-
- org.eclipse.jdt.core.javanature
-
-
diff --git a/build.xml b/build.xml
index 4ad005eba..1135d638b 100644
--- a/build.xml
+++ b/build.xml
@@ -140,6 +140,10 @@
+
+
+
+
diff --git a/extensions/build.xml b/extensions/build.xml
index c2378e31e..ed44ecab7 100644
--- a/extensions/build.xml
+++ b/extensions/build.xml
@@ -13,6 +13,7 @@
+
@@ -21,10 +22,12 @@
+
+
diff --git a/extensions/database/.classpath b/extensions/database/.classpath
new file mode 100644
index 000000000..e0f342dff
--- /dev/null
+++ b/extensions/database/.classpath
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/extensions/database/.eclipse-pmd b/extensions/database/.eclipse-pmd
new file mode 100644
index 000000000..4a705f215
--- /dev/null
+++ b/extensions/database/.eclipse-pmd
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/extensions/database/.eslintrc.json b/extensions/database/.eslintrc.json
new file mode 100644
index 000000000..d15f78754
--- /dev/null
+++ b/extensions/database/.eslintrc.json
@@ -0,0 +1,28 @@
+{
+ "env": {
+ "browser": true,
+ "es6": true
+ },
+ "extends": "eslint:recommended",
+ "parserOptions": {
+ "sourceType": "module"
+ },
+ "rules": {
+ "indent": [
+ "error",
+ 4
+ ],
+ "linebreak-style": [
+ "error",
+ "unix"
+ ],
+ "quotes": [
+ "error",
+ "double"
+ ],
+ "semi": [
+ "error",
+ "always"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/extensions/sample/.project b/extensions/database/.project
similarity index 76%
rename from extensions/sample/.project
rename to extensions/database/.project
index 8fd50839d..350d6ee87 100644
--- a/extensions/sample/.project
+++ b/extensions/database/.project
@@ -1,6 +1,6 @@
- grefine-sample-extension
+ refine-database-extension
@@ -20,10 +20,16 @@
+
+ ch.acanda.eclipse.pmd.builder.PMDBuilder
+
+
+
org.eclipse.jdt.core.javanature
org.eclipse.wst.common.project.facet.core.nature
org.eclipse.wst.jsdt.core.jsNature
+ ch.acanda.eclipse.pmd.builder.PMDNature
diff --git a/extensions/gdata/.settings/.jsdtscope b/extensions/database/.settings/.jsdtscope
similarity index 100%
rename from extensions/gdata/.settings/.jsdtscope
rename to extensions/database/.settings/.jsdtscope
diff --git a/extensions/database/.settings/org.eclipse.jdt.core.prefs b/extensions/database/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 000000000..d6721a52c
--- /dev/null
+++ b/extensions/database/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,280 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6
+org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_assignment=0
+org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
+org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
+org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
+org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
+org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
+org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
+org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
+org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
+org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=33
+org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_after_package=1
+org.eclipse.jdt.core.formatter.blank_lines_before_field=0
+org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=1
+org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
+org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
+org.eclipse.jdt.core.formatter.blank_lines_before_method=1
+org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
+org.eclipse.jdt.core.formatter.blank_lines_before_package=1
+org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
+org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
+org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
+org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
+org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
+org.eclipse.jdt.core.formatter.comment.format_block_comments=true
+org.eclipse.jdt.core.formatter.comment.format_header=false
+org.eclipse.jdt.core.formatter.comment.format_html=true
+org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true
+org.eclipse.jdt.core.formatter.comment.format_line_comments=true
+org.eclipse.jdt.core.formatter.comment.format_source_code=true
+org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
+org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
+org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
+org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
+org.eclipse.jdt.core.formatter.comment.line_length=80
+org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
+org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
+org.eclipse.jdt.core.formatter.compact_else_if=true
+org.eclipse.jdt.core.formatter.continuation_indentation=2
+org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
+org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
+org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
+org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
+org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
+org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
+org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_empty_lines=false
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
+org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
+org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
+org.eclipse.jdt.core.formatter.indentation.size=8
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
+org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
+org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
+org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
+org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert
+org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
+org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
+org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
+org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
+org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
+org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
+org.eclipse.jdt.core.formatter.join_lines_in_comments=true
+org.eclipse.jdt.core.formatter.join_wrapped_lines=true
+org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
+org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=true
+org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
+org.eclipse.jdt.core.formatter.lineSplit=120
+org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
+org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
+org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
+org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
+org.eclipse.jdt.core.formatter.tabulation.char=space
+org.eclipse.jdt.core.formatter.tabulation.size=4
+org.eclipse.jdt.core.formatter.use_on_off_tags=true
+org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
+org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
+org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
diff --git a/extensions/database/.settings/org.eclipse.jdt.ui.prefs b/extensions/database/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 000000000..e3ddcd8c4
--- /dev/null
+++ b/extensions/database/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,4 @@
+#Mon Sep 27 15:02:46 PDT 2010
+eclipse.preferences.version=1
+formatter_profile=_Google Refine
+formatter_settings_version=11
diff --git a/extensions/database/.settings/org.eclipse.wst.common.project.facet.core.xml b/extensions/database/.settings/org.eclipse.wst.common.project.facet.core.xml
new file mode 100644
index 000000000..8ca4d66e9
--- /dev/null
+++ b/extensions/database/.settings/org.eclipse.wst.common.project.facet.core.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/extensions/database/.settings/org.eclipse.wst.jsdt.ui.superType.container b/extensions/database/.settings/org.eclipse.wst.jsdt.ui.superType.container
new file mode 100644
index 000000000..3bd5d0a48
--- /dev/null
+++ b/extensions/database/.settings/org.eclipse.wst.jsdt.ui.superType.container
@@ -0,0 +1 @@
+org.eclipse.wst.jsdt.launching.baseBrowserLibrary
\ No newline at end of file
diff --git a/extensions/database/.settings/org.eclipse.wst.jsdt.ui.superType.name b/extensions/database/.settings/org.eclipse.wst.jsdt.ui.superType.name
new file mode 100644
index 000000000..05bd71b6e
--- /dev/null
+++ b/extensions/database/.settings/org.eclipse.wst.jsdt.ui.superType.name
@@ -0,0 +1 @@
+Window
\ No newline at end of file
diff --git a/extensions/database/.travis.yml b/extensions/database/.travis.yml
new file mode 100644
index 000000000..800ed8b08
--- /dev/null
+++ b/extensions/database/.travis.yml
@@ -0,0 +1,24 @@
+language: java
+
+jdk:
+ - oraclejdk8
+
+addons:
+ mariadb: '10.0'
+
+services:
+ - mysql
+ - postgresql
+
+before_install:
+
+ - mysql -u root -e 'CREATE DATABASE test_db;'
+ - mysql -u root test_db < test/conf/travis-mysql.sql
+
+ - psql -c 'CREATE DATABASE test_db;' -U postgres
+ - psql -U postgres test_db < test/conf/travis-pgsql.sql
+
+
+script:
+ - ant test
+
diff --git a/extensions/database/README.md b/extensions/database/README.md
new file mode 100644
index 000000000..28231b7df
--- /dev/null
+++ b/extensions/database/README.md
@@ -0,0 +1,41 @@
+This project is an extension for OpenRefine that provides a way to import database data using JDBC.
+
+
+INSTALL
+
+1. Before installing this extension download OpenRefine code from http://code.google.com/p/google-refine/source/checkout.
+
+2. Pull this extension's code into folder database under folder /extensions.
+For more information on how to write a OpenRefine extensions and where to put the files see http://code.google.com/p/google-refine/wiki/WriteAnExtension
+
+The folder structure should resemble this:
+grefine-all/
+----------/extensions
+--------------/database
+------------------/module
+------------------/src
+------------------build.xml
+------------------README (this file)
+
+3. Update build.xml in folder /extensions with build and clean ant tasks for database:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+4. If using Eclipse, make sure that you build project with ant
diff --git a/extensions/database/build.xml b/extensions/database/build.xml
new file mode 100644
index 000000000..b6ee98f30
--- /dev/null
+++ b/extensions/database/build.xml
@@ -0,0 +1,165 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/extensions/database/licenses/jdbc-client.LICENSE.txt b/extensions/database/licenses/jdbc-client.LICENSE.txt
new file mode 100644
index 000000000..a14b06b91
--- /dev/null
+++ b/extensions/database/licenses/jdbc-client.LICENSE.txt
@@ -0,0 +1,188 @@
+Copyright 2006 Google
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
\ No newline at end of file
diff --git a/extensions/gdata/module/MOD-INF/.gitignore b/extensions/database/module/MOD-INF/.gitignore
similarity index 100%
rename from extensions/gdata/module/MOD-INF/.gitignore
rename to extensions/database/module/MOD-INF/.gitignore
diff --git a/extensions/database/module/MOD-INF/controller.js b/extensions/database/module/MOD-INF/controller.js
new file mode 100644
index 000000000..5df821fbf
--- /dev/null
+++ b/extensions/database/module/MOD-INF/controller.js
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Controller for JDBC Database extension.
+ *
+ * This is run in the Butterfly (ie Refine) server context using the Rhino
+ * Javascript interpreter.
+ */
+
+var html = "text/html";
+var encoding = "UTF-8";
+var version = "0.1";
+var ClientSideResourceManager = Packages.com.google.refine.ClientSideResourceManager;
+
+var logger = Packages.org.slf4j.LoggerFactory.getLogger("database-extension"),
+File = Packages.java.io.File,
+refineServlet = Packages.com.google.refine.RefineServlet;
+
+/*
+ * Register our custom commands.
+ */
+function registerCommands() {
+
+ logger.info("Registering Database Extension Commands......");
+ var RS = Packages.com.google.refine.RefineServlet;
+ RS.registerCommand(module, "test-connect", Packages.com.google.refine.extension.database.cmd.TestConnectCommand());
+ RS.registerCommand(module, "connect", Packages.com.google.refine.extension.database.cmd.ConnectCommand());
+ RS.registerCommand(module, "saved-connection", Packages.com.google.refine.extension.database.cmd.SavedConnectionCommand());
+ RS.registerCommand(module, "execute-query", Packages.com.google.refine.extension.database.cmd.ExecuteQueryCommand());
+ RS.registerCommand(module, "test-query", Packages.com.google.refine.extension.database.cmd.TestQueryCommand());
+ logger.info("Database Extension Command Registeration done!!");
+}
+
+function registerOperations() {
+ logger.info("Database Operations Registered successfully...");
+}
+
+function registerFunctions() {
+ logger.info("Database Functions Registered successfully...");
+}
+
+
+/*
+ * Function invoked to initialize the extension.
+ */
+function init() {
+
+ logger.info("Initializing OpenRefine Database...");
+ logger.info("Database Extension Mount point " + module.getMountPoint());
+
+ registerCommands();
+ registerOperations();
+ registerFunctions();
+
+
+ // Register importer and exporter
+ var IM = Packages.com.google.refine.importing.ImportingManager;
+
+ IM.registerController(
+ module,
+ "database-import-controller",
+ new Packages.com.google.refine.extension.database.DatabaseImportController()
+ );
+
+
+ // Script files to inject into /index page
+ ClientSideResourceManager.addPaths(
+ "index/scripts",
+ module,
+ [
+ "scripts/index/jquery.contextMenu.min.js",
+ "scripts/index/jquery.ui.position.min.js",
+ "scripts/database-extension.js",
+ "scripts/index/database-import-controller.js",
+ "scripts/index/database-source-ui.js"
+ ]
+ );
+ // Style files to inject into /index page
+ ClientSideResourceManager.addPaths(
+ "index/styles",
+ module,
+ [
+ "styles/jquery.contextMenu.css",
+ "styles/pure.css",
+ "styles/bootstrap.css",
+ "styles/database-import.less"
+
+ ]
+ );
+
+ // Script files to inject into /project page
+ ClientSideResourceManager.addPaths(
+ "project/scripts",
+ module,
+ [
+ "scripts/database-extension.js",
+ "scripts/project/database-exporters.js"
+ ]
+ );
+}
+
+/*
+ * Function invoked to handle each request in a custom way.
+ */
+function process(path, request, response) {
+
+
+ var method = request.getMethod();
+
+ logger.info('receiving request for ' + path);
+ logger.info('receiving method for ' + method);
+
+ if (path == "/" || path == "") {
+ var context = {};
+ context.version = version;
+ send(request, response, "index.vt", context);
+ }
+}
+
+function send(request, response, template, context) {
+ butterfly.sendTextFromTemplate(request, response, context, template, encoding, html);
+}
diff --git a/extensions/database/module/MOD-INF/dbextension.properties b/extensions/database/module/MOD-INF/dbextension.properties
new file mode 100644
index 000000000..db482f687
--- /dev/null
+++ b/extensions/database/module/MOD-INF/dbextension.properties
@@ -0,0 +1,3 @@
+# Batch size for import data
+preview.batchSize = 100
+create.batchSize = 1000
diff --git a/main/webapp/WEB-INF/lib/commons-collections-3.2.1.jar b/extensions/database/module/MOD-INF/lib/commons-collections-3.2.1.jar
similarity index 100%
rename from main/webapp/WEB-INF/lib/commons-collections-3.2.1.jar
rename to extensions/database/module/MOD-INF/lib/commons-collections-3.2.1.jar
diff --git a/main/webapp/WEB-INF/lib/commons-io-1.4.jar b/extensions/database/module/MOD-INF/lib/commons-io-1.4.jar
similarity index 100%
rename from main/webapp/WEB-INF/lib/commons-io-1.4.jar
rename to extensions/database/module/MOD-INF/lib/commons-io-1.4.jar
diff --git a/extensions/database/module/MOD-INF/lib/google-http-client-1.20.0.jar b/extensions/database/module/MOD-INF/lib/google-http-client-1.20.0.jar
new file mode 100644
index 000000000..82887fb3e
Binary files /dev/null and b/extensions/database/module/MOD-INF/lib/google-http-client-1.20.0.jar differ
diff --git a/extensions/database/module/MOD-INF/lib/google-http-client-jackson2-1.20.0.jar b/extensions/database/module/MOD-INF/lib/google-http-client-jackson2-1.20.0.jar
new file mode 100644
index 000000000..674aea920
Binary files /dev/null and b/extensions/database/module/MOD-INF/lib/google-http-client-jackson2-1.20.0.jar differ
diff --git a/extensions/database/module/MOD-INF/lib/httpclient-4.0.1.jar b/extensions/database/module/MOD-INF/lib/httpclient-4.0.1.jar
new file mode 100644
index 000000000..ad32285ab
Binary files /dev/null and b/extensions/database/module/MOD-INF/lib/httpclient-4.0.1.jar differ
diff --git a/extensions/database/module/MOD-INF/lib/httpcore-4.0.1.jar b/extensions/database/module/MOD-INF/lib/httpcore-4.0.1.jar
new file mode 100644
index 000000000..4aef35e2f
Binary files /dev/null and b/extensions/database/module/MOD-INF/lib/httpcore-4.0.1.jar differ
diff --git a/extensions/database/module/MOD-INF/lib/jackson-core-asl-1.9.13.jar b/extensions/database/module/MOD-INF/lib/jackson-core-asl-1.9.13.jar
new file mode 100644
index 000000000..bb4fe1da1
Binary files /dev/null and b/extensions/database/module/MOD-INF/lib/jackson-core-asl-1.9.13.jar differ
diff --git a/extensions/database/module/MOD-INF/lib/jackson-mapper-asl-1.9.13.jar b/extensions/database/module/MOD-INF/lib/jackson-mapper-asl-1.9.13.jar
new file mode 100644
index 000000000..0f2073fc7
Binary files /dev/null and b/extensions/database/module/MOD-INF/lib/jackson-mapper-asl-1.9.13.jar differ
diff --git a/extensions/database/module/MOD-INF/lib/jasypt-1.9.2.jar b/extensions/database/module/MOD-INF/lib/jasypt-1.9.2.jar
new file mode 100644
index 000000000..c22a7e6d5
Binary files /dev/null and b/extensions/database/module/MOD-INF/lib/jasypt-1.9.2.jar differ
diff --git a/extensions/database/module/MOD-INF/lib/json-simple-1.1.1.jar b/extensions/database/module/MOD-INF/lib/json-simple-1.1.1.jar
new file mode 100644
index 000000000..dfd5856d0
Binary files /dev/null and b/extensions/database/module/MOD-INF/lib/json-simple-1.1.1.jar differ
diff --git a/extensions/database/module/MOD-INF/lib/mariadb-java-client-2.2.0.jar b/extensions/database/module/MOD-INF/lib/mariadb-java-client-2.2.0.jar
new file mode 100644
index 000000000..7575fec64
Binary files /dev/null and b/extensions/database/module/MOD-INF/lib/mariadb-java-client-2.2.0.jar differ
diff --git a/extensions/database/module/MOD-INF/lib/mysql-connector-java-5.1.44-bin.jar b/extensions/database/module/MOD-INF/lib/mysql-connector-java-5.1.44-bin.jar
new file mode 100644
index 000000000..2f2e32d51
Binary files /dev/null and b/extensions/database/module/MOD-INF/lib/mysql-connector-java-5.1.44-bin.jar differ
diff --git a/extensions/database/module/MOD-INF/lib/postgresql-42.1.4.jar b/extensions/database/module/MOD-INF/lib/postgresql-42.1.4.jar
new file mode 100644
index 000000000..08a54b105
Binary files /dev/null and b/extensions/database/module/MOD-INF/lib/postgresql-42.1.4.jar differ
diff --git a/extensions/database/module/MOD-INF/module.properties b/extensions/database/module/MOD-INF/module.properties
new file mode 100644
index 000000000..2b0cef961
--- /dev/null
+++ b/extensions/database/module/MOD-INF/module.properties
@@ -0,0 +1,7 @@
+name = database
+description = Database importer/exporter for OpenRefine
+templating.macros = macros.vm
+requires = core
+
+# Use our custom class for a module implementation.
+module-impl = com.google.refine.extension.database.DatabaseModuleImpl
diff --git a/extensions/database/module/images/fonts/glyphicons-halflings-regular.eot b/extensions/database/module/images/fonts/glyphicons-halflings-regular.eot
new file mode 100644
index 000000000..b93a4953f
Binary files /dev/null and b/extensions/database/module/images/fonts/glyphicons-halflings-regular.eot differ
diff --git a/extensions/database/module/images/fonts/glyphicons-halflings-regular.svg b/extensions/database/module/images/fonts/glyphicons-halflings-regular.svg
new file mode 100644
index 000000000..94fb5490a
--- /dev/null
+++ b/extensions/database/module/images/fonts/glyphicons-halflings-regular.svg
@@ -0,0 +1,288 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/database/module/images/fonts/glyphicons-halflings-regular.ttf b/extensions/database/module/images/fonts/glyphicons-halflings-regular.ttf
new file mode 100644
index 000000000..1413fc609
Binary files /dev/null and b/extensions/database/module/images/fonts/glyphicons-halflings-regular.ttf differ
diff --git a/extensions/database/module/images/fonts/glyphicons-halflings-regular.woff b/extensions/database/module/images/fonts/glyphicons-halflings-regular.woff
new file mode 100644
index 000000000..9e612858f
Binary files /dev/null and b/extensions/database/module/images/fonts/glyphicons-halflings-regular.woff differ
diff --git a/extensions/database/module/images/fonts/glyphicons-halflings-regular.woff2 b/extensions/database/module/images/fonts/glyphicons-halflings-regular.woff2
new file mode 100644
index 000000000..64539b54c
Binary files /dev/null and b/extensions/database/module/images/fonts/glyphicons-halflings-regular.woff2 differ
diff --git a/extensions/database/module/images/more-option-horiz-16.png b/extensions/database/module/images/more-option-horiz-16.png
new file mode 100644
index 000000000..0620d0a57
Binary files /dev/null and b/extensions/database/module/images/more-option-horiz-16.png differ
diff --git a/extensions/database/module/images/more_option-vert-16.png b/extensions/database/module/images/more_option-vert-16.png
new file mode 100644
index 000000000..fa41b4f3b
Binary files /dev/null and b/extensions/database/module/images/more_option-vert-16.png differ
diff --git a/extensions/database/module/index.vt b/extensions/database/module/index.vt
new file mode 100644
index 000000000..147cb507b
--- /dev/null
+++ b/extensions/database/module/index.vt
@@ -0,0 +1,20 @@
+#*
+ * Access this page at the URL /extension/database/
+ *#
+
+
+ OpenRefine Database Extension v$version
+
+
+ OpenRefine DATABASE Extension v$version
+ by Tony Opara
+ The JDBC extension allows OpenRefine to read
+ (and write, soon) data from JDBC compatible databases(MySQL, PostgreSQL, MariaDB).
+
+ Known Limitations
+
+
+
\ No newline at end of file
diff --git a/extensions/database/module/langs/translation-en.json b/extensions/database/module/langs/translation-en.json
new file mode 100644
index 000000000..c29b6930d
--- /dev/null
+++ b/extensions/database/module/langs/translation-en.json
@@ -0,0 +1,58 @@
+{
+ "database-import": {
+ "title": "Database Servers",
+ "preparing": "Preparing Result ...",
+ "checking": "Validating Query ...",
+ "creating": "Creating project ..."
+ },
+ "database-source": {
+ "alert-host": "You must specify a Database Host",
+ "alert-port": "You must specify a Database Port",
+ "alert-user": "You must specify a Database User",
+ "alert-password": "You must specify a Database Password",
+ "alert-connection-name": "You must specify a Connection Name",
+ "alert-initial-database": "You must specify an Initial Database",
+ "alert-query": "You must specify a valid Query",
+ "alert-invalid-query-keyword": "Query cannot contain Data Manipulation keyword:",
+ "alert-invalid-query-select": "Query must start with SELECT Keyword",
+ "form-validation-failure" : "New Connection From is Invalid!",
+ "alert-connection-edit": "Connection edited successfully",
+ "connectionNameLabel": "Name:",
+ "databaseTypeLabel": "Type:",
+ "databaseHostLabel": "Host:",
+ "databasePortLabel": "Port:",
+ "databaseUserLabel": "User:",
+ "databasePasswordLabel": "Password:",
+ "databaseNameLabel": "Database:",
+ "databaseSchemaLabel": "Schema:",
+ "databaseTestButton": "Test",
+ "databaseSaveButton": "Save",
+ "databaseConnectButton": "Connect",
+ "newConnectionButtonDiv": "New Connection",
+ "savedConnectionSpan": "Saved Connections"
+
+
+ },
+ "database-parsing": {
+ "start-over": "« Start Over",
+ "conf-pars": "Configure Parsing Options",
+ "proj-name": "Project name",
+ "create-proj": "Create Project »",
+ "updating-preview": "Updating preview ...",
+ "worksheet": "Worksheets",
+ "option": "Options",
+ "preview-button": "Update Preview",
+ "ignore-first": "Ignore first",
+ "ignore": "line(s) at beginning of file",
+ "parse-next": "Parse next",
+ "parse": "line(s) as column headers",
+ "discard-next": "Discard initial",
+ "discard": "row(s) of data",
+ "limit-next": "Load at most",
+ "limit": "row(s) of data",
+ "store-row": "Store blank rows",
+ "store-cell": "Store blank cells as nulls"
+ }
+
+}
+
\ No newline at end of file
diff --git a/extensions/database/module/langs/translation-fr.json b/extensions/database/module/langs/translation-fr.json
new file mode 100644
index 000000000..deaa69b84
--- /dev/null
+++ b/extensions/database/module/langs/translation-fr.json
@@ -0,0 +1,3 @@
+{
+
+}
diff --git a/extensions/database/module/langs/translation-he.json b/extensions/database/module/langs/translation-he.json
new file mode 100644
index 000000000..deaa69b84
--- /dev/null
+++ b/extensions/database/module/langs/translation-he.json
@@ -0,0 +1,3 @@
+{
+
+}
diff --git a/extensions/database/module/langs/translation-it.json b/extensions/database/module/langs/translation-it.json
new file mode 100644
index 000000000..deaa69b84
--- /dev/null
+++ b/extensions/database/module/langs/translation-it.json
@@ -0,0 +1,3 @@
+{
+
+}
diff --git a/extensions/database/module/macros.vm b/extensions/database/module/macros.vm
new file mode 100644
index 000000000..18a83bfce
--- /dev/null
+++ b/extensions/database/module/macros.vm
@@ -0,0 +1,14 @@
+#*
+ This file contains common velocity macros used in all .vt files.
+ For Velocity documentation, see:
+
+ http://velocity.apache.org/engine/releases/velocity-1.5/user-guide.html
+*#
+
+#macro( makeAList $list )
+
+ #foreach($item in $list)
+ $item
+ #end
+
+#end
\ No newline at end of file
diff --git a/extensions/database/module/scripts/database-extension.js b/extensions/database/module/scripts/database-extension.js
new file mode 100644
index 000000000..4c4347935
--- /dev/null
+++ b/extensions/database/module/scripts/database-extension.js
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+var DatabaseExtension = {};
+
+DatabaseExtension.currentConnection = {};
+
+DatabaseExtension.handleSavedConnectionClicked = function(menuKey, connectionName) {
+ var jdbcConnectionInfo = {};
+ jdbcConnectionInfo.connectionName = connectionName;
+
+ if(menuKey === "edit"){
+ DatabaseExtension.handleEditConnectionClicked(connectionName);
+
+ }else if(menuKey === "delete"){
+ DatabaseExtension.handleDeleteConnectionClicked(connectionName);
+
+ }else if(menuKey === "connect"){
+ DatabaseExtension.handleConnectClicked(connectionName);
+ }
+
+};
+
+
+DatabaseExtension.handleConnectClicked = function(connectionName) {
+
+ $.get(
+ "command/database/saved-connection" + '?' + $.param({"connectionName": connectionName}),
+ null,
+
+ function(savedDatabaseConfig) {
+
+ if(savedDatabaseConfig){
+
+ var savedConfig = savedDatabaseConfig.savedConnections[0];
+ var databaseConfig = {};
+ databaseConfig.connectionName = savedConfig.connectionName;
+ databaseConfig.databaseType = savedConfig.databaseType;
+ databaseConfig.databaseServer = savedConfig.databaseHost;
+ databaseConfig.databasePort = savedConfig.databasePort;
+ databaseConfig.databaseUser = savedConfig.databaseUser;
+ databaseConfig.databasePassword = savedConfig.databasePassword;
+ databaseConfig.initialDatabase = savedConfig.databaseName;
+ databaseConfig.initialSchema = savedConfig.databaseSchema;
+
+ $.post(
+ "command/database/connect",
+ databaseConfig,
+
+ function(databaseInfo) {
+
+ if(databaseInfo){
+ DatabaseExtension.currentConnection.databaseInfo;
+ $( "#currentConnectionNameInput" ).val(databaseConfig.connectionName);
+ $( "#currentDatabaseTypeInput" ).val(databaseConfig.databaseType);
+ $( "#currentDatabaseUserInput" ).val(databaseConfig.databaseUser);
+ $( "#currentDatabasePasswordInput" ).val(databaseConfig.databasePassword);
+ $( "#currentDatabaseHostInput" ).val(databaseConfig.databaseServer);
+ $( "#currentDatabasePortInput" ).val(databaseConfig.databasePort);
+ $( "#currentInitialDatabaseInput" ).val(databaseConfig.initialDatabase);
+
+ var connectionParam = "Connection[" + databaseConfig.connectionName + "] :: "
+ + "jdbc:"
+ + databaseConfig.databaseType + "://"
+ + databaseConfig.databaseServer + ":"
+ + databaseConfig.databasePort + "/"
+ + databaseConfig.initialDatabase;
+
+ $( "#connectionParameterSpan" ).text(connectionParam);
+ $( "#newConnectionDiv" ).hide();
+ $( "#sqlEditorDiv" ).show();
+
+ }else{
+ window.alert("Unable to establish connection to database");
+ }
+
+ },
+ "json"
+ ).fail(function( jqXhr, textStatus, errorThrown ){
+ alert( textStatus + ':' + errorThrown );
+ });
+
+ }
+
+ },
+ "json"
+ );
+};
+
+DatabaseExtension.handleDeleteConnectionClicked = function(connectionName) {
+ $.ajax({
+ url: 'command/database/saved-connection' + '?' + $.param({"connectionName": connectionName}),
+ type: 'DELETE',
+ contentType:'application/json',
+ data: null,
+ success: function(settings) {
+ if(settings){
+
+ $( "#menuListUl" ).empty();
+ var items = [];
+ $.each(settings.savedConnections,function(index,savedConnection){
+ items.push('');
+ })
+
+ $( "#menuListUl" ).append(items.join(''));
+ }
+ }
+ }).fail(function( jqXhr, textStatus, errorThrown ){
+ alert( textStatus + ':' + errorThrown );
+ });
+}
+
+DatabaseExtension.handleEditConnectionClicked = function(connectionName) {
+
+ $.get(
+ "command/database/saved-connection" + '?' + $.param({"connectionName": connectionName}),
+ null,
+ function(savedDatabaseConfig) {
+
+ if(savedDatabaseConfig){
+
+ var savedConfig = savedDatabaseConfig.savedConnections[0];
+ $( "#connectionName" ).val(savedConfig.connectionName);
+ $( "#databaseTypeSelect" ).val(savedConfig.databaseType);
+ $( "#databaseHost" ).val(savedConfig.databaseHost);
+ $( "#databasePort" ).val(savedConfig.databasePort);
+ $( "#databaseUser" ).val(savedConfig.databaseUser);
+ $( "#databasePassword" ).val(savedConfig.databasePassword);
+ $( "#initialDatabase" ).val(savedConfig.databaseName);
+ $( "#initialSchema" ).val(savedConfig.databaseSchema);
+ $( "#newConnectionControlDiv" ).hide();
+ $( "#editConnectionControlDiv" ).show();
+ $( "#newConnectionDiv" ).show();
+ $('#sqlEditorDiv').hide();
+ $("#connectionName").attr('readonly', 'readonly');
+
+ }
+
+ },
+ "json"
+ );
+
+}
diff --git a/extensions/database/module/scripts/index/database-import-controller.js b/extensions/database/module/scripts/index/database-import-controller.js
new file mode 100644
index 000000000..e76dfe845
--- /dev/null
+++ b/extensions/database/module/scripts/index/database-import-controller.js
@@ -0,0 +1,378 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+//Internationalization init
+var lang = navigator.language.split("-")[0]
+ || navigator.userLanguage.split("-")[0];
+var dictionary = "";
+$.ajax({
+ url : "command/core/load-language?",
+ type : "POST",
+ async : false,
+ data : {
+ module : "database",
+ },
+ success : function(data) {
+ dictionary = data;
+ }
+});
+$.i18n.setDictionary(dictionary);
+// End internationalization
+
+Refine.DatabaseImportController = function(createProjectUI) {
+ this._createProjectUI = createProjectUI;
+
+ this._parsingPanel = createProjectUI.addCustomPanel();
+
+ createProjectUI.addSourceSelectionUI({
+ label: "Database",
+ id: "database-source",
+ ui: new Refine.DatabaseSourceUI(this)
+ });
+
+};
+Refine.CreateProjectUI.controllers.push(Refine.DatabaseImportController);
+
+Refine.DatabaseImportController.prototype.startImportingDocument = function(queryInfo) {
+ var dismiss = DialogSystem.showBusy($.i18n._('database-import')["preparing"]);
+ //alert(queryInfo.query);
+ var self = this;
+
+ $.post(
+ "command/core/create-importing-job",
+ null,
+ function(data) {
+ $.post(
+ "command/core/importing-controller?" + $.param({
+ "controller": "database/database-import-controller",
+ "subCommand": "initialize-parser-ui"
+ }),
+ queryInfo,
+
+ function(data2) {
+ dismiss();
+
+ if (data2.status == 'ok') {
+ self._queryInfo = queryInfo;
+ self._jobID = data.jobID;
+ self._options = data2.options;
+
+ self._showParsingPanel();
+
+ } else {
+ alert(data2.message);
+ }
+ },
+ "json"
+ );
+ },
+ "json"
+ );
+};
+
+Refine.DatabaseImportController.prototype.getOptions = function() {
+ var options = {
+
+ };
+
+ var parseIntDefault = function(s, def) {
+ try {
+ var n = parseInt(s);
+ if (!isNaN(n)) {
+ return n;
+ }
+ } catch (e) {
+ // Ignore
+ }
+ return def;
+ };
+
+
+ if (this._parsingPanelElmts.skipCheckbox[0].checked) {
+ options.skipDataLines = parseIntDefault(this._parsingPanelElmts.skipInput[0].value, 0);
+ } else {
+ options.skipDataLines = 0;
+ }
+ if (this._parsingPanelElmts.limitCheckbox[0].checked) {
+ options.limit = parseIntDefault(this._parsingPanelElmts.limitInput[0].value, -1);
+ } else {
+ options.limit = -1;
+ }
+ options.storeBlankRows = this._parsingPanelElmts.storeBlankRowsCheckbox[0].checked;
+ options.storeBlankCellsAsNulls = this._parsingPanelElmts.storeBlankCellsAsNullsCheckbox[0].checked;
+
+ return options;
+};
+
+Refine.DatabaseImportController.prototype._showParsingPanel = function() {
+ var self = this;
+
+ this._parsingPanel.unbind().empty().html(
+ DOM.loadHTML("database",'scripts/index/database-parsing-panel.html'));
+
+ this._parsingPanelElmts = DOM.bind(this._parsingPanel);
+
+ this._parsingPanelElmts.startOverButton.html($.i18n._('database-parsing')["start-over"]);
+ this._parsingPanelElmts.database_conf_pars.html($.i18n._('database-parsing')["conf-pars"]);
+ this._parsingPanelElmts.database_proj_name.html($.i18n._('database-parsing')["proj-name"]);
+ this._parsingPanelElmts.createProjectButton.html($.i18n._('database-parsing')["create-proj"]);
+ this._parsingPanelElmts.database_options.html($.i18n._('database-parsing')["option"]);
+ this._parsingPanelElmts.previewButton.html($.i18n._('database-parsing')["preview-button"]);
+ this._parsingPanelElmts.database_updating.html($.i18n._('database-parsing')["updating-preview"]);
+ this._parsingPanelElmts.database_discard_next.html($.i18n._('database-parsing')["discard-next"]);
+ this._parsingPanelElmts.database_discard.html($.i18n._('database-parsing')["discard"]);
+ this._parsingPanelElmts.database_limit_next.html($.i18n._('database-parsing')["limit-next"]);
+ this._parsingPanelElmts.database_limit.html($.i18n._('database-parsing')["limit"]);
+ this._parsingPanelElmts.database_store_row.html($.i18n._('database-parsing')["store-row"]);
+ this._parsingPanelElmts.database_store_cell.html($.i18n._('database-parsing')["store-cell"]);
+
+ if (this._parsingPanelResizer) {
+ $(window).unbind('resize', this._parsingPanelResizer);
+ }
+
+ this._parsingPanelResizer = function() {
+ var elmts = self._parsingPanelElmts;
+ var width = self._parsingPanel.width();
+ var height = self._parsingPanel.height();
+ var headerHeight = elmts.wizardHeader.outerHeight(true);
+ var controlPanelHeight = 250;
+
+ elmts.dataPanel
+ .css("left", "0px")
+ .css("top", headerHeight + "px")
+ .css("width", (width - DOM.getHPaddings(elmts.dataPanel)) + "px")
+ .css("height", (height - headerHeight - controlPanelHeight - DOM.getVPaddings(elmts.dataPanel)) + "px");
+ elmts.progressPanel
+ .css("left", "0px")
+ .css("top", headerHeight + "px")
+ .css("width", (width - DOM.getHPaddings(elmts.progressPanel)) + "px")
+ .css("height", (height - headerHeight - controlPanelHeight - DOM.getVPaddings(elmts.progressPanel)) + "px");
+
+ elmts.controlPanel
+ .css("left", "0px")
+ .css("top", (height - controlPanelHeight) + "px")
+ .css("width", (width - DOM.getHPaddings(elmts.controlPanel)) + "px")
+ .css("height", (controlPanelHeight - DOM.getVPaddings(elmts.controlPanel)) + "px");
+ };
+
+ $(window).resize(this._parsingPanelResizer);
+ this._parsingPanelResizer();
+
+ this._parsingPanelElmts.startOverButton.click(function() {
+ // explicitly cancel the import job
+ Refine.CreateProjectUI.cancelImportingJob(self._jobID);
+
+ delete self._jobID;
+ delete self._options;
+
+ self._createProjectUI.showSourceSelectionPanel();
+ });
+
+ this._parsingPanelElmts.createProjectButton.click(function() { self._createProject(); });
+ this._parsingPanelElmts.previewButton.click(function() { self._updatePreview(); });
+ //alert("datetime::" + $.now());
+ //this._parsingPanelElmts.projectNameInput[0].value = this._queryInfo.connectionName + "_" + this._queryInfo.databaseUser + "_" + $.now();
+ this._parsingPanelElmts.projectNameInput[0].value = this._queryInfo.databaseServer + "_" + this._queryInfo.initialDatabase + "_" + $.now();
+
+
+ if (this._options.limit > 0) {
+ this._parsingPanelElmts.limitCheckbox.prop("checked", true);
+ this._parsingPanelElmts.limitInput[0].value = this._options.limit.toString();
+ }
+ if (this._options.skipDataLines > 0) {
+ this._parsingPanelElmts.skipCheckbox.prop("checked", true);
+ this._parsingPanelElmts.skipInput.value[0].value = this._options.skipDataLines.toString();
+ }
+ if (this._options.storeBlankRows) {
+ this._parsingPanelElmts.storeBlankRowsCheckbox.prop("checked", true);
+ }
+ if (this._options.storeBlankCellsAsNulls) {
+ this._parsingPanelElmts.storeBlankCellsAsNullsCheckbox.prop("checked", true);
+ }
+
+ var onChange = function() {
+ self._scheduleUpdatePreview();
+ };
+ this._parsingPanel.find("input").bind("change", onChange);
+ this._parsingPanel.find("select").bind("change", onChange);
+
+ this._createProjectUI.showCustomPanel(this._parsingPanel);
+ this._updatePreview();
+};
+
+Refine.DatabaseImportController.prototype._scheduleUpdatePreview = function() {
+ if (this._timerID != null) {
+ window.clearTimeout(this._timerID);
+ this._timerID = null;
+ }
+
+ var self = this;
+ this._timerID = window.setTimeout(function() {
+ self._timerID = null;
+ self._updatePreview();
+ }, 500); // 0.5 second
+ };
+
+Refine.DatabaseImportController.prototype._updatePreview = function() {
+ var self = this;
+ // alert("query::" + this._queryInfo.query);
+ this._parsingPanelElmts.dataPanel.hide();
+ this._parsingPanelElmts.progressPanel.show();
+ this._queryInfo.options = JSON.stringify(this.getOptions());
+ //alert("options:" + this._queryInfo.options);
+
+ $.post(
+ "command/core/importing-controller?" + $.param({
+ "controller": "database/database-import-controller",
+ "jobID": this._jobID,
+ "subCommand": "parse-preview"
+ }),
+
+ this._queryInfo,
+
+ function(result) {
+ if (result.status == "ok") {
+ self._getPreviewData(function(projectData) {
+ self._parsingPanelElmts.progressPanel.hide();
+ self._parsingPanelElmts.dataPanel.show();
+
+ new Refine.PreviewTable(projectData, self._parsingPanelElmts.dataPanel.unbind().empty());
+ });
+ } else {
+
+ alert('Errors:\n' + (result.message) ? result.message : Refine.CreateProjectUI.composeErrorMessage(job));
+ self._parsingPanelElmts.progressPanel.hide();
+
+ Refine.CreateProjectUI.cancelImportingJob(self._jobID);
+
+ delete self._jobID;
+ delete self._options;
+
+ self._createProjectUI.showSourceSelectionPanel();
+
+
+ }
+ },
+ "json"
+ );
+ };
+
+Refine.DatabaseImportController.prototype._getPreviewData = function(callback, numRows) {
+ var self = this;
+ var result = {};
+
+ $.post(
+ "command/core/get-models?" + $.param({ "importingJobID" : this._jobID }),
+ null,
+ function(data) {
+ for (var n in data) {
+ if (data.hasOwnProperty(n)) {
+ result[n] = data[n];
+ }
+ }
+
+ $.post(
+ "command/core/get-rows?" + $.param({
+ "importingJobID" : self._jobID,
+ "start" : 0,
+ "limit" : numRows || 100 // More than we parse for preview anyway
+ }),
+ null,
+ function(data) {
+ result.rowModel = data;
+ callback(result);
+ },
+ "jsonp"
+ );
+ },
+ "json"
+ );
+};
+
+Refine.DatabaseImportController.prototype._createProject = function() {
+ var projectName = $.trim(this._parsingPanelElmts.projectNameInput[0].value);
+ if (projectName.length == 0) {
+ window.alert("Please name the project.");
+ this._parsingPanelElmts.projectNameInput.focus();
+ return;
+ }
+
+ var self = this;
+ var options = this.getOptions();
+ options.projectName = projectName;
+
+ this._queryInfo.options = JSON.stringify(options);
+ $.post(
+ "command/core/importing-controller?" + $.param({
+ "controller": "database/database-import-controller",
+ "jobID": this._jobID,
+ "subCommand": "create-project"
+ }),
+ this._queryInfo,
+ function(o) {
+ if (o.status == 'error') {
+ alert(o.message);
+ } else {
+ var start = new Date();
+ var timerID = window.setInterval(
+ function() {
+ self._createProjectUI.pollImportJob(
+ start,
+ self._jobID,
+ timerID,
+ function(job) {
+ return "projectID" in job.config;
+ },
+ function(jobID, job) {
+ //alert("jobID::" + jobID + " job :" + job);
+ window.clearInterval(timerID);
+ Refine.CreateProjectUI.cancelImportingJob(jobID);
+ document.location = "project?project=" + job.config.projectID;
+ },
+ function(job) {
+ alert(Refine.CreateProjectUI.composeErrorMessage(job));
+ }
+ );
+ },
+ 1000
+ );
+ self._createProjectUI.showImportProgressPanel($.i18n._('database-import')["creating"], function() {
+ // stop the timed polling
+ window.clearInterval(timerID);
+
+ // explicitly cancel the import job
+ Refine.CreateProjectUI.cancelImportingJob(jobID);
+
+ self._createProjectUI.showSourceSelectionPanel();
+ });
+ }
+ },
+ "json"
+ );
+};
diff --git a/extensions/database/module/scripts/index/database-import-form.html b/extensions/database/module/scripts/index/database-import-form.html
new file mode 100644
index 000000000..de1f96f08
--- /dev/null
+++ b/extensions/database/module/scripts/index/database-import-form.html
@@ -0,0 +1,144 @@
+
+
\ No newline at end of file
diff --git a/extensions/database/module/scripts/index/database-parsing-panel.html b/extensions/database/module/scripts/index/database-parsing-panel.html
new file mode 100644
index 000000000..4ac5a1c75
--- /dev/null
+++ b/extensions/database/module/scripts/index/database-parsing-panel.html
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/database/module/scripts/index/database-source-ui.js b/extensions/database/module/scripts/index/database-source-ui.js
new file mode 100644
index 000000000..f93ad77ad
--- /dev/null
+++ b/extensions/database/module/scripts/index/database-source-ui.js
@@ -0,0 +1,505 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+$(function(){
+ $.contextMenu({
+ selector: '.context-menu-one',
+ trigger: 'left',
+ build: function($trigger, e) {
+
+ return {
+ callback: function(key, options) {
+ var m = "clicked: " + key;
+ DatabaseExtension.handleSavedConnectionClicked(key, $(this).text());
+
+ },
+
+ items: {
+ "edit": {name: " Edit "},
+ "sep0": "",
+ "delete": {name: " Delete "},
+ "sep1": "---------",
+ "connect": {name: " Connect "},
+ "dummy": {name: "", icon: ""}
+ }
+ };
+ }
+ });
+});
+
+Refine.DatabaseSourceUI = function(controller) {
+ this._controller = controller;
+};
+
+Refine.DatabaseSourceUI.prototype.attachUI = function(body) {
+ this._body = body;
+
+ this._body.html(DOM.loadHTML("database", "scripts/index/database-import-form.html"));
+ this._elmts = DOM.bind(this._body);
+ var self = this;
+
+ $('#database-title').text($.i18n._("database-import")["title"]);
+ $('#connectionNameLabel').html($.i18n._("database-source")["connectionNameLabel"]);
+ $('#databaseTypeLabel').html($.i18n._("database-source")["databaseTypeLabel"]);
+ $('#databaseHostLabel').text($.i18n._("database-source")["databaseHostLabel"]);
+ $('#databasePortLabel').text($.i18n._("database-source")["databasePortLabel"]);
+ $('#databaseUserLabel').text($.i18n._("database-source")["databaseUserLabel"]);
+ $('#databasePasswordLabel').text($.i18n._("database-source")["databasePasswordLabel"]);
+ $('#databaseNameLabel').text($.i18n._("database-source")["databaseNameLabel"]);
+ $('#databaseSchemaLabel').text($.i18n._("database-source")["databaseSchemaLabel"]);
+ $('#databaseTestButton').text($.i18n._("database-source")["databaseTestButton"]);
+ $('#databaseSaveButton').text($.i18n._("database-source")["databaseSaveButton"]);
+ $('#databaseConnectButton').text($.i18n._("database-source")["databaseConnectButton"]);
+ $('#newConnectionButtonDiv').text($.i18n._("database-source")["newConnectionButtonDiv"]);
+ $('#savedConnectionSpan').text($.i18n._("database-source")["savedConnectionSpan"]);
+
+
+ this._elmts.newConnectionButton.click(function(evt) {
+ self._resetDatabaseImportForm();
+ $( "#newConnectionDiv" ).show();
+ $( "#sqlEditorDiv" ).hide();
+ // self._body.find('.newConnectionDiv').show();
+ // self._body.find('.sqlEditorDiv').hide();
+
+ });
+
+
+ this._elmts.databaseTypeSelect.change(function(event) {
+ var type = $( "#databaseTypeSelect" ).val();
+ if(type === "postgresql"){
+ $( "#databaseUser" ).val("postgres");
+ $( "#databasePort" ).val("5432");
+ }else if(type === "mysql"){
+ $( "#databaseUser" ).val("root");
+ $( "#databasePort" ).val("3306");
+ }else if(type === "mariadb"){
+ $( "#databaseUser" ).val("root");
+ $( "#databasePort" ).val("3306");
+ }else{
+ $( "#databaseUser" ).val("root");
+ $( "#databasePort" ).val("3306");
+ }
+ });
+
+ this._elmts.testDatabaseButton.click(function(evt) {
+
+ if(self._validateNewConnectionForm() === true){
+ self._testDatabaseConnect(self._getConnectionInfo());
+ }
+
+ });
+
+ this._elmts.databaseConnectButton.click(function(evt) {
+
+ if(self._validateNewConnectionForm() === true){
+ self._connect(self._getConnectionInfo());
+ }
+
+
+ });
+
+ this._elmts.saveConnectionButton.click(function(evt) {
+
+ if(self._validateNewConnectionForm() == true){
+ var connectionNameInput = $.trim(self._elmts.connectionNameInput[0].value);
+ if (connectionNameInput.length === 0) {
+ window.alert($.i18n._('database-source')["alert-connection-name"]);
+ } else{
+ self._saveConnection(self._getConnectionInfo());
+ }
+
+ }
+
+ });
+
+ this._elmts.executeQueryButton.click(function(evt) {
+ var jdbcQueryInfo = {};
+ jdbcQueryInfo.connectionName = $( "#currentConnectionNameInput" ).val();
+ jdbcQueryInfo.databaseType = $( "#currentDatabaseTypeInput" ).val();
+ jdbcQueryInfo.databaseServer = $( "#currentDatabaseHostInput" ).val();
+ jdbcQueryInfo.databasePort = $( "#currentDatabasePortInput" ).val();
+ jdbcQueryInfo.databaseUser = $( "#currentDatabaseUserInput" ).val();
+ jdbcQueryInfo.databasePassword = $( "#currentDatabasePasswordInput" ).val();
+ jdbcQueryInfo.initialDatabase = $( "#currentInitialDatabaseInput" ).val();
+ jdbcQueryInfo.query = $.trim($( "#queryTextArea" ).val());
+
+// if(jdbcQueryInfo.query && jdbcQueryInfo.query.length > 0 ) {
+// self._executeQuery(jdbcQueryInfo);
+// }else{
+// window.alert($.i18n._('database-source')["alert-query"]);
+// }
+
+ if(self.validateQuery(jdbcQueryInfo.query)) {
+ self._executeQuery(jdbcQueryInfo);
+ }
+
+
+
+
+ });
+
+
+
+ this._elmts.editConnectionButton.click(function(evt) {
+
+ if(self._validateNewConnectionForm() == true){
+ var connectionNameInput = $.trim(self._elmts.connectionNameInput[0].value);
+ if (connectionNameInput.length === 0) {
+ window.alert($.i18n._('database-source')["alert-connection-name"]);
+ } else{
+ self._editConnection(self._getConnectionInfo());
+ }
+
+ }
+
+ });
+
+ this._elmts.cancelEditConnectionButton.click(function(evt) {
+ self._resetDatabaseImportForm();
+
+ });
+
+ //load saved connections from settings file in user home directory
+ self._loadSavedConnections();
+
+};//end Refine.createUI
+
+
+Refine.DatabaseSourceUI.prototype.focus = function() {
+};
+
+
+Refine.DatabaseSourceUI.prototype.validateQuery = function(query) {
+ //alert("query::" + query);
+ if(!query || query.length <= 0 ) {
+ window.alert($.i18n._('database-source')["alert-query"]);
+ return false;
+ }
+
+ var allCapsQuery = query.toUpperCase();
+
+ if(allCapsQuery.indexOf('DROP') > -1){
+ window.alert($.i18n._('database-source')["alert-invalid-query-keyword"] + " DROP");
+ return false;
+ }else if(allCapsQuery.indexOf('TRUNCATE') > -1){
+ window.alert($.i18n._('database-source')["alert-invalid-query-keyword"] + " TRUNCATE");
+ return false;
+ }else if(allCapsQuery.indexOf('DELETE') > -1){
+ window.alert($.i18n._('database-source')["alert-invalid-query-keyword"] + " DELETE");
+ return false;
+ }else if(allCapsQuery.indexOf('ROLLBACK') > -1){
+ window.alert($.i18n._('database-source')["alert-invalid-query-keyword"] + " ROLLBACK");
+ return false;
+ }else if(allCapsQuery.indexOf('SHUTDOWN') > -1){
+ window.alert($.i18n._('database-source')["alert-invalid-query-keyword"] + " SHUTDOWN");
+ return false;
+ }else if(allCapsQuery.indexOf('INSERT') > -1){
+ window.alert($.i18n._('database-source')["alert-invalid-query-keyword"] + " INSERT");
+ return false;
+ }else if(allCapsQuery.indexOf('ALTER') > -1){
+ window.alert($.i18n._('database-source')["alert-invalid-query-keyword"] + " ALTER");
+ return false;
+ }else if(allCapsQuery.indexOf('UPDATE') > -1){
+ window.alert($.i18n._('database-source')["alert-invalid-query-keyword"] + " UPDATE");
+ return false;
+ }else if(allCapsQuery.indexOf('LIMIT') > -1){
+ window.alert($.i18n._('database-source')["alert-invalid-query-keyword"] + " LIMIT");
+ return false;
+ }
+
+// if ((allCapsQuery.indexOf('DROP') > -1) || (allCapsQuery.indexOf('TRUNCATE') > -1) ||
+// (allCapsQuery.indexOf('DELETE') > -1) || (allCapsQuery.indexOf('ROLLBACK') > -1)
+// || (allCapsQuery.indexOf('SHUTDOWN') > -1) || (allCapsQuery.indexOf('INSERT') > -1)
+// || (allCapsQuery.indexOf('ALTER') > -1) || (allCapsQuery.indexOf('UPDATE') > -1))
+// {
+// window.alert($.i18n._('database-source')["alert-invalid-query-keyword"]);
+// return false;
+// }
+
+ if(!allCapsQuery.startsWith('SELECT')) {
+ window.alert($.i18n._('database-source')["alert-invalid-query-select"]);
+ return false;
+ }
+
+ return true;
+};
+
+Refine.DatabaseSourceUI.prototype._editConnection = function(connectionInfo) {
+ //alert("database user:" + connectionInfo.databaseUser);
+ var self = this;
+ $.ajax({
+ url: 'command/database/saved-connection',
+ type: 'PUT',
+ contentType:'application/x-www-form-urlencoded',
+ data: connectionInfo,
+ success: function(settings) {
+ if(settings){
+ $( "#menuListUl" ).empty();
+ var items = [];
+ $.each(settings.savedConnections,function(index,savedConnection){
+// items.push(''
+// + ''
+// + ' ');
+
+ items.push('');
+ })
+
+ $( "#menuListUl" ).append(items.join(''));
+ window.alert($.i18n._('database-source')["alert-connection-edit"]);
+ }
+ }
+ }).fail(function( jqXhr, textStatus, errorThrown ){
+ alert( textStatus + ':' + errorThrown );
+ });
+
+};
+
+Refine.DatabaseSourceUI.prototype._executeQuery = function(jdbcQueryInfo) {
+ var self = this;
+ //remove start line
+
+ var dismiss = DialogSystem.showBusy($.i18n._('database-import')["checking"]);
+ //$("#executeQueryBtn").text('Please wait ...').attr('disabled','disabled');
+
+ $.post(
+ "command/database/test-query",
+ jdbcQueryInfo,
+ function(jdbcConnectionResult) {
+ // $("#executeQueryBtn").text('Preview Query Result').removeAttr('disabled');
+ dismiss();
+ self._controller.startImportingDocument(jdbcQueryInfo);
+
+ },
+ "json"
+ ).fail(function( jqXhr, textStatus, errorThrown ){
+ //$("#executeQueryBtn").text('Preview Query Result').removeAttr('disabled');
+ dismiss();
+ alert( textStatus + ':' + errorThrown );
+ });
+ //remove end line
+
+ //self._controller.startImportingDocument(jdbcQueryInfo);
+}
+
+Refine.DatabaseSourceUI.prototype._saveConnection = function(jdbcConnectionInfo) {
+ var self = this;
+ $.post(
+ "command/database/saved-connection",
+ jdbcConnectionInfo,
+ function(settings) {
+ if(settings){
+
+// self._elmts.scListGroupDiv.empty();
+ self._elmts.menuListUl.empty();
+ var items = [];
+ $.each(settings.savedConnections,function(index,savedConnection){
+
+// items.push(''
+// + ''
+// + ' ');
+
+ items.push('');
+ })
+
+ self._elmts.menuListUl.append(items.join(''));
+ }
+
+ },
+ "json"
+ ).fail(function( jqXhr, textStatus, errorThrown ){
+ alert( textStatus + ':' + errorThrown );
+ });
+
+};
+
+Refine.DatabaseSourceUI.prototype._loadSavedConnections = function() {
+ var self = this;
+ $.get(
+ "command/database/saved-connection",
+ null,
+ function(settings) {
+ if(settings){
+
+ self._elmts.menuListUl.empty();
+ //self._elmts.scListGroupDiv.empty();
+ var items = [];
+ $.each(settings.savedConnections,function(index,savedConnection){
+
+// items.push('');
+
+ items.push('');
+
+ })
+
+ self._elmts.menuListUl.append(items.join(''));
+ // self._elmts.scListGroupDiv.append(items.join(''));
+ }
+
+ },
+ "json"
+ );
+
+};
+
+Refine.DatabaseSourceUI.prototype._testDatabaseConnect = function(jdbcConnectionInfo) {
+
+ var self = this;
+ $.post(
+ "command/database/test-connect",
+ jdbcConnectionInfo,
+ function(jdbcConnectionResult) {
+ if(jdbcConnectionResult && jdbcConnectionResult.connectionResult == true){
+ window.alert("Test Connection Succeeded!");
+ }else{
+ window.alert("Unable to establish connection to database");
+ }
+
+ },
+ "json"
+ ).fail(function( jqXhr, textStatus, errorThrown ){
+ alert( textStatus + ':' + errorThrown );
+ });
+};
+
+Refine.DatabaseSourceUI.prototype._connect = function(jdbcConnectionInfo) {
+
+ var self = this;
+ $.post(
+ "command/database/connect",
+ jdbcConnectionInfo,
+ function(databaseInfo) {
+
+ if(databaseInfo){
+ $( "#currentConnectionNameInput" ).val(jdbcConnectionInfo.connectionName);
+ $( "#currentDatabaseTypeInput" ).val(jdbcConnectionInfo.databaseType);
+ $( "#currentDatabaseUserInput" ).val(jdbcConnectionInfo.databaseUser);
+ $( "#currentDatabasePasswordInput" ).val(jdbcConnectionInfo.databasePassword);
+ $( "#currentDatabaseHostInput" ).val(jdbcConnectionInfo.databaseServer);
+ $( "#currentDatabasePortInput" ).val(jdbcConnectionInfo.databasePort);
+ $( "#currentInitialDatabaseInput" ).val(jdbcConnectionInfo.initialDatabase);
+
+ var connectionParam = "Connection :: "
+ + "jdbc:"
+ + jdbcConnectionInfo.databaseType + "://"
+ + jdbcConnectionInfo.databaseServer + ":"
+ + jdbcConnectionInfo.databasePort + "/"
+ + jdbcConnectionInfo.initialDatabase;
+
+ //alert("connectionParam::" + connectionParam);
+ $( "#connectionParameterSpan" ).text(connectionParam);
+ // self._body.find('.newConnectionDiv').hide();
+ // self._body.find('.sqlEditorDiv').show();
+ $( "#newConnectionDiv" ).hide();
+ $( "#sqlEditorDiv" ).show();
+
+ }else{
+ window.alert("Unable to establish connection to database");
+ return;
+ }
+
+ },
+ "json"
+ ).fail(function( jqXhr, textStatus, errorThrown ){
+ alert( textStatus + ':' + errorThrown );
+ });
+
+};
+
+Refine.DatabaseSourceUI.prototype._getConnectionInfo = function() {
+ var self = this;
+ var jdbcConnectionInfo = {};
+ jdbcConnectionInfo.connectionName = $.trim(self._elmts.connectionNameInput[0].value);
+ jdbcConnectionInfo.databaseType = $.trim(self._elmts.databaseTypeSelect[0].value);
+ jdbcConnectionInfo.databaseServer = $.trim(self._elmts.databaseHostInput[0].value);
+ jdbcConnectionInfo.databasePort = $.trim(self._elmts.databasePortInput[0].value);
+ jdbcConnectionInfo.databaseUser = $.trim(self._elmts.databaseUserInput[0].value);
+ jdbcConnectionInfo.databasePassword = $.trim(self._elmts.databasePasswordInput[0].value);
+ jdbcConnectionInfo.initialDatabase = $.trim(self._elmts.initialDatabaseInput[0].value);
+ jdbcConnectionInfo.initialSchema = $.trim(self._elmts.initialSchemaInput[0].value);
+ return jdbcConnectionInfo;
+
+}
+
+Refine.DatabaseSourceUI.prototype._validateNewConnectionForm = function() {
+
+ var self = this;
+ var connectionNameInput = $.trim(self._elmts.connectionNameInput[0].value);
+ var databaseTypeSelect = $.trim(self._elmts.databaseTypeSelect[0].value);
+ var databaseHostInput = $.trim(self._elmts.databaseHostInput[0].value);
+ var databasePortInput = $.trim(self._elmts.databasePortInput[0].value);
+ var databaseUserInput = $.trim(self._elmts.databaseUserInput[0].value);
+ var databasePasswordInput = $.trim(self._elmts.databasePasswordInput[0].value);
+ var initialDatabaseInput = $.trim(self._elmts.initialDatabaseInput[0].value);
+ var initialSchemaInput = $.trim(self._elmts.initialSchemaInput[0].value);
+
+ if (databaseHostInput.length === 0) {
+ window.alert($.i18n._('database-source')["alert-server"]);
+ return false;
+ }else if(databasePortInput.length === 0){
+ window.alert($.i18n._('database-source')["alert-port"]);
+ return false;
+ }else if(databaseUserInput.length === 0){
+ window.alert($.i18n._('database-source')["alert-user"]);
+ return false;
+ }else if(initialDatabaseInput.length === 0){
+ window.alert($.i18n._('database-source')["alert-initial-database"]);
+ return false;
+ }
+ else{
+ return true;
+
+ }
+
+ return true;
+};
+
+Refine.DatabaseSourceUI.prototype._resetDatabaseImportForm = function() {
+ var self = this;
+ $( "#connectionName" ).val("127.0.0.1");
+ $( "#databaseTypeSelect" ).val("postgresql");
+ $( "#databaseHost" ).val("127.0.0.1");
+ $( "#databasePort" ).val("5432");
+ $( "#databaseUser" ).val("postgres");
+ $( "#databasePassword" ).val("");
+ $( "#initialDatabase" ).val("");
+ $( "#initialSchema" ).val("");
+
+ $( "#editConnectionControlDiv" ).hide();
+ $( "#newConnectionControlDiv" ).show();
+ $('#connectionName').removeAttr('readonly');
+
+};
diff --git a/extensions/database/module/scripts/index/jquery.contextMenu.min.js b/extensions/database/module/scripts/index/jquery.contextMenu.min.js
new file mode 100644
index 000000000..bf6b6169f
--- /dev/null
+++ b/extensions/database/module/scripts/index/jquery.contextMenu.min.js
@@ -0,0 +1,2 @@
+!function(e){"function"==typeof define&&define.amd?define(["jquery"],e):e("object"==typeof exports?require("jquery"):jQuery)}(function(e){"use strict";function t(e){for(var t,n=e.split(/\s+/),a=[],o=0;t=n[o];o++)t=t.charAt(0).toUpperCase(),a.push(t);return a}function n(t){return t.id&&e('label[for="'+t.id+'"]').val()||t.name}function a(t,o,s){return s||(s=0),o.each(function(){var o,i,c=e(this),l=this,r=this.nodeName.toLowerCase();switch("label"===r&&c.find("input, textarea, select").length&&(o=c.text(),r=(l=(c=c.children().first()).get(0)).nodeName.toLowerCase()),r){case"menu":i={name:c.attr("label"),items:{}},s=a(i.items,c.children(),s);break;case"a":case"button":i={name:c.text(),disabled:!!c.attr("disabled"),callback:function(){c.get(0).click()}};break;case"menuitem":case"command":switch(c.attr("type")){case void 0:case"command":case"menuitem":i={name:c.attr("label"),disabled:!!c.attr("disabled"),icon:c.attr("icon"),callback:function(){c.get(0).click()}};break;case"checkbox":i={type:"checkbox",disabled:!!c.attr("disabled"),name:c.attr("label"),selected:!!c.attr("checked")};break;case"radio":i={type:"radio",disabled:!!c.attr("disabled"),name:c.attr("label"),radio:c.attr("radiogroup"),value:c.attr("id"),selected:!!c.attr("checked")};break;default:i=void 0}break;case"hr":i="-------";break;case"input":switch(c.attr("type")){case"text":i={type:"text",name:o||n(l),disabled:!!c.attr("disabled"),value:c.val()};break;case"checkbox":i={type:"checkbox",name:o||n(l),disabled:!!c.attr("disabled"),selected:!!c.attr("checked")};break;case"radio":i={type:"radio",name:o||n(l),disabled:!!c.attr("disabled"),radio:!!c.attr("name"),value:c.val(),selected:!!c.attr("checked")};break;default:i=void 0}break;case"select":i={type:"select",name:o||n(l),disabled:!!c.attr("disabled"),selected:c.val(),options:{}},c.children().each(function(){i.options[this.value]=e(this).text()});break;case"textarea":i={type:"textarea",name:o||n(l),disabled:!!c.attr("disabled"),value:c.val()};break;case"label":break;default:i={type:"html",html:c.clone(!0)}}i&&(t["key"+ ++s]=i)}),s}e.support.htmlMenuitem="HTMLMenuItemElement"in window,e.support.htmlCommand="HTMLCommandElement"in window,e.support.eventSelectstart="onselectstart"in document.documentElement,e.ui&&e.widget||(e.cleanData=function(t){return function(n){var a,o,s;for(s=0;null!=n[s];s++){o=n[s];try{(a=e._data(o,"events"))&&a.remove&&e(o).triggerHandler("remove")}catch(e){}}t(n)}}(e.cleanData));var o=null,s=!1,i=e(window),c=0,l={},r={},u={},d={selector:null,appendTo:null,trigger:"right",autoHide:!1,delay:200,reposition:!0,hideOnSecondTrigger:!1,selectableSubMenu:!1,classNames:{hover:"context-menu-hover",disabled:"context-menu-disabled",visible:"context-menu-visible",notSelectable:"context-menu-not-selectable",icon:"context-menu-icon",iconEdit:"context-menu-icon-edit",iconCut:"context-menu-icon-cut",iconCopy:"context-menu-icon-copy",iconPaste:"context-menu-icon-paste",iconDelete:"context-menu-icon-delete",iconAdd:"context-menu-icon-add",iconQuit:"context-menu-icon-quit",iconLoadingClass:"context-menu-icon-loading"},determinePosition:function(t){if(e.ui&&e.ui.position)t.css("display","block").position({my:"center top",at:"center bottom",of:this,offset:"0 5",collision:"fit"}).css("display","none");else{var n=this.offset();n.top+=this.outerHeight(),n.left+=this.outerWidth()/2-t.outerWidth()/2,t.css(n)}},position:function(e,t,n){var a;if(t||n){if("maintain"===t&&"maintain"===n)a=e.$menu.position();else{var o=e.$menu.offsetParent().offset();a={top:n-o.top,left:t-o.left}}var s=i.scrollTop()+i.height(),c=i.scrollLeft()+i.width(),l=e.$menu.outerHeight(),r=e.$menu.outerWidth();a.top+l>s&&(a.top-=l),a.top<0&&(a.top=0),a.left+r>c&&(a.left-=r),a.left<0&&(a.left=0),e.$menu.css(a)}else e.determinePosition.call(this,e.$menu)},positionSubmenu:function(t){if(void 0!==t)if(e.ui&&e.ui.position)t.css("display","block").position({my:"left top-5",at:"right top",of:this,collision:"flipfit fit"}).css("display","");else{var n={top:-9,left:this.outerWidth()-5};t.css(n)}},zIndex:1,animation:{duration:50,show:"slideDown",hide:"slideUp"},events:{show:e.noop,hide:e.noop,activated:e.noop},callback:null,items:{}},m={timer:null,pageX:null,pageY:null},p=function(e){for(var t=0,n=e;;)if(t=Math.max(t,parseInt(n.css("z-index"),10)||0),!(n=n.parent())||!n.length||"html body".indexOf(n.prop("nodeName").toLowerCase())>-1)break;return t},f={abortevent:function(e){e.preventDefault(),e.stopImmediatePropagation()},contextmenu:function(t){var n=e(this);if("right"===t.data.trigger&&(t.preventDefault(),t.stopImmediatePropagation()),!("right"!==t.data.trigger&&"demand"!==t.data.trigger&&t.originalEvent||!(void 0===t.mouseButton||!t.data||"left"===t.data.trigger&&0===t.mouseButton||"right"===t.data.trigger&&2===t.mouseButton)||n.hasClass("context-menu-active")||n.hasClass("context-menu-disabled"))){if(o=n,t.data.build){var a=t.data.build(o,t);if(!1===a)return;if(t.data=e.extend(!0,{},d,t.data,a||{}),!t.data.items||e.isEmptyObject(t.data.items))throw window.console&&(console.error||console.log).call(console,"No items specified to show in contextMenu"),new Error("No Items specified");t.data.$trigger=o,h.create(t.data)}var s=!1;for(var i in t.data.items)if(t.data.items.hasOwnProperty(i)){(e.isFunction(t.data.items[i].visible)?t.data.items[i].visible.call(e(t.currentTarget),i,t.data):void 0===t.data.items[i]||!t.data.items[i].visible||!0===t.data.items[i].visible)&&(s=!0)}s&&h.show.call(n,t.data,t.pageX,t.pageY)}},click:function(t){t.preventDefault(),t.stopImmediatePropagation(),e(this).trigger(e.Event("contextmenu",{data:t.data,pageX:t.pageX,pageY:t.pageY}))},mousedown:function(t){var n=e(this);o&&o.length&&!o.is(n)&&o.data("contextMenu").$menu.trigger("contextmenu:hide"),2===t.button&&(o=n.data("contextMenuActive",!0))},mouseup:function(t){var n=e(this);n.data("contextMenuActive")&&o&&o.length&&o.is(n)&&!n.hasClass("context-menu-disabled")&&(t.preventDefault(),t.stopImmediatePropagation(),o=n,n.trigger(e.Event("contextmenu",{data:t.data,pageX:t.pageX,pageY:t.pageY}))),n.removeData("contextMenuActive")},mouseenter:function(t){var n=e(this),a=e(t.relatedTarget),s=e(document);a.is(".context-menu-list")||a.closest(".context-menu-list").length||o&&o.length||(m.pageX=t.pageX,m.pageY=t.pageY,m.data=t.data,s.on("mousemove.contextMenuShow",f.mousemove),m.timer=setTimeout(function(){m.timer=null,s.off("mousemove.contextMenuShow"),o=n,n.trigger(e.Event("contextmenu",{data:m.data,pageX:m.pageX,pageY:m.pageY}))},t.data.delay))},mousemove:function(e){m.pageX=e.pageX,m.pageY=e.pageY},mouseleave:function(t){var n=e(t.relatedTarget);if(!n.is(".context-menu-list")&&!n.closest(".context-menu-list").length){try{clearTimeout(m.timer)}catch(t){}m.timer=null}},layerClick:function(t){var n,a,o=e(this).data("contextMenuRoot"),s=t.button,c=t.pageX,l=t.pageY;t.preventDefault(),setTimeout(function(){var r,u="left"===o.trigger&&0===s||"right"===o.trigger&&2===s;if(document.elementFromPoint&&o.$layer){if(o.$layer.hide(),(n=document.elementFromPoint(c-i.scrollLeft(),l-i.scrollTop())).isContentEditable){var d=document.createRange(),m=window.getSelection();d.selectNode(n),d.collapse(!0),m.removeAllRanges(),m.addRange(d)}e(n).trigger(t),o.$layer.show()}if(o.hideOnSecondTrigger&&u&&null!==o.$menu&&void 0!==o.$menu)o.$menu.trigger("contextmenu:hide");else{if(o.reposition&&u)if(document.elementFromPoint){if(o.$trigger.is(n))return void o.position.call(o.$trigger,o,c,l)}else if(a=o.$trigger.offset(),r=e(window),a.top+=r.scrollTop(),a.top<=t.pageY&&(a.left+=r.scrollLeft(),a.left<=t.pageX&&(a.bottom=a.top+o.$trigger.outerHeight(),a.bottom>=t.pageY&&(a.right=a.left+o.$trigger.outerWidth(),a.right>=t.pageX))))return void o.position.call(o.$trigger,o,c,l);n&&u&&o.$trigger.one("contextmenu:hidden",function(){e(n).contextMenu({x:c,y:l,button:s})}),null!==o&&void 0!==o&&null!==o.$menu&&void 0!==o.$menu&&o.$menu.trigger("contextmenu:hide")}},50)},keyStop:function(e,t){t.isInput||e.preventDefault(),e.stopPropagation()},key:function(e){var t={};o&&(t=o.data("contextMenu")||{}),void 0===t.zIndex&&(t.zIndex=0);var n=0,a=function(e){""!==e.style.zIndex?n=e.style.zIndex:null!==e.offsetParent&&void 0!==e.offsetParent?a(e.offsetParent):null!==e.parentElement&&void 0!==e.parentElement&&a(e.parentElement)};if(a(e.target),!(t.$menu&&parseInt(n,10)>parseInt(t.$menu.css("zIndex"),10))){switch(e.keyCode){case 9:case 38:if(f.keyStop(e,t),t.isInput){if(9===e.keyCode&&e.shiftKey)return e.preventDefault(),t.$selected&&t.$selected.find("input, textarea, select").blur(),void(null!==t.$menu&&void 0!==t.$menu&&t.$menu.trigger("prevcommand"));if(38===e.keyCode&&"checkbox"===t.$selected.find("input, textarea, select").prop("type"))return void e.preventDefault()}else if(9!==e.keyCode||e.shiftKey)return void(null!==t.$menu&&void 0!==t.$menu&&t.$menu.trigger("prevcommand"));break;case 40:if(f.keyStop(e,t),!t.isInput)return void(null!==t.$menu&&void 0!==t.$menu&&t.$menu.trigger("nextcommand"));if(9===e.keyCode)return e.preventDefault(),t.$selected&&t.$selected.find("input, textarea, select").blur(),void(null!==t.$menu&&void 0!==t.$menu&&t.$menu.trigger("nextcommand"));if(40===e.keyCode&&"checkbox"===t.$selected.find("input, textarea, select").prop("type"))return void e.preventDefault();break;case 37:if(f.keyStop(e,t),t.isInput||!t.$selected||!t.$selected.length)break;if(!t.$selected.parent().hasClass("context-menu-root")){var s=t.$selected.parent().parent();return t.$selected.trigger("contextmenu:blur"),void(t.$selected=s)}break;case 39:if(f.keyStop(e,t),t.isInput||!t.$selected||!t.$selected.length)break;var i=t.$selected.data("contextMenu")||{};if(i.$menu&&t.$selected.hasClass("context-menu-submenu"))return t.$selected=null,i.$selected=null,void i.$menu.trigger("nextcommand");break;case 35:case 36:return t.$selected&&t.$selected.find("input, textarea, select").length?void 0:((t.$selected&&t.$selected.parent()||t.$menu).children(":not(."+t.classNames.disabled+", ."+t.classNames.notSelectable+")")[36===e.keyCode?"first":"last"]().trigger("contextmenu:focus"),void e.preventDefault());case 13:if(f.keyStop(e,t),t.isInput){if(t.$selected&&!t.$selected.is("textarea, select"))return void e.preventDefault();break}return void(void 0!==t.$selected&&null!==t.$selected&&t.$selected.trigger("mouseup"));case 32:case 33:case 34:return void f.keyStop(e,t);case 27:return f.keyStop(e,t),void(null!==t.$menu&&void 0!==t.$menu&&t.$menu.trigger("contextmenu:hide"));default:var c=String.fromCharCode(e.keyCode).toUpperCase();if(t.accesskeys&&t.accesskeys[c])return void t.accesskeys[c].$node.trigger(t.accesskeys[c].$menu?"contextmenu:focus":"mouseup")}e.stopPropagation(),void 0!==t.$selected&&null!==t.$selected&&t.$selected.trigger(e)}},prevItem:function(t){t.stopPropagation();var n=e(this).data("contextMenu")||{},a=e(this).data("contextMenuRoot")||{};if(n.$selected){var o=n.$selected;(n=n.$selected.parent().data("contextMenu")||{}).$selected=o}for(var s=n.$menu.children(),i=n.$selected&&n.$selected.prev().length?n.$selected.prev():s.last(),c=i;i.hasClass(a.classNames.disabled)||i.hasClass(a.classNames.notSelectable)||i.is(":hidden");)if((i=i.prev().length?i.prev():s.last()).is(c))return;n.$selected&&f.itemMouseleave.call(n.$selected.get(0),t),f.itemMouseenter.call(i.get(0),t);var l=i.find("input, textarea, select");l.length&&l.focus()},nextItem:function(t){t.stopPropagation();var n=e(this).data("contextMenu")||{},a=e(this).data("contextMenuRoot")||{};if(n.$selected){var o=n.$selected;(n=n.$selected.parent().data("contextMenu")||{}).$selected=o}for(var s=n.$menu.children(),i=n.$selected&&n.$selected.next().length?n.$selected.next():s.first(),c=i;i.hasClass(a.classNames.disabled)||i.hasClass(a.classNames.notSelectable)||i.is(":hidden");)if((i=i.next().length?i.next():s.first()).is(c))return;n.$selected&&f.itemMouseleave.call(n.$selected.get(0),t),f.itemMouseenter.call(i.get(0),t);var l=i.find("input, textarea, select");l.length&&l.focus()},focusInput:function(){var t=e(this).closest(".context-menu-item"),n=t.data(),a=n.contextMenu,o=n.contextMenuRoot;o.$selected=a.$selected=t,o.isInput=a.isInput=!0},blurInput:function(){var t=e(this).closest(".context-menu-item").data(),n=t.contextMenu;t.contextMenuRoot.isInput=n.isInput=!1},menuMouseenter:function(){e(this).data().contextMenuRoot.hovering=!0},menuMouseleave:function(t){var n=e(this).data().contextMenuRoot;n.$layer&&n.$layer.is(t.relatedTarget)&&(n.hovering=!1)},itemMouseenter:function(t){var n=e(this),a=n.data(),o=a.contextMenu,s=a.contextMenuRoot;s.hovering=!0,t&&s.$layer&&s.$layer.is(t.relatedTarget)&&(t.preventDefault(),t.stopImmediatePropagation()),(o.$menu?o:s).$menu.children("."+s.classNames.hover).trigger("contextmenu:blur").children(".hover").trigger("contextmenu:blur"),n.hasClass(s.classNames.disabled)||n.hasClass(s.classNames.notSelectable)?o.$selected=null:n.trigger("contextmenu:focus")},itemMouseleave:function(t){var n=e(this),a=n.data(),o=a.contextMenu,s=a.contextMenuRoot;if(s!==o&&s.$layer&&s.$layer.is(t.relatedTarget))return void 0!==s.$selected&&null!==s.$selected&&s.$selected.trigger("contextmenu:blur"),t.preventDefault(),t.stopImmediatePropagation(),void(s.$selected=o.$selected=o.$node);o&&o.$menu&&o.$menu.hasClass("context-menu-visible")||n.trigger("contextmenu:blur")},itemClick:function(t){var n,a=e(this),o=a.data(),s=o.contextMenu,i=o.contextMenuRoot,c=o.contextMenuKey;if(!(!s.items[c]||a.is("."+i.classNames.disabled+", .context-menu-separator, ."+i.classNames.notSelectable)||a.is(".context-menu-submenu")&&!1===i.selectableSubMenu)){if(t.preventDefault(),t.stopImmediatePropagation(),e.isFunction(s.callbacks[c])&&Object.prototype.hasOwnProperty.call(s.callbacks,c))n=s.callbacks[c];else{if(!e.isFunction(i.callback))return;n=i.callback}!1!==n.call(i.$trigger,c,i,t)?i.$menu.trigger("contextmenu:hide"):i.$menu.parent().length&&h.update.call(i.$trigger,i)}},inputClick:function(e){e.stopImmediatePropagation()},hideMenu:function(t,n){var a=e(this).data("contextMenuRoot");h.hide.call(a.$trigger,a,n&&n.force)},focusItem:function(t){t.stopPropagation();var n=e(this),a=n.data(),o=a.contextMenu,s=a.contextMenuRoot;n.hasClass(s.classNames.disabled)||n.hasClass(s.classNames.notSelectable)||(n.addClass([s.classNames.hover,s.classNames.visible].join(" ")).parent().find(".context-menu-item").not(n).removeClass(s.classNames.visible).filter("."+s.classNames.hover).trigger("contextmenu:blur"),o.$selected=s.$selected=n,o&&o.$node&&o.$node.hasClass("context-menu-submenu")&&o.$node.addClass(s.classNames.hover),o.$node&&s.positionSubmenu.call(o.$node,o.$menu))},blurItem:function(t){t.stopPropagation();var n=e(this),a=n.data(),o=a.contextMenu,s=a.contextMenuRoot;o.autoHide&&n.removeClass(s.classNames.visible),n.removeClass(s.classNames.hover),o.$selected=null}},h={show:function(t,n,a){var s=e(this),i={};if(e("#context-menu-layer").trigger("mousedown"),t.$trigger=s,!1!==t.events.show.call(s,t)){if(h.update.call(s,t),t.position.call(s,t,n,a),t.zIndex){var c=t.zIndex;"function"==typeof t.zIndex&&(c=t.zIndex.call(s,t)),i.zIndex=p(s)+c}h.layer.call(t.$menu,t,i.zIndex),t.$menu.find("ul").css("zIndex",i.zIndex+1),t.$menu.css(i)[t.animation.show](t.animation.duration,function(){s.trigger("contextmenu:visible"),h.activated(t),t.events.activated()}),s.data("contextMenu",t).addClass("context-menu-active"),e(document).off("keydown.contextMenu").on("keydown.contextMenu",f.key),t.autoHide&&e(document).on("mousemove.contextMenuAutoHide",function(e){var n=s.offset();n.right=n.left+s.outerWidth(),n.bottom=n.top+s.outerHeight(),!t.$layer||t.hovering||e.pageX>=n.left&&e.pageX<=n.right&&e.pageY>=n.top&&e.pageY<=n.bottom||setTimeout(function(){t.hovering||null===t.$menu||void 0===t.$menu||t.$menu.trigger("contextmenu:hide")},50)})}else o=null},hide:function(t,n){var a=e(this);if(t||(t=a.data("contextMenu")||{}),n||!t.events||!1!==t.events.hide.call(a,t)){if(a.removeData("contextMenu").removeClass("context-menu-active"),t.$layer){setTimeout(function(e){return function(){e.remove()}}(t.$layer),10);try{delete t.$layer}catch(e){t.$layer=null}}o=null,t.$menu.find("."+t.classNames.hover).trigger("contextmenu:blur"),t.$selected=null,t.$menu.find("."+t.classNames.visible).removeClass(t.classNames.visible),e(document).off(".contextMenuAutoHide").off("keydown.contextMenu"),t.$menu&&t.$menu[t.animation.hide](t.animation.duration,function(){t.build&&(t.$menu.remove(),e.each(t,function(e){switch(e){case"ns":case"selector":case"build":case"trigger":return!0;default:t[e]=void 0;try{delete t[e]}catch(e){}return!0}})),setTimeout(function(){a.trigger("contextmenu:hidden")},10)})}},create:function(n,a){function o(t){var n=e(" ");if(t._accesskey)t._beforeAccesskey&&n.append(document.createTextNode(t._beforeAccesskey)),e(" ").addClass("context-menu-accesskey").text(t._accesskey).appendTo(n),t._afterAccesskey&&n.append(document.createTextNode(t._afterAccesskey));else if(t.isHtmlName){if(void 0!==t.accesskey)throw new Error("accesskeys are not compatible with HTML names and cannot be used together in the same item");n.html(t.name)}else n.text(t.name);return n}void 0===a&&(a=n),n.$menu=e('').addClass(n.className||"").data({contextMenu:n,contextMenuRoot:a}),e.each(["callbacks","commands","inputs"],function(e,t){n[t]={},a[t]||(a[t]={})}),a.accesskeys||(a.accesskeys={}),e.each(n.items,function(s,i){var c=e('').addClass(i.className||""),l=null,r=null;if(c.on("click",e.noop),"string"!=typeof i&&"cm_separator"!==i.type||(i={type:"cm_seperator"}),i.$node=c.data({contextMenu:n,contextMenuRoot:a,contextMenuKey:s}),void 0!==i.accesskey)for(var d,m=t(i.accesskey),p=0;d=m[p];p++)if(!a.accesskeys[d]){a.accesskeys[d]=i;var v=i.name.match(new RegExp("^(.*?)("+d+")(.*)$","i"));v&&(i._beforeAccesskey=v[1],i._accesskey=v[2],i._afterAccesskey=v[3]);break}if(i.type&&u[i.type])u[i.type].call(c,i,n,a),e.each([n,a],function(t,a){a.commands[s]=i,!e.isFunction(i.callback)||void 0!==a.callbacks[s]&&void 0!==n.type||(a.callbacks[s]=i.callback)});else{switch("cm_seperator"===i.type?c.addClass("context-menu-separator "+a.classNames.notSelectable):"html"===i.type?c.addClass("context-menu-html "+a.classNames.notSelectable):"sub"===i.type||(i.type?(l=e(" ").appendTo(c),o(i).appendTo(l),c.addClass("context-menu-input"),n.hasTypes=!0,e.each([n,a],function(e,t){t.commands[s]=i,t.inputs[s]=i})):i.items&&(i.type="sub")),i.type){case"cm_seperator":break;case"text":r=e(' ').attr("name","context-menu-input-"+s).val(i.value||"").appendTo(l);break;case"textarea":r=e(' ').attr("name","context-menu-input-"+s).val(i.value||"").appendTo(l),i.height&&r.height(i.height);break;case"checkbox":r=e(' ').attr("name","context-menu-input-"+s).val(i.value||"").prop("checked",!!i.selected).prependTo(l);break;case"radio":r=e(' ').attr("name","context-menu-input-"+i.radio).val(i.value||"").prop("checked",!!i.selected).prependTo(l);break;case"select":r=e(' ').attr("name","context-menu-input-"+s).appendTo(l),i.options&&(e.each(i.options,function(t,n){e(" ").val(t).text(n).appendTo(r)}),r.val(i.selected));break;case"sub":o(i).appendTo(c),i.appendTo=i.$node,c.data("contextMenu",i).addClass("context-menu-submenu"),i.callback=null,"function"==typeof i.items.then?h.processPromises(i,a,i.items):h.create(i,a);break;case"html":e(i.html).appendTo(c);break;default:e.each([n,a],function(t,a){a.commands[s]=i,!e.isFunction(i.callback)||void 0!==a.callbacks[s]&&void 0!==n.type||(a.callbacks[s]=i.callback)}),o(i).appendTo(c)}i.type&&"sub"!==i.type&&"html"!==i.type&&"cm_seperator"!==i.type&&(r.on("focus",f.focusInput).on("blur",f.blurInput),i.events&&r.on(i.events,n)),i.icon&&(e.isFunction(i.icon)?i._icon=i.icon.call(this,this,c,s,i):"string"==typeof i.icon&&"fa-"===i.icon.substring(0,3)?i._icon=a.classNames.icon+" "+a.classNames.icon+"--fa fa "+i.icon:i._icon=a.classNames.icon+" "+a.classNames.icon+"-"+i.icon,c.addClass(i._icon))}i.$input=r,i.$label=l,c.appendTo(n.$menu),!n.hasTypes&&e.support.eventSelectstart&&c.on("selectstart.disableTextSelect",f.abortevent)}),n.$node||n.$menu.css("display","none").addClass("context-menu-root"),n.$menu.appendTo(n.appendTo||document.body)},resize:function(t,n){var a;t.css({position:"absolute",display:"block"}),t.data("width",(a=t.get(0)).getBoundingClientRect?Math.ceil(a.getBoundingClientRect().width):t.outerWidth()+1),t.css({position:"static",minWidth:"0px",maxWidth:"100000px"}),t.find("> li > ul").each(function(){h.resize(e(this),!0)}),n||t.find("ul").addBack().css({position:"",display:"",minWidth:"",maxWidth:""}).outerWidth(function(){return e(this).data("width")})},update:function(t,n){var a=this;void 0===n&&(n=t,h.resize(t.$menu)),t.$menu.children().each(function(){var o,s=e(this),i=s.data("contextMenuKey"),c=t.items[i],l=e.isFunction(c.disabled)&&c.disabled.call(a,i,n)||!0===c.disabled;if(o=e.isFunction(c.visible)?c.visible.call(a,i,n):void 0===c.visible||!0===c.visible,s[o?"show":"hide"](),s[l?"addClass":"removeClass"](n.classNames.disabled),e.isFunction(c.icon)&&(s.removeClass(c._icon),c._icon=c.icon.call(this,a,s,i,c),s.addClass(c._icon)),c.type)switch(s.find("input, select, textarea").prop("disabled",l),c.type){case"text":case"textarea":c.$input.val(c.value||"");break;case"checkbox":case"radio":c.$input.val(c.value||"").prop("checked",!!c.selected);break;case"select":c.$input.val((0===c.selected?"0":c.selected)||"")}c.$menu&&h.update.call(a,c,n)})},layer:function(t,n){var a=t.$layer=e('').css({height:i.height(),width:i.width(),display:"block",position:"fixed","z-index":n,top:0,left:0,opacity:0,filter:"alpha(opacity=0)","background-color":"#000"}).data("contextMenuRoot",t).insertBefore(this).on("contextmenu",f.abortevent).on("mousedown",f.layerClick);return void 0===document.body.style.maxWidth&&a.css({position:"absolute",height:e(document).height()}),a},processPromises:function(e,t,n){function a(e,t,n){void 0===n?(n={error:{name:"No items and no error item",icon:"context-menu-icon context-menu-icon-quit"}},window.console&&(console.error||console.log).call(console,'When you reject a promise, provide an "items" object, equal to normal sub-menu items')):"string"==typeof n&&(n={error:{name:n}}),o(e,t,n)}function o(e,t,n){void 0!==t.$menu&&t.$menu.is(":visible")&&(e.$node.removeClass(t.classNames.iconLoadingClass),e.items=n,h.create(e,t,!0),h.update(e,t),t.positionSubmenu.call(e.$node,e.$menu))}e.$node.addClass(t.classNames.iconLoadingClass),n.then(function(e,t,n){void 0===n&&a(void 0),o(e,t,n)}.bind(this,e,t),a.bind(this,e,t))},activated:function(t){var n=t.$menu,a=n.offset(),o=e(window).height(),s=e(window).scrollTop(),i=n.height();i>o?n.css({height:o+"px","overflow-x":"hidden","overflow-y":"auto",top:s+"px"}):(a.tops+o)&&n.css({top:"0px"})}};e.fn.contextMenu=function(t){var n=this,a=t;if(this.length>0)if(void 0===t)this.first().trigger("contextmenu");else if(void 0!==t.x&&void 0!==t.y)this.first().trigger(e.Event("contextmenu",{pageX:t.x,pageY:t.y,mouseButton:t.button}));else if("hide"===t){var o=this.first().data("contextMenu")?this.first().data("contextMenu").$menu:null;o&&o.trigger("contextmenu:hide")}else"destroy"===t?e.contextMenu("destroy",{context:this}):e.isPlainObject(t)?(t.context=this,e.contextMenu("create",t)):t?this.removeClass("context-menu-disabled"):t||this.addClass("context-menu-disabled");else e.each(r,function(){this.selector===n.selector&&(a.data=this,e.extend(a.data,{trigger:"demand"}))}),f.contextmenu.call(a.target,a);return this},e.contextMenu=function(t,n){"string"!=typeof t&&(n=t,t="create"),"string"==typeof n?n={selector:n}:void 0===n&&(n={});var a=e.extend(!0,{},d,n||{}),o=e(document),i=o,u=!1;switch(a.context&&a.context.length?(i=e(a.context).first(),a.context=i.get(0),u=!e(a.context).is(document)):a.context=document,t){case"update":if(u)h.update(i);else for(var m in r)r.hasOwnProperty(m)&&h.update(r[m]);break;case"create":if(!a.selector)throw new Error("No selector specified");if(a.selector.match(/.context-menu-(list|item|input)($|\s)/))throw new Error('Cannot bind to selector "'+a.selector+'" as it contains a reserved className');if(!a.build&&(!a.items||e.isEmptyObject(a.items)))throw new Error("No Items specified");if(c++,a.ns=".contextMenu"+c,u||(l[a.selector]=a.ns),r[a.ns]=a,a.trigger||(a.trigger="right"),!s){var p="click"===a.itemClickEvent?"click.contextMenu":"mouseup.contextMenu",v={"contextmenu:focus.contextMenu":f.focusItem,"contextmenu:blur.contextMenu":f.blurItem,"contextmenu.contextMenu":f.abortevent,"mouseenter.contextMenu":f.itemMouseenter,"mouseleave.contextMenu":f.itemMouseleave};v[p]=f.itemClick,o.on({"contextmenu:hide.contextMenu":f.hideMenu,"prevcommand.contextMenu":f.prevItem,"nextcommand.contextMenu":f.nextItem,"contextmenu.contextMenu":f.abortevent,"mouseenter.contextMenu":f.menuMouseenter,"mouseleave.contextMenu":f.menuMouseleave},".context-menu-list").on("mouseup.contextMenu",".context-menu-input",f.inputClick).on(v,".context-menu-item"),s=!0}switch(i.on("contextmenu"+a.ns,a.selector,a,f.contextmenu),u&&i.on("remove"+a.ns,function(){e(this).contextMenu("destroy")}),a.trigger){case"hover":i.on("mouseenter"+a.ns,a.selector,a,f.mouseenter).on("mouseleave"+a.ns,a.selector,a,f.mouseleave);break;case"left":i.on("click"+a.ns,a.selector,a,f.click);break;case"touchstart":i.on("touchstart"+a.ns,a.selector,a,f.click)}a.build||h.create(a);break;case"destroy":var x;if(u){var g=a.context;e.each(r,function(t,n){if(!n)return!0;if(!e(g).is(n.selector))return!0;(x=e(".context-menu-list").filter(":visible")).length&&x.data().contextMenuRoot.$trigger.is(e(n.context).find(n.selector))&&x.trigger("contextmenu:hide",{force:!0});try{r[n.ns].$menu&&r[n.ns].$menu.remove(),delete r[n.ns]}catch(e){r[n.ns]=null}return e(n.context).off(n.ns),!0})}else if(a.selector){if(l[a.selector]){(x=e(".context-menu-list").filter(":visible")).length&&x.data().contextMenuRoot.$trigger.is(a.selector)&&x.trigger("contextmenu:hide",{force:!0});try{r[l[a.selector]].$menu&&r[l[a.selector]].$menu.remove(),delete r[l[a.selector]]}catch(e){r[l[a.selector]]=null}o.off(l[a.selector])}}else o.off(".contextMenu .contextMenuAutoHide"),e.each(r,function(t,n){e(n.context).off(n.ns)}),l={},r={},c=0,s=!1,e("#context-menu-layer, .context-menu-list").remove();break;case"html5":(!e.support.htmlCommand&&!e.support.htmlMenuitem||"boolean"==typeof n&&n)&&e('menu[type="context"]').each(function(){this.id&&e.contextMenu({selector:"[contextmenu="+this.id+"]",items:e.contextMenu.fromMenu(this)})}).css("display","none");break;default:throw new Error('Unknown operation "'+t+'"')}return this},e.contextMenu.setInputValues=function(t,n){void 0===n&&(n={}),e.each(t.inputs,function(e,t){switch(t.type){case"text":case"textarea":t.value=n[e]||"";break;case"checkbox":t.selected=!!n[e];break;case"radio":t.selected=(n[t.radio]||"")===t.value;break;case"select":t.selected=n[e]||""}})},e.contextMenu.getInputValues=function(t,n){return void 0===n&&(n={}),e.each(t.inputs,function(e,t){switch(t.type){case"text":case"textarea":case"select":n[e]=t.$input.val();break;case"checkbox":n[e]=t.$input.prop("checked");break;case"radio":t.$input.prop("checked")&&(n[t.radio]=t.value)}}),n},e.contextMenu.fromMenu=function(t){var n={};return a(n,e(t).children()),n},e.contextMenu.defaults=d,e.contextMenu.types=u,e.contextMenu.handle=f,e.contextMenu.op=h,e.contextMenu.menus=r});
+//# sourceMappingURL=jquery.contextMenu.min.js.map
diff --git a/extensions/database/module/scripts/index/jquery.ui.position.min.js b/extensions/database/module/scripts/index/jquery.ui.position.min.js
new file mode 100644
index 000000000..48d6a99b4
--- /dev/null
+++ b/extensions/database/module/scripts/index/jquery.ui.position.min.js
@@ -0,0 +1,6 @@
+/*! jQuery UI - v1.12.1 - 2016-09-16
+ * http://jqueryui.com
+ * Includes: position.js
+ * Copyright jQuery Foundation and other contributors; Licensed MIT */
+
+(function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)})(function(t){t.ui=t.ui||{},t.ui.version="1.12.1",function(){function e(t,e,i){return[parseFloat(t[0])*(u.test(t[0])?e/100:1),parseFloat(t[1])*(u.test(t[1])?i/100:1)]}function i(e,i){return parseInt(t.css(e,i),10)||0}function s(e){var i=e[0];return 9===i.nodeType?{width:e.width(),height:e.height(),offset:{top:0,left:0}}:t.isWindow(i)?{width:e.width(),height:e.height(),offset:{top:e.scrollTop(),left:e.scrollLeft()}}:i.preventDefault?{width:0,height:0,offset:{top:i.pageY,left:i.pageX}}:{width:e.outerWidth(),height:e.outerHeight(),offset:e.offset()}}var n,o=Math.max,a=Math.abs,r=/left|center|right/,l=/top|center|bottom/,h=/[\+\-]\d+(\.[\d]+)?%?/,c=/^\w+/,u=/%$/,d=t.fn.position;t.position={scrollbarWidth:function(){if(void 0!==n)return n;var e,i,s=t(""),o=s.children()[0];return t("body").append(s),e=o.offsetWidth,s.css("overflow","scroll"),i=o.offsetWidth,e===i&&(i=s[0].clientWidth),s.remove(),n=e-i},getScrollInfo:function(e){var i=e.isWindow||e.isDocument?"":e.element.css("overflow-x"),s=e.isWindow||e.isDocument?"":e.element.css("overflow-y"),n="scroll"===i||"auto"===i&&e.widthi?"left":e>0?"right":"center",vertical:0>r?"top":s>0?"bottom":"middle"};h>p&&p>a(e+i)&&(u.horizontal="center"),c>f&&f>a(s+r)&&(u.vertical="middle"),u.important=o(a(e),a(i))>o(a(s),a(r))?"horizontal":"vertical",n.using.call(this,t,u)}),l.offset(t.extend(D,{using:r}))})},t.ui.position={fit:{left:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollLeft:s.offset.left,a=s.width,r=t.left-e.collisionPosition.marginLeft,l=n-r,h=r+e.collisionWidth-a-n;e.collisionWidth>a?l>0&&0>=h?(i=t.left+l+e.collisionWidth-a-n,t.left+=l-i):t.left=h>0&&0>=l?n:l>h?n+a-e.collisionWidth:n:l>0?t.left+=l:h>0?t.left-=h:t.left=o(t.left-r,t.left)},top:function(t,e){var i,s=e.within,n=s.isWindow?s.scrollTop:s.offset.top,a=e.within.height,r=t.top-e.collisionPosition.marginTop,l=n-r,h=r+e.collisionHeight-a-n;e.collisionHeight>a?l>0&&0>=h?(i=t.top+l+e.collisionHeight-a-n,t.top+=l-i):t.top=h>0&&0>=l?n:l>h?n+a-e.collisionHeight:n:l>0?t.top+=l:h>0?t.top-=h:t.top=o(t.top-r,t.top)}},flip:{left:function(t,e){var i,s,n=e.within,o=n.offset.left+n.scrollLeft,r=n.width,l=n.isWindow?n.scrollLeft:n.offset.left,h=t.left-e.collisionPosition.marginLeft,c=h-l,u=h+e.collisionWidth-r-l,d="left"===e.my[0]?-e.elemWidth:"right"===e.my[0]?e.elemWidth:0,p="left"===e.at[0]?e.targetWidth:"right"===e.at[0]?-e.targetWidth:0,f=-2*e.offset[0];0>c?(i=t.left+d+p+f+e.collisionWidth-r-o,(0>i||a(c)>i)&&(t.left+=d+p+f)):u>0&&(s=t.left-e.collisionPosition.marginLeft+d+p+f-l,(s>0||u>a(s))&&(t.left+=d+p+f))},top:function(t,e){var i,s,n=e.within,o=n.offset.top+n.scrollTop,r=n.height,l=n.isWindow?n.scrollTop:n.offset.top,h=t.top-e.collisionPosition.marginTop,c=h-l,u=h+e.collisionHeight-r-l,d="top"===e.my[1],p=d?-e.elemHeight:"bottom"===e.my[1]?e.elemHeight:0,f="top"===e.at[1]?e.targetHeight:"bottom"===e.at[1]?-e.targetHeight:0,g=-2*e.offset[1];0>c?(s=t.top+p+f+g+e.collisionHeight-r-o,(0>s||a(c)>s)&&(t.top+=p+f+g)):u>0&&(i=t.top-e.collisionPosition.marginTop+p+f+g-l,(i>0||u>a(i))&&(t.top+=p+f+g))}},flipfit:{left:function(){t.ui.position.flip.left.apply(this,arguments),t.ui.position.fit.left.apply(this,arguments)},top:function(){t.ui.position.flip.top.apply(this,arguments),t.ui.position.fit.top.apply(this,arguments)}}}}(),t.ui.position});
\ No newline at end of file
diff --git a/extensions/database/module/scripts/project/database-exporters.js b/extensions/database/module/scripts/project/database-exporters.js
new file mode 100644
index 000000000..eacb5a488
--- /dev/null
+++ b/extensions/database/module/scripts/project/database-exporters.js
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+var dictionary = "";
+$.ajax({
+ url : "command/core/load-language?",
+ type : "POST",
+ async : false,
+ data : {
+ module : "database",
+
+ },
+ success : function(data) {
+ dictionary = data;
+ }
+});
+$.i18n.setDictionary(dictionary);
+// End internationalization
+
+(function() {
+
+})();
diff --git a/extensions/database/module/styles/bootstrap.css b/extensions/database/module/styles/bootstrap.css
new file mode 100644
index 000000000..39e2a1597
--- /dev/null
+++ b/extensions/database/module/styles/bootstrap.css
@@ -0,0 +1,1701 @@
+/*!
+ * Bootstrap v3.3.7 (http://getbootstrap.com)
+ * Copyright 2011-2017 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+
+/*!
+ * Generated using the Bootstrap Customizer (http://getbootstrap.com/docs/3.3/customize/?id=3426e25f732fc7882717e871fd08de17)
+ * Config saved to config.json and https://gist.github.com/3426e25f732fc7882717e871fd08de17
+ */
+/*!
+ * Bootstrap v3.3.7 (http://getbootstrap.com)
+ * Copyright 2011-2016 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
+
+.panel {
+ margin-bottom: 20px;
+ background-color: #ffffff;
+ border: 1px solid transparent;
+ border-radius: 4px;
+ -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
+ box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
+}
+.panel-body {
+ padding: 15px;
+}
+.panel-heading {
+ padding: 10px 15px;
+ border-bottom: 1px solid transparent;
+ border-top-right-radius: 3px;
+ border-top-left-radius: 3px;
+}
+.panel-heading > .dropdown .dropdown-toggle {
+ color: inherit;
+}
+.panel-title {
+ margin-top: 0;
+ margin-bottom: 0;
+ font-size: 16px;
+ color: inherit;
+}
+.panel-title > a,
+.panel-title > small,
+.panel-title > .small,
+.panel-title > small > a,
+.panel-title > .small > a {
+ color: inherit;
+}
+.panel-footer {
+ padding: 10px 15px;
+ background-color: #f5f5f5;
+ border-top: 1px solid #dddddd;
+ border-bottom-right-radius: 3px;
+ border-bottom-left-radius: 3px;
+}
+.panel > .list-group,
+.panel > .panel-collapse > .list-group {
+ margin-bottom: 0;
+}
+.panel > .list-group .list-group-item,
+.panel > .panel-collapse > .list-group .list-group-item {
+ border-width: 1px 0;
+ border-radius: 0;
+}
+.panel > .list-group:first-child .list-group-item:first-child,
+.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child {
+ border-top: 0;
+ border-top-right-radius: 3px;
+ border-top-left-radius: 3px;
+}
+.panel > .list-group:last-child .list-group-item:last-child,
+.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child {
+ border-bottom: 0;
+ border-bottom-right-radius: 3px;
+ border-bottom-left-radius: 3px;
+}
+.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child {
+ border-top-right-radius: 0;
+ border-top-left-radius: 0;
+}
+.panel-heading + .list-group .list-group-item:first-child {
+ border-top-width: 0;
+}
+.list-group + .panel-footer {
+ border-top-width: 0;
+}
+.panel > .table,
+.panel > .table-responsive > .table,
+.panel > .panel-collapse > .table {
+ margin-bottom: 0;
+}
+.panel > .table caption,
+.panel > .table-responsive > .table caption,
+.panel > .panel-collapse > .table caption {
+ padding-left: 15px;
+ padding-right: 15px;
+}
+.panel > .table:first-child,
+.panel > .table-responsive:first-child > .table:first-child {
+ border-top-right-radius: 3px;
+ border-top-left-radius: 3px;
+}
+.panel > .table:first-child > thead:first-child > tr:first-child,
+.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child,
+.panel > .table:first-child > tbody:first-child > tr:first-child,
+.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child {
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px;
+}
+.panel > .table:first-child > thead:first-child > tr:first-child td:first-child,
+.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child,
+.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child,
+.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child,
+.panel > .table:first-child > thead:first-child > tr:first-child th:first-child,
+.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child,
+.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child,
+.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child {
+ border-top-left-radius: 3px;
+}
+.panel > .table:first-child > thead:first-child > tr:first-child td:last-child,
+.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child,
+.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child,
+.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child,
+.panel > .table:first-child > thead:first-child > tr:first-child th:last-child,
+.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child,
+.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child,
+.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child {
+ border-top-right-radius: 3px;
+}
+.panel > .table:last-child,
+.panel > .table-responsive:last-child > .table:last-child {
+ border-bottom-right-radius: 3px;
+ border-bottom-left-radius: 3px;
+}
+.panel > .table:last-child > tbody:last-child > tr:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child,
+.panel > .table:last-child > tfoot:last-child > tr:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child {
+ border-bottom-left-radius: 3px;
+ border-bottom-right-radius: 3px;
+}
+.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child,
+.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child,
+.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child,
+.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child,
+.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child,
+.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child,
+.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child,
+.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child {
+ border-bottom-left-radius: 3px;
+}
+.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child,
+.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child,
+.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child,
+.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child,
+.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child {
+ border-bottom-right-radius: 3px;
+}
+.panel > .panel-body + .table,
+.panel > .panel-body + .table-responsive,
+.panel > .table + .panel-body,
+.panel > .table-responsive + .panel-body {
+ border-top: 1px solid #dddddd;
+}
+.panel > .table > tbody:first-child > tr:first-child th,
+.panel > .table > tbody:first-child > tr:first-child td {
+ border-top: 0;
+}
+.panel > .table-bordered,
+.panel > .table-responsive > .table-bordered {
+ border: 0;
+}
+.panel > .table-bordered > thead > tr > th:first-child,
+.panel > .table-responsive > .table-bordered > thead > tr > th:first-child,
+.panel > .table-bordered > tbody > tr > th:first-child,
+.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child,
+.panel > .table-bordered > tfoot > tr > th:first-child,
+.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child,
+.panel > .table-bordered > thead > tr > td:first-child,
+.panel > .table-responsive > .table-bordered > thead > tr > td:first-child,
+.panel > .table-bordered > tbody > tr > td:first-child,
+.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child,
+.panel > .table-bordered > tfoot > tr > td:first-child,
+.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child {
+ border-left: 0;
+}
+.panel > .table-bordered > thead > tr > th:last-child,
+.panel > .table-responsive > .table-bordered > thead > tr > th:last-child,
+.panel > .table-bordered > tbody > tr > th:last-child,
+.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child,
+.panel > .table-bordered > tfoot > tr > th:last-child,
+.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child,
+.panel > .table-bordered > thead > tr > td:last-child,
+.panel > .table-responsive > .table-bordered > thead > tr > td:last-child,
+.panel > .table-bordered > tbody > tr > td:last-child,
+.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child,
+.panel > .table-bordered > tfoot > tr > td:last-child,
+.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child {
+ border-right: 0;
+}
+.panel > .table-bordered > thead > tr:first-child > td,
+.panel > .table-responsive > .table-bordered > thead > tr:first-child > td,
+.panel > .table-bordered > tbody > tr:first-child > td,
+.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td,
+.panel > .table-bordered > thead > tr:first-child > th,
+.panel > .table-responsive > .table-bordered > thead > tr:first-child > th,
+.panel > .table-bordered > tbody > tr:first-child > th,
+.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th {
+ border-bottom: 0;
+}
+.panel > .table-bordered > tbody > tr:last-child > td,
+.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td,
+.panel > .table-bordered > tfoot > tr:last-child > td,
+.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td,
+.panel > .table-bordered > tbody > tr:last-child > th,
+.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th,
+.panel > .table-bordered > tfoot > tr:last-child > th,
+.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th {
+ border-bottom: 0;
+}
+.panel > .table-responsive {
+ border: 0;
+ margin-bottom: 0;
+}
+.panel-group {
+ margin-bottom: 20px;
+}
+.panel-group .panel {
+ margin-bottom: 0;
+ border-radius: 4px;
+}
+.panel-group .panel + .panel {
+ margin-top: 5px;
+}
+.panel-group .panel-heading {
+ border-bottom: 0;
+}
+.panel-group .panel-heading + .panel-collapse > .panel-body,
+.panel-group .panel-heading + .panel-collapse > .list-group {
+ border-top: 1px solid #dddddd;
+}
+.panel-group .panel-footer {
+ border-top: 0;
+}
+.panel-group .panel-footer + .panel-collapse .panel-body {
+ border-bottom: 1px solid #dddddd;
+}
+.panel-default {
+ border-color: #dddddd;
+}
+.panel-default > .panel-heading {
+ color: #333333;
+ background-color: #f5f5f5;
+ border-color: #dddddd;
+}
+.panel-default > .panel-heading + .panel-collapse > .panel-body {
+ border-top-color: #dddddd;
+}
+.panel-default > .panel-heading .badge {
+ color: #f5f5f5;
+ background-color: #333333;
+}
+.panel-default > .panel-footer + .panel-collapse > .panel-body {
+ border-bottom-color: #dddddd;
+}
+.panel-primary {
+ border-color: #337ab7;
+}
+.panel-primary > .panel-heading {
+ color: #ffffff;
+ background-color: #337ab7;
+ border-color: #337ab7;
+}
+.panel-primary > .panel-heading + .panel-collapse > .panel-body {
+ border-top-color: #337ab7;
+}
+.panel-primary > .panel-heading .badge {
+ color: #337ab7;
+ background-color: #ffffff;
+}
+.panel-primary > .panel-footer + .panel-collapse > .panel-body {
+ border-bottom-color: #337ab7;
+}
+.panel-success {
+ border-color: #d6e9c6;
+}
+.panel-success > .panel-heading {
+ color: #3c763d;
+ background-color: #dff0d8;
+ border-color: #d6e9c6;
+}
+.panel-success > .panel-heading + .panel-collapse > .panel-body {
+ border-top-color: #d6e9c6;
+}
+.panel-success > .panel-heading .badge {
+ color: #dff0d8;
+ background-color: #3c763d;
+}
+.panel-success > .panel-footer + .panel-collapse > .panel-body {
+ border-bottom-color: #d6e9c6;
+}
+.panel-info {
+ border-color: #bce8f1;
+}
+.panel-info > .panel-heading {
+ color: #31708f;
+ background-color: #d9edf7;
+ border-color: #bce8f1;
+}
+.panel-info > .panel-heading + .panel-collapse > .panel-body {
+ border-top-color: #bce8f1;
+}
+.panel-info > .panel-heading .badge {
+ color: #d9edf7;
+ background-color: #31708f;
+}
+.panel-info > .panel-footer + .panel-collapse > .panel-body {
+ border-bottom-color: #bce8f1;
+}
+.panel-warning {
+ border-color: #faebcc;
+}
+.panel-warning > .panel-heading {
+ color: #8a6d3b;
+ background-color: #fcf8e3;
+ border-color: #faebcc;
+}
+.panel-warning > .panel-heading + .panel-collapse > .panel-body {
+ border-top-color: #faebcc;
+}
+.panel-warning > .panel-heading .badge {
+ color: #fcf8e3;
+ background-color: #8a6d3b;
+}
+.panel-warning > .panel-footer + .panel-collapse > .panel-body {
+ border-bottom-color: #faebcc;
+}
+.panel-danger {
+ border-color: #ebccd1;
+}
+.panel-danger > .panel-heading {
+ color: #a94442;
+ background-color: #f2dede;
+ border-color: #ebccd1;
+}
+.panel-danger > .panel-heading + .panel-collapse > .panel-body {
+ border-top-color: #ebccd1;
+}
+.panel-danger > .panel-heading .badge {
+ color: #f2dede;
+ background-color: #a94442;
+}
+.panel-danger > .panel-footer + .panel-collapse > .panel-body {
+ border-bottom-color: #ebccd1;
+}
+.clearfix:before,
+.clearfix:after,
+.nav:before,
+.nav:after,
+.panel-body:before,
+.panel-body:after {
+ content: " ";
+ display: table;
+}
+.clearfix:after,
+.nav:after,
+.panel-body:after {
+ clear: both;
+}
+.center-block {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+}
+.pull-right {
+ float: right !important;
+}
+.pull-left {
+ float: left !important;
+}
+.hide {
+ display: none !important;
+}
+.show {
+ display: block !important;
+}
+.invisible {
+ visibility: hidden;
+}
+.text-hide {
+ font: 0/0 a;
+ color: transparent;
+ text-shadow: none;
+ background-color: transparent;
+ border: 0;
+}
+.hidden {
+ display: none !important;
+}
+.affix {
+ position: fixed;
+}
+
+.alert {
+ padding: 15px;
+ margin-bottom: 20px;
+ border: 1px solid transparent;
+ border-radius: 4px;
+}
+.alert h4 {
+ margin-top: 0;
+ color: inherit;
+}
+.alert .alert-link {
+ font-weight: bold;
+}
+.alert > p,
+.alert > ul {
+ margin-bottom: 0;
+}
+.alert > p + p {
+ margin-top: 5px;
+}
+.alert-dismissable,
+.alert-dismissible {
+ padding-right: 35px;
+}
+.alert-dismissable .close,
+.alert-dismissible .close {
+ position: relative;
+ top: -2px;
+ right: -21px;
+ color: inherit;
+}
+.alert-success {
+ background-color: #dff0d8;
+ border-color: #d6e9c6;
+ color: #3c763d;
+}
+.alert-success hr {
+ border-top-color: #c9e2b3;
+}
+.alert-success .alert-link {
+ color: #2b542c;
+}
+.alert-info {
+ background-color: #d9edf7;
+ border-color: #bce8f1;
+ color: #31708f;
+}
+.alert-info hr {
+ border-top-color: #a6e1ec;
+}
+.alert-info .alert-link {
+ color: #245269;
+}
+.alert-warning {
+ background-color: #fcf8e3;
+ border-color: #faebcc;
+ color: #8a6d3b;
+}
+.alert-warning hr {
+ border-top-color: #f7e1b5;
+}
+.alert-warning .alert-link {
+ color: #66512c;
+}
+.alert-danger {
+ background-color: #f2dede;
+ border-color: #ebccd1;
+ color: #a94442;
+}
+.alert-danger hr {
+ border-top-color: #e4b9c0;
+}
+.alert-danger .alert-link {
+ color: #843534;
+}
+
+@font-face {
+ font-family: 'Glyphicons Halflings';
+ src: url('../images/fonts/glyphicons-halflings-regular.eot');
+ src: url('../images/fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../images/fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../images/fonts/glyphicons-halflings-regular.woff') format('woff'), url('../images/fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../images/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
+}
+.glyphicon {
+ position: relative;
+ top: 1px;
+ display: inline-block;
+ font-family: 'Glyphicons Halflings';
+ font-style: normal;
+ font-weight: normal;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+.glyphicon-asterisk:before {
+ content: "\002a";
+}
+.glyphicon-plus:before {
+ content: "\002b";
+}
+.glyphicon-euro:before,
+.glyphicon-eur:before {
+ content: "\20ac";
+}
+.glyphicon-minus:before {
+ content: "\2212";
+}
+.glyphicon-cloud:before {
+ content: "\2601";
+}
+.glyphicon-envelope:before {
+ content: "\2709";
+}
+.glyphicon-pencil:before {
+ content: "\270f";
+}
+.glyphicon-glass:before {
+ content: "\e001";
+}
+.glyphicon-music:before {
+ content: "\e002";
+}
+.glyphicon-search:before {
+ content: "\e003";
+}
+.glyphicon-heart:before {
+ content: "\e005";
+}
+.glyphicon-star:before {
+ content: "\e006";
+}
+.glyphicon-star-empty:before {
+ content: "\e007";
+}
+.glyphicon-user:before {
+ content: "\e008";
+}
+.glyphicon-film:before {
+ content: "\e009";
+}
+.glyphicon-th-large:before {
+ content: "\e010";
+}
+.glyphicon-th:before {
+ content: "\e011";
+}
+.glyphicon-th-list:before {
+ content: "\e012";
+}
+.glyphicon-ok:before {
+ content: "\e013";
+}
+.glyphicon-remove:before {
+ content: "\e014";
+}
+.glyphicon-zoom-in:before {
+ content: "\e015";
+}
+.glyphicon-zoom-out:before {
+ content: "\e016";
+}
+.glyphicon-off:before {
+ content: "\e017";
+}
+.glyphicon-signal:before {
+ content: "\e018";
+}
+.glyphicon-cog:before {
+ content: "\e019";
+}
+.glyphicon-trash:before {
+ content: "\e020";
+}
+.glyphicon-home:before {
+ content: "\e021";
+}
+.glyphicon-file:before {
+ content: "\e022";
+}
+.glyphicon-time:before {
+ content: "\e023";
+}
+.glyphicon-road:before {
+ content: "\e024";
+}
+.glyphicon-download-alt:before {
+ content: "\e025";
+}
+.glyphicon-download:before {
+ content: "\e026";
+}
+.glyphicon-upload:before {
+ content: "\e027";
+}
+.glyphicon-inbox:before {
+ content: "\e028";
+}
+.glyphicon-play-circle:before {
+ content: "\e029";
+}
+.glyphicon-repeat:before {
+ content: "\e030";
+}
+.glyphicon-refresh:before {
+ content: "\e031";
+}
+.glyphicon-list-alt:before {
+ content: "\e032";
+}
+.glyphicon-lock:before {
+ content: "\e033";
+}
+.glyphicon-flag:before {
+ content: "\e034";
+}
+.glyphicon-headphones:before {
+ content: "\e035";
+}
+.glyphicon-volume-off:before {
+ content: "\e036";
+}
+.glyphicon-volume-down:before {
+ content: "\e037";
+}
+.glyphicon-volume-up:before {
+ content: "\e038";
+}
+.glyphicon-qrcode:before {
+ content: "\e039";
+}
+.glyphicon-barcode:before {
+ content: "\e040";
+}
+.glyphicon-tag:before {
+ content: "\e041";
+}
+.glyphicon-tags:before {
+ content: "\e042";
+}
+.glyphicon-book:before {
+ content: "\e043";
+}
+.glyphicon-bookmark:before {
+ content: "\e044";
+}
+.glyphicon-print:before {
+ content: "\e045";
+}
+.glyphicon-camera:before {
+ content: "\e046";
+}
+.glyphicon-font:before {
+ content: "\e047";
+}
+.glyphicon-bold:before {
+ content: "\e048";
+}
+.glyphicon-italic:before {
+ content: "\e049";
+}
+.glyphicon-text-height:before {
+ content: "\e050";
+}
+.glyphicon-text-width:before {
+ content: "\e051";
+}
+.glyphicon-align-left:before {
+ content: "\e052";
+}
+.glyphicon-align-center:before {
+ content: "\e053";
+}
+.glyphicon-align-right:before {
+ content: "\e054";
+}
+.glyphicon-align-justify:before {
+ content: "\e055";
+}
+.glyphicon-list:before {
+ content: "\e056";
+}
+.glyphicon-indent-left:before {
+ content: "\e057";
+}
+.glyphicon-indent-right:before {
+ content: "\e058";
+}
+.glyphicon-facetime-video:before {
+ content: "\e059";
+}
+.glyphicon-picture:before {
+ content: "\e060";
+}
+.glyphicon-map-marker:before {
+ content: "\e062";
+}
+.glyphicon-adjust:before {
+ content: "\e063";
+}
+.glyphicon-tint:before {
+ content: "\e064";
+}
+.glyphicon-edit:before {
+ content: "\e065";
+}
+.glyphicon-share:before {
+ content: "\e066";
+}
+.glyphicon-check:before {
+ content: "\e067";
+}
+.glyphicon-move:before {
+ content: "\e068";
+}
+.glyphicon-step-backward:before {
+ content: "\e069";
+}
+.glyphicon-fast-backward:before {
+ content: "\e070";
+}
+.glyphicon-backward:before {
+ content: "\e071";
+}
+.glyphicon-play:before {
+ content: "\e072";
+}
+.glyphicon-pause:before {
+ content: "\e073";
+}
+.glyphicon-stop:before {
+ content: "\e074";
+}
+.glyphicon-forward:before {
+ content: "\e075";
+}
+.glyphicon-fast-forward:before {
+ content: "\e076";
+}
+.glyphicon-step-forward:before {
+ content: "\e077";
+}
+.glyphicon-eject:before {
+ content: "\e078";
+}
+.glyphicon-chevron-left:before {
+ content: "\e079";
+}
+.glyphicon-chevron-right:before {
+ content: "\e080";
+}
+.glyphicon-plus-sign:before {
+ content: "\e081";
+}
+.glyphicon-minus-sign:before {
+ content: "\e082";
+}
+.glyphicon-remove-sign:before {
+ content: "\e083";
+}
+.glyphicon-ok-sign:before {
+ content: "\e084";
+}
+.glyphicon-question-sign:before {
+ content: "\e085";
+}
+.glyphicon-info-sign:before {
+ content: "\e086";
+}
+.glyphicon-screenshot:before {
+ content: "\e087";
+}
+.glyphicon-remove-circle:before {
+ content: "\e088";
+}
+.glyphicon-ok-circle:before {
+ content: "\e089";
+}
+.glyphicon-ban-circle:before {
+ content: "\e090";
+}
+.glyphicon-arrow-left:before {
+ content: "\e091";
+}
+.glyphicon-arrow-right:before {
+ content: "\e092";
+}
+.glyphicon-arrow-up:before {
+ content: "\e093";
+}
+.glyphicon-arrow-down:before {
+ content: "\e094";
+}
+.glyphicon-share-alt:before {
+ content: "\e095";
+}
+.glyphicon-resize-full:before {
+ content: "\e096";
+}
+.glyphicon-resize-small:before {
+ content: "\e097";
+}
+.glyphicon-exclamation-sign:before {
+ content: "\e101";
+}
+.glyphicon-gift:before {
+ content: "\e102";
+}
+.glyphicon-leaf:before {
+ content: "\e103";
+}
+.glyphicon-fire:before {
+ content: "\e104";
+}
+.glyphicon-eye-open:before {
+ content: "\e105";
+}
+.glyphicon-eye-close:before {
+ content: "\e106";
+}
+.glyphicon-warning-sign:before {
+ content: "\e107";
+}
+.glyphicon-plane:before {
+ content: "\e108";
+}
+.glyphicon-calendar:before {
+ content: "\e109";
+}
+.glyphicon-random:before {
+ content: "\e110";
+}
+.glyphicon-comment:before {
+ content: "\e111";
+}
+.glyphicon-magnet:before {
+ content: "\e112";
+}
+.glyphicon-chevron-up:before {
+ content: "\e113";
+}
+.glyphicon-chevron-down:before {
+ content: "\e114";
+}
+.glyphicon-retweet:before {
+ content: "\e115";
+}
+.glyphicon-shopping-cart:before {
+ content: "\e116";
+}
+.glyphicon-folder-close:before {
+ content: "\e117";
+}
+.glyphicon-folder-open:before {
+ content: "\e118";
+}
+.glyphicon-resize-vertical:before {
+ content: "\e119";
+}
+.glyphicon-resize-horizontal:before {
+ content: "\e120";
+}
+.glyphicon-hdd:before {
+ content: "\e121";
+}
+.glyphicon-bullhorn:before {
+ content: "\e122";
+}
+.glyphicon-bell:before {
+ content: "\e123";
+}
+.glyphicon-certificate:before {
+ content: "\e124";
+}
+.glyphicon-thumbs-up:before {
+ content: "\e125";
+}
+.glyphicon-thumbs-down:before {
+ content: "\e126";
+}
+.glyphicon-hand-right:before {
+ content: "\e127";
+}
+.glyphicon-hand-left:before {
+ content: "\e128";
+}
+.glyphicon-hand-up:before {
+ content: "\e129";
+}
+.glyphicon-hand-down:before {
+ content: "\e130";
+}
+.glyphicon-circle-arrow-right:before {
+ content: "\e131";
+}
+.glyphicon-circle-arrow-left:before {
+ content: "\e132";
+}
+.glyphicon-circle-arrow-up:before {
+ content: "\e133";
+}
+.glyphicon-circle-arrow-down:before {
+ content: "\e134";
+}
+.glyphicon-globe:before {
+ content: "\e135";
+}
+.glyphicon-wrench:before {
+ content: "\e136";
+}
+.glyphicon-tasks:before {
+ content: "\e137";
+}
+.glyphicon-filter:before {
+ content: "\e138";
+}
+.glyphicon-briefcase:before {
+ content: "\e139";
+}
+.glyphicon-fullscreen:before {
+ content: "\e140";
+}
+.glyphicon-dashboard:before {
+ content: "\e141";
+}
+.glyphicon-paperclip:before {
+ content: "\e142";
+}
+.glyphicon-heart-empty:before {
+ content: "\e143";
+}
+.glyphicon-link:before {
+ content: "\e144";
+}
+.glyphicon-phone:before {
+ content: "\e145";
+}
+.glyphicon-pushpin:before {
+ content: "\e146";
+}
+.glyphicon-usd:before {
+ content: "\e148";
+}
+.glyphicon-gbp:before {
+ content: "\e149";
+}
+.glyphicon-sort:before {
+ content: "\e150";
+}
+.glyphicon-sort-by-alphabet:before {
+ content: "\e151";
+}
+.glyphicon-sort-by-alphabet-alt:before {
+ content: "\e152";
+}
+.glyphicon-sort-by-order:before {
+ content: "\e153";
+}
+.glyphicon-sort-by-order-alt:before {
+ content: "\e154";
+}
+.glyphicon-sort-by-attributes:before {
+ content: "\e155";
+}
+.glyphicon-sort-by-attributes-alt:before {
+ content: "\e156";
+}
+.glyphicon-unchecked:before {
+ content: "\e157";
+}
+.glyphicon-expand:before {
+ content: "\e158";
+}
+.glyphicon-collapse-down:before {
+ content: "\e159";
+}
+.glyphicon-collapse-up:before {
+ content: "\e160";
+}
+.glyphicon-log-in:before {
+ content: "\e161";
+}
+.glyphicon-flash:before {
+ content: "\e162";
+}
+.glyphicon-log-out:before {
+ content: "\e163";
+}
+.glyphicon-new-window:before {
+ content: "\e164";
+}
+.glyphicon-record:before {
+ content: "\e165";
+}
+.glyphicon-save:before {
+ content: "\e166";
+}
+.glyphicon-open:before {
+ content: "\e167";
+}
+.glyphicon-saved:before {
+ content: "\e168";
+}
+.glyphicon-import:before {
+ content: "\e169";
+}
+.glyphicon-export:before {
+ content: "\e170";
+}
+.glyphicon-send:before {
+ content: "\e171";
+}
+.glyphicon-floppy-disk:before {
+ content: "\e172";
+}
+.glyphicon-floppy-saved:before {
+ content: "\e173";
+}
+.glyphicon-floppy-remove:before {
+ content: "\e174";
+}
+.glyphicon-floppy-save:before {
+ content: "\e175";
+}
+.glyphicon-floppy-open:before {
+ content: "\e176";
+}
+.glyphicon-credit-card:before {
+ content: "\e177";
+}
+.glyphicon-transfer:before {
+ content: "\e178";
+}
+.glyphicon-cutlery:before {
+ content: "\e179";
+}
+.glyphicon-header:before {
+ content: "\e180";
+}
+.glyphicon-compressed:before {
+ content: "\e181";
+}
+.glyphicon-earphone:before {
+ content: "\e182";
+}
+.glyphicon-phone-alt:before {
+ content: "\e183";
+}
+.glyphicon-tower:before {
+ content: "\e184";
+}
+.glyphicon-stats:before {
+ content: "\e185";
+}
+.glyphicon-sd-video:before {
+ content: "\e186";
+}
+.glyphicon-hd-video:before {
+ content: "\e187";
+}
+.glyphicon-subtitles:before {
+ content: "\e188";
+}
+.glyphicon-sound-stereo:before {
+ content: "\e189";
+}
+.glyphicon-sound-dolby:before {
+ content: "\e190";
+}
+.glyphicon-sound-5-1:before {
+ content: "\e191";
+}
+.glyphicon-sound-6-1:before {
+ content: "\e192";
+}
+.glyphicon-sound-7-1:before {
+ content: "\e193";
+}
+.glyphicon-copyright-mark:before {
+ content: "\e194";
+}
+.glyphicon-registration-mark:before {
+ content: "\e195";
+}
+.glyphicon-cloud-download:before {
+ content: "\e197";
+}
+.glyphicon-cloud-upload:before {
+ content: "\e198";
+}
+.glyphicon-tree-conifer:before {
+ content: "\e199";
+}
+.glyphicon-tree-deciduous:before {
+ content: "\e200";
+}
+.glyphicon-cd:before {
+ content: "\e201";
+}
+.glyphicon-save-file:before {
+ content: "\e202";
+}
+.glyphicon-open-file:before {
+ content: "\e203";
+}
+.glyphicon-level-up:before {
+ content: "\e204";
+}
+.glyphicon-copy:before {
+ content: "\e205";
+}
+.glyphicon-paste:before {
+ content: "\e206";
+}
+.glyphicon-alert:before {
+ content: "\e209";
+}
+.glyphicon-equalizer:before {
+ content: "\e210";
+}
+.glyphicon-king:before {
+ content: "\e211";
+}
+.glyphicon-queen:before {
+ content: "\e212";
+}
+.glyphicon-pawn:before {
+ content: "\e213";
+}
+.glyphicon-bishop:before {
+ content: "\e214";
+}
+.glyphicon-knight:before {
+ content: "\e215";
+}
+.glyphicon-baby-formula:before {
+ content: "\e216";
+}
+.glyphicon-tent:before {
+ content: "\26fa";
+}
+.glyphicon-blackboard:before {
+ content: "\e218";
+}
+.glyphicon-bed:before {
+ content: "\e219";
+}
+.glyphicon-apple:before {
+ content: "\f8ff";
+}
+.glyphicon-erase:before {
+ content: "\e221";
+}
+.glyphicon-hourglass:before {
+ content: "\231b";
+}
+.glyphicon-lamp:before {
+ content: "\e223";
+}
+.glyphicon-duplicate:before {
+ content: "\e224";
+}
+.glyphicon-piggy-bank:before {
+ content: "\e225";
+}
+.glyphicon-scissors:before {
+ content: "\e226";
+}
+.glyphicon-bitcoin:before {
+ content: "\e227";
+}
+.glyphicon-btc:before {
+ content: "\e227";
+}
+.glyphicon-xbt:before {
+ content: "\e227";
+}
+.glyphicon-yen:before {
+ content: "\00a5";
+}
+.glyphicon-jpy:before {
+ content: "\00a5";
+}
+.glyphicon-ruble:before {
+ content: "\20bd";
+}
+.glyphicon-rub:before {
+ content: "\20bd";
+}
+.glyphicon-scale:before {
+ content: "\e230";
+}
+.glyphicon-ice-lolly:before {
+ content: "\e231";
+}
+.glyphicon-ice-lolly-tasted:before {
+ content: "\e232";
+}
+.glyphicon-education:before {
+ content: "\e233";
+}
+.glyphicon-option-horizontal:before {
+ content: "\e234";
+}
+.glyphicon-option-vertical:before {
+ content: "\e235";
+}
+.glyphicon-menu-hamburger:before {
+ content: "\e236";
+}
+.glyphicon-modal-window:before {
+ content: "\e237";
+}
+.glyphicon-oil:before {
+ content: "\e238";
+}
+.glyphicon-grain:before {
+ content: "\e239";
+}
+.glyphicon-sunglasses:before {
+ content: "\e240";
+}
+.glyphicon-text-size:before {
+ content: "\e241";
+}
+.glyphicon-text-color:before {
+ content: "\e242";
+}
+.glyphicon-text-background:before {
+ content: "\e243";
+}
+.glyphicon-object-align-top:before {
+ content: "\e244";
+}
+.glyphicon-object-align-bottom:before {
+ content: "\e245";
+}
+.glyphicon-object-align-horizontal:before {
+ content: "\e246";
+}
+.glyphicon-object-align-left:before {
+ content: "\e247";
+}
+.glyphicon-object-align-vertical:before {
+ content: "\e248";
+}
+.glyphicon-object-align-right:before {
+ content: "\e249";
+}
+.glyphicon-triangle-right:before {
+ content: "\e250";
+}
+.glyphicon-triangle-left:before {
+ content: "\e251";
+}
+.glyphicon-triangle-bottom:before {
+ content: "\e252";
+}
+.glyphicon-triangle-top:before {
+ content: "\e253";
+}
+.glyphicon-console:before {
+ content: "\e254";
+}
+.glyphicon-superscript:before {
+ content: "\e255";
+}
+.glyphicon-subscript:before {
+ content: "\e256";
+}
+.glyphicon-menu-left:before {
+ content: "\e257";
+}
+.glyphicon-menu-right:before {
+ content: "\e258";
+}
+.glyphicon-menu-down:before {
+ content: "\e259";
+}
+.glyphicon-menu-up:before {
+ content: "\e260";
+}
+
+.nav {
+ margin-bottom: 0;
+ padding-left: 0;
+ list-style: none;
+}
+.nav > li {
+ position: relative;
+ display: block;
+}
+.nav > li > a {
+ position: relative;
+ display: block;
+ padding: 10px 15px;
+}
+.nav > li > a:hover,
+.nav > li > a:focus {
+ text-decoration: none;
+ background-color: #eeeeee;
+}
+.nav > li.disabled > a {
+ color: #777777;
+}
+.nav > li.disabled > a:hover,
+.nav > li.disabled > a:focus {
+ color: #777777;
+ text-decoration: none;
+ background-color: transparent;
+ cursor: not-allowed;
+}
+.nav .open > a,
+.nav .open > a:hover,
+.nav .open > a:focus {
+ background-color: #eeeeee;
+ border-color: #337ab7;
+}
+.nav .nav-divider {
+ height: 1px;
+ margin: 9px 0;
+ overflow: hidden;
+ background-color: #e5e5e5;
+}
+.nav > li > a > img {
+ max-width: none;
+}
+.nav-tabs {
+ border-bottom: 1px solid #dddddd;
+}
+.nav-tabs > li {
+ float: left;
+ margin-bottom: -1px;
+}
+.nav-tabs > li > a {
+ margin-right: 2px;
+ line-height: 1.42857143;
+ border: 1px solid transparent;
+ border-radius: 4px 4px 0 0;
+}
+.nav-tabs > li > a:hover {
+ border-color: #eeeeee #eeeeee #dddddd;
+}
+.nav-tabs > li.active > a,
+.nav-tabs > li.active > a:hover,
+.nav-tabs > li.active > a:focus {
+ color: #555555;
+ background-color: #ffffff;
+ border: 1px solid #dddddd;
+ border-bottom-color: transparent;
+ cursor: default;
+}
+.nav-tabs.nav-justified {
+ width: 100%;
+ border-bottom: 0;
+}
+.nav-tabs.nav-justified > li {
+ float: none;
+}
+.nav-tabs.nav-justified > li > a {
+ text-align: center;
+ margin-bottom: 5px;
+}
+.nav-tabs.nav-justified > .dropdown .dropdown-menu {
+ top: auto;
+ left: auto;
+}
+@media (min-width: 768px) {
+ .nav-tabs.nav-justified > li {
+ display: table-cell;
+ width: 1%;
+ }
+ .nav-tabs.nav-justified > li > a {
+ margin-bottom: 0;
+ }
+}
+.nav-tabs.nav-justified > li > a {
+ margin-right: 0;
+ border-radius: 4px;
+}
+.nav-tabs.nav-justified > .active > a,
+.nav-tabs.nav-justified > .active > a:hover,
+.nav-tabs.nav-justified > .active > a:focus {
+ border: 1px solid #dddddd;
+}
+@media (min-width: 768px) {
+ .nav-tabs.nav-justified > li > a {
+ border-bottom: 1px solid #dddddd;
+ border-radius: 4px 4px 0 0;
+ }
+ .nav-tabs.nav-justified > .active > a,
+ .nav-tabs.nav-justified > .active > a:hover,
+ .nav-tabs.nav-justified > .active > a:focus {
+ border-bottom-color: #ffffff;
+ }
+}
+.nav-pills > li {
+ float: left;
+}
+.nav-pills > li > a {
+ border-radius: 4px;
+}
+.nav-pills > li + li {
+ margin-left: 2px;
+}
+.nav-pills > li.active > a,
+.nav-pills > li.active > a:hover,
+.nav-pills > li.active > a:focus {
+ color: #ffffff;
+ background-color: #337ab7;
+}
+.nav-stacked > li {
+ float: none;
+}
+.nav-stacked > li + li {
+ margin-top: 2px;
+ margin-left: 0;
+}
+.nav-justified {
+ width: 100%;
+}
+.nav-justified > li {
+ float: none;
+}
+.nav-justified > li > a {
+ text-align: center;
+ margin-bottom: 5px;
+}
+.nav-justified > .dropdown .dropdown-menu {
+ top: auto;
+ left: auto;
+}
+@media (min-width: 768px) {
+ .nav-justified > li {
+ display: table-cell;
+ width: 1%;
+ }
+ .nav-justified > li > a {
+ margin-bottom: 0;
+ }
+}
+.nav-tabs-justified {
+ border-bottom: 0;
+}
+.nav-tabs-justified > li > a {
+ margin-right: 0;
+ border-radius: 4px;
+}
+.nav-tabs-justified > .active > a,
+.nav-tabs-justified > .active > a:hover,
+.nav-tabs-justified > .active > a:focus {
+ border: 1px solid #dddddd;
+}
+@media (min-width: 768px) {
+ .nav-tabs-justified > li > a {
+ border-bottom: 1px solid #dddddd;
+ border-radius: 4px 4px 0 0;
+ }
+ .nav-tabs-justified > .active > a,
+ .nav-tabs-justified > .active > a:hover,
+ .nav-tabs-justified > .active > a:focus {
+ border-bottom-color: #ffffff;
+ }
+}
+/*
+* List Group
+*/
+.list-group {
+ margin-bottom: 20px;
+ padding-left: 0;
+}
+.list-group-item {
+ position: relative;
+ display: block;
+ padding: 10px 15px;
+ margin-bottom: -1px;
+ background-color: #ffffff;
+ border: 1px solid #dddddd;
+}
+.list-group-item:first-child {
+ border-top-right-radius: 4px;
+ border-top-left-radius: 4px;
+}
+.list-group-item:last-child {
+ margin-bottom: 0;
+ border-bottom-right-radius: 4px;
+ border-bottom-left-radius: 4px;
+}
+a.list-group-item,
+button.list-group-item {
+ color: #555555;
+}
+a.list-group-item .list-group-item-heading,
+button.list-group-item .list-group-item-heading {
+ color: #333333;
+}
+a.list-group-item:hover,
+button.list-group-item:hover,
+a.list-group-item:focus,
+button.list-group-item:focus {
+ text-decoration: none;
+ color: #555555;
+ background-color: #f5f5f5;
+}
+button.list-group-item {
+ width: 100%;
+ text-align: left;
+}
+.list-group-item.disabled,
+.list-group-item.disabled:hover,
+.list-group-item.disabled:focus {
+ background-color: #eeeeee;
+ color: #777777;
+ cursor: not-allowed;
+}
+.list-group-item.disabled .list-group-item-heading,
+.list-group-item.disabled:hover .list-group-item-heading,
+.list-group-item.disabled:focus .list-group-item-heading {
+ color: inherit;
+}
+.list-group-item.disabled .list-group-item-text,
+.list-group-item.disabled:hover .list-group-item-text,
+.list-group-item.disabled:focus .list-group-item-text {
+ color: #777777;
+}
+.list-group-item.active,
+.list-group-item.active:hover,
+.list-group-item.active:focus {
+ z-index: 2;
+ color: #ffffff;
+ background-color: #337ab7;
+ border-color: #337ab7;
+}
+.list-group-item.active .list-group-item-heading,
+.list-group-item.active:hover .list-group-item-heading,
+.list-group-item.active:focus .list-group-item-heading,
+.list-group-item.active .list-group-item-heading > small,
+.list-group-item.active:hover .list-group-item-heading > small,
+.list-group-item.active:focus .list-group-item-heading > small,
+.list-group-item.active .list-group-item-heading > .small,
+.list-group-item.active:hover .list-group-item-heading > .small,
+.list-group-item.active:focus .list-group-item-heading > .small {
+ color: inherit;
+}
+.list-group-item.active .list-group-item-text,
+.list-group-item.active:hover .list-group-item-text,
+.list-group-item.active:focus .list-group-item-text {
+ color: #c7ddef;
+}
+.list-group-item-success {
+ color: #3c763d;
+ background-color: #dff0d8;
+}
+a.list-group-item-success,
+button.list-group-item-success {
+ color: #3c763d;
+}
+a.list-group-item-success .list-group-item-heading,
+button.list-group-item-success .list-group-item-heading {
+ color: inherit;
+}
+a.list-group-item-success:hover,
+button.list-group-item-success:hover,
+a.list-group-item-success:focus,
+button.list-group-item-success:focus {
+ color: #3c763d;
+ background-color: #d0e9c6;
+}
+a.list-group-item-success.active,
+button.list-group-item-success.active,
+a.list-group-item-success.active:hover,
+button.list-group-item-success.active:hover,
+a.list-group-item-success.active:focus,
+button.list-group-item-success.active:focus {
+ color: #fff;
+ background-color: #3c763d;
+ border-color: #3c763d;
+}
+.list-group-item-info {
+ color: #31708f;
+ background-color: #d9edf7;
+}
+a.list-group-item-info,
+button.list-group-item-info {
+ color: #31708f;
+}
+a.list-group-item-info .list-group-item-heading,
+button.list-group-item-info .list-group-item-heading {
+ color: inherit;
+}
+a.list-group-item-info:hover,
+button.list-group-item-info:hover,
+a.list-group-item-info:focus,
+button.list-group-item-info:focus {
+ color: #31708f;
+ background-color: #c4e3f3;
+}
+a.list-group-item-info.active,
+button.list-group-item-info.active,
+a.list-group-item-info.active:hover,
+button.list-group-item-info.active:hover,
+a.list-group-item-info.active:focus,
+button.list-group-item-info.active:focus {
+ color: #fff;
+ background-color: #31708f;
+ border-color: #31708f;
+}
+.list-group-item-warning {
+ color: #8a6d3b;
+ background-color: #fcf8e3;
+}
+a.list-group-item-warning,
+button.list-group-item-warning {
+ color: #8a6d3b;
+}
+a.list-group-item-warning .list-group-item-heading,
+button.list-group-item-warning .list-group-item-heading {
+ color: inherit;
+}
+a.list-group-item-warning:hover,
+button.list-group-item-warning:hover,
+a.list-group-item-warning:focus,
+button.list-group-item-warning:focus {
+ color: #8a6d3b;
+ background-color: #faf2cc;
+}
+a.list-group-item-warning.active,
+button.list-group-item-warning.active,
+a.list-group-item-warning.active:hover,
+button.list-group-item-warning.active:hover,
+a.list-group-item-warning.active:focus,
+button.list-group-item-warning.active:focus {
+ color: #fff;
+ background-color: #8a6d3b;
+ border-color: #8a6d3b;
+}
+.list-group-item-danger {
+ color: #a94442;
+ background-color: #f2dede;
+}
+a.list-group-item-danger,
+button.list-group-item-danger {
+ color: #a94442;
+}
+a.list-group-item-danger .list-group-item-heading,
+button.list-group-item-danger .list-group-item-heading {
+ color: inherit;
+}
+a.list-group-item-danger:hover,
+button.list-group-item-danger:hover,
+a.list-group-item-danger:focus,
+button.list-group-item-danger:focus {
+ color: #a94442;
+ background-color: #ebcccc;
+}
+a.list-group-item-danger.active,
+button.list-group-item-danger.active,
+a.list-group-item-danger.active:hover,
+button.list-group-item-danger.active:hover,
+a.list-group-item-danger.active:focus,
+button.list-group-item-danger.active:focus {
+ color: #fff;
+ background-color: #a94442;
+ border-color: #a94442;
+}
+.list-group-item-heading {
+ margin-top: 0;
+ margin-bottom: 5px;
+}
+.list-group-item-text {
+ margin-bottom: 0;
+ line-height: 1.3;
+}
+
+.well {
+ min-height: 20px;
+ padding: 19px;
+ margin-bottom: 20px;
+ background-color: #f5f5f5;
+ border: 1px solid #e3e3e3;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+}
+.well blockquote {
+ border-color: #ddd;
+ border-color: rgba(0, 0, 0, 0.15);
+}
+.well-lg {
+ padding: 24px;
+ border-radius: 6px;
+}
+.well-sm {
+ padding: 9px;
+ border-radius: 3px;
+}
diff --git a/extensions/database/module/styles/database-import.less b/extensions/database/module/styles/database-import.less
new file mode 100644
index 000000000..04cae2dae
--- /dev/null
+++ b/extensions/database/module/styles/database-import.less
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+@import-less url("theme.less");
+
+.database-container {
+// margin-left: 200px;
+ // width: 50%;
+// min-height: 50%;
+}
+
+.cleared {
+ clear: both;
+}
+
+.custom-restricted-width {
+ /* To limit the menu width to the content of the menu: */
+ display: inline-block;
+ /* Or set the width explicitly: */
+ /* width: 10em; */
+}
+
+
+.scrollable {
+ height: 400px;
+ overflow: auto;
+}
+
+.database-importing-wizard-header {
+ font-size: 1.3em;
+ background: @chrome_primary;
+ padding: @padding_tight;
+ }
+
+.database-importing-parsing-data-panel {
+ font-size: 1.1em;
+ position: absolute;
+ overflow: auto;
+ }
+
+.database-importing-progress-data-panel {
+ position: absolute;
+ overflow: auto;
+ font-size: 200%;
+ padding: 3em;
+ background: rgba(255, 255, 255, 0.7);
+ text-align: center;
+ }
+
+.database-importing-parsing-control-panel {
+ font-size: 1.3em;
+ position: absolute;
+ overflow: auto;
+ border-top: 5px solid @chrome_primary;
+ background: white;
+ padding: @padding_looser;
+ }
+
+.context-menu-text {
+ padding: 4px 4px 2px 2px;
+}
+
+
+.custom-restricted {
+ height: 300px;
+ width: 200px;
+ border: 1px solid #bce8f1;
+ border-radius: 4px;
+ margin-right:10px;
+ padding-right:10px;
+ margin-bottom:10px;
+}
+.custom-restricted-width {
+ /* To limit the menu width to the content of the menu: */
+ display: inline-block;
+ /* Or set the width explicitly: */
+ /* width: 10em; */
+ margin-right:10px;
+ padding-right:10px;
+ margin-bottom:10px;
+}
+.no-resize {
+ resize: none;
+}
+.layout-div {
+ width : 100%;
+
+}
+.layout-div .connection-div-layout {
+ min-width: 400px;
+}
+.new-connection-legend {
+ padding-left: 2px;
+ margin-left: 2px;
+ border-color: #bce8f1;
+}
+
+.sql-editor-div {
+ margin-left:40px;
+ padding-left:20px;
+}
+.new-connection-div {
+ margin-left:40px;
+ padding-left:20px;
+}
+.new-connection-fieldset {
+ border: 1px solid gray;
+}
+.sc-list {
+ border-bottom: 1px dotted #bce8f1;
+}
+
+.sc-context-more-vert:after {
+ content: "";
+ display: inline-block;
+ background: url("../images/more-option-horiz-16.png") no-repeat top right;
+ // line-height: 1;
+ width: 16px;
+ height: 16px;
+ -webkit-font-smoothing: antialiased;
+
+}
diff --git a/extensions/database/module/styles/jquery.contextMenu.css b/extensions/database/module/styles/jquery.contextMenu.css
new file mode 100644
index 000000000..0253e6e1b
--- /dev/null
+++ b/extensions/database/module/styles/jquery.contextMenu.css
@@ -0,0 +1,292 @@
+@charset "UTF-8";
+/*!
+ * jQuery contextMenu - Plugin for simple contextMenu handling
+ *
+ * Version: v2.6.3
+ *
+ * Authors: Björn Brala (SWIS.nl), Rodney Rehm, Addy Osmani (patches for FF)
+ * Web: http://swisnl.github.io/jQuery-contextMenu/
+ *
+ * Copyright (c) 2011-2017 SWIS BV and contributors
+ *
+ * Licensed under
+ * MIT License http://www.opensource.org/licenses/mit-license
+ *
+ * Date: 2017-10-30T19:03:13.936Z
+ */
+@-webkit-keyframes cm-spin {
+ 0% {
+ -webkit-transform: translateY(-50%) rotate(0deg);
+ transform: translateY(-50%) rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: translateY(-50%) rotate(359deg);
+ transform: translateY(-50%) rotate(359deg);
+ }
+}
+@-o-keyframes cm-spin {
+ 0% {
+ -webkit-transform: translateY(-50%) rotate(0deg);
+ -o-transform: translateY(-50%) rotate(0deg);
+ transform: translateY(-50%) rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: translateY(-50%) rotate(359deg);
+ -o-transform: translateY(-50%) rotate(359deg);
+ transform: translateY(-50%) rotate(359deg);
+ }
+}
+@keyframes cm-spin {
+ 0% {
+ -webkit-transform: translateY(-50%) rotate(0deg);
+ -o-transform: translateY(-50%) rotate(0deg);
+ transform: translateY(-50%) rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: translateY(-50%) rotate(359deg);
+ -o-transform: translateY(-50%) rotate(359deg);
+ transform: translateY(-50%) rotate(359deg);
+ }
+}
+
+@font-face {
+ font-family: "context-menu-icons";
+ font-style: normal;
+ font-weight: normal;
+
+ src: url("font/context-menu-icons.eot?2wp27");
+ src: url("font/context-menu-icons.eot?2wp27#iefix") format("embedded-opentype"), url("font/context-menu-icons.woff2?2wp27") format("woff2"), url("font/context-menu-icons.woff?2wp27") format("woff"), url("font/context-menu-icons.ttf?2wp27") format("truetype");
+}
+
+.context-menu-icon-add:before {
+ content: "\EA01";
+}
+
+.context-menu-icon-copy:before {
+ content: "\EA02";
+}
+
+.context-menu-icon-cut:before {
+ content: "\EA03";
+}
+
+.context-menu-icon-delete:before {
+ content: "\EA04";
+}
+
+.context-menu-icon-edit:before {
+ content: "\EA05";
+}
+
+.context-menu-icon-loading:before {
+ content: "\EA06";
+}
+
+.context-menu-icon-paste:before {
+ content: "\EA07";
+}
+
+.context-menu-icon-quit:before {
+ content: "\EA08";
+}
+
+.context-menu-icon::before {
+ position: absolute;
+ top: 50%;
+ left: 0;
+ width: 2em;
+ font-family: "context-menu-icons";
+ font-size: 1em;
+ font-style: normal;
+ font-weight: normal;
+ line-height: 1;
+ color: #2980b9;
+ text-align: center;
+ -webkit-transform: translateY(-50%);
+ -ms-transform: translateY(-50%);
+ -o-transform: translateY(-50%);
+ transform: translateY(-50%);
+
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+.context-menu-icon.context-menu-hover:before {
+ color: #fff;
+}
+
+.context-menu-icon.context-menu-disabled::before {
+ color: #bbb;
+}
+
+.context-menu-icon.context-menu-icon-loading:before {
+ -webkit-animation: cm-spin 2s infinite;
+ -o-animation: cm-spin 2s infinite;
+ animation: cm-spin 2s infinite;
+}
+
+.context-menu-icon.context-menu-icon--fa {
+ display: list-item;
+ font-family: inherit;
+}
+.context-menu-icon.context-menu-icon--fa::before {
+ position: absolute;
+ top: 50%;
+ left: 0;
+ width: 2em;
+ font-family: FontAwesome;
+ font-size: 1em;
+ font-style: normal;
+ font-weight: normal;
+ line-height: 1;
+ color: #2980b9;
+ text-align: center;
+ -webkit-transform: translateY(-50%);
+ -ms-transform: translateY(-50%);
+ -o-transform: translateY(-50%);
+ transform: translateY(-50%);
+
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+.context-menu-icon.context-menu-icon--fa.context-menu-hover:before {
+ color: #fff;
+}
+.context-menu-icon.context-menu-icon--fa.context-menu-disabled::before {
+ color: #bbb;
+}
+
+.context-menu-list {
+ position: absolute;
+ display: inline-block;
+ min-width: 13em;
+ max-width: 26em;
+ padding: .25em 0;
+ margin: .3em;
+ font-family: inherit;
+ font-size: inherit;
+ list-style-type: none;
+ background: #fff;
+ border: 1px solid #bebebe;
+ border-radius: .2em;
+ -webkit-box-shadow: 0 2px 5px rgba(0, 0, 0, .5);
+ box-shadow: 0 2px 5px rgba(0, 0, 0, .5);
+}
+
+.context-menu-item {
+ position: relative;
+ padding: .2em 2em;
+ color: #2f2f2f;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ background-color: #fff;
+ font: 15px "Helvetica Neue", Helvetica, Arial, sans-serif;
+}
+
+.context-menu-separator {
+ padding: 0;
+ margin: .35em 0;
+ border-bottom: 1px solid #e6e6e6;
+}
+
+.context-menu-item > label > input,
+.context-menu-item > label > textarea {
+ -webkit-user-select: text;
+ -moz-user-select: text;
+ -ms-user-select: text;
+ user-select: text;
+}
+
+.context-menu-item.context-menu-hover {
+ color: #fff;
+ cursor: pointer;
+ background-color: #2980b9;
+}
+
+.context-menu-item.context-menu-disabled {
+ color: #bbb;
+ cursor: default;
+ background-color: #fff;
+}
+
+.context-menu-input.context-menu-hover {
+ color: #2f2f2f;
+ cursor: default;
+}
+
+.context-menu-submenu:after {
+ position: absolute;
+ top: 50%;
+ right: .5em;
+ z-index: 1;
+ width: 0;
+ height: 0;
+ content: '';
+ border-color: transparent transparent transparent #2f2f2f;
+ border-style: solid;
+ border-width: .25em 0 .25em .25em;
+ -webkit-transform: translateY(-50%);
+ -ms-transform: translateY(-50%);
+ -o-transform: translateY(-50%);
+ transform: translateY(-50%);
+}
+
+/**
+ * Inputs
+ */
+.context-menu-item.context-menu-input {
+ padding: .3em .6em;
+}
+
+/* vertically align inside labels */
+.context-menu-input > label > * {
+ vertical-align: top;
+}
+
+/* position checkboxes and radios as icons */
+.context-menu-input > label > input[type="checkbox"],
+.context-menu-input > label > input[type="radio"] {
+ position: relative;
+ top: .12em;
+ margin-right: .4em;
+}
+
+.context-menu-input > label {
+ margin: 0;
+}
+
+.context-menu-input > label,
+.context-menu-input > label > input[type="text"],
+.context-menu-input > label > textarea,
+.context-menu-input > label > select {
+ display: block;
+ width: 100%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+.context-menu-input > label > textarea {
+ height: 7em;
+}
+
+.context-menu-item > .context-menu-list {
+ top: .3em;
+ /* re-positioned by js */
+ right: -.3em;
+ display: none;
+}
+
+.context-menu-item.context-menu-visible > .context-menu-list {
+ display: block;
+}
+
+.context-menu-accesskey {
+ text-decoration: underline;
+}
+/*Custom to display icons*/
+
+
+
diff --git a/extensions/database/module/styles/pure.css b/extensions/database/module/styles/pure.css
new file mode 100644
index 000000000..6b9d53736
--- /dev/null
+++ b/extensions/database/module/styles/pure.css
@@ -0,0 +1,1549 @@
+/*!
+Pure v1.0.0
+Copyright 2013 Yahoo!
+Licensed under the BSD License.
+https://github.com/yahoo/pure/blob/master/LICENSE.md
+*/
+/*!
+normalize.css v^3.0 | MIT License | git.io/normalize
+Copyright (c) Nicolas Gallagher and Jonathan Neal
+*/
+/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
+
+/**
+ * 1. Set default font family to sans-serif.
+ * 2. Prevent iOS and IE text size adjust after device orientation change,
+ * without disabling user zoom.
+ */
+
+html {
+ font-family: sans-serif; /* 1 */
+ -ms-text-size-adjust: 100%; /* 2 */
+ -webkit-text-size-adjust: 100%; /* 2 */
+}
+
+/**
+ * Remove default margin.
+ */
+
+body {
+ margin: 0;
+}
+
+/* HTML5 display definitions
+ ========================================================================== */
+
+/**
+ * Correct `block` display not defined for any HTML5 element in IE 8/9.
+ * Correct `block` display not defined for `details` or `summary` in IE 10/11
+ * and Firefox.
+ * Correct `block` display not defined for `main` in IE 11.
+ */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+menu,
+nav,
+section,
+summary {
+ display: block;
+}
+
+/**
+ * 1. Correct `inline-block` display not defined in IE 8/9.
+ * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
+ */
+
+audio,
+canvas,
+progress,
+video {
+ display: inline-block; /* 1 */
+ vertical-align: baseline; /* 2 */
+}
+
+/**
+ * Prevent modern browsers from displaying `audio` without controls.
+ * Remove excess height in iOS 5 devices.
+ */
+
+audio:not([controls]) {
+ display: none;
+ height: 0;
+}
+
+/**
+ * Address `[hidden]` styling not present in IE 8/9/10.
+ * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.
+ */
+
+[hidden],
+template {
+ display: none;
+}
+
+/* Links
+ ========================================================================== */
+
+/**
+ * Remove the gray background color from active links in IE 10.
+ */
+
+a {
+ background-color: transparent;
+}
+
+/**
+ * Improve readability of focused elements when they are also in an
+ * active/hover state.
+ */
+
+a:active,
+a:hover {
+ outline: 0;
+}
+
+/* Text-level semantics
+ ========================================================================== */
+
+/**
+ * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
+ */
+
+abbr[title] {
+ border-bottom: 1px dotted;
+}
+
+/**
+ * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
+ */
+
+b,
+strong {
+ font-weight: bold;
+}
+
+/**
+ * Address styling not present in Safari and Chrome.
+ */
+
+dfn {
+ font-style: italic;
+}
+
+/**
+ * Address variable `h1` font-size and margin within `section` and `article`
+ * contexts in Firefox 4+, Safari, and Chrome.
+ */
+
+h1 {
+ font-size: 2em;
+ margin: 0.67em 0;
+}
+
+/**
+ * Address styling not present in IE 8/9.
+ */
+
+mark {
+ background: #ff0;
+ color: #000;
+}
+
+/**
+ * Address inconsistent and variable font size in all browsers.
+ */
+
+small {
+ font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` affecting `line-height` in all browsers.
+ */
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sup {
+ top: -0.5em;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+/* Embedded content
+ ========================================================================== */
+
+/**
+ * Remove border when inside `a` element in IE 8/9/10.
+ */
+
+img {
+ border: 0;
+}
+
+/**
+ * Correct overflow not hidden in IE 9/10/11.
+ */
+
+svg:not(:root) {
+ overflow: hidden;
+}
+
+/* Grouping content
+ ========================================================================== */
+
+/**
+ * Address margin not present in IE 8/9 and Safari.
+ */
+
+figure {
+ margin: 1em 40px;
+}
+
+/**
+ * Address differences between Firefox and other browsers.
+ */
+
+hr {
+ box-sizing: content-box;
+ height: 0;
+}
+
+/**
+ * Contain overflow in all browsers.
+ */
+
+pre {
+ overflow: auto;
+}
+
+/**
+ * Address odd `em`-unit font size rendering in all browsers.
+ */
+
+code,
+kbd,
+pre,
+samp {
+ font-family: monospace, monospace;
+ font-size: 1em;
+}
+
+/* Forms
+ ========================================================================== */
+
+/**
+ * Known limitation: by default, Chrome and Safari on OS X allow very limited
+ * styling of `select`, unless a `border` property is set.
+ */
+
+/**
+ * 1. Correct color not being inherited.
+ * Known issue: affects color of disabled elements.
+ * 2. Correct font properties not being inherited.
+ * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
+ */
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ color: inherit; /* 1 */
+ font: inherit; /* 2 */
+ margin: 0; /* 3 */
+}
+
+/**
+ * Address `overflow` set to `hidden` in IE 8/9/10/11.
+ */
+
+button {
+ overflow: visible;
+}
+
+/**
+ * Address inconsistent `text-transform` inheritance for `button` and `select`.
+ * All other form control elements do not inherit `text-transform` values.
+ * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
+ * Correct `select` style inheritance in Firefox.
+ */
+
+button,
+select {
+ text-transform: none;
+}
+
+/**
+ * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
+ * and `video` controls.
+ * 2. Correct inability to style clickable `input` types in iOS.
+ * 3. Improve usability and consistency of cursor style between image-type
+ * `input` and others.
+ */
+
+button,
+html input[type="button"], /* 1 */
+input[type="reset"],
+input[type="submit"] {
+ -webkit-appearance: button; /* 2 */
+ cursor: pointer; /* 3 */
+}
+
+/**
+ * Re-set default cursor for disabled elements.
+ */
+
+button[disabled],
+html input[disabled] {
+ cursor: default;
+}
+
+/**
+ * Remove inner padding and border in Firefox 4+.
+ */
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ border: 0;
+ padding: 0;
+}
+
+/**
+ * Address Firefox 4+ setting `line-height` on `input` using `!important` in
+ * the UA stylesheet.
+ */
+
+input {
+ line-height: normal;
+}
+
+/**
+ * It's recommended that you don't attempt to style these elements.
+ * Firefox's implementation doesn't respect box-sizing, padding, or width.
+ *
+ * 1. Address box sizing set to `content-box` in IE 8/9/10.
+ * 2. Remove excess padding in IE 8/9/10.
+ */
+
+input[type="checkbox"],
+input[type="radio"] {
+ box-sizing: border-box; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * Fix the cursor style for Chrome's increment/decrement buttons. For certain
+ * `font-size` values of the `input`, it causes the cursor style of the
+ * decrement button to change from `default` to `text`.
+ */
+
+input[type="number"]::-webkit-inner-spin-button,
+input[type="number"]::-webkit-outer-spin-button {
+ height: auto;
+}
+
+/**
+ * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
+ * 2. Address `box-sizing` set to `border-box` in Safari and Chrome.
+ */
+
+input[type="search"] {
+ -webkit-appearance: textfield; /* 1 */
+ box-sizing: content-box; /* 2 */
+}
+
+/**
+ * Remove inner padding and search cancel button in Safari and Chrome on OS X.
+ * Safari (but not Chrome) clips the cancel button when the search input has
+ * padding (and `textfield` appearance).
+ */
+
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/**
+ * Define consistent border, margin, and padding.
+ */
+
+fieldset {
+ border: 1px solid #c0c0c0;
+ margin: 0 2px;
+ padding: 0.35em 0.625em 0.75em;
+}
+
+/**
+ * 1. Correct `color` not being inherited in IE 8/9/10/11.
+ * 2. Remove padding so people aren't caught out if they zero out fieldsets.
+ */
+
+legend {
+ border: 0; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * Remove default vertical scrollbar in IE 8/9/10/11.
+ */
+
+textarea {
+ overflow: auto;
+}
+
+/**
+ * Don't inherit the `font-weight` (applied by a rule above).
+ * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
+ */
+
+optgroup {
+ font-weight: bold;
+}
+
+/* Tables
+ ========================================================================== */
+
+/**
+ * Remove most spacing between table cells.
+ */
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+td,
+th {
+ padding: 0;
+}
+
+/*csslint important:false*/
+
+/* ==========================================================================
+ Pure Base Extras
+ ========================================================================== */
+
+/**
+ * Extra rules that Pure adds on top of Normalize.css
+ */
+
+/**
+ * Always hide an element when it has the `hidden` HTML attribute.
+ */
+
+.hidden,
+[hidden] {
+ display: none !important;
+}
+
+/**
+ * Add this class to an image to make it fit within it's fluid parent wrapper while maintaining
+ * aspect ratio.
+ */
+.pure-img {
+ max-width: 100%;
+ height: auto;
+ display: block;
+}
+
+/*csslint regex-selectors:false, known-properties:false, duplicate-properties:false*/
+
+.pure-g {
+ letter-spacing: -0.31em; /* Webkit: collapse white-space between units */
+ *letter-spacing: normal; /* reset IE < 8 */
+ *word-spacing: -0.43em; /* IE < 8: collapse white-space between units */
+ text-rendering: optimizespeed; /* Webkit: fixes text-rendering: optimizeLegibility */
+
+ /*
+ Sets the font stack to fonts known to work properly with the above letter
+ and word spacings. See: https://github.com/yahoo/pure/issues/41/
+
+ The following font stack makes Pure Grids work on all known environments.
+
+ * FreeSans: Ships with many Linux distros, including Ubuntu
+
+ * Arimo: Ships with Chrome OS. Arimo has to be defined before Helvetica and
+ Arial to get picked up by the browser, even though neither is available
+ in Chrome OS.
+
+ * Droid Sans: Ships with all versions of Android.
+
+ * Helvetica, Arial, sans-serif: Common font stack on OS X and Windows.
+ */
+ font-family: FreeSans, Arimo, "Droid Sans", Helvetica, Arial, sans-serif;
+
+ /* Use flexbox when possible to avoid `letter-spacing` side-effects. */
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-flow: row wrap;
+ -ms-flex-flow: row wrap;
+ flex-flow: row wrap;
+
+ /* Prevents distributing space between rows */
+ -webkit-align-content: flex-start;
+ -ms-flex-line-pack: start;
+ align-content: flex-start;
+}
+
+/* IE10 display: -ms-flexbox (and display: flex in IE 11) does not work inside a table; fall back to block and rely on font hack */
+@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
+ table .pure-g {
+ display: block;
+ }
+}
+
+/* Opera as of 12 on Windows needs word-spacing.
+ The ".opera-only" selector is used to prevent actual prefocus styling
+ and is not required in markup.
+*/
+.opera-only :-o-prefocus,
+.pure-g {
+ word-spacing: -0.43em;
+}
+
+.pure-u {
+ display: inline-block;
+ *display: inline; /* IE < 8: fake inline-block */
+ zoom: 1;
+ letter-spacing: normal;
+ word-spacing: normal;
+ vertical-align: top;
+ text-rendering: auto;
+}
+
+/*
+Resets the font family back to the OS/browser's default sans-serif font,
+this the same font stack that Normalize.css sets for the `body`.
+*/
+.pure-g [class *= "pure-u"] {
+ font-family: sans-serif;
+}
+
+.pure-u-1,
+.pure-u-1-1,
+.pure-u-1-2,
+.pure-u-1-3,
+.pure-u-2-3,
+.pure-u-1-4,
+.pure-u-3-4,
+.pure-u-1-5,
+.pure-u-2-5,
+.pure-u-3-5,
+.pure-u-4-5,
+.pure-u-5-5,
+.pure-u-1-6,
+.pure-u-5-6,
+.pure-u-1-8,
+.pure-u-3-8,
+.pure-u-5-8,
+.pure-u-7-8,
+.pure-u-1-12,
+.pure-u-5-12,
+.pure-u-7-12,
+.pure-u-11-12,
+.pure-u-1-24,
+.pure-u-2-24,
+.pure-u-3-24,
+.pure-u-4-24,
+.pure-u-5-24,
+.pure-u-6-24,
+.pure-u-7-24,
+.pure-u-8-24,
+.pure-u-9-24,
+.pure-u-10-24,
+.pure-u-11-24,
+.pure-u-12-24,
+.pure-u-13-24,
+.pure-u-14-24,
+.pure-u-15-24,
+.pure-u-16-24,
+.pure-u-17-24,
+.pure-u-18-24,
+.pure-u-19-24,
+.pure-u-20-24,
+.pure-u-21-24,
+.pure-u-22-24,
+.pure-u-23-24,
+.pure-u-24-24 {
+ display: inline-block;
+ *display: inline;
+ zoom: 1;
+ letter-spacing: normal;
+ word-spacing: normal;
+ vertical-align: top;
+ text-rendering: auto;
+}
+
+.pure-u-1-24 {
+ width: 4.1667%;
+ *width: 4.1357%;
+}
+
+.pure-u-1-12,
+.pure-u-2-24 {
+ width: 8.3333%;
+ *width: 8.3023%;
+}
+
+.pure-u-1-8,
+.pure-u-3-24 {
+ width: 12.5000%;
+ *width: 12.4690%;
+}
+
+.pure-u-1-6,
+.pure-u-4-24 {
+ width: 16.6667%;
+ *width: 16.6357%;
+}
+
+.pure-u-1-5 {
+ width: 20%;
+ *width: 19.9690%;
+}
+
+.pure-u-5-24 {
+ width: 20.8333%;
+ *width: 20.8023%;
+}
+
+.pure-u-1-4,
+.pure-u-6-24 {
+ width: 25%;
+ *width: 24.9690%;
+}
+
+.pure-u-7-24 {
+ width: 29.1667%;
+ *width: 29.1357%;
+}
+
+.pure-u-1-3,
+.pure-u-8-24 {
+ width: 33.3333%;
+ *width: 33.3023%;
+}
+
+.pure-u-3-8,
+.pure-u-9-24 {
+ width: 37.5000%;
+ *width: 37.4690%;
+}
+
+.pure-u-2-5 {
+ width: 40%;
+ *width: 39.9690%;
+}
+
+.pure-u-5-12,
+.pure-u-10-24 {
+ width: 41.6667%;
+ *width: 41.6357%;
+}
+
+.pure-u-11-24 {
+ width: 45.8333%;
+ *width: 45.8023%;
+}
+
+.pure-u-1-2,
+.pure-u-12-24 {
+ width: 50%;
+ *width: 49.9690%;
+}
+
+.pure-u-13-24 {
+ width: 54.1667%;
+ *width: 54.1357%;
+}
+
+.pure-u-7-12,
+.pure-u-14-24 {
+ width: 58.3333%;
+ *width: 58.3023%;
+}
+
+.pure-u-3-5 {
+ width: 60%;
+ *width: 59.9690%;
+}
+
+.pure-u-5-8,
+.pure-u-15-24 {
+ width: 62.5000%;
+ *width: 62.4690%;
+}
+
+.pure-u-2-3,
+.pure-u-16-24 {
+ width: 66.6667%;
+ *width: 66.6357%;
+}
+
+.pure-u-17-24 {
+ width: 70.8333%;
+ *width: 70.8023%;
+}
+
+.pure-u-3-4,
+.pure-u-18-24 {
+ width: 75%;
+ *width: 74.9690%;
+}
+
+.pure-u-19-24 {
+ width: 79.1667%;
+ *width: 79.1357%;
+}
+
+.pure-u-4-5 {
+ width: 80%;
+ *width: 79.9690%;
+}
+
+.pure-u-5-6,
+.pure-u-20-24 {
+ width: 83.3333%;
+ *width: 83.3023%;
+}
+
+.pure-u-7-8,
+.pure-u-21-24 {
+ width: 87.5000%;
+ *width: 87.4690%;
+}
+
+.pure-u-11-12,
+.pure-u-22-24 {
+ width: 91.6667%;
+ *width: 91.6357%;
+}
+
+.pure-u-23-24 {
+ width: 95.8333%;
+ *width: 95.8023%;
+}
+
+.pure-u-1,
+.pure-u-1-1,
+.pure-u-5-5,
+.pure-u-24-24 {
+ width: 100%;
+}
+.pure-button {
+ /* Structure */
+ display: inline-block;
+ zoom: 1;
+ line-height: normal;
+ white-space: nowrap;
+ vertical-align: middle;
+ text-align: center;
+ cursor: pointer;
+ -webkit-user-drag: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ box-sizing: border-box;
+}
+
+/* Firefox: Get rid of the inner focus border */
+.pure-button::-moz-focus-inner {
+ padding: 0;
+ border: 0;
+}
+
+/* Inherit .pure-g styles */
+.pure-button-group {
+ letter-spacing: -0.31em; /* Webkit: collapse white-space between units */
+ *letter-spacing: normal; /* reset IE < 8 */
+ *word-spacing: -0.43em; /* IE < 8: collapse white-space between units */
+ text-rendering: optimizespeed; /* Webkit: fixes text-rendering: optimizeLegibility */
+}
+
+.opera-only :-o-prefocus,
+.pure-button-group {
+ word-spacing: -0.43em;
+}
+
+.pure-button-group .pure-button {
+ letter-spacing: normal;
+ word-spacing: normal;
+ vertical-align: top;
+ text-rendering: auto;
+}
+
+/*csslint outline-none:false*/
+
+.pure-button {
+ font-family: inherit;
+ font-size: 100%;
+ padding: 0.5em 1em;
+ color: #444; /* rgba not supported (IE 8) */
+ color: rgba(0, 0, 0, 0.80); /* rgba supported */
+ border: 1px solid #999; /*IE 6/7/8*/
+ border: none rgba(0, 0, 0, 0); /*IE9 + everything else*/
+ background-color: #E6E6E6;
+ text-decoration: none;
+ border-radius: 2px;
+}
+
+.pure-button-hover,
+.pure-button:hover,
+.pure-button:focus {
+ /* csslint ignore:start */
+ filter: alpha(opacity=90);
+ /* csslint ignore:end */
+ background-image: -webkit-linear-gradient(transparent, rgba(0,0,0, 0.05) 40%, rgba(0,0,0, 0.10));
+ background-image: linear-gradient(transparent, rgba(0,0,0, 0.05) 40%, rgba(0,0,0, 0.10));
+}
+.pure-button:focus {
+ outline: 0;
+}
+.pure-button-active,
+.pure-button:active {
+ box-shadow: 0 0 0 1px rgba(0,0,0, 0.15) inset, 0 0 6px rgba(0,0,0, 0.20) inset;
+ border-color: #000\9;
+}
+
+.pure-button[disabled],
+.pure-button-disabled,
+.pure-button-disabled:hover,
+.pure-button-disabled:focus,
+.pure-button-disabled:active {
+ border: none;
+ background-image: none;
+ /* csslint ignore:start */
+ filter: alpha(opacity=40);
+ /* csslint ignore:end */
+ opacity: 0.40;
+ cursor: not-allowed;
+ box-shadow: none;
+ pointer-events: none;
+}
+
+.pure-button-hidden {
+ display: none;
+}
+
+.pure-button-primary,
+.pure-button-selected,
+a.pure-button-primary,
+a.pure-button-selected {
+ background-color: rgb(0, 120, 231);
+ color: #fff;
+}
+
+/* Button Groups */
+.pure-button-group .pure-button {
+ margin: 0;
+ border-radius: 0;
+ border-right: 1px solid #111; /* fallback color for rgba() for IE7/8 */
+ border-right: 1px solid rgba(0, 0, 0, 0.2);
+
+}
+
+.pure-button-group .pure-button:first-child {
+ border-top-left-radius: 2px;
+ border-bottom-left-radius: 2px;
+}
+.pure-button-group .pure-button:last-child {
+ border-top-right-radius: 2px;
+ border-bottom-right-radius: 2px;
+ border-right: none;
+}
+
+/*csslint box-model:false*/
+/*
+Box-model set to false because we're setting a height on select elements, which
+also have border and padding. This is done because some browsers don't render
+the padding. We explicitly set the box-model for select elements to border-box,
+so we can ignore the csslint warning.
+*/
+
+.pure-form input[type="text"],
+.pure-form input[type="password"],
+.pure-form input[type="email"],
+.pure-form input[type="url"],
+.pure-form input[type="date"],
+.pure-form input[type="month"],
+.pure-form input[type="time"],
+.pure-form input[type="datetime"],
+.pure-form input[type="datetime-local"],
+.pure-form input[type="week"],
+.pure-form input[type="number"],
+.pure-form input[type="search"],
+.pure-form input[type="tel"],
+.pure-form input[type="color"],
+.pure-form select,
+.pure-form textarea {
+ padding: 0.5em 0.6em;
+ display: inline-block;
+ border: 1px solid #ccc;
+ box-shadow: inset 0 1px 3px #ddd;
+ border-radius: 4px;
+ vertical-align: middle;
+ box-sizing: border-box;
+}
+
+/*
+Need to separate out the :not() selector from the rest of the CSS 2.1 selectors
+since IE8 won't execute CSS that contains a CSS3 selector.
+*/
+.pure-form input:not([type]) {
+ padding: 0.5em 0.6em;
+ display: inline-block;
+ border: 1px solid #ccc;
+ box-shadow: inset 0 1px 3px #ddd;
+ border-radius: 4px;
+ box-sizing: border-box;
+}
+
+
+/* Chrome (as of v.32/34 on OS X) needs additional room for color to display. */
+/* May be able to remove this tweak as color inputs become more standardized across browsers. */
+.pure-form input[type="color"] {
+ padding: 0.2em 0.5em;
+}
+
+
+.pure-form input[type="text"]:focus,
+.pure-form input[type="password"]:focus,
+.pure-form input[type="email"]:focus,
+.pure-form input[type="url"]:focus,
+.pure-form input[type="date"]:focus,
+.pure-form input[type="month"]:focus,
+.pure-form input[type="time"]:focus,
+.pure-form input[type="datetime"]:focus,
+.pure-form input[type="datetime-local"]:focus,
+.pure-form input[type="week"]:focus,
+.pure-form input[type="number"]:focus,
+.pure-form input[type="search"]:focus,
+.pure-form input[type="tel"]:focus,
+.pure-form input[type="color"]:focus,
+.pure-form select:focus,
+.pure-form textarea:focus {
+ outline: 0;
+ border-color: #129FEA;
+}
+
+/*
+Need to separate out the :not() selector from the rest of the CSS 2.1 selectors
+since IE8 won't execute CSS that contains a CSS3 selector.
+*/
+.pure-form input:not([type]):focus {
+ outline: 0;
+ border-color: #129FEA;
+}
+
+.pure-form input[type="file"]:focus,
+.pure-form input[type="radio"]:focus,
+.pure-form input[type="checkbox"]:focus {
+ outline: thin solid #129FEA;
+ outline: 1px auto #129FEA;
+}
+.pure-form .pure-checkbox,
+.pure-form .pure-radio {
+ margin: 0.5em 0;
+ display: block;
+}
+
+.pure-form input[type="text"][disabled],
+.pure-form input[type="password"][disabled],
+.pure-form input[type="email"][disabled],
+.pure-form input[type="url"][disabled],
+.pure-form input[type="date"][disabled],
+.pure-form input[type="month"][disabled],
+.pure-form input[type="time"][disabled],
+.pure-form input[type="datetime"][disabled],
+.pure-form input[type="datetime-local"][disabled],
+.pure-form input[type="week"][disabled],
+.pure-form input[type="number"][disabled],
+.pure-form input[type="search"][disabled],
+.pure-form input[type="tel"][disabled],
+.pure-form input[type="color"][disabled],
+.pure-form select[disabled],
+.pure-form textarea[disabled] {
+ cursor: not-allowed;
+ background-color: #eaeded;
+ color: #cad2d3;
+}
+
+/*
+Need to separate out the :not() selector from the rest of the CSS 2.1 selectors
+since IE8 won't execute CSS that contains a CSS3 selector.
+*/
+.pure-form input:not([type])[disabled] {
+ cursor: not-allowed;
+ background-color: #eaeded;
+ color: #cad2d3;
+}
+.pure-form input[readonly],
+.pure-form select[readonly],
+.pure-form textarea[readonly] {
+ background-color: #eee; /* menu hover bg color */
+ color: #777; /* menu text color */
+ border-color: #ccc;
+}
+
+.pure-form input:focus:invalid,
+.pure-form textarea:focus:invalid,
+.pure-form select:focus:invalid {
+ color: #b94a48;
+ border-color: #e9322d;
+}
+.pure-form input[type="file"]:focus:invalid:focus,
+.pure-form input[type="radio"]:focus:invalid:focus,
+.pure-form input[type="checkbox"]:focus:invalid:focus {
+ outline-color: #e9322d;
+}
+.pure-form select {
+ /* Normalizes the height; padding is not sufficient. */
+ height: 2.25em;
+ border: 1px solid #ccc;
+ background-color: white;
+}
+.pure-form select[multiple] {
+ height: auto;
+}
+.pure-form label {
+ margin: 0.5em 0 0.2em;
+}
+.pure-form fieldset {
+ margin: 0;
+ padding: 0.35em 0 0.75em;
+ border: 0;
+}
+.pure-form legend {
+ display: block;
+ width: 50%;
+ padding: 0.3em 0;
+ margin-bottom: 0.3em;
+ margin-left: 5px;
+ color: #333;
+ /* border-bottom: 1px solid #e5e5e5; */
+ border-bottom: 1px solid #bce8f1;
+}
+
+.pure-form-stacked input[type="text"],
+.pure-form-stacked input[type="password"],
+.pure-form-stacked input[type="email"],
+.pure-form-stacked input[type="url"],
+.pure-form-stacked input[type="date"],
+.pure-form-stacked input[type="month"],
+.pure-form-stacked input[type="time"],
+.pure-form-stacked input[type="datetime"],
+.pure-form-stacked input[type="datetime-local"],
+.pure-form-stacked input[type="week"],
+.pure-form-stacked input[type="number"],
+.pure-form-stacked input[type="search"],
+.pure-form-stacked input[type="tel"],
+.pure-form-stacked input[type="color"],
+.pure-form-stacked input[type="file"],
+.pure-form-stacked select,
+.pure-form-stacked label,
+.pure-form-stacked textarea {
+ display: block;
+ margin: 0.25em 0;
+}
+
+/*
+Need to separate out the :not() selector from the rest of the CSS 2.1 selectors
+since IE8 won't execute CSS that contains a CSS3 selector.
+*/
+.pure-form-stacked input:not([type]) {
+ display: block;
+ margin: 0.25em 0;
+}
+.pure-form-aligned input,
+.pure-form-aligned textarea,
+.pure-form-aligned select,
+/* NOTE: pure-help-inline is deprecated. Use .pure-form-message-inline instead. */
+.pure-form-aligned .pure-help-inline,
+.pure-form-message-inline {
+ display: inline-block;
+ *display: inline;
+ *zoom: 1;
+ vertical-align: middle;
+}
+.pure-form-aligned textarea {
+ vertical-align: top;
+}
+
+/* Aligned Forms */
+.pure-form-aligned .pure-control-group {
+ margin-bottom: 0.5em;
+}
+.pure-form-aligned .pure-control-group label {
+ text-align: right;
+ display: inline-block;
+ vertical-align: middle;
+ width: 10em;
+ margin: 0 1em 0 0;
+}
+.pure-form-aligned .pure-controls {
+ margin: 1.5em 0 0 11em;
+}
+
+/* Rounded Inputs */
+.pure-form input.pure-input-rounded,
+.pure-form .pure-input-rounded {
+ border-radius: 2em;
+ padding: 0.5em 1em;
+}
+
+/* Grouped Inputs */
+.pure-form .pure-group fieldset {
+ margin-bottom: 10px;
+}
+.pure-form .pure-group input,
+.pure-form .pure-group textarea {
+ display: block;
+ padding: 10px;
+ margin: 0 0 -1px;
+ border-radius: 0;
+ position: relative;
+ top: -1px;
+}
+.pure-form .pure-group input:focus,
+.pure-form .pure-group textarea:focus {
+ z-index: 3;
+}
+.pure-form .pure-group input:first-child,
+.pure-form .pure-group textarea:first-child {
+ top: 1px;
+ border-radius: 4px 4px 0 0;
+ margin: 0;
+}
+.pure-form .pure-group input:first-child:last-child,
+.pure-form .pure-group textarea:first-child:last-child {
+ top: 1px;
+ border-radius: 4px;
+ margin: 0;
+}
+.pure-form .pure-group input:last-child,
+.pure-form .pure-group textarea:last-child {
+ top: -2px;
+ border-radius: 0 0 4px 4px;
+ margin: 0;
+}
+.pure-form .pure-group button {
+ margin: 0.35em 0;
+}
+
+.pure-form .pure-input-1 {
+ width: 100%;
+}
+.pure-form .pure-input-3-4 {
+ width: 75%;
+}
+.pure-form .pure-input-2-3 {
+ width: 66%;
+}
+.pure-form .pure-input-1-2 {
+ width: 50%;
+}
+.pure-form .pure-input-1-3 {
+ width: 33%;
+}
+.pure-form .pure-input-1-4 {
+ width: 25%;
+}
+
+/* Inline help for forms */
+/* NOTE: pure-help-inline is deprecated. Use .pure-form-message-inline instead. */
+.pure-form .pure-help-inline,
+.pure-form-message-inline {
+ display: inline-block;
+ padding-left: 0.3em;
+ color: #666;
+ vertical-align: middle;
+ font-size: 0.875em;
+}
+
+/* Block help for forms */
+.pure-form-message {
+ display: block;
+ color: #666;
+ font-size: 0.875em;
+}
+
+@media only screen and (max-width : 480px) {
+ .pure-form button[type="submit"] {
+ margin: 0.7em 0 0;
+ }
+
+ .pure-form input:not([type]),
+ .pure-form input[type="text"],
+ .pure-form input[type="password"],
+ .pure-form input[type="email"],
+ .pure-form input[type="url"],
+ .pure-form input[type="date"],
+ .pure-form input[type="month"],
+ .pure-form input[type="time"],
+ .pure-form input[type="datetime"],
+ .pure-form input[type="datetime-local"],
+ .pure-form input[type="week"],
+ .pure-form input[type="number"],
+ .pure-form input[type="search"],
+ .pure-form input[type="tel"],
+ .pure-form input[type="color"],
+ .pure-form label {
+ margin-bottom: 0.3em;
+ display: block;
+ }
+
+ .pure-group input:not([type]),
+ .pure-group input[type="text"],
+ .pure-group input[type="password"],
+ .pure-group input[type="email"],
+ .pure-group input[type="url"],
+ .pure-group input[type="date"],
+ .pure-group input[type="month"],
+ .pure-group input[type="time"],
+ .pure-group input[type="datetime"],
+ .pure-group input[type="datetime-local"],
+ .pure-group input[type="week"],
+ .pure-group input[type="number"],
+ .pure-group input[type="search"],
+ .pure-group input[type="tel"],
+ .pure-group input[type="color"] {
+ margin-bottom: 0;
+ }
+
+ .pure-form-aligned .pure-control-group label {
+ margin-bottom: 0.3em;
+ text-align: left;
+ display: block;
+ width: 100%;
+ }
+
+ .pure-form-aligned .pure-controls {
+ margin: 1.5em 0 0 0;
+ }
+
+ /* NOTE: pure-help-inline is deprecated. Use .pure-form-message-inline instead. */
+ .pure-form .pure-help-inline,
+ .pure-form-message-inline,
+ .pure-form-message {
+ display: block;
+ font-size: 0.75em;
+ /* Increased bottom padding to make it group with its related input element. */
+ padding: 0.2em 0 0.8em;
+ }
+}
+
+/*csslint adjoining-classes: false, box-model:false*/
+.pure-menu {
+ box-sizing: border-box;
+}
+
+.pure-menu-fixed {
+ position: fixed;
+ left: 0;
+ top: 0;
+ z-index: 3;
+}
+
+.pure-menu-list,
+.pure-menu-item {
+ position: relative;
+}
+
+.pure-menu-list {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+.pure-menu-item {
+ padding: 0;
+ margin: 0;
+ height: 100%;
+}
+
+.pure-menu-link,
+.pure-menu-heading {
+ display: block;
+ text-decoration: none;
+ white-space: nowrap;
+}
+
+/* HORIZONTAL MENU */
+.pure-menu-horizontal {
+ width: 100%;
+ white-space: nowrap;
+}
+
+.pure-menu-horizontal .pure-menu-list {
+ display: inline-block;
+}
+
+/* Initial menus should be inline-block so that they are horizontal */
+.pure-menu-horizontal .pure-menu-item,
+.pure-menu-horizontal .pure-menu-heading,
+.pure-menu-horizontal .pure-menu-separator {
+ display: inline-block;
+ *display: inline;
+ zoom: 1;
+ vertical-align: middle;
+}
+
+/* Submenus should still be display: block; */
+.pure-menu-item .pure-menu-item {
+ display: block;
+}
+
+.pure-menu-children {
+ display: none;
+ position: absolute;
+ left: 100%;
+ top: 0;
+ margin: 0;
+ padding: 0;
+ z-index: 3;
+}
+
+.pure-menu-horizontal .pure-menu-children {
+ left: 0;
+ top: auto;
+ width: inherit;
+}
+
+.pure-menu-allow-hover:hover > .pure-menu-children,
+.pure-menu-active > .pure-menu-children {
+ display: block;
+ position: absolute;
+}
+
+/* Vertical Menus - show the dropdown arrow */
+.pure-menu-has-children > .pure-menu-link:after {
+ padding-left: 0.5em;
+ content: "\25B8";
+ font-size: small;
+}
+
+/* Horizontal Menus - show the dropdown arrow */
+.pure-menu-horizontal .pure-menu-has-children > .pure-menu-link:after {
+ content: "\25BE";
+}
+
+/* scrollable menus */
+.pure-menu-scrollable {
+ overflow-y: scroll;
+ overflow-x: scroll;
+}
+
+.pure-menu-scrollable .pure-menu-list {
+ display: block;
+}
+
+.pure-menu-horizontal.pure-menu-scrollable .pure-menu-list {
+ display: inline-block;
+}
+
+.pure-menu-horizontal.pure-menu-scrollable {
+ white-space: nowrap;
+ overflow-y: hidden;
+ overflow-x: auto;
+ -ms-overflow-style: none;
+ -webkit-overflow-scrolling: touch;
+ /* a little extra padding for this style to allow for scrollbars */
+ padding: .5em 0;
+}
+
+.pure-menu-horizontal.pure-menu-scrollable::-webkit-scrollbar {
+ display: none;
+}
+
+/* misc default styling */
+
+.pure-menu-separator,
+.pure-menu-horizontal .pure-menu-children .pure-menu-separator {
+ background-color: #ccc;
+ height: 1px;
+ margin: .3em 0;
+}
+
+.pure-menu-horizontal .pure-menu-separator {
+ width: 1px;
+ height: 1.3em;
+ margin: 0 .3em ;
+}
+
+/* Need to reset the separator since submenu is vertical */
+.pure-menu-horizontal .pure-menu-children .pure-menu-separator {
+ display: block;
+ width: auto;
+}
+
+.pure-menu-heading {
+ text-transform: uppercase;
+ color: #565d64;
+}
+
+.pure-menu-link {
+ /* color: #777; */
+}
+
+.pure-menu-children {
+ background-color: #fff;
+}
+
+.pure-menu-link,
+.pure-menu-disabled,
+.pure-menu-heading {
+ padding: .5em 1em;
+}
+
+.pure-menu-disabled {
+ opacity: .5;
+}
+
+.pure-menu-disabled .pure-menu-link:hover {
+ background-color: transparent;
+}
+
+.pure-menu-active > .pure-menu-link,
+.pure-menu-link:hover,
+.pure-menu-link:focus {
+ background-color: #eee;
+}
+
+.pure-menu-selected .pure-menu-link,
+.pure-menu-selected .pure-menu-link:visited {
+ color: #000;
+}
+
+.pure-table {
+ /* Remove spacing between table cells (from Normalize.css) */
+ border-collapse: collapse;
+ border-spacing: 0;
+ empty-cells: show;
+ border: 1px solid #cbcbcb;
+}
+
+.pure-table caption {
+ color: #000;
+ font: italic 85%/1 arial, sans-serif;
+ padding: 1em 0;
+ text-align: center;
+}
+
+.pure-table td,
+.pure-table th {
+ border-left: 1px solid #cbcbcb;/* inner column border */
+ border-width: 0 0 0 1px;
+ font-size: inherit;
+ margin: 0;
+ overflow: visible; /*to make ths where the title is really long work*/
+ padding: 0.5em 1em; /* cell padding */
+}
+
+/* Consider removing this next declaration block, as it causes problems when
+there's a rowspan on the first cell. Case added to the tests. issue#432 */
+.pure-table td:first-child,
+.pure-table th:first-child {
+ border-left-width: 0;
+}
+
+.pure-table thead {
+ background-color: #e0e0e0;
+ color: #000;
+ text-align: left;
+ vertical-align: bottom;
+}
+
+/*
+striping:
+ even - #fff (white)
+ odd - #f2f2f2 (light gray)
+*/
+.pure-table td {
+ background-color: transparent;
+}
+.pure-table-odd td {
+ background-color: #f2f2f2;
+}
+
+/* nth-child selector for modern browsers */
+.pure-table-striped tr:nth-child(2n-1) td {
+ background-color: #f2f2f2;
+}
+
+/* BORDERED TABLES */
+.pure-table-bordered td {
+ border-bottom: 1px solid #cbcbcb;
+}
+.pure-table-bordered tbody > tr:last-child > td {
+ border-bottom-width: 0;
+}
+
+
+/* HORIZONTAL BORDERED TABLES */
+
+.pure-table-horizontal td,
+.pure-table-horizontal th {
+ border-width: 0 0 1px 0;
+ border-bottom: 1px solid #cbcbcb;
+}
+.pure-table-horizontal tbody > tr:last-child > td {
+ border-bottom-width: 0;
+}
+
+
+/*
+ * -- HELPER STYLES --
+ * Over-riding some of the .pure-button styles to make my buttons look unique
+ */
+.button-success,
+.button-error,
+.button-warning,
+.button-secondary {
+ color: white;
+ border-radius: 4px;
+ text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
+}
+
+.button-success {
+ background: rgb(28, 184, 65); /* this is a green */
+}
+
+.button-error {
+ background: rgb(202, 60, 60); /* this is a maroon */
+}
+
+.button-warning {
+ background: rgb(223, 117, 20); /* this is an orange */
+}
+
+.button-secondary {
+ background: rgb(66, 184, 221); /* this is a light blue */
+}
+
+
+.custom-restricted {
+ height: 160px;
+ width: 150px;
+ border: 1px solid gray;
+ border-radius: 4px;
+}
+
diff --git a/extensions/database/module/styles/theme.less b/extensions/database/module/styles/theme.less
new file mode 100644
index 000000000..443a411bc
--- /dev/null
+++ b/extensions/database/module/styles/theme.less
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+@import-less url("../../../../main/webapp/modules/core/styles/theme.less");
diff --git a/extensions/database/src/com/google/refine/extension/database/DBQueryResultImportReader.java b/extensions/database/src/com/google/refine/extension/database/DBQueryResultImportReader.java
new file mode 100644
index 000000000..0da3fa055
--- /dev/null
+++ b/extensions/database/src/com/google/refine/extension/database/DBQueryResultImportReader.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.refine.extension.database;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.refine.extension.database.model.DatabaseColumn;
+import com.google.refine.extension.database.model.DatabaseQueryInfo;
+import com.google.refine.extension.database.model.DatabaseRow;
+import com.google.refine.importers.TabularImportingParserBase.TableDataReader;
+import com.google.refine.importing.ImportingJob;
+
+
+public class DBQueryResultImportReader implements TableDataReader {
+
+ private static final Logger logger = LoggerFactory.getLogger("DBQueryResultImportReader");
+
+ private final ImportingJob job;
+ private final String querySource;
+ private List dbColumns;
+ private final int batchSize;
+
+ private int nextRow = 0; // 0-based
+ private int batchRowStart = 0; // 0-based
+ private boolean end = false;
+ private List> rowsOfCells = null;
+ private boolean usedHeaders = false;
+ private DatabaseService databaseService;
+ private DatabaseQueryInfo dbQueryInfo;
+ private int processedRows = 0;
+ private static int progress = 0;
+
+
+ public DBQueryResultImportReader(
+ ImportingJob job,
+ DatabaseService databaseService,
+ String querySource,
+ List columns,
+ DatabaseQueryInfo dbQueryInfo,
+ int batchSize) {
+
+ this.job = job;
+ this.querySource = querySource;
+ this.batchSize = batchSize;
+ this.dbColumns = columns;
+ this.databaseService = databaseService;
+ this.dbQueryInfo = dbQueryInfo;
+ if(logger.isDebugEnabled()) {
+ logger.debug("batchSize:" + batchSize);
+ }
+
+ }
+
+ @Override
+ public List getNextRowOfCells() throws IOException {
+
+ try {
+
+ if (!usedHeaders) {
+ List row = new ArrayList(dbColumns.size());
+ for (DatabaseColumn cd : dbColumns) {
+ row.add(cd.getName());
+ }
+ usedHeaders = true;
+ //logger.info("Exit::getNextRowOfCells return header::row:" + row);
+ return row;
+ }
+
+ if (rowsOfCells == null || (nextRow >= batchRowStart + rowsOfCells.size() && !end)) {
+ int newBatchRowStart = batchRowStart + (rowsOfCells == null ? 0 : rowsOfCells.size());
+ rowsOfCells = getRowsOfCells(newBatchRowStart);
+ processedRows = processedRows + rowsOfCells.size();
+ batchRowStart = newBatchRowStart;
+ setProgress(job, querySource, -1 /* batchRowStart * 100 / totalRows */);
+ }
+
+ if (rowsOfCells != null && nextRow - batchRowStart < rowsOfCells.size()) {
+ List result = rowsOfCells.get(nextRow++ - batchRowStart);
+ if(nextRow >= batchSize) {
+ rowsOfCells = getRowsOfCells(processedRows);
+ processedRows = processedRows + rowsOfCells.size();
+
+ if(logger.isDebugEnabled()) {
+ logger.debug("[[ Returning last row in batch:nextRow::{}, processedRows:{} ]]", nextRow, processedRows);
+ }
+
+ nextRow = 0;
+ if(processedRows % 100 == 0) {
+ setProgress(job, querySource, progress++);
+ }
+ if(processedRows % 10000 == 0) {
+ if(logger.isDebugEnabled()) {
+ logger.debug("[[ {} rows processed... ]]",processedRows);
+ }
+ }
+ }
+ return result;
+ } else {
+ if(logger.isDebugEnabled()) {
+ logger.debug("[[processedRows:{} ]]", processedRows);
+ }
+ return null;
+ }
+
+
+ }catch(DatabaseServiceException e) {
+ logger.error("DatabaseServiceException::{}", e);
+ throw new IOException(e);
+
+ }
+
+
+ }
+
+ /**
+ * @param startRow
+ * @return
+ * @throws IOException
+ * @throws DatabaseServiceException
+ */
+ private List> getRowsOfCells(int startRow) throws IOException, DatabaseServiceException {
+ //logger.info("Entry getRowsOfCells::startRow:" + startRow);
+
+ List> rowsOfCells = new ArrayList>(batchSize);
+
+ String query = databaseService.buildLimitQuery(batchSize, startRow, dbQueryInfo.getQuery());
+ //logger.info("batchSize::" + batchSize + " startRow::" + startRow + " query::" + query );
+
+ List dbRows = databaseService.getRows(dbQueryInfo.getDbConfig(), query);
+
+ if(dbRows != null && !dbRows.isEmpty() && dbRows.size() > 0) {
+
+ for(DatabaseRow dbRow: dbRows) {
+ List row = dbRow.getValues();
+ List rowOfCells = new ArrayList(row.size());
+
+ for (int j = 0; j < row.size() && j < dbColumns.size(); j++) {
+
+ String text = row.get(j);
+ if (text == null || text.isEmpty()) {
+ rowOfCells.add(null);
+ }else {
+ DatabaseColumn col = dbColumns.get(j);
+ if(col.getType() == DatabaseColumnType.NUMBER) {
+ try {
+ rowOfCells.add(Long.parseLong(text));
+ continue;
+ } catch (NumberFormatException e) {}
+
+ }else if(col.getType() == DatabaseColumnType.DOUBLE || col.getType() == DatabaseColumnType.FLOAT ) {
+ try {
+ double d = Double.parseDouble(text);
+ if (!Double.isInfinite(d) && !Double.isNaN(d)) {
+ rowOfCells.add(d);
+ continue;
+ }
+ } catch (NumberFormatException e) {}
+
+ }
+
+ rowOfCells.add(text);
+ }
+
+ }
+
+ rowsOfCells.add(rowOfCells);
+
+ }
+
+ }
+ end = dbRows.size() < batchSize + 1;
+ //logger.info("Exit::getRowsOfCells::rowsOfCells:{}", rowsOfCells);
+ return rowsOfCells;
+
+ }
+
+ private static void setProgress(ImportingJob job, String querySource, int percent) {
+ job.setProgress(percent, "Reading " + querySource);
+ }
+
+ public List getColumns() {
+ return dbColumns;
+ }
+
+
+ public void setColumns(List columns) {
+ this.dbColumns = columns;
+ }
+
+
+ public int getNextRow() {
+ return nextRow;
+ }
+
+
+ public void setNextRow(int nextRow) {
+ this.nextRow = nextRow;
+ }
+
+
+ public int getBatchRowStart() {
+ return batchRowStart;
+ }
+
+
+ public void setBatchRowStart(int batchRowStart) {
+ this.batchRowStart = batchRowStart;
+ }
+
+
+ public boolean isEnd() {
+ return end;
+ }
+
+
+ public void setEnd(boolean end) {
+ this.end = end;
+ }
+
+
+ public List> getRowsOfCells() {
+ return rowsOfCells;
+ }
+
+
+ public void setRowsOfCells(List> rowsOfCells) {
+ this.rowsOfCells = rowsOfCells;
+ }
+
+
+ public boolean isUsedHeaders() {
+ return usedHeaders;
+ }
+
+
+ public void setUsedHeaders(boolean usedHeaders) {
+ this.usedHeaders = usedHeaders;
+ }
+
+
+ public ImportingJob getJob() {
+ return job;
+ }
+
+
+ public String getQuerySource() {
+ return querySource;
+ }
+
+
+ public int getBatchSize() {
+ return batchSize;
+ }
+
+
+}
diff --git a/extensions/database/src/com/google/refine/extension/database/DBQueryResultPreviewReader.java b/extensions/database/src/com/google/refine/extension/database/DBQueryResultPreviewReader.java
new file mode 100644
index 000000000..95f8a6cf7
--- /dev/null
+++ b/extensions/database/src/com/google/refine/extension/database/DBQueryResultPreviewReader.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.refine.extension.database;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.refine.extension.database.model.DatabaseColumn;
+import com.google.refine.extension.database.model.DatabaseQueryInfo;
+import com.google.refine.extension.database.model.DatabaseRow;
+import com.google.refine.importers.TabularImportingParserBase.TableDataReader;
+import com.google.refine.importing.ImportingJob;
+
+
+public class DBQueryResultPreviewReader implements TableDataReader {
+
+ private static final Logger logger = LoggerFactory.getLogger("DBQueryResultPreviewReader");
+
+ private final ImportingJob job;
+ private final String querySource;
+ private List dbColumns;
+ private final int batchSize;
+
+ private int nextRow = 0; // 0-based
+ private int batchRowStart = 0; // 0-based
+ private boolean end = false;
+ private List> rowsOfCells = null;
+ private boolean usedHeaders = false;
+ private DatabaseService databaseService;
+ private DatabaseQueryInfo dbQueryInfo;
+
+
+ public DBQueryResultPreviewReader(
+ ImportingJob job,
+ DatabaseService databaseService,
+ String querySource,
+ List columns,
+ DatabaseQueryInfo dbQueryInfo,
+ int batchSize) {
+
+ this.job = job;
+ this.querySource = querySource;
+ this.batchSize = batchSize;
+ this.dbColumns = columns;
+ this.databaseService = databaseService;
+ this.dbQueryInfo = dbQueryInfo;
+ logger.debug("DBQueryResultPreviewReader::batchSize:" + batchSize);
+
+ }
+
+ @Override
+ public List getNextRowOfCells() throws IOException {
+
+ // logger.info("Entry::getNextRowOfCells");
+
+ try {
+
+ if (!usedHeaders) {
+ List row = new ArrayList(dbColumns.size());
+ for (DatabaseColumn cd : dbColumns) {
+ row.add(cd.getName());
+ }
+ usedHeaders = true;
+ // logger.debug("Exit::getNextRowOfCells return header::row:" + row);
+ return row;
+ }
+
+ if (rowsOfCells == null || (nextRow >= batchRowStart + rowsOfCells.size() && !end)) {
+ int newBatchRowStart = batchRowStart + (rowsOfCells == null ? 0 : rowsOfCells.size());
+ rowsOfCells = getRowsOfCells(newBatchRowStart);
+ batchRowStart = newBatchRowStart;
+ setProgress(job, querySource, -1 /* batchRowStart * 100 / totalRows */);
+ // logger.info("getNextRowOfCells:: rowsOfCellsIsNull::rowsOfCells size:" + rowsOfCells.size() + ":batchRowStart:" + batchRowStart + " ::nextRow:" + nextRow);
+ }
+
+ if (rowsOfCells != null && nextRow - batchRowStart < rowsOfCells.size()) {
+ //logger.info("Exit::getNextRowOfCells :rowsOfCellsNotNull::rowsOfCells size:" + rowsOfCells.size() + ":batchRowStart:" + batchRowStart + " ::nextRow:" + nextRow);
+ return rowsOfCells.get(nextRow++ - batchRowStart);
+ } else {
+ if(logger.isDebugEnabled()) {
+ logger.debug("nextRow:{}, batchRowStart:{}", nextRow, batchRowStart);
+ }
+
+ return null;
+ }
+
+
+ }catch(DatabaseServiceException e) {
+ logger.error("DatabaseServiceException::preview:{}", e.getMessage());
+ IOException ioEx = new IOException(e.getMessage(), e);
+ throw ioEx;
+
+ }
+
+ }
+
+ /**
+ *
+ * @param startRow
+ * @return
+ * @throws IOException
+ * @throws DatabaseServiceException
+ */
+ private List> getRowsOfCells(int startRow) throws IOException, DatabaseServiceException {
+ //logger.info("Entry getRowsOfCells::startRow:" + startRow);
+
+ List> rowsOfCells = new ArrayList>(batchSize);
+
+ String query = databaseService.buildLimitQuery(batchSize, startRow, dbQueryInfo.getQuery());
+ if(logger.isDebugEnabled()) {
+ logger.debug("batchSize::" + batchSize + " startRow::" + startRow + " query::" + query );
+ }
+
+ List dbRows = databaseService.getRows(dbQueryInfo.getDbConfig(), query);
+
+ if(dbRows != null && !dbRows.isEmpty() && dbRows.size() > 0) {
+
+ for(DatabaseRow dbRow: dbRows) {
+ List row = dbRow.getValues();
+ List rowOfCells = new ArrayList(row.size());
+
+ for (int j = 0; j < row.size() && j < dbColumns.size(); j++) {
+
+ String text = row.get(j);
+ if (text == null || text.isEmpty()) {
+ rowOfCells.add(null);
+ }else {
+ DatabaseColumn col = dbColumns.get(j);
+ if(col.getType() == DatabaseColumnType.NUMBER) {
+ try {
+ rowOfCells.add(Long.parseLong(text));
+ continue;
+ } catch (NumberFormatException e) {}
+
+ }else if(col.getType() == DatabaseColumnType.DOUBLE || col.getType() == DatabaseColumnType.FLOAT ) {
+ try {
+ double d = Double.parseDouble(text);
+ if (!Double.isInfinite(d) && !Double.isNaN(d)) {
+ rowOfCells.add(d);
+ continue;
+ }
+ } catch (NumberFormatException e) {}
+
+ }
+
+ rowOfCells.add(text);
+ }
+
+ }
+ rowsOfCells.add(rowOfCells);
+
+ }
+
+ }
+ end = dbRows.size() < batchSize + 1;
+ //logger.info("Exit::getRowsOfCells::rowsOfCells:{}", rowsOfCells);
+ return rowsOfCells;
+
+ }
+
+ private static void setProgress(ImportingJob job, String querySource, int percent) {
+ job.setProgress(percent, "Reading " + querySource);
+ }
+
+ public List getColumns() {
+ return dbColumns;
+ }
+
+
+ public void setColumns(List columns) {
+ this.dbColumns = columns;
+ }
+
+
+ public int getNextRow() {
+ return nextRow;
+ }
+
+
+ public void setNextRow(int nextRow) {
+ this.nextRow = nextRow;
+ }
+
+
+ public int getBatchRowStart() {
+ return batchRowStart;
+ }
+
+
+ public void setBatchRowStart(int batchRowStart) {
+ this.batchRowStart = batchRowStart;
+ }
+
+
+ public boolean isEnd() {
+ return end;
+ }
+
+
+ public void setEnd(boolean end) {
+ this.end = end;
+ }
+
+
+ public List> getRowsOfCells() {
+ return rowsOfCells;
+ }
+
+
+ public void setRowsOfCells(List> rowsOfCells) {
+ this.rowsOfCells = rowsOfCells;
+ }
+
+
+ public boolean isUsedHeaders() {
+ return usedHeaders;
+ }
+
+
+ public void setUsedHeaders(boolean usedHeaders) {
+ this.usedHeaders = usedHeaders;
+ }
+
+
+ public ImportingJob getJob() {
+ return job;
+ }
+
+
+ public String getQuerySource() {
+ return querySource;
+ }
+
+
+ public int getBatchSize() {
+ return batchSize;
+ }
+
+
+}
diff --git a/extensions/database/src/com/google/refine/extension/database/DatabaseColumnType.java b/extensions/database/src/com/google/refine/extension/database/DatabaseColumnType.java
new file mode 100644
index 000000000..79a0c8f3a
--- /dev/null
+++ b/extensions/database/src/com/google/refine/extension/database/DatabaseColumnType.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.refine.extension.database;
+
+
+public enum DatabaseColumnType {
+
+ STRING,
+ NUMBER,
+ DATETIME,
+ LOCATION,
+ BOOLEAN,
+ DATE,
+ DOUBLE,
+ FLOAT
+
+}
diff --git a/extensions/database/src/com/google/refine/extension/database/DatabaseConfiguration.java b/extensions/database/src/com/google/refine/extension/database/DatabaseConfiguration.java
new file mode 100644
index 000000000..85c4cd6e8
--- /dev/null
+++ b/extensions/database/src/com/google/refine/extension/database/DatabaseConfiguration.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.refine.extension.database;
+
+
+public class DatabaseConfiguration {
+
+ private String connectionName;
+ private String databaseType;
+ private String databaseHost;
+ private int databasePort;
+ private String databaseUser;
+ private String databasePassword;
+ private String databaseName;
+ private String databaseSchema;
+
+ //optional parameters
+ private boolean useSSL;
+
+
+ public String getConnectionName() {
+ return connectionName;
+ }
+
+ public void setConnectionName(String connectionName) {
+ this.connectionName = connectionName;
+ }
+
+ public String getDatabaseType() {
+ return databaseType;
+ }
+
+ public void setDatabaseType(String databaseType) {
+ this.databaseType = databaseType;
+ }
+
+ public String getDatabaseHost() {
+ return databaseHost;
+ }
+
+ public void setDatabaseHost(String databaseServer) {
+ this.databaseHost = databaseServer;
+ }
+
+ public int getDatabasePort() {
+ return databasePort;
+ }
+
+ public void setDatabasePort(int databasePort) {
+ this.databasePort = databasePort;
+ }
+
+ public String getDatabaseUser() {
+ return databaseUser;
+ }
+
+ public void setDatabaseUser(String databaseUser) {
+ this.databaseUser = databaseUser;
+ }
+
+ public String getDatabasePassword() {
+ return databasePassword;
+ }
+
+ public void setDatabasePassword(String databasePassword) {
+ this.databasePassword = databasePassword;
+ }
+
+ public String getDatabaseName() {
+ return databaseName;
+ }
+
+ public void setDatabaseName(String initialDatabase) {
+ this.databaseName = initialDatabase;
+ }
+
+ public String getDatabaseSchema() {
+ return databaseSchema;
+ }
+
+ public void setDatabaseSchema(String initialSchema) {
+ this.databaseSchema = initialSchema;
+ }
+
+
+
+ public boolean isUseSSL() {
+ return useSSL;
+ }
+
+ public void setUseSSL(boolean useSSL) {
+ this.useSSL = useSSL;
+ }
+
+ @Override
+ public String toString() {
+ return "DatabaseConfiguration [connectionName=" + connectionName + ", databaseType=" + databaseType
+ + ", databaseHost=" + databaseHost + ", databasePort=" + databasePort + ", databaseUser=" + databaseUser
+ + ", databaseName=" + databaseName + ", databaseSchema="
+ + databaseSchema + ", useSSL=" + useSSL + "]";
+ }
+
+
+
+}
diff --git a/extensions/database/src/com/google/refine/extension/database/DatabaseImportController.java b/extensions/database/src/com/google/refine/extension/database/DatabaseImportController.java
new file mode 100644
index 000000000..f5650acf6
--- /dev/null
+++ b/extensions/database/src/com/google/refine/extension/database/DatabaseImportController.java
@@ -0,0 +1,493 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.google.refine.extension.database;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Properties;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.json.JSONWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.refine.ProjectManager;
+import com.google.refine.RefineServlet;
+import com.google.refine.commands.HttpUtilities;
+import com.google.refine.extension.database.model.DatabaseColumn;
+import com.google.refine.extension.database.model.DatabaseQueryInfo;
+import com.google.refine.importers.TabularImportingParserBase;
+import com.google.refine.importing.ImportingController;
+import com.google.refine.importing.ImportingJob;
+import com.google.refine.importing.ImportingManager;
+import com.google.refine.model.Project;
+import com.google.refine.model.medadata.ProjectMetadata;
+import com.google.refine.util.JSONUtilities;
+import com.google.refine.util.ParsingUtilities;
+
+
+public class DatabaseImportController implements ImportingController {
+
+ private static final Logger logger = LoggerFactory.getLogger("DatabaseImportController");
+ protected RefineServlet servlet;
+ public static int DEFAULT_PREVIEW_LIMIT = 100;
+ public static String OPTIONS_KEY = "options";
+
+ @Override
+ public void init(RefineServlet servlet) {
+ this.servlet = servlet;
+ }
+
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ HttpUtilities.respond(response, "error", "GET not implemented");
+ }
+
+ @Override
+ public void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ if(logger.isDebugEnabled()){
+ logger.debug("doPost Query String::{}", request.getQueryString());
+ }
+ response.setCharacterEncoding("UTF-8");
+ Properties parameters = ParsingUtilities.parseUrlParameters(request);
+
+ String subCommand = parameters.getProperty("subCommand");
+
+ if(logger.isDebugEnabled()){
+ logger.info("doPost::subCommand::{}", subCommand);
+ }
+
+ if ("initialize-parser-ui".equals(subCommand)) {
+ doInitializeParserUI(request, response, parameters);
+ } else if ("parse-preview".equals(subCommand)) {
+ try {
+
+ doParsePreview(request, response, parameters);
+
+ } catch (DatabaseServiceException e) {
+ logger.error("doPost::DatabaseServiceException::{}", e);
+ HttpUtilities.respond(response, "error", getDbServiceException(e));
+ }
+ } else if ("create-project".equals(subCommand)) {
+ doCreateProject(request, response, parameters);
+ } else {
+ HttpUtilities.respond(response, "error", "No such sub command");
+ }
+
+ }
+
+ private String getDbServiceException(Exception ex) {
+ String message = "";
+ if(ex instanceof DatabaseServiceException) {
+ DatabaseServiceException dbEx = (DatabaseServiceException) ex;
+ if(dbEx.isSqlException()) {
+ message = message + dbEx.getSqlCode() + " " + dbEx.getSqlState();
+ }
+ }
+ message = message + ex.getMessage();
+
+ return message;
+ }
+
+ /**
+ *
+ * @param request
+ * @param response
+ * @param parameters
+ * @throws ServletException
+ * @throws IOException
+ */
+ private void doInitializeParserUI(HttpServletRequest request, HttpServletResponse response, Properties parameters)
+ throws ServletException, IOException {
+ if(logger.isDebugEnabled()) {
+ logger.debug("::doInitializeParserUI::");
+ }
+
+
+ JSONObject result = new JSONObject();
+ JSONObject options = new JSONObject();
+ JSONUtilities.safePut(result, "status", "ok");
+ JSONUtilities.safePut(result, OPTIONS_KEY, options);
+
+ JSONUtilities.safePut(options, "skipDataLines", 0);
+ JSONUtilities.safePut(options, "storeBlankRows", true);
+ JSONUtilities.safePut(options, "storeBlankCellsAsNulls", true);
+ if(logger.isDebugEnabled()) {
+ logger.debug("doInitializeParserUI:::{}", result.toString());
+ }
+
+ HttpUtilities.respond(response, result.toString());
+
+ }
+
+
+ /**
+ * doParsePreview
+ * @param request
+ * @param response
+ * @param parameters
+ * @throws ServletException
+ * @throws IOException
+ * @throws DatabaseServiceException
+ */
+ private void doParsePreview(
+ HttpServletRequest request, HttpServletResponse response, Properties parameters)
+ throws ServletException, IOException, DatabaseServiceException {
+ if(logger.isDebugEnabled()) {
+ logger.debug("JobID::{}", parameters.getProperty("jobID"));
+ }
+
+
+ long jobID = Long.parseLong(parameters.getProperty("jobID"));
+ ImportingJob job = ImportingManager.getJob(jobID);
+ if (job == null) {
+ HttpUtilities.respond(response, "error", "No such import job");
+ return;
+ }
+
+
+ DatabaseQueryInfo databaseQueryInfo = getQueryInfo(request);
+
+
+ if(databaseQueryInfo == null) {
+ HttpUtilities.respond(response, "error", "Invalid or missing Query Info");
+ }
+
+ job.updating = true;
+ try {
+ JSONObject optionObj = ParsingUtilities.evaluateJsonStringToObject(
+ request.getParameter("options"));
+
+ List exceptions = new LinkedList();
+
+ job.prepareNewProject();
+
+ parsePreview(
+ databaseQueryInfo,
+ job.project,
+ job.metadata,
+ job,
+ DEFAULT_PREVIEW_LIMIT ,
+ optionObj,
+ exceptions
+ );
+// String exStr = getExceptionString(exceptions);
+// logger.info("exceptions::" + exStr);
+
+ Writer w = response.getWriter();
+ JSONWriter writer = new JSONWriter(w);
+ try {
+ writer.object();
+ if (exceptions.size() == 0) {
+ job.project.update(); // update all internal models, indexes, caches, etc.
+ writer.key("status");
+ writer.value("ok");
+ } else {
+ writer.key("status");
+ writer.value("error");
+ writer.key("message");
+ writer.value(getExceptionString(exceptions));
+// writer.array();
+// writeErrors(writer, exceptions);
+// writer.endArray();
+ }
+ writer.endObject();
+ } catch (JSONException e) {
+ throw new ServletException(e);
+ } finally {
+ w.flush();
+ w.close();
+ }
+
+ } catch (JSONException e) {
+ throw new ServletException(e);
+ } finally {
+ job.touch();
+ job.updating = false;
+ }
+ }
+
+
+
+ private String getExceptionString(List exceptions) {
+ String ex = "";
+ for(Exception e: exceptions) {
+ ex = ex + e.getLocalizedMessage() + "\n";
+ }
+ // TODO Auto-generated method stub
+ return ex;
+ }
+
+ /**
+ *
+ * @param dbQueryInfo
+ * @param project
+ * @param metadata
+ * @param job
+ * @param limit
+ * @param options
+ * @param exceptions
+ * @throws DatabaseServiceException
+ */
+ private static void parsePreview(
+ DatabaseQueryInfo dbQueryInfo,
+ Project project,
+ ProjectMetadata metadata,
+ final ImportingJob job,
+ int limit,
+ JSONObject options,
+ List exceptions) throws DatabaseServiceException{
+
+
+ DatabaseService databaseService = DatabaseService.get(dbQueryInfo.getDbConfig().getDatabaseType());
+ String querySource = getQuerySource(dbQueryInfo);
+
+ List columns = databaseService.getColumns(dbQueryInfo.getDbConfig(), dbQueryInfo.getQuery());
+
+
+ setProgress(job, querySource, -1);
+
+ JSONUtilities.safePut(options, "ignoreLines", 0); // number of blank lines at the beginning to ignore
+ JSONUtilities.safePut(options, "headerLines", 1); // number of header lines
+
+
+ TabularImportingParserBase.readTable(
+ project,
+ metadata,
+ job,
+ new DBQueryResultPreviewReader(job, databaseService, querySource, columns, dbQueryInfo, 100),
+ querySource,
+ limit,
+ options,
+ exceptions
+ );
+
+ setProgress(job, querySource, 100);
+
+ }
+
+
+ /**
+ * doCreateProject
+ * @param request
+ * @param response
+ * @param parameters
+ */
+ private void doCreateProject(HttpServletRequest request, HttpServletResponse response, Properties parameters)
+ throws ServletException, IOException{
+ if(logger.isDebugEnabled()) {
+ logger.debug("DatabaseImportController::doCreateProject:::{}", parameters.getProperty("jobID"));
+ }
+
+ long jobID = Long.parseLong(parameters.getProperty("jobID"));
+ final ImportingJob job = ImportingManager.getJob(jobID);
+ if (job == null) {
+ HttpUtilities.respond(response, "error", "No such import job");
+ return;
+ }
+
+ final DatabaseQueryInfo databaseQueryInfo = getQueryInfo(request);
+ if(databaseQueryInfo == null) {
+ HttpUtilities.respond(response, "error", "Invalid or missing Query Info");
+ }
+
+ job.updating = true;
+ try {
+ final JSONObject optionObj = ParsingUtilities.evaluateJsonStringToObject(
+ request.getParameter("options"));
+
+ final List exceptions = new LinkedList();
+
+ job.setState("creating-project");
+
+ final Project project = new Project();
+
+ new Thread() {
+ @Override
+ public void run() {
+ ProjectMetadata pm = new ProjectMetadata();
+ pm.setName(JSONUtilities.getString(optionObj, "projectName", "Untitled"));
+ pm.setEncoding(JSONUtilities.getString(optionObj, "encoding", "UTF-8"));
+
+ try {
+ parseCreate(
+ databaseQueryInfo,
+ project,
+ pm,
+ job,
+ -1,
+ optionObj,
+ exceptions
+ );
+ } catch (DatabaseServiceException e) {
+ logger.error("DatabaseImportController::doCreateProject:::run{}", e);
+ // throw new RuntimeException("DatabaseServiceException::", e);
+ }
+
+ if (!job.canceled) {
+ if (exceptions.size() > 0) {
+ job.setError(exceptions);
+ } else {
+ project.update(); // update all internal models, indexes, caches, etc.
+ ProjectManager.singleton.registerProject(project, pm);
+ job.setState("created-project");
+ job.setProjectID(project.id);
+ // logger.info("DatabaseImportController::doCreateProject:::run::projectID :{}", project.id);
+ }
+
+ job.touch();
+ job.updating = false;
+ }
+ }
+ }.start();
+
+ HttpUtilities.respond(response, "ok", "done");
+ } catch (JSONException e) {
+ throw new ServletException(e);
+ }
+ }
+
+
+ /**
+ * @param dbQueryInfo
+ * @param project
+ * @param metadata
+ * @param job
+ * @param limit
+ * @param options
+ * @param exceptions
+ * @throws DatabaseServiceException
+ */
+ private static void parseCreate(
+ DatabaseQueryInfo dbQueryInfo,
+ Project project,
+ ProjectMetadata metadata,
+ final ImportingJob job,
+ int limit,
+ JSONObject options,
+ List exceptions) throws DatabaseServiceException{
+
+
+ DatabaseService databaseService = DatabaseService.get(dbQueryInfo.getDbConfig().getDatabaseType());
+ String querySource = getQuerySource(dbQueryInfo);
+
+ List columns = databaseService.getColumns(dbQueryInfo.getDbConfig(), dbQueryInfo.getQuery());
+
+ setProgress(job, querySource, -1);
+
+ JSONUtilities.safePut(options, "ignoreLines", 0); // number of blank lines at the beginning to ignore
+ JSONUtilities.safePut(options, "headerLines", 1); // number of header lines
+
+ long startTime = System.currentTimeMillis() ;
+
+ TabularImportingParserBase.readTable(
+ project,
+ metadata,
+ job,
+ new DBQueryResultImportReader(job, databaseService, querySource, columns, dbQueryInfo, getCreateBatchSize()),
+ querySource,
+ limit,
+ options,
+ exceptions
+ );
+
+ long endTime = System.currentTimeMillis() ;
+ if(logger.isDebugEnabled()) {
+ logger.debug("Execution Time: {}", endTime - startTime);
+ }
+
+ setProgress(job, querySource, 100);
+
+ }
+
+ private static int getCreateBatchSize() {
+ String propBatchSize = DatabaseModuleImpl.getImportCreateBatchSize();
+ int batchSize = 100;
+ if(propBatchSize != null && !propBatchSize.isEmpty()) {
+ try {
+ batchSize = Integer.parseInt(propBatchSize);
+ }catch(NumberFormatException nfe) {
+
+ }
+ }
+ return batchSize;
+ }
+
+ /**
+ * @param request
+ * @return
+ */
+ private DatabaseQueryInfo getQueryInfo(HttpServletRequest request) {
+ DatabaseConfiguration jdbcConfig = new DatabaseConfiguration();
+ jdbcConfig.setConnectionName(request.getParameter("connectionName"));
+ jdbcConfig.setDatabaseType(request.getParameter("databaseType"));
+ jdbcConfig.setDatabaseHost(request.getParameter("databaseServer"));
+ jdbcConfig.setDatabasePort(Integer.parseInt(request.getParameter("databasePort")));
+ jdbcConfig.setDatabaseUser(request.getParameter("databaseUser"));
+ jdbcConfig.setDatabasePassword(request.getParameter("databasePassword"));
+ jdbcConfig.setDatabaseName(request.getParameter("initialDatabase"));
+ jdbcConfig.setDatabaseSchema(request.getParameter("initialSchema"));
+
+ String query = request.getParameter("query");
+ if(logger.isDebugEnabled()) {
+ logger.debug("jdbcConfig::{}, query::{}", jdbcConfig, query);
+ }
+ if (jdbcConfig.getDatabaseHost() == null || jdbcConfig.getDatabaseName() == null
+ || jdbcConfig.getDatabasePassword() == null || jdbcConfig.getDatabaseType() == null
+ || jdbcConfig.getDatabaseUser() == null || query == null) {
+ if(logger.isDebugEnabled()) {
+ logger.debug("Missing Database Configuration::{}", jdbcConfig);
+ }
+ return null;
+ }
+
+ return new DatabaseQueryInfo(jdbcConfig, query);
+ }
+
+
+ private static String getQuerySource(DatabaseQueryInfo dbQueryInfo) {
+ String dbType = dbQueryInfo.getDbConfig().getDatabaseType();
+ return DatabaseService.get(dbType).getDatabaseUrl(dbQueryInfo.getDbConfig());
+ }
+
+
+ private static void setProgress(ImportingJob job, String querySource, int percent) {
+ job.setProgress(percent, "Reading " + querySource);
+ }
+}
diff --git a/extensions/database/src/com/google/refine/extension/database/DatabaseModuleImpl.java b/extensions/database/src/com/google/refine/extension/database/DatabaseModuleImpl.java
new file mode 100644
index 000000000..45bb6aff7
--- /dev/null
+++ b/extensions/database/src/com/google/refine/extension/database/DatabaseModuleImpl.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.refine.extension.database;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.Properties;
+
+import javax.servlet.ServletConfig;
+
+import org.json.JSONException;
+import org.json.JSONWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.refine.Jsonizable;
+
+import edu.mit.simile.butterfly.ButterflyModuleImpl;
+
+
+public class DatabaseModuleImpl extends ButterflyModuleImpl implements Jsonizable {
+
+ private static final Logger logger = LoggerFactory.getLogger("DatabaseModuleImpl");
+
+ public static DatabaseModuleImpl instance;
+
+ public static Properties extensionProperties;
+
+ private static String DEFAULT_CREATE_PROJ_BATCH_SIZE = "100";
+ private static String DEFAULT_PREVIEW_BATCH_SIZE = "100";
+
+
+
+ @Override
+ public void init(ServletConfig config)
+ throws Exception {
+ // TODO Auto-generated method stub
+ super.init(config);
+
+
+ readModuleProperty();
+
+ // Set the singleton.
+ instance = this;
+
+ logger.info("*** Database Extension Module Initialization Completed!!***");
+ }
+
+ public static String getImportCreateBatchSize() {
+ if(extensionProperties == null) {
+ return DEFAULT_CREATE_PROJ_BATCH_SIZE;
+ }
+ return extensionProperties.getProperty("create.batchSize", DEFAULT_CREATE_PROJ_BATCH_SIZE);
+ }
+
+ public static String getImportPreviewBatchSize() {
+ if(extensionProperties == null) {
+ return DEFAULT_PREVIEW_BATCH_SIZE;
+ }
+ return extensionProperties.getProperty("preview.batchSize", DEFAULT_PREVIEW_BATCH_SIZE);
+ }
+
+ private void readModuleProperty() {
+ // The module path
+ File f = getPath();
+ if(logger.isDebugEnabled()) {
+ logger.debug("Module getPath(): {}", f.getPath());
+ }
+
+ // Load our custom properties.
+ File modFile = new File(f,"MOD-INF");
+ if(logger.isDebugEnabled()) {
+ logger.debug("Module File: {}", modFile.getPath());
+ }
+
+ if (modFile.exists()) {
+
+ extensionProperties = loadProperties (new File(modFile,"dbextension.properties"));
+
+ }
+
+ }
+
+ private Properties loadProperties(File propFile) {
+ Properties ps = new Properties();
+ try {
+ if (propFile.exists()) {
+ if(logger.isDebugEnabled()) {
+ logger.debug("Loading Extension properties ({})", propFile);
+ }
+ BufferedInputStream stream = null;
+ try {
+ ps = new Properties();
+ stream = new BufferedInputStream(new FileInputStream(propFile));
+ ps.load(stream);
+
+ } finally {
+ // Close the stream.
+ if (stream != null) stream.close();
+ }
+
+ }
+ } catch (Exception e) {
+ logger.error("Error loading Database properties", e);
+ }
+ return ps;
+ }
+
+ @Override
+ public void write(JSONWriter writer, Properties options)
+ throws JSONException {
+ // TODO Auto-generated method stub
+
+ }
+
+
+}
diff --git a/extensions/database/src/com/google/refine/extension/database/DatabaseService.java b/extensions/database/src/com/google/refine/extension/database/DatabaseService.java
new file mode 100644
index 000000000..13668a98f
--- /dev/null
+++ b/extensions/database/src/com/google/refine/extension/database/DatabaseService.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.refine.extension.database;
+
+import java.sql.Connection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.refine.extension.database.mariadb.MariaDBDatabaseService;
+import com.google.refine.extension.database.model.DatabaseColumn;
+import com.google.refine.extension.database.model.DatabaseInfo;
+import com.google.refine.extension.database.model.DatabaseRow;
+import com.google.refine.extension.database.mysql.MySQLDatabaseService;
+import com.google.refine.extension.database.pgsql.PgSQLDatabaseService;
+
+public abstract class DatabaseService {
+
+ private static final Logger logger = LoggerFactory.getLogger("DatabaseService");
+
+
+ public static class DBType {
+ private static Map databaseServiceMap = new HashMap();
+
+ static {
+ try {
+
+ DatabaseService.DBType.registerDatabase(MySQLDatabaseService.DB_NAME, MySQLDatabaseService.getInstance());
+ DatabaseService.DBType.registerDatabase(PgSQLDatabaseService.DB_NAME, PgSQLDatabaseService.getInstance());
+ DatabaseService.DBType.registerDatabase(MariaDBDatabaseService.DB_NAME, MariaDBDatabaseService.getInstance());
+
+ } catch (Exception e) {
+ logger.error("Exception occurred while trying to prepare databases!", e);
+ }
+ }
+
+ public static void registerDatabase(String name, DatabaseService db) {
+
+ if (!databaseServiceMap.containsKey(name)) {
+ //throw new DatabaseServiceException(name + " cannot be registered. Database Type already exists");
+ databaseServiceMap.put(name, db);
+ logger.info(String.format("Registered %s Database", name));
+ }else {
+ if(logger.isDebugEnabled()) {
+ logger.debug(name + " Database Type already exists");
+ }
+
+ }
+
+ }
+
+ public static DatabaseService getJdbcServiceFromType(String name) {
+ return databaseServiceMap.get(name);
+ }
+
+ }
+
+ protected String getDatabaseUrl(DatabaseConfiguration dbConfig) {
+ int port = dbConfig.getDatabasePort();
+ return "jdbc:" + dbConfig.getDatabaseType() + "://" + dbConfig.getDatabaseHost()
+ + ((port == 0) ? "" : (":" + port)) + "/" + dbConfig.getDatabaseName();
+ }
+
+ /**
+ * get Database
+ * @param dbType
+ * @return
+ */
+ public static DatabaseService get(String dbType) {
+ logger.debug("get called on DatabaseService with, {}", dbType);
+ DatabaseService databaseService = DatabaseService.DBType.getJdbcServiceFromType(dbType.toLowerCase());
+
+ logger.debug("DatabaseService found: {}", databaseService.getClass());
+ return databaseService;
+
+ }
+
+
+ //Database Service APIs
+ public abstract Connection getConnection(DatabaseConfiguration dbConfig) throws DatabaseServiceException;
+
+ public abstract boolean testConnection(DatabaseConfiguration dbConfig) throws DatabaseServiceException;
+
+ public abstract DatabaseInfo connect(DatabaseConfiguration dbConfig) throws DatabaseServiceException;
+
+ public abstract DatabaseInfo executeQuery(DatabaseConfiguration dbConfig, String query) throws DatabaseServiceException;
+
+ public abstract DatabaseInfo testQuery(DatabaseConfiguration dbConfig, String query) throws DatabaseServiceException;
+
+ public abstract String buildLimitQuery(Integer limit, Integer offset, String query);
+
+ public abstract List getColumns(DatabaseConfiguration dbConfig, String query) throws DatabaseServiceException;
+
+ public abstract List getRows(DatabaseConfiguration dbConfig, String query) throws DatabaseServiceException;
+
+}
diff --git a/extensions/database/src/com/google/refine/extension/database/DatabaseServiceException.java b/extensions/database/src/com/google/refine/extension/database/DatabaseServiceException.java
new file mode 100644
index 000000000..cb37f9040
--- /dev/null
+++ b/extensions/database/src/com/google/refine/extension/database/DatabaseServiceException.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.refine.extension.database;
+
+import java.sql.SQLException;
+
+public class DatabaseServiceException extends Exception {
+
+
+ private static final long serialVersionUID = 1L;
+
+ private boolean sqlException;
+ private String sqlState;
+ private int sqlCode;
+
+
+ public DatabaseServiceException(String exception) {
+ super(exception);
+ }
+
+
+ public DatabaseServiceException(boolean sqlException, String sqlState, int sqlCode, String message) {
+ super(message);
+ this.sqlException = sqlException;
+ this.sqlState = sqlState;
+ this.sqlCode = sqlCode;
+
+ }
+
+
+ public boolean isSqlException() {
+ return sqlException;
+ }
+
+
+ public void setSqlException(boolean sqlException) {
+ this.sqlException = sqlException;
+ }
+
+
+ public String getSqlState() {
+ return sqlState;
+ }
+
+
+ public void setSqlState(String sqlState) {
+ this.sqlState = sqlState;
+ }
+
+
+ public int getSqlCode() {
+ return sqlCode;
+ }
+
+
+ public void setSqlCode(int sqlCode) {
+ this.sqlCode = sqlCode;
+ }
+
+ public DatabaseServiceException(String string, SQLException e) {
+ super(string, e);
+ }
+
+}
diff --git a/extensions/database/src/com/google/refine/extension/database/DatabaseUtils.java b/extensions/database/src/com/google/refine/extension/database/DatabaseUtils.java
new file mode 100644
index 000000000..db6960581
--- /dev/null
+++ b/extensions/database/src/com/google/refine/extension/database/DatabaseUtils.java
@@ -0,0 +1,389 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.refine.extension.database;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+
+import org.codehaus.jackson.JsonGenerationException;
+import org.codehaus.jackson.JsonParseException;
+import org.codehaus.jackson.map.JsonMappingException;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.refine.ProjectManager;
+import com.google.refine.io.FileProjectManager;
+
+public class DatabaseUtils {
+
+ private static final Logger logger = LoggerFactory.getLogger("DatabaseUtils");
+
+
+ public final static String DATABASE_EXTENSION_DIR = "dbextension";
+ public final static String SETTINGS_FILE_NAME = ".saved-db-connections.json";
+ public final static String SAVED_CONNECTION_KEY = "savedConnections";
+
+ private static SimpleTextEncryptor textEncryptor = new SimpleTextEncryptor("Aa1Gb@tY7_Y");
+
+
+ public static int getSavedConnectionsSize() {
+ List scList = getSavedConnections();
+ if(scList == null || scList.isEmpty()) {
+ return 0;
+ }
+
+ return scList.size();
+ }
+ /**
+ * GET saved connections
+ * @return
+ */
+ public static List getSavedConnections() {
+ ObjectMapper mapper = new ObjectMapper();
+ try {
+ String filename = getExtensionFilePath();
+
+ File file = new File(filename);
+ if (!file.exists()) {
+ //logger.debug("saved connections file not found, creating new: {}", filename);
+
+ String dirPath = getExtensionFolder();
+ File dirFile = new File(dirPath);
+ boolean dirExists = true;
+ if(!dirFile.exists()) {
+ dirExists = dirFile.mkdir();
+ }
+
+ if(dirExists) {
+
+ SavedConnectionContainer sc = new SavedConnectionContainer(new ArrayList());
+ mapper.writerWithDefaultPrettyPrinter().writeValue(new File(filename), sc);
+ return sc.getSavedConnections();
+ //return decryptAll(sc.getSavedConnections());
+
+ }
+
+ }
+ //logger.debug("saved connections file found {}", filename);
+ SavedConnectionContainer savedConnectionContainer = mapper.readValue(new File(filename), SavedConnectionContainer.class);
+ //return decryptAll(savedConnectionContainer.getSavedConnections());
+ return savedConnectionContainer.getSavedConnections();
+
+ } catch (JsonParseException e) {
+ logger.error("JsonParseException: {}", e);
+ } catch (JsonMappingException e) {
+ logger.error("JsonMappingException: {}", e);
+ } catch (IOException e) {
+ logger.error("IOException: {}", e);
+ }
+ return null;
+ }
+
+
+
+
+
+ /**
+ * GET one saved connection
+ * @param connectionName
+ * @return
+ */
+ public static DatabaseConfiguration getSavedConnection(String connectionName) {
+ //logger.debug("get saved connection called with connectionName: {}", connectionName);
+ List savedConfigurations = getSavedConnections();
+
+ for (DatabaseConfiguration dc : savedConfigurations) {
+ //logger.debug("Saved Connection : {}", dc.getConnectionName());
+ if (dc.getConnectionName().equalsIgnoreCase(connectionName.trim())) {
+ //logger.debug("Saved Connection Found : {}", dc);
+ //dc.setDatabasePassword(decrypt(dc.getDatabasePassword()));
+ return dc;
+ }
+ }
+
+ return null;
+ }
+
+ public static String encrypt(String plainPassword) {
+ return textEncryptor.encrypt(plainPassword);
+ }
+
+ public static String decrypt(String encodedPassword) {
+ return textEncryptor.decrypt(encodedPassword);
+ }
+
+ public static List decryptAll(List savedConnections) {
+ List dbConfigs = new ArrayList(savedConnections.size());
+
+ for(DatabaseConfiguration d: savedConnections) {
+ d.setDatabasePassword(decrypt(d.getDatabasePassword()));
+ dbConfigs.add(d);
+
+ }
+ return dbConfigs;
+ }
+
+
+ /**
+ * ADD to saved connections
+ * @param dbConfig
+ */
+ public static void addToSavedConnections(DatabaseConfiguration dbConfig){
+
+ try {
+ ObjectMapper mapper = new ObjectMapper();
+ String savedConnectionFile = getExtensionFilePath();
+ SavedConnectionContainer savedConnectionContainer = mapper.readValue(new File(savedConnectionFile), SavedConnectionContainer.class);
+ savedConnectionContainer.getSavedConnections().add(dbConfig);
+
+ mapper.writerWithDefaultPrettyPrinter().writeValue(new File(savedConnectionFile), savedConnectionContainer);
+
+ } catch (JsonGenerationException e1) {
+ logger.error("JsonGenerationException: {}", e1);
+ // e1.printStackTrace();
+ } catch (JsonMappingException e1) {
+ logger.error("JsonMappingException: {}", e1);
+ // e1.printStackTrace();
+ } catch (IOException e1) {
+ logger.error("IOException: {}", e1);
+ // e1.printStackTrace();
+ }
+ }
+
+
+ public static void deleteAllSavedConnections() {
+ if(logger.isDebugEnabled()) {
+ logger.debug("delete All Saved Connections called...");
+ }
+
+ try {
+
+ List savedConnections = getSavedConnections();
+ if(logger.isDebugEnabled()) {
+ logger.debug("Size before delete SavedConnections :: {}", savedConnections.size());
+ }
+
+ ArrayList newSavedConns = new ArrayList();
+
+ ObjectMapper mapper = new ObjectMapper();
+ String savedConnectionFile = getExtensionFilePath();
+ SavedConnectionContainer savedConnectionContainer = mapper.readValue(new File(savedConnectionFile), SavedConnectionContainer.class);
+ savedConnectionContainer.setSavedConnections(newSavedConns);
+
+ if(logger.isDebugEnabled()) {
+ logger.debug("Size after delete SavedConnections :: {}", savedConnectionContainer.getSavedConnections().size());
+ }
+ mapper.writerWithDefaultPrettyPrinter().writeValue(new File(savedConnectionFile), savedConnectionContainer);
+
+ } catch (JsonGenerationException e1) {
+ logger.error("JsonGenerationException: {}", e1);
+ // e1.printStackTrace();
+ } catch (JsonMappingException e1) {
+ logger.error("JsonMappingException: {}", e1);
+ // e1.printStackTrace();
+ } catch (IOException e1) {
+ logger.error("IOException: {}", e1);
+ // e1.printStackTrace();
+ }
+
+ }
+
+ /**
+ * DELETE saved connections
+ * @param connectionName
+ */
+ public static void deleteSavedConnections(String connectionName) {
+ if(logger.isDebugEnabled()) {
+ logger.debug("deleteSavedConnections called with: {}", connectionName);
+ }
+
+ try {
+
+ List savedConnections = getSavedConnections();;
+ if(logger.isDebugEnabled()) {
+ logger.debug("Size before delete SavedConnections :: {}", savedConnections.size());
+ }
+
+ ArrayList newSavedConns = new ArrayList();
+ for(DatabaseConfiguration dc: savedConnections) {
+ if(!dc.getConnectionName().equalsIgnoreCase(connectionName.trim())) {
+ newSavedConns.add(dc);
+ }
+
+ }
+
+ ObjectMapper mapper = new ObjectMapper();
+ String savedConnectionFile = getExtensionFilePath();
+ SavedConnectionContainer savedConnectionContainer = mapper.readValue(new File(savedConnectionFile), SavedConnectionContainer.class);
+ savedConnectionContainer.setSavedConnections(newSavedConns);
+
+ if(logger.isDebugEnabled()) {
+ logger.debug("Size after delete SavedConnections :: {}", savedConnectionContainer.getSavedConnections().size());
+ }
+ mapper.writerWithDefaultPrettyPrinter().writeValue(new File(savedConnectionFile), savedConnectionContainer);
+
+ } catch (JsonGenerationException e1) {
+ logger.error("JsonGenerationException: {}", e1);
+ // e1.printStackTrace();
+ } catch (JsonMappingException e1) {
+ logger.error("JsonMappingException: {}", e1);
+ // e1.printStackTrace();
+ } catch (IOException e1) {
+ logger.error("IOException: {}", e1);
+ // e1.printStackTrace();
+ }
+ }
+
+ /**
+ * EDIT saved connections
+ * @param jdbcConfig
+ */
+ public static void editSavedConnections(DatabaseConfiguration jdbcConfig) {
+ if(logger.isDebugEnabled()) {
+ logger.debug("Edit SavedConnections called with: {}", jdbcConfig);
+ }
+
+
+ try {
+ ObjectMapper mapper = new ObjectMapper();
+ String savedConnectionFile = getExtensionFilePath();
+ SavedConnectionContainer savedConnectionContainer = mapper.readValue(new File(savedConnectionFile), SavedConnectionContainer.class);
+
+ List savedConnections = savedConnectionContainer.getSavedConnections();
+
+ ListIterator savedConnArrayIter = (ListIterator) savedConnections.listIterator();
+
+ while (savedConnArrayIter.hasNext()) {
+ DatabaseConfiguration sc = (DatabaseConfiguration) savedConnArrayIter.next();
+
+ if (sc.getConnectionName().equals(jdbcConfig.getConnectionName())) {
+ savedConnArrayIter.remove();
+ }
+
+ }
+
+ savedConnections.add(jdbcConfig);
+ savedConnectionContainer.setSavedConnections(savedConnections);
+
+ mapper.writerWithDefaultPrettyPrinter().writeValue(new File(savedConnectionFile), savedConnectionContainer);
+
+ } catch (JsonGenerationException e1) {
+ logger.error("JsonGenerationException: {}", e1);
+ e1.printStackTrace();
+ } catch (JsonMappingException e1) {
+ logger.error("JsonMappingException: {}", e1);
+ e1.printStackTrace();
+ } catch (IOException e1) {
+ logger.error("IOException: {}", e1);
+ e1.printStackTrace();
+ }
+ }
+
+ public static String getExtensionFilePath(){
+ File dir = ((FileProjectManager) ProjectManager.singleton).getWorkspaceDir();
+ String fileSep = System.getProperty("file.separator");
+ String filename = dir.getPath() + fileSep + DATABASE_EXTENSION_DIR + fileSep + SETTINGS_FILE_NAME;
+
+ // logger.info("** extension file name: {} **", filename);
+ return filename;
+ }
+ public static String getExtensionFolder(){
+ File dir = ((FileProjectManager) ProjectManager.singleton).getWorkspaceDir();
+ String fileSep = System.getProperty("file.separator");
+ String filename = dir.getPath() + fileSep + DATABASE_EXTENSION_DIR;
+ return filename;
+ }
+
+ public static DatabaseColumnType getDbColumnType(int dbColumnType) {
+
+ switch (dbColumnType) {
+ case java.sql.Types.BIGINT:
+ return DatabaseColumnType.NUMBER;
+ case java.sql.Types.FLOAT:
+ return DatabaseColumnType.FLOAT;
+ case java.sql.Types.REAL:
+ return DatabaseColumnType.DOUBLE;
+ case java.sql.Types.DOUBLE:
+ return DatabaseColumnType.DOUBLE;
+ case java.sql.Types.NUMERIC:
+ return DatabaseColumnType.NUMBER;
+ case java.sql.Types.DECIMAL:
+ return DatabaseColumnType.STRING;
+ case java.sql.Types.CHAR:
+ return DatabaseColumnType.STRING;
+ case java.sql.Types.VARCHAR:
+ return DatabaseColumnType.STRING;
+ case java.sql.Types.LONGVARCHAR:
+ return DatabaseColumnType.STRING;
+ case java.sql.Types.DATE:
+ return DatabaseColumnType.DATE;
+ case java.sql.Types.TIME:
+ return DatabaseColumnType.DATETIME;
+ case java.sql.Types.TIMESTAMP:
+ return DatabaseColumnType.DATETIME;
+ case java.sql.Types.BINARY:
+ return DatabaseColumnType.STRING;
+ case java.sql.Types.VARBINARY:
+ return DatabaseColumnType.STRING;
+ case java.sql.Types.LONGVARBINARY:
+ return DatabaseColumnType.STRING;
+ case java.sql.Types.NULL:
+ return DatabaseColumnType.STRING;
+ case java.sql.Types.OTHER:
+ return DatabaseColumnType.STRING;
+ case java.sql.Types.JAVA_OBJECT:
+ return DatabaseColumnType.STRING;
+ case java.sql.Types.DISTINCT:
+ return DatabaseColumnType.STRING;
+ case java.sql.Types.STRUCT:
+ return DatabaseColumnType.STRING;
+ case java.sql.Types.ARRAY:
+ return DatabaseColumnType.STRING;
+ case java.sql.Types.BLOB:
+ return DatabaseColumnType.STRING;
+ case java.sql.Types.CLOB:
+ return DatabaseColumnType.STRING;
+ case java.sql.Types.REF:
+ return DatabaseColumnType.STRING;
+ case java.sql.Types.BOOLEAN:
+ return DatabaseColumnType.BOOLEAN;
+ case java.sql.Types.INTEGER:
+ return DatabaseColumnType.NUMBER;
+
+ default:
+ return DatabaseColumnType.STRING;
+ }
+
+ }
+
+}
diff --git a/extensions/database/src/com/google/refine/extension/database/SQLType.java b/extensions/database/src/com/google/refine/extension/database/SQLType.java
new file mode 100644
index 000000000..2871b6abd
--- /dev/null
+++ b/extensions/database/src/com/google/refine/extension/database/SQLType.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.refine.extension.database;
+
+
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+public final class SQLType {
+
+ private static final Map jdbcDriverRegistry = new HashMap();
+ private final DriverContainer driverContainer;
+
+ private SQLType(DriverContainer container) {
+ this.driverContainer = container;
+ }
+
+ public static SQLType forName(String name) {
+ for (SQLType sqlType : jdbcDriverRegistry.values()) {
+ if (sqlType.getIdentifier().equalsIgnoreCase(name)) {
+ return sqlType;
+ }
+ }
+ return null;
+ }
+
+ public static SQLType registerSQLDriver(String identifier, String classpath) {
+ return registerSQLDriver(identifier, classpath, true);
+ }
+
+ public static SQLType registerSQLDriver(String identifier, String classpath, boolean useJDBCManager) {
+ DriverContainer driverContainer = new DriverContainer(identifier, classpath, useJDBCManager);
+ if (!jdbcDriverRegistry.containsKey(driverContainer)) {
+ SQLType newType = new SQLType(driverContainer);
+ jdbcDriverRegistry.put(driverContainer, newType);
+ return newType;
+ }
+ return null;
+ }
+
+
+ public String getClassPath() {
+ return this.driverContainer.classpath;
+ }
+
+ public String getIdentifier() {
+ return this.driverContainer.identifier;
+ }
+
+ public boolean usesJDBCManager() {
+ return this.driverContainer.useJDBCManager;
+ }
+
+
+ private static class DriverContainer {
+
+ public final String classpath;
+ public final String identifier;
+ public final boolean useJDBCManager;
+
+ private DriverContainer(String identifier, String classpath, boolean useJDBCManager) {
+ this.classpath = classpath;
+ this.identifier = identifier;
+ this.useJDBCManager = useJDBCManager;
+ }
+
+ public final boolean equals(Object obj) {
+ return obj instanceof DriverContainer && ((DriverContainer) obj).classpath.equals(this.classpath)
+ && ((DriverContainer) obj).identifier.equals(this.identifier)
+ && ((DriverContainer) obj).useJDBCManager == this.useJDBCManager;
+ }
+ }
+}
diff --git a/extensions/database/src/com/google/refine/extension/database/SavedConnectionContainer.java b/extensions/database/src/com/google/refine/extension/database/SavedConnectionContainer.java
new file mode 100644
index 000000000..15a45f731
--- /dev/null
+++ b/extensions/database/src/com/google/refine/extension/database/SavedConnectionContainer.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.refine.extension.database;
+
+import java.util.List;
+
+public class SavedConnectionContainer {
+ private List savedConnections;
+
+ public List getSavedConnections() {
+ return savedConnections;
+ }
+
+
+ public void setSavedConnections(List savedConnections) {
+ this.savedConnections = savedConnections;
+ }
+
+
+
+ public SavedConnectionContainer(List savedConnections) {
+ super();
+ this.savedConnections = savedConnections;
+ }
+
+
+ public SavedConnectionContainer() {
+
+ }
+
+
+}
diff --git a/extensions/database/src/com/google/refine/extension/database/SimpleTextEncryptor.java b/extensions/database/src/com/google/refine/extension/database/SimpleTextEncryptor.java
new file mode 100644
index 000000000..ad5f20d95
--- /dev/null
+++ b/extensions/database/src/com/google/refine/extension/database/SimpleTextEncryptor.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.refine.extension.database;
+
+import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
+import org.jasypt.util.text.TextEncryptor;
+
+
+public class SimpleTextEncryptor implements TextEncryptor {
+
+ private final StandardPBEStringEncryptor encryptor;
+
+ @Override
+ public String decrypt(String encryptedMessage) {
+ return this.encryptor.decrypt(encryptedMessage);
+ }
+
+ @Override
+ public String encrypt(String message) {
+ return this.encryptor.encrypt(message);
+ }
+
+ public SimpleTextEncryptor(String passwordChars) {
+ super();
+
+ this.encryptor = new StandardPBEStringEncryptor();
+ this.encryptor.setAlgorithm("PBEWithMD5AndDES");
+ this.encryptor.setPasswordCharArray(passwordChars.toCharArray());
+ }
+
+
+
+ public void setPassword(final String password) {
+ this.encryptor.setPassword(password);
+ }
+
+
+ /**
+ * Sets a password, as a char[]
+ *
+ * @since 1.8
+ * @param password the password to be set.
+ */
+ public void setPasswordCharArray(final char[] password) {
+ this.encryptor.setPasswordCharArray(password);
+ }
+
+
+
+}
diff --git a/extensions/database/src/com/google/refine/extension/database/cmd/ConnectCommand.java b/extensions/database/src/com/google/refine/extension/database/cmd/ConnectCommand.java
new file mode 100644
index 000000000..014b30930
--- /dev/null
+++ b/extensions/database/src/com/google/refine/extension/database/cmd/ConnectCommand.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.refine.extension.database.cmd;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.http.HttpStatus;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.json.JSONWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+//import com.google.refine.ProjectManager;
+import com.google.refine.extension.database.DatabaseConfiguration;
+import com.google.refine.extension.database.DatabaseService;
+import com.google.refine.extension.database.DatabaseServiceException;
+import com.google.refine.extension.database.model.DatabaseInfo;
+
+
+public class ConnectCommand extends DatabaseCommand {
+
+ private static final Logger logger = LoggerFactory.getLogger("ConnectCommand");
+
+ @Override
+ public void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+
+ DatabaseConfiguration databaseConfiguration = getJdbcConfiguration(request);
+ if(logger.isDebugEnabled()) {
+ logger.debug("ConnectCommand::Post::{}", databaseConfiguration);
+ }
+ // ProjectManager.singleton.setBusy(true);
+ try {
+
+ response.setCharacterEncoding("UTF-8");
+ response.setHeader("Content-Type", "application/json");
+ Writer w = response.getWriter();
+ JSONWriter writer = new JSONWriter(w);
+ ObjectMapper mapperObj = new ObjectMapper();
+
+ try {
+ DatabaseInfo databaseInfo = DatabaseService.get(databaseConfiguration.getDatabaseType())
+ .connect(databaseConfiguration);
+ String databaseInfoString = mapperObj.writeValueAsString(databaseInfo);
+ response.setStatus(HttpStatus.SC_OK);
+ writer.object();
+ writer.key("code");
+ writer.value("ok");
+ writer.key("databaseInfo");
+ writer.value(databaseInfoString);
+
+ writer.endObject();
+
+ } catch (DatabaseServiceException e) {
+ logger.error("ConnectCommand::Post::DatabaseServiceException::{}", e);
+ sendError(HttpStatus.SC_UNAUTHORIZED,response, writer, e);
+ }catch (Exception e) {
+ logger.error("ConnectCommand::Post::Exception::{}", e);
+ sendError(HttpStatus.SC_UNAUTHORIZED,response, writer, e);
+ } finally {
+ // w.flush();
+ w.close();
+ }
+ } catch (Exception e) {
+ logger.error("ConnectCommand::Post::Exception::{}", e);
+ throw new ServletException(e);
+ }
+// finally {
+// // ProjectManager.singleton.setBusy(false);
+// }
+
+
+ }
+
+}
diff --git a/extensions/database/src/com/google/refine/extension/database/cmd/DatabaseCommand.java b/extensions/database/src/com/google/refine/extension/database/cmd/DatabaseCommand.java
new file mode 100644
index 000000000..ccd967d95
--- /dev/null
+++ b/extensions/database/src/com/google/refine/extension/database/cmd/DatabaseCommand.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.refine.extension.database.cmd;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.json.JSONWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.refine.commands.Command;
+import com.google.refine.extension.database.DatabaseConfiguration;
+import com.google.refine.extension.database.DatabaseServiceException;
+
+public abstract class DatabaseCommand extends Command {
+
+ private static final Logger logger = LoggerFactory.getLogger("DatabaseCommand");
+
+ /**
+ *
+ * @param request
+ * @return
+ */
+ protected DatabaseConfiguration getJdbcConfiguration(HttpServletRequest request) {
+ DatabaseConfiguration jdbcConfig = new DatabaseConfiguration();
+
+ jdbcConfig.setConnectionName(request.getParameter("connectionName"));
+ jdbcConfig.setDatabaseType(request.getParameter("databaseType"));
+ jdbcConfig.setDatabaseHost(request.getParameter("databaseServer"));
+
+ String dbPort = request.getParameter("databasePort");
+ if(dbPort != null) {
+ try {
+ jdbcConfig.setDatabasePort(Integer.parseInt(dbPort));
+ }catch(NumberFormatException nfe) {}
+ }
+
+ jdbcConfig.setDatabaseUser(request.getParameter("databaseUser"));
+ jdbcConfig.setDatabasePassword(request.getParameter("databasePassword"));
+ jdbcConfig.setDatabaseName(request.getParameter("initialDatabase"));
+ jdbcConfig.setDatabaseSchema(request.getParameter("initialSchema"));
+
+ if(logger.isDebugEnabled()) {
+ logger.debug("JDBC Configuration: {}", jdbcConfig);
+ }
+ return jdbcConfig;
+ }
+ /**
+ *
+ * @param status
+ * @param response
+ * @param writer
+ * @param e
+ * @throws IOException
+ */
+ protected void sendError(int status, HttpServletResponse response, JSONWriter writer, Exception e)
+ throws IOException {
+
+ //logger.info("sendError::{}", writer);
+ response.sendError(status, e.getMessage());
+
+ }
+ /**
+ *
+ * @param status
+ * @param response
+ * @param writer
+ * @param e
+ * @throws IOException
+ */
+ protected void sendError(int status, HttpServletResponse response, JSONWriter writer, DatabaseServiceException e)
+ throws IOException {
+
+ String message = "";
+
+ if(e.getSqlState() != null) {
+
+ message = message + "SqlCode:" + e.getSqlCode() + "SqlState" + e.getSqlState();
+ }
+
+ message = message + e.getMessage();
+
+ response.sendError(status, e.getMessage());
+
+ }
+
+}
diff --git a/extensions/database/src/com/google/refine/extension/database/cmd/ExecuteQueryCommand.java b/extensions/database/src/com/google/refine/extension/database/cmd/ExecuteQueryCommand.java
new file mode 100644
index 000000000..90a0dd387
--- /dev/null
+++ b/extensions/database/src/com/google/refine/extension/database/cmd/ExecuteQueryCommand.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.refine.extension.database.cmd;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.http.HttpStatus;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.json.JSONWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+//import com.google.refine.ProjectManager;
+import com.google.refine.extension.database.DatabaseConfiguration;
+import com.google.refine.extension.database.DatabaseService;
+import com.google.refine.extension.database.DatabaseServiceException;
+import com.google.refine.extension.database.model.DatabaseInfo;
+
+
+public class ExecuteQueryCommand extends DatabaseCommand {
+
+ private static final Logger logger = LoggerFactory.getLogger("ExecuteQueryCommand");
+
+ @Override
+ public void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+
+
+ DatabaseConfiguration databaseConfiguration = getJdbcConfiguration(request);
+ String query = request.getParameter("queryString");
+ if(logger.isDebugEnabled()) {
+ logger.debug("QueryCommand::Post::DatabaseConfiguration::{}::Query::{} " ,databaseConfiguration, query);
+ }
+
+ //ProjectManager.singleton.setBusy(true);
+ try {
+
+ response.setCharacterEncoding("UTF-8");
+ response.setHeader("Content-Type", "application/json");
+ Writer w = response.getWriter();
+ JSONWriter writer = new JSONWriter(w);
+
+ try {
+ DatabaseInfo databaseInfo = DatabaseService.get(databaseConfiguration.getDatabaseType())
+ .executeQuery(databaseConfiguration, query);
+ ObjectMapper mapperObj = new ObjectMapper();
+
+ response.setStatus(HttpStatus.SC_OK);
+ String jsonStr = mapperObj.writeValueAsString(databaseInfo);
+
+ if(logger.isDebugEnabled()) {
+ logger.debug("QueryCommand::Post::Result::{} " ,jsonStr);
+ }
+
+
+ writer.object();
+ writer.key("code");
+ writer.value("ok");
+ writer.key("QueryResult");
+ writer.value(jsonStr);
+ writer.endObject();
+
+
+ } catch (DatabaseServiceException e) {
+ logger.error("QueryCommand::Post::DatabaseServiceException::{}", e);
+ sendError(HttpStatus.SC_BAD_REQUEST, response, writer, e);
+
+ } catch (Exception e) {
+ logger.error("QueryCommand::Post::Exception::{}", e);
+ sendError(HttpStatus.SC_BAD_REQUEST,response, writer, e);
+ } finally {
+ w.close();
+ }
+ } catch (Exception e) {
+ logger.error("QueryCommand::Post::Exception::{}", e);
+ throw new ServletException(e);
+ }
+// finally {
+// // ProjectManager.singleton.setBusy(false);
+// }
+
+
+ }
+
+}
diff --git a/extensions/database/src/com/google/refine/extension/database/cmd/SavedConnectionCommand.java b/extensions/database/src/com/google/refine/extension/database/cmd/SavedConnectionCommand.java
new file mode 100644
index 000000000..74b3af2ea
--- /dev/null
+++ b/extensions/database/src/com/google/refine/extension/database/cmd/SavedConnectionCommand.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.refine.extension.database.cmd;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.http.HttpStatus;
+import org.json.JSONException;
+import org.json.JSONWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.refine.extension.database.DatabaseConfiguration;
+import com.google.refine.extension.database.DatabaseUtils;
+
+
+public class SavedConnectionCommand extends DatabaseCommand {
+
+ private static final Logger logger = LoggerFactory.getLogger("SavedConnectionCommand");
+
+
+ @Override
+ public void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+
+ if(logger.isDebugEnabled()) {
+ logger.debug("SavedConnectionCommand::Get::connectionName::{}", request.getParameter("connectionName"));
+ }
+
+ String connectionName = request.getParameter("connectionName");
+ try {
+ response.setCharacterEncoding("UTF-8");
+ response.setHeader("Content-Type", "application/json");
+ if(connectionName == null || connectionName.isEmpty()) {
+ writeSavedConnectionResponse(response);
+ }else {
+
+ DatabaseConfiguration savedConnection = DatabaseUtils.getSavedConnection(connectionName);
+ writeSavedConnectionResponse(response, savedConnection);
+
+ }
+
+ } catch (Exception e) {
+ logger.error("Exception while loading settings {}", e);
+ }
+ }
+
+
+ @Override
+ public void doDelete(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+
+
+ if(logger.isDebugEnabled()) {
+ logger.debug("SavedConnectionCommand::Delete Connection: {}", request.getParameter("connectionName"));
+ }
+
+ String connectionName = request.getParameter("connectionName");
+
+ DatabaseConfiguration savedConn = DatabaseUtils.getSavedConnection(connectionName);
+ if(savedConn == null) {
+ //logger.error("Connection With name:: {} does not exist!", request.getParameter("connectionName"));
+ response.sendError(HttpStatus.SC_BAD_REQUEST, "Connection with name " + connectionName + " does not exists!");
+ response.flushBuffer();
+ return;
+ }
+
+ try {
+
+ DatabaseUtils.deleteSavedConnections(connectionName);
+ response.setCharacterEncoding("UTF-8");
+ response.setHeader("Content-Type", "application/json");
+
+ writeSavedConnectionResponse(response);
+
+ } catch (Exception e) {
+ logger.error("Exception while Deleting Connection with name: {}, error:{}",connectionName, e);
+ }
+ }
+
+ /**
+ *
+ * @param response
+ * @param savedConnection
+ * @throws IOException
+ * @throws JSONException
+ */
+ private void writeSavedConnectionResponse(HttpServletResponse response, DatabaseConfiguration savedConnection) throws IOException, JSONException {
+ Writer w = response.getWriter();
+ try {
+ JSONWriter writer = new JSONWriter(w);
+
+ writer.object();
+ writer.key(DatabaseUtils.SAVED_CONNECTION_KEY);
+ writer.array();
+
+ writer.object();
+ writer.key("connectionName");
+ writer.value(savedConnection.getConnectionName());
+
+ writer.key("databaseType");
+ writer.value(savedConnection.getDatabaseType());
+
+ writer.key("databaseHost");
+ writer.value(savedConnection.getDatabaseHost());
+
+ writer.key("databasePort");
+ writer.value(savedConnection.getDatabasePort());
+
+ writer.key("databaseName");
+ writer.value(savedConnection.getDatabaseName());
+
+ writer.key("databasePassword");
+
+ //
+ String dbPasswd = savedConnection.getDatabasePassword();
+ if(dbPasswd != null && !dbPasswd.isEmpty()) {
+ dbPasswd = DatabaseUtils.decrypt(savedConnection.getDatabasePassword());
+ //logger.info("Decrypted Password::" + dbPasswd);
+ }
+ writer.value(dbPasswd);
+ //
+
+ // writer.value(savedConnection.getDatabasePassword());
+
+ writer.key("databaseSchema");
+ writer.value(savedConnection.getDatabaseSchema());
+
+ writer.key("databaseUser");
+ writer.value(savedConnection.getDatabaseUser());
+
+ writer.endObject();
+ writer.endArray();
+
+ writer.endObject();
+
+ }finally {
+ w.flush();
+ w.close();
+ }
+
+ }
+ /**
+ *
+ * @param response
+ * @throws IOException
+ * @throws JSONException
+ */
+ private void writeSavedConnectionResponse(HttpServletResponse response) throws IOException, JSONException {
+ Writer w = response.getWriter();
+ try {
+
+ List savedConnections = DatabaseUtils.getSavedConnections();
+ JSONWriter writer = new JSONWriter(w);
+
+ writer.object();
+ writer.key(DatabaseUtils.SAVED_CONNECTION_KEY);
+ writer.array();
+
+ int size = savedConnections.size();
+
+ for (int i = 0; i < size; i++) {
+
+ writer.object();
+ DatabaseConfiguration dbConfig = (DatabaseConfiguration) savedConnections.get(i);
+
+ writer.key("connectionName");
+ writer.value(dbConfig.getConnectionName());
+
+ writer.key("databaseType");
+ writer.value(dbConfig.getDatabaseType());
+
+ writer.key("databaseHost");
+ writer.value(dbConfig.getDatabaseHost());
+
+ writer.key("databasePort");
+ writer.value(dbConfig.getDatabasePort());
+
+ writer.key("databaseName");
+ writer.value(dbConfig.getDatabaseName());
+
+ writer.key("databasePassword");
+
+ String dbPasswd = dbConfig.getDatabasePassword();
+ if(dbPasswd != null && !dbPasswd.isEmpty()) {
+ dbPasswd = DatabaseUtils.decrypt(dbConfig.getDatabasePassword());
+ }
+ // writer.value(dbConfig.getDatabasePassword());
+ writer.value(dbPasswd);
+
+ writer.key("databaseSchema");
+ writer.value(dbConfig.getDatabaseSchema());
+
+ writer.key("databaseUser");
+ writer.value(dbConfig.getDatabaseUser());
+
+ writer.endObject();
+
+ }
+ writer.endArray();
+ writer.endObject();
+ // logger.info("Saved Connection Get Response sent");
+ } finally {
+ w.flush();
+ w.close();
+ }
+ }
+
+ /**
+ * Add a new Saved JDBC connection configuration
+ */
+ @Override
+ public void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+
+ if(logger.isDebugEnabled()) {
+ logger.debug("doPost Connection: {}", request.getParameter("connectionName"));
+ }
+
+ DatabaseConfiguration jdbcConfig = getJdbcConfiguration(request);
+
+
+ response.setCharacterEncoding("UTF-8");
+ response.setHeader("Content-Type", "application/json");
+
+ if(jdbcConfig.getConnectionName() == null) {
+ response.sendError(HttpStatus.SC_BAD_REQUEST, "Connection Name is Required!");
+ response.flushBuffer();
+ return;
+ }
+
+ DatabaseConfiguration savedConn = DatabaseUtils.getSavedConnection(jdbcConfig.getConnectionName());
+ if(savedConn != null) {
+ response.sendError(HttpStatus.SC_BAD_REQUEST, "Connection with name " + jdbcConfig.getConnectionName() + " already exists!");
+ response.flushBuffer();
+ return;
+ }
+
+
+ if(jdbcConfig.getDatabasePassword() != null) {
+ //logger.debug("SavedConnectionCommand::Post::password::{}", jdbcConfig.getDatabasePassword());
+ jdbcConfig.setDatabasePassword(DatabaseUtils.encrypt(jdbcConfig.getDatabasePassword()));
+ }
+
+ DatabaseUtils.addToSavedConnections(jdbcConfig);
+
+ try {
+ response.setCharacterEncoding("UTF-8");
+ response.setHeader("Content-Type", "application/json");
+
+ writeSavedConnectionResponse(response);
+ } catch (Exception e) {
+ logger.error("Exception while loading settings {}", e);
+ }
+
+ }
+
+
+
+ @Override
+ public void doPut(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+
+
+ if(logger.isDebugEnabled()) {
+ logger.debug("databaseType::{} " , request.getParameter("databaseHost"));
+ }
+ // logger.info("databaseHost::{} " , request.getParameter("databaseServer"));
+
+ DatabaseConfiguration jdbcConfig = getJdbcConfiguration(request);
+ StringBuilder sb = new StringBuilder();
+ boolean error = false;
+ if(jdbcConfig.getConnectionName() == null) {
+ sb.append("Connection Name, ");
+ error = true;
+ }
+ if(jdbcConfig.getDatabaseHost() == null) {
+ sb.append("Database Host, ");
+ error = true;
+ }
+ if(jdbcConfig.getDatabaseUser() == null) {
+ sb.append("Database User, ");
+ error = true;
+ }
+ if(jdbcConfig.getDatabaseName() == null) {
+ sb.append("Database Name, ");
+ error = true;
+ }
+ if(error) {
+ sb.append(" is missing");
+ logger.debug("Connection Parameter errors::{}", sb.toString());
+ response.sendError(HttpStatus.SC_BAD_REQUEST, sb.toString());
+ }
+
+ if(logger.isDebugEnabled()) {
+ logger.debug("Connection Config:: {}", jdbcConfig.getConnectionName());
+ }
+
+ if(jdbcConfig.getDatabasePassword() != null) {
+ jdbcConfig.setDatabasePassword(DatabaseUtils.encrypt(jdbcConfig.getDatabasePassword()));
+ }
+
+ DatabaseUtils.editSavedConnections(jdbcConfig);
+
+ try {
+ response.setCharacterEncoding("UTF-8");
+ response.setHeader("Content-Type", "application/json");
+
+ writeSavedConnectionResponse(response);
+
+ } catch (Exception e) {
+ logger.error("Exception while loading settings {}", e);
+ }
+ }
+
+
+}
diff --git a/extensions/database/src/com/google/refine/extension/database/cmd/TestConnectCommand.java b/extensions/database/src/com/google/refine/extension/database/cmd/TestConnectCommand.java
new file mode 100644
index 000000000..b4cd0dd0b
--- /dev/null
+++ b/extensions/database/src/com/google/refine/extension/database/cmd/TestConnectCommand.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.refine.extension.database.cmd;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.http.HttpStatus;
+import org.json.JSONWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.refine.extension.database.DatabaseConfiguration;
+import com.google.refine.extension.database.DatabaseService;
+import com.google.refine.extension.database.DatabaseServiceException;
+
+
+
+public class TestConnectCommand extends DatabaseCommand {
+
+ private static final Logger logger = LoggerFactory.getLogger("TestConnectCommand");
+
+ @Override
+ public void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+
+
+ DatabaseConfiguration databaseConfiguration = getJdbcConfiguration(request);
+ if(logger.isDebugEnabled()) {
+ logger.debug("TestConnectCommand::Post::{}", databaseConfiguration);
+ }
+
+
+ //ProjectManager.singleton.setBusy(true);
+ try {
+
+
+ response.setCharacterEncoding("UTF-8");
+ response.setHeader("Content-Type", "application/json");
+
+ Writer w = response.getWriter();
+ JSONWriter writer = new JSONWriter(w);
+
+ try {
+
+ boolean connectionTestResult = DatabaseService.get(databaseConfiguration.getDatabaseType())
+ .testConnection(databaseConfiguration);
+
+ response.setStatus(HttpStatus.SC_OK);
+ writer.object();
+
+ writer.key("connectionResult");
+ writer.value(connectionTestResult);
+ writer.key("code");
+ writer.value("ok");
+ writer.endObject();
+
+ } catch (DatabaseServiceException e) {
+ logger.error("TestConnectCommand::Post::DatabaseServiceException::{}", e);
+ sendError(HttpStatus.SC_UNAUTHORIZED,response, writer, e);
+ } finally {
+ // writer.endObject();
+ // w.flush();
+ w.close();
+ }
+ } catch (Exception e) {
+ logger.error("TestConnectCommand::Post::Exception::{}", e);
+ throw new ServletException(e);
+ } finally {
+ //ProjectManager.singleton.setBusy(false);
+ }
+
+
+ }
+
+
+}
diff --git a/extensions/database/src/com/google/refine/extension/database/cmd/TestQueryCommand.java b/extensions/database/src/com/google/refine/extension/database/cmd/TestQueryCommand.java
new file mode 100644
index 000000000..a728fc1bd
--- /dev/null
+++ b/extensions/database/src/com/google/refine/extension/database/cmd/TestQueryCommand.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.refine.extension.database.cmd;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.http.HttpStatus;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.json.JSONWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+//import com.google.refine.ProjectManager;
+import com.google.refine.extension.database.DatabaseConfiguration;
+import com.google.refine.extension.database.DatabaseService;
+import com.google.refine.extension.database.DatabaseServiceException;
+import com.google.refine.extension.database.model.DatabaseInfo;
+
+
+public class TestQueryCommand extends DatabaseCommand {
+
+ private static final Logger logger = LoggerFactory.getLogger("TestQueryCommand");
+
+ @Override
+ public void doPost(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+
+ DatabaseConfiguration dbConfig = getJdbcConfiguration(request);
+ String query = request.getParameter("query");
+
+ if(logger.isDebugEnabled()) {
+ logger.debug("TestQueryCommand::Post::DatabaseConfiguration::{}::Query::{} " ,dbConfig, query);
+ }
+
+
+ //ProjectManager.singleton.setBusy(true);
+ try {
+
+ response.setCharacterEncoding("UTF-8");
+ response.setHeader("Content-Type", "application/json");
+ Writer w = response.getWriter();
+ JSONWriter writer = new JSONWriter(w);
+
+ try {
+ DatabaseInfo databaseInfo = DatabaseService.get(dbConfig.getDatabaseType())
+ .testQuery(dbConfig, query);
+ ObjectMapper mapperObj = new ObjectMapper();
+
+ response.setStatus(HttpStatus.SC_OK);
+ String jsonStr = mapperObj.writeValueAsString(databaseInfo);
+ if(logger.isDebugEnabled()) {
+ logger.debug("TestQueryCommand::Post::Result::{} " ,jsonStr);
+ }
+
+ writer.object();
+ writer.key("code");
+ writer.value("ok");
+ writer.key("QueryResult");
+ writer.value(jsonStr);
+ writer.endObject();
+
+
+ } catch (DatabaseServiceException e) {
+ logger.error("TestQueryCommand::Post::DatabaseServiceException::{}", e);
+ sendError(HttpStatus.SC_BAD_REQUEST, response, writer, e);
+
+ } catch (Exception e) {
+ logger.error("TestQueryCommand::Post::Exception::{}", e);
+ sendError(HttpStatus.SC_BAD_REQUEST,response, writer, e);
+ } finally {
+ w.close();
+ }
+ } catch (Exception e) {
+ logger.error("TestQueryCommand::Post::Exception::{}", e);
+ throw new ServletException(e);
+ }
+// finally {
+// // ProjectManager.singleton.setBusy(false);
+// }
+
+
+ }
+
+}
diff --git a/extensions/database/src/com/google/refine/extension/database/mariadb/MariaDBConnectionManager.java b/extensions/database/src/com/google/refine/extension/database/mariadb/MariaDBConnectionManager.java
new file mode 100644
index 000000000..4af014afc
--- /dev/null
+++ b/extensions/database/src/com/google/refine/extension/database/mariadb/MariaDBConnectionManager.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.refine.extension.database.mariadb;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.refine.extension.database.DatabaseConfiguration;
+import com.google.refine.extension.database.DatabaseServiceException;
+import com.google.refine.extension.database.SQLType;
+
+
+
+public class MariaDBConnectionManager {
+
+ private static final Logger logger = LoggerFactory.getLogger("MariaDBConnectionManager");
+ private Connection connection;
+ private SQLType type;
+
+ private static MariaDBConnectionManager instance;
+
+ /**
+ *
+ * @param type
+ * @param databaseConfiguration
+ * @throws SQLException
+ */
+ private MariaDBConnectionManager() {
+ type = SQLType.forName(MariaDBDatabaseService.DB_NAME);
+
+ }
+
+
+
+
+ /**
+ * Create a new instance of this connection manager.
+ *
+ * @return an instance of the manager
+ *
+ * @throws DatabaseServiceException
+ */
+ public static MariaDBConnectionManager getInstance() throws DatabaseServiceException {
+ if (instance == null) {
+ //logger.info("::Creating new MariaDB Connection Manager ::");
+ instance = new MariaDBConnectionManager();
+
+ }
+ return instance;
+ }
+
+
+ /**
+ * Get the SQL Database type.
+ *
+ * @return the type
+ */
+ public SQLType getType() {
+ return this.type;
+ }
+
+ /**
+ * testConnection
+ * @param databaseConfiguration
+ * @return
+ */
+ public boolean testConnection(DatabaseConfiguration databaseConfiguration) throws DatabaseServiceException{
+
+ try {
+ boolean connResult = false;
+
+ Connection conn = getConnection(databaseConfiguration, true);
+ if(conn != null) {
+ connResult = true;
+ conn.close();
+ }
+
+ return connResult;
+
+ }
+ catch (SQLException e) {
+ logger.error("Test connection Failed!", e);
+ throw new DatabaseServiceException(true, e.getSQLState(), e.getErrorCode(), e.getMessage());
+ }
+
+ }
+
+ /**
+ * Get a connection form the connection pool.
+ *
+ * @return connection from the pool
+ */
+ public Connection getConnection(DatabaseConfiguration databaseConfiguration, boolean forceNewConnection) throws DatabaseServiceException{
+ try {
+
+ // logger.info("connection::{}, forceNewConnection: {}", connection, forceNewConnection);
+
+ if (connection != null && !forceNewConnection) {
+ // logger.debug("connection closed::{}", connection.isClosed());
+ if (!connection.isClosed()) {
+ if(logger.isDebugEnabled()) {
+ logger.debug("Returning existing connection::{}", connection);
+ }
+
+
+ return connection;
+ }
+ }
+
+ Class.forName(type.getClassPath());
+ DriverManager.setLoginTimeout(10);
+ String dbURL = getDatabaseUrl(databaseConfiguration);
+ connection = DriverManager.getConnection(dbURL, databaseConfiguration.getDatabaseUser(),
+ databaseConfiguration.getDatabasePassword());
+
+ if(logger.isDebugEnabled()) {
+ logger.debug("*** Acquired New connection for ::{} **** ", dbURL);
+ }
+
+
+ return connection;
+
+
+ } catch (ClassNotFoundException e) {
+ logger.error("Jdbc Driver not found", e);
+ throw new DatabaseServiceException(e.getMessage());
+ } catch (SQLException e) {
+ logger.error("SQLException::Couldn't get a Connection!", e);
+ throw new DatabaseServiceException(true, e.getSQLState(), e.getErrorCode(), e.getMessage());
+ }
+ }
+
+
+ public void shutdown() {
+
+ if (connection != null) {
+ try {
+ connection.close();
+ }
+ catch (SQLException e) {
+ logger.warn("Non-Managed connection could not be closed. Whoops!", e);
+ }
+ }
+
+ }
+
+
+
+ private static String getDatabaseUrl(DatabaseConfiguration dbConfig) {
+
+ int port = dbConfig.getDatabasePort();
+ return "jdbc:" + dbConfig.getDatabaseType().toLowerCase() + "://" + dbConfig.getDatabaseHost()
+ + ((port == 0) ? "" : (":" + port)) + "/" + dbConfig.getDatabaseName();
+
+ }
+}
diff --git a/extensions/database/src/com/google/refine/extension/database/mariadb/MariaDBDatabaseService.java b/extensions/database/src/com/google/refine/extension/database/mariadb/MariaDBDatabaseService.java
new file mode 100644
index 000000000..f5760a6b2
--- /dev/null
+++ b/extensions/database/src/com/google/refine/extension/database/mariadb/MariaDBDatabaseService.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.refine.extension.database.mariadb;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.mariadb.jdbc.MariaDbResultSetMetaData;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.refine.extension.database.DatabaseConfiguration;
+import com.google.refine.extension.database.DatabaseService;
+import com.google.refine.extension.database.DatabaseServiceException;
+import com.google.refine.extension.database.DatabaseUtils;
+import com.google.refine.extension.database.SQLType;
+import com.google.refine.extension.database.model.DatabaseColumn;
+import com.google.refine.extension.database.model.DatabaseInfo;
+import com.google.refine.extension.database.model.DatabaseRow;
+import com.google.refine.extension.database.mysql.MySQLConnectionManager;
+
+
+
+public class MariaDBDatabaseService extends DatabaseService {
+
+ private static final Logger logger = LoggerFactory.getLogger("MariaDBDatabaseService");
+
+ public static final String DB_NAME = "mariadb";
+ public static final String DB_DRIVER = "org.mariadb.jdbc.Driver";
+
+ private static MariaDBDatabaseService instance;
+
+ private MariaDBDatabaseService() {
+ }
+
+ public static MariaDBDatabaseService getInstance() {
+ if (instance == null) {
+ SQLType.registerSQLDriver(DB_NAME, DB_DRIVER);
+ instance = new MariaDBDatabaseService();
+ if(logger.isDebugEnabled()) {
+ logger.debug("MariaDBDatabaseService Instance: {}", instance);
+ }
+ }
+ return instance;
+ }
+
+ @Override
+ public boolean testConnection(DatabaseConfiguration dbConfig) throws DatabaseServiceException{
+ return MariaDBConnectionManager.getInstance().testConnection(dbConfig);
+
+ }
+
+ @Override
+ public DatabaseInfo connect(DatabaseConfiguration dbConfig) throws DatabaseServiceException{
+ return getMetadata(dbConfig);
+ }
+
+
+ @Override
+ public DatabaseInfo executeQuery(DatabaseConfiguration dbConfig, String query) throws DatabaseServiceException{
+
+
+ try {
+ Connection connection = MariaDBConnectionManager.getInstance().getConnection(dbConfig, false);
+ Statement statement = connection.createStatement();
+ ResultSet queryResult = statement.executeQuery(query);
+ MariaDbResultSetMetaData metadata = (MariaDbResultSetMetaData)queryResult.getMetaData();
+ int columnCount = metadata.getColumnCount();
+ ArrayList columns = new ArrayList(columnCount);
+
+ for (int i = 1; i <= columnCount; i++) {
+ DatabaseColumn dc = new DatabaseColumn(
+ metadata.getColumnName(i),
+ metadata.getColumnLabel(i),
+ DatabaseUtils.getDbColumnType(metadata.getColumnType(i)),
+ metadata.getColumnDisplaySize(i));
+ columns.add(dc);
+ }
+ int index = 0;
+ List rows = new ArrayList();
+
+ while (queryResult.next()) {
+ DatabaseRow row = new DatabaseRow();
+ row.setIndex(index);
+ List values = new ArrayList(columnCount);
+ for (int i = 1; i <= columnCount; i++) {
+
+ values.add(queryResult.getString(i));
+
+ }
+ row.setValues(values);
+ rows.add(row);
+ index++;
+
+ }
+
+ DatabaseInfo dbInfo = new DatabaseInfo();
+ dbInfo.setColumns(columns);
+ dbInfo.setRows(rows);
+ return dbInfo;
+
+ } catch (SQLException e) {
+ logger.error("SQLException::", e);
+ throw new DatabaseServiceException(true, e.getSQLState(), e.getErrorCode(), e.getMessage());
+ } finally {
+ MariaDBConnectionManager.getInstance().shutdown();
+ }
+ }
+
+ /**
+ *
+ * @param connectionInfo
+ * @return
+ * @throws DatabaseServiceException
+ */
+ private DatabaseInfo getMetadata(DatabaseConfiguration connectionInfo) throws DatabaseServiceException {
+
+ try {
+ Connection connection = MariaDBConnectionManager.getInstance().getConnection(connectionInfo, true);
+ if(connection != null) {
+ java.sql.DatabaseMetaData metadata;
+
+ metadata = connection.getMetaData();
+
+ int dbMajorVersion = metadata.getDatabaseMajorVersion();
+ int dbMinorVersion = metadata.getDatabaseMinorVersion();
+ String dbProductVersion = metadata.getDatabaseProductVersion();
+ String dbProductName = metadata.getDatabaseProductName();
+ DatabaseInfo dbInfo = new DatabaseInfo();
+
+ dbInfo.setDatabaseMajorVersion(dbMajorVersion);
+ dbInfo.setDatabaseMinorVersion(dbMinorVersion);
+ dbInfo.setDatabaseProductVersion(dbProductVersion);
+ dbInfo.setDatabaseProductName(dbProductName);
+ return dbInfo;
+ }
+
+ } catch (SQLException e) {
+ logger.error("SQLException::", e);
+ throw new DatabaseServiceException(true, e.getSQLState(), e.getErrorCode(), e.getMessage());
+ }
+
+ return null;
+
+ }
+
+ @Override
+ public String buildLimitQuery(Integer limit, Integer offset, String query) {
+// if(logger.isDebugEnabled()) {
+// logger.info( "<<< original input query::{} >>>" , query );
+// }
+//
+ final int len = query.length();
+ String parsedQuery = len > 0 && query.endsWith(";") ? query.substring(0, len - 1) : query;
+
+
+ StringBuilder sb = new StringBuilder();
+ sb.append(parsedQuery);
+
+ if(limit != null) {
+ sb.append(" LIMIT" + " " + limit);
+ }
+
+ if(offset != null) {
+ sb.append(" OFFSET" + " " + offset);
+ }
+ sb.append(";");
+ String parsedQueryOut = sb.toString();
+
+// if(logger.isDebugEnabled()) {
+// logger.info( "<<>>" , parsedQueryOut );
+// }
+
+ return parsedQueryOut;
+ }
+
+ @Override
+ public ArrayList getColumns(DatabaseConfiguration dbConfig, String query) throws DatabaseServiceException{
+
+ try {
+
+ Connection connection = MariaDBConnectionManager.getInstance().getConnection(dbConfig, true);
+ Statement statement = connection.createStatement();
+
+ ResultSet queryResult = statement.executeQuery(query);
+ MariaDbResultSetMetaData metadata = (MariaDbResultSetMetaData) queryResult.getMetaData();
+ int columnCount = metadata.getColumnCount();
+ ArrayList columns = new ArrayList(columnCount);
+
+ for (int i = 1; i <= columnCount; i++) {
+ DatabaseColumn dc = new DatabaseColumn(metadata.getColumnName(i), metadata.getColumnLabel(i),
+ DatabaseUtils.getDbColumnType(metadata.getColumnType(i)), metadata.getColumnDisplaySize(i));
+ columns.add(dc);
+ }
+
+ return columns;
+
+ } catch (SQLException e) {
+ logger.error("SQLException::", e);
+ throw new DatabaseServiceException(true, e.getSQLState(), e.getErrorCode(), e.getMessage());
+ }
+
+ }
+
+ @Override
+ public List getRows(DatabaseConfiguration dbConfig, String query)
+ throws DatabaseServiceException {
+
+
+ try {
+ Connection connection = MariaDBConnectionManager.getInstance().getConnection(dbConfig, false);
+ Statement statement = connection.createStatement();
+ ResultSet queryResult = statement.executeQuery(query);
+ MariaDbResultSetMetaData metadata = (MariaDbResultSetMetaData)queryResult.getMetaData();
+ int columnCount = metadata.getColumnCount();
+
+ int index = 0;
+ List rows = new ArrayList();
+
+ while (queryResult.next()) {
+ DatabaseRow row = new DatabaseRow();
+ row.setIndex(index);
+ List values = new ArrayList(columnCount);
+ for (int i = 1; i <= columnCount; i++) {
+
+ values.add(queryResult.getString(i));
+
+ }
+ row.setValues(values);
+ rows.add(row);
+ index++;
+
+ }
+
+ return rows;
+
+ } catch (SQLException e) {
+ logger.error("SQLException::", e);
+ throw new DatabaseServiceException(true, e.getSQLState(), e.getErrorCode(), e.getMessage());
+ }
+ }
+
+ @Override
+ protected String getDatabaseUrl(DatabaseConfiguration dbConfig) {
+
+ int port = dbConfig.getDatabasePort();
+ return "jdbc:" + dbConfig.getDatabaseType() + "://" + dbConfig.getDatabaseHost()
+ + ((port == 0) ? "" : (":" + port)) + "/" + dbConfig.getDatabaseName() + "?useSSL=" + dbConfig.isUseSSL();
+
+ }
+
+ @Override
+ public Connection getConnection(DatabaseConfiguration dbConfig)
+ throws DatabaseServiceException {
+ // TODO Auto-generated method stub
+ return MariaDBConnectionManager.getInstance().getConnection(dbConfig, true);
+ }
+
+ @Override
+ public DatabaseInfo testQuery(DatabaseConfiguration dbConfig, String query)
+ throws DatabaseServiceException {
+ Statement statement = null;
+ ResultSet queryResult = null;
+ try {
+ Connection connection = MySQLConnectionManager.getInstance().getConnection(dbConfig, true);
+ statement = connection.createStatement();
+ queryResult = statement.executeQuery(query);
+
+ DatabaseInfo dbInfo = new DatabaseInfo();
+
+ return dbInfo;
+
+ } catch (SQLException e) {
+ logger.error("SQLException::", e);
+ throw new DatabaseServiceException(true, e.getSQLState(), e.getErrorCode(), e.getMessage());
+ } finally {
+ try {
+ if (queryResult != null) {
+ queryResult.close();
+
+ }
+ if (statement != null) {
+ statement.close();
+
+ }
+ } catch (SQLException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ MySQLConnectionManager.getInstance().shutdown();
+ }
+ }
+
+}
diff --git a/extensions/database/src/com/google/refine/extension/database/model/DatabaseColumn.java b/extensions/database/src/com/google/refine/extension/database/model/DatabaseColumn.java
new file mode 100644
index 000000000..35620146c
--- /dev/null
+++ b/extensions/database/src/com/google/refine/extension/database/model/DatabaseColumn.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.refine.extension.database.model;
+
+import com.google.refine.extension.database.DatabaseColumnType;
+
+public class DatabaseColumn {
+
+
+ private String name;
+ private int size;
+ private DatabaseColumnType type;
+ private String label;
+
+ public DatabaseColumnType getType() {
+ return type;
+ }
+
+
+
+ public void setType(DatabaseColumnType type) {
+ this.type = type;
+ }
+
+
+
+ public String getLabel() {
+ return label;
+ }
+
+
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+
+ public DatabaseColumn(String name, int size, DatabaseColumnType type) {
+ super();
+ this.name = name;
+ this.size = size;
+ this.type = type;
+ }
+
+
+ public DatabaseColumn(String name, String label, DatabaseColumnType type, int size) {
+ this.name = name;
+ this.label = label;
+ this.size = size;
+ this.type = type;
+ }
+
+
+ public String getName() {
+ return name;
+ }
+
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+
+ public int getSize() {
+ return size;
+ }
+
+
+ public void setSize(int size) {
+ this.size = size;
+ }
+
+
+
+
+ @Override
+ public String toString() {
+ return "DatabaseColumn [name=" + name + ", size=" + size + ", type=" + type + ", label=" + label + "]";
+ }
+
+
+
+}
diff --git a/extensions/database/src/com/google/refine/extension/database/model/DatabaseInfo.java b/extensions/database/src/com/google/refine/extension/database/model/DatabaseInfo.java
new file mode 100644
index 000000000..c5755e9a7
--- /dev/null
+++ b/extensions/database/src/com/google/refine/extension/database/model/DatabaseInfo.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.refine.extension.database.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DatabaseInfo {
+
+ private List tables;
+ private int dbMajorVersion;
+ private int dbMinorVersion;
+ private String dbProductVersion;
+ private String dbProductName;
+
+ private ArrayList columns;
+ private List rows;
+
+ public DatabaseInfo() {
+ // TODO Auto-generated constructor stub
+ }
+
+ public List getTables() {
+ return tables;
+ }
+
+ public void setTables(List tables) {
+ this.tables = tables;
+ }
+
+ public void setDatabaseMajorVersion(int dbMajorVersion) {
+ this.dbMajorVersion = dbMajorVersion;
+
+ }
+
+ public void setDatabaseMinorVersion(int dbMinorVersion) {
+ this.dbMinorVersion = dbMinorVersion;
+
+ }
+
+ public void setDatabaseProductVersion(String dbProductVersion) {
+ this.dbProductVersion = dbProductVersion;
+
+ }
+
+ public void setDatabaseProductName(String dbProductName) {
+ this.dbProductName = dbProductName;
+
+ }
+
+ public int getDbMajorVersion() {
+ return dbMajorVersion;
+ }
+
+ public void setDbMajorVersion(int dbMajorVersion) {
+ this.dbMajorVersion = dbMajorVersion;
+ }
+
+ public int getDbMinorVersion() {
+ return dbMinorVersion;
+ }
+
+ public void setDbMinorVersion(int dbMinorVersion) {
+ this.dbMinorVersion = dbMinorVersion;
+ }
+
+ public String getDbProductVersion() {
+ return dbProductVersion;
+ }
+
+ public void setDbProductVersion(String dbProductVersion) {
+ this.dbProductVersion = dbProductVersion;
+ }
+
+ public String getDbProductName() {
+ return dbProductName;
+ }
+
+ public void setDbProductName(String dbProductName) {
+ this.dbProductName = dbProductName;
+ }
+
+ public void setColumns(ArrayList columns) {
+ this.columns = columns;
+
+ }
+
+ public void setRows(List rows) {
+ this.rows = rows;
+
+ }
+
+ public ArrayList getColumns() {
+ return columns;
+ }
+
+
+ public List getRows() {
+ return rows;
+ }
+
+
+ @Override
+ public String toString() {
+ return "DatabaseInfo [tables=" + tables + ", dbMajorVersion=" + dbMajorVersion + ", dbMinorVersion="
+ + dbMinorVersion + ", dbProductVersion=" + dbProductVersion + ", dbProductName=" + dbProductName + "]";
+ }
+
+}
diff --git a/extensions/database/src/com/google/refine/extension/database/model/DatabaseQueryInfo.java b/extensions/database/src/com/google/refine/extension/database/model/DatabaseQueryInfo.java
new file mode 100644
index 000000000..e8f1fb8c8
--- /dev/null
+++ b/extensions/database/src/com/google/refine/extension/database/model/DatabaseQueryInfo.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.refine.extension.database.model;
+
+import com.google.refine.extension.database.DatabaseConfiguration;
+
+public class DatabaseQueryInfo {
+
+ private DatabaseConfiguration dbConfig;
+
+ private String query;
+
+ public DatabaseQueryInfo(DatabaseConfiguration databaseConfig, String query) {
+ super();
+ this.dbConfig = databaseConfig;
+ this.query = query;
+ }
+
+
+ public DatabaseConfiguration getDbConfig() {
+ return dbConfig;
+ }
+
+
+ public void setDbConfig(DatabaseConfiguration databaseConfig) {
+ this.dbConfig = databaseConfig;
+ }
+
+
+ public String getQuery() {
+ return query;
+ }
+
+
+ public void setQuery(String query) {
+ this.query = query;
+ }
+
+
+
+}
diff --git a/extensions/database/src/com/google/refine/extension/database/model/DatabaseRow.java b/extensions/database/src/com/google/refine/extension/database/model/DatabaseRow.java
new file mode 100644
index 000000000..3fd6b9979
--- /dev/null
+++ b/extensions/database/src/com/google/refine/extension/database/model/DatabaseRow.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.refine.extension.database.model;
+
+import java.util.List;
+
+public class DatabaseRow {
+
+ private int index;
+
+ private List values;
+
+
+ public int getIndex() {
+ return index;
+ }
+
+
+ public void setIndex(int index) {
+ this.index = index;
+ }
+
+
+ public List getValues() {
+ return values;
+ }
+
+
+ public void setValues(List values) {
+ this.values = values;
+ }
+
+
+
+
+}
diff --git a/extensions/database/src/com/google/refine/extension/database/model/DatabaseTable.java b/extensions/database/src/com/google/refine/extension/database/model/DatabaseTable.java
new file mode 100644
index 000000000..c7758de27
--- /dev/null
+++ b/extensions/database/src/com/google/refine/extension/database/model/DatabaseTable.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.refine.extension.database.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DatabaseTable {
+
+
+ private List columns = new ArrayList();
+
+ private String name;
+
+
+ public DatabaseTable(String name) {
+ this.name = name;
+ }
+
+
+ public List getColumns() {
+ return columns;
+ }
+
+
+ public void setColumns(List columns) {
+ this.columns = columns;
+ }
+
+
+ public String getName() {
+ return name;
+ }
+
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String toString() {
+ return "DatabaseTable [columns=" + columns + ", name=" + name + "]";
+ }
+
+
+
+}
diff --git a/extensions/database/src/com/google/refine/extension/database/mysql/MySQLConnectionManager.java b/extensions/database/src/com/google/refine/extension/database/mysql/MySQLConnectionManager.java
new file mode 100644
index 000000000..9e81fd264
--- /dev/null
+++ b/extensions/database/src/com/google/refine/extension/database/mysql/MySQLConnectionManager.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.refine.extension.database.mysql;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.refine.extension.database.DatabaseConfiguration;
+import com.google.refine.extension.database.DatabaseServiceException;
+import com.google.refine.extension.database.SQLType;
+
+
+public class MySQLConnectionManager {
+
+ private static final Logger logger = LoggerFactory.getLogger("MySQLConnectionManager");
+ private Connection connection;
+ private SQLType type;
+
+ private static MySQLConnectionManager instance;
+
+ /**
+ *
+ * @param type
+ * @param databaseConfiguration
+ * @throws SQLException
+ */
+ private MySQLConnectionManager() {
+ type = SQLType.forName(MySQLDatabaseService.DB_NAME);
+
+ }
+
+
+
+ /**
+ * Create a new instance of this connection manager.
+ *
+ * @return an instance of the manager
+ *
+ * @throws DatabaseServiceException
+ */
+ public static MySQLConnectionManager getInstance() throws DatabaseServiceException {
+ if (instance == null) {
+ logger.debug("::Creating new MySQLConnectionManager ::");
+ instance = new MySQLConnectionManager();
+
+ }
+ return instance;
+ }
+
+
+ /**
+ * Get the SQL Database type.
+ *
+ * @return the type
+ */
+ public SQLType getType() {
+ return this.type;
+ }
+
+ /**
+ * testConnection
+ * @param databaseConfiguration
+ * @return
+ */
+ public boolean testConnection(DatabaseConfiguration databaseConfiguration) throws DatabaseServiceException{
+
+ try {
+ boolean connResult = false;
+
+ Connection conn = getConnection(databaseConfiguration, true);
+ if(conn != null) {
+ connResult = true;
+ conn.close();
+ }
+
+ return connResult;
+
+ }
+ catch (SQLException e) {
+ logger.error("Test connection Failed!", e);
+ throw new DatabaseServiceException(true, e.getSQLState(), e.getErrorCode(), e.getMessage());
+ }
+
+ }
+
+ /**
+ * Get a connection form the connection pool.
+ *
+ * @return connection from the pool
+ */
+ public Connection getConnection(DatabaseConfiguration databaseConfiguration, boolean forceNewConnection) throws DatabaseServiceException{
+ try {
+
+ if (connection != null && !forceNewConnection) {
+ //logger.info("connection closed::{}", connection.isClosed());
+ if (!connection.isClosed()) {
+ if(logger.isDebugEnabled()){
+ logger.debug("Returning existing connection::{}", connection);
+ }
+
+ return connection;
+ }
+ }
+ String dbURL = getDatabaseUrl(databaseConfiguration);
+ Class.forName(type.getClassPath());
+
+ //logger.info("*** type.getClassPath() ::{}, {}**** ", type.getClassPath());
+
+ DriverManager.setLoginTimeout(10);
+
+ connection = DriverManager.getConnection(dbURL, databaseConfiguration.getDatabaseUser(),
+ databaseConfiguration.getDatabasePassword());
+
+ if(logger.isDebugEnabled()) {
+ logger.debug("*** Acquired New connection for ::{} **** ", dbURL);
+ }
+
+
+ return connection;
+
+
+ } catch (ClassNotFoundException e) {
+ logger.error("Jdbc Driver not found", e);
+ throw new DatabaseServiceException(e.getMessage());
+ } catch (SQLException e) {
+ logger.error("SQLException::Couldn't get a Connection!", e);
+ throw new DatabaseServiceException(true, e.getSQLState(), e.getErrorCode(), e.getMessage());
+ }
+ }
+
+
+ public void shutdown() {
+
+ if (connection != null) {
+ try {
+ connection.close();
+ }
+ catch (SQLException e) {
+ logger.warn("Non-Managed connection could not be closed. Whoops!", e);
+ }
+ }
+
+ }
+
+
+ private String getDatabaseUrl(DatabaseConfiguration dbConfig) {
+
+ int port = dbConfig.getDatabasePort();
+ return "jdbc:" + dbConfig.getDatabaseType() + "://" + dbConfig.getDatabaseHost()
+ + ((port == 0) ? "" : (":" + port)) + "/" + dbConfig.getDatabaseName() + "?useSSL=" + dbConfig.isUseSSL();
+
+ }
+}
diff --git a/extensions/database/src/com/google/refine/extension/database/mysql/MySQLDatabaseService.java b/extensions/database/src/com/google/refine/extension/database/mysql/MySQLDatabaseService.java
new file mode 100644
index 000000000..503936e16
--- /dev/null
+++ b/extensions/database/src/com/google/refine/extension/database/mysql/MySQLDatabaseService.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.refine.extension.database.mysql;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.refine.extension.database.DatabaseConfiguration;
+import com.google.refine.extension.database.DatabaseService;
+import com.google.refine.extension.database.DatabaseServiceException;
+import com.google.refine.extension.database.DatabaseUtils;
+import com.google.refine.extension.database.SQLType;
+import com.google.refine.extension.database.model.DatabaseColumn;
+import com.google.refine.extension.database.model.DatabaseInfo;
+import com.google.refine.extension.database.model.DatabaseRow;
+//import com.mysql.jdbc.ResultSetMetaData;
+
+public class MySQLDatabaseService extends DatabaseService {
+
+
+ private static final Logger logger = LoggerFactory.getLogger("MySQLDatabaseService");
+
+ public static final String DB_NAME = "mysql";
+ public static final String DB_DRIVER = "com.mysql.jdbc.Driver";
+
+ private static MySQLDatabaseService instance;
+
+ private MySQLDatabaseService() {
+ }
+
+ public static MySQLDatabaseService getInstance() {
+ if (instance == null) {
+ SQLType.registerSQLDriver(DB_NAME, DB_DRIVER, false);
+ instance = new MySQLDatabaseService();
+ if(logger.isDebugEnabled()) {
+ logger.debug("MySQLDatabaseService Instance: {}", instance);
+ }
+ }
+ return instance;
+ }
+
+ @Override
+ public boolean testConnection(DatabaseConfiguration dbConfig) throws DatabaseServiceException{
+ return MySQLConnectionManager.getInstance().testConnection(dbConfig);
+
+ }
+
+ @Override
+ public DatabaseInfo connect(DatabaseConfiguration dbConfig) throws DatabaseServiceException{
+ return getMetadata(dbConfig);
+ }
+
+
+ @Override
+ public DatabaseInfo executeQuery(DatabaseConfiguration dbConfig, String query) throws DatabaseServiceException{
+ try {
+ Connection connection = MySQLConnectionManager.getInstance().getConnection(dbConfig, false);
+ Statement statement = connection.createStatement();
+ ResultSet queryResult = statement.executeQuery(query);
+ java.sql.ResultSetMetaData metadata = queryResult.getMetaData();
+
+ if(metadata instanceof com.mysql.jdbc.ResultSetMetaData) {
+ metadata = (com.mysql.jdbc.ResultSetMetaData)metadata;
+ }
+ //ResultSetMetaData metadata = (ResultSetMetaData)queryResult.getMetaData();
+
+ int columnCount = metadata.getColumnCount();
+ ArrayList columns = new ArrayList(columnCount);
+
+ for (int i = 1; i <= columnCount; i++) {
+ DatabaseColumn dc = new DatabaseColumn(
+ metadata.getColumnName(i),
+ metadata.getColumnLabel(i),
+ DatabaseUtils.getDbColumnType(metadata.getColumnType(i)),
+ metadata.getColumnDisplaySize(i));
+ columns.add(dc);
+ }
+ int index = 0;
+ List rows = new ArrayList();
+
+ while (queryResult.next()) {
+ DatabaseRow row = new DatabaseRow();
+ row.setIndex(index);
+ List values = new ArrayList(columnCount);
+ for (int i = 1; i <= columnCount; i++) {
+
+ values.add(queryResult.getString(i));
+
+ }
+ row.setValues(values);
+ rows.add(row);
+ index++;
+
+ }
+
+ DatabaseInfo dbInfo = new DatabaseInfo();
+ dbInfo.setColumns(columns);
+ dbInfo.setRows(rows);
+ return dbInfo;
+
+ } catch (SQLException e) {
+ logger.error("SQLException::", e);
+ throw new DatabaseServiceException(true, e.getSQLState(), e.getErrorCode(), e.getMessage());
+ }finally {
+ MySQLConnectionManager.getInstance().shutdown();
+ }
+ }
+
+ /**
+ *
+ * @param connectionInfo
+ * @return
+ * @throws DatabaseServiceException
+ */
+ private DatabaseInfo getMetadata(DatabaseConfiguration connectionInfo) throws DatabaseServiceException {
+
+ try {
+ Connection connection = MySQLConnectionManager.getInstance().getConnection(connectionInfo, true);
+ if(connection != null) {
+ java.sql.DatabaseMetaData metadata;
+
+ metadata = connection.getMetaData();
+
+ int dbMajorVersion = metadata.getDatabaseMajorVersion();
+ int dbMinorVersion = metadata.getDatabaseMinorVersion();
+ String dbProductVersion = metadata.getDatabaseProductVersion();
+ String dbProductName = metadata.getDatabaseProductName();
+ DatabaseInfo dbInfo = new DatabaseInfo();
+
+ dbInfo.setDatabaseMajorVersion(dbMajorVersion);
+ dbInfo.setDatabaseMinorVersion(dbMinorVersion);
+ dbInfo.setDatabaseProductVersion(dbProductVersion);
+ dbInfo.setDatabaseProductName(dbProductName);
+ return dbInfo;
+ }
+
+ } catch (SQLException e) {
+ logger.error("SQLException::", e);
+ throw new DatabaseServiceException(true, e.getSQLState(), e.getErrorCode(), e.getMessage());
+ }
+
+
+ return null;
+
+ }
+
+ @Override
+ public String buildLimitQuery(Integer limit, Integer offset, String query) {
+ if(logger.isDebugEnabled()) {
+ logger.info( "<<< original input query::{} >>>" , query );
+ }
+
+ final int len = query.length();
+ String parsedQuery = len > 0 && query.endsWith(";") ? query.substring(0, len - 1) : query;
+
+
+ StringBuilder sb = new StringBuilder();
+ sb.append(parsedQuery);
+
+ if(limit != null) {
+ sb.append(" LIMIT" + " " + limit);
+ }
+
+ if(offset != null) {
+ sb.append(" OFFSET" + " " + offset);
+ }
+ sb.append(";");
+ String parsedQueryOut = sb.toString();
+
+ if(logger.isDebugEnabled()) {
+ logger.info( "<<>>" , parsedQueryOut );
+ }
+
+ return parsedQueryOut;
+ }
+
+ @Override
+ public ArrayList getColumns(DatabaseConfiguration dbConfig, String query) throws DatabaseServiceException{
+
+ try {
+ Connection connection = MySQLConnectionManager.getInstance().getConnection(dbConfig, true);
+ Statement statement = connection.createStatement();
+
+ ResultSet queryResult = statement.executeQuery(query);
+ java.sql.ResultSetMetaData metadata = queryResult.getMetaData();
+ if(metadata instanceof com.mysql.jdbc.ResultSetMetaData) {
+ metadata = (com.mysql.jdbc.ResultSetMetaData)metadata;
+ }
+
+ //ResultSetMetaData metadata = (ResultSetMetaData) queryResult.getMetaData();
+ int columnCount = metadata.getColumnCount();
+ ArrayList columns = new ArrayList(columnCount);
+
+ for (int i = 1; i <= columnCount; i++) {
+ DatabaseColumn dc = new DatabaseColumn(metadata.getColumnName(i), metadata.getColumnLabel(i),
+ DatabaseUtils.getDbColumnType(metadata.getColumnType(i)), metadata.getColumnDisplaySize(i));
+ columns.add(dc);
+ }
+
+ return columns;
+
+ } catch (SQLException e) {
+ logger.error("SQLException::", e);
+ throw new DatabaseServiceException(true, e.getSQLState(), e.getErrorCode(), e.getMessage());
+ }
+
+
+ }
+
+ @Override
+ public List getRows(DatabaseConfiguration dbConfig, String query)
+ throws DatabaseServiceException {
+
+ try {
+ Connection connection = MySQLConnectionManager.getInstance().getConnection(dbConfig, false);
+
+ Statement statement = connection.createStatement();
+ statement.setFetchSize(10);
+ ResultSet queryResult = statement.executeQuery(query);
+
+ java.sql.ResultSetMetaData metadata = queryResult.getMetaData();
+ if(metadata instanceof com.mysql.jdbc.ResultSetMetaData) {
+ metadata = (com.mysql.jdbc.ResultSetMetaData)metadata;
+ }
+ //logger.info("metadata class::" + metadata.getClass());
+
+ int columnCount = metadata.getColumnCount();
+
+ int index = 0;
+ List rows = new ArrayList();
+
+ while (queryResult.next()) {
+ DatabaseRow row = new DatabaseRow();
+ row.setIndex(index);
+ List values = new ArrayList(columnCount);
+ for (int i = 1; i <= columnCount; i++) {
+
+ values.add(queryResult.getString(i));
+
+ }
+ row.setValues(values);
+ rows.add(row);
+ index++;
+
+ }
+
+ return rows;
+
+ } catch (SQLException e) {
+ logger.error("SQLException::", e);
+ throw new DatabaseServiceException(true, e.getSQLState(), e.getErrorCode(), e.getMessage());
+ }
+ }
+
+ @Override
+ protected String getDatabaseUrl(DatabaseConfiguration dbConfig) {
+
+ int port = dbConfig.getDatabasePort();
+ return "jdbc:" + dbConfig.getDatabaseType() + "://" + dbConfig.getDatabaseHost()
+ + ((port == 0) ? "" : (":" + port)) + "/" + dbConfig.getDatabaseName() + "?useSSL=" + dbConfig.isUseSSL();
+
+ }
+
+
+ @Override
+ public Connection getConnection(DatabaseConfiguration dbConfig)
+ throws DatabaseServiceException {
+ // TODO Auto-generated method stub
+ return MySQLConnectionManager.getInstance().getConnection(dbConfig, true);
+ }
+
+ @Override
+ public DatabaseInfo testQuery(DatabaseConfiguration dbConfig, String query)
+ throws DatabaseServiceException {
+ Statement statement = null;
+ ResultSet queryResult = null;
+ try {
+ Connection connection = MySQLConnectionManager.getInstance().getConnection(dbConfig, true);
+ statement = connection.createStatement();
+ queryResult = statement.executeQuery(query);
+
+ DatabaseInfo dbInfo = new DatabaseInfo();
+
+ return dbInfo;
+
+ } catch (SQLException e) {
+ logger.error("SQLException::", e);
+ throw new DatabaseServiceException(true, e.getSQLState(), e.getErrorCode(), e.getMessage());
+ } finally {
+ try {
+ if (queryResult != null) {
+ queryResult.close();
+
+ }
+ if (statement != null) {
+ statement.close();
+
+ }
+ } catch (SQLException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ MySQLConnectionManager.getInstance().shutdown();
+ }
+ }
+
+
+}
diff --git a/extensions/database/src/com/google/refine/extension/database/pgsql/PgSQLConnectionManager.java b/extensions/database/src/com/google/refine/extension/database/pgsql/PgSQLConnectionManager.java
new file mode 100644
index 000000000..bef6c9a69
--- /dev/null
+++ b/extensions/database/src/com/google/refine/extension/database/pgsql/PgSQLConnectionManager.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.refine.extension.database.pgsql;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.refine.extension.database.DatabaseConfiguration;
+import com.google.refine.extension.database.DatabaseServiceException;
+import com.google.refine.extension.database.SQLType;
+
+
+
+public class PgSQLConnectionManager {
+
+ private static final Logger logger = LoggerFactory.getLogger("PgSQLConnectionManager");
+ private Connection connection;
+ private SQLType type;
+
+ private static PgSQLConnectionManager instance;
+
+
+
+ /**
+ *
+ * @param type
+ * @param databaseConfiguration
+ * @throws SQLException
+ */
+ private PgSQLConnectionManager() {
+ type = SQLType.forName(PgSQLDatabaseService.DB_NAME);
+
+ }
+
+
+
+
+ /**
+ * Create a new instance of this connection manager.
+ *
+ * @return an instance of the manager
+ *
+ * @throws DatabaseServiceException
+ */
+ public static PgSQLConnectionManager getInstance() throws DatabaseServiceException {
+ if (instance == null) {
+ if(logger.isDebugEnabled()) {
+ logger.debug("::Creating new PgSQL ConnectionManager ::");
+ }
+
+ instance = new PgSQLConnectionManager();
+
+ }
+ return instance;
+ }
+
+
+ /**
+ * Get the SQL Database type.
+ *
+ * @return the type
+ */
+ public SQLType getType() {
+ return this.type;
+ }
+
+ /**
+ * testConnection
+ * @param databaseConfiguration
+ * @return
+ */
+ public boolean testConnection(DatabaseConfiguration databaseConfiguration) throws DatabaseServiceException{
+
+ try {
+ boolean connResult = false;
+
+ Connection conn = getConnection(databaseConfiguration, true);
+ if(conn != null) {
+ connResult = true;
+ conn.close();
+ }
+
+ return connResult;
+
+ }
+ catch (SQLException e) {
+ logger.error("Test connection Failed!", e);
+ throw new DatabaseServiceException(true, e.getSQLState(), e.getErrorCode(), e.getMessage());
+ }
+
+ }
+
+ /**
+ * Get a connection form the connection pool.
+ *
+ * @return connection from the pool
+ */
+ public Connection getConnection(DatabaseConfiguration databaseConfiguration, boolean forceNewConnection) throws DatabaseServiceException{
+ try {
+
+ // logger.info("connection::{}, forceNewConnection: {}", connection, forceNewConnection);
+
+ if (connection != null && !forceNewConnection) {
+ // logger.info("connection closed::{}", connection.isClosed());
+ if (!connection.isClosed()) {
+ if(logger.isDebugEnabled()){
+ logger.debug("Returning existing connection::{}", connection);
+ }
+ return connection;
+ }
+ }
+
+ Class.forName(type.getClassPath());
+ DriverManager.setLoginTimeout(10);
+ String dbURL = getDatabaseUrl(databaseConfiguration);
+ connection = DriverManager.getConnection(dbURL, databaseConfiguration.getDatabaseUser(),
+ databaseConfiguration.getDatabasePassword());
+
+ logger.debug("*** Acquired New connection for ::{} **** ", dbURL);
+
+ return connection;
+
+
+ } catch (ClassNotFoundException e) {
+ logger.error("Jdbc Driver not found", e);
+ throw new DatabaseServiceException(e.getMessage());
+ } catch (SQLException e) {
+ logger.error("SQLException::Couldn't get a Connection!", e);
+ throw new DatabaseServiceException(true, e.getSQLState(), e.getErrorCode(), e.getMessage());
+ }
+ }
+
+
+ public void shutdown() {
+
+ if (connection != null) {
+ try {
+ connection.close();
+ }
+ catch (SQLException e) {
+ logger.warn("Non-Managed connection could not be closed. Whoops!", e);
+ }
+ }
+
+ }
+
+
+ private static String getDatabaseUrl(DatabaseConfiguration dbConfig) {
+
+ int port = dbConfig.getDatabasePort();
+ return "jdbc:" + dbConfig.getDatabaseType().toLowerCase() + "://" + dbConfig.getDatabaseHost()
+ + ((port == 0) ? "" : (":" + port)) + "/" + dbConfig.getDatabaseName();
+
+ }
+}
diff --git a/extensions/database/src/com/google/refine/extension/database/pgsql/PgSQLDatabaseService.java b/extensions/database/src/com/google/refine/extension/database/pgsql/PgSQLDatabaseService.java
new file mode 100644
index 000000000..9a6308373
--- /dev/null
+++ b/extensions/database/src/com/google/refine/extension/database/pgsql/PgSQLDatabaseService.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (c) 2017, Tony Opara
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * - Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Google nor the names of its contributors may be used to
+ * endorse or promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.google.refine.extension.database.pgsql;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.postgresql.jdbc.PgResultSetMetaData;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.refine.extension.database.DatabaseConfiguration;
+import com.google.refine.extension.database.DatabaseService;
+import com.google.refine.extension.database.DatabaseServiceException;
+import com.google.refine.extension.database.DatabaseUtils;
+import com.google.refine.extension.database.SQLType;
+import com.google.refine.extension.database.model.DatabaseColumn;
+import com.google.refine.extension.database.model.DatabaseInfo;
+import com.google.refine.extension.database.model.DatabaseRow;
+import com.google.refine.extension.database.mysql.MySQLConnectionManager;
+
+public class PgSQLDatabaseService extends DatabaseService {
+
+ private static final Logger logger = LoggerFactory.getLogger("PgSQLDatabaseService");
+
+ public static final String DB_NAME = "postgresql";
+ public static final String DB_DRIVER = "org.postgresql.Driver";
+
+ private static PgSQLDatabaseService instance;
+
+ private PgSQLDatabaseService() {
+ }
+
+ public static PgSQLDatabaseService getInstance() {
+ if (instance == null) {
+ SQLType.registerSQLDriver(DB_NAME, DB_DRIVER);
+ instance = new PgSQLDatabaseService();
+ if(logger.isDebugEnabled()) {
+ logger.debug("PgSQLDatabaseService Instance: {}", instance);
+ }
+ }
+ return instance;
+ }
+
+
+ @Override
+ public boolean testConnection(DatabaseConfiguration dbConfig) throws DatabaseServiceException{
+ return PgSQLConnectionManager.getInstance().testConnection(dbConfig);
+
+ }
+
+ @Override
+ public DatabaseInfo connect(DatabaseConfiguration dbConfig) throws DatabaseServiceException{
+ return getMetadata(dbConfig);
+ }
+
+
+ @Override
+ public DatabaseInfo executeQuery(DatabaseConfiguration dbConfig, String query) throws DatabaseServiceException{
+
+
+ try {
+ Connection connection = PgSQLConnectionManager.getInstance().getConnection(dbConfig, false);
+ Statement statement = connection.createStatement();
+ ResultSet queryResult = statement.executeQuery(query);
+ PgResultSetMetaData metadata = (PgResultSetMetaData)queryResult.getMetaData();
+ int columnCount = metadata.getColumnCount();
+ ArrayList columns = new ArrayList(columnCount);
+
+ for (int i = 1; i <= columnCount; i++) {
+ DatabaseColumn dc = new DatabaseColumn(
+ metadata.getColumnName(i),
+ metadata.getColumnLabel(i),
+ DatabaseUtils.getDbColumnType(metadata.getColumnType(i)),
+ metadata.getColumnDisplaySize(i));
+ columns.add(dc);
+ }
+ int index = 0;
+ List rows = new ArrayList();
+
+ while (queryResult.next()) {
+ DatabaseRow row = new DatabaseRow();
+ row.setIndex(index);
+ List values = new ArrayList(columnCount);
+ for (int i = 1; i <= columnCount; i++) {
+
+ values.add(queryResult.getString(i));
+
+ }
+ row.setValues(values);
+ rows.add(row);
+ index++;
+
+ }
+
+ DatabaseInfo dbInfo = new DatabaseInfo();
+ dbInfo.setColumns(columns);
+ dbInfo.setRows(rows);
+ return dbInfo;
+
+ } catch (SQLException e) {
+ logger.error("SQLException::", e);
+ throw new DatabaseServiceException(true, e.getSQLState(), e.getErrorCode(), e.getMessage());
+ }finally {
+ PgSQLConnectionManager.getInstance().shutdown();
+ }
+ }
+
+ /**
+ *
+ * @param connectionInfo
+ * @return
+ * @throws DatabaseServiceException
+ */
+ private DatabaseInfo getMetadata(DatabaseConfiguration connectionInfo) throws DatabaseServiceException {
+
+
+
+ try {
+ Connection connection = PgSQLConnectionManager.getInstance().getConnection(connectionInfo, true);
+ if(connection != null) {
+ java.sql.DatabaseMetaData metadata;
+
+ metadata = connection.getMetaData();
+
+ int dbMajorVersion = metadata.getDatabaseMajorVersion();
+ int dbMinorVersion = metadata.getDatabaseMinorVersion();
+ String dbProductVersion = metadata.getDatabaseProductVersion();
+ String dbProductName = metadata.getDatabaseProductName();
+ DatabaseInfo dbInfo = new DatabaseInfo();
+
+ dbInfo.setDatabaseMajorVersion(dbMajorVersion);
+ dbInfo.setDatabaseMinorVersion(dbMinorVersion);
+ dbInfo.setDatabaseProductVersion(dbProductVersion);
+ dbInfo.setDatabaseProductName(dbProductName);
+ return dbInfo;
+ }
+
+ } catch (SQLException e) {
+ logger.error("SQLException::", e);
+ throw new DatabaseServiceException(true, e.getSQLState(), e.getErrorCode(), e.getMessage());
+ }
+
+ return null;
+ }
+
+ @Override
+ public String buildLimitQuery(Integer limit, Integer offset, String query) {
+ if(logger.isDebugEnabled()) {
+ logger.debug( "<<< original input query::{} >>>" , query );
+ }
+
+ final int len = query.length();
+ String parsedQuery = len > 0 && query.endsWith(";") ? query.substring(0, len - 1) : query;
+
+
+ StringBuilder sb = new StringBuilder();
+ sb.append(parsedQuery);
+
+ if(limit != null) {
+ sb.append(" LIMIT" + " " + limit);
+ }
+
+ if(offset != null) {
+ sb.append(" OFFSET" + " " + offset);
+ }
+ sb.append(";");
+ String parsedQueryOut = sb.toString();
+
+ if(logger.isDebugEnabled()) {
+ logger.debug( "<<>>" , parsedQueryOut );
+ }
+
+ return parsedQueryOut;
+ }
+
+ @Override
+ public ArrayList getColumns(DatabaseConfiguration dbConfig, String query) throws DatabaseServiceException{
+
+ try {
+ Connection connection = PgSQLConnectionManager.getInstance().getConnection(dbConfig, true);
+ Statement statement = connection.createStatement();
+
+ ResultSet queryResult = statement.executeQuery(query);
+ PgResultSetMetaData metadata = (PgResultSetMetaData) queryResult.getMetaData();
+ int columnCount = metadata.getColumnCount();
+ ArrayList columns = new ArrayList(columnCount);
+
+ for (int i = 1; i <= columnCount; i++) {
+ DatabaseColumn dc = new DatabaseColumn(metadata.getColumnName(i), metadata.getColumnLabel(i),
+ DatabaseUtils.getDbColumnType(metadata.getColumnType(i)), metadata.getColumnDisplaySize(i));
+ columns.add(dc);
+ }
+
+ return columns;
+
+ } catch (SQLException e) {
+ logger.error("SQLException::", e);
+ throw new DatabaseServiceException(true, e.getSQLState(), e.getErrorCode(), e.getMessage());
+ }
+
+
+ }
+
+ @Override
+ public List getRows(DatabaseConfiguration dbConfig, String query)
+ throws DatabaseServiceException {
+
+ try {
+ Connection connection = PgSQLConnectionManager.getInstance().getConnection(dbConfig, false);
+ Statement statement = connection.createStatement();
+ statement.setFetchSize(10);
+ ResultSet queryResult = statement.executeQuery(query);
+ PgResultSetMetaData metadata = (PgResultSetMetaData)queryResult.getMetaData();
+ int columnCount = metadata.getColumnCount();
+
+ int index = 0;
+ List rows = new ArrayList();
+
+ while (queryResult.next()) {
+ DatabaseRow row = new DatabaseRow();
+ row.setIndex(index);
+ List values = new ArrayList(columnCount);
+ for (int i = 1; i <= columnCount; i++) {
+ values.add(queryResult.getString(i));
+ }
+ row.setValues(values);
+ rows.add(row);
+ index++;
+
+ }
+
+ return rows;
+
+ } catch (SQLException e) {
+ logger.error("SQLException::{}::{}", e);
+ throw new DatabaseServiceException(true, e.getSQLState(), e.getErrorCode(), e.getMessage());
+ }
+ }
+
+ @Override
+ protected String getDatabaseUrl(DatabaseConfiguration dbConfig) {
+
+ int port = dbConfig.getDatabasePort();
+ return "jdbc:" + dbConfig.getDatabaseType() + "://" + dbConfig.getDatabaseHost()
+ + ((port == 0) ? "" : (":" + port)) + "/" + dbConfig.getDatabaseName() + "?useSSL=" + dbConfig.isUseSSL();
+
+ }
+
+ @Override
+ public Connection getConnection(DatabaseConfiguration dbConfig)
+ throws DatabaseServiceException {
+ return PgSQLConnectionManager.getInstance().getConnection(dbConfig, true);
+ }
+
+ @Override
+ public DatabaseInfo testQuery(DatabaseConfiguration dbConfig, String query)
+ throws DatabaseServiceException {
+ Statement statement = null;
+ ResultSet queryResult = null;
+ try {
+ Connection connection = MySQLConnectionManager.getInstance().getConnection(dbConfig, true);
+ statement = connection.createStatement();
+ queryResult = statement.executeQuery(query);
+
+ DatabaseInfo dbInfo = new DatabaseInfo();
+
+ return dbInfo;
+
+ } catch (SQLException e) {
+ logger.error("SQLException::", e);
+ throw new DatabaseServiceException(true, e.getSQLState(), e.getErrorCode(), e.getMessage());
+ } finally {
+ try {
+ if (queryResult != null) {
+ queryResult.close();
+
+ }
+ if (statement != null) {
+ statement.close();
+
+ }
+ } catch (SQLException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ //MySQLConnectionManager.getInstance().shutdown();
+ }
+ }
+
+}
diff --git a/extensions/database/test/com/google/refine/extension/database/DBExtensionTestUtils.java b/extensions/database/test/com/google/refine/extension/database/DBExtensionTestUtils.java
new file mode 100644
index 000000000..c8df3d8b3
--- /dev/null
+++ b/extensions/database/test/com/google/refine/extension/database/DBExtensionTestUtils.java
@@ -0,0 +1,434 @@
+package com.google.refine.extension.database;
+
+import java.io.File;
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.Date;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+import org.apache.commons.lang.text.StrSubstitutor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DBExtensionTestUtils {
+
+ private static final Logger logger = LoggerFactory.getLogger("DBExtensionTestUtils");
+
+ private static final String MYSQL_DB_NAME = "mysql";
+ private static final String DEFAULT_MYSQL_HOST = "127.0.0.1";
+ private static final int DEFAULT_MYSQL_PORT = 3306;
+ private static final String DEFAULT_MYSQL_USER = "root";
+ private static final String DEFAULT_MYSQL_PASSWORD = "secret";
+ private static final String DEFAULT_MYSQL_DB_NAME = "testdb";
+
+ private static final String PGSQL_DB_NAME = "postgresql";
+ private static final String DEFAULT_PGSQL_HOST = "127.0.0.1";
+ private static final int DEFAULT_PGSQL_PORT = 5432;
+ private static final String DEFAULT_PGSQL_USER = "postgres";
+ private static final String DEFAULT_PGSQL_PASSWORD = "";
+ private static final String DEFAULT_PGSQL_DB_NAME = "openrefine";
+
+ private static final String DEFAULT_TEST_TABLE = "test_data";
+
+ private static final int SAMPLE_SIZE = 500000;
+ private static final int BATCH_SIZE = 1000;
+
+ private static Random rand = new Random();
+
+ private Map mncMap;
+ private Map mccMap;
+
+
+ /**
+ * Create Test Table with one row of Data
+ * @param dbConfig DatabaseConfiguration to test
+ * @param tableName
+ * @throws DatabaseServiceException
+ * @throws SQLException
+ */
+ public static void initTestData(DatabaseConfiguration dbConfig, String tableName)
+ throws DatabaseServiceException, SQLException {
+
+ Statement stmt = null;
+ Connection conn = null;
+ try {
+ DatabaseService dbService = DatabaseService.get(dbConfig.getDatabaseType());
+ conn = dbService.getConnection(dbConfig);
+ stmt = conn.createStatement();
+
+ DatabaseMetaData dbm = conn.getMetaData();
+ // check if "employee" table is there
+ ResultSet tables = dbm.getTables(null, null, tableName, null);
+ if (tables.next()) {
+ stmt.executeUpdate("DROP TABLE " + tableName);
+ //System.out.println("Drop Table Result::" + dropResult);
+ }
+
+ String createSQL = " CREATE TABLE " + tableName + " ( "
+ + " ID INT NOT NULL, "
+ + " NAME VARCHAR (20) NOT NULL, "
+ + " CITY VARCHAR (20) NOT NULL,"
+ + " PRIMARY KEY (ID) );";
+
+
+ stmt.executeUpdate(createSQL);
+ //System.out.println("Create Table Result::" + createResult);
+
+ String insertTableSQL = "INSERT INTO " + tableName
+ + "(ID, NAME, CITY) " + "VALUES"
+ + "(1,'frank lens','Dallas')";
+
+ stmt.executeUpdate(insertTableSQL);
+ // System.out.println("Insert Data Result::" + insertResult);
+
+ logger.info("Database Test Init Data Created!!!");
+
+ } finally {
+ if(stmt != null) {
+ try {
+ stmt.close();
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ }
+ if(conn != null) {
+ try {
+ conn.close();
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ }
+
+
+ public static void initTestData(DatabaseConfiguration dbConfig)
+ throws DatabaseServiceException, SQLException {
+
+ Statement stmt = null;
+ Connection conn = null;
+ try {
+ DatabaseService dbService = DatabaseService.get(dbConfig.getDatabaseType());
+ conn = dbService.getConnection(dbConfig);
+ stmt = conn.createStatement();
+
+ DatabaseMetaData dbm = conn.getMetaData();
+ // check if "employee" table is there
+ ResultSet tables = dbm.getTables(null, null, DEFAULT_TEST_TABLE, null);
+ if (tables.next()) {
+ stmt.executeUpdate("DROP TABLE " + DEFAULT_TEST_TABLE);
+ //System.out.println("Drop Table Result::" + dropResult);
+ }
+
+ String createSQL = " CREATE TABLE TEST_DATA( "
+ + " ID INT NOT NULL, "
+ + " NAME VARCHAR (20) NOT NULL, "
+ + " CITY VARCHAR (20) NOT NULL,"
+ + " PRIMARY KEY (ID) );";
+
+
+ stmt.executeUpdate(createSQL);
+ //System.out.println("Create Table Result::" + createResult);
+
+ String insertTableSQL = "INSERT INTO TEST_DATA"
+ + "(ID, NAME, CITY) " + "VALUES"
+ + "(1,'frank lens','Dallas')";
+
+ stmt.executeUpdate(insertTableSQL);
+ // System.out.println("Insert Data Result::" + insertResult);
+
+ logger.info("Database Test Init Data Created!!!");
+
+ } finally {
+ if(stmt != null) {
+ try {
+ stmt.close();
+ } catch (SQLException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ if(conn != null) {
+ try {
+ conn.close();
+ } catch (SQLException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+
+ }
+
+ /**
+ * CREATE test data in MySQL
+ * Table name: test_data
+ * @param sampleSize
+ * @param batchSize
+ * @throws DatabaseServiceException
+ * @throws SQLException
+ */
+ public void generateMySQLTestData(int sampleSize, int batchSize)
+ throws DatabaseServiceException, SQLException {
+ mncMap = new HashMap();
+ mccMap = new HashMap();
+ mccMap.put(0, 302);
+ mccMap.put(1, 311);
+ mccMap.put(2, 730);
+ mccMap.put(1, 622);
+
+ mncMap.put(0, 006);
+ mncMap.put(1, 140);
+ mncMap.put(2, 380);
+ mncMap.put(3, 710);
+
+ DatabaseConfiguration dc = new DatabaseConfiguration();
+ dc.setDatabaseHost(DEFAULT_MYSQL_HOST);
+ dc.setDatabaseName(DEFAULT_MYSQL_DB_NAME);
+ dc.setDatabasePassword(DEFAULT_MYSQL_PASSWORD);
+ dc.setDatabasePort(DEFAULT_MYSQL_PORT);
+ dc.setDatabaseType(MYSQL_DB_NAME);
+ dc.setDatabaseUser(DEFAULT_MYSQL_USER);
+ dc.setUseSSL(false);
+
+ String truncateTableSQL = "TRUNCATE test_data";
+
+ String insertTableSQL = "INSERT INTO test_data("
+ + "id, ue_id, start_time, end_date, bytes_upload, bytes_download, cell_id, mcc, mnc, lac, imei)"
+ + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);";
+
+ Connection conn = DatabaseService.get(MYSQL_DB_NAME).getConnection(dc);
+
+ Statement truncateStmt = conn.createStatement();
+ int result = truncateStmt.executeUpdate(truncateTableSQL);
+ System.out.println("Truncate Table Result::" + result);
+ truncateStmt.close();
+
+
+ conn.setAutoCommit(false);
+
+ PreparedStatement stmt = conn.prepareStatement(insertTableSQL);
+
+ int counter=1;
+ for (int i = 0; i < sampleSize; i++) {
+ stmt.setLong(1, i);
+ stmt.setString(2, getNextUeId());
+ stmt.setDate(3, getNextStartDate());
+ stmt.setDate(4, getNextEndDate());
+ stmt.setInt(5, rand.nextInt());
+ stmt.setInt(6, rand.nextInt());
+ stmt.setInt(7, rand.nextInt(10));
+ stmt.setInt(8, getMCC());
+ stmt.setInt(9, getMNC());
+ stmt.setInt(10, rand.nextInt(100));
+ stmt.setString(11, getNextIMEI());
+
+ stmt.addBatch();
+
+ //Execute batch of 1000 records
+ if(i%batchSize==0){
+ stmt.executeBatch();
+ conn.commit();
+ System.out.println("Batch "+(counter++)+" executed successfully");
+ }
+ }
+ //execute final batch
+ stmt.executeBatch();
+ System.out.println("Final Batch Executed "+(counter++)+" executed successfully");
+ conn.commit();
+ conn.close();
+ }
+
+ /**
+ *
+ * @param sampleSize
+ * @param batchSize
+ * @throws DatabaseServiceException
+ * @throws SQLException
+ */
+ public void generatePgSQLTestData(int sampleSize, int batchSize) throws DatabaseServiceException, SQLException {
+ mncMap = new HashMap();
+ mccMap = new HashMap();
+ mccMap.put(0, 302);
+ mccMap.put(1, 311);
+ mccMap.put(2, 730);
+ mccMap.put(1, 622);
+
+ mncMap.put(0, 006);
+ mncMap.put(1, 140);
+ mncMap.put(2, 380);
+ mncMap.put(3, 710);
+
+ DatabaseConfiguration dc = new DatabaseConfiguration();
+ dc.setDatabaseHost(DEFAULT_PGSQL_HOST);
+ dc.setDatabaseName(DEFAULT_PGSQL_DB_NAME);
+ dc.setDatabasePassword(DEFAULT_PGSQL_PASSWORD);
+ dc.setDatabasePort(DEFAULT_PGSQL_PORT);
+ dc.setDatabaseType(PGSQL_DB_NAME);
+ dc.setDatabaseUser(DEFAULT_PGSQL_USER);
+ dc.setUseSSL(false);
+
+ String truncateTableSQL = "TRUNCATE public.test_data";
+
+ String insertTableSQL = "INSERT INTO public.test_data("
+ + "id, ue_id, start_time, end_date, bytes_upload, bytes_download, cell_id, mcc, mnc, lac, imei)"
+ + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);";
+
+ Connection conn = DatabaseService.get(PGSQL_DB_NAME).getConnection(dc);
+
+ Statement truncateStmt = conn.createStatement();
+ int result = truncateStmt.executeUpdate(truncateTableSQL);
+ System.out.println("Truncate Table Result::" + result);
+ truncateStmt.close();
+
+
+ conn.setAutoCommit(false);
+
+ PreparedStatement stmt = conn.prepareStatement(insertTableSQL);
+
+ int counter=1;
+ for (int i = 0; i < sampleSize; i++) {
+ stmt.setLong(1, i);
+ stmt.setString(2, getNextUeId());
+ stmt.setDate(3, getNextStartDate());
+ stmt.setDate(4, getNextEndDate());
+ stmt.setInt(5, rand.nextInt());
+ stmt.setInt(6, rand.nextInt());
+ stmt.setInt(7, rand.nextInt(10));
+ stmt.setInt(8, getMCC());
+ stmt.setInt(9, getMNC());
+ stmt.setInt(10, rand.nextInt(100));
+ stmt.setString(11, getNextIMEI());
+
+ stmt.addBatch();
+
+ //Execute batch of 1000 records
+ if(i%batchSize==0){
+ stmt.executeBatch();
+ conn.commit();
+ System.out.println("Batch "+(counter++)+" executed successfully");
+ }
+ }
+ //execute final batch
+ stmt.executeBatch();
+ System.out.println("Final Batch Executed "+(counter++)+" executed successfully");
+ conn.commit();
+ conn.close();
+
+ }
+
+ private String getNextIMEI() {
+ int n = 1000000000 + rand.nextInt(900000000);
+ return "" + n;
+ }
+
+
+
+ private int getMNC() {
+
+ return mncMap.get(rand.nextInt(3));
+ }
+
+ private int getMCC() {
+
+ return mccMap.get(rand.nextInt(3));
+ }
+
+ private Date getNextEndDate() {
+
+ return new Date(System.currentTimeMillis() + 1);
+ }
+
+ private Date getNextStartDate() {
+
+ return new Date(System.currentTimeMillis());
+ }
+
+ private String getNextUeId() {
+
+ int n = 300000000 + rand.nextInt(900000000);
+
+ return "" + n;
+ }
+
+ public static void main(String[] args) throws DatabaseServiceException, SQLException {
+ DBExtensionTestUtils testUtil = new DBExtensionTestUtils();
+ testUtil.generatePgSQLTestData(SAMPLE_SIZE, BATCH_SIZE);
+ // testUtil.generateMySQLTestData();
+ }
+
+
+ public static void cleanUpTestData(DatabaseConfiguration dbConfig) {
+ Statement stmt = null;
+ Connection conn = null;
+ try {
+ DatabaseService dbService = DatabaseService.get(dbConfig.getDatabaseType());
+ conn = dbService.getConnection(dbConfig);
+ stmt = conn.createStatement();
+
+ DatabaseMetaData dbm = conn.getMetaData();
+ // check if "employee" table is there
+ ResultSet tables = dbm.getTables(null, null, DEFAULT_TEST_TABLE, null);
+ if (tables.next()) {
+ stmt.executeUpdate("DROP TABLE " + DEFAULT_TEST_TABLE);
+ //System.out.println("Drop Table Result::" + dropResult);
+ }
+
+ logger.info("Database Test Cleanup Done");
+
+ } catch (DatabaseServiceException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (SQLException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } finally {
+ if(stmt != null) {
+ try {
+ stmt.close();
+ } catch (SQLException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ if(conn != null) {
+ try {
+ conn.close();
+ } catch (SQLException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+
+ }
+
+ public static File createTempDirectory(String name)
+ throws IOException {
+ File dir = File.createTempFile(name, "");
+ dir.delete();
+ dir.mkdir();
+ return dir;
+ }
+
+ public static String getJDBCUrl(DatabaseConfiguration dbConfig) {
+ Map substitutes = new HashMap();
+ substitutes.put("dbType", dbConfig.getDatabaseType());
+ substitutes.put("host", dbConfig.getDatabaseHost());
+ substitutes.put("port", "" + dbConfig.getDatabasePort());
+ substitutes.put("dbName", dbConfig.getDatabaseName());
+ substitutes.put("useSSL", dbConfig.isUseSSL());
+ String urlTemplate = "jdbc:${dbType}://${host}:${port}/${dbName}?useSSL=${useSSL}";
+ StrSubstitutor strSub = new StrSubstitutor(substitutes);
+ return strSub.replace(urlTemplate);
+ }
+
+}
diff --git a/extensions/database/test/com/google/refine/extension/database/DBExtensionTests.java b/extensions/database/test/com/google/refine/extension/database/DBExtensionTests.java
new file mode 100644
index 000000000..7d31085ef
--- /dev/null
+++ b/extensions/database/test/com/google/refine/extension/database/DBExtensionTests.java
@@ -0,0 +1,70 @@
+/*
+
+Copyright 2010,2011 Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+*/
+
+package com.google.refine.extension.database;
+
+import java.util.Properties;
+
+import org.slf4j.Logger;
+
+public class DBExtensionTests {
+
+ protected final String MYSQL_DB_NAME = "mysql";
+ protected final String DEFAULT_MYSQL_HOST = "127.0.0.1";
+ protected final String DEFAULT_MYSQL_PORT = "3306";
+ protected final String DEFAULT_MYSQL_USER = "root";
+ protected final String DEFAULT_MYSQL_PASSWORD = "secret";
+ protected final String DEFAULT_MYSQL_DB_NAME = "testdb";
+
+ protected final String PGSQL_DB_NAME = "postgresql";
+ protected final String DEFAULT_PGSQL_HOST = "127.0.0.1";
+ protected final String DEFAULT_PGSQL_PORT = "5432";
+ protected final String DEFAULT_PGSQL_USER = "postgres";
+ protected final String DEFAULT_PGSQL_PASSWORD = "";
+ protected final String DEFAULT_PGSQL_DB_NAME = "testdb";
+
+
+ protected final String MARIA_DB_NAME = "mariadb";
+ protected final String DEFAULT_MARIADB_HOST = "127.0.0.1";
+ protected final String DEFAULT_MARIADB_PORT = "3306";
+ protected final String DEFAULT_MARIADB_USER = "root";
+ protected final String DEFAULT_MARIADB_PASSWORD = "secret";
+ protected final String DEFAULT_MARIADB_NAME = "testdb";
+
+ protected final String DEFAULT_TEST_TABLE = "test_data";
+
+ protected Properties properties;
+
+ protected Logger logger;
+
+}
diff --git a/extensions/database/test/com/google/refine/extension/database/DatabaseImportControllerTest.java b/extensions/database/test/com/google/refine/extension/database/DatabaseImportControllerTest.java
new file mode 100644
index 000000000..df75a81db
--- /dev/null
+++ b/extensions/database/test/com/google/refine/extension/database/DatabaseImportControllerTest.java
@@ -0,0 +1,259 @@
+package com.google.refine.extension.database;
+
+
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Optional;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+import com.google.refine.ProjectManager;
+import com.google.refine.model.medadata.ProjectMetadata;
+import com.google.refine.RefineServlet;
+import com.google.refine.extension.database.mysql.MySQLDatabaseService;
+import com.google.refine.extension.database.stub.RefineDbServletStub;
+import com.google.refine.importing.ImportingJob;
+import com.google.refine.importing.ImportingManager;
+import com.google.refine.io.FileProjectManager;
+import com.google.refine.model.Project;
+
+
+
+@Test(groups = { "requiresMySQL" })
+public class DatabaseImportControllerTest extends DBExtensionTests{
+
+ @Mock
+ private HttpServletRequest request;
+
+ @Mock
+ private HttpServletResponse response;
+
+ private Project project;
+ private ProjectMetadata metadata;
+ private ImportingJob job;
+ private RefineServlet servlet;
+
+ private String JSON_OPTION = "{\"mode\":\"row-based\"}}";
+
+ private DatabaseConfiguration testDbConfig;
+
+ private String query;
+
+ //System under test
+ private DatabaseImportController SUT = null;
+
+ @BeforeMethod
+ public void setUp() throws JSONException, IOException {
+ MockitoAnnotations.initMocks(this);
+
+ File dir = DBExtensionTestUtils.createTempDirectory("OR_DBExtension_Test_WorkspaceDir");
+ FileProjectManager.initialize(dir);
+
+ servlet = new RefineDbServletStub();
+ ImportingManager.initialize(servlet);
+ project = new Project();
+ metadata = new ProjectMetadata();
+ job = ImportingManager.createJob();
+
+ metadata.setName("Database Import Test Project");
+ ProjectManager.singleton.registerProject(project, metadata);
+ SUT = new DatabaseImportController();
+
+ }
+
+ @AfterMethod
+ public void tearDown() {
+ SUT = null;
+ request = null;
+ response = null;
+ project = null;
+ metadata = null;
+ ImportingManager.disposeJob(job.id);
+ job = null;
+ //options = null;
+ }
+
+
+ @Test
+ public void testDoGet() {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+
+ try {
+ when(response.getWriter()).thenReturn(pw);
+
+ SUT.doGet(request, response);
+
+ String result = sw.getBuffer().toString().trim();
+ JSONObject json = new JSONObject(result);
+
+ String code = json.getString("status");
+ String message = json.getString("message");
+ Assert.assertNotNull(code);
+ Assert.assertNotNull(message);
+ Assert.assertEquals(code, "error");
+ Assert.assertEquals(message, "GET not implemented");
+
+
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ @Test
+ public void testDoPostInvalidSubCommand() throws IOException, ServletException, JSONException {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ when(request.getQueryString()).thenReturn(
+ "http://127.0.0.1:3333/command/core/importing-controller?controller=database/database-import-controller&subCommand=invalid-sub-command");
+
+ when(response.getWriter()).thenReturn(pw);
+ //test
+ SUT.doPost(request, response);
+
+ String result = sw.getBuffer().toString().trim();
+ JSONObject json = new JSONObject(result);
+
+ String code = json.getString("status");
+ String message = json.getString("message");
+ Assert.assertNotNull(code);
+ Assert.assertNotNull(message);
+ Assert.assertEquals(code, "error");
+ Assert.assertEquals(message, "No such sub command");
+ }
+
+
+
+ @Test
+ public void testDoPostInitializeParser() throws ServletException, IOException, JSONException {
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+
+ when(request.getQueryString()).thenReturn(
+ "http://127.0.0.1:3333/command/core/importing-controller?controller=database/database-import-controller&subCommand=initialize-parser-ui");
+ when(response.getWriter()).thenReturn(pw);
+
+ SUT.doPost(request, response);
+
+ String result = sw.getBuffer().toString().trim();
+ JSONObject json = new JSONObject(result);
+
+ String status = json.getString("status");
+ //System.out.println("json::" + json);
+ Assert.assertEquals(status, "ok");
+ }
+
+ @Test
+ public void testDoPostParsePreview() throws IOException, ServletException, JSONException {
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+
+ long jobId = job.id;
+
+ when(request.getQueryString()).thenReturn(
+ "http://127.0.0.1:3333/command/core/importing-controller?controller=database%2Fdatabase-import-controller&jobID="
+ + jobId + "&subCommand=parse-preview");
+ when(response.getWriter()).thenReturn(pw);
+
+
+ when(request.getParameter("databaseType")).thenReturn(testDbConfig.getDatabaseType());
+ when(request.getParameter("databaseServer")).thenReturn(testDbConfig.getDatabaseHost());
+ when(request.getParameter("databasePort")).thenReturn("" + testDbConfig.getDatabasePort());
+ when(request.getParameter("databaseUser")).thenReturn(testDbConfig.getDatabaseUser());
+ when(request.getParameter("databasePassword")).thenReturn(testDbConfig.getDatabasePassword());
+ when(request.getParameter("initialDatabase")).thenReturn(testDbConfig.getDatabaseName());
+ when(request.getParameter("query")).thenReturn(query);
+ when(request.getParameter("options")).thenReturn(JSON_OPTION);
+
+ SUT.doPost(request, response);
+
+ String result = sw.getBuffer().toString().trim();
+ JSONObject json = new JSONObject(result);
+
+ String status = json.getString("status");
+ //System.out.println("json::" + json);
+ Assert.assertEquals(status, "ok");
+ }
+
+ @Test
+ public void testDoPostCreateProject() throws IOException, ServletException, JSONException {
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+
+ long jobId = job.id;
+
+ when(request.getQueryString()).thenReturn(
+ "http://127.0.0.1:3333/command/core/importing-controller?controller=database%2Fdatabase-import-controller&jobID="
+ + jobId + "&subCommand=create-project");
+ when(response.getWriter()).thenReturn(pw);
+
+
+ when(request.getParameter("databaseType")).thenReturn(testDbConfig.getDatabaseType());
+ when(request.getParameter("databaseServer")).thenReturn(testDbConfig.getDatabaseHost());
+ when(request.getParameter("databasePort")).thenReturn("" + testDbConfig.getDatabasePort());
+ when(request.getParameter("databaseUser")).thenReturn(testDbConfig.getDatabaseUser());
+ when(request.getParameter("databasePassword")).thenReturn(testDbConfig.getDatabasePassword());
+ when(request.getParameter("initialDatabase")).thenReturn(testDbConfig.getDatabaseName());
+ when(request.getParameter("query")).thenReturn(query);
+ when(request.getParameter("options")).thenReturn(JSON_OPTION);
+
+
+ SUT.doPost(request, response);
+
+ String result = sw.getBuffer().toString().trim();
+ JSONObject json = new JSONObject(result);
+
+ String status = json.getString("status");
+ //System.out.println("json::" + json);
+ Assert.assertEquals(status, "ok");
+ }
+
+ @BeforeTest
+ @Parameters({ "mySqlDbName", "mySqlDbHost", "mySqlDbPort", "mySqlDbUser", "mySqlDbPassword", "mySqlTestTable"})
+ public void beforeTest(
+ @Optional(DEFAULT_MYSQL_DB_NAME) String mySqlDbName, @Optional(DEFAULT_MYSQL_HOST) String mySqlDbHost,
+ @Optional(DEFAULT_MYSQL_PORT) String mySqlDbPort, @Optional(DEFAULT_MYSQL_USER) String mySqlDbUser,
+ @Optional(DEFAULT_MYSQL_PASSWORD) String mySqlDbPassword, @Optional(DEFAULT_TEST_TABLE) String mySqlTestTable) {
+
+ MockitoAnnotations.initMocks(this);
+
+ testDbConfig = new DatabaseConfiguration();
+ testDbConfig.setDatabaseHost(mySqlDbHost);
+ testDbConfig.setDatabaseName(mySqlDbName);
+ testDbConfig.setDatabasePassword(mySqlDbPassword);
+ testDbConfig.setDatabasePort(Integer.parseInt(mySqlDbPort));
+ testDbConfig.setDatabaseType(MySQLDatabaseService.DB_NAME);
+ testDbConfig.setDatabaseUser(mySqlDbUser);
+ testDbConfig.setUseSSL(false);
+ query = "SELECT count(*) FROM " + mySqlTestTable;
+
+ //testTable = mySqlTestTable;
+
+
+ DatabaseService.DBType.registerDatabase(MySQLDatabaseService.DB_NAME, MySQLDatabaseService.getInstance());
+
+ }
+
+
+}
diff --git a/extensions/database/test/com/google/refine/extension/database/DatabaseServiceTest.java b/extensions/database/test/com/google/refine/extension/database/DatabaseServiceTest.java
new file mode 100644
index 000000000..2999d36c5
--- /dev/null
+++ b/extensions/database/test/com/google/refine/extension/database/DatabaseServiceTest.java
@@ -0,0 +1,150 @@
+package com.google.refine.extension.database;
+
+
+import java.sql.Connection;
+import java.util.List;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Optional;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+import com.google.refine.extension.database.mariadb.MariaDBDatabaseService;
+import com.google.refine.extension.database.model.DatabaseColumn;
+import com.google.refine.extension.database.model.DatabaseInfo;
+import com.google.refine.extension.database.model.DatabaseRow;
+import com.google.refine.extension.database.mysql.MySQLDatabaseService;
+import com.google.refine.extension.database.pgsql.PgSQLDatabaseService;
+
+
+
+
+public class DatabaseServiceTest extends DBExtensionTests{
+
+ private DatabaseConfiguration testDbConfig;
+ private String testTable;
+
+
+ @BeforeTest
+ @Parameters({ "mySqlDbName", "mySqlDbHost", "mySqlDbPort", "mySqlDbUser", "mySqlDbPassword", "mySqlTestTable"})
+ public void beforeTest(@Optional(DEFAULT_MYSQL_DB_NAME) String mySqlDbName, @Optional(DEFAULT_MYSQL_HOST) String mySqlDbHost,
+ @Optional(DEFAULT_MYSQL_PORT) String mySqlDbPort, @Optional(DEFAULT_MYSQL_USER) String mySqlDbUser,
+ @Optional(DEFAULT_MYSQL_PASSWORD) String mySqlDbPassword, @Optional(DEFAULT_TEST_TABLE) String mySqlTestTable) {
+
+
+ testDbConfig = new DatabaseConfiguration();
+ testDbConfig.setDatabaseHost(mySqlDbHost);
+ testDbConfig.setDatabaseName(mySqlDbName);
+ testDbConfig.setDatabasePassword(mySqlDbPassword);
+ testDbConfig.setDatabasePort(Integer.parseInt(mySqlDbPort));
+ testDbConfig.setDatabaseType(MySQLDatabaseService.DB_NAME);
+ testDbConfig.setDatabaseUser(mySqlDbUser);
+ testDbConfig.setUseSSL(false);
+
+ testTable = mySqlTestTable;
+ // DBExtensionTestUtils.initTestData(testDbConfig);
+
+ DatabaseService.DBType.registerDatabase(MariaDBDatabaseService.DB_NAME, MariaDBDatabaseService.getInstance());
+ DatabaseService.DBType.registerDatabase(MySQLDatabaseService.DB_NAME, MySQLDatabaseService.getInstance());
+ DatabaseService.DBType.registerDatabase(PgSQLDatabaseService.DB_NAME, PgSQLDatabaseService.getInstance());
+
+ }
+
+
+
+ @Test
+ public void testGetDatabaseUrl() {
+ DatabaseService dbService = DatabaseService.get(testDbConfig.getDatabaseType());
+ String dbUrl = dbService.getDatabaseUrl(testDbConfig);
+ Assert.assertNotNull(dbUrl);
+ Assert.assertEquals(dbUrl, DBExtensionTestUtils.getJDBCUrl(testDbConfig));
+ }
+
+
+ @Test(groups = {"requiresPgSQL"})
+ public void testGetPgSQLDBService() {
+ DatabaseService dbService = DatabaseService.get(PgSQLDatabaseService.DB_NAME);
+ Assert.assertNotNull(dbService);
+ Assert.assertEquals(dbService.getClass(), PgSQLDatabaseService.class);
+ }
+
+ @Test(groups = {"requiresMySQL"})
+ public void testGetMySQLDBService() {
+
+ DatabaseService dbService = DatabaseService.get(MySQLDatabaseService.DB_NAME);
+ Assert.assertNotNull(dbService);
+ Assert.assertEquals(dbService.getClass(), MySQLDatabaseService.class);
+ }
+
+ @Test(groups = {"requiresMariaDB"})
+ public void testGetMariaDBSQLDBService() {
+
+ DatabaseService dbService = DatabaseService.get(MariaDBDatabaseService.DB_NAME);
+ Assert.assertNotNull(dbService);
+ Assert.assertEquals(dbService.getClass(), MariaDBDatabaseService.class);
+ }
+
+ @Test(groups = {"requiresMySQL"})
+ public void testGetConnection() throws DatabaseServiceException {
+ DatabaseService dbService = DatabaseService.get(testDbConfig.getDatabaseType());
+ Connection conn = dbService.getConnection(testDbConfig);
+ Assert.assertNotNull(conn);
+ }
+
+ @Test(groups = {"requiresMySQL"})
+ public void testTestConnection() throws DatabaseServiceException {
+ DatabaseService dbService = DatabaseService.get(testDbConfig.getDatabaseType());
+ boolean result = dbService.testConnection(testDbConfig);
+ Assert.assertEquals(result, true);
+ }
+
+ @Test(groups = {"requiresMySQL"})
+ public void testConnect() throws DatabaseServiceException {
+ DatabaseService dbService = DatabaseService.get(testDbConfig.getDatabaseType());
+ DatabaseInfo databaseInfo = dbService.connect(testDbConfig);
+ Assert.assertNotNull(databaseInfo);
+ }
+
+ @Test(groups = {"requiresMySQL"})
+ public void testExecuteQuery() throws DatabaseServiceException {
+ DatabaseService dbService = DatabaseService.get(testDbConfig.getDatabaseType());
+ DatabaseInfo databaseInfo = dbService.testQuery(testDbConfig,
+ "SELECT * FROM " + testTable);
+
+ Assert.assertNotNull(databaseInfo);
+ }
+
+ @Test
+ public void testBuildLimitQuery() {
+ DatabaseService dbService = DatabaseService.get(testDbConfig.getDatabaseType());
+ String limitQuery = dbService.buildLimitQuery(100, 0, "SELECT * FROM " + testTable);
+
+ Assert.assertNotNull(limitQuery);
+
+ Assert.assertEquals(limitQuery, "SELECT * FROM " + testTable + " LIMIT " + 100 + " OFFSET " + 0 + ";");
+ }
+
+ @Test(groups = {"requiresMySQL"})
+ public void testGetColumns() throws DatabaseServiceException {
+ List dbColumns;
+
+ DatabaseService dbService = DatabaseService.get(testDbConfig.getDatabaseType());
+ dbColumns = dbService.getColumns(testDbConfig,"SELECT * FROM " + testTable);
+ Assert.assertNotNull(dbColumns);
+
+ int cols = dbColumns.size();
+ Assert.assertEquals(cols, 10);
+ }
+
+ @Test(groups = {"requiresMySQL"})
+ public void testGetRows() throws DatabaseServiceException {
+ DatabaseService dbService = DatabaseService.get(testDbConfig.getDatabaseType());
+ List dbRows = dbService.getRows(testDbConfig,
+ "SELECT * FROM " + testTable);
+
+ Assert.assertNotNull(dbRows);
+ Assert.assertEquals(dbRows.size(), 1);
+ }
+
+}
diff --git a/extensions/database/test/com/google/refine/extension/database/DatabaseTestConfig.java b/extensions/database/test/com/google/refine/extension/database/DatabaseTestConfig.java
new file mode 100644
index 000000000..ffebbe8e6
--- /dev/null
+++ b/extensions/database/test/com/google/refine/extension/database/DatabaseTestConfig.java
@@ -0,0 +1,81 @@
+package com.google.refine.extension.database;
+
+import java.sql.SQLException;
+
+import org.testng.annotations.AfterSuite;
+import org.testng.annotations.BeforeSuite;
+import org.testng.annotations.Optional;
+import org.testng.annotations.Parameters;
+
+import com.google.refine.extension.database.mariadb.MariaDBDatabaseService;
+import com.google.refine.extension.database.mysql.MySQLDatabaseService;
+import com.google.refine.extension.database.pgsql.PgSQLDatabaseService;
+
+public class DatabaseTestConfig extends DBExtensionTests {
+
+ private DatabaseConfiguration mysqlDbConfig;
+ private DatabaseConfiguration pgsqlDbConfig;
+ private DatabaseConfiguration mariadbDbConfig;
+
+ @BeforeSuite
+ @Parameters({ "mySqlDbName", "mySqlDbHost", "mySqlDbPort", "mySqlDbUser", "mySqlDbPassword", "mySqlTestTable",
+ "pgSqlDbName", "pgSqlDbHost", "pgSqlDbPort", "pgSqlDbUser", "pgSqlDbPassword", "pgSqlTestTable",
+ "mariadbDbName", "mariadbDbHost", "mariadbDbPort", "mariadbyDbUser", "mariadbDbPassword", "mariadbTestTable"})
+ public void beforeSuite(
+ @Optional(DEFAULT_MYSQL_DB_NAME) String mySqlDbName, @Optional(DEFAULT_MYSQL_HOST) String mySqlDbHost,
+ @Optional(DEFAULT_MYSQL_PORT) String mySqlDbPort, @Optional(DEFAULT_MYSQL_USER) String mySqlDbUser,
+ @Optional(DEFAULT_MYSQL_PASSWORD) String mySqlDbPassword, @Optional(DEFAULT_TEST_TABLE) String mySqlTestTable,
+
+ @Optional(DEFAULT_PGSQL_DB_NAME) String pgSqlDbName, @Optional(DEFAULT_PGSQL_HOST) String pgSqlDbHost,
+ @Optional(DEFAULT_PGSQL_PORT) String pgSqlDbPort, @Optional(DEFAULT_PGSQL_USER) String pgSqlDbUser,
+ @Optional(DEFAULT_PGSQL_PASSWORD) String pgSqlDbPassword, @Optional(DEFAULT_TEST_TABLE) String pgSqlTestTable,
+
+ @Optional(DEFAULT_MARIADB_NAME) String mariadbDbName, @Optional(DEFAULT_MARIADB_HOST) String mariadbDbHost,
+ @Optional(DEFAULT_MARIADB_PORT) String mariadbDbPort, @Optional(DEFAULT_MARIADB_USER) String mariadbyDbUser,
+ @Optional(DEFAULT_MARIADB_PASSWORD) String mariadbDbPassword, @Optional(DEFAULT_TEST_TABLE) String mariadbTestTable)
+ throws DatabaseServiceException, SQLException {
+
+ //System.out.println("@BeforeSuite\n");
+ mysqlDbConfig = new DatabaseConfiguration();
+ mysqlDbConfig.setDatabaseHost(mySqlDbHost);
+ mysqlDbConfig.setDatabaseName(mySqlDbName);
+ mysqlDbConfig.setDatabasePassword(mySqlDbPassword);
+ mysqlDbConfig.setDatabasePort(Integer.parseInt(mySqlDbPort));
+ mysqlDbConfig.setDatabaseType(MySQLDatabaseService.DB_NAME);
+ mysqlDbConfig.setDatabaseUser(mySqlDbUser);
+ mysqlDbConfig.setUseSSL(false);
+
+ pgsqlDbConfig = new DatabaseConfiguration();
+ pgsqlDbConfig.setDatabaseHost(pgSqlDbHost);
+ pgsqlDbConfig.setDatabaseName(pgSqlDbName);
+ pgsqlDbConfig.setDatabasePassword(pgSqlDbPassword);
+ pgsqlDbConfig.setDatabasePort(Integer.parseInt(pgSqlDbPort));
+ pgsqlDbConfig.setDatabaseType(PgSQLDatabaseService.DB_NAME);
+ pgsqlDbConfig.setDatabaseUser(pgSqlDbUser);
+ pgsqlDbConfig.setUseSSL(false);
+
+ mariadbDbConfig = new DatabaseConfiguration();
+ mariadbDbConfig.setDatabaseHost(mariadbDbHost);
+ mariadbDbConfig.setDatabaseName(mariadbDbName);
+ mariadbDbConfig.setDatabasePassword(mariadbDbPassword);
+ mariadbDbConfig.setDatabasePort(Integer.parseInt(mariadbDbPort));
+ mariadbDbConfig.setDatabaseType(MariaDBDatabaseService.DB_NAME);
+ mariadbDbConfig.setDatabaseUser(mariadbyDbUser);
+ mariadbDbConfig.setUseSSL(false);
+
+ DBExtensionTestUtils.initTestData(mysqlDbConfig);
+ DBExtensionTestUtils.initTestData(pgsqlDbConfig);
+ DBExtensionTestUtils.initTestData(mariadbDbConfig);
+ }
+
+ @AfterSuite
+ public void afterSuite() {
+ // System.out.println("@AfterSuite");
+
+ DBExtensionTestUtils.cleanUpTestData(mysqlDbConfig);
+ DBExtensionTestUtils.cleanUpTestData(pgsqlDbConfig);
+ DBExtensionTestUtils.cleanUpTestData(mariadbDbConfig);
+ }
+
+}
+
diff --git a/extensions/database/test/com/google/refine/extension/database/SimpleTextEncryptorTest.java b/extensions/database/test/com/google/refine/extension/database/SimpleTextEncryptorTest.java
new file mode 100644
index 000000000..1ef21450f
--- /dev/null
+++ b/extensions/database/test/com/google/refine/extension/database/SimpleTextEncryptorTest.java
@@ -0,0 +1,28 @@
+package com.google.refine.extension.database;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class SimpleTextEncryptorTest {
+
+ @Test
+ public void encrypt() {
+ SimpleTextEncryptor textEncryptor = new SimpleTextEncryptor("WEWssa!@d445d");
+ String password = "testpass";
+ String encPass = textEncryptor.encrypt(password);
+ Assert.assertNotNull(encPass);
+ Assert.assertNotEquals(encPass, password);
+
+ }
+
+ @Test
+ public void decrypt() {
+ SimpleTextEncryptor textEncryptor = new SimpleTextEncryptor("OOEWssa!@d445d");
+ String password = "testpass";
+ String encPass = textEncryptor.encrypt(password);
+ Assert.assertNotNull(encPass);
+ Assert.assertNotEquals(encPass, password);
+ String decPass = textEncryptor.decrypt(encPass);
+ Assert.assertEquals(decPass, password);
+ }
+}
diff --git a/extensions/database/test/com/google/refine/extension/database/cmd/ConnectCommandTest.java b/extensions/database/test/com/google/refine/extension/database/cmd/ConnectCommandTest.java
new file mode 100644
index 000000000..13463a1bd
--- /dev/null
+++ b/extensions/database/test/com/google/refine/extension/database/cmd/ConnectCommandTest.java
@@ -0,0 +1,98 @@
+
+package com.google.refine.extension.database.cmd;
+
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.testng.Assert;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Optional;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+import com.google.refine.extension.database.DBExtensionTests;
+import com.google.refine.extension.database.DatabaseConfiguration;
+import com.google.refine.extension.database.DatabaseService;
+import com.google.refine.extension.database.mysql.MySQLDatabaseService;
+
+
+@Test(groups = { "requiresMySQL" })
+public class ConnectCommandTest extends DBExtensionTests {
+
+
+ @Mock
+ private HttpServletRequest request;
+
+ @Mock
+ private HttpServletResponse response;
+
+ private DatabaseConfiguration testDbConfig;
+ // private String testTable;
+
+
+ @BeforeTest
+ @Parameters({ "mySqlDbName", "mySqlDbHost", "mySqlDbPort", "mySqlDbUser", "mySqlDbPassword", "mySqlTestTable"})
+ public void beforeTest(@Optional(DEFAULT_MYSQL_DB_NAME) String mySqlDbName, @Optional(DEFAULT_MYSQL_HOST) String mySqlDbHost,
+ @Optional(DEFAULT_MYSQL_PORT) String mySqlDbPort, @Optional(DEFAULT_MYSQL_USER) String mySqlDbUser,
+ @Optional(DEFAULT_MYSQL_PASSWORD) String mySqlDbPassword, @Optional(DEFAULT_TEST_TABLE) String mySqlTestTable) {
+
+ MockitoAnnotations.initMocks(this);
+
+ testDbConfig = new DatabaseConfiguration();
+ testDbConfig.setDatabaseHost(mySqlDbHost);
+ testDbConfig.setDatabaseName(mySqlDbName);
+ testDbConfig.setDatabasePassword(mySqlDbPassword);
+ testDbConfig.setDatabasePort(Integer.parseInt(mySqlDbPort));
+ testDbConfig.setDatabaseType(MySQLDatabaseService.DB_NAME);
+ testDbConfig.setDatabaseUser(mySqlDbUser);
+ testDbConfig.setUseSSL(false);
+
+ //testTable = mySqlTestTable;
+ //DBExtensionTestUtils.initTestData(testDbConfig);
+
+ DatabaseService.DBType.registerDatabase(MySQLDatabaseService.DB_NAME, MySQLDatabaseService.getInstance());
+
+ }
+
+
+ @Test
+ public void testDoPost() throws IOException, ServletException, JSONException {
+
+ when(request.getParameter("databaseType")).thenReturn(MySQLDatabaseService.DB_NAME);
+ when(request.getParameter("databaseServer")).thenReturn(testDbConfig.getDatabaseHost());
+ when(request.getParameter("databasePort")).thenReturn("" + testDbConfig.getDatabasePort());
+ when(request.getParameter("databaseUser")).thenReturn(testDbConfig.getDatabaseUser());
+ when(request.getParameter("databasePassword")).thenReturn(testDbConfig.getDatabasePassword());
+ when(request.getParameter("initialDatabase")).thenReturn(testDbConfig.getDatabaseName());
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+
+ when(response.getWriter()).thenReturn(pw);
+ ConnectCommand connectCommand = new ConnectCommand();
+
+ connectCommand.doPost(request, response);
+
+ String result = sw.getBuffer().toString().trim();
+ JSONObject json = new JSONObject(result);
+
+ String code = json.getString("code");
+ Assert.assertEquals(code, "ok");
+
+ String databaseInfo = json.getString("databaseInfo");
+ Assert.assertNotNull(databaseInfo);
+ }
+
+
+}
diff --git a/extensions/database/test/com/google/refine/extension/database/cmd/ExecuteQueryCommandTest.java b/extensions/database/test/com/google/refine/extension/database/cmd/ExecuteQueryCommandTest.java
new file mode 100644
index 000000000..935c9e04f
--- /dev/null
+++ b/extensions/database/test/com/google/refine/extension/database/cmd/ExecuteQueryCommandTest.java
@@ -0,0 +1,96 @@
+package com.google.refine.extension.database.cmd;
+
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.testng.Assert;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Optional;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+import com.google.refine.extension.database.DBExtensionTests;
+import com.google.refine.extension.database.DatabaseConfiguration;
+import com.google.refine.extension.database.DatabaseService;
+import com.google.refine.extension.database.mysql.MySQLDatabaseService;
+
+@Test(groups = { "requiresMySQL" })
+public class ExecuteQueryCommandTest extends DBExtensionTests {
+
+ @Mock
+ private HttpServletRequest request;
+
+ @Mock
+ private HttpServletResponse response;
+
+
+ private DatabaseConfiguration testDbConfig;
+ private String testTable;
+
+
+ @BeforeTest
+ @Parameters({ "mySqlDbName", "mySqlDbHost", "mySqlDbPort", "mySqlDbUser", "mySqlDbPassword", "mySqlTestTable"})
+ public void beforeTest(@Optional(DEFAULT_MYSQL_DB_NAME) String mySqlDbName, @Optional(DEFAULT_MYSQL_HOST) String mySqlDbHost,
+ @Optional(DEFAULT_MYSQL_PORT) String mySqlDbPort, @Optional(DEFAULT_MYSQL_USER) String mySqlDbUser,
+ @Optional(DEFAULT_MYSQL_PASSWORD) String mySqlDbPassword, @Optional(DEFAULT_TEST_TABLE) String mySqlTestTable) {
+
+ MockitoAnnotations.initMocks(this);
+ testDbConfig = new DatabaseConfiguration();
+ testDbConfig.setDatabaseHost(mySqlDbHost);
+ testDbConfig.setDatabaseName(mySqlDbName);
+ testDbConfig.setDatabasePassword(mySqlDbPassword);
+ testDbConfig.setDatabasePort(Integer.parseInt(mySqlDbPort));
+ testDbConfig.setDatabaseType(MySQLDatabaseService.DB_NAME);
+ testDbConfig.setDatabaseUser(mySqlDbUser);
+ testDbConfig.setUseSSL(false);
+
+ testTable = mySqlTestTable;
+ // DBExtensionTestUtils.initTestData(testDbConfig);
+
+ DatabaseService.DBType.registerDatabase(MySQLDatabaseService.DB_NAME, MySQLDatabaseService.getInstance());
+
+ }
+
+ @Test
+ public void testDoPost() throws IOException, ServletException, JSONException {
+
+ when(request.getParameter("databaseType")).thenReturn(testDbConfig.getDatabaseType());
+ when(request.getParameter("databaseServer")).thenReturn(testDbConfig.getDatabaseHost());
+ when(request.getParameter("databasePort")).thenReturn("" + testDbConfig.getDatabasePort());
+ when(request.getParameter("databaseUser")).thenReturn(testDbConfig.getDatabaseUser());
+ when(request.getParameter("databasePassword")).thenReturn(testDbConfig.getDatabasePassword());
+ when(request.getParameter("initialDatabase")).thenReturn(testDbConfig.getDatabaseName());
+ when(request.getParameter("queryString")).thenReturn("SELECT count(*) FROM " + testTable);
+
+
+ StringWriter sw = new StringWriter();
+
+ PrintWriter pw = new PrintWriter(sw);
+
+ when(response.getWriter()).thenReturn(pw);
+ ExecuteQueryCommand executeQueryCommand = new ExecuteQueryCommand();
+
+ executeQueryCommand.doPost(request, response);
+
+ String result = sw.getBuffer().toString().trim();
+ JSONObject json = new JSONObject(result);
+
+ String code = json.getString("code");
+ Assert.assertEquals(code, "ok");
+
+ String queryResult = json.getString("QueryResult");
+ Assert.assertNotNull(queryResult);
+ }
+
+}
diff --git a/extensions/database/test/com/google/refine/extension/database/cmd/SavedConnectionCommandTest.java b/extensions/database/test/com/google/refine/extension/database/cmd/SavedConnectionCommandTest.java
new file mode 100644
index 000000000..0dc52016c
--- /dev/null
+++ b/extensions/database/test/com/google/refine/extension/database/cmd/SavedConnectionCommandTest.java
@@ -0,0 +1,293 @@
+package com.google.refine.extension.database.cmd;
+
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Optional;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+import com.google.refine.ProjectManager;
+import com.google.refine.model.medadata.ProjectMetadata;
+import com.google.refine.RefineServlet;
+import com.google.refine.extension.database.DBExtensionTestUtils;
+import com.google.refine.extension.database.DBExtensionTests;
+import com.google.refine.extension.database.DatabaseConfiguration;
+import com.google.refine.extension.database.DatabaseService;
+import com.google.refine.extension.database.mysql.MySQLDatabaseService;
+import com.google.refine.extension.database.stub.RefineDbServletStub;
+import com.google.refine.importing.ImportingManager;
+import com.google.refine.io.FileProjectManager;
+import com.google.refine.model.Project;
+
+public class SavedConnectionCommandTest extends DBExtensionTests{
+
+ @Mock
+ private HttpServletRequest request;
+
+ @Mock
+ private HttpServletResponse response;
+
+ private DatabaseConfiguration testDbConfig;
+
+ private Project project;
+ private ProjectMetadata metadata;
+ //private ImportingJob job;
+ private RefineServlet servlet;
+
+ // private String JSON_OPTION = "{\"mode\":\"row-based\"}}";
+
+
+ //System under test
+ private SavedConnectionCommand SUT = null;
+
+ @BeforeMethod
+ public void setUp() throws JSONException, IOException {
+ MockitoAnnotations.initMocks(this);
+
+ File dir = DBExtensionTestUtils.createTempDirectory("OR_DBExtension_Test_WorkspaceDir");
+ FileProjectManager.initialize(dir);
+
+ servlet = new RefineDbServletStub();
+ ImportingManager.initialize(servlet);
+ project = new Project();
+ metadata = new ProjectMetadata();
+ //job = ImportingManager.createJob();
+
+ metadata.setName("Save DB Config Test Project");
+ ProjectManager.singleton.registerProject(project, metadata);
+ SUT = new SavedConnectionCommand();
+
+ }
+
+ @AfterMethod
+ public void tearDown() {
+ SUT = null;
+ request = null;
+ response = null;
+ project = null;
+ metadata = null;
+ // ImportingManager.disposeJob(job.id);
+ // job = null;
+ //options = null;
+ }
+
+ @BeforeTest
+ @Parameters({ "mySqlDbName", "mySqlDbHost", "mySqlDbPort", "mySqlDbUser", "mySqlDbPassword", "mySqlTestTable"})
+ public void beforeTest(@Optional(DEFAULT_MYSQL_DB_NAME) String mySqlDbName, @Optional(DEFAULT_MYSQL_HOST) String mySqlDbHost,
+ @Optional(DEFAULT_MYSQL_PORT) String mySqlDbPort, @Optional(DEFAULT_MYSQL_USER) String mySqlDbUser,
+ @Optional(DEFAULT_MYSQL_PASSWORD) String mySqlDbPassword, @Optional(DEFAULT_TEST_TABLE) String mySqlTestTable) {
+
+ // MockitoAnnotations.initMocks(this);
+ testDbConfig = new DatabaseConfiguration();
+ testDbConfig.setDatabaseHost(mySqlDbHost);
+ testDbConfig.setDatabaseName(mySqlDbName);
+ testDbConfig.setDatabasePassword(mySqlDbPassword);
+ testDbConfig.setDatabasePort(Integer.parseInt(mySqlDbPort));
+ testDbConfig.setDatabaseType(MySQLDatabaseService.DB_NAME);
+ testDbConfig.setDatabaseUser(mySqlDbUser);
+ testDbConfig.setUseSSL(false);
+
+ DatabaseService.DBType.registerDatabase(MySQLDatabaseService.DB_NAME, MySQLDatabaseService.getInstance());
+
+ }
+
+
+
+ private void saveDatabaseConfiguration(String savedDbName) {
+
+ when(request.getParameter("connectionName")).thenReturn(savedDbName);
+ when(request.getParameter("databaseType")).thenReturn(MySQLDatabaseService.DB_NAME);
+ when(request.getParameter("databaseServer")).thenReturn(testDbConfig.getDatabaseHost());
+ when(request.getParameter("databasePort")).thenReturn("" + testDbConfig.getDatabasePort());
+ when(request.getParameter("databaseUser")).thenReturn(testDbConfig.getDatabaseUser());
+ when(request.getParameter("databasePassword")).thenReturn(testDbConfig.getDatabasePassword());
+ when(request.getParameter("initialDatabase")).thenReturn(testDbConfig.getDatabaseName());
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+
+ try {
+ when(response.getWriter()).thenReturn(pw);
+
+ SUT.doPost(request, response);
+
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+
+ @Test
+ public void testDoPost() throws IOException, ServletException, JSONException {
+
+ when(request.getParameter("connectionName")).thenReturn("test-db-name");
+ when(request.getParameter("databaseType")).thenReturn(MySQLDatabaseService.DB_NAME);
+ when(request.getParameter("databaseServer")).thenReturn(testDbConfig.getDatabaseHost());
+ when(request.getParameter("databasePort")).thenReturn("" + testDbConfig.getDatabasePort());
+ when(request.getParameter("databaseUser")).thenReturn(testDbConfig.getDatabaseUser());
+ when(request.getParameter("databasePassword")).thenReturn(testDbConfig.getDatabasePassword());
+ when(request.getParameter("initialDatabase")).thenReturn(testDbConfig.getDatabaseName());
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+
+ when(response.getWriter()).thenReturn(pw);
+
+ SUT.doPost(request, response);
+
+ String result = sw.getBuffer().toString().trim();
+
+ JSONObject json = new JSONObject(result);
+
+ JSONArray savedConnections = json.getJSONArray("savedConnections");
+ Assert.assertNotNull(savedConnections);
+
+ int len = savedConnections.length();
+
+ Assert.assertEquals(len, 1);
+ }
+
+ @Test
+ public void testDoGet() throws IOException, ServletException, JSONException {
+ String testDbName = "testLocalDb";
+ //add saved connection
+ saveDatabaseConfiguration(testDbName);
+
+
+ when(request.getParameter("connectionName")).thenReturn(testDbName);
+ when(request.getParameter("databaseType")).thenReturn(MySQLDatabaseService.DB_NAME);
+ when(request.getParameter("databaseServer")).thenReturn(testDbConfig.getDatabaseHost());
+ when(request.getParameter("databasePort")).thenReturn("" + testDbConfig.getDatabasePort());
+ when(request.getParameter("databaseUser")).thenReturn(testDbConfig.getDatabaseUser());
+ when(request.getParameter("databasePassword")).thenReturn(testDbConfig.getDatabasePassword());
+ when(request.getParameter("initialDatabase")).thenReturn(testDbConfig.getDatabaseName());
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+
+ when(response.getWriter()).thenReturn(pw);
+
+ SUT.doGet(request, response);
+
+ JSONObject json = new JSONObject(sw.getBuffer().toString().trim());
+
+ JSONArray savedConnections = json.getJSONArray("savedConnections");
+ Assert.assertNotNull(savedConnections);
+
+ Assert.assertEquals(savedConnections.length(), 1);
+
+ JSONObject sc = (JSONObject)savedConnections.get(0);
+ // System.out.println("sc" + sc);
+ String connName = sc.getString("connectionName");
+ Assert.assertEquals(connName, testDbName);
+ }
+
+ @Test
+ public void testDoPut() throws IOException, ServletException, JSONException {
+ String testDbName = "testLocalDb";
+ saveDatabaseConfiguration(testDbName);
+
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+
+ when(response.getWriter()).thenReturn(pw);
+
+ //modify database config
+ String newHost = "localhost";
+ when(request.getParameter("connectionName")).thenReturn(testDbName);
+ when(request.getParameter("databaseType")).thenReturn(MySQLDatabaseService.DB_NAME);
+ when(request.getParameter("databaseServer")).thenReturn(newHost);
+ when(request.getParameter("databasePort")).thenReturn("" + testDbConfig.getDatabasePort());
+ when(request.getParameter("databaseUser")).thenReturn(testDbConfig.getDatabaseUser());
+ when(request.getParameter("databasePassword")).thenReturn(testDbConfig.getDatabasePassword());
+ when(request.getParameter("initialDatabase")).thenReturn(testDbConfig.getDatabaseName());
+
+ SUT.doPut(request, response);
+
+ JSONObject json = new JSONObject(sw.getBuffer().toString().trim());
+ JSONArray savedConnections = json.getJSONArray("savedConnections");
+ Assert.assertNotNull(savedConnections);
+
+ Assert.assertEquals(savedConnections.length(), 1);
+
+ JSONObject sc = (JSONObject)savedConnections.get(0);
+ System.out.println("sc" + sc);
+ String newDbHost = sc.getString("databaseHost");
+ Assert.assertEquals(newDbHost, newHost);
+ }
+
+ @Test
+ public void testDoDeleteValidConnectionName() {
+ String testDbName = "testLocalDb";
+ saveDatabaseConfiguration(testDbName);
+
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+
+ try {
+ when(response.getWriter()).thenReturn(pw);
+ when(request.getParameter("connectionName")).thenReturn(testDbName);
+ SUT.doDelete(request, response);
+
+ JSONObject json = new JSONObject(sw.getBuffer().toString().trim());
+ JSONArray savedConnections = json.getJSONArray("savedConnections");
+ Assert.assertNotNull(savedConnections);
+
+ Assert.assertEquals(savedConnections.length(), 0);
+
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ @Test
+ public void testDoDeleteInValidConnectionName() {
+ String testDbName = "testLocalDb";
+ saveDatabaseConfiguration(testDbName);
+
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+
+ try {
+ when(response.getWriter()).thenReturn(pw);
+
+ when(request.getParameter("connectionName")).thenReturn("noDbName");
+
+ SUT.doDelete(request, response);
+
+ // String result = sw.getBuffer().toString().trim();
+
+ JSONObject json = new JSONObject();
+
+ Assert.assertNotNull(json);
+
+
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/extensions/database/test/com/google/refine/extension/database/cmd/TestConnectCommandTest.java b/extensions/database/test/com/google/refine/extension/database/cmd/TestConnectCommandTest.java
new file mode 100644
index 000000000..178344ada
--- /dev/null
+++ b/extensions/database/test/com/google/refine/extension/database/cmd/TestConnectCommandTest.java
@@ -0,0 +1,96 @@
+package com.google.refine.extension.database.cmd;
+
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.testng.Assert;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Optional;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+import com.google.refine.extension.database.DBExtensionTests;
+import com.google.refine.extension.database.DatabaseConfiguration;
+import com.google.refine.extension.database.DatabaseService;
+import com.google.refine.extension.database.mysql.MySQLDatabaseService;
+
+
+@Test(groups = { "requiresMySQL" })
+public class TestConnectCommandTest extends DBExtensionTests{
+
+ @Mock
+ private HttpServletRequest request;
+
+ @Mock
+ private HttpServletResponse response;
+
+ private DatabaseConfiguration testDbConfig;
+ // private String testTable;
+
+
+ @BeforeTest
+ @Parameters({ "mySqlDbName", "mySqlDbHost", "mySqlDbPort", "mySqlDbUser", "mySqlDbPassword", "mySqlTestTable"})
+ public void beforeTest(@Optional(DEFAULT_MYSQL_DB_NAME) String mySqlDbName, @Optional(DEFAULT_MYSQL_HOST) String mySqlDbHost,
+ @Optional(DEFAULT_MYSQL_PORT) String mySqlDbPort, @Optional(DEFAULT_MYSQL_USER) String mySqlDbUser,
+ @Optional(DEFAULT_MYSQL_PASSWORD) String mySqlDbPassword, @Optional(DEFAULT_TEST_TABLE) String mySqlTestTable) {
+
+ MockitoAnnotations.initMocks(this);
+
+ testDbConfig = new DatabaseConfiguration();
+ testDbConfig.setDatabaseHost(mySqlDbHost);
+ testDbConfig.setDatabaseName(mySqlDbName);
+ testDbConfig.setDatabasePassword(mySqlDbPassword);
+ testDbConfig.setDatabasePort(Integer.parseInt(mySqlDbPort));
+ testDbConfig.setDatabaseType(MySQLDatabaseService.DB_NAME);
+ testDbConfig.setDatabaseUser(mySqlDbUser);
+ testDbConfig.setUseSSL(false);
+
+ //testTable = mySqlTestTable;
+ // DBExtensionTestUtils.initTestData(testDbConfig);
+
+ DatabaseService.DBType.registerDatabase(MySQLDatabaseService.DB_NAME, MySQLDatabaseService.getInstance());
+
+ }
+
+
+
+ @Test
+ public void testDoPost() throws IOException, ServletException, JSONException {
+
+ when(request.getParameter("databaseType")).thenReturn(MySQLDatabaseService.DB_NAME);
+ when(request.getParameter("databaseServer")).thenReturn(testDbConfig.getDatabaseHost());
+ when(request.getParameter("databasePort")).thenReturn("" + testDbConfig.getDatabasePort());
+ when(request.getParameter("databaseUser")).thenReturn(testDbConfig.getDatabaseUser());
+ when(request.getParameter("databasePassword")).thenReturn(testDbConfig.getDatabasePassword());
+ when(request.getParameter("initialDatabase")).thenReturn(testDbConfig.getDatabaseName());
+
+
+ StringWriter sw = new StringWriter();
+
+ PrintWriter pw = new PrintWriter(sw);
+
+ when(response.getWriter()).thenReturn(pw);
+ TestConnectCommand connectCommand = new TestConnectCommand();
+
+ connectCommand.doPost(request, response);
+
+ String result = sw.getBuffer().toString().trim();
+ JSONObject json = new JSONObject(result);
+
+ String code = json.getString("code");
+ Assert.assertEquals(code, "ok");
+
+ }
+
+}
diff --git a/extensions/database/test/com/google/refine/extension/database/cmd/TestQueryCommandTest.java b/extensions/database/test/com/google/refine/extension/database/cmd/TestQueryCommandTest.java
new file mode 100644
index 000000000..8ef3047e6
--- /dev/null
+++ b/extensions/database/test/com/google/refine/extension/database/cmd/TestQueryCommandTest.java
@@ -0,0 +1,98 @@
+package com.google.refine.extension.database.cmd;
+
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.testng.Assert;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Optional;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+import com.google.refine.extension.database.DBExtensionTests;
+import com.google.refine.extension.database.DatabaseConfiguration;
+import com.google.refine.extension.database.DatabaseService;
+import com.google.refine.extension.database.mysql.MySQLDatabaseService;
+
+@Test(groups = { "requiresMySQL" })
+public class TestQueryCommandTest extends DBExtensionTests {
+
+ @Mock
+ private HttpServletRequest request;
+
+ @Mock
+ private HttpServletResponse response;
+
+
+ private DatabaseConfiguration testDbConfig;
+ private String testTable;
+
+
+ @BeforeTest
+ @Parameters({ "mySqlDbName", "mySqlDbHost", "mySqlDbPort", "mySqlDbUser", "mySqlDbPassword", "mySqlTestTable"})
+ public void beforeTest(@Optional(DEFAULT_MYSQL_DB_NAME) String mySqlDbName, @Optional(DEFAULT_MYSQL_HOST) String mySqlDbHost,
+ @Optional(DEFAULT_MYSQL_PORT) String mySqlDbPort, @Optional(DEFAULT_MYSQL_USER) String mySqlDbUser,
+ @Optional(DEFAULT_MYSQL_PASSWORD) String mySqlDbPassword, @Optional(DEFAULT_TEST_TABLE) String mySqlTestTable) {
+
+ MockitoAnnotations.initMocks(this);
+ testDbConfig = new DatabaseConfiguration();
+ testDbConfig.setDatabaseHost(mySqlDbHost);
+ testDbConfig.setDatabaseName(mySqlDbName);
+ testDbConfig.setDatabasePassword(mySqlDbPassword);
+ testDbConfig.setDatabasePort(Integer.parseInt(mySqlDbPort));
+ testDbConfig.setDatabaseType(MySQLDatabaseService.DB_NAME);
+ testDbConfig.setDatabaseUser(mySqlDbUser);
+ testDbConfig.setUseSSL(false);
+
+ testTable = mySqlTestTable;
+ //DBExtensionTestUtils.initTestData(testDbConfig);
+
+ DatabaseService.DBType.registerDatabase(MySQLDatabaseService.DB_NAME, MySQLDatabaseService.getInstance());
+
+ }
+
+
+ @Test
+ public void testDoPost() throws IOException, ServletException, JSONException {
+
+ when(request.getParameter("databaseType")).thenReturn(testDbConfig.getDatabaseType());
+ when(request.getParameter("databaseServer")).thenReturn(testDbConfig.getDatabaseHost());
+ when(request.getParameter("databasePort")).thenReturn("" + testDbConfig.getDatabasePort());
+ when(request.getParameter("databaseUser")).thenReturn(testDbConfig.getDatabaseUser());
+ when(request.getParameter("databasePassword")).thenReturn(testDbConfig.getDatabasePassword());
+ when(request.getParameter("initialDatabase")).thenReturn(testDbConfig.getDatabaseName());
+ when(request.getParameter("query")).thenReturn("SELECT count(*) FROM " + testTable);
+
+
+ StringWriter sw = new StringWriter();
+
+ PrintWriter pw = new PrintWriter(sw);
+
+ when(response.getWriter()).thenReturn(pw);
+ TestQueryCommand executeQueryCommand = new TestQueryCommand();
+
+ executeQueryCommand.doPost(request, response);
+
+ String result = sw.getBuffer().toString().trim();
+ JSONObject json = new JSONObject(result);
+
+ String code = json.getString("code");
+ Assert.assertEquals(code, "ok");
+
+ String queryResult = json.getString("QueryResult");
+ Assert.assertNotNull(queryResult);
+
+ }
+
+}
diff --git a/extensions/database/test/com/google/refine/extension/database/mariadb/MariaDBConnectionManagerTest.java b/extensions/database/test/com/google/refine/extension/database/mariadb/MariaDBConnectionManagerTest.java
new file mode 100644
index 000000000..ba1aeb849
--- /dev/null
+++ b/extensions/database/test/com/google/refine/extension/database/mariadb/MariaDBConnectionManagerTest.java
@@ -0,0 +1,76 @@
+package com.google.refine.extension.database.mariadb;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import org.mockito.MockitoAnnotations;
+import org.testng.Assert;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Optional;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+import com.google.refine.extension.database.DBExtensionTests;
+import com.google.refine.extension.database.DatabaseConfiguration;
+import com.google.refine.extension.database.DatabaseService;
+import com.google.refine.extension.database.DatabaseServiceException;
+
+@Test(groups = { "requiresMariaDB" })
+public class MariaDBConnectionManagerTest extends DBExtensionTests {
+
+
+
+ private DatabaseConfiguration testDbConfig;
+
+
+ @BeforeTest
+ @Parameters({ "mariaDbName", "mariaDbHost", "mariaDbPort", "mariaDbUser", "mariaDbPassword", "mariaDbTestTable"})
+ public void beforeTest(@Optional(DEFAULT_MARIADB_NAME) String mariaDbName, @Optional(DEFAULT_MARIADB_HOST) String mariaDbHost,
+ @Optional(DEFAULT_MARIADB_PORT) String mariaDbPort, @Optional(DEFAULT_MARIADB_USER) String mariaDbUser,
+ @Optional(DEFAULT_MARIADB_PASSWORD) String mariaDbPassword, @Optional(DEFAULT_TEST_TABLE) String mariaDbTestTable) {
+
+ MockitoAnnotations.initMocks(this);
+
+ testDbConfig = new DatabaseConfiguration();
+ testDbConfig.setDatabaseHost(mariaDbHost);
+ testDbConfig.setDatabaseName(mariaDbName);
+ testDbConfig.setDatabasePassword(mariaDbPassword);
+ testDbConfig.setDatabasePort(Integer.parseInt(mariaDbPort));
+ testDbConfig.setDatabaseType(MariaDBDatabaseService.DB_NAME);
+ testDbConfig.setDatabaseUser(mariaDbUser);
+ testDbConfig.setUseSSL(false);
+
+// testTable = mariaDbTestTable;
+ // DBExtensionTestUtils.initTestData(testDbConfig);
+
+ DatabaseService.DBType.registerDatabase(MariaDBDatabaseService.DB_NAME, MariaDBDatabaseService.getInstance());
+
+ }
+
+
+ @Test
+ public void testTestConnection() throws DatabaseServiceException {
+ boolean conn = MariaDBConnectionManager.getInstance().testConnection(testDbConfig);
+ Assert.assertEquals(conn, true);
+ }
+
+ @Test
+ public void testGetConnection() throws DatabaseServiceException {
+ Connection conn = MariaDBConnectionManager.getInstance().getConnection(testDbConfig, true);
+ Assert.assertNotNull(conn);
+ }
+
+ @Test
+ public void testShutdown() throws DatabaseServiceException, SQLException {
+ Connection conn = MariaDBConnectionManager.getInstance().getConnection(testDbConfig, true);
+ Assert.assertNotNull(conn);
+
+ MariaDBConnectionManager.getInstance().shutdown();
+
+ if(conn != null) {
+ Assert.assertEquals(conn.isClosed(), true);
+ }
+
+ }
+
+}
diff --git a/extensions/database/test/com/google/refine/extension/database/mariadb/MariaDBDatabaseServiceTest.java b/extensions/database/test/com/google/refine/extension/database/mariadb/MariaDBDatabaseServiceTest.java
new file mode 100644
index 000000000..927eb05d8
--- /dev/null
+++ b/extensions/database/test/com/google/refine/extension/database/mariadb/MariaDBDatabaseServiceTest.java
@@ -0,0 +1,135 @@
+package com.google.refine.extension.database.mariadb;
+
+import java.sql.Connection;
+import java.util.List;
+
+import org.mockito.MockitoAnnotations;
+import org.testng.Assert;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Optional;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+import com.google.refine.extension.database.DBExtensionTestUtils;
+import com.google.refine.extension.database.DBExtensionTests;
+import com.google.refine.extension.database.DatabaseConfiguration;
+import com.google.refine.extension.database.DatabaseService;
+import com.google.refine.extension.database.DatabaseServiceException;
+import com.google.refine.extension.database.model.DatabaseColumn;
+import com.google.refine.extension.database.model.DatabaseInfo;
+import com.google.refine.extension.database.model.DatabaseRow;
+
+@Test(groups = { "requiresMariaDB" })
+public class MariaDBDatabaseServiceTest extends DBExtensionTests{
+
+
+
+ private DatabaseConfiguration testDbConfig;
+
+ private String testTable;
+
+
+ @BeforeTest
+ @Parameters({ "mariaDbName", "mariaDbHost", "mariaDbPort", "mariaDbUser", "mariaDbPassword", "mariaDbTestTable"})
+ public void beforeTest(@Optional(DEFAULT_MARIADB_NAME) String mariaDbName, @Optional(DEFAULT_MARIADB_HOST) String mariaDbHost,
+ @Optional(DEFAULT_MARIADB_PORT) String mariaDbPort, @Optional(DEFAULT_MARIADB_USER) String mariaDbUser,
+ @Optional(DEFAULT_MARIADB_PASSWORD) String mariaDbPassword, @Optional(DEFAULT_TEST_TABLE) String mariaDbTestTable) {
+
+ MockitoAnnotations.initMocks(this);
+
+ testDbConfig = new DatabaseConfiguration();
+ testDbConfig.setDatabaseHost(mariaDbHost);
+ testDbConfig.setDatabaseName(mariaDbName);
+ testDbConfig.setDatabasePassword(mariaDbPassword);
+ testDbConfig.setDatabasePort(Integer.parseInt(mariaDbPort));
+ testDbConfig.setDatabaseType(MariaDBDatabaseService.DB_NAME);
+ testDbConfig.setDatabaseUser(mariaDbUser);
+ testDbConfig.setUseSSL(false);
+
+ testTable = mariaDbTestTable;
+ //DBExtensionTestUtils.initTestData(testDbConfig);
+
+ DatabaseService.DBType.registerDatabase(MariaDBDatabaseService.DB_NAME, MariaDBDatabaseService.getInstance());
+
+ }
+
+ @Test
+ public void testGetDatabaseUrl() {
+ MariaDBDatabaseService pgSqlService = (MariaDBDatabaseService)DatabaseService.get(MariaDBDatabaseService.DB_NAME);
+ String dbUrl = pgSqlService.getDatabaseUrl(testDbConfig);
+ //System.out.println("dbUrl:" + dbUrl);
+ Assert.assertNotNull(dbUrl);
+ Assert.assertEquals(dbUrl, DBExtensionTestUtils.getJDBCUrl(testDbConfig));
+ }
+
+ @Test
+ public void testGetConnection() throws DatabaseServiceException {
+ MariaDBDatabaseService pgSqlService = (MariaDBDatabaseService)DatabaseService.get(MariaDBDatabaseService.DB_NAME);
+ Connection conn = pgSqlService.getConnection(testDbConfig);
+
+ Assert.assertNotNull(conn);
+ }
+
+ @Test
+ public void testTestConnection() throws DatabaseServiceException {
+ MariaDBDatabaseService pgSqlService = (MariaDBDatabaseService)DatabaseService.get(MariaDBDatabaseService.DB_NAME);
+
+ boolean result = pgSqlService.testConnection(testDbConfig);
+ Assert.assertEquals(result, true);
+ }
+
+ @Test
+ public void testConnect() throws DatabaseServiceException {
+ MariaDBDatabaseService pgSqlService = (MariaDBDatabaseService)DatabaseService.get(MariaDBDatabaseService.DB_NAME);
+ DatabaseInfo databaseInfo = pgSqlService.connect(testDbConfig);
+ Assert.assertNotNull(databaseInfo);
+ }
+
+ @Test
+ public void testExecuteQuery() throws DatabaseServiceException {
+ MariaDBDatabaseService pgSqlService = (MariaDBDatabaseService) DatabaseService
+ .get(MariaDBDatabaseService.DB_NAME);
+ DatabaseInfo databaseInfo = pgSqlService.testQuery(testDbConfig, "SELECT * FROM " + testTable);
+
+ Assert.assertNotNull(databaseInfo);
+ }
+
+ @Test
+ public void testBuildLimitQuery() {
+ MariaDBDatabaseService pgSqlService = (MariaDBDatabaseService) DatabaseService.get(MariaDBDatabaseService.DB_NAME);
+ String limitQuery = pgSqlService.buildLimitQuery(100, 0, "SELECT * FROM " + testTable);
+ Assert.assertNotNull(limitQuery);
+
+ Assert.assertEquals(limitQuery, "SELECT * FROM " + testTable + " LIMIT " + 100 + " OFFSET " + 0 + ";");
+
+ }
+
+ @Test
+ public void testGetRows() throws DatabaseServiceException {
+ MariaDBDatabaseService pgSqlService = (MariaDBDatabaseService) DatabaseService
+ .get(MariaDBDatabaseService.DB_NAME);
+ List dbRows = pgSqlService.getRows(testDbConfig, "SELECT * FROM " + testTable);
+
+ Assert.assertNotNull(dbRows);
+ }
+
+ @Test
+ public void testGetInstance() {
+ MariaDBDatabaseService instance = MariaDBDatabaseService.getInstance();
+ Assert.assertNotNull(instance);
+ }
+
+ @Test
+ public void testGetColumns() throws DatabaseServiceException {
+ List dbColumns;
+
+ MariaDBDatabaseService pgSqlService = (MariaDBDatabaseService) DatabaseService
+ .get(MariaDBDatabaseService.DB_NAME);
+
+ dbColumns = pgSqlService.getColumns(testDbConfig, "SELECT * FROM " + testTable);
+
+ Assert.assertNotNull(dbColumns);
+ }
+
+
+}
diff --git a/extensions/database/test/com/google/refine/extension/database/mysql/MySQLConnectionManagerTest.java b/extensions/database/test/com/google/refine/extension/database/mysql/MySQLConnectionManagerTest.java
new file mode 100644
index 000000000..bd01aba75
--- /dev/null
+++ b/extensions/database/test/com/google/refine/extension/database/mysql/MySQLConnectionManagerTest.java
@@ -0,0 +1,76 @@
+package com.google.refine.extension.database.mysql;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import org.mockito.MockitoAnnotations;
+import org.testng.Assert;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Optional;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+import com.google.refine.extension.database.DBExtensionTests;
+import com.google.refine.extension.database.DatabaseConfiguration;
+import com.google.refine.extension.database.DatabaseService;
+import com.google.refine.extension.database.DatabaseServiceException;
+
+@Test(groups = { "requiresMySQL" })
+public class MySQLConnectionManagerTest extends DBExtensionTests {
+
+ private DatabaseConfiguration testDbConfig;
+
+
+ @BeforeTest
+ @Parameters({ "mySqlDbName", "mySqlDbHost", "mySqlDbPort", "mySqlDbUser", "mySqlDbPassword", "mySqlTestTable"})
+ public void beforeTest(@Optional(DEFAULT_MYSQL_DB_NAME) String mySqlDbName, @Optional(DEFAULT_MYSQL_HOST) String mySqlDbHost,
+ @Optional(DEFAULT_MYSQL_PORT) String mySqlDbPort, @Optional(DEFAULT_MYSQL_USER) String mySqlDbUser,
+ @Optional(DEFAULT_MYSQL_PASSWORD) String mySqlDbPassword, @Optional(DEFAULT_TEST_TABLE) String mySqlTestTable) {
+
+ MockitoAnnotations.initMocks(this);
+
+ testDbConfig = new DatabaseConfiguration();
+ testDbConfig.setDatabaseHost(mySqlDbHost);
+ testDbConfig.setDatabaseName(mySqlDbName);
+ testDbConfig.setDatabasePassword(mySqlDbPassword);
+ testDbConfig.setDatabasePort(Integer.parseInt(mySqlDbPort));
+ testDbConfig.setDatabaseType(MySQLDatabaseService.DB_NAME);
+ testDbConfig.setDatabaseUser(mySqlDbUser);
+ testDbConfig.setUseSSL(false);
+
+ //testTable = mySqlTestTable;
+ // DBExtensionTestUtils.initTestData(testDbConfig);
+
+ DatabaseService.DBType.registerDatabase(MySQLDatabaseService.DB_NAME, MySQLDatabaseService.getInstance());
+
+ }
+
+
+ @Test
+ public void testTestConnection() throws DatabaseServiceException {
+
+ boolean conn = MySQLConnectionManager.getInstance().testConnection(testDbConfig);
+ Assert.assertEquals(conn, true);
+ }
+
+ @Test
+ public void testGetConnection() throws DatabaseServiceException {
+
+ Connection conn = MySQLConnectionManager.getInstance().getConnection(testDbConfig, true);
+ Assert.assertNotNull(conn);
+ }
+
+ @Test
+ public void testShutdown() throws DatabaseServiceException, SQLException {
+
+ Connection conn = MySQLConnectionManager.getInstance().getConnection(testDbConfig, true);
+ Assert.assertNotNull(conn);
+
+ MySQLConnectionManager.getInstance().shutdown();
+
+ if(conn != null) {
+ Assert.assertEquals(conn.isClosed(), true);
+ }
+ }
+
+}
diff --git a/extensions/database/test/com/google/refine/extension/database/mysql/MySQLDatabaseServiceTest.java b/extensions/database/test/com/google/refine/extension/database/mysql/MySQLDatabaseServiceTest.java
new file mode 100644
index 000000000..653d468df
--- /dev/null
+++ b/extensions/database/test/com/google/refine/extension/database/mysql/MySQLDatabaseServiceTest.java
@@ -0,0 +1,135 @@
+package com.google.refine.extension.database.mysql;
+
+import java.sql.Connection;
+import java.util.List;
+
+import org.mockito.MockitoAnnotations;
+import org.testng.Assert;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Optional;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+import com.google.refine.extension.database.DBExtensionTestUtils;
+import com.google.refine.extension.database.DBExtensionTests;
+import com.google.refine.extension.database.DatabaseConfiguration;
+import com.google.refine.extension.database.DatabaseService;
+import com.google.refine.extension.database.DatabaseServiceException;
+import com.google.refine.extension.database.model.DatabaseColumn;
+import com.google.refine.extension.database.model.DatabaseInfo;
+import com.google.refine.extension.database.model.DatabaseRow;
+
+
+@Test(groups = { "requiresMySQL" })
+public class MySQLDatabaseServiceTest extends DBExtensionTests{
+
+ private DatabaseConfiguration testDbConfig;
+ private String testTable;
+
+ @BeforeTest
+ @Parameters({ "mySqlDbName", "mySqlDbHost", "mySqlDbPort", "mySqlDbUser", "mySqlDbPassword", "mySqlTestTable"})
+ public void beforeTest(@Optional(DEFAULT_MYSQL_DB_NAME) String mySqlDbName, @Optional(DEFAULT_MYSQL_HOST) String mySqlDbHost,
+ @Optional(DEFAULT_MYSQL_PORT) String mySqlDbPort, @Optional(DEFAULT_MYSQL_USER) String mySqlDbUser,
+ @Optional(DEFAULT_MYSQL_PASSWORD) String mySqlDbPassword, @Optional(DEFAULT_TEST_TABLE) String mySqlTestTable) {
+
+ MockitoAnnotations.initMocks(this);
+
+ testDbConfig = new DatabaseConfiguration();
+ testDbConfig.setDatabaseHost(mySqlDbHost);
+ testDbConfig.setDatabaseName(mySqlDbName);
+ testDbConfig.setDatabasePassword(mySqlDbPassword);
+ testDbConfig.setDatabasePort(Integer.parseInt(mySqlDbPort));
+ testDbConfig.setDatabaseType(MySQLDatabaseService.DB_NAME);
+ testDbConfig.setDatabaseUser(mySqlDbUser);
+ testDbConfig.setUseSSL(false);
+
+ testTable = mySqlTestTable;
+ //DBExtensionTestUtils.initTestData(testDbConfig);
+
+ DatabaseService.DBType.registerDatabase(MySQLDatabaseService.DB_NAME, MySQLDatabaseService.getInstance());
+
+ }
+
+
+ @Test
+ public void testGetDatabaseUrl() {
+ MySQLDatabaseService pgSqlService = (MySQLDatabaseService)DatabaseService.get(MySQLDatabaseService.DB_NAME);
+ String dbUrl = pgSqlService.getDatabaseUrl(testDbConfig);
+ //System.out.println("dbUrl:" + dbUrl);
+ Assert.assertNotNull(dbUrl);
+ Assert.assertEquals(dbUrl, DBExtensionTestUtils.getJDBCUrl(testDbConfig));
+ }
+
+
+ @Test
+ public void testGetConnection() throws DatabaseServiceException {
+ MySQLDatabaseService pgSqlService = (MySQLDatabaseService)DatabaseService.get(MySQLDatabaseService.DB_NAME);
+ Connection conn = pgSqlService.getConnection(testDbConfig);
+
+ Assert.assertNotNull(conn);
+ }
+
+ @Test
+ public void testTestConnection() throws DatabaseServiceException {
+ MySQLDatabaseService pgSqlService = (MySQLDatabaseService)DatabaseService.get(MySQLDatabaseService.DB_NAME);
+
+ boolean result = pgSqlService.testConnection(testDbConfig);
+ Assert.assertEquals(result, true);
+ }
+
+ @Test
+ public void testConnect() throws DatabaseServiceException {
+ MySQLDatabaseService pgSqlService = (MySQLDatabaseService)DatabaseService.get(MySQLDatabaseService.DB_NAME);
+ DatabaseInfo databaseInfo = pgSqlService.connect(testDbConfig);
+ Assert.assertNotNull(databaseInfo);
+ }
+
+ @Test
+ public void testExecuteQuery() throws DatabaseServiceException {
+
+ MySQLDatabaseService pgSqlService = (MySQLDatabaseService) DatabaseService
+ .get(MySQLDatabaseService.DB_NAME);
+ DatabaseInfo databaseInfo = pgSqlService.testQuery(testDbConfig, "SELECT * FROM " + testTable);
+
+ Assert.assertNotNull(databaseInfo);
+ }
+
+ @Test
+ public void testBuildLimitQuery() {
+ MySQLDatabaseService pgSqlService = (MySQLDatabaseService) DatabaseService.get(MySQLDatabaseService.DB_NAME);
+ String limitQuery = pgSqlService.buildLimitQuery(100, 0, "SELECT * FROM " + testTable);
+ Assert.assertNotNull(limitQuery);
+
+ Assert.assertEquals(limitQuery, "SELECT * FROM " + testTable + " LIMIT " + 100 + " OFFSET " + 0 + ";");
+
+ }
+
+ @Test
+ public void testGetRows() throws DatabaseServiceException {
+ MySQLDatabaseService pgSqlService = (MySQLDatabaseService) DatabaseService
+ .get(MySQLDatabaseService.DB_NAME);
+ List dbRows = pgSqlService.getRows(testDbConfig, "SELECT * FROM " + testTable);
+
+ Assert.assertNotNull(dbRows);
+ }
+
+ @Test
+ public void testGetInstance() {
+ MySQLDatabaseService instance = MySQLDatabaseService.getInstance();
+ Assert.assertNotNull(instance);
+ }
+
+ @Test
+ public void testGetColumns() throws DatabaseServiceException {
+ List dbColumns;
+ MySQLDatabaseService pgSqlService = (MySQLDatabaseService) DatabaseService
+ .get(MySQLDatabaseService.DB_NAME);
+
+ dbColumns = pgSqlService.getColumns(testDbConfig, "SELECT * FROM " + testTable);
+
+ Assert.assertNotNull(dbColumns);
+ }
+
+
+
+}
diff --git a/extensions/database/test/com/google/refine/extension/database/pgsql/PgSQLConnectionManagerTest.java b/extensions/database/test/com/google/refine/extension/database/pgsql/PgSQLConnectionManagerTest.java
new file mode 100644
index 000000000..4c4d744d8
--- /dev/null
+++ b/extensions/database/test/com/google/refine/extension/database/pgsql/PgSQLConnectionManagerTest.java
@@ -0,0 +1,79 @@
+package com.google.refine.extension.database.pgsql;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import org.mockito.MockitoAnnotations;
+import org.testng.Assert;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Optional;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+import com.google.refine.extension.database.DBExtensionTests;
+import com.google.refine.extension.database.DatabaseConfiguration;
+import com.google.refine.extension.database.DatabaseService;
+import com.google.refine.extension.database.DatabaseServiceException;
+
+
+@Test(groups = { "requiresPgSQL" })
+public class PgSQLConnectionManagerTest extends DBExtensionTests {
+
+ private DatabaseConfiguration testDbConfig;
+
+
+ @BeforeTest
+ @Parameters({ "pgSqlDbName", "pgSqlDbHost", "pgSqlDbPort", "pgSqlDbUser", "pgSqlDbPassword", "pgSqlTestTable"})
+ public void beforeTest(@Optional(DEFAULT_PGSQL_DB_NAME) String pgSqlDbName, @Optional(DEFAULT_PGSQL_HOST) String pgSqlDbHost,
+ @Optional(DEFAULT_PGSQL_PORT) String pgSqlDbPort, @Optional(DEFAULT_PGSQL_USER) String pgSqlDbUser,
+ @Optional(DEFAULT_PGSQL_PASSWORD) String pgSqlDbPassword, @Optional(DEFAULT_TEST_TABLE) String pgSqlTestTable) {
+
+ MockitoAnnotations.initMocks(this);
+
+ testDbConfig = new DatabaseConfiguration();
+ testDbConfig.setDatabaseHost(pgSqlDbHost);
+ testDbConfig.setDatabaseName(pgSqlDbName);
+ testDbConfig.setDatabasePassword(pgSqlDbPassword);
+ testDbConfig.setDatabasePort(Integer.parseInt(pgSqlDbPort));
+ testDbConfig.setDatabaseType(PgSQLDatabaseService.DB_NAME);
+ testDbConfig.setDatabaseUser(pgSqlDbUser);
+ testDbConfig.setUseSSL(false);
+
+ //testTable = mySqlTestTable;
+ //DBExtensionTestUtils.initTestData(testDbConfig);
+
+ DatabaseService.DBType.registerDatabase(PgSQLDatabaseService.DB_NAME, PgSQLDatabaseService.getInstance());
+
+ }
+
+
+
+ @Test
+ public void testTestConnection() throws DatabaseServiceException {
+
+ boolean isConnected = PgSQLConnectionManager.getInstance().testConnection(testDbConfig);
+ Assert.assertEquals(isConnected, true);
+ }
+
+ @Test
+ public void testGetConnection() throws DatabaseServiceException {
+
+ Connection conn = PgSQLConnectionManager.getInstance().getConnection(testDbConfig, true);
+ Assert.assertNotNull(conn);
+ }
+
+ @Test
+ public void testShutdown() throws DatabaseServiceException, SQLException {
+
+ Connection conn = PgSQLConnectionManager.getInstance().getConnection(testDbConfig, true);
+ Assert.assertNotNull(conn);
+
+ PgSQLConnectionManager.getInstance().shutdown();
+
+ if(conn != null) {
+ Assert.assertEquals(conn.isClosed(), true);
+ }
+
+ }
+
+}
diff --git a/extensions/database/test/com/google/refine/extension/database/pgsql/PgSQLDatabaseServiceTest.java b/extensions/database/test/com/google/refine/extension/database/pgsql/PgSQLDatabaseServiceTest.java
new file mode 100644
index 000000000..161d669b7
--- /dev/null
+++ b/extensions/database/test/com/google/refine/extension/database/pgsql/PgSQLDatabaseServiceTest.java
@@ -0,0 +1,137 @@
+package com.google.refine.extension.database.pgsql;
+
+import java.sql.Connection;
+import java.util.List;
+
+import org.mockito.MockitoAnnotations;
+import org.testng.Assert;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Optional;
+import org.testng.annotations.Parameters;
+import org.testng.annotations.Test;
+
+import com.google.refine.extension.database.DBExtensionTestUtils;
+import com.google.refine.extension.database.DBExtensionTests;
+import com.google.refine.extension.database.DatabaseConfiguration;
+import com.google.refine.extension.database.DatabaseService;
+import com.google.refine.extension.database.DatabaseServiceException;
+import com.google.refine.extension.database.model.DatabaseColumn;
+import com.google.refine.extension.database.model.DatabaseInfo;
+import com.google.refine.extension.database.model.DatabaseRow;
+
+@Test(groups = { "requiresPgSQL" })
+public class PgSQLDatabaseServiceTest extends DBExtensionTests{
+
+ private DatabaseConfiguration testDbConfig;
+ private String testTable;
+
+
+ @BeforeTest
+ @Parameters({ "pgSqlDbName", "pgSqlDbHost", "pgSqlDbPort", "pgSqlDbUser", "pgSqlDbPassword", "pgSqlTestTable"})
+ public void beforeTest(@Optional(DEFAULT_PGSQL_DB_NAME) String pgSqlDbName, @Optional(DEFAULT_PGSQL_HOST) String pgSqlDbHost,
+ @Optional(DEFAULT_PGSQL_PORT) String pgSqlDbPort, @Optional(DEFAULT_PGSQL_USER) String pgSqlDbUser,
+ @Optional(DEFAULT_PGSQL_PASSWORD) String pgSqlDbPassword, @Optional(DEFAULT_TEST_TABLE) String pgSqlTestTable) {
+
+ MockitoAnnotations.initMocks(this);
+ testDbConfig = new DatabaseConfiguration();
+ testDbConfig.setDatabaseHost(pgSqlDbHost);
+ testDbConfig.setDatabaseName(pgSqlDbName);
+ testDbConfig.setDatabasePassword(pgSqlDbPassword);
+ testDbConfig.setDatabasePort(Integer.parseInt(pgSqlDbPort));
+ testDbConfig.setDatabaseType(PgSQLDatabaseService.DB_NAME);
+ testDbConfig.setDatabaseUser(pgSqlDbUser);
+ testDbConfig.setUseSSL(false);
+
+ testTable = pgSqlTestTable;
+ //DBExtensionTestUtils.initTestData(testDbConfig);
+
+ DatabaseService.DBType.registerDatabase(PgSQLDatabaseService.DB_NAME, PgSQLDatabaseService.getInstance());
+ }
+
+
+
+ @Test
+ public void testGetDatabaseUrl() {
+ PgSQLDatabaseService pgSqlService = (PgSQLDatabaseService)DatabaseService.get(PgSQLDatabaseService.DB_NAME);
+ String dbUrl = pgSqlService.getDatabaseUrl(testDbConfig);
+
+ Assert.assertNotNull(dbUrl);
+ Assert.assertEquals(dbUrl, DBExtensionTestUtils.getJDBCUrl(testDbConfig));
+ }
+
+
+ @Test
+ public void testGetConnection() throws DatabaseServiceException {
+
+ PgSQLDatabaseService pgSqlService = (PgSQLDatabaseService)DatabaseService.get(PgSQLDatabaseService.DB_NAME);
+ Connection conn = pgSqlService.getConnection(testDbConfig);
+
+ Assert.assertNotNull(conn);
+ }
+
+ @Test
+ public void testTestConnection() throws DatabaseServiceException {
+ PgSQLDatabaseService pgSqlService = (PgSQLDatabaseService)DatabaseService.get(PgSQLDatabaseService.DB_NAME);
+
+ boolean result = pgSqlService.testConnection(testDbConfig);
+ Assert.assertEquals(result, true);
+ }
+
+ @Test
+ public void testConnect() throws DatabaseServiceException {
+
+ PgSQLDatabaseService pgSqlService = (PgSQLDatabaseService)DatabaseService.get(PgSQLDatabaseService.DB_NAME);
+ DatabaseInfo databaseInfo = pgSqlService.connect(testDbConfig);
+ Assert.assertNotNull(databaseInfo);
+ }
+
+ @Test
+ public void testExecuteQuery() throws DatabaseServiceException {
+
+ PgSQLDatabaseService pgSqlService = (PgSQLDatabaseService) DatabaseService
+ .get(PgSQLDatabaseService.DB_NAME);
+ DatabaseInfo databaseInfo = pgSqlService.testQuery(testDbConfig, "SELECT * FROM " + testTable);
+
+ Assert.assertNotNull(databaseInfo);
+ }
+
+ @Test
+ public void testBuildLimitQuery() {
+ PgSQLDatabaseService pgSqlService = (PgSQLDatabaseService) DatabaseService.get(PgSQLDatabaseService.DB_NAME);
+ String limitQuery = pgSqlService.buildLimitQuery(100, 0, "SELECT * FROM " + testTable);
+ Assert.assertNotNull(limitQuery);
+
+ Assert.assertEquals(limitQuery, "SELECT * FROM " + testTable + " LIMIT " + 100 + " OFFSET " + 0 + ";");
+
+ }
+
+ @Test
+ public void testGetRows() throws DatabaseServiceException {
+ PgSQLDatabaseService pgSqlService = (PgSQLDatabaseService) DatabaseService
+ .get(PgSQLDatabaseService.DB_NAME);
+ List dbRows = pgSqlService.getRows(testDbConfig, "SELECT * FROM " + testTable);
+
+ Assert.assertNotNull(dbRows);
+ }
+
+ @Test
+ public void testGetInstance() {
+ PgSQLDatabaseService instance = PgSQLDatabaseService.getInstance();
+ Assert.assertNotNull(instance);
+ }
+
+ @Test
+ public void testGetColumns() throws DatabaseServiceException {
+ List dbColumns;
+
+ PgSQLDatabaseService pgSqlService = (PgSQLDatabaseService) DatabaseService
+ .get(PgSQLDatabaseService.DB_NAME);
+
+ dbColumns = pgSqlService.getColumns(testDbConfig, "SELECT * FROM " + testTable);
+
+ Assert.assertNotNull(dbColumns);
+
+ }
+
+
+}
diff --git a/extensions/database/test/com/google/refine/extension/database/stub/RefineDbServletStub.java b/extensions/database/test/com/google/refine/extension/database/stub/RefineDbServletStub.java
new file mode 100644
index 000000000..c76e85c26
--- /dev/null
+++ b/extensions/database/test/com/google/refine/extension/database/stub/RefineDbServletStub.java
@@ -0,0 +1,95 @@
+/*
+
+Copyright 2010, Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+*/
+
+package com.google.refine.extension.database.stub;
+
+import java.io.File;
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.google.refine.RefineServlet;
+import com.google.refine.commands.Command;
+
+/**
+ * Exposes protected methods of com.google.refine.RefineServlet as public for unit testing
+ *
+ */
+public class RefineDbServletStub extends RefineServlet {
+
+ private static File tempDir = null;
+
+ //requirement of extending HttpServlet, not required for testing
+ private static final long serialVersionUID = 1L;
+
+ public void wrapService(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
+ super.service(request, response);
+ }
+
+ public String wrapGetCommandName(HttpServletRequest request){
+ return super.getCommandKey(request);
+ }
+
+ @Override
+ public File getTempDir() {
+ if (tempDir == null) {
+ try {
+ tempDir = File.createTempFile("refine-test-dir", "");
+ tempDir.deleteOnExit();
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to create temp directory",e);
+ }
+ }
+ return tempDir;
+ }
+
+ //-------------------helper methods--------------
+ /**
+ * Helper method for inserting a mock object
+ * @param commandName
+ * @param command
+ */
+ public void insertCommand(String commandName, Command command ){
+ registerOneCommand("core/" + commandName, command);
+ }
+
+ /**
+ * Helper method for clearing up after testing
+ * @param commandName
+ */
+ public void removeCommand( String commandName ){
+ unregisterCommand("core/" + commandName);
+ }
+}
diff --git a/extensions/database/test/conf/appveyor_tests.xml b/extensions/database/test/conf/appveyor_tests.xml
new file mode 100644
index 000000000..1d906be12
--- /dev/null
+++ b/extensions/database/test/conf/appveyor_tests.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/extensions/database/test/conf/tests.xml b/extensions/database/test/conf/tests.xml
new file mode 100644
index 000000000..f14cc34ad
--- /dev/null
+++ b/extensions/database/test/conf/tests.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/extensions/database/test/conf/travis-mariadb.sql b/extensions/database/test/conf/travis-mariadb.sql
new file mode 100644
index 000000000..c5716c5a8
--- /dev/null
+++ b/extensions/database/test/conf/travis-mariadb.sql
@@ -0,0 +1,18 @@
+CREATE DATABASE IF NOT EXISTS `test_db` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
+USE test_db;
+
+CREATE TABLE IF NOT EXISTS `test_table` (
+ `id` int(11) NOT NULL,
+ `ue_id` char(8) NOT NULL,
+ `start_time` timestamp NOT NULL,
+ `end_date` date DEFAULT NULL,
+ `bytes_upload` int(11) NOT NULL,
+ `bytes_download` int(11) NOT NULL,
+ `mcc` char(3) DEFAULT NULL,
+ `mnc` char(3) NOT NULL,
+ `lac` varchar(11) DEFAULT NULL,
+ `imei` char(16) NOT NULL
+);
+
+INSERT INTO test_table(id, ue_id, start_time, end_date, bytes_upload, bytes_download, mcc, mnc, lac, imei)
+ VALUES (1, '11100022', now(), now(), 1024, 2048, 321, 543, 12209823498, 1344498988877487);
\ No newline at end of file
diff --git a/extensions/database/test/conf/travis-mysql.sql b/extensions/database/test/conf/travis-mysql.sql
new file mode 100644
index 000000000..d2b53c80e
--- /dev/null
+++ b/extensions/database/test/conf/travis-mysql.sql
@@ -0,0 +1,17 @@
+USE test_db;
+
+CREATE TABLE IF NOT EXISTS `test_table` (
+ `id` int(11) NOT NULL,
+ `ue_id` char(8) NOT NULL,
+ `start_time` timestamp NOT NULL,
+ `end_date` date DEFAULT NULL,
+ `bytes_upload` int(11) NOT NULL,
+ `bytes_download` int(11) NOT NULL,
+ `mcc` char(3) DEFAULT NULL,
+ `mnc` char(3) NOT NULL,
+ `lac` varchar(11) DEFAULT NULL,
+ `imei` char(16) NOT NULL
+);
+
+INSERT INTO test_table(id, ue_id, start_time, end_date, bytes_upload, bytes_download, mcc, mnc, lac, imei)
+ VALUES (1, '11100022', now(), now(), 1024, 2048, 321, 543, 12209823498, 1344498988877487);
\ No newline at end of file
diff --git a/extensions/database/test/conf/travis-pgsql.sql b/extensions/database/test/conf/travis-pgsql.sql
new file mode 100644
index 000000000..f8299a964
--- /dev/null
+++ b/extensions/database/test/conf/travis-pgsql.sql
@@ -0,0 +1,17 @@
+USE test_db;
+
+CREATE TABLE IF NOT EXISTS test_table (
+ id integer NOT NULL,
+ ue_id char(8) NOT NULL,
+ start_time timestamp NOT NULL,
+ end_date date DEFAULT NULL,
+ bytes_upload integer NOT NULL,
+ bytes_download integer NOT NULL,
+ mcc char(3) DEFAULT NULL,
+ mnc char(3) NOT NULL,
+ lac varchar(11) DEFAULT NULL,
+ imei char(16) NOT NULL
+);
+
+INSERT INTO test_table(id, ue_id, start_time, end_date, bytes_upload, bytes_download, mcc, mnc, lac, imei)
+ VALUES (1, '11100022', now(), now(), 1024, 2048, 321, 543, 12209823498, 1344498988877487);
\ No newline at end of file
diff --git a/extensions/database/test/conf/travis_tests.xml b/extensions/database/test/conf/travis_tests.xml
new file mode 100644
index 000000000..fcf2204bf
--- /dev/null
+++ b/extensions/database/test/conf/travis_tests.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/database/test/log4j-test.properties b/extensions/database/test/log4j-test.properties
new file mode 100644
index 000000000..0ed0d9f64
--- /dev/null
+++ b/extensions/database/test/log4j-test.properties
@@ -0,0 +1,5 @@
+log4j.rootLogger=ERROR, console
+log4j.logger.com.google.refine.extension.database=DEBUG
+
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+log4j.appender.console.layout=com.google.refine.logging.IndentingLayout
diff --git a/extensions/gdata/.classpath b/extensions/gdata/.classpath
deleted file mode 100644
index f6d5efd90..000000000
--- a/extensions/gdata/.classpath
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/extensions/gdata/.project b/extensions/gdata/.project
deleted file mode 100644
index c6f5e416a..000000000
--- a/extensions/gdata/.project
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
- grefine-gdata-extension
-
-
- gridworks
- gridworks-server
-
-
-
- org.eclipse.wst.jsdt.core.javascriptValidator
-
-
-
-
- org.eclipse.wst.common.project.facet.core.builder
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
-
- org.eclipse.jdt.core.javanature
- org.eclipse.wst.common.project.facet.core.nature
- org.eclipse.wst.jsdt.core.jsNature
-
-
diff --git a/extensions/gdata/src/com/google/refine/extension/gdata/FusionTableImporter.java b/extensions/gdata/src/com/google/refine/extension/gdata/FusionTableImporter.java
index 8e4dbfbb7..c02b3253f 100644
--- a/extensions/gdata/src/com/google/refine/extension/gdata/FusionTableImporter.java
+++ b/extensions/gdata/src/com/google/refine/extension/gdata/FusionTableImporter.java
@@ -39,11 +39,11 @@ import com.google.api.services.fusiontables.model.Column;
import com.google.api.services.fusiontables.model.Sqlresponse;
import com.google.api.services.fusiontables.model.Table;
-import com.google.refine.ProjectMetadata;
import com.google.refine.importers.TabularImportingParserBase;
import com.google.refine.importers.TabularImportingParserBase.TableDataReader;
import com.google.refine.importing.ImportingJob;
import com.google.refine.model.Project;
+import com.google.refine.model.medadata.ProjectMetadata;
import com.google.refine.util.JSONUtilities;
/**
diff --git a/extensions/gdata/src/com/google/refine/extension/gdata/GDataImporter.java b/extensions/gdata/src/com/google/refine/extension/gdata/GDataImporter.java
index edbb4a8e1..a4d58c2a6 100644
--- a/extensions/gdata/src/com/google/refine/extension/gdata/GDataImporter.java
+++ b/extensions/gdata/src/com/google/refine/extension/gdata/GDataImporter.java
@@ -45,11 +45,11 @@ import com.google.gdata.data.spreadsheet.SpreadsheetEntry;
import com.google.gdata.data.spreadsheet.WorksheetEntry;
import com.google.gdata.util.ServiceException;
-import com.google.refine.ProjectMetadata;
import com.google.refine.importers.TabularImportingParserBase;
import com.google.refine.importers.TabularImportingParserBase.TableDataReader;
import com.google.refine.importing.ImportingJob;
import com.google.refine.model.Project;
+import com.google.refine.model.medadata.ProjectMetadata;
import com.google.refine.util.JSONUtilities;
/**
diff --git a/extensions/gdata/src/com/google/refine/extension/gdata/GDataImportingController.java b/extensions/gdata/src/com/google/refine/extension/gdata/GDataImportingController.java
index fb654c83c..3e9e47da1 100644
--- a/extensions/gdata/src/com/google/refine/extension/gdata/GDataImportingController.java
+++ b/extensions/gdata/src/com/google/refine/extension/gdata/GDataImportingController.java
@@ -65,7 +65,6 @@ import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.ServiceException;
import com.google.refine.ProjectManager;
-import com.google.refine.ProjectMetadata;
import com.google.refine.RefineServlet;
import com.google.refine.commands.HttpUtilities;
import com.google.refine.importing.DefaultImportingController;
@@ -73,6 +72,7 @@ import com.google.refine.importing.ImportingController;
import com.google.refine.importing.ImportingJob;
import com.google.refine.importing.ImportingManager;
import com.google.refine.model.Project;
+import com.google.refine.model.medadata.ProjectMetadata;
import com.google.refine.util.JSONUtilities;
import com.google.refine.util.ParsingUtilities;
diff --git a/extensions/jython/.classpath b/extensions/jython/.classpath
deleted file mode 100644
index fc48762f5..000000000
--- a/extensions/jython/.classpath
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/extensions/jython/.project b/extensions/jython/.project
deleted file mode 100644
index 8d1297098..000000000
--- a/extensions/jython/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
- grefine-jython
-
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
-
- org.eclipse.jdt.core.javanature
-
-
diff --git a/extensions/jython/src/com/google/refine/jython/JythonHasFieldsWrapper.java b/extensions/jython/src/com/google/refine/jython/JythonHasFieldsWrapper.java
index 55b49207c..f2eb3fda6 100644
--- a/extensions/jython/src/com/google/refine/jython/JythonHasFieldsWrapper.java
+++ b/extensions/jython/src/com/google/refine/jython/JythonHasFieldsWrapper.java
@@ -55,7 +55,12 @@ public class JythonHasFieldsWrapper extends PyObject {
@Override
public PyObject __finditem__(PyObject key) {
String k = (String) key.__tojava__(String.class);
- Object v = _obj.getField(k, _bindings);
+ return __findattr_ex__(k);
+ }
+
+ @Override
+ public PyObject __findattr_ex__(String name) {
+ Object v = _obj.getField(name, _bindings);
if (v != null) {
if (v instanceof PyObject) {
return (PyObject) v;
@@ -70,5 +75,4 @@ public class JythonHasFieldsWrapper extends PyObject {
return null;
}
}
-
}
diff --git a/extensions/jython/tests/src/com/google/refine/jython/JythonAttributeTest.java b/extensions/jython/tests/src/com/google/refine/jython/JythonAttributeTest.java
new file mode 100644
index 000000000..c97a071fc
--- /dev/null
+++ b/extensions/jython/tests/src/com/google/refine/jython/JythonAttributeTest.java
@@ -0,0 +1,50 @@
+package com.google.refine.jython;
+
+import java.util.Properties;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.refine.expr.Evaluable;
+import com.google.refine.expr.HasFields;
+import com.google.refine.model.Cell;
+import com.google.refine.model.Row;
+
+public class JythonAttributeTest {
+
+ class MyFieldObject implements HasFields {
+
+ @Override
+ public Object getField(String name, Properties bindings) {
+ if ("sunshine".equals(name)) {
+ return "hammock";
+ }
+ return null;
+ }
+
+ @Override
+ public boolean fieldAlsoHasFields(String name) {
+ return true;
+ }
+
+ }
+
+ @Test
+ public void testWrappedObjectsHaveAttributes() {
+ Row row = new Row(2);
+ row.setCell(0, new Cell("sunshine", null));
+ row.setCell(1, new Cell("hammock", null));
+
+ Properties props = new Properties();
+
+ MyFieldObject obj = new MyFieldObject();
+ JythonHasFieldsWrapper wrapper = new JythonHasFieldsWrapper(obj, props);
+ Assert.assertEquals(wrapper.__findattr__("sunshine").toString(), "hammock");
+
+ props.put("cell", obj);
+
+ Evaluable eval = new JythonEvaluable("return cell.sunshine");
+ String result = (String)eval.evaluate(props).toString();
+ Assert.assertEquals(result, "hammock");
+ }
+}
diff --git a/extensions/pc-axis/.classpath b/extensions/pc-axis/.classpath
deleted file mode 100644
index f68b34a90..000000000
--- a/extensions/pc-axis/.classpath
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
diff --git a/extensions/pc-axis/.project b/extensions/pc-axis/.project
deleted file mode 100644
index eef3b87d9..000000000
--- a/extensions/pc-axis/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
- refine-pd-extension
-
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
-
- org.eclipse.jdt.core.javanature
-
-
diff --git a/extensions/pc-axis/module/MOD-INF/.gitignore b/extensions/pc-axis/module/MOD-INF/.gitignore
deleted file mode 100644
index 840e7d312..000000000
--- a/extensions/pc-axis/module/MOD-INF/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/classes/
diff --git a/extensions/pc-axis/src/com/google/refine/pcaxis/PCAxisImporter.java b/extensions/pc-axis/src/com/google/refine/pcaxis/PCAxisImporter.java
index 64e78a67d..ea137ec7e 100644
--- a/extensions/pc-axis/src/com/google/refine/pcaxis/PCAxisImporter.java
+++ b/extensions/pc-axis/src/com/google/refine/pcaxis/PCAxisImporter.java
@@ -41,10 +41,10 @@ import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.refine.ProjectMetadata;
import com.google.refine.importers.TabularImportingParserBase;
import com.google.refine.importing.ImportingJob;
import com.google.refine.model.Project;
+import com.google.refine.model.medadata.ProjectMetadata;
import com.google.refine.util.JSONUtilities;
public class PCAxisImporter extends TabularImportingParserBase {
diff --git a/extensions/sample/.classpath b/extensions/sample/.classpath
deleted file mode 100644
index 03c606eeb..000000000
--- a/extensions/sample/.classpath
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/extensions/sample/.settings/.jsdtscope b/extensions/sample/.settings/.jsdtscope
deleted file mode 100644
index b13d30357..000000000
--- a/extensions/sample/.settings/.jsdtscope
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/extensions/sample/module/MOD-INF/.gitignore b/extensions/sample/module/MOD-INF/.gitignore
deleted file mode 100644
index 840e7d312..000000000
--- a/extensions/sample/module/MOD-INF/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/classes/
diff --git a/graphics/icon/google-refine-128px.png b/graphics/icon/google-refine-128px.png
deleted file mode 100644
index 2f63bcc93..000000000
Binary files a/graphics/icon/google-refine-128px.png and /dev/null differ
diff --git a/graphics/icon/google-refine-16px.png b/graphics/icon/google-refine-16px.png
deleted file mode 100644
index 33bbd2aa9..000000000
Binary files a/graphics/icon/google-refine-16px.png and /dev/null differ
diff --git a/graphics/icon/google-refine-256px.png b/graphics/icon/google-refine-256px.png
deleted file mode 100644
index aa6554616..000000000
Binary files a/graphics/icon/google-refine-256px.png and /dev/null differ
diff --git a/graphics/icon/google-refine-32px.png b/graphics/icon/google-refine-32px.png
deleted file mode 100644
index 924e40904..000000000
Binary files a/graphics/icon/google-refine-32px.png and /dev/null differ
diff --git a/graphics/icon/google-refine-48px.png b/graphics/icon/google-refine-48px.png
deleted file mode 100644
index 1fcd254a9..000000000
Binary files a/graphics/icon/google-refine-48px.png and /dev/null differ
diff --git a/graphics/icon/google-refine-512px.png b/graphics/icon/google-refine-512px.png
deleted file mode 100644
index 39375f567..000000000
Binary files a/graphics/icon/google-refine-512px.png and /dev/null differ
diff --git a/graphics/icon/google-refine-logo.ai b/graphics/icon/google-refine-logo.ai
deleted file mode 100644
index 207e6e67c..000000000
--- a/graphics/icon/google-refine-logo.ai
+++ /dev/null
@@ -1,4393 +0,0 @@
-%PDF-1.5
%
-1 0 obj
<>/OCGs[5 0 R 65 0 R 193 0 R 345 0 R 433 0 R 609 0 R 857 0 R 1105 0 R 1106 0 R 1107 0 R 1108 0 R 1109 0 R 1110 0 R 1111 0 R 1296 0 R 1297 0 R 1298 0 R 1299 0 R 1300 0 R 1301 0 R 1302 0 R 1487 0 R 1488 0 R 1489 0 R 1490 0 R 1491 0 R 1492 0 R 1493 0 R 1494 0 R 1710 0 R 1711 0 R 1712 0 R 1713 0 R 1714 0 R 1715 0 R 1716 0 R 1717 0 R]>>/Pages 3 0 R/Type/Catalog>>
endobj
2 0 obj
<>stream
-
-
-
-
- application/pdf
-
-
- Print
-
-
-
-
- 2010-10-18T14:36:50-07:00
- 2010-10-18T14:36:50-07:00
- 2010-09-27T16:24:05-07:00
- Adobe Illustrator CS5
-
-
-
- 224
- 256
- JPEG
- /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA
AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK
DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f
Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAADgAwER
AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA
AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB
UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE
1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ
qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy
obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp
0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo
+DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9UkgAkmgG5JxV5lr3mzW
vNWpS6B5VYxWMfw32qAkArWh4sOi+FN2+WbbDpo4o8eTn0DrcueWQ8MOXe0v5QaVGitDqV3Hc0HO
UFKFu5AABA/2RyX8oS6gUj8kO82vH5e+Zbbex813cdOkbeoF79aS0/DB+bxnnAfj4J/LzHKZbGi/
mzaj/RtfgnA7SgEnp/PE/wCvBx6Y84n8fFeHOOUvx8ly6l+clr9q0s74DufTHj/LJCcHh6U9SEjJ
qB0BXDz7+YFsaXnlV5qdfq/qfhxE2D8nhPKf4+xP5rKOcG/+VwrB/wAdHQby0/m3rT/g1iwfybf0
yBX8+BziQnvl/wDMjytrkoggna2umNEt7oCNm/1SCyH5cq5j5tFkxiyLHk34tVCew2LKMxHJdirs
VdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVYN+a+t3drpNtpFiSLzWZDACOvpigYD/AFi6
r8q5sOz8QlIyPKLha3IRHhHOSO8vaXpXl/SorCKWMMo5TyEgNJIR8TH+HtjllPJK6RjEccatHPrG
nL/u4E/5IJ/hkRgmejI5496i3mCzH2VdvoAH68mNLJidTFSbzD/JD9Jb+zJDS+bE6nyXLql4wSSQ
CK3ckCRVr+s4DhjyG5SMsuZ2CYJEXUMZ3cHcFSFFP9iMoMq6NwF9VdNORhUry/12LfrrlZysxjSP
zH+X2gayhae3EFz2u7cBH/2W1G+kZfh1k4cjfvaculjLoxiO4/MHyXQN/ue0NP8AWMiIPf4nT/hl
GZJjhz/0JtAllxf0osx8tefPLvmBVS1n9K8I+Kzmoslf8ns/+xOYOfSTx8xt3uZi1MJ8ubIcxm92
KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV5n+cCPb32gamwrbwTOkp8CSjj7wrfdm27LP1R
db2gPpLXIg0OxGZ1OFa4SYKW1wkwUm1e1je4nSFPtOafIdzkJkRFs4CzTLFgiEAgKgxgceJ8M1XE
bvq7LhFUgOU2mPvWSyY/ShOZFDIO6TTZx/1WQW91DPGrowIYbZgSgYndzYzBCozKqkt0yIDIoKu+
3TLmli3mH8vNB1hjcRqbC/ryW5twBVutXTYN8xQ++ZeHVzhtzDjZdNGW42KSxeYfPnk4rHrMJ1nR
12F6hLSIvu53/wCRg+TZacGHN9Hpl3fj9DWM2XF9Xqizjy95v0HX4g+n3IaWlXtnosy/NK/iKjNf
m008Z9Qc7FnjPkU5yhtdirsVdirsVdirsVdirsVdirsVdirsVedfmN5g1nTtbggsbuSCJrZXZEIo
WMjiv3AZtdDhhKBJF7uu1mWUZUD0ZF5B1C9v/LyXF5M08xlkUyN1oDtmJrICOSg5GlmZQsoX81NN
+u+S7wheUloUuU/2DUc/8AzZPQT4co89kayF4z5MV0C/tdX0iB0kBuoUWOcftBwKbjwalRmzlcJe
TrQBIeaq/ONirihGXCiLDUbDYkxpbZF5ctaRtdON3+GP5DqfvzX6ue/C52lhtxJ3XMJy3MFZSrCq
nYg9KYQaVK3WfS5PVhrJZMaundD4jMkEZRR+pxyDjNj6U2ivfrMIdH5xt/nvmKcfCXJGTiDdcCt1
xV2xFD0PXFLENd/LXSb2UXmlu2k6ih5JLb1CcvHgCOJ91I+nMzFrJAVL1RcXJpYnePpKAt/OvnDy
q6W3mqzN9YV4pqUFC1Pc7K3ybi2SlpcWXfGaPcxjqcmPaYsd7PdE8xaNrdt9Y0y6Sdf20Gzp7Oh+
Jc12XDLGakHOx5YzFgpjlTY7FXYq7FXYq7FXYq7FXYq7FVG9meG1llSnJBUV6ZKAs0xmaFsZubiC
9Z5ryytbiWOGQo8sKuwEaM6irVNOWZoiYjYkb97icXEdwCwLyx+Z+vyavp2mJbWVvaXN1DFIkEJj
ossiqxADUrQ+GZ2fQw4TKySB3uLh1cuIRoUS9iv7SO9sbmzk/u7mJ4X+UilT+vNJCXCQe52so2CH
h3lSxkME6xP9W1XT5mj5jdWU/wC65F/aXkG+XbOkykH3F0MBXvDK7e5S9UwXKehexirxVr/s4z+0
n+Zocx94GxybtpbHmpegyXCRSsERiAZD0p3OZAncbDRw0aLMre804RpHDPGVQBVUOtaD6c1E8c7s
guzjONUCESGBFQajKqbLXVxS40IIIqDsQcVSmeCfTZWurQcrY/30HgPEZlRkMg4Zc+9xpROM3Hkm
Vrdw3UQliaqnqO4Pgcx5wMTRciExIWFauQZN1xS3XAq2RI5Y2jkUPG4IdGAKkHqCDhGynd595p8n
to1zBr/lZDbXlu5eW2QkowAqeK+4BBXoR0zY4M/iAwybhwc2HgIlBnvlbzJZeYdHi1C2+Fj8FxDW
pjlA+JT+se2avUYDjlwl2GHKMkbCb5S2uxV2KuxV2KuxV2KuxV2KoTVp4oNNuJZU5xotWSvGo+eW
YgTIAMMhqJtgt15z0q2V6aaX5KyH/SCNnUqf2D2ObIaWR/i+x151ER/D9rEtK1PybDrun/V/Lzxz
/WofRlN9IwV/UHFuJShod6ZlZMeUwNz2o/wtGPJj4hUeve9yzn3dPHbqD9F/mPq9mBxivR9Zj92c
CQ/izjN/hlxYYnu2dLnjw5SO9Hag3ERSgfHG9UbuDQ5dijZILTklVFE291Bex+m4Afuv8RlU8csZ
sNkZiYooO6tZLc1+1GejeHzzIx5BL3tE8Zj7lOOd0NUYqfY0yZiCxEqRUWr6gn2bmT5FiR+OVHBA
9A2DNIdUVH5j1Resocf5Sr/ADKjpIdzYNVPvRUfmq7/bijYe1R/E5WdFHoS2DVy7kOmqtHd+taL9
X5/bjJ5IT9woMmcFxqW7AZqlcdk6XXJYaC+tmiB/3YnxKf8AP55hnTA/Sbcoagj6gmFtf2lz/cyq
x/l6H7jvlE8Uo8w3xyRlyKIrlbN1cVUL+NpLSQL9tRzQ9+S7jLMRqTDILiwOaaXyhrUfmKxQtoOp
EJqlqnSKQn7SjtvUr9I2qMzZQ8WJxn648nEjPwzxj6TzepW1zb3VvHc28glgmUPFIpqGVhUEZppR
INF2oIIsKmBLsVdirsVdirsVdirsVSbznKYvK2pSD9mEn8RmRpReSPvadQf3ZeC3WplwanOjEHQm
Sjocpk8xaYe31uD/AJOLjmHol7iyxfWPe+mc5R6J5d+a0H1LzJoWsqKK9baZh2CtX8Vlb7s3HZsr
hKPxdXr41ISUNTb/AEcezD9RzP0/1ODl5IB2MU3wkqRRlPehFR+vMkCw0HYprZarHKBFcUDHbkej
fPMPLpzHeLlY8wOxau9NIq9vuO8ff6MOPP0kjJh6hL+ZBodiOozKpx7XCTBSbXiTI0m14kwUm2R+
XtXVwLG4Ne0LHuP5T/DNdqsFeoOdps1+kplcaLp8xqE9J/5o/h/DpmPDUTHm3ywRPko/Vdatf95r
gXMY/wB1y9fvP9cnx45cxXuY8GSPI2uXXfSYJfW727fzUqp/z+nAdNe8TajUV9QpMLe8trgVhlV/
EA7j5jrmPLHKPMN8ZiXIpPFBbST3ui3iCS1uQQIz0oRXb6P1ZlZLMRMcw48KEjA8ik3lLU7nylrp
8q6pIW0y6YvpF23QFj9gntyP3N7Ng1OMZoeJH6hzZaeZxy4Jcuj0vNU7F2KuxV2KuxV2KuxV2Kpb
5k0qTVtDvNOikEUl1GUWRgSAag1NMtwZOCYl3NeWHFEjveYH8j9XPXVIP+AfNt/Ksf5pdd/J0u9E
6Z+TGp2epWl22owMtvNHKyhHqQjBqD7shk7TjKJFHcMoaCQINvWM07s2F/m5p31vydNMoq9lLHOt
OtK+m34PXM7s6fDlrvcTWwvH7mHG7+taJBcVqXVGc/5VKN+ObzCKm6bIfSt1AU+rSf78hQk+4FMu
w9R3EteXofJCh8tprtMrHWHhoktXi7H9oZjZdMJbjm3485Gx5JlLb2l9H6kbDkeki/xGYkZyxmi5
BhGYsJXc2lxbn4xVOzjpmZDJGXJxpwMVESZZTC14kyNJtekpUgg0I3BHUYDFILNNC1hb6DhIf9Jj
Hxj+YfzD+OabU6fgNjkXaafNxjfmmtcxXJaYKylWAZT1B3GI2QUBc6Lp7VlWtsy7l0PECnsdhmRD
UT5c2mWCPPkxuW8kF2JVmaUxEenK3UhTt45sRjHDVVbgmZ4ru048w6HZ+aNCEZok1PUtZu8cg7H2
7NmuxzOGbnziMsF35e+ari/hl0TV6x67pvwTK/2pY12EnuRty8dj3yrWacRPHH6JNulzmQ4ZfUGZ
ZguW7FXYq7FXYq7FXYq7FXYq7FXYqhNXsE1DSryxf7N1DJF8uakA/Rk8c+GQPcWM48USO94doU7N
5emt5KiS1l4FT1ALA/rJzq4fWHm5/SU4v15aPZzD9j4T8iP+bccJrLILkHoBSsPmXTjrgxpXscFJ
Vre7mgfnE5U9/A/MZXPGJCiyjMjknVrrkEg4XA4MdieqnMHJpSN4uVDUA810+l286+pbMFJ6U3Q/
d0wQ1Eo7STLCDuEtnt7i3NJUIHZuoP05lwnGXJx5RMeamHyVMbRFpezWs6TwtR0NR4HxByvJjEhR
ZwmYmwz7TtRhvrVZ4u+zp3Vu4OaLNiMJUXb4sgmLCKrlTaxXX9d9dzaW7fuFNJHH7ZHYewzaaXTc
PqPN12o1F+kckmEmZlOLbI/K2oVL2bnr8cX/ABsP45rtbi/ic7SZP4UJ500O8WaHzLovwaxp3xOF
/wB2xL1BHcgV+Y28Mq0+QVwS+ktuaBB44/UGY+WNftte0W31OBSglBEkR3KSKaMv39PbNfnwnHMx
Ln4conESCaZS2OxV2KuxV2KuxV2KuxV2KuxV2KvDNbtv0X5v1+wpxjuCLmIdvjIkoPl6h+7Oo0M+
KED8Hn9XDhnIJpGnreWqdSqMw/2DE/wyRPDnYAXiY+HzZOGiLj92sUfQ8A7fN9/+I0yuO9lkVMPk
qQqRB5CQgqQCx9gBUnIyIHNIFqsF5PA3KJyh706fSMjPGJcwmMyOSZ2/mA043MYYHqy/0OYk9H/N
LkR1PeFf0tHvN4nEch7D4T/wJ2+7K+LLDnuGXDjly2UpdFuU3iYSDw+yf6ZOOqieezGWAjkqaVf3
mk3QeSNhA9BMtNiPEHpUY5sccsaB3TinLGd+SaeYPMkbx/VbJ+SuP3sq+BH2R/HMXS6Qg8Um/Uam
xUWOCTNjTg2uEmCk2iLS8kt7iOdD8UbAj39vpyueMSBBZwnwmw9AhnjuLdZUPKOReQ+RGaGUTE0X
cxkCLSH8mQw8rXK9UW+lEZ8V9OP+Ncn2n/eD+r+tGg+j4s8zXOc7FXYq7FXYq7FXYq7FXYq7FXYq
8k/OGzNr5g0vVFFFuYWt5CPGM9T9Ev4ZvOycmxj3F1HaUNwe8N6BSTR41b7PxqfkWP8AXMnVbZCX
Gw7wY3Dbu96LU/a58G9qHfNmclR4nCEd6XX0we8mYfZ5FV+S7D8BhxCohZc1tvFLPKsUS8nY0Awz
kIiyiMSTQZBc2sWnaRKFNZZAFZ/Ek9PlTNfDIcmQdwcuUBCB72Ph82VOGuD4KSv5Eddu+RpUTBqV
3DQRykAfsncfccqngjLmGcckhyKYQ+YpBtNEGHipp+BrmNLRDoW+OpPUIgXui3H94gRj4rQ/euV+
FljyZceOXNx07TpCPRuAC2ygMrVPgB1x8eY5hPhRPIrH0W4X7Eit86g/xyQ1UeoYnTlRbT75OsZI
8VIP6ssGaB6sDikOiMHmpNG0G7huSUuVRvqMZFGZn22B7KxqcxM+nE5gjl1cnDm4YkH4Mx/LzRZd
I8p2VtMvC5kBnnU7ENKeQBHiq0BzT6zLx5CRydrpcfDAAskzFch2KuxV2KuxV2KuxV2KuxV2KuxV
hH5wab9a8otcqP3lhNHMCOvFj6bf8TB+jM/s2dZa73D10Lx33MU8pSiTR1b/ACzX7gc22qPq+Dq8
HJQaJLfWb6c/7rjMqD/WFSf15cJcWOI+DVIVMlIoEmuJljiUvI52AzOlIRFlxwCTQZjpemRWMNB8
Uzf3kn8B7Zqc2czPk5+PGIjzQPmmfjBBFX7bFj/sRT/jbL9DHclq1J2AY8HzZOGiFAiQSOPibeND
/wASPtkbvYJ5KfqEkkmpPUnJUhcHxpVysSaDrgISiEt7lnCBDzPVe4+fh9OVmcQLtkIlNLbS4VFZ
wJW/lIqv3HrmHkzk7BvhjA5rrpbC0i9R7hrNO3CVo1+hK8T92UAX0ttukmXzDrF1di10FrnUpf5W
hQinj8CK1PdsMoQiLlsmMpSNR3Zl5P8Ay5vDfjXPNJE98CGt7OoZI6dC9PhqOyjYdeua7Va4VwY+
Xe5+n0hB4p83ouat2DsVdirsVdirsVdirsVdirsVdirsVQWt6cupaPe2B/4+oJIgT2LKQp+g75Zi
nwyEu4sMkeKJHe8e8iy/7ipoG2khmYMp6gED+IOdBqOY9zpMXJd5pZ4ZUlQf70RNA30MG/jluk3F
dxtqz87TLR9JisIan4rhx+8fw/yR7ZTmzGZ8m3HjER5pjXKG1ifmi45aiIx0iQA/M7/qIzaaMVC+
9wtQbkhLeJI4vrNwKr/uqP8AmP8ATLpTJNBpA6qMk7yuXc1ZuuWRFCkFVtre5uG4wRtIe9BsPmeg
wSyRjzKiJPJNE0RIIxLqE4iTsibsT4fP5ZjHVGRqAtuGGt5FFQwgilvH9Vh/nO8zD5n7OVyn/OPE
fs/ayA7tvvULrW9H01TGZAXHWKP4mr7+/wAzkCJS3KRQasIvOvmGn6HsDa2jf8fs/wAK08QzCh/2
KnKcmbFj+o79zdjwTnyGzKNH/J7T1kFzr95JqVwd2iUskQPhyrzb7x8s1+XtOR2gKc7HoIj6jbO7
DTrDT7cW9jbx20A39OJQgr4mnU5rpzlI2TbnRgIigKRGRZOxV2KuxV2KuxV2KuxV2KuxV2KuxV2K
uxV40bcaV5/1rTqcYrlvrEI7fH+8AHyEhH0Zv4S48MT3bOlyR4cpCM1Wy+tpbilfTnR2/wBWtG/X
hxT4b9zDJG696OrlbY4sqqWYgKBUk9ABirBby9gm1Se4cGWMufTUbBgNlr7UGbeESIAOvmbkSjE0
vVtSkEpj9CKlE5/CAPYdT92V+NDGK5lkMcpJxZ+WrGAc7gmdhua/Cg+gfxOY89XI8tm6OADm3ca1
bQxmOyEaxpsZ2okK/LpyPyyMcJ5z/ak5OkUjOvRSXQSyhl1XUH2UhT9yKAT9wy6Ww39MWsAk95T/
AE/8vfO2t0fVbhdJs23MK/FIQf8AIU/8Sb6Mwcmvxw+n1FzMeinL6tma6D+W3lTR+Lpai7uVofrF
1SQ1H8q04L9Armtza3JPrQ8nPx6SEOlllGYjkuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2
KvLfzTt/qHmjRNaUUSUG2nb/AFT1P+xkP3Zt+zpcUJR+LrNdGpCSIKUjV/5iR91P65O92qlWRPSt
0r/eTfFTwQbD/gjgBs+5JFBKtRsbi+HoGb0bT/dnDd39qnZR9+X45iO9WWmcTLbovstI06zoYIhz
H+7G+JvvPT6MZ5pS5lMcYChqnmLS9OBEsoeYdIE+Jq+/8v04wxGSymAk1unnPzU9NNsmWyr/AHjf
BD/snaganhv8sullxYeZ3+1hHFky8hsyzR/ybiZkm8wX73bqNraAlYx7cz8RH+qFzX5e1D/AK97n
Y+zwPqLPtK0TSNJh9HTrSK1To3pqAzU/mb7TfSc1uTLKZuRtzoY4x5CkblbN2KuxV2KuxV2KuxV2
KuxV2KuxV2KuxV2KuxV2KuxV2KuxVh35saX9e8nXEirWWxdLlPGgPF/+EcnM7s/Jw5R57OJrYcWM
+SVeXIzquj6VNX4ZVkad/DhxV/xFMyc0uCUnFxR4ohrWry3huJJZ5FhQbfGQqqBsq79wBv74cMTW
yMshbGp/N9vJMLbS7eXULptkSNWoT7ChY/dmUMNC5Gg0eJewFpjZeR/PuuUbUZ10ayb/AHUu8pH+
qpr/AME4+WUT1mKH0+ot8NJknz9IZdoX5X+VNKKyNbm+uRv611RwD7JQJ+FcwMuvyT60PJzMejhH
pbLFVVUKoAUCgA2AAzDcpvFXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FVqyxM5RX
UuOqggkfRhpFrsCVK7tobq1mtZhyhnRopF8VcFSPuOGMiDY6IkLFF5VpflX80tKjl0fTmhhsfUcx
3zvGdmoDxrzkUGlacOubjJqNPP1Su+51cMGaPpHLvTXTPyetGkFz5g1CbUZzu0SEpH7gsSXI+XHK
cnaR5QHC2w0A5yNs40vRdJ0qH0dOtIrVO/pqAW/1m6t9JzAyZZTNyNubDHGPIUjcrZuxV2KuxV2K
uxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KsY83atf/AFm10HTJhbXl8rS3N53t7ZCAzr/l
sTxXMzTYgQZyFgdO8uLqMhFRGxP2BLB5R8iRxIklvFK6dZmkYys1almZTUsTmR42Y8raPDxBaPL3
lxWCafc6hbMdlFrdXAOw7c2bDxZP4hH4gIqH8Jl8CUXD5f1niG07zFfqDuv1horgfc0f6zlUskB9
UI/DZtEJfwyP3qjXfnfRG9S7VfMFhSshhjWC7TxKxr+7kHsKHIcGHJy9B+YZcWSHP1D7U60XzJo2
sxlrG4DSp/fWz/BNGehDxn4h+rMfLglDmG/HljPkUzypsdirsVdirsVdirsVdirsVdirsVdirsVd
irsVdirsVdirsVdirsVdirAvzBiS11/SNReT04biOWymkb7KnaWL7zyGbTs+fpkPi67XR3B+CXs7
xtxfY5sAARYcEkjmn3l23JDXTjY/DF/E/wAMwdXL+FzNNH+JN/3tvIZ7cVrvLD/N7r/lfrzF2kKL
k7xNhFC+juIVaP7J6/Mdsq8MxO7Z4gkNko1fy1pWpyC4dWt79P7rULZjFcIRsKOvX5NXLseaUduY
7jyap4oy35HvQkeseadC+HU4TrWmL/x/2qhbpB4yw1o/zT54Tix5Pp9Mu48vmgZZw+r1DvHNkWk6
3pWr2wuNOuUuI/2uJ+JSezqfiU+xGYuTFKBqQpyYZIyFgo7K2bsVdirsVdirsVdirsVdirsVdirs
VdirsVdirsVdirsVdirsVYr+Zdn6/lWW44CRtPliuwhFQRG1HB/2DNmZoJ1lA79nF1kbxnyYrF6E
QSFpK2sgrbs25TpQBv5fCvT5dNsInmObqyRyKbJ5hurGNIXt0aJRxRlJFQPv3yn8rHIbB3bRqJQ2
rZExebrc/wB5Ay/6rBv18chLQHoWY1o6hc2v2HMz25ZJD/eRsvwuB8q0bwOAaWdUeSfzMbsJpBq1
pIqsWMXMVX1BxBr4H7J+/MaWCQ83IjmifJGBwRUHbKabbY/rFhLYalHr2kxAX6grdW6igu4/tNG1
P26AlG8QBmRjPFEwly+5pmOGXFHn97KtN1G01KwhvrN/Ut51DI3f3BHYg7EeOYM4GJIPMOZCYkLC
JyDJ2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxVD6lZR32n3VlJ/d3MTwt8nUr/HJQ
lwyB7mM48QI73j1jLJNplrHJtPCksUi9+cGx/wCIZ1ENiT7vtedn3e9EWeq8F9GcepAex6j5ZPJp
73jsWMM1bHkiJLTknrWjerEew+0Mrjl3qWxZnH1juEMs5BodiO2W8LVae+X9aSJ/qdyQbeQ/CW3C
sfn2OYOq09+qPMOZp89ek8mS/Uol3t2a3Pgh+H/gDVfwzXeITz3c7wx02QmqXN1b2hMxjejKY5Fq
rBgaj4TUH78twwjKW1teaUox3QlnqCaJeLqKGmhaq4+up2trpjx9b2R2+F/A75HLi4xw/wAceXmP
xyZ4snD6v4ZfYWbZrXPdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdiryPULf6h50
v7Ej929ybmMdit1EWansH5Z0WmnxYL8vuLotRHhzV+N0koVuJIif7ssG/wBhX+mba7APe68ijSpb
X08LhonKsew7/RkZ4hIbpjMjkmJ1K1nYpexFJVNDLHsajbcHMYYZR3gdm45AfqG64Wfqb2syTj+W
vFvuODxa+oUvh39Jtkeja+YbVodS5RvAtUkYfbUdvds1+o03FK4b25uHUUKn0STUdZlv7kyN8MY2
ij7KP65m4tOICnEy5jM2m3lq8huEn0y5USQzqSEbcMCKOv0jMTW4iKmOjlaTJdxLIfI80x0me1kd
pE0+7ns7eR92MMTfu6nvxU8a+2avVgcV94BdlpieGu40yHMVyHYq7FXYqoX63LWNytqeNyYnEDeE
hU8T9+ShXEL5MZ3RrmxDTvN0caRsZ2QUQtDOTIJAxp8DN8at4VJGbPLouodfj1bNs1TsnYq7FXYq
7FXYq7FXYq7FXYq7FXYq7FXnP5jWfoeaNE1EbLcq1rIR/Mjc0r8+bZuOzsnonF1evh6oyYtrEYt7
i8k7zGNUHf4viY/embfTzsRHdbrMsaJRmkaX9Xj+u3Yo6gskZ/ZAFan3yvUajiPDFnixcPqKSLK8
shP7TElj+JJzP2AcVd6wDfATQdD/ABx4VtH2uqaiBSN2dR1DDkPxzHyYMfVtjlkOSYc3aIy3MECA
CrA1jPzLgnj92YstjUSW4b8wFHTZ7y+vFTQLJ57uNxS4Vma0jNeskpVdvYdcry5RGPrIr7WzFiMj
6R+p6l5f0n9FaXHaNJ605Ly3M9Kc5pWLyNTsOR29s0GbJxyt3eLHwxpMcqbHYq7FXYqo3twbaznu
QhkMMbyCMGhbgpNK79aZKEbIHexlKgSl2gWOkCBbu0s/QkeoDuGZ+Pbg8nxemR9mm2W55zuibasM
Y1YFJvlDe7FXYq7FXYq7FXYq7FXYq7FXYq7FXYqxT8y7Uv5a+uqKyaZcQ3ajvRW4v/wjk5maCVZK
/nCnF1kbhfduxKWzSfUYLo0eNEJHhzBHA/czZtY5KiQ6wwuQK3XrpYNLmqwVpB6ag9TyND+GHTxu
YRmNRKQ22lX1xGEt4ykRoZJ5KqG+QPxU+jM+WojE2ebixxE8laSLRtPIFxMLic7BBULU7bKtWY18
Pwys55y5ekMvDiPMp1pmgeatU4m0sRYW3a6vR6dAf99wL8XyJ65hZdZjj1s+X63Lx6SculMp038t
dIiZZtWmk1e5Xek/wwA/5MC/D/wVc1uXtCcto+kfjq5+PRQjz3LLIYYYIlihjWKJBRI0AVQPAAbZ
hEk7lywK5L8CXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYqhtSsY7/Trqyl/u7q
J4W+TqV/jkoS4ZA9zGceIEd7zjy6GGgpc3PGNrBXgnLGgE0TemA1flyzcZT66HX7nVY4+mz0QKXs
V/dBNMs5NVuo/hBgTlGhP80rfAlfGuWn0j1HhDWBxHYWU8tfIXmHUgDrF6unWx62dl8cpHg8zbA/
6oIzFlroR+gWe8uTHRyl9Rr3Mp0Tyh5d0WjWNmiz97mT95MfH42qRX22zBy6meT6i5mPBCHIJzlD
c7FXYq7FXYq7FXYq7FVO5keO2lkjXk6IzIviQKgZDLIxiSOYDKAsgFg2j61qM84mOon636zqbObl
6TqGKgVX+7qOnwnKtH2fLJihnGSfqiCRYMb6jhrpy57NWXWgTljIjsSPPy3/AGM3s7uK6t1mj6N1
GxIPcGmZU4GJophISFhWyLJ2KuxV2KuxV2KuxV2KuxV2KuxVD32o2FhEJr24jt4mPFXkYKCx7Cvf
JQhKRoC2MpiO5NIe38w6BcmlvqVrK3SiTRsa/INkjhmOYPyYjLE8iEsfyF5Uub+XUJbY3DXEhnaJ
pGaAyt9p/TB4kt3rlw1eQR4Qar5tf5aBN0n8Fvb28Sw28SQwoKJHGoVQPYCgzGJJNlvAA5KmBLsV
dirsVdirsVdirsVdirsVdirHrnyTpl1qUt5MzLFK6yfV4gIxyCBDyYb0PGvw0Na75lYdVLFDggK5
/abcbJpYznxS3Tuzs7Wzt0trWMRQp9lB77k77knuTmPKZkbPNvjERFBWyLJ2KuxV2KuxV2KuxV2K
uxV2KuxVhnme+ls/NVu/wkvZ0s+e/FhI3r8O3JlMde9Bmy0kIyxkHv8A7HX6qZjMEdyJsE/SduZ7
1I5lJpGsiKQQKg9vemHMBA1FcRMxcldtF0+N/W0cnSbsCjm3CiNj29WE/u3+dOXgRlHHLlP1D8dW
7gH8PpKrH5hubKia5AIkA/46NuGe2PvIN3h/2VV/ysgcAP0H4df2/jZkMxH1fPp+xPgQQCDUHcEZ
jOQ7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FWI/mLbL9UsL4ry9C
cwyf6lwtP+Tqx5sOz5esx73B10fSD3MYi1efm0S3DW8yEj7RCN8+wObc4BV1Y+11YzHldFFQ+YdR
guE+su5CncbVI9jTfKzpYSHpbBqJxO7LYrpZrcSqyTWzqTU/D8NN69vn0zVShRrkXYidi+YUvLmp
xx+nZFq2shZbF/5WXdoD9HxR/wCTt+zuNVhIN9ev6/1stNlBFfL9TI8wnLdirsVdirsVdirsVdir
sVdirsVdirsVdirsVdirsVdirsVdirsVdirsVSnzZYNf+W9QtkFZTC0kI/4ti/eR/wDDoMv00+HI
D5tWeHFAh5Q9vLcywmDf60kcob+VeAqx+ZOdRDMBHfo87LGSdurUt/Jbzywo3O3ibhxk+IFl2NAe
m/hk44xIAn6ixMjE10R9tqs7WL2/B4LZzyZg1E/4foPGhyjJhAlxWCQ2xyExrkE38qR399MkVvDK
2n+tDcPeOoWIGCVZP3bVPqc+HH4fpzX63LCt/q3Hzc3SY5X/AEXpGaR27sVdirsVdirsVdirsVdi
rsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVeVRw2+nSXWn15XdtcPbQW0amSZogxeIiJKvxEb
r275uxMyAPSrdOYcJI62idM8iaxdMJHiSwQ7+vdUmnNTWqwoeC/Nmr7Y5O0QNhv7th8049CTz2ZZ
pvkfQrNlmnRtQuV3E12RJQ+Kx0Ea/MLX3zXZdZkltdDyc7HpYR8z5sgzFcl2KuxV2KuxV2KuxV2K
uxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KpFquuXVtrCWEbRQxmBZjPMjMtSzqRUPGBTiP
vzKw4BKHFvzcbLmMZV5K9nqi+t/pEUYMzBEvYKGOQ9FD/tIfAGo/yq7ZCeIgbfJlDKCf0ptlDe7F
XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FWKefLFpUsblCwZHe
H92CW/eLzBAFSTWLiB4tmw0GXhJBcHXY+IApXo2geap4PTk4Wdo7h2E68mI/4xAjr3DccydRqsN7
Cz+Px1cfBpstb7BnyrxULUmgpU9T880ztm8VdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsV
dirsVdirsVdirsVdirsVf//Z
-
-
-
-
-
- uuid:fc6e5f3d-7fcc-7942-b7c9-1536cd5e2491
- xmp.did:F87F1174072068118C14A1D6B52AC88B
- uuid:5D20892493BFDB11914A8590D31508C8
- proof:pdf
-
- xmp.iid:F77F1174072068118C14A1D6B52AC88B
- xmp.did:F77F1174072068118C14A1D6B52AC88B
- uuid:5D20892493BFDB11914A8590D31508C8
- proof:pdf
-
-
-
-
- saved
- xmp.iid:F77F1174072068118C14A1D6B52AC88B
- 2010-09-17T16:55:22-07:00
- Adobe Illustrator CS5
- /
-
-
- saved
- xmp.iid:F87F1174072068118C14A1D6B52AC88B
- 2010-09-27T16:24:05-07:00
- Adobe Illustrator CS5
- /
-
-
-
-
-
- Document
- Print
-
-
- False
- True
- 1
-
- 612.000000
- 1090.666992
- Pixels
-
-
-
- Cyan
- Magenta
- Yellow
- Black
-
-
-
-
-
- Default Swatch Group
- 0
-
-
-
- White
- RGB
- PROCESS
- 255
- 255
- 255
-
-
- Black
- RGB
- PROCESS
- 35
- 31
- 32
-
-
- CMYK Red
- RGB
- PROCESS
- 237
- 28
- 36
-
-
- CMYK Yellow
- RGB
- PROCESS
- 255
- 242
- 0
-
-
- CMYK Green
- RGB
- PROCESS
- 0
- 166
- 81
-
-
- CMYK Cyan
- RGB
- PROCESS
- 0
- 174
- 239
-
-
- CMYK Blue
- RGB
- PROCESS
- 46
- 49
- 146
-
-
- CMYK Magenta
- RGB
- PROCESS
- 236
- 0
- 140
-
-
- C=15 M=100 Y=90 K=10
- RGB
- PROCESS
- 190
- 30
- 45
-
-
- C=0 M=90 Y=85 K=0
- RGB
- PROCESS
- 239
- 65
- 54
-
-
- C=0 M=80 Y=95 K=0
- RGB
- PROCESS
- 241
- 90
- 41
-
-
- C=0 M=50 Y=100 K=0
- RGB
- PROCESS
- 247
- 148
- 30
-
-
- C=0 M=35 Y=85 K=0
- RGB
- PROCESS
- 251
- 176
- 64
-
-
- C=5 M=0 Y=90 K=0
- RGB
- PROCESS
- 249
- 237
- 50
-
-
- C=20 M=0 Y=100 K=0
- RGB
- PROCESS
- 215
- 223
- 35
-
-
- C=50 M=0 Y=100 K=0
- RGB
- PROCESS
- 141
- 198
- 63
-
-
- C=75 M=0 Y=100 K=0
- RGB
- PROCESS
- 57
- 181
- 74
-
-
- C=85 M=10 Y=100 K=10
- RGB
- PROCESS
- 0
- 148
- 68
-
-
- C=90 M=30 Y=95 K=30
- RGB
- PROCESS
- 0
- 104
- 56
-
-
- C=75 M=0 Y=75 K=0
- RGB
- PROCESS
- 43
- 182
- 115
-
-
- C=80 M=10 Y=45 K=0
- RGB
- PROCESS
- 0
- 167
- 157
-
-
- C=70 M=15 Y=0 K=0
- RGB
- PROCESS
- 39
- 170
- 225
-
-
- C=85 M=50 Y=0 K=0
- RGB
- PROCESS
- 28
- 117
- 188
-
-
- C=100 M=95 Y=5 K=0
- RGB
- PROCESS
- 43
- 57
- 144
-
-
- C=100 M=100 Y=25 K=25
- RGB
- PROCESS
- 38
- 34
- 98
-
-
- C=75 M=100 Y=0 K=0
- RGB
- PROCESS
- 102
- 45
- 145
-
-
- C=50 M=100 Y=0 K=0
- RGB
- PROCESS
- 146
- 39
- 143
-
-
- C=35 M=100 Y=35 K=10
- RGB
- PROCESS
- 158
- 31
- 99
-
-
- C=10 M=100 Y=50 K=0
- RGB
- PROCESS
- 218
- 28
- 92
-
-
- C=0 M=95 Y=20 K=0
- RGB
- PROCESS
- 238
- 42
- 123
-
-
- C=25 M=25 Y=40 K=0
- RGB
- PROCESS
- 194
- 181
- 155
-
-
- C=40 M=45 Y=50 K=5
- RGB
- PROCESS
- 155
- 133
- 121
-
-
- C=50 M=50 Y=60 K=25
- RGB
- PROCESS
- 114
- 102
- 88
-
-
- C=55 M=60 Y=65 K=40
- RGB
- PROCESS
- 89
- 74
- 66
-
-
- C=25 M=40 Y=65 K=0
- RGB
- PROCESS
- 196
- 154
- 108
-
-
- C=30 M=50 Y=75 K=10
- RGB
- PROCESS
- 169
- 124
- 80
-
-
- C=35 M=60 Y=80 K=25
- RGB
- PROCESS
- 139
- 94
- 60
-
-
- C=40 M=65 Y=90 K=35
- RGB
- PROCESS
- 117
- 76
- 41
-
-
- C=40 M=70 Y=100 K=50
- RGB
- PROCESS
- 96
- 57
- 19
-
-
- C=50 M=70 Y=80 K=70
- RGB
- PROCESS
- 60
- 36
- 21
-
-
-
-
-
- Grays
- 1
-
-
-
- C=0 M=0 Y=0 K=100
- RGB
- PROCESS
- 35
- 31
- 32
-
-
- C=0 M=0 Y=0 K=90
- RGB
- PROCESS
- 65
- 64
- 66
-
-
- C=0 M=0 Y=0 K=80
- RGB
- PROCESS
- 88
- 89
- 91
-
-
- C=0 M=0 Y=0 K=70
- RGB
- PROCESS
- 109
- 110
- 113
-
-
- C=0 M=0 Y=0 K=60
- RGB
- PROCESS
- 128
- 130
- 133
-
-
- C=0 M=0 Y=0 K=50
- RGB
- PROCESS
- 147
- 149
- 152
-
-
- C=0 M=0 Y=0 K=40
- RGB
- PROCESS
- 167
- 169
- 172
-
-
- C=0 M=0 Y=0 K=30
- RGB
- PROCESS
- 188
- 190
- 192
-
-
- C=0 M=0 Y=0 K=20
- RGB
- PROCESS
- 209
- 211
- 212
-
-
- C=0 M=0 Y=0 K=10
- RGB
- PROCESS
- 230
- 231
- 232
-
-
- C=0 M=0 Y=0 K=5
- RGB
- PROCESS
- 241
- 242
- 242
-
-
-
-
-
- Brights
- 1
-
-
-
- C=0 M=100 Y=100 K=0
- RGB
- PROCESS
- 237
- 28
- 36
-
-
- C=0 M=75 Y=100 K=0
- RGB
- PROCESS
- 242
- 101
- 34
-
-
- C=0 M=10 Y=95 K=0
- RGB
- PROCESS
- 255
- 222
- 23
-
-
- C=85 M=10 Y=100 K=0
- RGB
- PROCESS
- 0
- 161
- 75
-
-
- C=100 M=90 Y=0 K=0
- RGB
- PROCESS
- 33
- 64
- 154
-
-
- C=60 M=90 Y=0 K=0
- RGB
- PROCESS
- 127
- 63
- 152
-
-
-
-
-
-
-
-
- Adobe PDF library 9.90
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
endstream
endobj
3 0 obj
<>
endobj
7 0 obj
<>/Resources<>/Properties<>/Shading<>/XObject<>>>/Thumb 1815 0 R/TrimBox[0.0 0.0 612.0 1090.67]/Type/Page>>
endobj
1719 0 obj
<>stream
-HWn]7߯8?p|5/&{a^X8SU#^IHهlxv6mnoZyl}V{hnCSۓѦSQM^J#94uoɤƖ\_zILYx䱗߫{7}>khFK55ipV{aSp >>,-\KcU%mwwm7/Om~9JK2j_a6)NiؕK3
-NMAsFnQgg'`UTgKtC=wihC+H-=LUx6q`