Index: openacs-4/packages/acs-tcl/tcl/site-nodes-procs.tcl
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/acs-tcl/tcl/site-nodes-procs.tcl,v
diff -u -r1.93 -r1.94
--- openacs-4/packages/acs-tcl/tcl/site-nodes-procs.tcl 15 Jun 2015 13:13:41 -0000 1.93
+++ openacs-4/packages/acs-tcl/tcl/site-nodes-procs.tcl 7 Aug 2017 23:48:00 -0000 1.94
@@ -9,6 +9,73 @@
}
+
+#####################################################################
+#
+# For the sitenodes implmementation there are two versions available.
+# One has the option to use either the classical site-nodes code based
+# on nsvs or the newer XOTcl based code. The classical code has the
+# disadvantage that it takes a while on start-up, uses a lot of
+# memory, and is non-scalable on size and parallelization. The new
+# version is much faster from a factor of two to a several thousand
+# times - but requires XOTcl, which has not made it yet to the
+# acs-core procs. So, the implementation checks, if the installation
+# fullfills the requirements of the new code, if not, it falls back to
+# the classical implementation.
+#
+# Some timings:
+# simple installation:
+# nsv-based get_children: 291 microseconds
+# xotcl-based get_children: 30 microseconds
+#
+# implementation with 130.000 site-nodes
+# nsv-based get_children: 1535380 microseconds
+# xotcl-based get_children: 186 microseconds
+#
+# array set n [nsv_get site_nodes /]
+# ds_comment [time {site_node::get_children -node_id $n(node_id)}]
+# ds_comment [time {::xo::site_node get_children -node_id $n(node_id)}]
+#
+# The easiest and most straightforward implementation is to put the
+# few XOTcl classes here into this file (what i did for now), since it
+# makes it easier to handle reloads, etc.
+#
+# If the variable UseXotclSiteNodes is set, we define a few of the
+# ad_procs below to use the XOTcl-based infrastructure.
+#
+# In case, you are using dotlrn, make sure to use an up-to-date
+# version of dotlrn that does not bypass the API to access the nsv
+# "site_nodes". Make sure to use as well the two fixes by Victor
+# Guerra for applets-procs.tcl and dotlrn-procs.tcl from May 12 2010.
+#
+# -gustaf neumann (May 2010)
+#
+#####################################################################
+#
+
+#
+# Per default, use the classical code
+#
+set UseXotclSiteNodes 0
+
+#
+# Turn on UseXotclSiteNodes in cases, where all requirements are met.
+# The XOTcl classes below depend on XOTcl 2, xotcl-core (in particular
+# 05-db-procs.tcl). The current implementation does not support
+# oracle, the implementation does not distinguish btw. AOLserver and
+# NaviServer (uses simply ns_cache_eval for speed and simplicity).
+#
+
+if {[info commands ::nx::Object] ne ""
+ && [ns_info name] eq "NaviServer"
+ && [db_driverkey ""] eq "postgresql"
+ && [db_string check_base_tables {select 1 from pg_class where relname = 'apm_package_versions'} -default 0]
+ && [apm_package_installed_p xotcl-core]
+} {
+ set UseXotclSiteNodes 1
+ ns_log notice "use XOTcl Site Nodes"
+}
+
#----------------------------------------------------------------------
# site_nodes data structure
#----------------------------------------------------------------------
@@ -33,10 +100,10 @@
create a new site node
} {
set var_list [list \
- [list name $name] \
- [list parent_id $parent_id] \
- [list directory_p $directory_p] \
- [list pattern_p $pattern_p]]
+ [list name $name] \
+ [list parent_id $parent_id] \
+ [list directory_p $directory_p] \
+ [list pattern_p $pattern_p]]
set node_id [package_instantiate_object -var_list $var_list site_node]
@@ -50,13 +117,13 @@
set url [site_node::get_url -node_id $parent_id]
append url $name
if { $directory_p == "t" } { append url "/" }
- nsv_set site_node_url_by_node_id $node_id $url
- nsv_set site_nodes $url \
- [list url $url node_id $node_id parent_id $parent_id name $name \
- directory_p $directory_p pattern_p $pattern_p \
- object_id "" object_type "" \
- package_key "" package_id "" \
- instance_name "" package_type ""]
+ nsv_set site_node_url_by_node_id $node_id $url
+ nsv_set site_nodes $url \
+ [list url $url node_id $node_id parent_id $parent_id name $name \
+ directory_p $directory_p pattern_p $pattern_p \
+ object_id "" object_type "" \
+ package_key "" package_id "" \
+ instance_name "" package_type ""]
} -finally {
ns_mutex unlock [nsv_get site_nodes_mutex mutex]
}
@@ -66,11 +133,46 @@
ad_proc -public site_node::delete {
{-node_id:required}
+ -delete_subnodes:boolean
+ -delete_package:boolean
} {
delete the site node
} {
- db_exec_plsql delete_site_node {}
- update_cache -node_id $node_id
+ if {!$delete_subnodes_p} {
+ set n_subnodes [llength [site_node::get_children \
+ -node_id $node_id]]
+ if {$n_subnodes != 0} {
+ error "Site node has subnodes. To force use -delete_subnodes option"
+ }
+ }
+
+ set nodes_to_delete {}
+
+ # breadth-first visit of the node tree, so we can delete children
+ # starting from leaves, then their parents and so on to the top
+ # (and thus not triggering reference constraint errors)
+ set queue [list $node_id]
+ while {$queue ne ""} {
+ set parent_id [lindex $queue 0]
+ lappend nodes_to_delete $parent_id
+ set queue [lrange $queue 1 end]
+ lappend queue {*}[site_node::get_children \
+ -element "node_id" \
+ -node_id $parent_id]
+ }
+
+ # delete nodes in reverse order, starting from leaves
+ foreach node_id [lreverse $nodes_to_delete] {
+ # first delete package_id under this node...
+ set package_id [site_node::get_object_id \
+ -node_id $node_id]
+ if {$delete_package_p} {
+ apm_package_instance_delete $package_id
+ }
+ # ...then the node itself
+ db_exec_plsql delete_site_node {}
+ update_cache -node_id $node_id
+ }
}
ad_proc -public site_node::mount {
@@ -88,29 +190,29 @@
with_finally -code {
#Now update the nsv caches.
array set node [site_node::get_from_node_id -node_id $node_id]
-
+
foreach var [list object_type package_key package_id instance_name package_type] {
set $var ""
}
-
+
db_0or1row get_package_info {
select 'apm_package' as object_type,
- p.package_key,
- p.package_id,
- p.instance_name,
- t.package_type
+ p.package_key,
+ p.package_id,
+ p.instance_name,
+ t.package_type
from apm_packages p, apm_package_types t
where p.package_id = :object_id
- and t.package_key = p.package_key
+ and t.package_key = p.package_key
}
-
+
nsv_set site_nodes $node(url) \
[list url $node(url) node_id $node(node_id) parent_id $node(parent_id) name $node(name) \
directory_p $node(directory_p) pattern_p $node(pattern_p) \
object_id $object_id object_type $object_type \
package_key $package_key package_id $package_id \
instance_name $instance_name package_type $package_type]
-
+
set url_by_object_id [list $node(url)]
if { [nsv_exists site_node_url_by_object_id $object_id] } {
set url_by_object_id [concat [nsv_get site_node_url_by_object_id $object_id] $url_by_object_id]
@@ -120,7 +222,7 @@
$url_by_object_id]
}
nsv_set site_node_url_by_object_id $object_id $url_by_object_id
-
+
if { $package_key ne "" } {
set url_by_package_key [list $node(url)]
if { [nsv_exists site_node_url_by_package_key $package_key] } {
@@ -181,17 +283,17 @@
@param node_id The id of the node in the site map where the package should be mounted.
@param parent_node_id If no node_id is specified this will be the parent node under which the
- new node is created. Defaults to the main site node id.
+ new node is created. Defaults to the main site node id.
@param node_name If node_id is not specified then this will be the name of the
- new site node that is created. Defaults to package_key.
+ new site node that is created. Defaults to package_key.
@param package_name The name of the new package instance. Defaults to pretty name of package type.
@param context_id The context_id of the package. Defaults to the closest ancestor package
- in the site map.
+ in the site map.
@param package_key The key of the package type to instantiate.
@param package_id The id of the new package. Optional.
@return The id of the instantiated package
-
+
@author Peter Marklund
} {
# Create a new node if none was provided and none exists
@@ -208,7 +310,7 @@
# Create the node if it doesn't exists
set parent_url [get_url -notrailing -node_id $parent_node_id]
- set url "${parent_url}/${node_name}"
+ set url "${parent_url}/${node_name}"
if { ![exists_p -url $url] } {
set node_id [site_node::new -name $node_name -parent_id $parent_node_id]
@@ -252,11 +354,11 @@
if {[nsv_exists apm_package_inherit_order $package_key]} {
foreach inherited_package_key [nsv_get apm_package_inherit_order $package_key] {
- apm_invoke_callback_proc \
- -package_key $inherited_package_key \
- -type before-unmount \
- -arg_list [list package_id $package_id node_id $node_id]
- }
+ apm_invoke_callback_proc \
+ -package_key $inherited_package_key \
+ -type before-unmount \
+ -arg_list [list package_id $package_id node_id $node_id]
+ }
}
db_dml unmount_object {}
@@ -290,19 +392,19 @@
ns_mutex lock [nsv_get site_nodes_mutex mutex]
with_finally -code {
-
- # Lars: We need to record the object_id's touched, so we can sort the
- # object_id->url mappings again. We store them sorted by length of the URL
+
+ # Lars: We need to record the object_id's touched, so we can sort the
+ # object_id->url mappings again. We store them sorted by length of the URL
if { [nsv_exists site_node_url_by_node_id $node_id] } {
set old_url [nsv_get site_node_url_by_node_id $node_id]
if { $sync_children_p } {
append old_url *
}
-
+
# This is a little cumbersome, but we have to remove the entry for
- # the object_id->url mapping, for each object_id that used to be
+ # the object_id->url mapping, for each object_id that used to be
# mounted here
-
+
# Loop over all the URLs under the node we're updating
set cur_nodes [nsv_array get site_nodes $old_url]
foreach {cur_node_url curr_node_values} $cur_nodes {
@@ -311,15 +413,15 @@
set cur_object_id $cur_node(object_id)
if { $cur_object_id ne "" } {
# Remove the URL from the url_by_object_id entry for that object_id
- set cur_url_by_object_id [nsv_get site_node_url_by_object_id $cur_object_id]
- set cur_idx [lsearch -exact $cur_url_by_object_id $cur_node_url]
+ set cur_url_by_object_id [nsv_get site_node_url_by_object_id $cur_object_id]
+ set cur_idx [lsearch -exact $cur_url_by_object_id $cur_node_url]
if { $cur_idx != -1 } {
set cur_url_by_object_id \
- [lreplace $cur_url_by_object_id $cur_idx $cur_idx]
+ [lreplace $cur_url_by_object_id $cur_idx $cur_idx]
nsv_set site_node_url_by_object_id $cur_object_id $cur_url_by_object_id
}
}
-
+
# Find the package_key previously mounted there
set cur_package_key $cur_node(package_key)
if { $cur_package_key ne "" } {
@@ -328,13 +430,13 @@
set cur_idx [lsearch -exact $cur_url_by_package_key $cur_node_url]
if { $cur_idx != -1 } {
set cur_url_by_package_key \
- [lreplace $cur_url_by_package_key $cur_idx $cur_idx]
+ [lreplace $cur_url_by_package_key $cur_idx $cur_idx]
nsv_set site_node_url_by_package_key $cur_package_key $cur_url_by_package_key
}
}
nsv_unset site_nodes $cur_node_url
nsv_unset site_node_url_by_node_id $cur_node(node_id)
- }
+ }
}
# Note that in the queries below, we use connect by instead of site_node.url
@@ -345,7 +447,7 @@
} else {
set query_name select_site_node
}
-
+
set cur_obj_ids [list]
db_foreach $query_name {} {
if {$parent_id eq ""} {
@@ -387,10 +489,10 @@
# with 4000+ URLs mapped to one instance of the attachments package.
# The sort facilitates deleting child nodes before parent nodes.
foreach object_id [lsort -unique $cur_obj_ids] {
- nsv_set site_node_url_by_object_id $object_id [lsort \
- -decreasing \
- -command util::string_length_compare \
- [nsv_get site_node_url_by_object_id $object_id] ]
+ nsv_set site_node_url_by_object_id $object_id [lsort \
+ -decreasing \
+ -command util::string_length_compare \
+ [nsv_get site_node_url_by_object_id $object_id] ]
}
} -finally {
ns_mutex unlock [nsv_get site_nodes_mutex mutex]
@@ -405,7 +507,7 @@
either url or node_id is required, if both are passed url is ignored
- The array elements are: package_id, package_key, object_type, directory_p,
+ The array elements are: package_id, package_key, object_type, directory_p,
instance_name, pattern_p, parent_id, node_id, object_id, url.
} {
if {$url eq "" && $node_id eq ""} {
@@ -431,7 +533,7 @@
either url or node_id is required, if both are passed url is ignored
- The array elements are: package_id, package_key, object_type, directory_p,
+ The array elements are: package_id, package_key, object_type, directory_p,
instance_name, pattern_p, parent_id, node_id, object_id, url.
@see site_node::get
@@ -444,7 +546,7 @@
{-node_id:required}
} {
returns an array representing the site node for the given node_id
-
+
@see site_node::get
} {
return [get_from_url -url [get_url -node_id $node_id]]
@@ -505,7 +607,7 @@
} {
set url_no_trailing [string trimright $url "/"]
return [nsv_exists site_nodes "$url_no_trailing/"]
-}
+}
ad_proc -public site_node::get_from_object_id {
{-object_id:required}
@@ -520,7 +622,7 @@
ad_proc -public site_node::get_all_from_object_id {
{-object_id:required}
} {
- Return a list of site node info associated with the given object_id.
+ Return a list of site node info associated with the given object_id.
The nodes will be ordered descendingly by url (children before their parents).
} {
set node_id_list [list]
@@ -546,7 +648,7 @@
if {[nsv_exists site_node_url_by_node_id $node_id]} {
set url [nsv_get site_node_url_by_node_id $node_id]
}
-
+
if { $notrailing_p } {
set url [string trimright $url "/"]
}
@@ -584,7 +686,15 @@
} {
return the site node id associated with the given object_id
} {
- set url [lindex [get_url_from_object_id -object_id $object_id] 0]
+ set urls [get_url_from_object_id -object_id $object_id]
+ if {[llength $urls] == 0} {
+ set url ""
+ } else {
+ if {[llength $urls] > 1} {
+ ad_log warning "get_node_id_from_object_id for object $object_id returns [llength $urls] urls, first one is returned"
+ }
+ set url [lindex $urls 0]
+ }
if { $url ne "" } {
return [get_node_id -url $url]
} else {
@@ -621,7 +731,7 @@
while {$node_id ne "" } {
array set node [get -node_id $node_id]
-
+
if {$array_result_p} {
lappend result [array get node]
} else {
@@ -630,7 +740,7 @@
set node_id $node(parent_id)
}
-
+
return $result
}
@@ -651,34 +761,34 @@
{-element {}}
{-node_id:required}
} {
- This proc gives answers to questions such as: What are all the package_id's
+ This proc gives answers to questions such as: What are all the package_id's
(or any of the other available elements) for all the instances of package_key or package_type mounted
under node_id xxx?
@param node_id The node for which you want to find the children.
@option all Set this if you want all children, not just direct children
-
+
@option package_type If specified, this will limit the returned nodes to those with an
- package of the specified package type (normally apm_service or
- apm_application) mounted. Conflicts with the -package_key option.
-
+ package of the specified package type (normally apm_service or
+ apm_application) mounted. Conflicts with the -package_key option.
+
@param package_key If specified, this will limit the returned nodes to those with a
- package of the specified package key mounted. Conflicts with the
- -package_type option. Can take one or more packges keys as a Tcl list.
+ package of the specified package key mounted. Conflicts with the
+ -package_type option. Can take one or more packges keys as a Tcl list.
- @param filters Takes a list of { element value element value ... } for filtering
- the result list. Only nodes where element is value for each of the
- filters in the list will get included. For example:
- -filters { package_key "acs-subsite" }.
-
- @param element The element of the site node you wish returned. Defaults to url, but
- the following elements are available: object_type, url, object_id,
- instance_name, package_type, package_id, name, node_id, directory_p.
-
- @return A list of URLs of the site_nodes immediately under this site node, or all children,
+ @param filters Takes a list of { element value element value ... } for filtering
+ the result list. Only nodes where element is value for each of the
+ filters in the list will get included. For example:
+ -filters { package_key "acs-subsite" }.
+
+ @param element The element of the site node you wish returned. Defaults to url, but
+ the following elements are available: object_type, url, object_id,
+ instance_name, package_type, package_id, name, node_id, directory_p.
+
+ @return A list of URLs of the site_nodes immediately under this site node, or all children,
if the -all switch is specified.
-
+
@author Lars Pind (lars@collaboraid.biz)
} {
if { $package_type ne "" && $package_key ne "" } {
@@ -693,11 +803,11 @@
set node_url [site_node::get_url -node_id $node_id]
- if { !$all_p } {
+ if { !$all_p } {
set child_urls [list]
set s [string length "$node_url"]
# find all child_urls who have only one path element below node_id
- # by clipping the node url and last character and seeing if there
+ # by clipping the node url and last character and seeing if there
# is a / in the string. about 2x faster than the RE version.
foreach child_url [nsv_array names site_nodes "${node_url}?*"] {
if { [string first / [string range $child_url $s end-1]] < 0 } {
@@ -747,7 +857,7 @@
}
}
- # if we had filters or were getting a particular element then we
+ # if we had filters or were getting a particular element then we
# have our results in return_val otherwise it's just urls
if { $element ne ""
|| [llength $filters] > 0} {
@@ -766,35 +876,35 @@
} {
Starting with the node at with given id, or at given url,
climb up the site map and return the id of the first not-null
- mounted object. If no ancestor object is found the empty string is
+ mounted object. If no ancestor object is found the empty string is
returned.
- Will ignore itself and only return true ancestors unless
+ Will ignore itself and only return true ancestors unless
include_self
is set.
- @param url The url of the node to start from. You must provide
- either url or node_id. An empty url is taken to mean
+ @param url The url of the node to start from. You must provide
+ either url or node_id. An empty url is taken to mean
the main site.
- @param node_id The id of the node to start from. Takes precedence
+ @param node_id The id of the node to start from. Takes precedence
over any provided url.
- @param package_key Restrict search to objects of this package type. You
+ @param package_key Restrict search to objects of this package type. You
may supply a list of package_keys.
- @param include_self Return the package_id at the passed-in node if it is
- of the desired package_key. Ignored if package_key is
+ @param include_self Return the package_id at the passed-in node if it is
+ of the desired package_key. Ignored if package_key is
empty.
@return The id of the first object found and an empty string if no object
- is found. Throws an error if no node with given url can be found.
+ is found. Throws an error if no node with given url can be found.
@author Peter Marklund
} {
# Make sure we have a url to work with
if { $url eq "" } {
- if { $node_id eq "" } {
- set url "/"
- } else {
- set url [site_node::get_url -node_id $node_id]
- }
+ if { $node_id eq "" } {
+ set url "/"
+ } else {
+ set url [site_node::get_url -node_id $node_id]
+ }
}
# should we return the package at the passed-in node/url?
@@ -811,27 +921,28 @@
# move up a level
set url [string trimright $url /]
set url [string range $url 0 [string last / $url]]
-
+
array set node_array [site_node::get -url $url]
# are we looking for a specific package_key?
- if { $package_key eq "" || \
- [lsearch -exact $package_key $node_array(package_key)] != -1 } {
+ if { $package_key eq ""
+ || $node_array(package_key) in $package_key
+ } {
set elm_value $node_array($element)
- }
+ }
}
return $elm_value
-}
+}
ad_proc -public site_node::get_package_url {
{-package_key:required}
} {
Get the URL of any mounted instance of a package with the given package_key.
If there is more than one mounted instance of a package, returns
- the first URL. To see all of the mounted URLs, use the
+ the first URL. To see all of the mounted URLs, use the
site_node::get_children proc.
@return a URL, or empty string if no instance of the package is mounted.
@@ -857,7 +968,7 @@
Returns folder name to use, or empty string if the supplied folder name wasn't acceptable.
} {
set existing_urls [site_node::get_children -node_id $parent_node_id -element name]
-
+
array set parent_node [site_node::get -node_id $parent_node_id]
if { $parent_node(package_key) ne "" } {
# Find all the page or directory names under this package
@@ -873,10 +984,10 @@
lappend existing_urls $name
}
}
- }
+ }
if { $folder ne "" } {
- if { [lsearch $existing_urls $folder] != -1 } {
+ if { $folder in $existing_urls } {
# The folder is on the list
if { $current_node_id eq "" } {
# New node: Complain
@@ -885,8 +996,9 @@
# Renaming an existing node: Check to see if the node is merely conflicting with itself
set parent_url [site_node::get_url -node_id $parent_node_id]
set new_node_url "$parent_url$folder"
- if { ![site_node::exists_p -url $new_node_url] || \
- $current_node_id != [site_node::get_node_id -url $new_node_url] } {
+ if { ![site_node::exists_p -url $new_node_url]
+ || $current_node_id != [site_node::get_node_id -url $new_node_url]
+ } {
return {}
}
}
@@ -942,7 +1054,7 @@
@param sync_p If "t", we flush the in-memory site map
@param delete_p If "t", we attempt to delete the site node. This
- will fail if you have not cleaned up child nodes
+ will fail if you have not cleaned up child nodes
@param node_id The node_id to unmount
} {
@@ -975,10 +1087,10 @@
package_keys
} {
- Use site_node::closest_ancestor_package. Note that - site_node_closest_ancestor_package will include the passed-in node in the - search, whereas the new proc doesn't by default. If you want to include - the passed-in node, call site_node::closest_ancestor_package with the + Use site_node::closest_ancestor_package. Note that + site_node_closest_ancestor_package will include the passed-in node in the + search, whereas the new proc doesn't by default. If you want to include + the passed-in node, call site_node::closest_ancestor_package with the -include_self flag
@@ -1016,39 +1128,39 @@ # Try the URL as is. if {[catch {nsv_get site_nodes $url} result] == 0} { - array set node $result - if {$node(package_key) in $package_keys} { - return $node(package_id) - } + array set node $result + if {$node(package_key) in $package_keys} { + return $node(package_id) + } } - + # Add a trailing slash and try again. if {[string index $url end] ne "/"} { - append url "/" - if {[catch {nsv_get site_nodes $url} result] == 0} { - array set node $result - if {$node(package_key) in $package_keys} { - return $node(package_id) - } - } + append url "/" + if {[catch {nsv_get site_nodes $url} result] == 0} { + array set node $result + if {$node(package_key) in $package_keys} { + return $node(package_id) + } + } } - + # Try successively shorter prefixes. while {$url ne ""} { - # Chop off last component and try again. - set url [string trimright $url /] - set url [string range $url 0 [string last / $url]] - - if {[catch {nsv_get site_nodes $url} result] == 0} { - array set node $result - if {$node(pattern_p) == "t" - && $node(object_id) ne "" - && $node(package_key) in $package_keys} { - return $node(package_id) - } - } + # Chop off last component and try again. + set url [string trimright $url /] + set url [string range $url 0 [string last / $url]] + + if {[catch {nsv_get site_nodes $url} result] == 0} { + array set node $result + if {$node(pattern_p) == "t" + && $node(object_id) ne "" + && $node(package_key) in $package_keys} { + return $node(package_id) + } + } } - + return $default } @@ -1087,15 +1199,665 @@ ad_proc -public site_node::conn_url { } { - Use this in place of ns_conn url when referencing host_nodes. This proc returns the appropriate ns_conn url value, depending on if host_node_map is used for current connection, or hostname's domain. + Use this in place of ns_conn url when referencing host_nodes. + This proc returns the appropriate ns_conn url value, depending on + if host_node_map is used for current connection, or hostname's + domain. } { set ns_conn_url [ns_conn url] set subsite_get_url [subsite::get_url] set joined_url [file join $subsite_get_url $ns_conn_url] # join drops ending slash for some cases. Add back if appropriate. - if { [string range $ns_conn_url end end] eq "/" && [string range $joined_url end end] ne "/" } { + if { [string index $ns_conn_url end] eq "/" && [string index $joined_url end] ne "/" } { append joined_url "/" } return $joined_url } + +##################################################################### +# old end of file +##################################################################### + +if {$UseXotclSiteNodes} { + + # + # If we are in this branch of the "if" statement, we want to use the + # XOTcl-based infrastructure. + # + # First, we define a class for handling SiteNodes in the ::xo + # namespace (like other XOTcl based support functions). Afterwards + # we define some of the procs above to used this infrastructure. + # + + namespace eval ::xo { + + ##################################################### + # @class SiteNode + ##################################################### + # + # This class capsulates access to site-nodes stored in the + # database. It is written in a style to support the the needs + # of the Tcl-based API above. + # + # @author Gustaf Neumann + + ::nx::Class create SiteNode { + + # + # @method get + # returns a site node from url or site-node with all its context info + # + + :public method get { + {-url ""} + {-node_id ""} + } { + if {$url eq "" && $node_id eq ""} { + error "site_node::get \"must pass in either url or node_id\"" + } + + # + # make sure, we have a node_id + # + if {$node_id eq ""} { + set node_id [:get_node_id -url $url] + } + + return [:properties -node_id $node_id] + } + + # + # @method properties + # returns a site node from node_id with all its context info + # + + :protected method properties { + -node_id:integer,required + } { + # + # Get url, since it is not returned by the later query. + + # TODO: I did not want to modifiy the query for the time + # being. When doing the Oracle support, the retrieval of the URL + # should be moved into the query below.... + # + set url [:get_url -node_id $node_id] + + # + # get site-node with context from the database + # + ::db_1row dbqd.acs-tcl.tcl.site-nodes-procs.site_node::update_cache.select_site_node {} + + set object_type [expr {$package_id eq "" ? "" : "apm_package"}] + set list [list url $url node_id $node_id parent_id $parent_id name $name \ + directory_p $directory_p pattern_p $pattern_p object_id $object_id \ + object_type $object_type package_key $package_key package_id $package_id \ + instance_name $instance_name package_type $package_type] + return $list + } + + # + # @method get_children + # get children of a site node + # + + :public method get_children { + -node_id:required + -all:switch + {-package_type ""} + {-package_key ""} + {-filters ""} + {-element ""} + } { + # + # Fitering happens here exactly like in the nsv-based version. If should be possible to + # realize (at least some of the) filtering via the SQL query + # + if {$all} { + # + # the following query is just for PG, TODO: Oracle is missing + # + set child_urls [::xo::dc list [current method]-all { + select site_node__url(children.node_id) + from site_nodes as parent, site_nodes as children + where parent.node_id = :node_id + and children.tree_sortkey between parent.tree_sortkey and tree_right(parent.tree_sortkey) + and children.tree_sortkey <> parent.tree_sortkey + }] + } else { + if {$package_key ne ""} { + # + # Simple optimization for package_keys; seems to be frequenty used. + # We leave the logic below unmodified, which could be optimized as well. + # + set package_key_clause "and package_id = object_id and package_key = :package_key" + set from "site_nodes, apm_packages" + } else { + set package_key_clause "" + set from "site_nodes" + } + set sql [::xo::dc select \ + -vars site_node__url(node_id) \ + -from $from \ + -where "parent_id = :node_id $package_key_clause" \ + -map_function_names true] + set child_urls [::xo::dc list [current method] $sql] + } + + if { $package_type ne "" } { + lappend filters package_type $package_type + } elseif { $package_key ne "" } { + lappend filters package_key $package_key + } + + if { [llength $filters] > 0 } { + set return_val [list] + foreach child_url $child_urls { + array unset site_node + if {![catch {array set site_node [:get -url $child_url]}]} { + + set passed_p 1 + foreach { elm val } $filters { + if { $site_node($elm) ne $val } { + set passed_p 0 + break + } + } + if { $passed_p } { + if { $element ne "" } { + lappend return_val $site_node($element) + } else { + lappend return_val $child_url + } + } + } + } + } elseif { $element ne "" } { + set return_val [list] + foreach child_url $child_urls { + array unset site_node + if {![catch {array set site_node [:get -url $child_url]}]} { + lappend return_val $site_node($element) + } + } + } else { + set return_val $child_urls + } + + return $return_val + } + + + # + # @method get_urls_from_object_id + # + # returns a list of urls for site_nodes that have the given + # object mounted or the empty list if there are none. The urls + # will be returned in descending order meaning any children + # will come before their parents. This ordering is useful when + # deleting site nodes as we must delete child site nodes before + # their parents. + # + + :public method get_urls_from_object_id { + -object_id:required + } { + # + # the following query is just for PG, TODO: Oracle is missing + # + set child_urls [::xo::dc list [current method]-all { + select site_node__url(node_id) + from site_nodes + where object_id = :object_id + order by tree_sortkey desc + }] + } + + :public method get_urls_from_package_key { + -package_key:required + } { + return [::xo::dc list [current method]-urls-from-package-key { + select site_node__url(node_id) + from site_nodes n, apm_packages p + where p.package_key = :package_key + and n.object_id = p.package_id + }] + } + + # + # @method get_node_id get_package_url + # just a small stub for a special case for method + # get_urls_from_package_key + # + :public method get_package_url { + -package_key:required + } { + return [lindex [:get_urls_from_package_key -package_key $package_key] 0] + } + + # + # @method get_node_id + # obtain node id from url, using directly the stored procedure + # site_node.node_id + # + # ::xo::db::sql::site_node node_id -url url ?-parent_id parent_id? + # + :public forward get_node_id ::xo::db::sql::site_node node_id + + # + # @method get_url + # obtain url from node-id, using directly the stored procedure + # site_node.url + # + # ::xo::db::sql::site_node url -node_id node_id + # + :public forward get_url ::xo::db::sql::site_node url + + # + # @method flush_cache + # a stub to be overloaded by the cache manager + # + :public method flush_cache {{-node_id ""} {-with_subtree:boolean}} {;} + + # Create an object "site_node" to provide a user-interface close + # to the classical one. + :create site_node + } + + ##################################################### + # Caching + ##################################################### + + if {[info commands ::ns_cache_names] ne ""} { + set createCache [expr {"xo_site_nodes" ni [::ns_cache_names]}] + } else { + set createCache [catch {ns_cache flush xo_site_nodes NOTHING}] + } + if {$createCache} { + ns_log notice "creating xo_site_nodes cache" + ns_cache create xo_site_nodes -size 2000000 + } + + # + # SiteNodeCache is a mixin class for caching the SiteNode objects. + # Add/remove caching methods as wanted. Removing the registry of + # the object mixin deactivates caching for these methods + # completely. + # + + ::nx::Class create SiteNodeCache { + + :public method get_children { + -node_id:required + {-all:switch} {-package_type ""} {-package_key ""} {-filters ""} {-element ""} + } { + ns_cache_eval xo_site_nodes get_children-$node_id-$all-$package_type-$package_key-$filters-$element { next } + } + + :public method get_node_id {-url:required} { + return [ns_cache_eval xo_site_nodes id-$url { next }] + } + + :protected method properties {-node_id:required} { + set key ::xo_site_nodes_property($node_id) + if {[info exists $key]} { + return [set $key] + } + set $key [ns_cache_eval xo_site_nodes p-$node_id { next }] + return [set $key] + } + + :public method get_url {-node_id:required} { + ns_cache_eval xo_site_nodes url-$node_id { next } + } + + :public method get_urls_from_object_id {-object_id:required} { + ns_cache_eval xo_site_nodes urls-$object_id { next } + } + + # The cache value from the following method is currently not + # flushed, but just used for package keys, not instances, so it + # should be safe. + :public method get_package_url {-package_key:required} { + ns_cache_eval xo_site_nodes package_url-$package_key { next } + } + + :protected method flush_all {patterns} { + foreach pattern $patterns { + foreach key [ns_cache names xo_site_nodes $pattern] { + #:msg ......key=$key + ::xo::clusterwide ns_cache flush xo_site_nodes $key + } + } + } + + :public method flush_cache {{-node_id ""} {-with_subtree:boolean true}} { + # + # Flush entries from site-node tree, including the current node, + # the root of flushed (sub)tree. If the node_id is not provided, + # or it is the node_id of root of the full site-node tree, flush + # the whole tree. + # + + set old_url [:get_url -node_id $node_id] + + if {$node_id eq "" || $old_url eq "/"} { + foreach i [ns_cache names xo_site_nodes] { + ::xo::clusterwide ns_cache flush xo_site_nodes $i + } + } else { + set limit_clause [expr {$with_subtree ? "" : "limit 1"}] + # + # the following query is just for PG, TODO: Oracle is missing + # + set tree [::xo::dc list_of_lists [current method]-flush-tree " + select site_node__url(children.node_id), children.node_id, children.object_id + from site_nodes as parent, site_nodes as children + where parent.node_id = :node_id + and children.tree_sortkey between parent.tree_sortkey and tree_right(parent.tree_sortkey) + $limit_clause + "] + foreach entry $tree { + lassign $entry url node_id object_id + foreach key [list p-$node_id url-$node_id urls-$object_id] { + ::xo::clusterwide ns_cache flush xo_site_nodes $key + } + :flush_all get_children-$node_id-* + } + regsub {/$} $old_url "" old_url + :flush_all id-$old_url* + } + } + + } + + # Turn on caching by registering the mixin (backward compatibility + # for early XOTcl2-versions, probably not needed anymore). + if {[package require nsf] >= "2.0.0"} { + site_node object mixins add SiteNodeCache + } else { + site_node object mixin add SiteNodeCache + } + } + + ##################################################################### + # Begin of overwritten procs from above + ##################################################################### + # + # The site-node implementation above depends on the nsv-array + # "site_nodes". We have to overwrite this API to avoid these calls + # and/or to use the XOTcl-based infrastructure. + + ad_proc -public site_node::new { + {-name:required} + {-parent_id:required} + {-directory_p t} + {-pattern_p t} + } { + create a new site node + } { + set var_list [list \ + [list name $name] \ + [list parent_id $parent_id] \ + [list directory_p $directory_p] \ + [list pattern_p $pattern_p]] + + set node_id [package_instantiate_object -var_list $var_list site_node] + return $node_id + } + + ad_proc -public site_node::mount { + {-node_id:required} + {-object_id:required} + {-context_id} + } { + mount object at site node + } { + db_dml mount_object {} + db_dml update_object_package_id {} + + # We might have for this node_id (or under it) some entries in + # the cache, so flush these first. Since the cache might + # contain children, we have to flush on all ancestor nodes up + # to the top node. + set ancestors [site_node::get_ancestors -node_id $node_id -element node_id] + foreach n $ancestors { + site_node::update_cache -sync_children -node_id $n + } + + # DAVEB update context_id if it is passed in + # some code relies on context_id to be set by + # instantiate_and_mount so we can't assume + # anything at this point. Callers that need to set context_id + # for example, when an unmounted package is mounted, + # should pass in the correct context_id + if {[info exists context_id]} { + db_dml update_package_context_id "" + } + + set package_key [apm_package_key_from_id $object_id] + foreach inherited_package_key [nsv_get apm_package_inherit_order $package_key] { + apm_invoke_callback_proc \ + -package_key $inherited_package_key \ + -type after-mount \ + -arg_list [list package_id $object_id node_id $node_id] + } + } + + ad_proc -private site_node::init_cache {} { + Initialize the site node cache; actually, this means flushing the + cache in case we have root site node. + } { + ns_log notice "site_node::init_cache" + set root_node_id [::db_string get_root_node_id {} -default {}] + if { $root_node_id ne "" } { + # If we are called during the *-init procs, the database + # interface might not be initialized yet. However, in this + # situation, there is nothing to flush yet. + if {[info commands ::xo::db::sql::site_node] ne ""} { + ::xo::site_node flush_cache -node_id $root_node_id + } + } + } + + ad_proc -private site_node::update_cache { + {-sync_children:boolean} + {-node_id:required} + } { + Brings the in memory copy of the site nodes hierarchy in sync with the + database version. Only updates the given node and its children. + } { + #ns_log Notice "site_node::update_cache for node_id $node_id" + ::xo::site_node flush_cache -node_id $node_id -with_subtree $sync_children_p + } + + ad_proc -public site_node::get { + {-url ""} + {-node_id ""} + } { + Returns an array representing the site node that matches the given url. + Either url or node_id is required, if both are passed url is ignored. + The array elements are: package_id, package_key, object_type, directory_p, + instance_name, pattern_p, parent_id, node_id, object_id, url. + } { + return [::xo::site_node get -url $url -node_id $node_id] + } + + ad_proc -public site_node::get_from_url { + {-url:required} + {-exact:boolean} + } { + Returns an array representing the site node that matches the given url.+ + A trailing '/' will be appended to $url if required and not present.
+ + If the '-exact' switch is not present and $url is not found, returns the + first match found by successively removing the trailing $url path component.
+ + @see site_node::get + } { + # TODO: The switch "-exact" does nothing here... Needed? + return [::xo::site_node get -node_id [::xo::site_node get_node_id -url $url]] + } + + ad_proc -public site_node::exists_p { + {-url:required} + } { + Returns 1 if a site node exists at the given url and 0 otherwise. + } { + + set url_no_trailing [string trimright $url "/"] + + # get_node_id returns always a node_id, which might be the node_id + # of the root. In order to check, whether the provided url is + # really a site-node, we do an inverse lookup and check whether + # the returned node_id has the same url as the provided one. + # + set node_id [::xo::site_node get_node_id -url $url_no_trailing] + return [expr {[::xo::site_node get_url -node_id $node_id] eq "$url_no_trailing/"}] + } + + ad_proc -public site_node::get_url { + {-node_id:required} + {-notrailing:boolean} + } { + return the url of this node_id + + @notrailing If true then strip any + trailing slash ('/'). This means the empty string is returned for the root. + } { + set url [::xo::site_node get_url -node_id $node_id] + if { $notrailing_p } { + set url [string trimright $url "/"] + } + return $url + } + + ad_proc -public site_node::get_url_from_object_id { + {-object_id:required} + } { + returns a list of urls for site_nodes that have the given object + mounted or the empty list if there are none. The + url:s will be returned in descending order meaning any children will + come before their parents. This ordering is useful when deleting site nodes + as we must delete child site nodes before their parents. + } { + ::xo::site_node get_urls_from_object_id -object_id $object_id + } + + ad_proc -public site_node::get_children { + {-all:boolean} + {-package_type {}} + {-package_key {}} + {-filters {}} + {-element {}} + {-node_id:required} + } { + This proc gives answers to questions such as: What are all the package_id's + (or any of the other available elements) for all the instances of package_key or package_type mounted + under node_id xxx? + + @param node_id The node for which you want to find the children. + + @option all Set this if you want all children, not just direct children + + @option package_type If specified, this will limit the returned nodes to those with an + package of the specified package type (normally apm_service or + apm_application) mounted. Conflicts with the -package_key option. + + @param package_key If specified, this will limit the returned nodes to those with a + package of the specified package key mounted. Conflicts with the + -package_type option. Can take one or more packges keys as a Tcl list. + + @param filters Takes a list of { element value element value ... } for filtering + the result list. Only nodes where element is value for each of the + filters in the list will get included. For example: + -filters { package_key "acs-subsite" }. + + @param element The element of the site node you wish returned. Defaults to url, but + the following elements are available: object_type, url, object_id, + instance_name, package_type, package_id, name, node_id, directory_p. + + @return A list of URLs of the site_nodes immediately under this site node, or all children, + if the -all switch is specified. + } { + ::xo::site_node get_children -all=$all_p -package_type $package_type -package_key $package_key \ + -filters $filters -element $element -node_id $node_id + } + + ad_proc -public site_node::get_package_url { + {-package_key:required} + } { + Get the URL of any mounted instance of a package with the given package_key. + + If there is more than one mounted instance of a package, returns + the first URL. To see all of the mounted URLs, use the + site_node::get_children proc. + + @return a URL, or empty string if no instance of the package is mounted. + @see site_node::get_children + } { + return [::xo::site_node get_package_url -package_key $package_key] + } + + ad_proc -deprecated -warn site_node_closest_ancestor_package { + { -default "" } + { -url "" } + package_keys + } { +
+ Use site_node::closest_ancestor_package. Note that + site_node_closest_ancestor_package will include the passed-in node in the + search, whereas the new proc doesn't by default. If you want to include + the passed-in node, call site_node::closest_ancestor_package with the + -include_self flag +
+ ++ Finds the package id of a package of specified type that is + closest to the node id represented by url (or by ad_conn url).Note + that closest means the nearest ancestor node of the specified + type, or the current node if it is of the correct type. + +
+ + Usage: + +
+ # Pull out the package_id of the subsite closest to our current node + set pkg_id [site_node::closest_ancestor_package -include_self -package_key "acs-subsite"] ++ + @param default The value to return if no package can be found + @param current_node_id The node from which to start the search + @param package_keys The type(s) of the package(s) for which we are looking + + @return
package_id
of the nearest package of the
+ specified type (package_key
). Returns $default if no
+ such package can be found.
+
+ @see site_node::closest_ancestor_package
+ } {
+
+ if {$url eq ""} {
+ set url [ad_conn url]
+ }
+
+ set result [site_node::closest_ancestor_package -package_key $package_keys -url $url -include_self]
+ if {$result eq ""} {
+ set result $default
+ }
+ return $result
+ }
+ #
+ # End of overwritten procs.
+ #
+}
+
+#
+# Local variables:
+# mode: tcl
+# tcl-indent-level: 4
+# indent-tabs-mode: nil
+# End: