<?xml version='1.0' ?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
               "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
<!ENTITY % myvars SYSTEM "../variables.ent">
%myvars;
]>
<sect1 id="groups-design" xreflabel="OpenACS 4 Groups Design">
<title>Groups Design</title>

<authorblurb>
<para>By <ulink url="http://planitia.org">Rafael H. Schloming</ulink> and Mark Thomas</para>
</authorblurb>


<sect2 id="groups-design-essentials">
<title>Essentials</title>


<itemizedlist>
<listitem><para>User directory</para></listitem>

<listitem><para>Sitewide administrator directory</para></listitem>

<listitem><para>Subsite administrator directory</para></listitem>

<listitem><para>TCL script directory</para></listitem>

<listitem><para><xref linkend="groups-requirements"/></para></listitem>

<listitem><para>Data model</para></listitem>

<listitem><para>PL/SQL file </para>

<itemizedlist>
<listitem><para><ulink url="/doc/sql/display-sql?url=community-core-create.sql&amp;package_key=acs-kernel">
community-core-create.sql</ulink></para></listitem>

<listitem><para><ulink url="/doc/sql/display-sql?url=groups-create.sql&amp;package_key=acs-kernel">groups-create.sql</ulink></para></listitem>
</itemizedlist>
</listitem>

<listitem><para>ER diagram</para></listitem>

<listitem><para>Transaction flow diagram</para></listitem>
</itemizedlist>

</sect2>

<sect2 id="groups-design-intro">
<title>Introduction</title>


<para>Almost all database-backed websites have users, and need to model the
grouping of users. The OpenACS 4 Parties and Groups system is intended to provide
the flexibility needed to model complex real-world organizational structures,
particularly to support powerful subsite services; that is, where one OpenACS
installation can support what appears to the user as distinct web services
for different user communities.</para>

</sect2>

<sect2 id="groups-design-hist-considerations">
<title>Historical Considerations</title>


<para>The primary limitation of the OpenACS 3.x user group system is that it
restricts the application developer to representing a &quot;flat group&quot;
that contains only users: The <computeroutput>user_groups</computeroutput> table may contain the
<computeroutput>group_id</computeroutput> of a parent group, but parent-child relationship
support is limited because it only allows one kind of relationship between
groups to be represented. Moreover, the Oracle database&#39;s limited support
for tree-like structures makes the queries over these relationships
expensive.</para>

<para>In addition, the Module Scoping design in OpenACS 3.0 introduced a
<emphasis>party</emphasis> abstraction - a thing that is a person or a group of people -
though not in the form of an explicit table. Rather, the triple of
<computeroutput>scope</computeroutput>, <computeroutput>user_id</computeroutput>, and <computeroutput>group_id</computeroutput> columns
was used to identify the party. One disadvantage of this design convention is
that it increases a data model&#39;s complexity by requiring the programmer
to:</para>

<itemizedlist>
<listitem><para>add these three columns to each &quot;scoped&quot; table</para></listitem>

<listitem><para>define a multi-column check constraint to protect against data corruption
(e.g., a row with a <computeroutput>scope</computeroutput> value of &quot;group&quot; but a null
<computeroutput>group_id</computeroutput>)</para></listitem>

<listitem><para>perform extra checks in <computeroutput>Tcl</computeroutput> and <computeroutput>PL/SQL</computeroutput>
functions and procedures to check both the <computeroutput>user_id</computeroutput> and
<computeroutput>group_id</computeroutput> values</para></listitem>
</itemizedlist>

</sect2>

<sect2 id="groups-design-competitors">
<title>Competitive Analysis</title>


<para>...</para>

</sect2>

<sect2 id="groups-design-design-tradeoffs">
<title>Design Tradeoffs</title>


<para>The core of the Group Systems data model is quite simple, but it was
designed in the hopes of modeling &quot;real world&quot; organizations which
can be complex graph structures. The Groups System only considers groups that
can be modeled using directed acyclic graphs, but queries over these
structures are still complex enough to slow the system down. Since almost
every page will have at least one membership check, a number of triggers,
views, and auxiliary tables have been created in the hopes of increasing
performance. To keep the triggers simple and the number of triggers small,
the data model disallows updates on the membership and composition tables,
only inserts and deletes are permitted.</para>

<para>The data model has tried to balance the need to model actual organizations
without making the system too complex or too slow. The added triggers, views,
and tables and will increase storage requirements and the insert and delete
times in an effort to speed access time. The limited flexibility (no updates
on membership) trades against the complexity of the code.</para>

</sect2>

<sect2 id="groups-design-data-model">
<title>Data Model Discussion</title>


<para>The Group System data model consists of the following tables:</para>

<variablelist>
<varlistentry>
<term><computeroutput>parties</computeroutput>

</term>
 
<listitem><para>The set of all defined parties: any <emphasis>person</emphasis>, <emphasis>user</emphasis>, or
<emphasis>group</emphasis> must have a corresponding row in this table.</para></listitem>
</varlistentry>

<varlistentry>
<term><computeroutput>persons</computeroutput>

</term>
 
<listitem><para>The set of all defined persons. To allow easy sorting of persons, the
name requirement <link linkend="groups-requirements-30-10">30.10</link> is met by
splitting the person&#39;s name into two columns: <computeroutput>first_names</computeroutput> and
<computeroutput>last_name</computeroutput>.</para></listitem>
</varlistentry>

<varlistentry>
<term><computeroutput>users</computeroutput>

</term>
 
<listitem><para>The set of all registered users; this table includes information about
the user&#39;s email address and the user&#39;s visits to the site.</para></listitem>
</varlistentry>

<varlistentry>
<term><computeroutput>user_preferences</computeroutput>

</term>
 
<listitem><para>Preferences for the user.</para></listitem>
</varlistentry>

<varlistentry>
<term><computeroutput>groups</computeroutput>

</term>
 
<listitem><para>The set of all defined groups.</para></listitem>
</varlistentry>

<varlistentry>
<term><computeroutput>group_types</computeroutput>

</term>
 
<listitem><para>When a new type of group is created, this table holds additional
knowledge level attributes for the group and its subtypes.</para></listitem>
</varlistentry>

<varlistentry>
<term><computeroutput>membership_rels</computeroutput>

</term>
 
<listitem><para>The set of direct membership relationships between a group and a
party.</para></listitem>
</varlistentry>

<varlistentry>
<term><computeroutput>group_member_index</computeroutput>

</term>
 
<listitem><para>A mapping of a party <emphasis role="strong">P</emphasis> to the groups
{<emphasis role="strong">G<subscript>i</subscript></emphasis>}the party is a member of; this mapping
includes the type of relationship by including the appropriate<computeroutput>rel_id</computeroutput>
from the <computeroutput>membership_rels</computeroutput> table.</para></listitem>
</varlistentry>

<varlistentry>
<term><computeroutput>composition_rels</computeroutput>

</term>
 
<listitem><para>The set of direct component relationships between a group and another
group.</para></listitem>
</varlistentry>

<varlistentry>
<term><computeroutput>group_component_index</computeroutput>

</term>
 
<listitem><para>A mapping of a group <emphasis role="strong">G</emphasis>to the set of groups
{<emphasis role="strong">G<subscript>i</subscript></emphasis>} that <emphasis role="strong">G</emphasis> is a component of;
this mapping includes the type of relationship by including the
appropriate<computeroutput>rel_id</computeroutput> from the <computeroutput>composition_rels</computeroutput> table.</para></listitem>
</varlistentry>
</variablelist>

<para>New groups are created through the <computeroutput>group.new</computeroutput> constructor.
When a specialized type of group is required, the group type can be extended
by an application developer. Membership constraints can be specified at
creation time by passing a parent group to the constructor.</para>

<para>The <computeroutput>membership_rels</computeroutput> and <computeroutput>composition_rels</computeroutput> tables indicate
a group&#39;s direct members and direct components; these tables do not
provide a record of the members or components that are in the group by virtue
of being a member or component of one of the group&#39;s component groups.
Site pages will query group membership often, but the network of component
groups can become a very complex directed acyclic graph and traversing this
graph for every query will quickly degrade performance. To make membership
queries responsive, the data model includes triggers (described in the next
paragraph) which watch for changes in membership or composition and update
tables that maintain the group party mappings, i.e.,
<computeroutput>group_member_index</computeroutput> and <computeroutput>group_component_index</computeroutput>. One can think
of these tables as a manually maintained index.</para>

<para>The following triggers keep the <computeroutput>group_*_index</computeroutput> tables up to
date:</para>

<variablelist>
<varlistentry>
<term><computeroutput>membership_rels_in_tr</computeroutput>

</term>
 
<listitem><para>Is executed when a new group/member relationship is created (an insert on
<computeroutput>membership_rels</computeroutput>)</para></listitem>
</varlistentry>

<varlistentry>
<term><computeroutput>membership_rels_del_tr</computeroutput>

</term>
 
<listitem><para>Is executed when a group/member relationship is deleted (a delete on
<computeroutput>membership_rels</computeroutput>)</para></listitem>
</varlistentry>

<varlistentry>
<term><computeroutput>composition_rels_in_tr</computeroutput>

</term>
 
<listitem><para>Is executed when a new group/component relationship is created (an insert
on <computeroutput>composition_rels</computeroutput>)</para></listitem>
</varlistentry>

<varlistentry>
<term><computeroutput>composition_rels_del_tr</computeroutput>

</term>
 
<listitem><para>Is executed when a group/component relationship is deleted (a delete on
<computeroutput>composition_rels</computeroutput>)</para></listitem>
</varlistentry>
</variablelist>

<para>The data model provides the following views onto the
<computeroutput>group_member_index</computeroutput> and <computeroutput>group_component_index</computeroutput> tables. No
code outside of Groups System should modify the <computeroutput>group_*_index</computeroutput>
tables.</para>

<variablelist>
<varlistentry>
<term><computeroutput>group_member_map</computeroutput>

</term>
 
<listitem><para>A mapping of a party to the groups the party is a member of; this mapping
includes the type of relationship by including the appropriate<computeroutput>rel_id</computeroutput>
from the <computeroutput>membership_rels</computeroutput> table.</para></listitem>
</varlistentry>

<varlistentry>
<term><computeroutput>group_approved_member_map</computeroutput>

</term>
 
<listitem><para>A mapping of a party to the groups the party is an approved member of
(<computeroutput>member_state</computeroutput> is &#39;approved&#39;); this mapping includes the type
of relationship by including the appropriate<computeroutput>rel_id</computeroutput> from the
<computeroutput>membership_rels</computeroutput> table.</para></listitem>
</varlistentry>

<varlistentry>
<term><computeroutput>group_distinct_member_map</computeroutput>

</term>
 
<listitem><para>A person may appear in the group member map multiple times, for example,
by being a member of two different groups that are both components of a third
group. This view is strictly a mapping of <emphasis role="strong">approved</emphasis> members
to groups.</para></listitem>
</varlistentry>

<varlistentry>
<term><computeroutput>group_component_map</computeroutput>

</term>
 
<listitem><para>A mapping of a group <emphasis role="strong">G</emphasis>to the set of groups
{<emphasis role="strong">G<subscript>i</subscript></emphasis>} group <emphasis role="strong">G</emphasis> is a component of;
this mapping includes the type of relationship by including the
appropriate<computeroutput>rel_id</computeroutput> from the <computeroutput>composition_rels</computeroutput> table.</para></listitem>
</varlistentry>

<varlistentry>
<term><computeroutput>party_member_map</computeroutput>

</term>
 
<listitem><para>A mapping of a party <emphasis role="strong">P</emphasis> to the set of parties
{<emphasis role="strong">P<subscript>i</subscript></emphasis>} party <emphasis role="strong">P</emphasis> is a member
of.</para></listitem>
</varlistentry>

<varlistentry>
<term><computeroutput>party_approved_member_map</computeroutput>

</term>
 
<listitem><para>A mapping of a party <emphasis role="strong">P</emphasis> to the set of parties
{<emphasis role="strong">P<subscript>i</subscript></emphasis>} party <emphasis role="strong">P</emphasis> is an
<emphasis role="strong">approved</emphasis> member of.</para></listitem>
</varlistentry>
</variablelist>

</sect2>

<sect2 id="groups-design-api">
<title>API</title>

<para>
The API consists of tables and views and PL/SQL functions. 
</para>

<sect3 id="groups-design-tables-views">
<title>Tables and Views</title>

<para>The <computeroutput>group_types</computeroutput> table is used to create new types of groups.</para>

<para>The <computeroutput>group_member_map</computeroutput>, <computeroutput>group_approved_member_map</computeroutput>,
<computeroutput>group_distinct_member_map</computeroutput>, <computeroutput>group_component_map</computeroutput>,
<computeroutput>party_member_map</computeroutput>, and <computeroutput>party_approved_member_map</computeroutput> views are
used to query group membership and composition.</para>

</sect3>

<sect3 id="groups-design-pl-sql-api">
<title>PL/SQL API</title>

<para><emphasis role="strong">Person</emphasis></para>

<para><computeroutput>person.new</computeroutput> creates a new person and returns the
<computeroutput>person_id</computeroutput>. The function must be given the full name of the person in
two pieces: <computeroutput>first_names</computeroutput> and <computeroutput>last_name</computeroutput>. All other fields are
optional and default to null except for <computeroutput>object_type</computeroutput> which defaults
to person and <computeroutput>creation_date</computeroutput> which defaults to <computeroutput>sysdate</computeroutput>. The
interface for this function is:</para>

<programlisting>
function person.new (
  person_id          persons.person_id%TYPE,
  object_type        acs_objects.object_type%TYPE,
  creation_date      acs_objects.creation_date%TYPE,
  creation_user      acs_objects.creation_user%TYPE,
  creation_ip        acs_objects.creation_ip%TYPE,
  email              parties.email%TYPE,
  url                parties.url%TYPE,
  first_names        persons.first_names%TYPE,
  last_name          persons.last_name%TYPE
) return persons.person_id%TYPE;
</programlisting>

<para><computeroutput>person.delete</computeroutput> deletes the person whose <computeroutput>person_id</computeroutput> is
passed to it. The interface for this procedure is:</para>

<programlisting>
procedure person.delete (
  person_id     persons.person_id%TYPE
);
</programlisting>

<para><computeroutput>person.name</computeroutput> returns the name of the person whose
<computeroutput>person_id</computeroutput> is passed to it. The interface for this function is:</para>

<programlisting>
function person.name (
  person_id     persons.person_id%TYPE
) return varchar;
</programlisting>

<para><emphasis role="strong">User</emphasis></para>

<para><computeroutput>acs_user.new</computeroutput> creates a new user and returns the <computeroutput>user_id</computeroutput>.
The function must be given the user&#39;s email address and the full name of
the user in two pieces: <computeroutput>first_names</computeroutput> and <computeroutput>last_name</computeroutput>. All
other fields are optional. The interface for this function is:</para>

<programlisting>
function acs_user.new (
  user_id            users.user_id%TYPE,
  object_type        acs_objects.object_type%TYPE,
  creation_date      acs_objects.creation_date%TYPE,
  creation_user      acs_objects.creation_user%TYPE,
  creation_ip        acs_objects.creation_ip%TYPE,
  email              parties.email%TYPE,
  url                parties.url%TYPE,
  first_names        persons.first_names%TYPE,
  last_name          persons.last_name%TYPE
  password           users.password%TYPE,
  salt               users.salt%TYPE,
  password_question  users.password_question%TYPE,
  password_answer    users.password_answer%TYPE,
  screen_name        users.screen_name%TYPE,
  email_verified_p   users.email_verified_p%TYPE
) return users.user_id%TYPE;
</programlisting>

<para><computeroutput>acs_user.delete</computeroutput> deletes the user whose <computeroutput>user_id</computeroutput> is passed
to it. The interface for this procedure is:</para>

<programlisting>
procedure acs_user.delete (
  user_id       users.user_id%TYPE
);
</programlisting>

<para><computeroutput>acs_user.receives_alerts_p</computeroutput> returns &#39;t&#39; if the user should
receive email alerts and &#39;f&#39; otherwise. The interface for this
function is:</para>

<programlisting>
function acs_user.receives_alerts_p (
  user_id       users.user_id%TYPE
) return varchar;
</programlisting>

<para>Use the procedures <computeroutput>acs_user.approve_email</computeroutput> and
<computeroutput>acs_user.unapprove_email</computeroutput> to specify whether the user&#39;s email
address is valid. The interface for these procedures are:</para>

<programlisting>
procedure acs_user.approve_email (
  user_id       users.user_id%TYPE
);

procedure acs_user.unapprove_email (
  user_id       users.user_id%TYPE
);
</programlisting>

<para><emphasis role="strong">Group</emphasis></para>

<para><computeroutput>acs_group.new</computeroutput> creates a new group and returns the
<computeroutput>group_id</computeroutput>. All fields are optional and default to null except for
<computeroutput>object_type</computeroutput> which defaults to &#39;group&#39;,
<computeroutput>creation_date</computeroutput> which defaults to <computeroutput>sysdate</computeroutput>, and
<computeroutput>group_name</computeroutput> which is required. The interface for
this function is:</para>

<programlisting>
function acs_group.new (
  group_id           groups.group_id%TYPE,
  object_type        acs_objects.object_type%TYPE,
  creation_date      acs_objects.creation_date%TYPE,
  creation_user      acs_objects.creation_user%TYPE,
  creation_ip        acs_objects.creation_ip%TYPE,
  email              parties.email%TYPE,
  url                parties.url%TYPE,
  group_name         groups.group_name%TYPE
) return groups.group_id%TYPE;
</programlisting>

<para><computeroutput>acs_group.name</computeroutput> returns the name of the group whose
<computeroutput>group_id</computeroutput> is passed to it. The interface for this function is:</para>

<programlisting>
function acs_group.name (
  group_id      groups.group_id%TYPE
) return varchar;
</programlisting>

<para><computeroutput>acs_group.member_p</computeroutput> returns &#39;t&#39; if the specified party is
a member of the specified group. Returns &#39;f&#39; otherwise. The interface
for this function is:</para>

<programlisting>
function acs_group.member_p (
  group_id      groups.group_id%TYPE,
  party_id      parties.party_id%TYPE,
) return char;
</programlisting>

<para><emphasis role="strong">Membership Relationship</emphasis></para>

<para><computeroutput>membership_rel.new</computeroutput> creates a new membership relationship type
between two parties and returns the relationship type&#39;s <computeroutput>rel_id</computeroutput>.
All fields are optional and default to null except for <computeroutput>rel_type</computeroutput>
which defaults to membership_rel. The interface for this function is:</para>

<programlisting>
function membership_rel.new (
  rel_id             membership_rels.rel_id%TYPE,
  rel_type           acs_rels.rel_type%TYPE,
  object_id_one      acs_rels.object_id_one%TYPE,
  object_id_two      acs_rels.object_id_two%TYPE,
  member_state       membership_rels.member_state%TYPE,
  creation_user      acs_objects.creation_user%TYPE,
  creation_ip        acs_objects.creation_ip%TYPE,
) return membership_rels.rel_id%TYPE;
</programlisting>

<para><computeroutput>membership_rel.ban</computeroutput> sets the <computeroutput>member_state</computeroutput> of the given
<computeroutput>rel_id</computeroutput> to &#39;banned&#39;. The interface for this procedure is:</para>

<programlisting>
procedure membership_rel.ban (
  rel_id           membership_rels.rel_id%TYPE
);
</programlisting>

<para><computeroutput>membership_rel.approve</computeroutput> sets the <computeroutput>member_state</computeroutput> of the
given <computeroutput>rel_id</computeroutput> to &#39;approved&#39;. The interface for this procedure
is:</para>

<programlisting>
procedure membership_rel.approve (
  rel_id           membership_rels.rel_id%TYPE
);
</programlisting>

<para><computeroutput>membership_rel.reject</computeroutput> sets the <computeroutput>member_state</computeroutput> of the given
<computeroutput>rel_id</computeroutput> to &#39;rejected. The interface for this procedure is:</para>

<programlisting>
procedure membership_rel.reject (
  rel_id           membership_rels.rel_id%TYPE
);
</programlisting>

<para><computeroutput>membership_rel.unapprove</computeroutput> sets the <computeroutput>member_state</computeroutput> of the
given <computeroutput>rel_id</computeroutput> to an empty string &#39;&#39;. The interface for this
procedure is:</para>

<programlisting>
procedure membership_rel.unapprove (
  rel_id           membership_rels.rel_id%TYPE
);
</programlisting>

<para><computeroutput>membership_rel.deleted</computeroutput> sets the <computeroutput>member_state</computeroutput> of the
given <computeroutput>rel_id</computeroutput> to &#39;deleted&#39;. The interface for this procedure
is:</para>

<programlisting>
procedure membership_rel.deleted (
  rel_id           membership_rels.rel_id%TYPE
);
</programlisting>

<para><computeroutput>membership_rel.delete</computeroutput> deletes the given <computeroutput>rel_id</computeroutput>. The
interface for this procedure is:</para>

<programlisting>
procedure membership_rel.delete (
  rel_id           membership_rels.rel_id%TYPE
);
</programlisting>

<para><emphasis role="strong">Composition Relationship</emphasis></para>

<para><computeroutput>composition_rel.new</computeroutput> creates a new composition relationship type
and returns the relationship&#39;s <computeroutput>rel_id</computeroutput>. All fields are optional
and default to null except for <computeroutput>rel_type</computeroutput> which defaults to
composition_rel. The interface for this function is:</para>

<programlisting>
function membership_rel.new (
  rel_id             composition_rels.rel_id%TYPE,
  rel_type           acs_rels.rel_type%TYPE,
  object_id_one      acs_rels.object_id_one%TYPE,
  object_id_two      acs_rels.object_id_two%TYPE,
  creation_user      acs_objects.creation_user%TYPE,
  creation_ip        acs_objects.creation_ip%TYPE,
) return composition_rels.rel_id%TYPE;
</programlisting>

<para><computeroutput>composition_rel.delete</computeroutput> deletes the given <computeroutput>rel_id</computeroutput>. The
interface for this procedure is:</para>

<programlisting>
procedure membership_rel.delete (
  rel_id           composition_rels.rel_id%TYPE
);
</programlisting>

</sect3>

</sect2>

<sect2 id="groups-design-ui">
<title>User Interface</title>


<para>Describe the admin pages.</para>

</sect2>

<sect2 id="groups-design-config">
<title>Configuration/Parameters</title>


<para>...</para>

</sect2>

<sect2 id="groups-design-acc-tests">
<title>Acceptance Tests</title>


<para>...</para>

</sect2>

<sect2 id="groups-design-future">
<title>Future Improvements/Areas of Likely Change</title>


<para>...</para>

</sect2>

<sect2 id="groups-design-authors">
<title>Authors</title>


<variablelist>
<varlistentry>
<term>System creator

</term>
 
<listitem><para><ulink url="mailto:rhs@mit.edu">Rafael H. Schloming</ulink></para></listitem>
</varlistentry>

<varlistentry>
<term>System owner

</term>
 
<listitem><para><ulink url="mailto:rhs@mit.edu">Rafael H. Schloming</ulink></para></listitem>
</varlistentry>

<varlistentry>
<term>Documentation author

</term>
 
<listitem><para>Mark Thomas</para></listitem>
</varlistentry>
</variablelist>

</sect2>

<sect2 id="groups-design-rev-history">
<title>Revision History</title>

<informaltable>
<tgroup cols="4">
<thead>
<row>
<entry><emphasis role="strong">Document Revision #</emphasis></entry>
<entry><emphasis role="strong">Action Taken, Notes</emphasis></entry>
<entry><emphasis role="strong">When?</emphasis></entry>
<entry><emphasis role="strong">By Whom?</emphasis></entry>
</row>
</thead>
<tbody>
<row>
<entry>0.1</entry>
<entry>Creation</entry>
<entry>08/22/2000</entry>
<entry><ulink url="mailto:rhs@mit.edu">Rafael H. Schloming</ulink></entry>
</row>

<row>
<entry>0.2</entry>
<entry>Initial Revision</entry>
<entry>08/30/2000</entry>
<entry>
Mark Thomas
</entry>
</row>

<row>
<entry>0.3</entry>
<entry>Additional revisions; tried to clarify membership/compostion</entry>
<entry>09/08/2000</entry>
<entry>
Mark Thomas
</entry>
</row>
</tbody></tgroup></informaltable>


</sect2>

</sect1>
