diff --git a/deploy/lib/MLClient.rb b/deploy/lib/MLClient.rb index 5d0fc522..222230a1 100644 --- a/deploy/lib/MLClient.rb +++ b/deploy/lib/MLClient.rb @@ -17,18 +17,26 @@ class MLClient def initialize(options) @ml_username = options[:user_name] @ml_password = options[:password] - @logger = options[:logger] + @logger = options[:logger] || logger @request = {} end - def self.set_logger(logger) + def MLClient.logger() + @@logger ||= Logger.new(STDOUT) + end + + def MLClient.logger=(logger) @@logger = logger end + def logger() + @logger + end + def get_http if (!@http) @http = Roxy::Http.new({ - :logger => @logger + :logger => logger }) end @http @@ -37,7 +45,7 @@ def get_http def build_request_params(url, verb) uri = URI.parse url if (!@request[verb]) - @logger.debug("creating new #{verb} request\n") + logger.debug("creating new #{verb} request\n") @request[verb] = Net::HTTP.const_get(verb.capitalize).new(uri.request_uri) @request[verb].add_field 'Connection', 'keep-alive' @request[verb].add_field 'Keep-Alive', '30' @@ -52,7 +60,7 @@ def build_request_params(url, verb) :protocol => uri.scheme, :user_name => @ml_username, :password => @ml_password, - :logger => @logger + :logger => logger } end diff --git a/deploy/lib/server_config.rb b/deploy/lib/server_config.rb index ab5347cc..c21b688d 100644 --- a/deploy/lib/server_config.rb +++ b/deploy/lib/server_config.rb @@ -56,7 +56,7 @@ def initialize(options) :logger => options[:logger] }) - @server_version = @properties["ml.server-version"] + @server_version = @properties["ml.server-version"].to_i if (@properties["ml.bootstrap-port"]) @bootstrap_port = @properties["ml.bootstrap-port"] @@ -69,12 +69,12 @@ def initialize(options) end end - @logger.debug "config: #{@config_file}" - @logger.debug "pwd: #{ServerConfig.pwd}" - @logger.debug "user: #{@ml_username}" - @logger.debug "password: #{@ml_password}" - @logger.debug "hostname: #{@hostname}" - @logger.debug "port: #{@bootstrap_port}" + logger.debug "config: #{@config_file}" + logger.debug "pwd: #{ServerConfig.pwd}" + logger.debug "user: #{@ml_username}" + logger.debug "password: #{@ml_password}" + logger.debug "hostname: #{@hostname}" + logger.debug "port: #{@bootstrap_port}" end def self.pwd @@ -86,9 +86,9 @@ def get_properties end def info - @logger.info "Properties:" + logger.info "Properties:" @properties.each do |k, v| - @logger.info k + ": " + v + logger.info k + ": " + v end end @@ -101,9 +101,9 @@ def self.init force_props = find_arg(['--force-properties']) != nil ? true : false force_config = find_arg(['--force-config']) != nil ? true : false if (!force and !force_props and File.exists?(build_properties)) then - @@logger.error "build.properties file found." - @@logger.error " Use --force to reset all configuration files." - @@logger.error " Use --force-properties to reset just the properties file.\n" + logger.error "build.properties file found." + logger.error " Use --force to reset all configuration files." + logger.error " Use --force-properties to reset just the properties file.\n" else #create clean properties file FileUtils.cp sample_properties, build_properties @@ -119,9 +119,9 @@ def self.init properties = ServerConfig.properties target_config = File.expand_path(properties["ml.config.file"], __FILE__) if (!force and !force_config and File.exists?(target_config)) then - @@logger.error "ml-config.xml file found." - @@logger.error " Use --force to reset all configuration files." - @@logger.error " Use --force-config to reset just the configuration file.\n" + logger.error "ml-config.xml file found." + logger.error " Use --force to reset all configuration files." + logger.error " Use --force-config to reset just the configuration file.\n" else #create clean marklogic configuration file FileUtils.cp sample_config, target_config @@ -133,7 +133,7 @@ def self.initcpf target_config = File.expand_path("../../pipeline-config.xml", __FILE__) if (File.exists?(target_config)) then - @@logger.error "initcpf has already been run. Use --force to rerun it.\n" + logger.error "initcpf has already been run. Use --force to rerun it.\n" else FileUtils.cp sample_config, target_config end @@ -274,9 +274,9 @@ def execute_query(query, properties = {}) def restart group = ARGV.shift if (group) - @logger.info("Restarting MarkLogic Server group #{group} on #{@hostname}") + logger.info("Restarting MarkLogic Server group #{group} on #{@hostname}") else - @logger.info("Restarting MarkLogic Server on #{@hostname}") + logger.info("Restarting MarkLogic Server on #{@hostname}") end setup = open(File.expand_path('../xquery/setup.xqy', __FILE__)).readlines.join r = execute_query %Q{#{setup} setup:do-restart("#{group}")} @@ -299,39 +299,81 @@ def self.plugin runme << " #{plugin_command}" if plugin_command runme << " #{package} " if package runme << " #{package_version} " if package_version - @@logger.debug runme + logger.debug runme output = `#{runme}` - @@logger.info(output) + logger.info(output) end def config - @logger.info get_config + logger.info get_config end def bootstrap if @hostname && @hostname != "" - @logger.info("Bootstrapping your project into MarkLogic on #{@hostname}...") + logger.info("Bootstrapping your project into MarkLogic on #{@hostname}...") setup = open(File.expand_path('../xquery/setup.xqy', __FILE__)).readlines.join r = execute_query %Q{#{setup} setup:do-setup(#{get_config})} - @logger.debug r.body + logger.debug r.body - if (r.body.match("(note: restart required)")) then - @logger.warn("NOTE*** RESTART OF MARKLOGIC IS REQUIRED") + if (r.body.match(" @ml_password, :xcc_server => @hostname, :xcc_port => @properties["ml.xcc-port"], - :logger => @logger + :logger => logger }) end @xcc @@ -802,21 +844,21 @@ def execute_query_5(query, properties = {}) end if (db_id != nil) then - @logger.debug("using dbid: #{db_id}") + logger.debug("using dbid: #{db_id}") r = go "http://#{@hostname}:#{@bootstrap_port}/qconsole/endpoints/eval.xqy", "post", {}, { :dbid => db_id, :resulttype => "text", :q => query } - @logger.debug(r.body) + logger.debug(r.body) else - @logger.debug("using sid: #{sid}") + logger.debug("using sid: #{sid}") r = go "http://#{@hostname}:#{@bootstrap_port}/qconsole/endpoints/eval.xqy", "post", {}, { :sid => sid, :resulttype => "text", :q => query } - @logger.debug(r.body) + logger.debug(r.body) end if (r.body.match(/\{"error"/)) then @@ -826,7 +868,7 @@ def execute_query_5(query, properties = {}) return r end - def self.substitute_properties(sub_me, with_me, prefix = "") + def ServerConfig.substitute_properties(sub_me, with_me, prefix = "") dangling_vars = {} begin needs_rescan = false @@ -859,7 +901,7 @@ def self.substitute_properties(sub_me, with_me, prefix = "") sub_me end - def self.load_properties(properties_filename, prefix = "") + def ServerConfig.load_properties(properties_filename, prefix = "") properties = {} File.open(properties_filename, 'r') do |properties_file| properties_file.read.each_line do |line| @@ -1050,9 +1092,9 @@ def build_config(config_file) config end - def self.properties - default_properties_file = File.expand_path("../../default.properties", __FILE__) - properties_file = File.expand_path("../../build.properties", __FILE__) + def ServerConfig.properties(prop_file_location = "../..") + default_properties_file = File.expand_path("#{prop_file_location}/default.properties", __FILE__) + properties_file = File.expand_path("#{prop_file_location}/build.properties", __FILE__) if !File.exist?(properties_file) then raise ExitException.new("You must run ml init to configure your application.") end @@ -1067,7 +1109,7 @@ def self.properties properties["environment"] = environment if environment - env_properties_file = File.expand_path("../../#{environment}.properties", __FILE__) + env_properties_file = File.expand_path("#{prop_file_location}/#{environment}.properties", __FILE__) if (File.exists?(env_properties_file)) properties.merge!(ServerConfig.load_properties(env_properties_file, "ml.")) end diff --git a/deploy/lib/xquery/setup.xqy b/deploy/lib/xquery/setup.xqy index 660a6ef4..5aa4be14 100644 --- a/deploy/lib/xquery/setup.xqy +++ b/deploy/lib/xquery/setup.xqy @@ -43,102 +43,255 @@ declare variable $default-user := xdmp:user("nobody"); declare variable $context-path := fn:resolve-uri(".", xdmp:get-request-path()); +declare variable $roll-back := map:map(); + +declare variable $restart-needed as xs:boolean := fn:false(); + (: A note on naming conventions: $admin-config refers to the configuration passed around by the Admin APIs $import-config is the import/export configuration format that setup:get-configuration() generates :) +declare function setup:add-rollback( + $key as xs:string, + $value as item()+) +{ + map:put( + $roll-back, + $key, + (map:get($roll-back, $key), $value)) +}; + +declare function setup:get-rollback-config() +{ + let $config := + element configuration + { + element gr:http-servers + { + map:get($roll-back, "http-servers") + }, + element gr:xdbc-servers + { + map:get($roll-back, "xdbc-servers") + }, + element db:databases + { + map:get($roll-back, "databases") + }, + element as:assignments + { + map:get($roll-back, "assignments") + }, + element mt:mimetypes + { + map:get($roll-back, "mimetypes") + }, + element sec:amps + { + map:get($roll-back, "amps") + }, + element sec:users + { + map:get($roll-back, "users") + }, + element sec:roles + { + map:get($roll-back, "roles") + }, + element sec:privileges + { + map:get($roll-back, "privileges") + } + } + return + $config +}; + declare function setup:do-setup($import-config as element(configuration)) as item()* { + try + { setup:create-privileges($import-config), setup:create-roles($import-config), - setup:create-roles($import-config), setup:create-users($import-config), - setup:create-amps($import-config), setup:create-mimetypes($import-config), setup:create-forests($import-config), setup:create-databases($import-config), setup:attach-forests($import-config), - setup:apply-databases-settings($import-config), + setup:create-amps($import-config), + setup:apply-database-settings($import-config), setup:configure-databases($import-config), setup:create-appservers($import-config), - setup:apply-appservers-settings($import-config) + setup:apply-appservers-settings($import-config), + if ($restart-needed) then + "note: restart required" + else () + } + catch($ex) + { + setup:do-wipe(setup:get-rollback-config()), + $ex + } }; declare function setup:do-wipe($import-config as element(configuration)) as item()* { - let $config := admin:get-configuration() + let $admin-config := admin:get-configuration() let $groupid := xdmp:group() - return - ( - for $as-name in ($import-config/gr:http-servers/gr:http-server/gr:http-server-name, $import-config/gr:xdbc-servers/gr:xdbc-server/gr:xdbc-server-name) + let $remove-appservers := + for $as-name in ($import-config/gr:http-servers/gr:http-server/gr:http-server-name, + $import-config/gr:xdbc-servers/gr:xdbc-server/gr:xdbc-server-name) return - try { - if (admin:appserver-exists($config, $groupid, $as-name)) then - xdmp:set($config, admin:appserver-delete($config, admin:appserver-get-id($config, $groupid, $as-name))) - else () - } catch($ex) { - xdmp:log($ex) - } - , - admin:save-configuration-without-restart($config) - ), + if (admin:appserver-exists($admin-config, $groupid, $as-name)) then + xdmp:set( + $admin-config, + admin:appserver-delete( + $admin-config, + admin:appserver-get-id($admin-config, $groupid, $as-name))) + else () + return + if (admin:save-configuration-without-restart($admin-config)) then + xdmp:set($restart-needed, fn:true()) + else (), + + let $admin-config := admin:get-configuration() + for $amp in $import-config/sec:amps/sec:amp + where admin:database-exists($admin-config, $amp/sec:db-name) + return + try + { + xdmp:eval( + 'import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; + declare variable $amp external; + if (sec:amp-exists($amp/sec:namespace, $amp/sec:local-name, $amp/sec:doc-uri, xdmp:database($amp/sec:db-name))) then + sec:remove-amp( + $amp/sec:namespace, + $amp/sec:local-name, + $amp/sec:doc-uri, + xdmp:database($amp/sec:db-name)) + else ()', + (xs:QName("amp"), $amp), + + {xdmp:database("Security")} + ) + } + catch($ex) + { + if ($ex/error:code = "SEC-AMPDNE") then () + else + xdmp:rethrow() + }, for $db-config in $import-config/db:databases/db:database return - try { setup:delete-database-and-forests($db-config) } catch ($e) {xdmp:log($e)}, + setup:delete-database-and-forests($db-config), (: Even though we delete forests that are attached to the database above, we will delete : forests named in the config file. When named forests are in use, we'll be able to : delete them even if they aren't attached to the database for whatever reason. :) - let $config := admin:get-configuration() - return - ( + let $admin-config := admin:get-configuration() + let $remove-forests := for $forest-name in $import-config/as:assignments/as:assignment/as:forest-name return - if (admin:forest-exists($config, $forest-name)) then - try { - xdmp:set($config, admin:forest-delete($config, admin:forest-get-id($config, $forest-name), fn:true())) - } catch ($e) { - xdmp:log($e) - } + if (admin:forest-exists($admin-config, $forest-name)) then + xdmp:set( + $admin-config, + admin:forest-delete( + $admin-config, + admin:forest-get-id($admin-config, $forest-name), fn:true())) else () - , - admin:save-configuration($config) - ), - - let $config := admin:get-configuration() return - ( + if (admin:save-configuration-without-restart($admin-config)) then + xdmp:set($restart-needed, fn:true()) + else (), + + let $admin-config := admin:get-configuration() + let $remove-mimetypes := for $x in $import-config/mt:mimetypes/mt:mimetype return - try { xdmp:set($config, admin:mimetypes-delete($config, admin:mimetype($x/mt:name, $x/mt:extension, $x/mt:format))) } catch ($e) {xdmp:log($e)} - , - admin:save-configuration($config) - ), + try + { + xdmp:set( + $admin-config, + admin:mimetypes-delete( + $admin-config, + admin:mimetype($x/mt:name, $x/mt:extension, $x/mt:format))) + } + catch($ex) + { + if ($ex/error:code = "ADMIN-NOSUCHITEM") then () + else + xdmp:rethrow() + } + return + admin:save-configuration($admin-config), for $user in $import-config/sec:users/sec:user/sec:user-name return - xdmp:eval('import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; - declare variable $user as xs:string external; - try { sec:remove-user($user) } catch ($e) {xdmp:log($e)}', - (xs:QName("user"), $user), {xdmp:database("Security")}), + try + { + xdmp:eval( + 'import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; + declare variable $user as xs:string external; + sec:remove-user($user)', + (xs:QName("user"), $user), + + {xdmp:database("Security")} + ) + } + catch($ex) + { + if ($ex/error:code = "SEC-USERDNE") then () + else + xdmp:rethrow() + }, for $role in $import-config/sec:roles/sec:role/sec:role-name return - xdmp:eval('import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; - declare variable $role as xs:string external; - try { sec:remove-role($role) } catch ($e) {xdmp:log($e)}', - (xs:QName("role"), $role), {xdmp:database("Security")}), + try + { + xdmp:eval( + 'import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; + declare variable $role as xs:string external; + sec:remove-role($role)', + (xs:QName("role"), $role), + + {xdmp:database("Security")} + ) + } + catch($ex) + { + if ($ex/error:code = "SEC-ROLEDNE") then () + else + xdmp:rethrow() + }, - for $priv in $import-config/sec:privileges/sec:privilege - return - xdmp:eval('import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; - declare variable $action as xs:string external; - declare variable $kind as xs:string external; - try { sec:remove-privilege($action, $kind) } catch ($e) {xdmp:log($e)}', - (xs:QName("action"), $priv/sec:action, xs:QName("kind"), $priv/sec:kind), {xdmp:database("Security")}) + for $priv in $import-config/sec:privileges/sec:privilege + return + try + { + xdmp:eval( + 'import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; + declare variable $action as xs:string external; + declare variable $kind as xs:string external; + sec:remove-privilege($action, $kind)', + (xs:QName("action"), $priv/sec:action, + xs:QName("kind"), $priv/sec:kind), + + {xdmp:database("Security")} + ) + } + catch($ex) + { + if ($ex/error:code = "SEC-PRIVDNE") then () + else + xdmp:rethrow() + }, + if ($restart-needed) then + "note: restart required" + else () }; (:::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -146,35 +299,44 @@ declare function setup:do-wipe($import-config as element(configuration)) as item :: to it. ::) -declare function setup:delete-database-and-forests($database-config as element(db:database)) +declare function setup:delete-database-and-forests($db-config as element(db:database)) { - let $db-name := $database-config/db:database-name - let $config := admin:get-configuration() + let $db-name := $db-config/db:database-name + let $admin-config := admin:get-configuration() return - if (admin:database-exists($config, $db-name)) then - let $db-id := admin:database-get-id($config, $db-name) - let $forest-ids := admin:database-get-attached-forests($config, $db-id) - let $detach := ( + if (admin:database-exists($admin-config, $db-name)) then + let $db-id := admin:database-get-id($admin-config, $db-name) + let $forest-ids := admin:database-get-attached-forests($admin-config, $db-id) + let $detach := + ( for $id in $forest-ids return - xdmp:set($config, admin:database-detach-forest($config, $db-id, $id)), - admin:save-configuration-without-restart($config) + xdmp:set($admin-config, admin:database-detach-forest($admin-config, $db-id, $id)), + if (admin:save-configuration-without-restart($admin-config)) then + xdmp:set($restart-needed, fn:true()) + else () ) - let $config := admin:get-configuration() + let $admin-config := admin:get-configuration() let $forest-ids := if (fn:exists($forest-ids)) then $forest-ids else (: For the case where the database exists but the forests are detached :) - setup:find-forest-ids($database-config) - let $config := admin:forest-delete($config, $forest-ids, fn:true()) - let $config := admin:database-delete($config, $db-id) - return admin:save-configuration-without-restart($config) + setup:find-forest-ids($db-config) + let $admin-config := admin:forest-delete($admin-config, $forest-ids, fn:true()) + let $admin-config := admin:database-delete($admin-config, $db-id) + return + if (admin:save-configuration-without-restart($admin-config)) then + xdmp:set($restart-needed, fn:true()) + else () else (: The database does not exist. Check for the forests anyway :) - let $forest-ids := setup:find-forest-ids($database-config) - let $config := admin:forest-delete($config, $forest-ids, fn:true()) - return admin:save-configuration-without-restart($config) + let $forest-ids := setup:find-forest-ids($db-config) + let $admin-config := admin:forest-delete($admin-config, $forest-ids, fn:true()) + return + if (admin:save-configuration-without-restart($admin-config)) then + xdmp:set($restart-needed, fn:true()) + else () }; (:::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -182,40 +344,51 @@ declare function setup:delete-database-and-forests($database-config as element(d ::) declare function setup:do-restart($group-name as xs:string?) as item()* { - try { + try + { let $group-id := if ($group-name = "") then xdmp:group() else xdmp:group($group-name) - return ( - xdmp:restart(xdmp:group-hosts($group-id), "Restarting hosts to make configuration changes take effect"), + return + ( + xdmp:restart( + xdmp:group-hosts($group-id), + "Restarting hosts to make configuration changes take effect"), fn:concat($group-name, "Group restarted") ) - } catch ($e) { + } + catch ($e) + { if ($e/error:code = "XDMP-NOSUCHGROUP") then fn:concat("Cannot restart group ", $group-name, ", no such group") else - $e + xdmp:rethrow() } }; (:::::::::::::::::::::::::::::::::::::::::::::::::::::::::: :: ::) -declare function setup:find-forest-ids($database-config as element(db:database)) as xs:unsignedLong* +declare function setup:find-forest-ids( + $db-config as element(db:database)) as xs:unsignedLong* { let $group-id := xdmp:group() - let $config := admin:get-configuration() - let $hosts := admin:group-get-host-ids($config, $group-id) - let $data-directory := $database-config/db:forests/db:data-directory + let $admin-config := admin:get-configuration() + let $hosts := admin:group-get-host-ids($admin-config, $group-id) + let $data-directory := $db-config/db:forests/db:data-directory for $host at $i in $hosts - for $j in (1 to $database-config/db:forests-per-host) - let $name := fn:string-join( - ($database-config/db:database-name, xdmp:host-name($host), xs:string($j)), "-") + for $j in (1 to $db-config/db:forests-per-host) + let $name := + fn:string-join(( + $db-config/db:database-name, + xdmp:host-name($host), + xs:string($j)), + "-") return - if (admin:forest-exists($config, $name)) then - admin:forest-get-id($config, $name) + if (admin:forest-exists($admin-config, $name)) then + admin:forest-get-id($admin-config, $name) else () }; @@ -225,33 +398,43 @@ declare function setup:find-forest-ids($database-config as element(db:database)) declare function setup:create-mimetypes($import-config as element(configuration)) as item()* { - try { - for $mimetype-config in setup:get-mimetypes-from-config($import-config) - return - setup:create-mimetype($mimetype-config/mt:name, $mimetype-config/mt:extension, $mimetype-config/mt:format) - } catch ($e) { - fn:concat("Mimetype creation failed: ", $e//err:format-string) - } -}; - -declare function setup:create-mimetype($name as xs:string, $extension as xs:string, $format as xs:string) as item()* -{ - try { - let $admin-config := admin:get-configuration() - return - + for $mimetype-config in $import-config/mt:mimetypes/mt:mimetype + let $name as xs:string := $mimetype-config/mt:name + let $extension as xs:string := $mimetype-config/mt:extension + let $format as xs:string := $mimetype-config/mt:format + let $admin-config := admin:get-configuration() + return if (admin:mimetypes-get($admin-config)[mt:name = $name]) then fn:concat("Mimetype ", $name, " already exists, not recreated..") else let $admin-config := admin:mimetypes-add($admin-config, admin:mimetype($name, $extension, $format)) - let $restart-hosts := - admin:save-configuration-without-restart($admin-config) return - fn:concat("Mimetype ", $name, " succesfully created", if ($restart-hosts) then " (note: restart required)" else ()) - } catch ($e) { - fn:concat("Mimetype ", $name, " creation failed: ", $e//err:format-string) - } + ( + if (admin:save-configuration-without-restart($admin-config)) then + xdmp:set($restart-needed, fn:true()) + else (), + setup:add-rollback("mimetypes", $mimetype-config), + fn:concat("Mimetype ", $name, " succesfully created") + ) +}; + +declare function setup:validate-mimetypes($import-config as element(configuration)) +{ + for $mimetype-config in $import-config/mt:mimetypes/mt:mimetype + let $name as xs:string := $mimetype-config/mt:name + let $extension as xs:string := $mimetype-config/mt:extension + let $format as xs:string := $mimetype-config/mt:format + let $admin-config := admin:get-configuration() + let $match := admin:mimetypes-get($admin-config)[mt:name = $name] + return + if ($match) then + if ($match/mt:extension != $extension or $match/mt:format != $format) then + setup:validation-fail(fn:concat("Mimetype mismatch: ", $name)) + else + () + else + setup:validation-fail(fn:concat("Missing mimetype: ", $name)) }; (:::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -260,37 +443,26 @@ declare function setup:create-mimetype($name as xs:string, $extension as xs:stri declare function setup:create-forests($import-config as element(configuration)) as item()* { - try { - for $db-config in - setup:get-databases-from-config($import-config) - let $database-name := - setup:get-database-name-from-database-config($db-config) - let $forests-per-host := $db-config/db:forests-per-host - let $forest-config := setup:get-database-forest-configs($import-config, $database-name) - return - if (fn:exists($forests-per-host)) then - setup:create-forests-from-count($db-config, $database-name, $forests-per-host) - else - setup:create-forests-from-config($import-config, $db-config, $database-name) - - -(: - for $forest-config in - setup:get-forests-from-config($import-config) - - let $forest-name := - setup:get-forest-name-from-forest-config($forest-config) - let $data-directory := - setup:get-data-directory-from-forest-config($forest-config) - let $host-name := - setup:get-hostname-from-forest-config($forest-config) - return - setup:create-forest-by-host-name($forest-name, $data-directory, $host-name) -:) + for $db-config in setup:get-databases-from-config($import-config) + let $database-name := setup:get-database-name-from-database-config($db-config) + let $forests-per-host := $db-config/db:forests-per-host + return + if (fn:exists($forests-per-host)) then + setup:create-forests-from-count($db-config, $database-name, $forests-per-host) + else + setup:create-forests-from-config($import-config, $db-config, $database-name) +}; - } catch ($e) { - fn:concat("Forests creation failed: ", $e//err:format-string) - } +declare function setup:validate-forests($import-config as element(configuration)) +{ + for $db-config in setup:get-databases-from-config($import-config) + let $database-name := setup:get-database-name-from-database-config($db-config) + let $forests-per-host := $db-config/db:forests-per-host + return + if (fn:exists($forests-per-host)) then + setup:validate-forests-from-count($db-config, $database-name, $forests-per-host) + else + setup:validate-forests-from-config($import-config, $db-config, $database-name) }; (:::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -301,18 +473,32 @@ declare function setup:create-forests-from-config( $db-config as element(db:database), $database-name as xs:string) as item()* { - xdmp:log(fn:concat("Roxy building ", $database-name, " forests by configuration")), - let $forest-configs := setup:get-database-forest-configs($import-config, $database-name) - for $forest-config in $forest-configs - let $forest-name := - setup:get-forest-name-from-forest-config($forest-config) - let $data-directory := - setup:get-data-directory-from-forest-config($forest-config) - let $host-name := - setup:get-hostname-from-forest-config($forest-config) - let $log := xdmp:log(fn:concat("forest; name=", $forest-name, ", data=", $data-directory, ", host=", $host-name)) + for $forest-config in setup:get-database-forest-configs($import-config, $database-name) + let $forest-name as xs:string? := $forest-config/as:forest-name[fn:string-length(.) > 0] + let $data-directory as xs:string? := $forest-config/as:data-directory[fn:string-length(.) > 0] + let $host-name as xs:string? := $forest-config/as:host[fn:string-length(.) > 0] + return + setup:create-forest( + $forest-name, + $data-directory, + if ($host-name) then xdmp:host($host-name) else ()) + +}; + +declare function setup:validate-forests-from-config( + $import-config as element(configuration), + $db-config as element(db:database), + $database-name as xs:string) +{ + for $forest-config in setup:get-database-forest-configs($import-config, $database-name) + let $forest-name as xs:string? := $forest-config/as:forest-name[fn:string-length(.) > 0] + let $data-directory as xs:string? := $forest-config/as:data-directory[fn:string-length(.) > 0] + let $host-name as xs:string? := $forest-config/as:host[fn:string-length(.) > 0] return - setup:create-forest-by-host-name($forest-name, $data-directory, $host-name) + setup:validate-forest( + $forest-name, + $data-directory, + if ($host-name) then xdmp:host($host-name) else ()) }; (:::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -323,19 +509,31 @@ declare function setup:create-forests-from-count( $database-name as xs:string, $forests-per-host as xs:int) as item()* { - let $group-id := xdmp:group() - let $config := admin:get-configuration() - let $hosts := admin:group-get-host-ids($config, $group-id) let $data-directory := $db-config/db:forests/db:data-directory - for $host at $i in $hosts + for $host at $i in admin:group-get-host-ids(admin:get-configuration(), xdmp:group()) for $j in (1 to $forests-per-host) let $forest-name := fn:string-join(($database-name, xdmp:host-name($host), xs:string($j)), "-") - let $log := xdmp:log(fn:concat("Create forest ", $forest-name, " on host ", $host, " at dir ", $data-directory)) - return setup:create-forest( - $forest-name, - $data-directory, - $host - ) + return + setup:create-forest( + $forest-name, + $data-directory, + $host) +}; + +declare function setup:validate-forests-from-count( + $db-config as element(db:database), + $database-name as xs:string, + $forests-per-host as xs:int) +{ + let $data-directory := $db-config/db:forests/db:data-directory + for $host at $i in admin:group-get-host-ids(admin:get-configuration(), xdmp:group()) + for $j in (1 to $forests-per-host) + let $forest-name := fn:string-join(($database-name, xdmp:host-name($host), xs:string($j)), "-") + return + setup:validate-forest( + $forest-name, + $data-directory, + $host) }; (:::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -343,58 +541,74 @@ declare function setup:create-forests-from-count( ::) declare function setup:get-database-forest-configs( $import-config as element(configuration), - $database-name as xs:string) as element(as:assignment)* + $db as xs:string) as element(as:assignment)* { - let $names := $import-config/db:databases/db:database[db:database-name = $database-name]/db:forests/db:forest-id/@name - return $import-config/as:assignments/as:assignment[as:forest-name = $names] + $import-config/as:assignments/as:assignment[ + as:forest-name = $import-config/db:databases/db:database[db:database-name = $db]/db:forests/db:forest-id/@name] }; (:::::::::::::::::::::::::::::::::::::::::::::::::::::::::: :: ::) -declare function setup:create-forest-by-host-name( +declare function setup:create-forest( $forest-name as xs:string, $data-directory as xs:string?, - $host-name as xs:string?) as item()* + $host-id as xs:unsignedLong?) as item()* { - let $host-id := - if ($host-name) then xdmp:host($host-name) - else () - let $log := xdmp:log(fn:concat("create-forest-by-host-name; host-id=", $host-id)) - return - setup:create-forest($forest-name, $data-directory, $host-id) + if (xdmp:forests()[$forest-name = xdmp:forest-name(.)]) then + fn:concat("Forest ", $forest-name, " already exists, not recreated..") + else + let $host := ($host-id, $default-host)[1] + let $admin-config := + admin:forest-create(admin:get-configuration(), $forest-name, $host, $data-directory) + return + ( + if (admin:save-configuration-without-restart($admin-config)) then + xdmp:set($restart-needed, fn:true()) + else (), + setup:add-rollback( + "assignments", + element as:assignment + { + element as:forest-name { $forest-name } + }), + fn:string-join(( + "Forest ", $forest-name, " succesfully created", + if ($data-directory) then (" at ", $data-directory) + else (), + if ($host) then (" on ", xdmp:host-name($host)) + else ()), "") + ) }; -(:::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - :: - ::) -declare function setup:create-forest( +declare function setup:validate-forest( $forest-name as xs:string, $data-directory as xs:string?, - $host-id as xs:unsignedLong?) as item()* + $host-id as xs:unsignedLong?) { - (: xdmp:log( text { "setup:create-forest", $forest-name, $data-directory, xdmp:host-name($host-id) }), :) - try { - if (xdmp:forests()[$forest-name = xdmp:forest-name(.)]) then - fn:concat("Forest ", $forest-name, " already exists, not recreated..") - else - let $host := ($host-id, $default-host)[1] - let $admin-config := admin:get-configuration() - let $admin-config := - admin:forest-create($admin-config, $forest-name, $host, $data-directory) - let $restart-hosts := - admin:save-configuration-without-restart($admin-config) - return - fn:string-join(( - "Forest ", $forest-name, " succesfully created", - if ($data-directory) then (" at ", $data-directory) - else (), - if ($host) then (" on ", xdmp:host-name($host)) - else (), - "..", if ($restart-hosts) then " (note: restart required)" else ()), "") - } catch ($e) { - fn:concat("Forest ", $forest-name, " creation failed: ", $e//err:format-string) - } + if (xdmp:forests()[$forest-name = xdmp:forest-name(.)]) then + let $forest-id := xdmp:forest($forest-name) + let $admin-config := admin:get-configuration() + return + ( + if ($data-directory) then + let $actual := admin:forest-get-data-directory($admin-config, $forest-id) + return + if ($actual = $data-directory) then () + else + setup:validation-fail(fn:concat("Forest data directory mismatch: ", $data-directory, " != ", $actual)) + else (), + + if ($host-id) then + let $actual := admin:forest-get-host($admin-config, $forest-id) + return + if ($actual = $host-id) then () + else + setup:validation-fail(fn:concat("Forest host mismatch: ", $host-id, " != ", $actual)) + else () + ) + else + setup:validation-fail(fn:concat("Forest missing: ", $forest-name)) }; (:::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -403,42 +617,36 @@ declare function setup:create-forest( declare function setup:create-databases($import-config as element(configuration)) as item()* { - try { - for $database-config in - setup:get-databases-from-config($import-config) - - return - setup:create-database($database-config) - - } catch ($e) { - fn:concat("Databases creation failed: ", $e//err:format-string) - } + for $db-config in setup:get-databases-from-config($import-config) + let $database-name := setup:get-database-name-from-database-config($db-config) + return + if (xdmp:databases()[xdmp:database-name(.) = $database-name]) then + fn:concat("Database ", $database-name, " already exists, not recreated..") + else + let $admin-config := + admin:database-create( + admin:get-configuration(), + $database-name, + $default-security, + $default-schemas) + return + ( + if (admin:save-configuration-without-restart($admin-config)) then + xdmp:set($restart-needed, fn:true()) + else (), + setup:add-rollback("databases", $db-config), + fn:concat("Database ", $database-name, " succesfully created.") + ) }; -declare function setup:create-database($database-config as element(db:database)) as item()* +declare function setup:validate-databases($import-config as element(configuration)) { - xdmp:log("create-database-with-forests"), - let $database-name := - setup:get-database-name-from-database-config($database-config) + for $db-config in setup:get-databases-from-config($import-config) + let $database-name := setup:get-database-name-from-database-config($db-config) return - - try { - if (xdmp:databases()[$database-name = xdmp:database-name(.)]) then - fn:concat("Database ", $database-name, " already exists, not recreated..") - else - let $admin-config := - admin:get-configuration() - let $admin-config := - admin:database-create($admin-config, $database-name, $default-security, $default-schemas) - let $restart-hosts := - admin:save-configuration-without-restart($admin-config) - - return - fn:concat("Database ", $database-name, " succesfully created..", if ($restart-hosts) then " (note: restart required)" else ()) - - } catch ($e) { - fn:concat("Database ", $database-name, " creation failed: ", $e//err:format-string) - } + if (xdmp:databases()[xdmp:database-name(.) = $database-name]) then () + else + setup:validation-fail(fn:concat("Missing database: ", $database-name)) }; (:::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -447,919 +655,1628 @@ declare function setup:create-database($database-config as element(db:database)) declare function setup:attach-forests($import-config as element(configuration)) as item()* { - try { - (: - for $database-config in - setup:get-databases-from-config($import-config) - - return - setup:attach-forests-to-database($database-config) - :) - - for $db-config in - setup:get-databases-from-config($import-config) - let $database-name := - setup:get-database-name-from-database-config($db-config) - let $forests-per-host := $db-config/db:forests-per-host - let $forest-config := setup:get-database-forest-configs($import-config, $database-name) - return - if (fn:exists($forests-per-host)) then - setup:attach-forests-by-count($db-config) - else - setup:attach-forests-by-config($import-config, $db-config, $database-name) - - + for $db-config in setup:get-databases-from-config($import-config) + let $database-name := setup:get-database-name-from-database-config($db-config) + let $forests-per-host := $db-config/db:forests-per-host + let $forest-config := setup:get-database-forest-configs($import-config, $database-name) + return + if (fn:exists($forests-per-host)) then + setup:attach-forests-by-count($db-config) + else + setup:attach-forests-by-config($import-config, $db-config, $database-name) +}; - } catch ($e) { - fn:concat("Attaching forests failed: ", $e//err:format-string) - } +declare function setup:validate-attached-forests($import-config as element(configuration)) +{ + for $db-config in setup:get-databases-from-config($import-config) + let $database-name := setup:get-database-name-from-database-config($db-config) + let $forests-per-host := $db-config/db:forests-per-host + let $forest-config := setup:get-database-forest-configs($import-config, $database-name) + return + if (fn:exists($forests-per-host)) then + setup:validate-attached-forests-by-count($db-config) + else + setup:validate-attached-forests-by-config($import-config, $db-config, $database-name) }; declare function setup:attach-forests-by-config( $import-config as element(configuration), - $database-config as element(db:database), + $db-config as element(db:database), $database-name as xs:string) as item()* { - for $forest-ref in setup:get-forest-refs-from-database-config($database-config) + for $forest-ref in $db-config/db:forests/db:forest-id return setup:attach-database-forest($database-name, $forest-ref/@name) }; +declare function setup:validate-attached-forests-by-config( + $import-config as element(configuration), + $db-config as element(db:database), + $database-name as xs:string) +{ + for $forest-ref in $db-config/db:forests/db:forest-id + return + setup:validate-attached-database-forest($database-name, $forest-ref/@name) +}; + declare function setup:attach-forests-by-count($db-config as element(db:database)) as item()* { - let $database-name := - setup:get-database-name-from-database-config($db-config) + let $database-name := setup:get-database-name-from-database-config($db-config) + for $host in admin:group-get-host-ids(admin:get-configuration(), xdmp:group()) + let $hostname := xdmp:host-name($host) + for $j in (1 to setup:get-forests-per-host-from-database-config($db-config)) + let $forest-name := fn:string-join(($database-name, $hostname, xs:string($j)), "-") return + setup:attach-database-forest($database-name, $forest-name) +}; - try { - let $group-id := xdmp:group() - let $config := admin:get-configuration() - let $hosts := admin:group-get-host-ids($config, $group-id) - let $forests-per-host := setup:get-forests-per-host-from-database-config($db-config) - for $host at $i in $hosts - for $j in (1 to $forests-per-host) - let $forest-name := fn:string-join(($database-name, xdmp:host-name($host), xs:string($j)), "-") - let $log := xdmp:log(fn:concat("Attach forest ", $forest-name, " on host ", $host)) +declare function setup:validate-attached-forests-by-count($db-config as element(db:database)) +{ + let $database-name := setup:get-database-name-from-database-config($db-config) + for $host in admin:group-get-host-ids(admin:get-configuration(), xdmp:group()) + let $hostname := xdmp:host-name($host) + for $j in (1 to setup:get-forests-per-host-from-database-config($db-config)) + let $forest-name := fn:string-join(($database-name, $hostname, xs:string($j)), "-") + return + setup:validate-attached-database-forest($database-name, $forest-name) +}; - return - setup:attach-database-forest($database-name, $forest-name) +declare function setup:attach-database-forest( + $database-name as xs:string, $forest-name as xs:string) as item()* +{ + let $db := xdmp:database($database-name) + let $forest := xdmp:forest($forest-name) + let $admin-config := admin:get-configuration() - } catch ($e) { - fn:concat("Attaching forests to database ", $database-name, " failed: ", $e//err:format-string) - } + (: if the forests are already attached we need to detach them first :) + let $admin-config := + if (xdmp:database-forests(xdmp:database($database-name))[$forest-name = xdmp:forest-name(.)]) then + admin:database-detach-forest($admin-config, $db, $forest) + else + $admin-config + let $admin-config := admin:database-attach-forest($admin-config, $db, $forest) + return + ( + if (admin:save-configuration-without-restart($admin-config)) then + xdmp:set($restart-needed, fn:true()) + else (), + fn:concat("Forest ", $forest-name, " succesfully attached to database ", $database-name) + ) }; -declare function setup:attach-database-forest($database-name as xs:string, $forest-name as xs:string) as item()* +declare function setup:validate-attached-database-forest( + $database-name as xs:string, $forest-name as xs:string) { - try { - if (fn:not(xdmp:databases()[$database-name = xdmp:database-name(.)])) then - fn:concat("Database ", $database-name, " does not exist, forest ", $forest-name, " not attached. Database creation might have failed..") - else if (fn:not(xdmp:forests()[$forest-name = xdmp:forest-name(.)])) then - fn:concat("Forest ", $forest-name, " does not exist, not attached to database ", $database-name, ". Forest creation might have failed or is missing in the import..") - else if (xdmp:database-forests(xdmp:database($database-name))[$forest-name = xdmp:forest-name(.)]) then - let $admin-config := - admin:get-configuration() - let $admin-config := - admin:database-detach-forest($admin-config, xdmp:database($database-name), xdmp:forest($forest-name)) - let $admin-config := - admin:database-attach-forest($admin-config, xdmp:database($database-name), xdmp:forest($forest-name)) - let $restart-hosts := - admin:save-configuration-without-restart($admin-config) - return - fn:concat("Forest ", $forest-name, " succesfully reattached to database ", $database-name, "..", if ($restart-hosts) then " (note: restart required)" else ()) + let $db := xdmp:database($database-name) + let $forest := xdmp:forest($forest-name) + let $admin-config := admin:get-configuration() + return + if (xdmp:database-forests(xdmp:database($database-name))[$forest-name = xdmp:forest-name(.)]) then () else - let $admin-config := - admin:get-configuration() - let $admin-config := - admin:database-attach-forest($admin-config, xdmp:database($database-name), xdmp:forest($forest-name)) - let $restart-hosts := - admin:save-configuration-without-restart($admin-config) - return - fn:concat("Forest ", $forest-name, " succesfully attached to database ", $database-name, "..", if ($restart-hosts) then " (note: restart required)" else ()) - - } catch ($e) { - fn:concat("Attaching forest ", $forest-name, " to database ", $database-name, " failed: ", $e//err:format-string) - } + setup:validation-fail(fn:concat("Forest not attached to database: ", $forest-name, " => ", $database-name)) }; (:::::::::::::::::::::::::::::::::::::::::::::::::::::::::: :: Applying of database settings ::) -declare function setup:apply-databases-settings($import-config as element(configuration)) as item()* +declare function setup:apply-database-settings($import-config as element(configuration)) as item()* { - try { - for $database-config in - setup:get-databases-from-config($import-config) - + let $admin-config := admin:get-configuration() + for $db-config in setup:get-databases-from-config($import-config) + let $database-name := setup:get-database-name-from-database-config($db-config) + let $database := xdmp:database($database-name) + let $settings := + + language + stemmed-searches + word-searches + word-positions + fast-phrase-searches + fast-reverse-searches + fast-case-sensitive-searches + fast-diacritic-sensitive-searches + fast-element-word-searches + element-word-positions + fast-element-phrase-searches + element-value-positions + attribute-value-positions + three-character-searches + three-character-word-positions + fast-element-character-searches + trailing-wildcard-searches + trailing-wildcard-word-positions + fast-element-trailing-wildcard-searches + two-character-searches + one-character-searches + uri-lexicon + collection-lexicon + reindexer-enable + reindexer-throttle + reindexer-timestamp + directory-creation + maintain-last-modified + maintain-directory-last-modified + inherit-permissions + inherit-collections + inherit-quality + format-compatibility + index-detection + expunge-locks + tf-normalization + + let $apply-settings := + for $setting in $settings/*:setting + let $value := fn:data(xdmp:value(fn:concat("$db-config/db:", $setting))) + where fn:exists($value) return - setup:apply-database-settings($database-config) + xdmp:set( + $admin-config, + xdmp:value(fn:concat("admin:database-set-", $setting, "($admin-config, $database, $value)"))) + return + ( + if (admin:save-configuration-without-restart($admin-config)) then + xdmp:set($restart-needed, fn:true()) + else (), - } catch ($e) { - fn:concat("Applying database settings failed: ", $e//err:format-string) - } + fn:concat("Database ", $database-name, " settings applied succesfully.") + ) }; -declare function setup:apply-database-settings($database-config as element(db:database)) as item()* +declare function setup:validate-database-settings($import-config as element(configuration)) { - let $database-name := - setup:get-database-name-from-database-config($database-config) - + let $admin-config := admin:get-configuration() + for $db-config in setup:get-databases-from-config($import-config) + let $database := xdmp:database(setup:get-database-name-from-database-config($db-config)) + let $settings := + + language + stemmed-searches + word-searches + word-positions + fast-phrase-searches + fast-reverse-searches + fast-case-sensitive-searches + fast-diacritic-sensitive-searches + fast-element-word-searches + element-word-positions + fast-element-phrase-searches + element-value-positions + attribute-value-positions + three-character-searches + three-character-word-positions + fast-element-character-searches + trailing-wildcard-searches + trailing-wildcard-word-positions + fast-element-trailing-wildcard-searches + two-character-searches + one-character-searches + uri-lexicon + collection-lexicon + reindexer-enable + reindexer-throttle + reindexer-timestamp + directory-creation + maintain-last-modified + maintain-directory-last-modified + inherit-permissions + inherit-collections + inherit-quality + format-compatibility + index-detection + expunge-locks + tf-normalization + + for $setting in $settings/*:setting + let $expected := fn:data(xdmp:value(fn:concat("$db-config/db:", $setting))) + let $actual := xdmp:value(fn:concat("admin:database-get-", $setting, "($admin-config, $database)")) + where fn:exists($expected) return + if ($expected = $actual) then () + else + setup:validation-fail(fn:concat("database ", $setting, " mismatch: ", $expected, " != ", $actual)) +}; - try { - let $admin-config := admin:get-configuration() - let $database := xdmp:database($database-name) - - let $value := setup:get-setting-from-database-config-as-string($database-config, "language") - let $admin-config := - if ($value) then - admin:database-set-language($admin-config, $database, $value) - else - $admin-config - - let $value := setup:get-setting-from-database-config-as-string($database-config, "stemmed-searches") - let $admin-config := - if ($value) then - admin:database-set-stemmed-searches($admin-config, $database, $value) - else - $admin-config - - let $value := setup:get-setting-from-database-config-as-boolean($database-config, "word-searches") - let $admin-config := - if (fn:exists($value)) then - admin:database-set-word-searches($admin-config, $database, $value) - else - $admin-config - - let $value := setup:get-setting-from-database-config-as-boolean($database-config, "word-positions") - let $admin-config := - if (fn:exists($value)) then - admin:database-set-word-positions($admin-config, $database, $value) - else - $admin-config - - let $value := setup:get-setting-from-database-config-as-boolean($database-config, "fast-phrase-searches") - let $admin-config := - if (fn:exists($value)) then - admin:database-set-fast-phrase-searches($admin-config, $database, $value) - else - $admin-config - - let $value := setup:get-setting-from-database-config-as-boolean($database-config, "fast-reverse-searches") - let $admin-config := - if (fn:exists($value)) then - admin:database-set-fast-reverse-searches($admin-config, $database, $value) - else - $admin-config - - let $value := setup:get-setting-from-database-config-as-boolean($database-config, "fast-case-sensitive-searches") - let $admin-config := - if (fn:exists($value)) then - admin:database-set-fast-case-sensitive-searches($admin-config, $database, $value) - else - $admin-config - - let $value := setup:get-setting-from-database-config-as-boolean($database-config, "fast-diacritic-sensitive-searches") - let $admin-config := - if (fn:exists($value)) then - admin:database-set-fast-diacritic-sensitive-searches($admin-config, $database, $value) - else - $admin-config - - let $value := setup:get-setting-from-database-config-as-boolean($database-config, "fast-element-word-searches") - let $admin-config := - if (fn:exists($value)) then - admin:database-set-fast-element-word-searches($admin-config, $database, $value) - else - $admin-config +(:::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + :: Configuration of databases + ::) - let $value := setup:get-setting-from-database-config-as-boolean($database-config, "element-word-positions") - let $admin-config := - if (fn:exists($value)) then - admin:database-set-element-word-positions($admin-config, $database, $value) - else - $admin-config +declare function setup:configure-databases($import-config as element(configuration)) as item()* +{ + for $db-config in setup:get-databases-from-config($import-config) + let $database-name := setup:get-database-name-from-database-config($db-config) + let $database := xdmp:database($database-name) - let $value := setup:get-setting-from-database-config-as-boolean($database-config, "fast-element-phrase-searches") - let $admin-config := - if (fn:exists($value)) then - admin:database-set-fast-element-phrase-searches($admin-config, $database, $value) + let $remove-existing-range-path-indexes := + (: wrap in try catch because this function is new to 6.0 and will fail in older version of ML :) + try + { + if (xdmp:eval(' + import module namespace admin = "http://marklogic.com/xdmp/admin" at "/MarkLogic/admin.xqy"; + declare variable $database external; + let $admin-config := admin:get-configuration() + let $remove-existing-indexes := + for $index in admin:database-get-range-path-indexes($admin-config, $database) + return + xdmp:set( + $admin-config, + admin:database-delete-range-path-index($admin-config, $database, $index)) + return + admin:save-configuration-without-restart($admin-config)', + (xs:QName("database"), $database))) then + xdmp:set($restart-needed, fn:true()) + else () + } + catch($ex) + { + if ($ex/error:code = "XDMP-UNDFUN") then () else - $admin-config + xdmp:rethrow() + } - let $value := setup:get-setting-from-database-config-as-boolean($database-config, "element-value-positions") - let $admin-config := - if (fn:exists($value)) then - admin:database-set-element-value-positions($admin-config, $database, $value) + let $remove-existing-path-namespaces := + (: wrap in try catch because this function is new to 6.0 and will fail in older version of ML :) + try + { + if (xdmp:eval(' + import module namespace admin = "http://marklogic.com/xdmp/admin" at "/MarkLogic/admin.xqy"; + declare variable $database external; + let $admin-config := admin:get-configuration() + let $remove-existing-indexes := + for $index in admin:database-get-path-namespaces($admin-config, $database) + return + xdmp:set($admin-config, admin:database-delete-path-namespace($admin-config, $database, $index)) + return + admin:save-configuration-without-restart($admin-config)', + (xs:QName("database"), $database))) then + xdmp:set($restart-needed, fn:true()) + else () + } + catch($ex) + { + if ($ex/error:code = "XDMP-UNDFUN") then () else - $admin-config + xdmp:rethrow() + } - let $value := setup:get-setting-from-database-config-as-boolean($database-config, "attribute-value-positions") - let $admin-config := - if (fn:exists($value)) then - admin:database-set-attribute-value-positions($admin-config, $database, $value) - else - $admin-config + let $admin-config := setup:add-word-lexicons(admin:get-configuration(), $database, $db-config) + let $admin-config := setup:add-fragment-roots($admin-config, $database, $db-config) + let $admin-config := setup:add-fragment-parents($admin-config, $database, $db-config) - let $value := setup:get-setting-from-database-config-as-boolean($database-config, "three-character-searches") - let $admin-config := - if (fn:exists($value)) then - admin:database-set-three-character-searches($admin-config, $database, $value) - else - $admin-config + let $admin-config := setup:config-word-query($admin-config, $database, $db-config) + (: + + + + - let $value := setup:get-setting-from-database-config-as-boolean($database-config, "three-character-word-positions") - let $admin-config := - if (fn:exists($value)) then - admin:database-set-three-character-word-positions($admin-config, $database, $value) - else - $admin-config + :) - let $value := setup:get-setting-from-database-config-as-boolean($database-config, "fast-element-character-searches") - let $admin-config := - if (fn:exists($value)) then - admin:database-set-fast-element-character-searches($admin-config, $database, $value) - else - $admin-config + let $admin-config := setup:set-schema-database($admin-config, $db-config, $database) + let $admin-config := setup:set-security-database($admin-config, $db-config, $database) + let $admin-config := setup:set-triggers-database($admin-config, $db-config, $database) - let $value := setup:get-setting-from-database-config-as-boolean($database-config, "trailing-wildcard-searches") - let $admin-config := - if (fn:exists($value)) then - admin:database-set-trailing-wildcard-searches($admin-config, $database, $value) - else - $admin-config + (: remove any existing range index (copied from default.xqy) :) + let $remove-existing-indexes := + for $index in admin:database-get-range-element-indexes($admin-config, $database) + return + xdmp:set( + $admin-config, + admin:database-delete-range-element-index($admin-config, $database, $index)) - let $value := setup:get-setting-from-database-config-as-boolean($database-config, "trailing-wildcard-word-positions") - let $admin-config := - if (fn:exists($value)) then - admin:database-set-trailing-wildcard-word-positions($admin-config, $database, $value) - else - $admin-config + let $admin-config := setup:add-range-element-indexes($admin-config, $database, $db-config) - let $value := setup:get-setting-from-database-config-as-boolean($database-config, "fast-element-trailing-wildcard-searches") - let $admin-config := - if (fn:exists($value)) then - admin:database-set-fast-element-trailing-wildcard-searches($admin-config, $database, $value) - else - $admin-config + (: remove any existing range element attribute index :) + let $remove-existing-indexes := + for $index in admin:database-get-range-element-attribute-indexes($admin-config, $database) + return + xdmp:set( + $admin-config, + admin:database-delete-range-element-attribute-index($admin-config, $database, $index)) - let $value := setup:get-setting-from-database-config-as-boolean($database-config, "two-character-searches") - let $admin-config := - if (fn:exists($value)) then - admin:database-set-two-character-searches($admin-config, $database, $value) - else - $admin-config + let $admin-config := setup:add-range-element-attribute-indexes($admin-config, $database, $db-config) - let $value := setup:get-setting-from-database-config-as-boolean($database-config, "one-character-searches") - let $admin-config := - if (fn:exists($value)) then - admin:database-set-one-character-searches($admin-config, $database, $value) - else - $admin-config + let $admin-config := setup:add-path-namespaces($admin-config, $database, $db-config) + let $admin-config := setup:add-range-path-indexes($admin-config, $database, $db-config) - let $value := setup:get-setting-from-database-config-as-boolean($database-config, "uri-lexicon") - let $admin-config := - if (fn:exists($value)) then - admin:database-set-uri-lexicon($admin-config, $database, $value) - else - $admin-config + (: remove any existing geospatial element attribute pair indexes :) + let $remove-existing-indexes := + for $index in admin:database-get-geospatial-element-indexes($admin-config, $database) + return + xdmp:set( + $admin-config, + admin:database-delete-geospatial-element-index($admin-config, $database, $index)) - let $value := setup:get-setting-from-database-config-as-boolean($database-config, "collection-lexicon") - let $admin-config := - if (fn:exists($value)) then - admin:database-set-collection-lexicon($admin-config, $database, $value) - else - $admin-config + let $admin-config := setup:add-geospatial-element-indexes($admin-config, $database, $db-config) - let $value := setup:get-setting-from-database-config-as-boolean($database-config, "reindexer-enable") - let $admin-config := - if (fn:exists($value)) then - admin:database-set-reindexer-enable($admin-config, $database, $value) - else - $admin-config + (: remove any existing geospatial element attribute pair indexes :) + let $remove-existing-indexes := + for $index in admin:database-get-geospatial-element-attribute-pair-indexes($admin-config, $database) + return + xdmp:set( + $admin-config, + admin:database-delete-geospatial-element-attribute-pair-index($admin-config, $database, $index)) - let $value := setup:get-setting-from-database-config-as-string($database-config, "reindexer-throttle") - let $admin-config := - if ($value) then - admin:database-set-reindexer-throttle($admin-config, $database, $value) - else - $admin-config + let $admin-config := setup:add-geospatial-element-attribute-pair-indexes($admin-config, $database, $db-config) - let $value := setup:get-setting-from-database-config-as-string($database-config, "reindexer-timestamp") - let $admin-config := - if ($value) then - admin:database-set-reindexer-timestamp($admin-config, $database, $value) - else - $admin-config + (: remove any existing geospatial element pair indexes :) + let $remove-existing-indexes := + for $index in admin:database-get-geospatial-element-pair-indexes($admin-config, $database) + return + xdmp:set( + $admin-config, + admin:database-delete-geospatial-element-pair-index($admin-config, $database, $index)) - let $value := setup:get-setting-from-database-config-as-string($database-config, "directory-creation") - let $admin-config := - if ($value) then - admin:database-set-directory-creation($admin-config, $database, $value) - else - $admin-config + let $admin-config := setup:add-geospatial-element-pair-indexes($admin-config, $database, $db-config) - let $value := setup:get-setting-from-database-config-as-boolean($database-config, "maintain-last-modified") - let $admin-config := - if (fn:exists($value)) then - admin:database-set-maintain-last-modified($admin-config, $database, $value) - else - $admin-config + (: remove any existing geospatial element pair indexes :) + let $remove-existing-indexes := + for $index in admin:database-get-geospatial-element-child-indexes($admin-config, $database) + return + xdmp:set( + $admin-config, + admin:database-delete-geospatial-element-child-index($admin-config, $database, $index)) - let $value := setup:get-setting-from-database-config-as-boolean($database-config, "maintain-directory-last-modified") - let $admin-config := - if (fn:exists($value)) then - admin:database-set-maintain-directory-last-modified($admin-config, $database, $value) - else - $admin-config + let $admin-config := setup:add-geospatial-element-child-indexes($admin-config, $database, $db-config) - let $value := setup:get-setting-from-database-config-as-boolean($database-config, "inherit-permissions") - let $admin-config := - if (fn:exists($value)) then - admin:database-set-inherit-permissions($admin-config, $database, $value) - else - $admin-config + (: remove any existing field (copied from default.xqy) :) + let $remove-existing-fields := + for $field as xs:string in admin:database-get-fields($admin-config, $database)/db:field-name[fn:not(. = "")] + return + xdmp:set( + $admin-config, + admin:database-delete-field($admin-config, $database, $field)) - let $value := setup:get-setting-from-database-config-as-boolean($database-config, "inherit-collections") - let $admin-config := - if (fn:exists($value)) then - admin:database-set-inherit-collections($admin-config, $database, $value) - else - $admin-config + let $admin-config := setup:add-fields($admin-config, $database, $db-config) + let $admin-config := setup:add-field-includes($admin-config, $database, $db-config) + let $admin-config := setup:add-field-excludes($admin-config, $database, $db-config) - let $value := setup:get-setting-from-database-config-as-boolean($database-config, "inherit-quality") - let $admin-config := - if (fn:exists($value)) then - admin:database-set-inherit-quality($admin-config, $database, $value) - else - $admin-config - let $value := setup:get-setting-from-database-config-as-string($database-config, "format-compatibility") - let $admin-config := - if ($value) then - admin:database-set-format-compatibility($admin-config, $database, $value) - else - $admin-config + let $remove-existing-indexes := + (: wrap in try catch because this function is new to 5.0 and will fail in older version of ML :) + try + { + xdmp:set( + $admin-config, + xdmp:eval(' + import module namespace admin = "http://marklogic.com/xdmp/admin" at "/MarkLogic/admin.xqy"; + declare variable $admin-config external; + declare variable $database external; - let $value := setup:get-setting-from-database-config-as-string($database-config, "index-detection") - let $admin-config := - if ($value) then - admin:database-set-index-detection($admin-config, $database, $value) + let $remove-existing-indexes := + for $index in admin:database-get-range-field-indexes($admin-config, $database) + return + xdmp:set( + $admin-config, + admin:database-delete-range-field-index($admin-config, $database, $index)) + return + $admin-config', + (xs:QName("admin-config"), $admin-config, + xs:QName("database"), $database))) + } + catch($ex) + { + if ($ex/error:code = "XDMP-UNDFUN") then () else - $admin-config + xdmp:rethrow() + } - let $value := setup:get-setting-from-database-config-as-string($database-config, "expunge-locks") - let $admin-config := - if ($value) then - admin:database-set-expunge-locks($admin-config, $database, $value) - else - $admin-config + let $admin-config := setup:add-range-field-indexes($admin-config, $database, $db-config) - let $value := setup:get-setting-from-database-config-as-string($database-config, "tf-normalization") - let $admin-config := - if ($value) then - admin:database-set-tf-normalization($admin-config, $database, $value) - else - $admin-config + let $remove-existing-element-word-lexicons := + for $lexicon in admin:database-get-element-word-lexicons($admin-config, $database) + return + xdmp:set( + $admin-config, + admin:database-delete-element-word-lexicon($admin-config, $database, $lexicon)) + let $admin-config := setup:add-element-word-lexicons($admin-config, $database, $db-config) - let $restart-hosts := - admin:save-configuration-without-restart($admin-config) + let $remove-existing-element-attribute-word-lexicons := + for $lexicon in admin:database-get-element-attribute-word-lexicons($admin-config, $database) return - fn:concat("Database ", $database-name, " settings applied succesfully..", if ($restart-hosts) then " (note: restart required)" else ()) + xdmp:set( + $admin-config, + admin:database-delete-element-attribute-word-lexicon($admin-config, $database, $lexicon)) + let $admin-config := setup:add-element-attribute-word-lexicons($admin-config, $database, $db-config) - } catch ($e) { - fn:concat("Applying settings to database ", $database-name, " failed: ", $e//err:format-string) - } + return + ( + if (admin:save-configuration-without-restart($admin-config)) then + xdmp:set($restart-needed, fn:true()) + else (), + fn:concat("Database ", $database-name, " configured succesfully.") + ) }; -(:::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - :: Configuration of databases - ::) - -declare function setup:configure-databases($import-config as element(configuration)) as item()* +(: TODO: YOU ARE HERE :) +declare function setup:validate-databases-indexes($import-config as element(configuration)) { - try { - for $database-config in - setup:get-databases-from-config($import-config) - - return - setup:configure-database($database-config) + for $db-config in setup:get-databases-from-config($import-config) + let $database-name := setup:get-database-name-from-database-config($db-config) + let $database := xdmp:database($database-name) + let $admin-config := admin:get-configuration() + return + ( + setup:validate-word-lexicons($admin-config, $database, $db-config), + setup:validate-fragment-roots($admin-config, $database, $db-config), + setup:validate-fragment-parents($admin-config, $database, $db-config), + setup:validate-word-query($admin-config, $database, $db-config), + setup:validate-schema-database($admin-config, $db-config, $database), + setup:validate-security-database($admin-config, $db-config, $database), + setup:validate-triggers-database($admin-config, $db-config, $database), + setup:validate-range-element-indexes($admin-config, $database, $db-config), + setup:validate-range-element-attribute-indexes($admin-config, $database, $db-config), + setup:validate-path-namespaces($admin-config, $database, $db-config), + setup:validate-range-path-indexes($admin-config, $database, $db-config), + setup:validate-geospatial-element-indexes($admin-config, $database, $db-config), + setup:validate-geospatial-element-attribute-pair-indexes($admin-config, $database, $db-config), + setup:validate-geospatial-element-pair-indexes($admin-config, $database, $db-config), + setup:validate-geospatial-element-child-indexes($admin-config, $database, $db-config)(:, + setup:validate-fields($admin-config, $database, $db-config), + setup:validate-field-includes($admin-config, $database, $db-config), + setup:validate-field-excludes($admin-config, $database, $db-config), + setup:validate-range-field-indexes($admin-config, $database, $db-config), + setup:validate-element-word-lexicons($admin-config, $database, $db-config), + setup:validate-element-attribute-word-lexicons($admin-config, $database, $db-config):) + ) - } catch ($e) { - fn:concat("Configuring databases failed: ", $e//err:format-string) - } }; -declare function setup:configure-database($database-config as element(db:database)) as item()* +declare function setup:add-fields( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) as element(configuration) { - let $database-name := - setup:get-database-name-from-database-config($database-config) - - return + setup:add-fields-R( + $admin-config, + $database, + for $e in $db-config/db:fields/db:field[db:field-name != ""] + return + admin:database-field($e/db:field-name, $e/db:include-root)) +}; - try { - let $database := - xdmp:database($database-name) +declare function setup:add-fields-R( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $field-configs as element(db:field)*) as element(configuration) +{ + if ($field-configs) then + setup:add-fields-R( + admin:database-add-field($admin-config, $database, $field-configs[1]), + $database, + fn:subsequence($field-configs, 2)) + else + $admin-config +}; - let $restart-hosts := - try - { - xdmp:eval(' - import module namespace admin = "http://marklogic.com/xdmp/admin" at "/MarkLogic/admin.xqy"; - let $admin-config := admin:get-configuration() - let $remove-existing-indexes := - for $index in admin:database-get-range-path-indexes($admin-config, $database) - return - xdmp:set($admin-config, admin:database-delete-range-path-index($admin-config, $database, $index)) - return - admin:save-configuration-without-restart($admin-config)') - } - catch($ex) - { - fn:false() - } +declare function setup:add-field-includes( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) as element(configuration) +{ + setup:add-field-includes-R( + $admin-config, + $database, + $db-config/db:fields/db:field[db:field-name != ""]) +}; - (: remove any existing path namespaces :) - let $restart-hosts := - try - { - xdmp:eval(' - let $admin-config := admin:get-configuration() - let $remove-existing-indexes := - for $index in admin:database-get-path-namespaces($admin-config, $database) - return - xdmp:set($admin-config, admin:database-delete-path-namespace($admin-config, $database, $index)) +declare function setup:add-field-includes-R( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $field-configs as element(db:field)*) as element(configuration) +{ + if ($field-configs) then + setup:add-field-includes-R( + admin:database-add-field-included-element( + $admin-config, + $database, + $field-configs[1]/db:field-name, + for $e in $field-configs[1]/db:included-elements/db:included-element return - (admin:save-configuration-without-restart($admin-config) or $restart-hosts)') - } - catch($ex) - { - fn:false() - } - - let $admin-config := - admin:get-configuration() - - let $admin-config := - setup:add-word-lexicons($admin-config, $database, $database-config) - - let $admin-config := - setup:add-fragment-roots($admin-config, $database, $database-config) - (: - - - - - - - - - - - - + admin:database-included-element( + $e/db:namespace-uri, + $e/db:localname, + $e/db:weight, + $e/db:attribute-namespace-uri, + $e/db:attribute-localname, + $e/db:attribute-value)), + $database, + fn:subsequence($field-configs, 2)) + else + $admin-config +}; - :) +declare function setup:add-field-excludes( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) as element(configuration) +{ + setup:add-field-excludes-R( + $admin-config, + $database, + $db-config/db:fields/db:field[db:field-name != ""]) +}; - let $admin-config := setup:set-schema-database($admin-config, $database-config, $database) - let $admin-config := setup:set-security-database($admin-config, $database-config, $database) - let $admin-config := setup:set-triggers-database($admin-config, $database-config, $database) +declare function setup:add-field-excludes-R( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $field-configs as element(db:field)*) as element(configuration) +{ + if ($field-configs) then + setup:add-field-excludes-R( + admin:database-add-field-excluded-element( + $admin-config, + $database, + $field-configs[1]/db:field-name, + for $e in $field-configs[1]/db:excluded-elements/db:excluded-element + return + if (fn:starts-with(xdmp:version(), "4")) then + admin:database-excluded-element( + $e/db:namespace-uri, + $e/db:localname) + else + xdmp:eval( + 'import module namespace admin = "http://marklogic.com/xdmp/admin" at "/MarkLogic/admin.xqy"; + declare variable $e external; + + admin:database-excluded-element( + $e/db:namespace-uri, + $e/db:localname, + $e/db:attribute-namespace-uri, + $e/db:attribute-localname, + $e/db:attribute-value)', + (xs:QName("e"), $e), + + same-statement + )), + $database, + fn:subsequence($field-configs, 2)) + else + $admin-config +}; - (: remove any existing range index (copied from default.xqy) :) - let $remove-existing-indexes := - for $index in admin:database-get-range-element-indexes($admin-config, $database) - return - xdmp:set($admin-config, admin:database-delete-range-element-index($admin-config, $database, $index)) +declare function setup:add-range-element-indexes( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) as element(configuration) +{ + setup:add-range-element-indexes-R( + $admin-config, + $database, + $db-config/db:range-element-indexes/db:range-element-index) +}; - let $admin-config := setup:add-range-element-indexes($admin-config, $database, $database-config) +declare function setup:add-range-element-indexes-R( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $index-configs as element(db:range-element-index)*) as element(configuration) +{ + if ($index-configs) then + setup:add-range-element-indexes-R( + admin:database-add-range-element-index($admin-config, $database, $index-configs[1]), + $database, + fn:subsequence($index-configs, 2)) + else + $admin-config +}; - (: remove any existing range element attribute index :) - let $remove-existing-indexes := - for $index in admin:database-get-range-element-attribute-indexes($admin-config, $database) - return - xdmp:set($admin-config, admin:database-delete-range-element-attribute-index($admin-config, $database, $index)) +declare function setup:validate-range-element-indexes( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) +{ + let $existing := admin:database-get-range-element-indexes($admin-config, $database) + for $expected in $db-config/db:range-element-indexes/db:range-element-index + return + if ($existing[fn:deep-equal(., $expected)]) then () + else + setup:validation-fail(fn:concat("Missing range element index: ", $expected/db:localname)) +}; - let $admin-config := setup:add-range-element-attribute-indexes($admin-config, $database, $database-config) +declare function setup:add-range-element-attribute-indexes( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) as element(configuration) +{ + setup:add-range-element-attribute-indexes-R( + $admin-config, + $database, + $db-config/db:range-element-attribute-indexes/db:range-element-attribute-index) +}; - let $admin-config := setup:add-path-namespaces($admin-config, $database, $database-config) - let $admin-config := setup:add-range-path-indexes($admin-config, $database, $database-config) +declare function setup:add-range-element-attribute-indexes-R( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $index-configs as element(db:range-element-attribute-index)*) as element(configuration) +{ + if ($index-configs) then + setup:add-range-element-attribute-indexes-R( + admin:database-add-range-element-attribute-index($admin-config, $database, $index-configs[1]), + $database, + fn:subsequence($index-configs, 2)) + else + $admin-config +}; - (: remove any existing geospatial element attribute pair indexes :) - let $remove-existing-indexes := - for $index in admin:database-get-geospatial-element-attribute-pair-indexes($admin-config, $database) - return - xdmp:set($admin-config, admin:database-delete-geospatial-element-attribute-pair-index($admin-config, $database, $index)) +declare function setup:validate-range-element-attribute-indexes( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) +{ + let $existing := admin:database-get-range-element-attribute-indexes($admin-config, $database) + for $expected in $db-config/db:range-element-attribute-indexes/db:range-element-attribute-index + return + if ($existing[fn:deep-equal(., $expected)]) then () + else + setup:validation-fail(fn:concat("Missing range element attribute index: ", $expected/db:localname)) +}; - let $admin-config := setup:add-geospatial-element-attribute-pair-indexes($admin-config, $database, $database-config) +declare function setup:add-path-namespaces( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) as element(configuration) +{ + setup:add-path-namespaces-R( + $admin-config, + $database, + $db-config/db:path-namespaces/db:path-namespace) +}; - (: remove any existing geospatial element pair indexes :) - let $remove-existing-indexes := - for $index in admin:database-get-geospatial-element-pair-indexes($admin-config, $database) - return - xdmp:set($admin-config, admin:database-delete-geospatial-element-pair-index($admin-config, $database, $index)) +declare function setup:add-path-namespaces-R( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $index-configs as element(db:path-namespace)*) as element(configuration) +{ + if ($index-configs) then + setup:add-path-namespaces-R( + xdmp:eval(' + import module namespace admin = "http://marklogic.com/xdmp/admin" at "/MarkLogic/admin.xqy"; + declare variable $admin-config external; + declare variable $database external; + declare variable $index-config external; + admin:database-add-path-namespace($admin-config, $database, $index-config)', + ( + xs:QName("admin-config"), $admin-config, + xs:QName("database"), $database, + xs:QName("index-config"), $index-configs[1] + )), + $database, + fn:subsequence($index-configs, 2)) + else + $admin-config +}; - let $admin-config := setup:add-geospatial-element-pair-indexes($admin-config, $database, $database-config) +declare function setup:validate-path-namespaces( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) +{ + let $existing := + try + { + xdmp:eval(' + import module namespace admin = "http://marklogic.com/xdmp/admin" at "/MarkLogic/admin.xqy"; + declare variable $admin-config external; + declare variable $database external; + admin:database-get-path-namespaces($admin-config, $database)', + ( + xs:QName("admin-config"), $admin-config, + xs:QName("database"), $database + )) + } + catch($ex) + { + if ($ex/error:code = "XDMP-UNDFUN") then () + else + xdmp:rethrow() + } + for $expected in $db-config/db:path-namespaces/db:path-namespace + return + if ($existing[fn:deep-equal(., $expected)]) then () + else + setup:validation-fail(fn:concat("Missing path namespace: ", $expected/db:prefix, " => ", $expected/db:namespace-uri)) +}; - (: remove any existing geospatial element pair indexes :) - let $remove-existing-indexes := - for $index in admin:database-get-geospatial-element-child-indexes($admin-config, $database) - return - xdmp:set($admin-config, admin:database-delete-geospatial-element-child-index($admin-config, $database, $index)) +declare function setup:add-range-path-indexes( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) as element(configuration) +{ + setup:add-range-path-indexes-R( + $admin-config, + $database, + $db-config/db:range-path-indexes/db:range-path-index) +}; - let $admin-config := setup:add-geospatial-element-child-indexes($admin-config, $database, $database-config) +declare function setup:add-range-path-indexes-R( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $index-configs as element(db:range-path-index)*) as element(configuration) +{ + if ($index-configs) then + setup:add-range-path-indexes-R( + xdmp:eval(' + import module namespace admin = "http://marklogic.com/xdmp/admin" at "/MarkLogic/admin.xqy"; + declare variable $admin-config external; + declare variable $database external; + declare variable $index-config external; + admin:database-add-range-path-index($admin-config, $database, $index-config)', + ( + xs:QName("admin-config"), $admin-config, + xs:QName("database"), $database, + xs:QName("index-config"), $index-configs[1] + )), + $database, + fn:subsequence($index-configs, 2)) + else + $admin-config +}; - (: remove any existing field (copied from default.xqy) :) - let $remove-existing-fields := - for $field as xs:string in admin:database-get-fields($admin-config, $database)/db:field-name[fn:not(. = "")] - return - xdmp:set($admin-config, admin:database-delete-field($admin-config, $database, $field)) +declare function setup:validate-range-path-indexes( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) +{ + let $existing := + try + { + xdmp:eval(' + import module namespace admin = "http://marklogic.com/xdmp/admin" at "/MarkLogic/admin.xqy"; + declare variable $admin-config external; + declare variable $database external; + admin:database-get-range-path-indexes($admin-config, $database)', + ( + xs:QName("admin-config"), $admin-config, + xs:QName("database"), $database + )) + } + catch($ex) + { + if ($ex/error:code = "XDMP-UNDFUN") then () + else + xdmp:rethrow() + } + for $expected in $db-config/db:range-path-indexes/db:range-path-index + return + if ($existing[fn:deep-equal(., $expected)]) then () + else + setup:validation-fail(fn:concat("Missing range path index: ", $expected/db:path-expression)) +}; - let $admin-config := setup:add-fields($admin-config, $database, $database-config) +declare function setup:add-element-word-lexicons( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) as element(configuration) +{ + setup:add-element-word-lexicons-R( + $admin-config, + $database, + $db-config/db:element-word-lexicons/db:element-word-lexicon) +}; - (: remove any existing range field indexes :) - let $remove-existing-indexes := - for $index in admin:database-get-range-field-indexes($admin-config, $database) - return - xdmp:set($admin-config, admin:database-delete-range-field-index($admin-config, $database, $index)) +declare function setup:add-element-word-lexicons-R( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $index-configs as element(db:element-word-lexicon)*) as element(configuration) +{ + if ($index-configs) then + setup:add-element-word-lexicons-R( + admin:database-add-element-word-lexicon($admin-config, $database, $index-configs[1]), + $database, + fn:subsequence($index-configs, 2)) + else + $admin-config +}; - let $admin-config := setup:add-range-field-indexes($admin-config, $database, $database-config) +declare function setup:add-element-attribute-word-lexicons( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) as element(configuration) +{ + setup:add-element-attribute-word-lexicons-R( + $admin-config, + $database, + $db-config/db:element-attribute-word-lexicons/db:element-attribute-word-lexicon) +}; - let $restart-hosts := - (admin:save-configuration-without-restart($admin-config) or $restart-hosts) +declare function setup:add-element-attribute-word-lexicons-R( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $index-configs as element(db:element-attribute-word-lexicon)*) as element(configuration) +{ + if ($index-configs) then + setup:add-element-attribute-word-lexicons-R( + admin:database-add-element-attribute-word-lexicon($admin-config, $database, $index-configs[1]), + $database, + fn:subsequence($index-configs, 2)) + else + $admin-config +}; - return - fn:concat("Database ", $database-name, " configured succesfully..", if ($restart-hosts) then " (note: restart required)" else ()) - } catch ($e) { - fn:concat("Database ", $database-name, " configuration failed: ", $e//err:format-string) - } +declare function setup:add-range-field-indexes( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) as element(configuration) +{ + setup:add-range-field-indexes-R( + $admin-config, + $database, + $db-config/db:range-field-indexes/db:range-field-index) }; -declare function setup:add-fields($admin-config as element(configuration), $database as xs:unsignedLong, $database-config as element(db:database)) as element(configuration) +declare function setup:add-range-field-indexes-R( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $index-configs as element(db:range-field-index)*) as element(configuration) { - let $field-configs := $database-config/db:fields/db:field - return setup:add-fields-R($admin-config, $database, $field-configs) + if ($index-configs) then + setup:add-range-field-indexes-R( + xdmp:eval(' + import module namespace admin = "http://marklogic.com/xdmp/admin" at "/MarkLogic/admin.xqy"; + declare variable $admin-config external; + declare variable $database external; + declare variable $index-config external; + admin:database-add-range-field-index($admin-config, $database, $index-config)', + ( + xs:QName("admin-config"), $admin-config, + xs:QName("database"), $database, + xs:QName("index-config"), $index-configs[1] + )), + $database, + fn:subsequence($index-configs, 2)) + else + $admin-config }; -declare function setup:add-fields-R($admin-config as element(configuration), $database as xs:unsignedLong, $field-configs as element(db:field)*) as element(configuration) +declare function setup:add-geospatial-element-indexes( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) as element(configuration) { - if (fn:empty($field-configs)) then - $admin-config + setup:add-geospatial-element-indexes-R( + $admin-config, + $database, + $db-config/db:geospatial-element-indexes/db:geospatial-element-index) +}; + +declare function setup:add-geospatial-element-indexes-R( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $index-configs as element(db:geospatial-element-index)*) as element(configuration) +{ + if ($index-configs) then + setup:add-geospatial-element-indexes-R( + admin:database-add-geospatial-element-index($admin-config, $database, $index-configs[1]), + $database, + fn:subsequence($index-configs, 2)) else - let $field-config := $field-configs[1] - let $admin-config := - try { admin:database-add-field($admin-config, $database, $field-config) } - catch ($e) { $admin-config } - return setup:add-fields-R($admin-config, $database, fn:subsequence($field-configs, 2)) + $admin-config }; +declare function setup:validate-geospatial-element-indexes( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) +{ + let $existing := admin:database-get-geospatial-element-indexes($admin-config, $database) + for $expected in $db-config/db:geospatial-element-indexes/db:geospatial-element-index + return + if ($existing[fn:deep-equal(., $expected)]) then () + else + setup:validation-fail(fn:concat("Missing geospatial element index: ", $expected/db:localname)) +}; -declare function setup:add-range-element-indexes($admin-config as element(configuration), $database as xs:unsignedLong, $database-config as element(db:database)) as element(configuration) +declare function setup:add-geospatial-element-attribute-pair-indexes( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) as element(configuration) { - let $index-configs := $database-config/db:range-element-indexes/db:range-element-index - return setup:add-range-element-indexes-R($admin-config, $database, $index-configs) + setup:add-geospatial-element-attribute-pair-indexes-R( + $admin-config, + $database, + $db-config/db:geospatial-element-attribute-pair-indexes/db:geospatial-element-attribute-pair-index) }; -declare function setup:add-range-element-indexes-R($admin-config as element(configuration), $database as xs:unsignedLong, $index-configs as element(db:range-element-index)*) as element(configuration) +declare function setup:add-geospatial-element-attribute-pair-indexes-R( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $index-configs as element(db:geospatial-element-attribute-pair-index)*) as element(configuration) { - if (fn:empty($index-configs)) then - $admin-config + if ($index-configs) then + setup:add-geospatial-element-attribute-pair-indexes-R( + admin:database-add-geospatial-element-attribute-pair-index($admin-config, $database, $index-configs[1]), + $database, + fn:subsequence($index-configs, 2)) else - let $index-config := $index-configs[1] - let $admin-config := - try { admin:database-add-range-element-index($admin-config, $database, $index-config) } - catch ($e) { $admin-config } - return setup:add-range-element-indexes-R($admin-config, $database, fn:subsequence($index-configs, 2)) + $admin-config }; -declare function setup:add-range-element-attribute-indexes($admin-config as element(configuration), $database as xs:unsignedLong, $database-config as element(db:database)) as element(configuration) +declare function setup:validate-geospatial-element-attribute-pair-indexes( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) { - let $index-configs := $database-config/db:range-element-attribute-indexes/db:range-element-attribute-index - return setup:add-range-element-attribute-indexes-R($admin-config, $database, $index-configs) + let $existing := admin:database-get-geospatial-element-attribute-pair-indexes($admin-config, $database) + for $expected in $db-config/db:geospatial-element-attribute-pair-indexes/db:geospatial-element-attribute-pair-index + return + if ($existing[fn:deep-equal(., $expected)]) then () + else + setup:validation-fail(fn:concat("Missing geospatial element attribute pair index: ", $expected/db:localname)) }; -declare function setup:add-range-element-attribute-indexes-R($admin-config as element(configuration), $database as xs:unsignedLong, $index-configs as element(db:range-element-attribute-index)*) as element(configuration) +declare function setup:add-geospatial-element-pair-indexes( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) as element(configuration) { - if (fn:empty($index-configs)) then - $admin-config + setup:add-geospatial-element-pair-indexes-R( + $admin-config, + $database, + $db-config/db:geospatial-element-pair-indexes/db:geospatial-element-pair-index) +}; + +declare function setup:add-geospatial-element-pair-indexes-R( + $admin-config as element(configuration), $database as xs:unsignedLong, + $index-configs as element(db:geospatial-element-pair-index)*) as element(configuration) +{ + if ($index-configs) then + setup:add-geospatial-element-pair-indexes-R( + admin:database-add-geospatial-element-pair-index($admin-config, $database, $index-configs[1]), + $database, + fn:subsequence($index-configs, 2)) else - let $index-config := $index-configs[1] - let $admin-config := - try { - admin:database-add-range-element-attribute-index($admin-config, $database, $index-config) - } catch ($e) { - xdmp:log(fn:concat("Failed to create element attribute index ", xdmp:quote($index-config), "; ", $e/error:code), "error"), - $admin-config - } - return setup:add-range-element-attribute-indexes-R($admin-config, $database, fn:subsequence($index-configs, 2)) + $admin-config }; -declare function setup:add-path-namespaces($admin-config as element(configuration), $database as xs:unsignedLong, $database-config as element(db:database)) as element(configuration) +declare function setup:validate-geospatial-element-pair-indexes( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) { - let $index-configs := $database-config/db:path-namespaces/db:path-namespace - return setup:add-path-namespaces-R($admin-config, $database, $index-configs) + let $existing := admin:database-get-geospatial-element-pair-indexes($admin-config, $database) + for $expected in $db-config/db:geospatial-element-pair-indexes/db:geospatial-element-pair-index + return + if ($existing[fn:deep-equal(., $expected)]) then () + else + setup:validation-fail(fn:concat("Missing geospatial element pair index: ", $expected/db:localname)) }; -declare function setup:add-path-namespaces-R($admin-config as element(configuration), $database as xs:unsignedLong, $index-configs as element(db:path-namespace)*) as element(configuration) +declare function setup:add-geospatial-element-child-indexes( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) as element(configuration) { - if (fn:empty($index-configs)) then - $admin-config - else - let $index-config := $index-configs[1] - let $admin-config := - try { - xdmp:eval(' - import module namespace admin = "http://marklogic.com/xdmp/admin" at "/MarkLogic/admin.xqy"; - declare variable $admin-config external; - declare variable $database external; - declare variable $index-config external; - admin:database-add-path-namespace($admin-config, $database, $index-config)', - ( - xs:QName("admin-config"), $admin-config, - xs:QName("database"), $database, - xs:QName("index-config"), $index-config - )) - } catch ($e) { - xdmp:log(fn:concat("Failed to create path namespace ", xdmp:quote($index-config), "; ", $e/error:code), "error"), - $admin-config - } - return setup:add-path-namespaces-R($admin-config, $database, fn:subsequence($index-configs, 2)) + setup:add-geospatial-element-child-indexes-R( + $admin-config, + $database, + $db-config/db:geospatial-element-child-indexes/db:geospatial-element-child-index) }; -declare function setup:add-range-path-indexes($admin-config as element(configuration), $database as xs:unsignedLong, $database-config as element(db:database)) as element(configuration) +declare function setup:add-geospatial-element-child-indexes-R( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $index-configs as element(db:geospatial-element-child-index)*) as element(configuration) { - let $index-configs := $database-config/db:range-path-indexes/db:range-path-index - return setup:add-range-path-indexes-R($admin-config, $database, $index-configs) + if ($index-configs) then + setup:add-geospatial-element-child-indexes-R( + admin:database-add-geospatial-element-child-index($admin-config, $database, $index-configs[1]), + $database, + fn:subsequence($index-configs, 2)) + else + $admin-config }; -declare function setup:add-range-path-indexes-R($admin-config as element(configuration), $database as xs:unsignedLong, $index-configs as element(db:range-path-index)*) as element(configuration) +declare function setup:validate-geospatial-element-child-indexes( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) { - if (fn:empty($index-configs)) then - $admin-config - else - let $index-config := $index-configs[1] - let $admin-config := - try { - xdmp:eval(' - import module namespace admin = "http://marklogic.com/xdmp/admin" at "/MarkLogic/admin.xqy"; - declare variable $admin-config external; - declare variable $database external; - declare variable $index-config external; - admin:database-add-range-path-index($admin-config, $database, $index-config)', - ( - xs:QName("admin-config"), $admin-config, - xs:QName("database"), $database, - xs:QName("index-config"), $index-config - )) - } catch ($e) { - xdmp:log(fn:concat("Failed to create path index ", xdmp:quote($index-config), "; ", $e/error:code), "error"), - $admin-config - } - return setup:add-range-path-indexes-R($admin-config, $database, fn:subsequence($index-configs, 2)) + let $existing := admin:database-get-geospatial-element-child-indexes($admin-config, $database) + for $expected in $db-config/db:geospatial-element-child-indexes/db:geospatial-element-child-index + return + if ($existing[fn:deep-equal(., $expected)]) then () + else + setup:validation-fail(fn:concat("Missing geospatial element child index: ", $expected/db:localname)) }; -declare function setup:add-range-field-indexes($admin-config as element(configuration), $database as xs:unsignedLong, $database-config as element(db:database)) as element(configuration) +declare function setup:add-word-lexicons( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) as element(configuration) { - let $index-configs := $database-config/db:range-field-indexes/db:range-field-index - return setup:add-range-field-indexes-R($admin-config, $database, $index-configs) + setup:add-word-lexicons-R( + $admin-config, + $database, + $db-config/db:word-lexicons/db:word-lexicon) }; -declare function setup:add-range-field-indexes-R( +declare function setup:add-word-lexicons-R( $admin-config as element(configuration), $database as xs:unsignedLong, - $index-configs as element(db:range-field-index)*) as element(configuration) + $collations as xs:string*) as element(configuration) { - if (fn:empty($index-configs)) then - $admin-config + if ($collations) then + setup:add-word-lexicons-R( + setup:safe-database-add-word-lexicon($admin-config, $database, $collations[1]), + $database, + fn:subsequence($collations, 2)) else - let $index-config := $index-configs[1] - let $admin-config := - try { - admin:database-add-range-field-index($admin-config, $database, $index-config) - } catch ($e) { - xdmp:log(fn:concat("Error while building range-field-index: ", xdmp:quote($e))), - $admin-config - } - return setup:add-range-field-indexes-R($admin-config, $database, fn:subsequence($index-configs, 2)) + $admin-config }; -declare function setup:add-geospatial-element-attribute-pair-indexes($admin-config as element(configuration), $database as xs:unsignedLong, $database-config as element(db:database)) as element(configuration) +declare function setup:validate-word-lexicons( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) { - let $index-configs := $database-config/db:geospatial-element-attribute-pair-indexes/db:geospatial-element-attribute-pair-index - return setup:add-geospatial-element-attribute-pair-indexes-R($admin-config, $database, $index-configs) + let $existing := admin:database-get-word-lexicons($admin-config, $database) + for $expected in $db-config/db:word-lexicons/db:word-lexicon + return + if ($existing[$expected]) then () + else + setup:validation-fail(fn:concat("Database missing word lexicon: ", $expected)) }; -declare function setup:add-geospatial-element-attribute-pair-indexes-R($admin-config as element(configuration), $database as xs:unsignedLong, $index-configs as element(db:geospatial-element-attribute-pair-index)*) as element(configuration) + +declare function setup:safe-database-add-word-lexicon( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $collation as xs:string) as element(configuration) { - if (fn:empty($index-configs)) then - $admin-config - else - let $index-config := $index-configs[1] - let $admin-config := - try { admin:database-add-geospatial-element-attribute-pair-index($admin-config, $database, $index-config) } - catch ($e) { $admin-config } - return setup:add-geospatial-element-attribute-pair-indexes-R($admin-config, $database, fn:subsequence($index-configs, 2)) + admin:database-add-word-lexicon( + $admin-config, + $database, + admin:database-word-lexicon($collation)) }; -declare function setup:add-geospatial-element-pair-indexes($admin-config as element(configuration), $database as xs:unsignedLong, $database-config as element(db:database)) as element(configuration) +declare function setup:add-fragment-roots( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) as element(configuration) { - let $index-configs := $database-config/db:geospatial-element-pair-indexes/db:geospatial-element-pair-index - return setup:add-geospatial-element-pair-indexes-R($admin-config, $database, $index-configs) + (: remove any existing fragment roots first :) + for $root in admin:database-get-fragment-roots($admin-config, $database) + return + xdmp:set($admin-config, admin:database-delete-fragment-root($admin-config, $database, $root)), + + setup:add-fragment-roots-R( + $admin-config, + $database, + $db-config/db:fragment-roots/db:fragment-root) }; -declare function setup:add-geospatial-element-pair-indexes-R($admin-config as element(configuration), $database as xs:unsignedLong, $index-configs as element(db:geospatial-element-pair-index)*) as element(configuration) +declare function setup:add-fragment-roots-R( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $fragment-roots as element(db:fragment-root)*) as element(configuration) { - if (fn:empty($index-configs)) then - $admin-config + if ($fragment-roots) then + setup:add-fragment-roots-R( + admin:database-add-fragment-root( + $admin-config, + $database, + admin:database-fragment-root( + $fragment-roots[1]/db:namespace-uri, + $fragment-roots[1]/db:localname)), + $database, + fn:subsequence($fragment-roots, 2)) else - let $index-config := $index-configs[1] - let $admin-config := - try { admin:database-add-geospatial-element-pair-index($admin-config, $database, $index-config) } - catch ($e) { $admin-config } - return setup:add-geospatial-element-pair-indexes-R($admin-config, $database, fn:subsequence($index-configs, 2)) + $admin-config +}; + +declare function setup:validate-fragment-roots( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) +{ + let $existing := admin:database-get-fragment-roots($admin-config, $database) + for $expected in $db-config/db:fragment-roots/db:fragment-root + return + if ($existing[db:namespace-uri = $expected/db:namespace-uri and db:localname = $expected/db:localname]) then () + else + setup:validation-fail(fn:concat("Missing fragment root: ", $expected/db:localname)) }; -declare function setup:add-geospatial-element-child-indexes($admin-config as element(configuration), $database as xs:unsignedLong, $database-config as element(db:database)) as element(configuration) +declare function setup:add-fragment-parents( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) as element(configuration) { + (: remove any existing fragment parents first :) + for $parent in admin:database-get-fragment-parents($admin-config, $database) + return + xdmp:set($admin-config, admin:database-delete-fragment-parent($admin-config, $database, $parent)), - setup:add-geospatial-element-child-indexes-R( + setup:add-fragment-parents-R( $admin-config, $database, - $database-config/db:geospatial-element-child-indexes/db:geospatial-element-child-index) + $db-config/db:fragment-parents/db:fragment-parent) }; -declare function setup:add-geospatial-element-child-indexes-R($admin-config as element(configuration), $database as xs:unsignedLong, $index-configs as element(db:geospatial-element-child-index)*) as element(configuration) +declare function setup:add-fragment-parents-R( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $fragment-parents as element(db:fragment-parent)*) as element(configuration) { - if (fn:empty($index-configs)) then - $admin-config + if ($fragment-parents) then + setup:add-fragment-parents-R( + admin:database-add-fragment-parent( + $admin-config, + $database, + admin:database-fragment-parent( + $fragment-parents[1]/db:namespace-uri, + $fragment-parents[1]/db:localname)), + $database, + fn:subsequence($fragment-parents, 2)) else - let $admin-config := - try { admin:database-add-geospatial-element-child-index($admin-config, $database, $index-configs[1]) } - catch ($e) { xdmp:log(fn:concat("Error building geo-element-child-index: ", xdmp:quote($e))), $admin-config } - return setup:add-geospatial-element-child-indexes-R($admin-config, $database, fn:subsequence($index-configs, 2)) + $admin-config }; -declare function setup:add-word-lexicons($admin-config as element(configuration), $database as xs:unsignedLong, $database-config as element(db:database)) as element(configuration) +declare function setup:validate-fragment-parents( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) { - let $collations := fn:string($database-config/db:word-lexicons/db:word-lexicon) - return setup:add-word-lexicons-R($admin-config, $database, $collations) + let $existing := admin:database-get-fragment-parents($admin-config, $database) + for $expected in $db-config/db:fragment-parents/db:fragment-parent + return + if ($existing[db:namespace-uri = $expected/db:namespace-uri and db:localname = $expected/db:localname]) then () + else + setup:validation-fail(fn:concat("Missing fragment root: ", $expected/db:localname)) }; -declare function setup:add-word-lexicons-R($admin-config as element(configuration), $database as xs:unsignedLong, $collations as xs:string*) as element(configuration) +declare function setup:config-word-query( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) as element(configuration) { - if (fn:empty($collations)) then + let $empty-field := $db-config/db:fields/db:field[db:field-name = ""] + return + ( + (: remove existing word query included elements first :) + for $element in admin:database-get-word-query-included-elements($admin-config, $database) + return + xdmp:set( + $admin-config, + admin:database-delete-word-query-included-element( + $admin-config, + $database, + $element)), + + (: now add the new ones :) + for $element in $empty-field/db:included-elements/db:included-element + return + xdmp:set( + $admin-config, + admin:database-add-word-query-included-element( + $admin-config, + $database, + $element)), + + (: remove existing word query excluded elements first :) + for $element in admin:database-get-word-query-excluded-elements($admin-config, $database) + return + xdmp:set( + $admin-config, + admin:database-delete-word-query-excluded-element( + $admin-config, + $database, + $element)), + + (: now add the new ones :) + for $element in $empty-field/db:excluded-elements/db:excluded-element + return + xdmp:set( + $admin-config, + admin:database-add-word-query-excluded-element( + $admin-config, + $database, + $element)), + + let $fast-case-sensitive-searches := $empty-field/db:fast-case-sensitive-searches + where $fast-case-sensitive-searches + return + xdmp:set( + $admin-config, + admin:database-set-word-query-fast-case-sensitive-searches( + $admin-config, + $database, + $fast-case-sensitive-searches)), + + let $fast-diacritic-sensitive-searches := $empty-field/db:fast-diacritic-sensitive-searches + where $fast-diacritic-sensitive-searches + return + xdmp:set( + $admin-config, + admin:database-set-word-query-fast-diacritic-sensitive-searches( + $admin-config, + $database, + $fast-diacritic-sensitive-searches)), + + let $fast-phrase-searches := $empty-field/db:fast-phrase-searches + where $fast-phrase-searches + return + xdmp:set( + $admin-config, + admin:database-set-word-query-fast-phrase-searches( + $admin-config, + $database, + $fast-phrase-searches)), + + let $include-root := $empty-field/db:include-root + where $include-root + return + xdmp:set( + $admin-config, + admin:database-set-word-query-include-document-root( + $admin-config, + $database, + $include-root)), + + let $one-character-searches := $empty-field/db:one-character-searches + where $one-character-searches + return + xdmp:set( + $admin-config, + admin:database-set-word-query-one-character-searches( + $admin-config, + $database, + $one-character-searches)), + + let $stemmed-searches := $empty-field/db:stemmed-searches + where $stemmed-searches + return + xdmp:set( + $admin-config, + admin:database-set-word-query-stemmed-searches( + $admin-config, + $database, + $stemmed-searches)), + + let $three-character-searches := $empty-field/db:three-character-searches + where $three-character-searches + return + xdmp:set( + $admin-config, + admin:database-set-word-query-three-character-searches( + $admin-config, + $database, + $three-character-searches)), + + let $three-character-word-positions := $empty-field/db:three-character-word-positions + where $three-character-word-positions + return + xdmp:set( + $admin-config, + admin:database-set-word-query-three-character-word-positions( + $admin-config, + $database, + $three-character-word-positions)), + + let $trailing-wildcard-searches := $empty-field/db:trailing-wildcard-searches + where $trailing-wildcard-searches + return + xdmp:set( + $admin-config, + admin:database-set-word-query-trailing-wildcard-searches( + $admin-config, + $database, + $trailing-wildcard-searches)), + + let $trailing-wildcard-word-positions := $empty-field/db:trailing-wildcard-word-positions + where $trailing-wildcard-word-positions + return + xdmp:set( + $admin-config, + admin:database-set-word-query-trailing-wildcard-word-positions( + $admin-config, + $database, + $trailing-wildcard-word-positions)), + + let $two-character-searches := $empty-field/db:two-character-searches + where $two-character-searches + return + xdmp:set( + $admin-config, + admin:database-set-word-query-two-character-searches( + $admin-config, + $database, + $two-character-searches)), + + let $word-searches := $empty-field/db:word-searches + where $word-searches + return + xdmp:set( + $admin-config, + admin:database-set-word-query-word-searches( + $admin-config, + $database, + $word-searches)), + $admin-config - else - let $admin-config := setup:safe-database-add-word-lexicon($admin-config, $database, $collations[1]) - return setup:add-word-lexicons-R($admin-config, $database, fn:subsequence($collations, 2)) + ) }; - -declare function setup:safe-database-add-word-lexicon($admin-config as element(configuration), $database as xs:unsignedLong, $collation as xs:string) as element(configuration) +declare function setup:validate-word-query( + $admin-config as element(configuration), + $database as xs:unsignedLong, + $db-config as element(db:database)) { - try { - let $lexspec := admin:database-word-lexicon($collation) - return admin:database-add-word-lexicon($admin-config, $database, $lexspec) - } catch ($e) {$admin-config} -}; + let $empty-field := $db-config/db:fields/db:field[db:field-name = ""] + return + ( + let $existing := admin:database-get-word-query-included-elements($admin-config, $database) + for $expected in $empty-field/db:included-elements/db:included-element + return + if ($existing[fn:deep-equal(., $expected)]) then () + else + setup:validation-fail(fn:concat("Missing word query included element: ", $expected/db:localname)), -declare function setup:add-fragment-roots($admin-config as element(configuration), $database as xs:unsignedLong, $database-config as element(db:database)) as element(configuration) -{ - let $fragment-roots := $database-config/db:fragment-roots/db:fragment-root - return setup:add-fragment-roots-R($admin-config, $database, $fragment-roots) + let $existing := admin:database-get-word-query-excluded-elements($admin-config, $database) + for $expected in $empty-field/db:excluded-elements/db:excluded-element + return + if ($existing[fn:deep-equal(., $expected)]) then () + else + setup:validation-fail(fn:concat("Missing word query excluded element: ", $expected/db:localname)), -}; + let $actual := admin:database-get-word-query-fast-case-sensitive-searches($admin-config, $database) + let $expected := $empty-field/db:fast-case-sensitive-searches + where $expected + return + if ($expected = $actual) then () + else + setup:validation-fail(fn:concat("word query fast case sensitive searches mismatch! ", $expected, " != ", $actual)), -declare function setup:add-fragment-roots-R($admin-config as element(configuration), $database as xs:unsignedLong, $fragment-roots as element(db:fragment-root)*) as element(configuration) -{ - if (fn:empty($fragment-roots)) then - $admin-config - else - let $fragment-root := $fragment-roots[1] - let $admin-config := - try { admin:database-add-fragment-root($admin-config, $database, admin:database-fragment-root($fragment-root/db:namespace-uri, $fragment-root/db:localname)) } - catch ($e) { $admin-config } - return setup:add-fragment-roots-R($admin-config, $database, fn:subsequence($fragment-roots, 2)) -}; + let $actual := admin:database-get-word-query-fast-diacritic-sensitive-searches($admin-config, $database) + let $expected := $empty-field/db:fast-diacritic-sensitive-searches + where $expected + return + if ($expected = $actual) then () + else + setup:validation-fail(fn:concat("word query fast diacritic sensitive searches mismatch! ", $expected, " != ", $actual)), -declare function setup:safe-database-add-range-element-index($admin-config as element(configuration), $database-name, $rangespec) as element(configuration) -{ - try { - admin:database-add-range-element-index($admin-config, xdmp:database($database-name), $rangespec) - } - catch ($e) { - $admin-config - } -}; + let $actual := admin:database-get-word-query-fast-phrase-searches($admin-config, $database) + let $expected := $empty-field/db:fast-phrase-searches + where $expected + return + if ($expected = $actual) then () + else + setup:validation-fail(fn:concat("word query fast phrase searches mismatch! ", $expected, " != ", $actual)), + let $actual := admin:database-get-word-query-include-document-root($admin-config, $database) + let $expected := $empty-field/db:include-root + where $expected + return + if ($expected = $actual) then () + else + setup:validation-fail(fn:concat("word query include document root mismatch! ", $expected, " != ", $actual)), -(: $includes is a sequence of alternating namespaces and element names. Assumes no attributes and weights of 1.0 :) -declare function setup:safe-database-add-field($admin-config as element(configuration), $database-name, $field-name, $incl-root, $includes) as element(configuration) -{ - let $fieldspec := admin:database-field($field-name, $incl-root) - let $admin-config := - try {admin:database-add-field($admin-config, xdmp:database($database-name), $fieldspec)} - catch ($e) {$admin-config} + let $actual := admin:database-get-word-query-one-character-searches($admin-config, $database) + let $expected := $empty-field/db:one-character-searches + where $expected + return + if ($expected = $actual) then () + else + setup:validation-fail(fn:concat("word query one character searches mismatch! ", $expected, " != ", $actual)), - return setup:safe-database-field-includes($admin-config, $database-name, $field-name, $includes) -}; + let $actual := admin:database-get-word-query-stemmed-searches($admin-config, $database) + let $expected := $empty-field/db:stemmed-searches + where $expected + return + if ($expected = $actual) then () + else + setup:validation-fail(fn:concat("word query stemmed searches mismatch! ", $expected, " != ", $actual)), + let $actual := admin:database-get-word-query-three-character-searches($admin-config, $database) + let $expected := $empty-field/db:three-character-searches + where $expected + return + if ($expected = $actual) then () + else + setup:validation-fail(fn:concat("word query three character searches mismatch! ", $expected, " != ", $actual)), -declare function setup:safe-database-field-includes($admin-config as element(configuration), $database-name, $field-name, $includes) as element(configuration) -{ - if (fn:count($includes) = 0) then - $admin-config - else if (fn:count($includes) >= 2) then - let $fieldspec := admin:database-included-element($includes[1], $includes[2], 1.0, "", "", "") - let $admin-config := - try {admin:database-add-field-included-element($admin-config, xdmp:database($database-name), $field-name, $fieldspec)} - catch ($e) {$admin-config} - return (: recurse :) - setup:safe-database-field-includes($admin-config, $database-name, $field-name, fn:subsequence($includes, 3)) - else - fn:error(xs:QName("error"), "Odd number of field includes") + let $actual := admin:database-get-word-query-three-character-word-positions($admin-config, $database) + let $expected := $empty-field/db:three-character-word-positions + where $expected + return + if ($expected = $actual) then () + else + setup:validation-fail(fn:concat("word query three character word positions mismatch! ", $expected, " != ", $actual)), + + let $actual := admin:database-get-word-query-trailing-wildcard-searches($admin-config, $database) + let $expected := $empty-field/db:trailing-wildcard-searches + where $expected + return + if ($expected = $actual) then () + else + setup:validation-fail(fn:concat("word query trailing wildcard searches mismatch! ", $expected, " != ", $actual)), + + let $actual := admin:database-get-word-query-trailing-wildcard-word-positions($admin-config, $database) + let $expected := $empty-field/db:trailing-wildcard-word-positions + where $expected + return + if ($expected = $actual) then () + else + setup:validation-fail(fn:concat("word query trailing wildcard word positions mismatch! ", $expected, " != ", $actual)), + + let $actual := admin:database-get-word-query-two-character-searches($admin-config, $database) + let $expected := $empty-field/db:two-character-searches + where $expected + return + if ($expected = $actual) then () + else + setup:validation-fail(fn:concat("word query two character searches mismatch! ", $expected, " != ", $actual)), + + let $actual := admin:database-get-word-query-word-searches($admin-config, $database) + let $expected := $empty-field/db:word-searches + where $expected + return + if ($expected = $actual) then () + else + setup:validation-fail(fn:concat("word query word searches mismatch! ", $expected, " != ", $actual)) + ) }; (: if the triggers database is 0, set it to 0. - if the triggers database is set to an ID of another database in the import, get its new ID and set it to that + if the triggers database is set to an ID of another database in the import, + get its new ID and set it to that :) -declare function setup:set-triggers-database($admin-config as element(configuration), $database-config as element(db:database), $database as xs:unsignedLong) as element(configuration) +declare function setup:set-triggers-database( + $admin-config as element(configuration), + $db-config as element(db:database), + $database as xs:unsignedLong) as element(configuration) +{ + let $triggers-database-id := + if ($db-config/db:triggers-database/@name) then + xdmp:database($db-config/db:triggers-database/@name) + else + 0 + return + admin:database-set-triggers-database( + $admin-config, + $database, + $triggers-database-id) +}; + +declare function setup:validate-triggers-database( + $admin-config as element(configuration), + $db-config as element(db:database), + $database as xs:unsignedLong) { - let $triggers-database-id := if ($database-config/db:triggers-database/@name) then xdmp:database($database-config/db:triggers-database/@name) else 0 + let $actual := admin:database-get-triggers-database($admin-config, $database) + let $expected := + if ($db-config/db:triggers-database/@name) then + xdmp:database($db-config/db:triggers-database/@name) + else + 0 return - admin:database-set-triggers-database($admin-config, $database, $triggers-database-id) + if ($expected = $actual) then () + else + setup:validation-fail(fn:concat("Triggers database mismatch! ", $expected, " != ", $actual)) }; (: if the schema database is 0, set it to 0. - if the schema database is set to an ID of another database in the import, get its new ID and set it to that + if the schema database is set to an ID of another database in the import, + get its new ID and set it to that :) -declare function setup:set-schema-database($admin-config as element(configuration), $database-config as element(db:database), $database as xs:unsignedLong) as element(configuration) +declare function setup:set-schema-database( + $admin-config as element(configuration), + $db-config as element(db:database), + $database as xs:unsignedLong) as element(configuration) { - let $schema-database-id := if ($database-config/db:schema-database/@name) then xdmp:database($database-config/db:schema-database/@name) else $default-schemas + let $schema-database-id := + if ($db-config/db:schema-database/@name) then + xdmp:database($db-config/db:schema-database/@name) + else + $default-schemas return - admin:database-set-schema-database($admin-config, $database, $schema-database-id) + admin:database-set-schema-database( + $admin-config, + $database, + $schema-database-id) }; +declare function setup:validate-schema-database( + $admin-config as element(configuration), + $db-config as element(db:database), + $database as xs:unsignedLong) +{ + let $actual := admin:database-get-schema-database($admin-config, $database) + let $expected := + if ($db-config/db:schema-database/@name) then + xdmp:database($db-config/db:schema-database/@name) + else + $default-schemas + return + if ($expected = $actual) then () + else + setup:validation-fail(fn:concat("Schema database mismatch! ", $expected, " != ", $actual)) +}; (: if the security database is 0, set it to 0. - if the security database is set to an ID of another database in the import, get its new ID and set it to that + if the security database is set to an ID of another database in the import, + get its new ID and set it to that :) -declare function setup:set-security-database($admin-config as element(configuration), $database-config as element(db:database), $database as xs:unsignedLong) as element(configuration) +declare function setup:set-security-database( + $admin-config as element(configuration), + $db-config as element(db:database), + $database as xs:unsignedLong) as element(configuration) +{ + let $security-database-id := + if ($db-config/db:security-database/@name) then + xdmp:database($db-config/db:security-database/@name) + else + $default-security + return + admin:database-set-security-database( + $admin-config, + $database, + $security-database-id) +}; + +declare function setup:validate-security-database( + $admin-config as element(configuration), + $db-config as element(db:database), + $database as xs:unsignedLong) { - let $security-database-id := if ($database-config/db:security-database/@name) then xdmp:database($database-config/db:security-database/@name) else $default-security + let $actual := admin:database-get-security-database($admin-config, $database) + let $expected := + if ($db-config/db:security-database/@name) then + xdmp:database($db-config/db:security-database/@name) + else + $default-security return - admin:database-set-security-database($admin-config, $database, $security-database-id) + if ($expected = $actual) then () + else + setup:validation-fail(fn:concat("Security database mismatch! ", $expected, " != ", $actual)) }; (:::::::::::::::::::::::::::::::::::::::::::::::::::::::::: :: Creation of app servers ::) -declare function setup:create-appservers($import-config as element(configuration)) as item()* +declare function setup:create-appservers( + $import-config as element(configuration)) as item()* { - try { - for $http-config in - setup:get-http-servers-from-config($import-config) - return - setup:create-appserver($http-config), - - for $xdbc-config in - setup:get-xdbc-servers-from-config($import-config) - return - setup:create-xdbcserver($xdbc-config) + for $http-config in $import-config//gr:http-servers/gr:http-server + return + setup:create-appserver($http-config), - } catch ($e) { - fn:concat("App servers creation failed: ", $e//err:format-string) - } + for $xdbc-config in $import-config//gr:xdbc-servers/gr:xdbc-server + return + setup:create-xdbcserver($xdbc-config) }; -declare function setup:create-appserver($server-config as element(gr:http-server)) as item()* +declare function setup:validate-appservers( + $import-config as element(configuration)) as item()* { - let $server-name := - setup:get-server-name-from-http-config($server-config) + for $http-config in $import-config//gr:http-servers/gr:http-server return + setup:validate-appserver($http-config), - try { - if (xdmp:servers()[$server-name = xdmp:server-name(.)]) then + for $xdbc-config in $import-config//gr:xdbc-servers/gr:xdbc-server + return + setup:validate-xdbcserver($xdbc-config) +}; + +declare function setup:create-appserver( + $server-config as element(gr:http-server)) as item()* +{ + let $server-name as xs:string? := $server-config/gr:http-server-name[fn:string-length(.) > 0] + return + if (xdmp:servers()[xdmp:server-name(.) = $server-name]) then fn:concat("HTTP Server ", $server-name, " already exists, not recreated..") else - let $log := xdmp:log(fn:concat("building app server ", $server-name)) - let $admin-config := - admin:get-configuration() - let $root := $server-config/gr:root[fn:string-length(.) > 0] - let $root := - if ($root) then $root else "/" - let $port := - xs:unsignedLong($server-config/gr:port) - let $is-webdav := - xs:boolean($server-config/gr:webDAV) + let $root := if ($root) then $root else "/" + let $port := xs:unsignedLong($server-config/gr:port) + let $is-webdav := xs:boolean($server-config/gr:webDAV) let $database-id := if ($server-config/gr:database/@name) then xdmp:database($server-config/gr:database/@name) @@ -1373,124 +2290,61 @@ declare function setup:create-appserver($server-config as element(gr:http-server else 0 + let $admin-config := admin:get-configuration() let $admin-config := if ($is-webdav) then (: Note: database id is stored as modules is for webdav servers :) - admin:webdav-server-create($admin-config, $default-group, $server-name, $root, $port, $modules-id) + admin:webdav-server-create( + $admin-config, + $default-group, + $server-name, + $root, + $port, + $modules-id) else - admin:http-server-create($admin-config, $default-group, $server-name, $root, $port, $modules-id, $database-id) - let $restart-hosts := - admin:save-configuration-without-restart($admin-config) - + admin:http-server-create( + $admin-config, + $default-group, + $server-name, + $root, + $port, + $modules-id, + $database-id) return - fn:concat("HTTP Server ", $server-name, " succesfully created..", if ($restart-hosts) then " (note: restart required)" else ()) - - } catch ($e) { - fn:concat("HTTP Server ", $server-name, " creation failed: ", $e//err:format-string) - } + ( + if (admin:save-configuration-without-restart($admin-config)) then + xdmp:set($restart-needed, fn:true()) + else (), + fn:concat("HTTP Server ", $server-name, " succesfully created.") + ) }; -declare function setup:create-xdbcserver($server-config as element(gr:xdbc-server)) as item()* +declare function setup:validate-appserver( + $server-config as element(gr:http-server)) as item()* { - let $server-name := - setup:get-server-name-from-xdbc-config($server-config) + let $server-name as xs:string? := $server-config/gr:http-server-name[fn:string-length(.) > 0] return - - try { - if (xdmp:servers()[$server-name = xdmp:server-name(.)]) then - fn:concat("XDBC Server ", $server-name, " already exists, not recreated..") + if (xdmp:servers()[xdmp:server-name(.) = $server-name]) then () else - let $admin-config := - admin:get-configuration() - - let $root := $server-config/gr:root[fn:string-length(.) > 0] - let $root := - if ($root) then $root else "/" - let $port := - xs:unsignedLong($server-config/gr:port) - let $database-id := - if ($server-config/gr:database/@name) then - xdmp:database($server-config/gr:database/@name) - else - 0 - let $modules-id := - if ($server-config/gr:modules/@name eq "filesystem") then - 0 - else if ($server-config/gr:modules/@name) then - xdmp:database($server-config/gr:modules/@name) - else - 0 - - let $admin-config := - admin:xdbc-server-create($admin-config, $default-group, $server-name, $root, $port, $modules-id, $database-id) - let $restart-hosts := - admin:save-configuration-without-restart($admin-config) - - return - fn:concat("XDBC Server ", $server-name, " succesfully created..", if ($restart-hosts) then " (note: restart required)" else ()) - - } catch ($e) { - fn:concat("XDBC Server ", $server-name, " creation failed: ", $e//err:format-string) - } -}; - -(:::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - :: Configuration of app servers - ::) - -declare function setup:apply-appservers-settings($import-config as element(configuration)) as item()* -{ - try { - for $http-config in - setup:get-http-servers-from-config($import-config) - return - setup:configure-http-server($http-config), - - for $xdbc-config in - setup:get-xdbc-servers-from-config($import-config) - return - setup:configure-xdbc-server($xdbc-config), - - for $task-config in - setup:get-task-servers-from-config($import-config) - return - setup:configure-task-server($task-config) - - } catch ($e) { - fn:concat("Applying servers settings failed: ", $e//err:format-string) - } + setup:validation-fail(fn:concat("Missing HTTP server: ", $server-name)) }; -declare function setup:configure-http-server($server-config as element(gr:http-server)) as item()* +declare function setup:create-xdbcserver( + $server-config as element(gr:xdbc-server)) as item()* { - let $server-name := setup:get-server-name-from-http-config($server-config) - let $server-id := xdmp:server($server-name) - let $admin-config := setup:configure-server($server-config, $server-id) - return - xdmp:eval(' - import module namespace admin = "http://marklogic.com/xdmp/admin" at "/MarkLogic/admin.xqy"; - import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; - - declare namespace gr="http://marklogic.com/xdmp/group"; - declare namespace err="http://marklogic.com/xdmp/error"; - - declare variable $server-config as element() external; - declare variable $server-name as xs:string external; - declare variable $admin-config as element() external; - declare variable $server-id external; - declare variable $default-user external; - try { - let $default-user := if ($server-config/gr:default-user/@name) then xdmp:user($server-config/gr:default-user/@name) else $default-user - let $is-webdav := - xs:boolean($server-config/gr:webDAV) - - (: reconnect databases in case the appserver already existed :) + let $server-name as xs:string? := $server-config/gr:xdbc-server-name[fn:string-length(.) > 0] + return + if (xdmp:servers()[xdmp:server-name(.) = $server-name]) then + fn:concat("XDBC Server ", $server-name, " already exists, not recreated..") + else + let $root := $server-config/gr:root[fn:string-length(.) > 0] + let $root := if ($root) then $root else "/" + let $port := xs:unsignedLong($server-config/gr:port) let $database-id := if ($server-config/gr:database/@name) then xdmp:database($server-config/gr:database/@name) else 0 - let $_ := xdmp:log(text {"Modules db name:", $server-config/gr:modules/@name}) let $modules-id := if ($server-config/gr:modules/@name eq "filesystem") then 0 @@ -1498,98 +2352,99 @@ declare function setup:configure-http-server($server-config as element(gr:http-s xdmp:database($server-config/gr:modules/@name) else 0 - let $root := $server-config/gr:root[fn:string-length(.) > 0] - let $root := - if ($root) then $root else "/" + let $admin-config := admin:get-configuration() let $admin-config := - if ($is-webdav) then - admin:appserver-set-database($admin-config, $server-id, $modules-id) - else - admin:appserver-set-database($admin-config, $server-id, $database-id) - let $admin-config := - if ($is-webdav) then - $admin-config - else - admin:appserver-set-modules-database($admin-config, $server-id, $modules-id) + admin:xdbc-server-create( + $admin-config, + $default-group, + $server-name, + $root, + $port, + $modules-id, + $database-id) + return + ( + if (admin:save-configuration-without-restart($admin-config)) then + xdmp:set($restart-needed, fn:true()) + else (), + fn:concat("XDBC Server ", $server-name, " succesfully created.") + ) +}; - let $admin-config := admin:appserver-set-root($admin-config, $server-id, $root) +declare function setup:validate-xdbcserver( + $server-config as element(gr:xdbc-server)) as item()* +{ + let $server-name as xs:string? := $server-config/gr:xdbc-server-name[fn:string-length(.) > 0] + return + if (xdmp:servers()[xdmp:server-name(.) = $server-name]) then () + else + setup:validation-fail(fn:concat("Missing XDBC server: ", $server-name)) +}; - let $value := $server-config/gr:session-timeout[fn:string-length(.) > 0] - let $admin-config := - if ($value) then - admin:appserver-set-session-timeout($admin-config, $server-id, $value) - else - $admin-config +(:::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + :: Configuration of app servers + ::) - let $value := $server-config/gr:static-expires[fn:string-length(.) > 0] - let $admin-config := - if ($value) then - admin:appserver-set-static-expires($admin-config, $server-id, $value) - else - $admin-config +declare function setup:apply-appservers-settings( + $import-config as element(configuration)) as item()* +{ + for $http-config in $import-config//gr:http-servers/gr:http-server + return + setup:configure-http-server($http-config), - let $admin-config := admin:appserver-set-default-user($admin-config, $server-id, $default-user) + for $xdbc-config in $import-config//gr:xdbc-servers/gr:xdbc-server + return + setup:configure-xdbc-server($xdbc-config), - let $admin-config := - if ($is-webdav) then - let $value := $server-config/gr:compute-content-length[fn:string-length(.) > 0] - return - if ($value) then - admin:appserver-set-compute-content-length($admin-config, $server-id, $value) - else - $admin-config - else - let $value := $server-config/gr:error-handler[fn:string-length(.) > 0] - let $admin-config := - if ($value) then - admin:appserver-set-error-handler($admin-config, $server-id, $value) - else - $admin-config + for $task-config in $import-config/gr:task-server + return + setup:configure-task-server($task-config) +}; - let $value := $server-config/gr:url-rewriter[fn:string-length(.) > 0] - let $admin-config := - if ($value) then - admin:appserver-set-url-rewriter($admin-config, $server-id, $value) - else - $admin-config - - return $admin-config - - (: TODO ? - 0 - true - true - - ALL:!LOW:@STRENGTH - true - - :) - - let $restart-hosts := - admin:save-configuration-without-restart($admin-config) - return - fn:concat("HTTP Server ", $server-name, " settings applied succesfully..", if ($restart-hosts) then " (note: restart required)" else ()) +declare function setup:validate-appservers-settings( + $import-config as element(configuration)) as item()* +{ + for $http-config in $import-config//gr:http-servers/gr:http-server + return + setup:validate-http-server($http-config), - } catch ($e) { - fn:concat("Applying settings to HTTP Server ", $server-name, " failed: ", $e//err:format-string) - }', - (xs:QName("server-config"), $server-config, - xs:QName("server-name"), $server-name, - xs:QName("admin-config"), $admin-config, - xs:QName("server-id"), $server-id, - xs:QName("default-user"), $default-user)) + for $xdbc-config in $import-config//gr:xdbc-servers/gr:xdbc-server + return + setup:validate-xdbc-server($xdbc-config), + + for $task-config in $import-config/gr:task-server + return + setup:validate-task-server($task-config) }; -declare function setup:configure-xdbc-server($server-config as element(gr:xdbc-server)) as item()* +declare function setup:configure-http-server( + $server-config as element(gr:http-server)) as item()* { - let $server-name := - setup:get-server-name-from-xdbc-config($server-config) + let $server-name as xs:string? := $server-config/gr:http-server-name[fn:string-length(.) > 0] + let $server-id := xdmp:server($server-name) + let $admin-config := setup:configure-server($server-config, $server-id) return + ( + if (xdmp:eval(' + import module namespace admin = "http://marklogic.com/xdmp/admin" at "/MarkLogic/admin.xqy"; + import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; - try { - let $server-id := xdmp:server($server-name) - let $admin-config := setup:configure-server($server-config, $server-id) + declare namespace gr="http://marklogic.com/xdmp/group"; + declare namespace err="http://marklogic.com/xdmp/error"; + + declare variable $server-config as element() external; + declare variable $server-name as xs:string external; + declare variable $admin-config as element() external; + declare variable $server-id external; + declare variable $default-user external; + + let $default-user := + if ($server-config/gr:default-user/@name) then + xdmp:user($server-config/gr:default-user/@name) + else + $default-user + let $is-webdav := xs:boolean($server-config/gr:webDAV) (: reconnect databases in case the appserver already existed :) let $database-id := @@ -1604,260 +2459,357 @@ declare function setup:configure-xdbc-server($server-config as element(gr:xdbc-s xdmp:database($server-config/gr:modules/@name) else 0 - let $admin-config := - admin:appserver-set-database($admin-config, $server-id, $database-id) - let $admin-config := - admin:appserver-set-modules-database($admin-config, $server-id, $modules-id) - - let $restart-hosts := - admin:save-configuration-without-restart($admin-config) - - return - fn:concat("XDBC Server ", $server-name, " settings applied succesfully..", if ($restart-hosts) then " (note: restart required)" else ()) - - } catch ($e) { - fn:concat("Applying settings to XDBC Server ", $server-name, " failed: ", $e//err:format-string) - } -}; - -declare function setup:configure-task-server($server-config as element(gr:task-server)) as item()* -{ - try { - let $admin-config := admin:get-configuration() - - let $value as xs:boolean? := $server-config/gr:debug-allow - let $admin-config := - if (fn:exists($value)) then - admin:taskserver-set-debug-allow($admin-config, $default-group, $value) - else - $admin-config - - let $value := $server-config/gr:debug-threads - let $admin-config := - if (fn:exists($value)) then - admin:taskserver-set-debug-threads($admin-config, $default-group, $value) - else - $admin-config - - let $value := $server-config/gr:default-time-limit - let $admin-config := - if (fn:exists($value)) then - admin:taskserver-set-default-time-limit($admin-config, $default-group, $value) - else - $admin-config + let $root := $server-config/gr:root[fn:string-length(.) > 0] + let $root := if ($root) then $root else "/" - let $value as xs:boolean? := $server-config/gr:log-errors let $admin-config := - if (fn:exists($value)) then - admin:taskserver-set-log-errors($admin-config, $default-group, $value) + if ($is-webdav) then + admin:appserver-set-database($admin-config, $server-id, $modules-id) else - $admin-config - - let $value := $server-config/gr:max-time-limit + admin:appserver-set-database($admin-config, $server-id, $database-id) let $admin-config := - if (fn:exists($value)) then - admin:taskserver-set-max-time-limit($admin-config, $default-group, $value) - else + if ($is-webdav) then $admin-config - - let $value := $server-config/gr:post-commit-trigger-depth - let $admin-config := - if (fn:exists($value)) then - admin:taskserver-set-post-commit-trigger-depth($admin-config, $default-group, $value) else - $admin-config + admin:appserver-set-modules-database($admin-config, $server-id, $modules-id) - let $value := $server-config/gr:pre-commit-trigger-depth - let $admin-config := - if (fn:exists($value)) then - admin:taskserver-set-pre-commit-trigger-depth($admin-config, $default-group, $value) - else - $admin-config + let $admin-config := admin:appserver-set-root($admin-config, $server-id, $root) - let $value := $server-config/gr:pre-commit-trigger-limit + let $value := $server-config/gr:session-timeout[fn:string-length(.) > 0] let $admin-config := - if (fn:exists($value)) then - admin:taskserver-set-pre-commit-trigger-limit($admin-config, $default-group, $value) + if ($value) then + admin:appserver-set-session-timeout($admin-config, $server-id, $value) else $admin-config - let $value as xs:boolean? := $server-config/gr:profile-allow + let $value := $server-config/gr:static-expires[fn:string-length(.) > 0] let $admin-config := - if (fn:exists($value)) then - admin:taskserver-set-profile-allow($admin-config, $default-group, $value) + if ($value) then + admin:appserver-set-static-expires($admin-config, $server-id, $value) else $admin-config - let $value := $server-config/gr:queue-size let $admin-config := - if (fn:exists($value)) then - admin:taskserver-set-queue-size($admin-config, $default-group, $value) - else - $admin-config + admin:appserver-set-default-user($admin-config, $server-id, $default-user) - let $value := $server-config/gr:threads let $admin-config := - if (fn:exists($value)) then - admin:taskserver-set-threads($admin-config, $default-group, $value) + if ($is-webdav) then + let $value := $server-config/gr:compute-content-length[fn:string-length(.) > 0] + return + if ($value) then + admin:appserver-set-compute-content-length($admin-config, $server-id, $value) + else + $admin-config else - $admin-config + let $value := $server-config/gr:error-handler[fn:string-length(.) > 0] + let $admin-config := + if ($value) then + admin:appserver-set-error-handler($admin-config, $server-id, $value) + else + $admin-config - let $restart-hosts := - admin:save-configuration-without-restart($admin-config) + let $value := $server-config/gr:url-rewriter[fn:string-length(.) > 0] + let $admin-config := + if ($value) then + admin:appserver-set-url-rewriter($admin-config, $server-id, $value) + else + $admin-config + + return $admin-config + + (: TODO ? + 0 + true + true + + ALL:!LOW:@STRENGTH + true + + :) return - fn:concat("Task Server settings applied succesfully..", if ($restart-hosts) then " (note: restart required)" else ()) - - } catch ($e) { - xdmp:log($e), - fn:concat("Applying settings to Task Server failed: ", $e//err:format-string) - } + admin:save-configuration-without-restart($admin-config)', + (xs:QName("server-config"), $server-config, + xs:QName("server-name"), $server-name, + xs:QName("admin-config"), $admin-config, + xs:QName("server-id"), $server-id, + xs:QName("default-user"), $default-user))) then + xdmp:set($restart-needed, fn:true()) + else (), + fn:concat("HTTP Server ", $server-name, " settings applied succesfully.") + ) }; -declare function setup:configure-server($server-config as element(), $server-id as xs:unsignedLong) as element(configuration) +declare function setup:validate-http-server( + $server-config as element(gr:http-server)) as item()* { - let $admin-config := - admin:get-configuration() - let $last-login-id := if ($server-config/gr:last-login/@name) then xdmp:database($server-config/gr:last-login/@name) else 0 - - let $admin-config := admin:appserver-set-last-login($admin-config, $server-id, $last-login-id) - - let $value := $server-config/gr:display-last-login[fn:string-length(.) > 0] - let $admin-config := - if ($value) then - admin:appserver-set-display-last-login($admin-config, $server-id, $value) + let $server-name as xs:string? := $server-config/gr:http-server-name[fn:string-length(.) > 0] + let $server-id := xdmp:server($server-name) + let $admin-config := admin:get-configuration() + let $default-user := + if ($server-config/gr:default-user/@name) then + xdmp:user($server-config/gr:default-user/@name) else - $admin-config + $default-user + let $is-webdav := xs:boolean($server-config/gr:webDAV) - let $value := $server-config/gr:backlog[fn:string-length(.) > 0] - let $admin-config := - if ($value) then - admin:appserver-set-backlog($admin-config, $server-id, $value) + (: reconnect databases in case the appserver already existed :) + let $database-id := + if ($server-config/gr:database/@name) then + xdmp:database($server-config/gr:database/@name) else - $admin-config - - let $value := $server-config/gr:threads[fn:string-length(.) > 0] - let $admin-config := - if ($value) then - admin:appserver-set-threads($admin-config, $server-id, $server-config/gr:threads) + 0 + let $modules-id := + if ($server-config/gr:modules/@name eq "filesystem") then + 0 + else if ($server-config/gr:modules/@name) then + xdmp:database($server-config/gr:modules/@name) else - $admin-config + 0 + let $root := $server-config/gr:root[fn:string-length(.) > 0] + let $root := if ($root) then $root else "/" - let $value := $server-config/gr:request-timeout[fn:string-length(.) > 0] - let $admin-config := - if ($value) then - admin:appserver-set-request-timeout($admin-config, $server-id, $value) - else - $admin-config + return + ( + setup:validate-server($server-config, $server-id), - let $value := $server-config/gr:keep-alive-timeout[fn:string-length(.) > 0] - let $admin-config := - if ($value) then - admin:appserver-set-keep-alive-timeout($admin-config, $server-id, $value) - else - $admin-config + let $actual := admin:appserver-get-database($admin-config, $server-id) + return + if ($is-webdav) then + if ($modules-id = $actual) then () + else + setup:validation-fail(fn:concat("Invalid Appserver database: ", $modules-id, " != ", $actual)) + else + ( + if ($database-id = $actual) then () + else + setup:validation-fail(fn:concat("Invalid Appserver database: ", $database-id, " != ", $actual)), - let $value := $server-config/gr:max-time-limit[fn:string-length(.) > 0] - let $admin-config := - if ($value) then - admin:appserver-set-max-time-limit($admin-config, $server-id, $value) - else - $admin-config + let $actual := admin:appserver-get-modules-database($admin-config, $server-id) + return + if ($actual = $modules-id) then () + else + setup:validation-fail(fn:concat("Invalid Appserver modules database: ", $modules-id, " != ", $actual)) + ), - let $value := $server-config/gr:default-time-limit[fn:string-length(.) > 0] - let $admin-config := - if ($value) then - admin:appserver-set-default-time-limit($admin-config, $server-id, $value) - else - $admin-config + let $actual := admin:appserver-get-root($admin-config, $server-id) + return + if ($root = $actual) then () + else + setup:validation-fail(fn:concat("Appserver root mismatch: ", $root, " != ", $actual)), - let $value := $server-config/gr:pre-commit-trigger-depth[fn:string-length(.) > 0] - let $admin-config := - if ($value) then - admin:appserver-set-pre-commit-trigger-depth($admin-config, $server-id, $value) - else - $admin-config + let $expected := $server-config/gr:session-timeout[fn:string-length(.) > 0] + let $actual := admin:appserver-get-session-timeout($admin-config, $server-id) + return + if ($expected = $actual) then () + else + setup:validation-fail(fn:concat("Appserver session timeout mismatch: ", $expected, " != ", $actual)), - let $value := $server-config/gr:pre-commit-trigger-limit[fn:string-length(.) > 0] - let $admin-config := - if ($value) then - admin:appserver-set-pre-commit-trigger-limit($admin-config, $server-id, $value) - else - $admin-config + let $expected := $server-config/gr:static-expires[fn:string-length(.) > 0] + let $actual := admin:appserver-get-static-expires($admin-config, $server-id) + return + if ($expected = $actual) then () + else + setup:validation-fail(fn:concat("Appserver static expires mismatch: ", $expected, " != ", $actual)), - let $value := $server-config/gr:collation[fn:string-length(.) > 0] - let $admin-config := - if ($value) then - admin:appserver-set-collation($admin-config, $server-id, $value) - else - $admin-config + let $actual := admin:appserver-get-default-user($admin-config, $server-id) + return + if ($default-user = $actual) then () + else + setup:validation-fail(fn:concat("Appserver default user mismatch: ", $default-user, " != ", $actual)), - let $value := $server-config/gr:authentication[fn:string-length(.) > 0] - let $admin-config := - if ($value) then - admin:appserver-set-authentication($admin-config, $server-id, $value) + if ($is-webdav) then + let $expected := $server-config/gr:compute-content-length[fn:string-length(.) > 0] + let $actual := admin:appserver-get-compute-content-length($admin-config, $server-id) + return + if ($expected = $actual) then () + else + setup:validation-fail(fn:concat("Appserver compute content length mismatch: ", $expected, " != ", $actual)) else - $admin-config - - (: be carefull: privilege should be a lookup! :) - (: + ( + let $expected := $server-config/gr:error-handler[fn:string-length(.) > 0] + let $actual := admin:appserver-get-error-handler($admin-config, $server-id) + return + if ($expected = $actual) then () + else + setup:validation-fail(fn:concat("Appserver error handler mismatch: ", $expected, " != ", $actual)), - let $value := $server-config/gr:privilege[fn:string-length(.) > 0] - let $admin-config := - if ($value) then - admin:appserver-set-privilege($admin-config, $server-id, $value) - else - $admin-config - :) + let $expected := $server-config/gr:url-rewriter[fn:string-length(.) > 0] + let $actual := admin:appserver-get-url-rewriter($admin-config, $server-id) + return + if ($expected = $actual) then () + else + setup:validation-fail(fn:concat("Appserver url rewriter mismatch: ", $expected, " != ", $actual)) + ) + ) +}; - let $value := $server-config/gr:concurrent-request-limit[fn:string-length(.) > 0] - let $admin-config := - if ($value) then - admin:appserver-set-concurrent-request-limit($admin-config, $server-id, $value) - else - $admin-config +declare function setup:configure-xdbc-server( + $server-config as element(gr:xdbc-server)) as item()* +{ + let $server-name as xs:string? := $server-config/gr:xdbc-server-name[fn:string-length(.) > 0] + return + (: reconnect databases in case the appserver already existed :) + let $database-id := + if ($server-config/gr:database/@name) then + xdmp:database($server-config/gr:database/@name) + else + 0 + let $modules-id := + if ($server-config/gr:modules/@name eq "filesystem") then + 0 + else if ($server-config/gr:modules/@name) then + xdmp:database($server-config/gr:modules/@name) + else + 0 + let $server-id := xdmp:server($server-name) + let $admin-config := + admin:appserver-set-modules-database( + admin:appserver-set-database( + setup:configure-server($server-config, $server-id), + $server-id, + $database-id), + $server-id, + $modules-id) + return + ( + if (admin:save-configuration-without-restart($admin-config)) then + xdmp:set($restart-needed, fn:true()) + else (), + fn:concat("XDBC Server ", $server-name, " settings applied succesfully.") + ) +}; - let $value := $server-config/gr:log-errors[fn:string-length(.) > 0] - let $admin-config := - if ($value) then - admin:appserver-set-log-errors($admin-config, $server-id, $value) - else - $admin-config +declare function setup:validate-xdbc-server( + $server-config as element(gr:xdbc-server)) as item()* +{ + let $server-id := xdmp:server($server-config/gr:xdbc-server-name[fn:string-length(.) > 0]) + let $admin-config := admin:get-configuration() + return + ( + setup:validate-server($server-config, $server-id), - let $value := $server-config/gr:debug-allow[fn:string-length(.) > 0] - let $admin-config := - if ($value) then - admin:appserver-set-debug-allow($admin-config, $server-id, $value) - else - $admin-config + let $expected := ($server-config/gr:database/@name/xdmp:database(.), 0)[1] + let $actual := admin:appserver-get-database($admin-config, $server-id) + return + if ($expected = $actual) then () + else + setup:validation-fail(fn:concat("XDBC Server database mismatch: ", $expected, " != ", $actual)), - let $value := $server-config/gr:profile-allow[fn:string-length(.) > 0] - let $admin-config := - if ($value) then - admin:appserver-set-profile-allow($admin-config, $server-id, $value) - else - $admin-config + let $expected := + if ($server-config/gr:modules/@name eq "filesystem") then + 0 + else if ($server-config/gr:modules/@name) then + xdmp:database($server-config/gr:modules/@name) + else + 0 + let $actual := admin:appserver-get-modules-database($admin-config, $server-id) + return + if ($expected = $actual) then () + else + setup:validation-fail(fn:concat("XDBC Server modules database mismatch: ", $expected, " != ", $actual)) + ) +}; - let $value := $server-config/gr:default-xquery-version[fn:string-length(.) > 0] - let $admin-config := - if ($value) then - admin:appserver-set-default-xquery-version($admin-config, $server-id, $value) - else - $admin-config +declare function setup:configure-task-server( + $server-config as element(gr:task-server)) as item()* +{ + let $admin-config := admin:get-configuration() + let $settings := + + debug-allow + debug-threads + default-time-limit + log-errors + max-time-limit + post-commit-trigger-depth + pre-commit-trigger-depth + pre-commit-trigger-limit + profile-allow + queue-size + threads + + let $apply-settings := + for $setting in $settings/*:setting + let $value := fn:data(xdmp:value(fn:concat("$server-config/gr:", $setting))) + where fn:exists($value) + return + xdmp:set($admin-config, xdmp:value(fn:concat("admin:taskserver-set-", $setting, "($admin-config, $default-group, $value)"))) + return + ( + if (admin:save-configuration-without-restart($admin-config)) then + xdmp:set($restart-needed, fn:true()) + else (), + fn:concat("Task Server settings applied succesfully.") + ) +}; - let $value := $server-config/gr:output-sgml-character-entities[fn:string-length(.) > 0] - let $admin-config := - if ($value) then - admin:appserver-set-output-sgml-character-entities($admin-config, $server-id, $value) +declare function setup:validate-task-server( + $server-config as element(gr:task-server)) as item()* +{ + let $admin-config := admin:get-configuration() + let $settings := + + debug-allow + debug-threads + default-time-limit + log-errors + max-time-limit + post-commit-trigger-depth + pre-commit-trigger-depth + pre-commit-trigger-limit + profile-allow + queue-size + threads + + for $setting in $settings/*:setting + let $expected := fn:data(xdmp:value(fn:concat("$server-config/gr:", $setting))) + let $actual := xdmp:value(fn:concat("admin:taskserver-get-", $setting, "($admin-config, $default-group)")) + where fn:exists($expected) + return + if ($expected = $actual) then () else - $admin-config + setup:validation-fail(fn:concat("Task Server ", $setting, " mismatch: ", $expected, " != ", $actual)) +}; - let $value := $server-config/gr:output-encoding[fn:string-length(.) > 0] - let $admin-config := - if ($value) then - admin:appserver-set-output-encoding($admin-config, $server-id, $value) - else - $admin-config +declare function setup:configure-server( + $server-config as element(), + $server-id as xs:unsignedLong) as element(configuration) +{ + let $admin-config := admin:get-configuration() + let $settings := + + last-login + display-last-login + backlog + threads + request-timeout + keep-alive-timeout + max-time-limit + default-time-limit + pre-commit-trigger-depth + pre-commit-trigger-limit + collation + authentication + concurrent-request-limit + log-errors + debug-allow + profile-allow + default-xquery-version + output-sgml-character-entities + output-encoding + + let $apply-settings := + for $setting in $settings/*:setting + let $value := + if ($setting/@value) then + fn:data(xdmp:value($setting/@value)) + else + fn:data(xdmp:value(fn:concat("$server-config/gr:", $setting, "[fn:string-length(.) > 0]"))) + where (fn:exists($value)) + return + xdmp:set($admin-config, + xdmp:value(fn:concat("admin:appserver-set-", $setting, "($admin-config, $server-id, $value)"))) let $namespaces := $server-config/gr:namespaces/gr:namespace let $admin-config := @@ -1865,316 +2817,409 @@ declare function setup:configure-server($server-config as element(), $server-id let $old-ns := admin:appserver-get-namespaces($admin-config, $server-id) let $config := (: First delete any namespace that matches the prefix and uri :) - admin:appserver-delete-namespace($admin-config, $server-id, + admin:appserver-delete-namespace( + $admin-config, + $server-id, for $ns in $namespaces - let $same-prefix := $old-ns[gr:prefix = $ns/gr:prefix][gr:namespace-uri ne $ns/gr:namespace-uri] + let $same-prefix := + $old-ns[gr:prefix = $ns/gr:prefix][gr:namespace-uri ne $ns/gr:namespace-uri] return if ($same-prefix) then admin:group-namespace($same-prefix/gr:prefix, $same-prefix/gr:namespace-uri) - else () - - ) - return (: Then add in any namespace whose prefix isn't already defined :) - admin:appserver-add-namespace($config, $server-id, + else ()) + return + (: Then add in any namespace whose prefix isn't already defined :) + admin:appserver-add-namespace( + $config, + $server-id, for $ns in $namespaces - return + return if ($old-ns[gr:prefix = $ns/gr:prefix][gr:namespace-uri = $ns/gr:namespace-uri]) then () else - admin:group-namespace($ns/gr:prefix, $ns/gr:namespace-uri) - ) + admin:group-namespace($ns/gr:prefix, $ns/gr:namespace-uri)) else - $admin-config - - + $admin-config (: TODO: schemas, request-blackouts :) - return $admin-config }; -declare function setup:create-privileges($import-config as element(configuration)) +declare function setup:validate-server( + $server-config as element(), + $server-id as xs:unsignedLong) as element(configuration) { - for $priv in setup:get-privileges-from-config($import-config) + let $admin-config := admin:get-configuration() + let $_ := + let $actual := admin:appserver-get-last-login($admin-config, $server-id) + let $expected := + if ($server-config/gr:last-login/@name) then + xdmp:database($server-config/gr:last-login/@name) + else 0 + return + if ($actual = $expected) then () + else + setup:validation-fail(fn:concat("Appserver last-login mismatch: ", $expected, " != ", $actual)) + let $settings := + + display-last-login + backlog + threads + request-timeout + keep-alive-timeout + max-time-limit + default-time-limit + pre-commit-trigger-depth + pre-commit-trigger-limit + collation + authentication + concurrent-request-limit + log-errors + debug-allow + profile-allow + default-xquery-version + output-sgml-character-entities + output-encoding + + for $setting in $settings/*:setting + let $expected := fn:data(xdmp:value(fn:concat("$server-config/gr:", $setting, "[fn:string-length(.) > 0]"))) + let $actual := xdmp:value(fn:concat("admin:appserver-get-", $setting, "($admin-config, $server-id)")) + where $expected return - setup:create-privilege($priv/sec:privilege-name, - $priv/sec:action, - $priv/sec:kind, - ()) + if ($expected = $actual) then () + else + setup:validation-fail(fn:concat("Appserver ", $setting, " mismatch: ", $expected, " != ", $actual)), + + let $admin-config := admin:get-configuration() + let $existing := admin:appserver-get-namespaces($admin-config, $server-id) + for $expected in $server-config/gr:namespaces/gr:namespace + return + if ($existing[fn:deep-equal(., $expected)]) then () + else + setup:validation-fail(fn:concat("Appserver missing namespace: ", $expected/gr:namespace-uri)) }; -declare function setup:create-privilege($privilege-name as xs:string, - $action as xs:string?, - $kind as xs:string, - $role-names as xs:string*) +declare function setup:create-privileges( + $import-config as element(configuration)) { + for $privilege in $import-config/sec:privileges/sec:privilege + let $privilege-name as xs:string := $privilege/sec:privilege-name + let $action as xs:string? := $privilege/sec:action + let $kind as xs:string := $privilege/sec:kind + let $role-names as xs:string* := () let $match := setup:get-privileges()/sec:privilege[sec:privilege-name = $privilege-name] return if ($match) then if ($match/sec:action != $action or $match/sec:kind != $kind) then fn:error( xs:QName("PRIV-MISMATCH"), - fn:concat("Configured privilege conflicts with existing one: name=", $privilege-name, - "; action=", $action, "; kind=", $kind) + fn:concat( + "Configured privilege conflicts with existing one: name=", + $privilege-name, + "; action=", $action, "; kind=", + $kind) ) - else - (: It's a match. No need to mess with it. :) - () + else () (: It's a match. No need to mess with it. :) else + ( (: Create this new privilege :) - xdmp:eval('import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; - declare variable $privilege-name as xs:string external; - declare variable $action as xs:string external; - declare variable $kind as xs:string external; - declare variable $role-names as element() external; - sec:create-privilege($privilege-name, $action, $kind, $role-names/*)', - (xs:QName("privilege-name"), $privilege-name, - xs:QName("action"), $action, - xs:QName("kind"), $kind, - xs:QName("role-names"), {for $r in $role-names return {$r}}), - {$default-security}) -}; - -declare function setup:create-roles($import-config as element(configuration)) -{ - try { - for $role in setup:get-roles-from-config($import-config) - return - setup:create-role($role/sec:role-name, - $role/sec:description, - $role/sec:role-names/sec:role-name, - $role/sec:permissions/*, - $role/sec:collections/*, - $role/sec:privileges/*, - $role/sec:amps/*) - - } catch ($e) { - xdmp:log(fn:concat("Role creation failed: ", $e//err:format-string)) - } + xdmp:eval( + 'import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; + declare variable $privilege-name as xs:string external; + declare variable $action as xs:string external; + declare variable $kind as xs:string external; + declare variable $role-names as element() external; + sec:create-privilege($privilege-name, $action, $kind, $role-names/*)', + (xs:QName("privilege-name"), $privilege-name, + xs:QName("action"), $action, + xs:QName("kind"), $kind, + xs:QName("role-names"), {for $r in $role-names return {$r}}), + + {$default-security} + ), + setup:add-rollback("privileges", $privilege) + ) +}; + +declare function setup:validate-privileges( + $import-config as element(configuration)) +{ + for $privilege in $import-config/sec:privileges/sec:privilege + let $privilege-name as xs:string := $privilege/sec:privilege-name + let $action as xs:string? := $privilege/sec:action + let $kind as xs:string := $privilege/sec:kind + let $match := setup:get-privileges()/sec:privilege[sec:privilege-name = $privilege-name] + return + if ($match) then + if ($match/sec:action != $action or $match/sec:kind != $kind) then + setup:validation-fail( + fn:concat( + "Privilege mismatch: name=", + $privilege-name, + "; action=", $action, "; kind=", + $kind)) + else () (: It's a match. :) + else + setup:validation-fail(fn:concat("Missing privilege: ", $privilege-name)) }; -declare function setup:create-role($role-name as xs:string, - $description as xs:string?, - $role-names as xs:string*, - $permissions as element(sec:permission)*, - $collections as xs:string*, - $privileges as element(sec:privilege)*, - $amps as element(sec:amp)*) +declare function setup:create-roles( + $import-config as element(configuration)) { - try { + for $role in $import-config//sec:roles/sec:role + let $role-name as xs:string := $role/sec:role-name + let $description as xs:string? := $role/sec:description + let $role-names as xs:string* := $role/sec:role-names/sec:role-name + let $permissions as element(sec:permission)* := $role/sec:permissions/* + let $collections as xs:string* := $role/sec:collections/* + let $privileges as element(sec:privilege)* := $role/sec:privileges/* + let $amps as element(sec:amp)* := $role/sec:amps/* + let $eval-options := + + {$default-security} + + return + (: if the role exists, then update it :) if (setup:get-roles(())/sec:role[sec:role-name = $role-name]) then ( - xdmp:eval('import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; - declare variable $role-name as xs:string external; - declare variable $description as xs:string external; - sec:role-set-description($role-name, $description)', - (xs:QName("role-name"), $role-name, - xs:QName("description"), fn:string($description)), - {$default-security}), + xdmp:eval( + 'import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; + declare variable $role-name as xs:string external; + declare variable $description as xs:string external; + sec:role-set-description($role-name, $description)', + (xs:QName("role-name"), $role-name, + xs:QName("description"), fn:string($description)), + $eval-options), if ($role-names) then - xdmp:eval('import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; - declare variable $role-name as xs:string external; - declare variable $role-names as element() external; - sec:role-set-roles($role-name, $role-names/*)', - (xs:QName("role-name"), $role-name, - xs:QName("role-names"), {for $r in $role-names return {$r}}), - {$default-security}) + xdmp:eval( + 'import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; + declare variable $role-name as xs:string external; + declare variable $role-names as element() external; + sec:role-set-roles($role-name, $role-names/*)', + (xs:QName("role-name"), $role-name, + xs:QName("role-names"), {for $r in $role-names return {$r}}), + $eval-options) else (), if ($permissions) then - xdmp:eval('import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; - declare variable $role-name as xs:string external; - declare variable $permissions as element() external; - sec:role-set-default-permissions($role-name, $permissions/*)', - (xs:QName("role-name"), $role-name, - xs:QName("permissions"), {for $p in $permissions return xdmp:permission($p/sec:role-name, $p/sec:capability)}), - {$default-security}) + xdmp:eval( + 'import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; + declare variable $role-name as xs:string external; + declare variable $permissions as element() external; + sec:role-set-default-permissions($role-name, $permissions/*)', + ( + xs:QName("role-name"), $role-name, + xs:QName("permissions"), + + { + for $p in $permissions + return + xdmp:permission($p/sec:role-name, $p/sec:capability) + } + + ), + $eval-options) else (), if ($collections) then - xdmp:eval('import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; - declare variable $role-name as xs:string external; - declare variable $collections as element() external; - sec:role-set-default-collections($role-name, $collections/*)', - (xs:QName("role-name"), $role-name, - xs:QName("collections"), {for $c in $collections return {$c}}), - {$default-security}) + xdmp:eval( + 'import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; + declare variable $role-name as xs:string external; + declare variable $collections as element() external; + sec:role-set-default-collections($role-name, $collections/*)', + (xs:QName("role-name"), $role-name, + xs:QName("collections"), {for $c in $collections return {$c}}), + $eval-options) else (), for $privilege in $privileges let $priv := setup:get-privilege-by-name($privilege/sec:privilege-name) return - xdmp:eval('import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; - declare variable $action as xs:string external; - declare variable $kind as xs:string external; - declare variable $role-name as xs:string external; - sec:privilege-add-roles($action, $kind, $role-name)', - (xs:QName("action"), $priv/sec:action, - xs:QName("kind"), $priv/sec:kind, - xs:QName("role-name"), $role-name), - {$default-security}), + xdmp:eval( + 'import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; + declare variable $action as xs:string external; + declare variable $kind as xs:string external; + declare variable $role-name as xs:string external; + sec:privilege-add-roles($action, $kind, $role-name)', + (xs:QName("action"), $priv/sec:action, + xs:QName("kind"), $priv/sec:kind, + xs:QName("role-name"), $role-name), + $eval-options), for $amp in $amps return - xdmp:eval('import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; - declare variable $namespace as xs:string external; - declare variable $local-name as xs:string external; - declare variable $document-uri as xs:string external; - declare variable $database as xs:unsignedLong external; - declare variable $role-name as xs:string external; - sec:amp-add-roles($namespace, $local-name, $document-uri, $database, $role-name)', - (xs:QName("namespace"), $amp/sec:namespace, - xs:QName("local-name"), $amp/sec:local-name, - xs:QName("document-uri"), $amp/sec:document-uri, - xs:QName("database"), if ($amp/sec:database-name eq "filesystem") then 0 else xdmp:database($amp/sec:database-name), - xs:QName("role-name"), $role-name), - {$default-security}) + xdmp:eval( + 'import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; + declare variable $namespace as xs:string external; + declare variable $local-name as xs:string external; + declare variable $document-uri as xs:string external; + declare variable $database as xs:unsignedLong external; + declare variable $role-name as xs:string external; + sec:amp-add-roles($namespace, $local-name, $document-uri, $database, $role-name)', + (xs:QName("namespace"), $amp/sec:namespace, + xs:QName("local-name"), $amp/sec:local-name, + xs:QName("document-uri"), $amp/sec:document-uri, + xs:QName("database"), if ($amp/sec:database-name eq "filesystem") then 0 else xdmp:database($amp/sec:database-name), + xs:QName("role-name"), $role-name), + $eval-options) ) + (: role is new. create it :) else ( - xdmp:log(text {"creating role:", $role-name}), - xdmp:eval('import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; - declare variable $role-name as xs:string external; - declare variable $description as xs:string external; - declare variable $collections as element() external; - sec:create-role($role-name, $description, (), (), $collections/*)', - (xs:QName("role-name"), $role-name, - xs:QName("description"), fn:string($description), - xs:QName("collections"), {for $c in $collections return {$c}}), - {$default-security}) -(: - if ($permissions) then - xdmp:eval('import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; - declare variable $role-name as xs:string external; - declare variable $permissions as element() external; - sec:role-set-default-permissions($role-name, for $p in $permissions/* return xdmp:permission($p/sec:role-name, $p/sec:capability))', - (xs:QName("role-name"), $role-name, - xs:QName("permissions"), {$permissions}), - {$default-security}) - else (), - - if ($role-names) then - xdmp:eval('import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; - declare variable $role-name as xs:string external; - declare variable $role-names as element() external; - sec:role-set-roles($role-name, $role-names/*)', - (xs:QName("role-name"), $role-name, - xs:QName("role-names"), {for $r in $role-names return {$r}}), - {$default-security}) - else () -:) + xdmp:eval( + 'import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; + declare variable $role-name as xs:string external; + declare variable $description as xs:string external; + declare variable $collections as element() external; + sec:create-role($role-name, $description, (), (), $collections/*)', + (xs:QName("role-name"), $role-name, + xs:QName("description"), fn:string($description), + xs:QName("collections"), {for $c in $collections return {$c}}), + $eval-options), + setup:add-rollback("roles", $role) ) - } - catch ($e) { - xdmp:log($e), - fn:concat("Role ", $role-name, " creation failed: ", $e//err:format-string) - } }; -declare function setup:create-users($import-config as element(configuration)) +declare function setup:validate-roles( + $import-config as element(configuration)) { - try { - for $user in setup:get-users-from-config($import-config) - return - setup:create-user($user/sec:user-name, - $user/sec:description, - $user/sec:password, - $user/sec:role-names/*, - $user/sec:permissions/*, - $user/sec:collections/*) - - } catch ($e) { - xdmp:log($e), - fn:concat("User creation failed: ", $e//err:format-string) - } + for $role in $import-config//sec:roles/sec:role + let $role-name as xs:string := $role/sec:role-name + let $description as xs:string? := $role/sec:description + let $role-names as xs:string* := $role/sec:role-names/sec:role-name + let $permissions as element(sec:permission)* := $role/sec:permissions/* + let $collections as xs:string* := $role/sec:collections/* + let $privileges as element(sec:privilege)* := $role/sec:privileges/* + let $amps as element(sec:amp)* := $role/sec:amps/* + let $match := setup:get-roles(())/sec:role[sec:role-name = $role-name] + return + (: if the role exists, then update it :) + if ($match) then + if ($match/sec:role-name != $role-name or + $match/sec:description != $description or + $match/sec:role-names/sec:role-name != $role-names) then + setup:validation-fail(fn:concat("Mismatched role: ", $role-name)) + else () + else + setup:validation-fail(fn:concat("Missing role: ", $role-name)) }; -declare function setup:create-user($user-name as xs:string, - $description as xs:string?, - $password as xs:string, - $role-names as xs:string*, - $permissions as element(sec:permission)*, - $collections as xs:string* ) +declare function setup:create-users($import-config as element(configuration)) { - try { + for $user in $import-config//sec:users/sec:user + let $user-name as xs:string := $user/sec:user-name + let $description as xs:string? := $user/sec:description + let $password as xs:string := $user/sec:password + let $role-names as xs:string* := $user/sec:role-names/* + let $permissions as element(sec:permission)* := $user/sec:permissions/* + let $collections as xs:string* := $user/sec:collections/* + let $eval-options := + + {$default-security} + + return if (setup:get-users(())/sec:user[sec:user-name = $user-name]) then ( - xdmp:eval('import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; - declare variable $user-name as xs:string external; - declare variable $description as xs:string external; - sec:user-set-description($user-name, $description)', - (xs:QName("user-name"), $user-name, - xs:QName("description"), fn:string($description)), - {$default-security}), - - xdmp:eval('import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; - declare variable $user-name as xs:string external; - declare variable $password as xs:string external; - sec:user-set-password($user-name, $password)', - (xs:QName("user-name"), $user-name, - xs:QName("password"), fn:string($password)), - {$default-security}), + xdmp:eval( + 'import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; + declare variable $user-name as xs:string external; + declare variable $description as xs:string external; + sec:user-set-description($user-name, $description)', + (xs:QName("user-name"), $user-name, + xs:QName("description"), fn:string($description)), + $eval-options), + + xdmp:eval( + 'import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; + declare variable $user-name as xs:string external; + declare variable $password as xs:string external; + sec:user-set-password($user-name, $password)', + (xs:QName("user-name"), $user-name, + xs:QName("password"), fn:string($password)), + $eval-options), if ($role-names) then - xdmp:eval('import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; - declare variable $user-name as xs:string external; - declare variable $role-names as element() external; - sec:user-set-roles($user-name, $role-names/*)', - (xs:QName("user-name"), $user-name, - xs:QName("role-names"), {for $r in $role-names return {$r}}), - {$default-security}) + xdmp:eval( + 'import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; + declare variable $user-name as xs:string external; + declare variable $role-names as element() external; + sec:user-set-roles($user-name, $role-names/*)', + (xs:QName("user-name"), $user-name, + xs:QName("role-names"), {for $r in $role-names return {$r}}), + $eval-options) else (), if ($permissions) then - xdmp:eval('import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; - declare variable $user-name as xs:string external; - declare variable $permissions as element() external; - sec:user-set-default-permissions($user-name, $permissions/*)', - (xs:QName("user-name"), $user-name, - xs:QName("permissions"), {for $p in $permissions return xdmp:permission($p/sec:role-name, $p/sec:capability)}), - {$default-security}) + xdmp:eval( + 'import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; + declare variable $user-name as xs:string external; + declare variable $permissions as element() external; + sec:user-set-default-permissions($user-name, $permissions/*)', + (xs:QName("user-name"), $user-name, + xs:QName("permissions"), {for $p in $permissions return xdmp:permission($p/sec:role-name, $p/sec:capability)}), + $eval-options) else (), if ($collections) then - xdmp:eval('import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; - declare variable $user-name as xs:string external; - declare variable $collections as element() external; - sec:user-set-default-collections($user-name, $collections/*)', - (xs:QName("user-name"), $user-name, - xs:QName("collections"), {for $c in $collections return {$c}}), - {$default-security}) + xdmp:eval( + 'import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; + declare variable $user-name as xs:string external; + declare variable $collections as element() external; + sec:user-set-default-collections($user-name, $collections/*)', + (xs:QName("user-name"), $user-name, + xs:QName("collections"), {for $c in $collections return {$c}}), + $eval-options) else () ) else ( - xdmp:eval('import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; - declare variable $user-name as xs:string external; - declare variable $description as xs:string external; - declare variable $password as xs:string external; - declare variable $role-names as element() external; - declare variable $permissions as element() external; - declare variable $collections as element() external; - sec:create-user($user-name, $description, $password, $role-names/*, $permissions/*, $collections/*)', - (xs:QName("user-name"), $user-name, - xs:QName("description"), fn:string($description), - xs:QName("password"), $password, - xs:QName("role-names"), {for $r in $role-names return {$r}}, - xs:QName("permissions"), , - xs:QName("collections"), {for $c in $collections return {$c}}), - {$default-security}), + xdmp:eval( + 'import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; + declare variable $user-name as xs:string external; + declare variable $description as xs:string external; + declare variable $password as xs:string external; + declare variable $role-names as element() external; + declare variable $permissions as element() external; + declare variable $collections as element() external; + sec:create-user($user-name, $description, $password, $role-names/*, $permissions/*, $collections/*)', + (xs:QName("user-name"), $user-name, + xs:QName("description"), fn:string($description), + xs:QName("password"), $password, + xs:QName("role-names"), {for $r in $role-names return {$r}}, + xs:QName("permissions"), , + xs:QName("collections"), {for $c in $collections return {$c}}), + $eval-options), + setup:add-rollback("users", $user), if ($permissions) then - xdmp:eval('import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; - declare variable $user-name as xs:string external; - declare variable $permissions as element() external; - sec:user-set-default-permissions($user-name, for $p in $permissions/* return xdmp:permission($p/sec:role-name, $p/sec:capability))', - (xs:QName("user-name"), $user-name, - xs:QName("permissions"), {$permissions}), - {$default-security}) + xdmp:eval( + 'import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; + declare variable $user-name as xs:string external; + declare variable $permissions as element() external; + sec:user-set-default-permissions($user-name, for $p in $permissions/* return xdmp:permission($p/sec:role-name, $p/sec:capability))', + (xs:QName("user-name"), $user-name, + xs:QName("permissions"), {$permissions}), + $eval-options) else () ) - } - catch ($e) { - fn:concat("User ", $user-name, " creation failed: ", $e//err:format-string) - } +}; + +declare function setup:validate-users($import-config as element(configuration)) +{ + for $user in $import-config//sec:users/sec:user + let $user-name as xs:string := $user/sec:user-name + let $description as xs:string? := $user/sec:description + let $password as xs:string := $user/sec:password + let $role-names as xs:string* := $user/sec:role-names/* + let $permissions as element(sec:permission)* := $user/sec:permissions/* + let $collections as xs:string* := $user/sec:collections/* + let $match := setup:get-users(())/sec:user[sec:user-name = $user-name] + return + if ($match) then + if ($match/sec:description != $description or + $match/sec:role-names/* != $role-names) then + setup:validation-fail(fn:concat("User mismatch: ", $user-name)) + else () + else + setup:validation-fail(fn:concat("Missing user: ", $user-name)) }; (:::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -2182,9 +3227,15 @@ declare function setup:create-user($user-name as xs:string, ::) declare function setup:create-amps($import-config) { + let $existing-amps := setup:get-amps(()) for $amp in $import-config/sec:amps/sec:amp return - try { + if ($existing-amps/sec:amp[sec:namespace = $amp/sec:namespace and + sec:local-name = $amp/sec:local-name and + sec:document-uri = $amp/sec:doc-uri and + sec:db-name = $amp/sec:db-name]) then () + else + ( xdmp:eval( 'import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; declare variable $amp external; @@ -2199,21 +3250,34 @@ declare function setup:create-amps($import-config) {xdmp:security-database()} - ) - } catch ($e) { - fn:concat("Failed to create amp: ", $e//err:format-string) - } + ), + setup:add-rollback("amps", $amp) + ) +}; + +declare function setup:validate-amps($import-config) +{ + let $existing-amps := setup:get-amps(()) + for $amp in $import-config/sec:amps/sec:amp + return + if ($existing-amps/sec:amp[sec:namespace = $amp/sec:namespace and + sec:local-name = $amp/sec:local-name and + sec:document-uri = $amp/sec:doc-uri and + sec:db-name = $amp/sec:db-name]) then () + else + setup:validation-fail(fn:concat("Missing amp: ", $amp/sec:local-name)) }; (:::::::::::::::::::::::::::::::::::::::::::::::::::::::::: :: Export configuration to XML ::) -declare function setup:get-configuration($databases as xs:string*, - $forests as xs:string*, - $app-servers as xs:string*, - $user-ids as xs:unsignedLong*, - $role-ids as xs:unsignedLong*, - $mimetypes as xs:string*) as element() +declare function setup:get-configuration( + $databases as xs:string*, + $forests as xs:string*, + $app-servers as xs:string*, + $user-ids as xs:unsignedLong*, + $role-ids as xs:unsignedLong*, + $mimetypes as xs:string*) as element() { {setup:get-app-servers($app-servers)} @@ -2244,8 +3308,8 @@ declare function setup:get-app-servers($names as xs:string*) as element()* ) ) } - - , + , + let $xdbc-servers := $groups/gr:xdbc-servers/gr:xdbc-server[gr:xdbc-server-name = $names] where $xdbc-servers return @@ -2306,28 +3370,37 @@ declare function setup:get-databases($names as xs:string*) as element(db:databas }; declare function setup:get-role-name($id as xs:unsignedLong) as xs:string? { - xdmp:eval('import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; - declare variable $id as xs:unsignedLong external; - sec:get-role-names($id)', - (xs:QName("id"), $id), - {$default-security}) + xdmp:eval( + 'import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; + declare variable $id as xs:unsignedLong external; + sec:get-role-names($id)', + (xs:QName("id"), $id), + + {$default-security} + ) }; declare function setup:get-role-privileges($role as element(sec:role)) as element(sec:privilege)* { - xdmp:eval('import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; - declare variable $role-name as xs:string external; - sec:role-privileges($role-name)', - (xs:QName("role-name"), fn:string($role/sec:role-name)), - {$default-security})[sec:role-ids/sec:role-id = $role/sec:role-id] + xdmp:eval( + 'import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; + declare variable $role-name as xs:string external; + sec:role-privileges($role-name)', + (xs:QName("role-name"), fn:string($role/sec:role-name)), + + {$default-security} + )[sec:role-ids/sec:role-id = $role/sec:role-id] }; declare function setup:get-privileges() as element(sec:privileges)? { { - xdmp:eval('import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; - /sec:privilege', - (), - {$default-security}) + xdmp:eval( + 'import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; + /sec:privilege', + (), + + {$default-security} + ) } }; @@ -2338,24 +3411,33 @@ declare function setup:get-privilege-by-name($name as xs:string) as element(sec: declare variable $name external; /sec:privilege[sec:privilege-name = $name]', (xs:QName("name"), $name), - {$default-security}) + + {$default-security} + ) }; declare function setup:get-users($ids as xs:unsignedLong*) as element(sec:users)? { - let $users := xdmp:eval('import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; - /sec:user', - (), - {$default-security}) let $users := - if ($ids) then $users[sec:user-id = $ids] - else $users + xdmp:eval( + 'import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; + /sec:user', + (), + + {$default-security} + ) + let $users := + if ($ids) then + $users[sec:user-id = $ids] + else + $users where $users return { for $user in $users return - element sec:user { + element sec:user + { $user/@*, $user/*[fn:not(self::sec:user-id) and fn:not(self::sec:digest-password) and @@ -2365,29 +3447,37 @@ declare function setup:get-users($ids as xs:unsignedLong*) as element(sec:users) element sec:password {()}, - element sec:role-names { + element sec:role-names + { for $id in $user/sec:role-ids/* return element sec:role-name {setup:get-role-name($id)} }, - element sec:permissions { + element sec:permissions + { for $perm in $user/sec:permissions/sec:permission return - element sec:permission { + element sec:permission + { $perm/sec:capability, element sec:role-name {setup:get-role-name($perm/sec:role-id)} } } } - } + } + }; declare function setup:get-roles($ids as xs:unsignedLong*) as element(sec:roles)? { - let $roles := xdmp:eval('import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; - /sec:role', - (), - {$default-security}) + let $roles := + xdmp:eval( + 'import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; + /sec:role', + (), + + {$default-security} + ) let $roles := if ($ids) then $roles[sec:role-id = $ids] else $roles @@ -2397,21 +3487,25 @@ declare function setup:get-roles($ids as xs:unsignedLong*) as element(sec:roles) { for $role in $roles return - element sec:role { + element sec:role + { $role/@*, $role/*[fn:not(self::sec:role-id) and fn:not(self::sec:role-ids) and fn:not(self::sec:permissions)], - element sec:role-names { + element sec:role-names + { for $id in $role/sec:role-ids/* return element sec:role-name {setup:get-role-name($id)} }, - element sec:permissions { + element sec:permissions + { for $perm in $role/sec:permissions/sec:permission return - element sec:permission { + element sec:permission + { $perm/sec:capability, element sec:role-name {setup:get-role-name($perm/sec:role-id)} } @@ -2430,7 +3524,49 @@ declare function setup:get-roles($ids as xs:unsignedLong*) as element(sec:roles) } }; -declare function setup:get-mimetypes($names as xs:string*) as element(mt:mimetypes)? { +declare function setup:get-amps($ids as xs:unsignedLong*) as element(sec:amps)? { + let $amps := + xdmp:eval( + 'import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; + /sec:amp', + (), + + {$default-security} + ) + let $amps := + if ($ids) then $amps[sec:amp-id = $ids] + else $amps + where $amps + return + + { + for $amp in $amps + return + element sec:amp + { + $amp/@*, + $amp/*[fn:not(self::sec:amp-id) and + fn:not(self::sec:role-ids) and + fn:not(self::sec:database)], + element sec:role-names + { + for $id in $amp/sec:role-ids/* + return + element sec:role-name {setup:get-role-name($id)} + }, + + element sec:db-name + { + if ($amp/sec:database = 0) then "filesystem" + else + xdmp:database-name($amp/sec:database) + } + } + } +}; + +declare function setup:get-mimetypes($names as xs:string*) as element(mt:mimetypes)? +{ let $mimes := setup:read-config-file("mimetypes.xml")/mt:mimetypes let $mimes := $mimes/mt:mimetype[mt:name = $names] where $mimes @@ -2446,39 +3582,48 @@ declare function setup:get-mimetypes($names as xs:string*) as element(mt:mimetyp :: Resolving IDs to names ::) -declare function setup:resolve-database-id-to-name($node as element()) as element()? { +declare function setup:resolve-database-id-to-name($node as element()) as element()? +{ if (fn:data($node) ne 0) then - element {fn:node-name($node)} { + element {fn:node-name($node)} + { attribute {xs:QName("name")} { xdmp:database-name(fn:data($node)) } } else () }; -declare function setup:resolve-forest-id-to-name($node as element()) as element()? { +declare function setup:resolve-forest-id-to-name($node as element()) as element()? +{ if (fn:data($node) ne 0) then - element {fn:node-name($node)} { + element {fn:node-name($node)} + { attribute {xs:QName("name")} { xdmp:forest-name(fn:data($node)) } } else () }; -declare function setup:resolve-host-id-to-name($node as element()) as element()? { +declare function setup:resolve-host-id-to-name($node as element()) as element()? +{ if (fn:data($node) ne 0) then - element {fn:node-name($node)} { + element {fn:node-name($node)} + { attribute {xs:QName("name")} { xdmp:host-name(fn:data($node)) } } else () }; -declare function setup:resolve-user-id-to-name($node as element()) as element()? { +declare function setup:resolve-user-id-to-name($node as element()) as element()? +{ if (fn:data($node) ne 0) then - element {fn:node-name($node)} { + element {fn:node-name($node)} + { attribute {xs:QName("name")} { setup:user-name(fn:data($node)) } } else () }; -declare function setup:resolve-ids-to-names($nodes as item()*) as item()* { +declare function setup:resolve-ids-to-names($nodes as item()*) as item()* +{ for $node in $nodes return typeswitch ($node) @@ -2516,14 +3661,16 @@ declare function setup:resolve-ids-to-names($nodes as item()*) as item()* { (: Default :) case element() return if ($node/node()) then - element {fn:node-name($node)} { + element {fn:node-name($node)} + { $node/@*, setup:resolve-ids-to-names($node/node()) } else () case document-node() return - document { + document + { setup:resolve-ids-to-names($node/node()) } @@ -2535,111 +3682,92 @@ declare function setup:resolve-ids-to-names($nodes as item()*) as item()* { :: Stripping default properties ::) -declare function setup:default-http-server() as element(gr:http-server) +declare function setup:strip-default-properties-from-http-server( + $node as element(gr:http-server)) as element(gr:http-server) { - (: Just pretend to create, do not save! :) - let $admin-config := admin:get-configuration() - let $admin-config := - admin:http-server-create($admin-config, $default-group, "default", "/", 19999, $default-modules, $default-database) - return - $admin-config//gr:http-servers/gr:http-server[gr:http-server-name eq "default"] -}; - -declare function setup:strip-default-properties-from-http-server($node as element(gr:http-server)) as element(gr:http-server) { - element { fn:node-name($node) } { + element { fn:node-name($node) } + { $node/@*, let $default-properties := - setup:default-http-server()/* - + admin:http-server-create( + admin:get-configuration(), + $default-group, + "default", + "/", + 19999, + $default-modules, + $default-database)//gr:http-servers/gr:http-server[gr:http-server-name eq "default"]/* for $property in $node/* - where - fn:not($default-properties[fn:deep-equal(., $property)]) - and - fn:not(xs:boolean($node/gr:webDAV) and $property/self::gr:compute-content-length) - and - fn:not($property/self::gr:http-server-id) + where fn:not($default-properties[fn:deep-equal(., $property)]) and + fn:not(xs:boolean($node/gr:webDAV) and $property/self::gr:compute-content-length) and + fn:not($property/self::gr:http-server-id) return $property } }; -declare function setup:default-xdbc-server() as element(gr:xdbc-server) +declare function setup:strip-default-properties-from-xdbc-server( + $node as element(gr:xdbc-server)) as element(gr:xdbc-server) { - (: Just pretend to create, do not save! :) - let $admin-config := admin:get-configuration() - let $admin-config := - admin:xdbc-server-create($admin-config, $default-group, "default", "/", 19999, $default-modules, $default-database) - return - $admin-config//gr:xdbc-servers/gr:xdbc-server[gr:xdbc-server-name eq "default"] -}; - -declare function setup:strip-default-properties-from-xdbc-server($node as element(gr:xdbc-server)) as element(gr:xdbc-server) { - element { fn:node-name($node) } { + element { fn:node-name($node) } + { $node/@*, let $default-properties := - setup:default-xdbc-server()/* - + admin:xdbc-server-create( + admin:get-configuration(), + $default-group, + "default", + "/", + 19999, + $default-modules, + $default-database)//gr:xdbc-servers/gr:xdbc-server[gr:xdbc-server-name eq "default"]/* for $property in $node/* - where - fn:not($default-properties[fn:deep-equal(., $property)]) - and - fn:not($property/self::gr:xdbc-server-id) + where fn:not($default-properties[fn:deep-equal(., $property)]) and + fn:not($property/self::gr:xdbc-server-id) return $property } }; -declare function setup:default-database() as element(db:database) +declare function setup:strip-default-properties-from-database( + $node as element(db:database)) as element(db:database) { - (: Just pretend to create, do not save! :) - let $admin-config := admin:get-configuration() - let $admin-config := - admin:database-create($admin-config, "default", $default-security, $default-schemas) - return - $admin-config//db:databases/db:database[db:database-name eq "default"] -}; - -declare function setup:strip-default-properties-from-database($node as element(db:database)) as element(db:database) { - element { fn:node-name($node) } { + element { fn:node-name($node) } + { $node/@*, let $default-properties := - setup:default-database()/* - + admin:database-create( + admin:get-configuration(), + "default", + $default-security, + $default-schemas)/db:databases/db:database[db:database-name eq "default"]/* for $property in $node/* - where - fn:not($default-properties[fn:deep-equal(., $property)]) - and - fn:not($property/self::db:database-id) + where fn:not($default-properties[fn:deep-equal(., $property)]) and + fn:not($property/self::db:database-id) return $property } }; -declare function setup:default-forest() as element(as:assignment) +declare function setup:strip-default-properties-from-forest( + $node as element(as:assignment)) as element(as:assignment) { - (: Just pretend to create, do not save! :) - let $admin-config := admin:get-configuration() - let $admin-config := - admin:forest-create($admin-config, "default", $default-host, ()) - return - $admin-config//as:assignments/as:assignment[as:forest-name eq "default"] -}; - -declare function setup:strip-default-properties-from-forest($node as element(as:assignment)) as element(as:assignment) { - element { fn:node-name($node) } { + element { fn:node-name($node) } + { $node/@*, let $default-properties := - setup:default-forest()/* - + admin:forest-create( + admin:get-configuration(), + "default", + $default-host, + ())//as:assignments/as:assignment[as:forest-name eq "default"]/* for $property in $node/* - where - fn:not($default-properties[fn:deep-equal(., $property)]) - and - fn:not($property/self::as:forest-id) + where fn:not($default-properties[fn:deep-equal(., $property)]) and + fn:not($property/self::as:forest-id) return $property } @@ -2649,41 +3777,14 @@ declare function setup:strip-default-properties-from-forest($node as element(as: :: Accessing import-config ::) -declare function setup:get-mimetypes-from-config($import-config as element(configuration)) as element(mt:mimetype)* -{ - $import-config//mt:mimetypes/mt:mimetype -}; - -declare function setup:get-forests-from-config($import-config as element(configuration)) as element(as:assignment)* -{ - $import-config//as:assignments/as:assignment -}; - -declare function setup:get-forest-name-from-forest-config($forest-config as element(as:assignment)) as xs:string? -{ - fn:data( - $forest-config/as:forest-name[fn:string-length(.) > 0] - ) -}; - -declare function setup:get-data-directory-from-forest-config($forest-config as element(as:assignment)) as xs:string? -{ - fn:data( - $forest-config/as:data-directory[fn:string-length(.) > 0] - ) -}; - -declare function setup:get-hostname-from-forest-config($forest-config as element(as:assignment)) as xs:string? -{ - fn:string($forest-config/as:host)[fn:string-length(.) > 0] -}; - -declare function setup:get-databases-from-config($import-config as element(configuration)) as element(db:database)* +declare function setup:get-databases-from-config( + $import-config as element(configuration)) as element(db:database)* { - for $db in $import-config//db:databases/db:database + for $db in $import-config/db:databases/db:database return if (fn:exists($db/@import)) then - element db:database { + element db:database + { $db/*, let $ignore := $db/*/fn:node-name(.) return @@ -2693,123 +3794,23 @@ declare function setup:get-databases-from-config($import-config as element(confi $db }; -declare function setup:get-database-name-from-database-config($database-config as element(db:database)) as xs:string? -{ - fn:data( - $database-config/db:database-name[fn:string-length(.) > 0] - ) -}; - -declare function setup:get-forest-refs-from-database-config($database-config as element(db:database)) as element(db:forest-id)* -{ - $database-config/db:forests/db:forest-id -}; - -declare function setup:get-forest-from-config($import-config as element(configuration), $forest-ref as element(db:forest-id)) as element(as:assignment)? +declare function setup:get-database-name-from-database-config( + $db-config as element(db:database)) as xs:string? { - $import-config//as:assignment[as:forest-id eq $forest-ref] + $db-config/db:database-name[fn:string-length(.) > 0] }; -declare function setup:get-forests-per-host-from-database-config($database-config as element(db:database)) as xs:positiveInteger? +declare function setup:get-forests-per-host-from-database-config( + $db-config as element(db:database)) as xs:positiveInteger? { - let $forests-per-host := fn:data($database-config/db:forests-per-host) + let $forests-per-host := fn:data($db-config/db:forests-per-host) return - if (fn:string-length($forests-per-host) > 0) then xs:positiveInteger($forests-per-host) - else xs:positiveInteger("1") (: Default forests per host is 1 :) -}; - -declare function setup:get-http-servers-from-config($import-config as element(configuration)) as element(gr:http-server)* -{ - $import-config//gr:http-servers/gr:http-server -}; - -declare function setup:get-server-name-from-http-config($server-config as element(gr:http-server)) as xs:string? -{ - fn:data( - $server-config/gr:http-server-name[fn:string-length(.) > 0] - ) -}; - -declare function setup:get-xdbc-servers-from-config($import-config as element(configuration)) as element(gr:xdbc-server)* -{ - $import-config//gr:xdbc-servers/gr:xdbc-server -}; - -declare function setup:get-task-servers-from-config($import-config as element(configuration)) as element(gr:task-server)* -{ - $import-config//gr:task-server -}; - -declare function setup:get-server-name-from-xdbc-config($server-config as element(gr:xdbc-server)) as xs:string? -{ - fn:data( - $server-config/gr:xdbc-server-name[fn:string-length(.) > 0] - ) -}; - -declare function setup:get-servers-from-config($import-config as element(configuration)) as item()* -{ - for $server in - $import-config//gr:http-servers/gr:http-server - return ( - $server/gr:http-server-name, - - if (xs:boolean($server/gr:webDAV)) then - "WebDAV" + if (fn:string-length($forests-per-host) > 0) then + xs:positiveInteger($forests-per-host) else - "HTTP", - - fn:data($server/gr:port) - ), - - for $server in - $import-config//gr:xdbc-servers/gr:xdbc-server - return ( - $server/gr:xdbc-server-name, - - "XDBC", - - fn:data($server/gr:port) - ) -}; - -declare function setup:get-setting-from-database-config($database-config as element(db:database), $setting-name as xs:string) as element()? -{ - xdmp:value(fn:concat("$database-config/*:", $setting-name)) -}; - -declare function setup:get-setting-from-database-config-as-string($database-config as element(db:database), $setting-name as xs:string) as xs:string? -{ - let $setting := setup:get-setting-from-database-config($database-config, $setting-name) - where $setting - return - fn:string($setting) -}; - -declare function setup:get-setting-from-database-config-as-boolean($database-config as element(db:database), $setting-name as xs:string) as xs:boolean? -{ - let $str := setup:get-setting-from-database-config-as-string($database-config, $setting-name) - where $str - return - setup:to-boolean($str) -}; - -declare function setup:get-privileges-from-config($import-config as element(configuration)) as element(sec:privilege)* -{ - $import-config/sec:privileges/sec:privilege -}; - -declare function setup:get-roles-from-config($import-config as element(configuration)) as element(sec:role)* -{ - $import-config//sec:roles/sec:role -}; - -declare function setup:get-users-from-config($import-config as element(configuration)) as element(sec:user)* -{ - $import-config//sec:users/sec:user + xs:positiveInteger("1") (: Default forests per host is 1 :) }; - (:::::::::::::::::::::::::::::::::::::::::::::::::::::::::: :: Utility functions ::) @@ -2820,12 +3821,8 @@ declare function setup:read-config-file($filename as xs:string) as document-node xdmp:read-cluster-config-file($filename) }; -declare function setup:to-boolean($value as xs:string) as xs:boolean +declare function setup:user-name($user-id as xs:unsignedLong?) as xs:string { - fn:boolean(fn:lower-case($value) = ("1", "y", "yes", "true")) -}; - -declare function setup:user-name($user-id as xs:unsignedLong?) as xs:string { let $user-id := if ($user-id) then $user-id @@ -2833,16 +3830,15 @@ declare function setup:user-name($user-id as xs:unsignedLong?) as xs:string { fn:data(xdmp:get-request-user()) return xdmp:eval( - ' - xquery version "1.0-ml"; - import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; + 'xquery version "1.0-ml"; + import module namespace sec="http://marklogic.com/xdmp/security" at "/MarkLogic/security.xqy"; - declare variable $user-id as xs:unsignedLong external; - sec:get-user-names($user-id) - ', + declare variable $user-id as xs:unsignedLong external; + sec:get-user-names($user-id)', (xs:QName("user-id"), $user-id), - {$default-security} - ) + + {$default-security} + ) }; declare function display:template($title, $main-content, $left-content) @@ -3130,28 +4126,28 @@ declare function display:template($title, $main-content, $left-content) declare function display:tab-bar($labels, $links, $index) { ( - -
    - { - for $label at $i in $labels - return -
  • - { - element a - { - if ($i = $index) then - attribute class {"active"} - else - (), - attribute href {$links[$i]}, - $label - } - } -
  • - } -
-
, -


 

+ +
    + { + for $label at $i in $labels + return +
  • + { + element a + { + if ($i = $index) then + attribute class {"active"} + else + (), + attribute href {$links[$i]}, + $label + } + } +
  • + } +
+
, +


 

) }; @@ -3165,8 +4161,7 @@ declare function display:dropdown($name, $options, $selected, $disabled) if ($disabled) then attribute disabled {"disabled"} else - () - , + (), for $option in $options return @@ -3301,4 +4296,25 @@ declare function display:export-ui() , display:left-links() ) +}; + +declare function setup:validation-fail($message) +{ + xdmp:log($message), + fn:error(xs:QName("VALIDATION-FAIL"), $message) +}; + +declare function setup:validate-install($import-config as element(configuration)) +{ + setup:validate-privileges($import-config), + setup:validate-roles($import-config), + setup:validate-users($import-config), + setup:validate-mimetypes($import-config), + setup:validate-forests($import-config), + setup:validate-databases($import-config), + setup:validate-attached-forests($import-config), + setup:validate-amps($import-config), + setup:validate-database-settings($import-config), + setup:validate-databases-indexes($import-config), + setup:validate-appservers($import-config) }; \ No newline at end of file diff --git a/deploy/sample/ml-config.sample.xml b/deploy/sample/ml-config.sample.xml index a370b547..a46b4514 100644 --- a/deploy/sample/ml-config.sample.xml +++ b/deploy/sample/ml-config.sample.xml @@ -68,6 +68,20 @@ true manual false + + + + +
+ + + + + + diff --git a/deploy/test/data/ml4-config.xml b/deploy/test/data/ml4-config.xml new file mode 100644 index 00000000..8db5fa72 --- /dev/null +++ b/deploy/test/data/ml4-config.xml @@ -0,0 +1,549 @@ + + + 16 + 16 + 3600 + 600 + 100000 + 1000 + 1000 + 10000 + true + true + true + + + + + + @ml.app-name + @ml.app-port + + + @ml.modules-root + @ml.authentication-method + + @ml.url-rewriter + @ml.error-handler + + @ml.test-appserver + + + @ml.xdbc-server + + + + + @ml.content-db + + @ml.test-content-db-assignment + @ml.test-modules-db-assignment + + @ml.modules-db + + @ml.schemas-assignment + @ml.triggers-assignment + + + + + @ml.test-content-db-xml + + @ml.content-db + @ml.content-forests-per-host + @ml.schemas-mapping + @ml.triggers-mapping + + + + true + true + manual + false + + + http://www.marklogic.com/ns/sample + frag-root + + + http://www.marklogic.com/ns/sample2 + frag-root2 + + + + + http://www.marklogic.com/ns/sample + frag-parents + + + http://www.marklogic.com/ns/sample + frag-parents2 + + + + + + false + + http://marklogic.com/collation/ + http://marklogic.com/collation/it/S2/CU/T00BB/AS + + decompounding + true + true + true + true + true + true + true + true + true + true + + + http://www.marklogic.com/ns/sample + word-query-include + 1.0 + + + + + + http://www.marklogic.com/ns/sample2 + word-query-include2 + 1.0 + + + + + + + + http://www.marklogic.com/ns/sample + word-query-exclude + + + http://www.marklogic.com/ns/sample2 + word-query-exclude2 + + + + + + test + true + + + + http://www.marklogic.com/ns/sample + sample-included-element + 1.0 + + + + + + http://www.marklogic.com/ns/sample2 + sample-included-element2 + 1.0 + + + + + + + + http://www.marklogic.com/ns/sample + sample-excluded-element + + + + + + http://www.marklogic.com/ns/sample2 + sample-excluded-element2 + + + + + + + + test2 + true + + + + http://www.marklogic.com/ns/sample + sample-included-element + 1.0 + + + + + + http://www.marklogic.com/ns/sample2 + sample-included-element2 + 1.0 + + + + + + + + http://www.marklogic.com/ns/sample + sample-excluded-element + + + + + + http://www.marklogic.com/ns/sample2 + sample-excluded-element2 + + + + + + + + + + string + http://marklogic.com/ns/sample + name + http://marklogic.com/collation/codepoint + false + + + string + http://marklogic.com/ns/sample2 + name2 + http://marklogic.com/collation/codepoint + false + + + + + string + http://marklogic.com/ns/sample + person + + name + http://marklogic.com/collation/codepoint + false + + + string + http://marklogic.com/ns/sample2 + person2 + + name2 + http://marklogic.com/collation/codepoint + false + + + + + + + + + + + http://www.marklogic.com/ns/sample + geo-element + wgs84 + point + false + + + http://www.marklogic.com/ns/sample + geo-element2 + wgs84 + point + false + + + + + http://marklogic.com/ns/sample + geo + + lat + + lon + wgs84 + false + + + http://marklogic.com/ns/sample + geo2 + + lat2 + + lon2 + wgs84 + false + + + + + http://marklogic.com/ns/sample + geo + http://marklogic.com/ns/sample + lat + http://marklogic.com/ns/sample + lon + wgs84 + true + + + http://marklogic.com/ns/sample + geo2 + http://marklogic.com/ns/sample + lat2 + http://marklogic.com/ns/sample + lon2 + wgs84 + true + + + + + http://marklogic.com/ns/sample + geo + http://marklogic.com/ns/sample + pos + wgs84 + point + false + + + http://marklogic.com/ns/sample + geo2 + http://marklogic.com/ns/sample + pos2 + wgs84 + point + false + + + + + http://www.marklogic.com/ns/sample + sample-element + http://marklogic.com/collation/ + + + http://www.marklogic.com/ns/sample + sample-element2 + http://marklogic.com/collation/ + + + + + http://www.marklogic.com/ns/sample + sample-element + + sample-attribute + http://marklogic.com/collation/ + + + http://www.marklogic.com/ns/sample2 + sample-element2 + + sample-attribute2 + http://marklogic.com/collation/ + + + + + + @ml.modules-db + + + + off + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + + false + false + false + false + automatic + false + + + @ml.test-modules-db-xml + + @ml.triggers-db-xml + @ml.schemas-db-xml + + + + @ml.app-role + A role for users of the @ml.app-name application + + + + + execute + @ml.app-role + + + update + @ml.app-role + + + insert + @ml.app-role + + + read + @ml.app-role + + + + + + + xdmp:value + + + xdmp:add-response-header + + + xdmp:invoke + + + xdmp:with-namespaces + + + + + @ml.app-role2 + A second role for users of the @ml.app-name application + + + + + execute + @ml.app-role2 + + + update + @ml.app-role2 + + + insert + @ml.app-role2 + + + read + @ml.app-role2 + + + + + + + xdmp:value + + + xdmp:add-response-header + + + xdmp:invoke + + + xdmp:with-namespaces + + + + + + + @ml.app-name-user + A user for the @ml.app-name application + password + + @ml.app-role + + + + + + @ml.app-name-user2 + A second user for the @ml.app-name application + password2 + + @ml.app-role2 + + + + + + + + http://marklogic.com/roxy + sample + /app/models/sample.xqy + @ml.app-modules-db + admin + + + http://marklogic.com/roxy + sample2 + /app/models/sample2.xqy + @ml.app-modules-db + admin + + + + + my-action + http://marklogic.com/custom/privilege/my-action + execute + + + my-action2 + http://marklogic.com/custom/privilege/my-action2 + execute + + + + + application/crazy + crazy stuff + text + + + application/crazy2 + crazy stuff 2 + text + + + \ No newline at end of file diff --git a/deploy/test/data/ml4-properties/build.properties b/deploy/test/data/ml4-properties/build.properties new file mode 100644 index 00000000..522dd4bb --- /dev/null +++ b/deploy/test/data/ml4-properties/build.properties @@ -0,0 +1,80 @@ +################################################################# +# This file contains overrides to values in default.properties +# Make changes here, not in default.properties +################################################################# + +# +# Admin username/password that will exist on the dev/cert/prod servers +# +user=admin +password=admin + +# Your Application's name +app-name=roxy-deployer-tester + +# The root of you modules database or filesystem dir +modules-root=/ + +# +# the location of your marklogic configuration file +# +config.file=${basedir}/deploy/test/data/ml4-config.xml + +# +# Unit Testing +# Leave commented out for no unit testing +# turn these on if you are using the roxy unit tester +# +test-content-db=${app-name}-content-test +test-modules-db=${app-name}-modules-test +test-port=8902 + +# +# Leave commented out for default +# +schemas-db=${app-name}-schemas + +# +# Leave commented out for default +# turn it on if you are using triggers or CPF +# +triggers-db=${app-name}-triggers + +# +# the port that the Docs appserver is running on +# Docs appserver is required for boostrapping +# set this to 8000 for ML 4.x and 8002 for ML 5.x +# you should only override this if your Docs appserver +# is running on a funky port +# +# bootstrap-port= + +# +# The ports used by your application +# +app-port=8900 +xcc-port=8901 + +content-forests-per-host=2 + +# +# The authentication method used for your appserver +# application-level, basic, digest, digestbasic +# +authentication-method=digest + +# +# The user used as the default user in application level authentication. +# Using the admin user as the default user is equivalent to turning security off. +# +# default-user=${app-name}-user + +# +# the uris or IP addresses of your servers +# WARNING: if you are running these scripts on WINDOWS you may need to change localhost to 127.0.0.1 +# There have been reported issues with dns resolution when localhost wasn't in the hosts file. +# +local-server=localhost +#dev-server= +#cert-server= +#prod-server= diff --git a/deploy/test/data/ml4-properties/default.properties b/deploy/test/data/ml4-properties/default.properties new file mode 100644 index 00000000..f54caed0 --- /dev/null +++ b/deploy/test/data/ml4-properties/default.properties @@ -0,0 +1,118 @@ +################################################################# +# This file contains default application configuration options +# Don't mess with this file. Instead, copy it to build.properties +# and mess with that +################################################################# + +# +# the location of your code to load into ML +# +xquery.dir=${basedir}/src + +# +# the location of your unit test code +# +xquery-test.dir=${basedir}/src/test + +# +# the location of your xml data to load into ML +# +data.dir=${basedir}/data + +# +# the location of your marklogic configuration file +# +config.file=${basedir}/deploy/ml-config.xml + +# +# Admin username/password that will exist on the dev/cert/prod servers +# +user=admin +password=admin + +# +# Your Application's name +# +app-name=roxy +modules-root=/ + +# The role that is given permissions and execute privileges +app-role=${app-name}-role + +# +# The names of your databases. Forests are given the same names +# +app-modules-db=${app-name}-modules +content-db=${app-name}-content +modules-db=${app-name}-modules + +# +# Number of forests to create per host in the group for the content-db +# +content-forests-per-host=1 + +# +# A location on disk to store the forest data. "data directory" in the admin ui +# +# forest-data-dir= + +# Leave commented out for no test db +# turn it on if you are using the roxy unit tester +# test-content-db=${app-name}-content-test +# test-modules-db=${app-modules-db} + +# Leave commented out for default +# schemas-db=${app-name}-schemas + +# Leave commented out for default +# turn it on if you are using triggers or CPF +# triggers-db=${app-name}-triggers + +# +# the port that the Docs appserver is running on +# Docs appserver is required for boostrapping +# +bootstrap-port-five=8002 +bootstrap-port-four=8000 + +# +# The ports used by your application +# +app-port=8040 +xcc-port=8041 + +# Leave commented out for no test appserver +# turn it on if you are using the roxy unit tester +# test-port=8042 + +# +# The authentication used for your appserver +# +authentication-method=digest +default-user=${app-name}-user + +# +# The default values point to Roxy file +# +url-rewriter=/roxy/rewrite.xqy +error-handler=/roxy/error.xqy + +environments=local,dev,prod + +# +# The Major version of ML server across your environments (4 or 5). You can override +# this value in build.properties if all of your servers are the same version +# or override it in each ${env}.properties file if each server has a different +# version. +# +server-version=5 + +# +# the uris or IP addresses of your servers +# WARNING: if you are running these scripts on windows you may need to change localhost to 127.0.0.1 +# There have been reported issues with dns resolution when localhost wasn't in the hosts file. +# +local-server=localhost +#dev-server= +#cert-server= +#prod-server= diff --git a/deploy/test/data/ml5-config.xml b/deploy/test/data/ml5-config.xml new file mode 100644 index 00000000..6ed3120d --- /dev/null +++ b/deploy/test/data/ml5-config.xml @@ -0,0 +1,569 @@ + + + 16 + 16 + 3600 + 600 + 100000 + 1000 + 1000 + 10000 + true + true + true + + + + + + @ml.app-name + @ml.app-port + + + @ml.modules-root + @ml.authentication-method + + @ml.url-rewriter + @ml.error-handler + + @ml.test-appserver + + + @ml.xdbc-server + + + + + @ml.content-db + + @ml.test-content-db-assignment + @ml.test-modules-db-assignment + + @ml.modules-db + + @ml.schemas-assignment + @ml.triggers-assignment + + + + + @ml.test-content-db-xml + + @ml.content-db + @ml.content-forests-per-host + @ml.schemas-mapping + @ml.triggers-mapping + + + + true + true + manual + false + + + http://www.marklogic.com/ns/sample + frag-root + + + http://www.marklogic.com/ns/sample2 + frag-root2 + + + + + http://www.marklogic.com/ns/sample + frag-parents + + + http://www.marklogic.com/ns/sample + frag-parents2 + + + + + + false + + http://marklogic.com/collation/ + http://marklogic.com/collation/it/S2/CU/T00BB/AS + + decompounding + true + true + true + true + true + true + true + true + true + true + + + http://www.marklogic.com/ns/sample + word-query-include + 1.0 + + + + + + http://www.marklogic.com/ns/sample2 + word-query-include2 + 1.0 + + + + + + + + http://www.marklogic.com/ns/sample + word-query-exclude + + + + + + http://www.marklogic.com/ns/sample2 + word-query-exclude2 + + + + + + + + + test + true + + + + http://www.marklogic.com/ns/sample + sample-included-element + 1.0 + + + + + + http://www.marklogic.com/ns/sample2 + sample-included-element2 + 1.0 + + + + + + + + http://www.marklogic.com/ns/sample + sample-excluded-element + + + + + + http://www.marklogic.com/ns/sample2 + sample-excluded-element2 + + + + + + + + test2 + true + + + + http://www.marklogic.com/ns/sample + sample-included-element + 1.0 + + + + + + http://www.marklogic.com/ns/sample2 + sample-included-element2 + 1.0 + + + + + + + + http://www.marklogic.com/ns/sample + sample-excluded-element + + + + + + http://www.marklogic.com/ns/sample2 + sample-excluded-element2 + + + + + + + + + + string + http://marklogic.com/ns/sample + name + http://marklogic.com/collation/codepoint + false + + + string + http://marklogic.com/ns/sample2 + name2 + http://marklogic.com/collation/codepoint + false + + + + + string + http://marklogic.com/ns/sample + person + + name + http://marklogic.com/collation/codepoint + false + + + string + http://marklogic.com/ns/sample2 + person2 + + name2 + http://marklogic.com/collation/codepoint + false + + + + + date + sample + + false + + + date + sample2 + + false + + + + + + + + + http://www.marklogic.com/ns/sample + geo-element + wgs84 + point + false + reject + + + http://www.marklogic.com/ns/sample + geo-element2 + wgs84 + point + false + reject + + + + + http://marklogic.com/ns/sample + geo + + lat + + lon + wgs84 + false + + + http://marklogic.com/ns/sample + geo2 + + lat2 + + lon2 + wgs84 + false + + + + + http://marklogic.com/ns/sample + geo + http://marklogic.com/ns/sample + lat + http://marklogic.com/ns/sample + lon + wgs84 + true + + + http://marklogic.com/ns/sample + geo2 + http://marklogic.com/ns/sample + lat2 + http://marklogic.com/ns/sample + lon2 + wgs84 + true + + + + + http://marklogic.com/ns/sample + geo + http://marklogic.com/ns/sample + pos + wgs84 + point + false + + + http://marklogic.com/ns/sample + geo2 + http://marklogic.com/ns/sample + pos2 + wgs84 + point + false + + + + + http://www.marklogic.com/ns/sample + sample-element + http://marklogic.com/collation/ + + + http://www.marklogic.com/ns/sample + sample-element2 + http://marklogic.com/collation/ + + + + + http://www.marklogic.com/ns/sample + sample-element + + sample-attribute + http://marklogic.com/collation/ + + + http://www.marklogic.com/ns/sample2 + sample-element2 + + sample-attribute2 + http://marklogic.com/collation/ + + + + + + @ml.modules-db + + + + off + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + + false + false + false + false + automatic + false + + + @ml.test-modules-db-xml + + @ml.triggers-db-xml + @ml.schemas-db-xml + + + + @ml.app-role + A role for users of the @ml.app-name application + + + + + execute + @ml.app-role + + + update + @ml.app-role + + + insert + @ml.app-role + + + read + @ml.app-role + + + + + + + xdmp:value + + + xdmp:add-response-header + + + xdmp:invoke + + + xdmp:with-namespaces + + + + + @ml.app-role2 + A second role for users of the @ml.app-name application + + + + + execute + @ml.app-role2 + + + update + @ml.app-role2 + + + insert + @ml.app-role2 + + + read + @ml.app-role2 + + + + + + + xdmp:value + + + xdmp:add-response-header + + + xdmp:invoke + + + xdmp:with-namespaces + + + + + + + @ml.app-name-user + A user for the @ml.app-name application + password + + @ml.app-role + + + + + + @ml.app-name-user2 + A second user for the @ml.app-name application + password2 + + @ml.app-role2 + + + + + + + + http://marklogic.com/roxy + sample + /app/models/sample.xqy + @ml.app-modules-db + admin + + + http://marklogic.com/roxy + sample2 + /app/models/sample2.xqy + @ml.app-modules-db + admin + + + + + my-action + http://marklogic.com/custom/privilege/my-action + execute + + + my-action2 + http://marklogic.com/custom/privilege/my-action2 + execute + + + + + application/crazy + crazy stuff + text + + + application/crazy2 + crazy stuff 2 + text + + + \ No newline at end of file diff --git a/deploy/test/data/ml6-config.xml b/deploy/test/data/ml6-config.xml new file mode 100644 index 00000000..532abd53 --- /dev/null +++ b/deploy/test/data/ml6-config.xml @@ -0,0 +1,591 @@ + + + 16 + 16 + 3600 + 600 + 100000 + 1000 + 1000 + 10000 + true + true + true + + + + + + @ml.app-name + @ml.app-port + + + @ml.modules-root + @ml.authentication-method + + @ml.url-rewriter + @ml.error-handler + + @ml.test-appserver + + + @ml.xdbc-server + + + + + @ml.content-db + + @ml.test-content-db-assignment + @ml.test-modules-db-assignment + + @ml.modules-db + + @ml.schemas-assignment + @ml.triggers-assignment + + + + + @ml.test-content-db-xml + + @ml.content-db + @ml.content-forests-per-host + @ml.schemas-mapping + @ml.triggers-mapping + + + + true + true + manual + false + + + http://www.marklogic.com/ns/sample + frag-root + + + http://www.marklogic.com/ns/sample2 + frag-root2 + + + + + http://www.marklogic.com/ns/sample + frag-parents + + + http://www.marklogic.com/ns/sample + frag-parents2 + + + + + + false + + http://marklogic.com/collation/ + http://marklogic.com/collation/it/S2/CU/T00BB/AS + + decompounding + true + true + true + true + true + true + true + true + true + true + + + http://www.marklogic.com/ns/sample + word-query-include + 1.0 + + + + + + http://www.marklogic.com/ns/sample2 + word-query-include2 + 1.0 + + + + + + + + http://www.marklogic.com/ns/sample + word-query-exclude + + + + + + http://www.marklogic.com/ns/sample2 + word-query-exclude2 + + + + + + + + + test + true + + + + http://www.marklogic.com/ns/sample + sample-included-element + 1.0 + + + + + + http://www.marklogic.com/ns/sample2 + sample-included-element2 + 1.0 + + + + + + + + http://www.marklogic.com/ns/sample + sample-excluded-element + + + + + + http://www.marklogic.com/ns/sample2 + sample-excluded-element2 + + + + + + + + test2 + true + + + + http://www.marklogic.com/ns/sample + sample-included-element + 1.0 + + + + + + http://www.marklogic.com/ns/sample2 + sample-included-element2 + 1.0 + + + + + + + + http://www.marklogic.com/ns/sample + sample-excluded-element + + + + + + http://www.marklogic.com/ns/sample2 + sample-excluded-element2 + + + + + + + + + + string + http://marklogic.com/ns/sample + name + http://marklogic.com/collation/codepoint + false + + + string + http://marklogic.com/ns/sample2 + name2 + http://marklogic.com/collation/codepoint + false + + + + + string + http://marklogic.com/ns/sample + person + + name + http://marklogic.com/collation/codepoint + false + + + string + http://marklogic.com/ns/sample2 + person2 + + name2 + http://marklogic.com/collation/codepoint + false + + + + + date + sample + + false + + + date + sample2 + + false + + + + + sample + http://marklogic.com/ns/sample + + + sample2 + http://marklogic.com/ns/sample2 + + + + + string + http://marklogic.com/collation/codepoint + /sample:root/sample:child + false + reject + + + string + http://marklogic.com/collation/codepoint + /sample:root/sample:child2 + false + reject + + + + + http://www.marklogic.com/ns/sample + geo-element + wgs84 + point + false + reject + + + http://www.marklogic.com/ns/sample + geo-element2 + wgs84 + point + false + reject + + + + + http://marklogic.com/ns/sample + geo + + lat + + lon + wgs84 + false + + + http://marklogic.com/ns/sample + geo2 + + lat2 + + lon2 + wgs84 + false + + + + + http://marklogic.com/ns/sample + geo + http://marklogic.com/ns/sample + lat + http://marklogic.com/ns/sample + lon + wgs84 + true + + + http://marklogic.com/ns/sample + geo2 + http://marklogic.com/ns/sample + lat2 + http://marklogic.com/ns/sample + lon2 + wgs84 + true + + + + + http://marklogic.com/ns/sample + geo + http://marklogic.com/ns/sample + pos + wgs84 + point + false + + + http://marklogic.com/ns/sample + geo2 + http://marklogic.com/ns/sample + pos2 + wgs84 + point + false + + + + + http://www.marklogic.com/ns/sample + sample-element + http://marklogic.com/collation/ + + + http://www.marklogic.com/ns/sample + sample-element2 + http://marklogic.com/collation/ + + + + + http://www.marklogic.com/ns/sample + sample-element + + sample-attribute + http://marklogic.com/collation/ + + + http://www.marklogic.com/ns/sample2 + sample-element2 + + sample-attribute2 + http://marklogic.com/collation/ + + + + + + @ml.modules-db + + + + off + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + false + + false + false + false + false + automatic + false + + + @ml.test-modules-db-xml + + @ml.triggers-db-xml + @ml.schemas-db-xml + + + + @ml.app-role + A role for users of the @ml.app-name application + + + + + execute + @ml.app-role + + + update + @ml.app-role + + + insert + @ml.app-role + + + read + @ml.app-role + + + + + + + xdmp:value + + + xdmp:add-response-header + + + xdmp:invoke + + + xdmp:with-namespaces + + + + + @ml.app-role2 + A second role for users of the @ml.app-name application + + + + + execute + @ml.app-role2 + + + update + @ml.app-role2 + + + insert + @ml.app-role2 + + + read + @ml.app-role2 + + + + + + + xdmp:value + + + xdmp:add-response-header + + + xdmp:invoke + + + xdmp:with-namespaces + + + + + + + @ml.app-name-user + A user for the @ml.app-name application + password + + @ml.app-role + + + + + + @ml.app-name-user2 + A second user for the @ml.app-name application + password2 + + @ml.app-role2 + + + + + + + + http://marklogic.com/roxy + sample + /app/models/sample.xqy + @ml.app-modules-db + admin + + + http://marklogic.com/roxy + sample2 + /app/models/sample2.xqy + @ml.app-modules-db + admin + + + + + my-action + http://marklogic.com/custom/privilege/my-action + execute + + + my-action2 + http://marklogic.com/custom/privilege/my-action2 + execute + + + + + application/crazy + crazy stuff + text + + + application/crazy2 + crazy stuff 2 + text + + + \ No newline at end of file diff --git a/deploy/test/data/ml6-properties/build.properties b/deploy/test/data/ml6-properties/build.properties new file mode 100644 index 00000000..75289878 --- /dev/null +++ b/deploy/test/data/ml6-properties/build.properties @@ -0,0 +1,3 @@ +user=admin +password=admin +app-name=roxy-unit-tests \ No newline at end of file diff --git a/deploy/test/data/ml6-properties/default.properties b/deploy/test/data/ml6-properties/default.properties new file mode 100644 index 00000000..9aa744d7 --- /dev/null +++ b/deploy/test/data/ml6-properties/default.properties @@ -0,0 +1,3 @@ +user=admin-user +password=admin-user +app-name=roxy-unit-tests \ No newline at end of file diff --git a/deploy/test/test_main.rb b/deploy/test/test_main.rb new file mode 100644 index 00000000..2870df8a --- /dev/null +++ b/deploy/test/test_main.rb @@ -0,0 +1 @@ +require 'test/test_server_config' diff --git a/deploy/test/test_server_config.rb b/deploy/test/test_server_config.rb new file mode 100644 index 00000000..5bf86c8c --- /dev/null +++ b/deploy/test/test_server_config.rb @@ -0,0 +1,61 @@ +require 'test/unit' +require 'server_config' +require 'util' + +class TestProperties < Test::Unit::TestCase + def test_load_properties + properties = ServerConfig.load_properties(File.expand_path("../data/ml6-properties/default.properties", __FILE__), "test.") + assert(properties.is_a?(Hash)) + assert_equal('admin-user', properties['test.user']) + assert_equal('admin-user', properties['test.password']) + assert_equal('roxy-unit-tests', properties['test.app-name']) + end + + def test_substitute_properties + sub_me = { 'username' => 'bob-${last-name}', 'password' => '123' } + with_me = { 'last-name' => 'smith' } + properties = ServerConfig.substitute_properties(sub_me, with_me) + assert(properties.is_a?(Hash)) + assert_equal('bob-smith', properties['username']) + assert_equal('123', properties['password']) + + sub_me = { 'username' => 'bob-${last-name}', 'password' => '123' } + with_me = { 'ml.last-name' => 'smith' } + properties = ServerConfig.substitute_properties(sub_me, with_me, "ml.") + assert(properties.is_a?(Hash)) + assert_equal('bob-smith', properties['username']) + assert_equal('123', properties['password']) + end + + def test_properties + properties = ServerConfig.properties(File.expand_path("../data/ml6-properties/", __FILE__)) + assert(properties.is_a?(Hash)) + assert_equal('admin', properties['ml.user']) + assert_equal('admin', properties['ml.password']) + assert_equal('roxy-unit-tests', properties['ml.app-name']) + end + + def test_build_config + end + + def test_bootstrap + # cheat the local environment into the command line + ARGV << "local" + + properties = ServerConfig.properties(File.expand_path("../data/ml4-properties/", __FILE__)) + ServerConfig.logger.debug(properties) + s = ServerConfig.new({ + :config_file => File.expand_path("../data/ml4-config.xml", __FILE__), + :properties => properties, + :logger => Logger.new(STDOUT) + }) + + s.bootstrap + assert(s.validate_install, "Bootstrap passes validation") + + s.bootstrap + assert(s.validate_install, "Bootstrap passes validation") + + s.wipe + end +end \ No newline at end of file diff --git a/ml b/ml index 14d2f282..52b21756 100755 --- a/ml +++ b/ml @@ -70,6 +70,14 @@ then else usage fi +elif [ "$1" == 'self-test' ] +then + if [ -e deploy/test/test_main.rb ] + then + ruby -I deploy -I deploy/lib -I deploy/test deploy/test/test_main.rb + else + printf "\nERROR: You must run this command inside a valid Roxy Project\n\n" + fi else if [ -e deploy/lib/ml.rb ] then