0.00%
Search · Index

Weblog Page

Showing 51 - 60 of 230 Postings (summary)

Internationalization and Localization Overview

Created by Gustaf Neumann, last modified by Gustaf Neumann 17 Feb 2008, at 07:08 AM

Table14.1.Internationalization and Localization Overview

Stage Task Who
Internationalization Package Developer uses the acs-lang tools to replace all visible text in a package with message keys. (More information) Package Developer
Release Management The newly internationalized package is released. Package Developer
The translation server is updated with the new package. Translation server maintainers
Localization Translators work in their respective locales to write text for each message key. (More information) Translators
Release Management The translated text in the database of the translation server is compared to the current translations in the OpenACS code base, conflicts are resolved, and the new text is written to catalog files on the translation server. Translation server maintainers
The catalog files are committed to the OpenACS code base. Translation server maintainers
A new version of OpenACS core and/or affected packages is released and published in the OpenACS.org repository. Release Manager
Upgrading Site Administrators upgrade their OpenACS sites, either via the automatic upgrade from the Repository or via tarball or CVS Site Administrators
Site Administrators import the new translations. Existing local translations, if they exist, are not overwritten. Site Administrators

Sending HTML email from your application

Created by Gustaf Neumann, last modified by Gustaf Neumann 17 Feb 2008, at 07:08 AM

by Jade Rubick

OpenACS docs are written by the named authors, and may be edited by OpenACS documentation staff.

Sending email is fairly simple using the acs-mail-lite package. Sending HTML email is only slightly more complicated.

    set subject "my subject"

    set message "<b>Bold</b> not bold"

    set from_addr "me@myemail.com"

    set to_addr "me@myemail.com"

    # the from to html closes any open tags.
    set message_html [ad_html_text_convert -from html -to html $message]

    # some mailers chop off the last few characters.
    append message_html "   "
    set message_text [ad_html_text_convert -from html -to text $message]

    set message_data [build_mime_message $message_text $message_html]

    set extra_headers [ns_set new]

    ns_set put $extra_headers MIME-Version [ns_set get $message_data MIME-Version]
    ns_set put $extra_headers Content-ID [ns_set get $message_data Content-ID]
    ns_set put $extra_headers Content-Type [ns_set get $message_data Content-Type]
    set message [ns_set get $message_data body]

    acs_mail_lite::send  -to_addr $to_addr  -from_addr $from_addr  -subject $subject  -body $message  -extraheaders $extra_headers
    

External Authentication Requirements

Created by Gustaf Neumann, last modified by Gustaf Neumann 17 Feb 2008, at 07:08 AM

People have plenty of usernames and passwords already, we don't want them to have yet another. We want people to be able to log in to OpenACS with the same password they use to log in to any other system.

Besides, administrators have better things to do than create accounts for people. So we want them to be able to create just one account on a central server (e.g. LDAP or RADIUS), and when they log on to OpenACS, an account will automatically be created for them here.

Finally, security is increased with fewer passwords, since users generally can't remember all those passwords, so they tend to keep them all the same and never change them.

  • Transparent: Users don't have to do anything special to get an account on the local OpenACS system, if they already have an account on the external authentication server.

  • Fall-back: Users who don't have an account on the external authentication server are still allowed to create a local account on OpenACS. This could be for external students who should have access to .LRN, but not to the rest of the university's resources.

  • Authentication Client Only: We want OpenACS to be able to authenticate by asking some remote authority to verify the user's username/password combination. The goal is explicitly not (at this point) to have OpenACS act as an authentication server for other systems, although this could be easily added later. The goal is also not to devise an infrastructure for letting OpenACS access resources in various other systems on the user's behalf, such as IMAP, iCalendar, SMB file servers, etc., although this is definitely an interesting use-case.

  • Easy configuration: We would like people to be able to configure this without having to write code. In particular, we want to build drivers that know how to talk with LDAP, RADIUS, PAM, etc., and which won't have to be locally modified. Only configuration and policies should change, code should not.

  • Usability: The solution must be easy to use for end users and administrators alike. There's frequently a positive feedback effect between usability and security, because when authentication schemes have poor usability, users will think up ways to circumvent them.

  • Open and modular: The design should be on the one hand open to add other authentification mechanisms when needed and on the other hand very modular to enable a start with minimal requirements (driver implementations) as soon as possible.

The problem can be split into several logically separate parts. Each has a section below.

  • Authority: The name of an authority trusted to authenticate users.

  • Authentication Driver: An implementation of the authentication service contract, which talks to an authentication of a certain type, e.g. PAM, RADIUS, LDAP, or Active Directory.

  • Authentication API: The API through which login pages and applications talk to the authentication service. There's one and only one implementation of the authentication API, namly the one included in OpenACS Core.

  • Authentication Driver API: The service contract which authentication drivers implement.

Authentication:

Account Management (NO PICTURE YET)

Batch Synchronization (NO PICTURE YET)

Feature Status Description
New API
EXT-AUTH-01 A Extend Authentication/Acct Status API
EXT-AUTH-03 A Account Creation API
EXT-AUTH-05 A Password Management API
EXT-AUTH-30 A Authority Management API
Feature Status Description
Login
EXT-AUTH-04 A Rewrite login, register, and admin pages to use APIs
EXT-AUTH-38 A ad_form complain feature
EXT-AUTH-19 A Rewrite password recovery to use API
EXT-AUTH-21 A Rewrite email verification with API
EXT-AUTH-28 A Username is email switch

Users will log in using a username, a authority, and a password. The authority is the source for user/password verification. OpenACS can be an authority itself.

Each user in OpenACS will belong to exactly one authority, which can either be the "local" OpenACS users table, in which case the password column is used, or it can be some external authority, which will be communicated with using some protocol, as implemented by an authentication driver.

Username will be separate from email address. It can be an email address, it can look like an email address but not be the name of an actual email mailbox, or it can be something else entirely.

We're assuming that user information (name, email, etc.) will either already be in the users table through a batch synchronization job, or that the relevant authentication implementation supports real-time synchronization of user data. Specifically, if you want remote users who haven't yet logged in to OpenACS to show up in user searches, you'll have to do the batch synchronization.

All in all, the login box will be an includeable template and look like this:

Username:  ________
Password:  ________
Authority: [URZ   ]
            Athena
            Local

[Forgot my password]
[New user registration]

If there's only one active authority, we don't display the authority drop-down element at all.

Feature Status Description
Configuration
EXT-AUTH-07 A Admin pages to control Ext-Auth parameters

The site-wide systems administrator can configure the authentication process from a page linked under /acs-admin.

  • Authorities - ordered list of authorities defined

  • Account Registration Allowed: Yes/No. Account registration can be disabled altogether.

  • Registration authority - the authority in which accounts should be created, using the relevant driver, if account registration is allowed.

  • Username is email? - instead of asking for username, we'll ask for email. And we'll store the value in both columns, username and email. This is a setting that spans all authorities, and is primarily meant for backwards compatibility with the old OpenACS login process.

The local authority driver is an encapsulation of current functionality within an driver matching a service contract. The other drivers call external functions. The possible functions for each authority are split into several drivers for convenience. One driver handles authentication, one account creation, and one changing passwords.

Feature Status Description
create service contract
EXT-AUTH-16 A Create service contract for Authentication
EXT-AUTH-17 A Create service contract for Acct. Creation
EXT-AUTH-29 A Create service contract for Passwd Management
Feature Status Description
EXT-AUTH-18 A Authority configuration data model

Each authority is defined like this:

  • Authority pretty-name, e.g. "URZ"

  • Authentication Driver, e.g. "RADIUS". In practice, this would be a reference to a service contract implementation.

  • Authentication Driver configuration settings, e.g. host name, port, etc., as required by the particular driver. Note that this is per authority, not per driver, i.e., you can have multiple authorities with the same driver but different configuration options.

  • AuthenticationAllowed - true/false, so you can disable login through some authority without having to delete the authority, and hence also all the users who belong to that authority.

  • ForgottenPasswordUrl - a URL to redirect to instead of trying to use the authentication driver's password management features.

  • ChangePasswordUrl - a URL to redirect to instead of trying to use the authentication driver's password management features.

  • Account Creation Driver, e.g. "RADIUS". In practice, this would be a reference to a service contract implementation. The reason we have separate drivers for authentication and account creation is that organizations are likely to have a home-grown account registration process.

  • Account Creation Driver configuration settings, e.g. host name, port, etc., as required by the particular driver. Note that this is per authority, not per driver, i.e., you can have multiple authorities with the same driver but different configuration options.

  • RegistrationUrl - instead of registering using OpenACS, redirect to a certain URL site for account registration.

  • RegistrationAllowed - true/false, so you can disable registration using this account.

  • Sort order: Preference order of authorities.

  • HelpContactText: Text or HTML to be displayed when user has trouble authenticating with the authority. Should include contact information such as a phone number or email.

Each authority driver will have a set of configuration options dependent on the driver, such as host, port, etc. We will need to find a mechanism for the driver to tell us which configuration options are available, a way to set these, and a way for the driver to access these settings.

OpenACS will come pre-configured with one authority, which is the "local" authority, meaning we'll authenticate as normal using the local users table. This will, just like any other authority, be implemetned using a service contract.

Feature Status Description
Synchronizing and linking users
EXT-AUTH-28 A Create service contract for Batch Sync.
EXT-AUTH-38 A Batch User Synchronization API
EXT-AUTH-38 A IMS Synchronization driver
EXT-AUTH-08 A Automation of batch Synchronization
EXT-AUTH-15 B On-demand syncronization

Regardless of the login method, the user needs to have a row in the OpenACS users table. This can happen through a batch job, in real-time, or both in combination. We use the IMS Enterprise 1.1 specification.

Batch job means that we do a synchronization (import new users, modify changed, purge deleted) on a regular interval, e.g. every night. You can also decide to have a monthly full synchronization, plus daily incremental ones. That's up to you. The advantage is that you have all users in OpenACS, so when you search for a user, you'll see all the organization's users, not just those who happen to have used the OpenACS-based system. The down-side is that it takes some time for user information to propagate. This can be remedied by using the real-time solution. The batch job will also require error logging and an admin interface to view logs.

If an email already belongs to some other user, we log it as an error.

A user will always belong to exactly one authority, which can be either the "local" authority or some other. Thus, the OpenACS user's table will have to be augmented with the following columns:

  • Authority. Reference to the site-wide authorities list. The authority which can authenticate this user.

  • Authority-specific username.

Real-time means that the first time the user logs into OpenACS, we'll query the authority that authenticated him for information about this user. That authentication authority will then give us at least first names, last name and email. The pros and cons are the opposite of batch jobs. Using both in combination is ideal.

Note: One solution to the "two users from different authorities have the same email" problem above would be to allow users to belong to multiple authorities. Then we would notice that the email already exists, ask the user if he thinks he's the same person, and if so, ask him to prove so by authenticating using the other authority. Thus he'll have just authenticated in two different authorities, and we can record that this is the same person. We'd still have a problem if there was an email conflict between two accounts on the same authority. Hm. I don't think it's worth spending too much time trying to solve this problem through software.

Feature Status Description
EXT-AUTH-31
EXT-AUTH-31 A Upgrade user data model for ext-auth

After having authenticated using the relevant authority driver, we'll look for the username/authority pair in the users table.

If we don't find any, that means that we're either not doing batch synchronizing, or that the user has been added since the last sync. In that case, we'll try to do a real-time synchronization, if the driver supports it. If it does, it'll return email, first_names, last_name, and other relevant information, and we'll create a row in the local users table using that information.

If that doesn't work, we'll tell the user that their account isn't yet available, and the driver will supply a message for us, which could say "The account should be available tomorrow. If not, contact X."

If a user doesn't have an account, the site-wide configuration can allow the user to register for one, as defined in the configuration discussed above. This section is about normal account registration through a authority driver.

The account creation service contract implementation will need to tell us which information to ask the user for:

  • Required Fields: A list of fields which are required.

  • Optional Fields: A list of fields which are optional.

The fields to choose from are these:

  • Username

  • First names

  • Last name

  • Email

  • URL

  • Password

  • Secret question

  • Secret answer

It should return the following:

  • Creation status (OK, Try-Again, Fail)

  • Creation message: What went wrong, or a welcome message.

  • Account status: Is the account ready for use?

  • User information: first_names, last_name, email, url, password, password_hash, secret_question, secret_answer. The driver only needs to return the columns which were changed or added through the registration process. Typically, only the "local" driver will return password and secret question/answer.

After creating the remote account, a local account is created with the information gathered through the form/returned by the driver.

By default, a local account creation implementation is provided, which will create a new OpenACS user, and, in addition to the default local account creation above, also store the password in hashed form.

Password management is about changing password, retrieving password, and resetting password.

It's up to the authority driver implementation to decide whether to support any or all of these features, and to say so using the CanXXX methods (see driver API below).

Additionally, the authority can be configured with a URL to redirect to in the case of forgotten passwords, or when the user desires to change password.

Feature Status Description
EXT-AUTH-20
EXT-AUTH-20 A Login over HTTPS

Login pages must be able to be sent over a secure connection (https), so your password won't get sent over the wire in cleartext, while leaving the rest of the site non-secure (http). I believe that this requires some (minor) changes to the current session handling code.

Email verification needs to be handled both at registration and at login.

In both cases, it'll be handled by the driver sending automatically sending the email containing a link for the user to verify his account. Then the driver will return an account status of "closed,temporary", and a message that says "Check your inbox and click the link in the email".

OpenACS will have a page which receives the email verification, for use by local accounts. Other authorities will have to implement their own page, most likely on the authority's own server.

There are a number of items which touch on external authentication and session management. And even though they're not directly linked to external authentication, I would recommend that we handle a number of them, either because they're important for security, or because it makes sense to fix them while we're messing with this part of the codebase anyway.

Feature Status Description
EXT-AUTH-33
EXT-AUTH-33 A Untrusted Logins

I like the idea of having multiple login levels:

  1. Not logged in

  2. Untrusted login: We'll show you un-sensitive personal content, but won't let you modify anything or see personal data. A normal login becomes untrusted after a certain amount of time, and the user will have to re-enter his/her password in order to gain access to personal data. Untrusted login never expires, unless explicitly done so through either changing password or clicking a special "expire all logins" link.

  3. Normal login: The user is logged, and has type his password sufficiently recently that we trust the login. All normal operations are allowed. Will degrade to untrusted login after a specified amount of time.

  4. Secure login: The user is logged in over a secure connection (HTTPS), potentially even using a special secure password. This would be for sensitive actions, such as credit card transactions.

There are two advantages to this. First, when people's login expires, we can ask them to re-enter only their password, and not both username and password, since we'll still remember who they were the last time their login was valid. This is a much faster operation (the password input field will be focused by default, so you just type your password and hit Return) that typing both username and password, which will make it practical to have your site configured to expire people's login after e.g. 2, 4, or 8 hours.

The other advantage is that we can still offer certain functionality to you, even when your login is not trusted. For example, we could let you browse publically available forums, and only when you want to post do you need to log in. This makes it even more feasible to have a more secure login expiration setting.

By default, auth::require_login would bounce to the login page if the user is only logged in at the untrusted level. Only if you explicitly say auth::require_login -untrusted will we give you the user_id of a user who's only logged in in untrusted mode.

Similarly, ad_conn user_id will continue to return 0 (not logged in) when the user is only logged in untrusted, and we'll supply another variable, ad_conn untrusted_user_id, which wlll be set to the user_id for all login levels.

This should ensure that we get full access to the new feature, while leaving all current code just as secure as it was before.

Feature Status Description
EXT-AUTH-34
EXT-AUTH-34 A Expire Logins

Currently, OpenACS is unusable in practice without persistent login. The login will expire after just a few minutes of inactivity, and you'll get bounced to the login page where you have to enter both email and password again. Unacceptable in practice.

We should change the default, so a non-persistent login doesn't expire until you either close your browser, or a few hours have elapsed. Even if you are constantly active, the login should still expire after at most x number of hours. We can still make the login expire after a period of inactivity, but the amount of time should be configurable and default to something reasonable like an hour or so.

This will require looking into and changing the design of the current session handling code.

Feature Status Description
EXT-AUTH-23
EXT-AUTH-23 Single sign-on

Instead of redirecting to the login page, auth::require_login can redirect to an authentication server, which can redirect back to a page that logs the user in. This should be very easy to implement.

Alternatively, if you want to combine this with fallback to OpenACS accounts, we would instead present the normal login screen, but put a button which says "Login using X", where X is the redirection-based external authority.

Feature Status Description
EXT-AUTH-22
EXT-AUTH-22 B rewrite cookie handling

Currently, if you've ever left a permanent login cookie on someone elses machine, that person will be forever logged in until he/she explicitly logs out. You can change your password, you can do anything you want, but unless a logout is requested from that particular browser, that browser will be logged in forever.

I want to change our session handling code so that old login cookies can be expired. This would be done automatically whenever you change your password, and we could also offer a link which does this without changing passwords. It's an important security measure.

The implementation is simply to autogenerate some secret token which is stored in the users table, and is also stored in the cookie somehow. Then, whenever we want to expire all logins, we'll just regenerate a new secret token, and the other cookies will be void. Of course, we don't want to incur a DB hit on every request, so we'll need to cache the secret token, or only check it when refreshing the session cookie, which, I believe, normally happens every 10 minutes or so.

Feature Status Description
EXT-AUTH-24
EXT-AUTH-24 A Email on password change

As an additional security measure, we should email the account's email address whenever the password is changed, so that he/she is at least alerted to the fact.

Feature Status Description
EXT-AUTH-25
EXT-AUTH-25 A Implement password policy

Again, to increase security, we should add password policies, such as passwords needing to be changed after a certain number of days, change on next login (after a new random password has been generated), or requiring that the password satisfies certain complexity rules, i.e. both upper and lowercase characters, numbers, special chars, etc.

It would good to extend the current maximum password length from 10 to at least 32 characters.

Feature Status Description
EXT-AUTH-26
EXT-AUTH-26 B Login without explicit domain

In order to make it easier for people, we've been toying with the idea of a functionality like this:

  • If the user enters "foobar@ix.urz.uni-heidelberg.de", it is translated to mean username = "foobar", authority = "ix.urz.uni-heidelberg.de".

  • If the user enters "foobar", it's recognized to not include any authority, and the default authority of "ix.urz.uni-heidelberg.de" is used.

  • If the user enters "foo@bar.com", it's recognized as not belonging to any known authority, and as such, it's translated to mean username = "foo@bar.com", authority = "local".

If this is deemed desirable, a way to implement this would be through these settings:

  • Split: A regexp which will split the user's entry into username and authority parts. For example "^([^@]+)(@[^@]+)?$". An easier to use but less flexible method would be that you simply specify a certain character to split by, such as "@" or "\". If the regexp doesn't match, or in the latter case, if there's more than one occurrence of the specified character sequence, the split will fail, signaling that the user's entry was not valid.

  • Default authority: The default authority will be the first one in the sort order.

The relevant code in user-login.tcl would look like this:

if { ![auth::split_username -username_var username -authority_var authority] } {
    # bounce back to the form with a message saying that the login wasn't valid.
    ad_script_abort
}

# username will now contain username
# authority will now contain authority
Feature Status Description
EXT-AUTH-27
EXT-AUTH-27 B Who's online list

While we're touching the session handling code, anyway, it would be nice to add a feature to show who's currently online, a nice real-time collaboration feature frequently requested by members of the community. This is particularly interesting when integrated with a chat or instant messaging service like Jabber.

What I'm concretely suggesting is that we keep a record of which authenticated users have requested pags on the site in the last x minutes (typically about 5), and thus are considered to be currently online. There's nothing more to it. This lets us display a list of "active users" somewhere on the site, and make their name a link to a real-time chat service like Jabber.

We've already made the changes necessary to security-procs.tcl to do this on an earlier project, but haven't quite finished the work and put it back into the tree.

Feature Status Description
EXT-AUTH-28
EXT-AUTH-28 implement subsite-level config

If we want to, we could let subsite administrators configure the login process for that particular subsite. This would probably only entail letting the subsite admin leave out certain authorities defined site-wide, and change the sort order.

I think we should leave this out until we have a use case for it, someone who'd need it.

Feature Status Description
EXT-AUTH-32
EXT-AUTH-32 A Parameters for Service Contract Implementation
EXT-AUTH-35 A Make the Authentication API a service contract

For completely free-form authentication logic and mechanisms, something like Andrew Grumet's Pluggable Authentication for OACS Draft is interesting. He's proposing a scheme where the entire user interaction is encapsulated in, and left entirely to, a service contract. This certainly opens up more advanced possibilities, such as perhaps smart cards, personal certificates, etc.

I have chosen not to go this route, because I think that most people are going to want to use a username/password-based scheme, and having easy configuration through a web UI is more important than total flexibility at this point.

Besides, we can always do this in the future, by letting the public Authentication API (auth::require_login and auth::authenticate) be implemented through a service contract.

Feature Status Description
EXT-AUTH-36
EXT-AUTH-36 A Authenticate against multiple servers

Both OKI and OpenACS supports a form of stacking, where you can be logged into multiple authorities at the same time. This is useful if, for example, you need to get login tokens such as Kerberos tickets for access to shared resources.

I can see the value in this, but for simplicity's sake, I'm in favor of keeping this use-case out of the loop until we have someone with a real requirement who could help us guide development.

For now, OpenACS is still more of an integrated suite, it doesn't access many outside applications. I think it would be excellent for OpenACS to do so, e.g. by using an IMAP server to store emails, an iCal server to store calendar appointments, LDAP for user/group data and access control lists, SMB for file storage, etc. But at the moment, we don't have any users of such things that are ready. We have some who are on the steps, but let's wait till they're there.

Feature Status Description
Implement specific drivers
EXT-AUTH-09 A Create Auth. drivers for Local Authority
EXT-AUTH-10 A Create Acct. Creation driver for Local Authority
EXT-AUTH-11 A Create Auth. driver for PAM
EXT-AUTH-12 X Create Acct. Creation driver for PAM - this functionality is explicitly excluded from PAM
EXT-AUTH-13 A Create Acct. Creation driver for LDAP
EXT-AUTH-14 A Create Auth. driver for LDAP

We'll need drivers for:

  • Operating system (Linux/Solaris) PAM: Delegate to the operating system, which can then talk to RADIUS, LDAP, whatever. This is convenient because there'll be plenty of drivers for the OS PAM level, so we don't have to write them all ourselves. The downside is that we can't do things like account creation, password management, real-time account synchronization, etc., not supported by PAM (I'm not entirely sure what is and is not supported).

  • RADIUS

  • LDAP

RADIUS is a simple username/password-type authentication server.

It also supports sending a challenge to which the user must respond with the proper answer (e.g. mother's maiden name, or could be additional password), but we will not support this feature.

A RADIUS client implementation in Python can be found in the exUserFolder module for Zope (documentation).

We'd really appreciate feedback on this proposal. Please follow up at this openacs.org forums thread.

Document Revision # Action Taken, Notes When? By Whom?
1 Updated work-in-progress for consortium-sponsored ext-auth work at Collaboraid. 20 Aug 2003 Joel Aufrecht

TCLWebtest

Created by Anett Szabo, last modified by Gustaf Neumann 17 Feb 2008, at 07:08 AM

PL/SQL Standards

Created by Gustaf Neumann, last modified by Gustaf Neumann 17 Feb 2008, at 07:08 AM

By Richard Li and Yon Feldman

OpenACS docs are written by the named authors, and may be edited by OpenACS documentation staff.

Like any other part of the OpenACS, PL/SQL (or pl/pgsql) code must be maintainable and professional. This means that it must be consistent and therefore must abide by certain standards. The standards will ensure that our product will be useful long after the current people building and maintaining it are around. Following are some standards and guidelines that will help us achieve this goal:

  1. All PL/SQL code must be well documented. We must write code that is maintainable by others, this is especially true in our case because we are building an open source toolkit than anyone can download and browse the source code. So document like you are trying to impress your "Introduction to Programming" professor or TA.

  2. It is important to be consistent throughout an application as much as is possible given the nature of team development. This means carrying style and other conventions suchs as naming within an application, not just within one file.

  1. Encapsulation of related fuctionality is key to maintainability and upgradeability of our software. Try to bundle your code into packages whenever possible. This will make upgrading, bug fixing, and customizing, among other things, a possibility.

  2. When creating functions or procedures use the following template, it demonstrates most of the guidelines set forth in this document that correspond to functions and procedures:

            create or replace procedure|function <proc_or_func_name> (
                     <param_1>    in|out|inout <datatype>,
                     <param_2>    in|out|inout <datatype>,
                     ...
                     <param_n>    in|out|inout <datatype>
            )
            [return <datatype>]
            is
                    <local_var_1>    <datatype>
                    <local_var_2>    <datatype>
                    ...
                    <local_var_n>    <datatype>
            begin
                    ...
            end <proc_or_func_name>;
            /
            show errors
    
    
  3. Always use create or replace procedure|function <proc_or_func_name>. It makes reloading packages much easier and painless to someone who is upgrading or fixing a bug.

  4. Always qualify end statements, i.e., the end statement for a package should be end <package_name>;, not just end;; same goes for procedures, functions, package bodies, and triggers.

  5. Always use the "show errors" SQL*Plus command after each PL/SQL block. It will help you debug when there are compilation errors in your PL/SQL code.

  6. Name parameters as simply as possible, i.e., use the column name if the parameter corresponds to a table column. We're deprecating the v_* and *_in syntax in favor of named parameters notation:

    
            acs_user.create(first_names => 'Jane', last_name => 'Doe', etc.)
          
            instead of
          
            acs_user.create(first_names_in => 'Jane', last_name_in => 'Doe', etc.)
          
    

    To achieve this we must fully qualify arguements passed into procedures or functions when using them inside a SQL statement. This will get rid of any ambiguities in your code, i.e. it will tell the parser when you want the value of the column and when you want the value from the local variable. Here is an example:

            create or replace package body mypackage
                .
                .
                procedure myproc(party_id in parties.party_id%TYPE) is begin
                    .
                    .
                    delete
                      from parties
                     where party_id = myproc.party_id;
                    .
                    .
                end myproc;
                .
                .
            end mypackage;
            /
            show errors
    
    
  7. Explicitly designate each parameter as "in," "out," or "inout."

  8. Each parameter should be on its own line, with a tab after the parameter name, then in/out/inout, then a space, and finally the datatype.

  9. Use %TYPE and %ROWTYPE whenever possible.

  10. Use 't' and 'f' for booleans, not the PL/SQL "boolean" datatype because it can't be used in SQL queries.

  11. All new functions (e.g., acs_object.new, party.new, etc.) should optionally accept an ID:

    
            create or replace package acs_object
            as
                function new (
                    object_id       in acs_objects.object_id%TYPE default null,
                    object_type     in acs_objects.object_type%TYPE default 'acs_object',
    	        creation_date   in acs_objects.creation_date%TYPE default sysdate,
                    creation_user   in acs_objects.creation_user%TYPE default null,
                    creation_ip     in acs_objects.creation_ip%TYPE default null,
                    context_id      in acs_objects.context_id%TYPE default null
               ) return acs_objects.object_id%TYPE;
         

    takes the optional argument object_id. Do this to allow people to use the same API call when they are doing double click protection, that is, tehy have already gotten an object_id and now they want to create the object with that object_id.

Some general style guidelines to follow for the purpose of consistency across applications.

  1. Standard indentation is 4 spaces. Our PL/SQL code is not only viewable in the SQL files but also through our SQL and PL/SQL browsers. This means that we should try to make it as consistent as possible to all source code readers.

  2. Lowercase everything, with the exception of %TYPE and %ROWTYPE.

Upgrading Platform components

Created by Gustaf Neumann, last modified by Gustaf Neumann 17 Feb 2008, at 07:08 AM

OpenACS Full Text Search requires several pieces: the OpenFTS code, some database functions, and the OpenFTS Engine. This section describes how to upgrade OpenFTS from 0.2 to 0.3.2 and upgrade the search engine on an OpenACS site at the same time.

  1. Uninstall the old OpenFTS Engine from the $OPENACS_SERVICE_NAME database.

    1. Browse to http://yourserver/openfts.

    2. Click Administration.

    3. Click Drop OpenFTS Engine

  2. Build and install the new OpenFTS driver and supporting tcl procedures. (This section of shell code is not fully documented; please exercise care.)

    cd /usr/local/src/
              tar xzf /var/tmp/Search-OpenFTS-tcl-0.3.2.tar.gz
              chown -R root.root Search-OpenFTS-tcl-0.3.2/
              cd Search-OpenFTS-tcl-0.3.2/
              ./configure --with-aolserver-src=/usr/local/src/aolserver/aolserver --with-tcl=/usr/lib/
              cd aolserver/
              make
              

    Back up the old fts driver as a precaution and install the newly compiled one

    mv /usr/local/aolserver/bin/nsfts.so /usr/local/aolserver/bin/nsfts-0.2.so
              cp nsfts.so /usr/local/aolserver/bin
              

    Build and install the OpenFTS code for PostGresSQL

    cd /usr/local/src/Search-OpenFTS-tcl-0.3.2/
              cp -r pgsql_contrib_openfts /usr/local/src/postgresql-7.2.3/contrib /usr/local/src/postgresql-7.2.3/contrib/pgsql_contrib_openfts
              make
              su - postgres
              cd tsearch/
              make
              make install
              exit

    In order for the OpenACS 4.6 OpenFTS Engine to use the OpenFTS 0.3.2 driver, we need some commands added to the database.

    [root root]# su - $OPENACS_SERVICE_NAME
    
              [$OPENACS_SERVICE_NAME dev]$ psql $OPENACS_SERVICE_NAME -f /usr/local/pgsql/share/contrib/openfts.sql
              CREATE
              CREATE
              [$OPENACS_SERVICE_NAME dev]$ psql $OPENACS_SERVICE_NAME -f /usr/local/src/postgresql-7.2.3/contrib/tsearch/tsearch.sql
              BEGIN
              CREATE
              (~30 more lines)
              [$OPENACS_SERVICE_NAME dev]$ exit
              [root root]#
              su - $OPENACS_SERVICE_NAME
    psql $OPENACS_SERVICE_NAME -f /usr/local/pgsql/share/contrib/openfts.sql
    psql $OPENACS_SERVICE_NAME -f /usr/local/src/postgresql-7.2.3/contrib/tsearch/tsearch.sql
    exit
    
  3. OPTIONAL: Install the new OpenFTS Engine.If you want to upgrade the OpenFTS Engine, do these steps. (You must have already upgraded the OpenFTS driver to 0.3.2.)

    1. Browse to http://yourserver/admin/site-map

    2. On the openfts line, click on set parameters.

    3. Change the value of openfts_tcl_src_path from /usr/local/src/Search-OpenFTS-tcl-0.2/ to /usr/local/src/Search-OpenFTS-tcl-0.3.2/

    4. Click Set Parameters

    5. [root root]# restart-aolserver $OPENACS_SERVICE_NAME
      
    6. Browse to http://yourserver/openfts

    7. Click Administration.

    8. Click Initialize OpenFTS Engine

An OpenACS database created in PostGreSQL 7.2 will not work correctly in PostGreSQL 7.3. This is because 7.2 truncates function names to 31 characters, but 7.3 does not. This does not cause problems in 7.2, because truncation occurs both at function creation and at function calling, so they still match. But if you use a database created in 7.2 in 7.3, the function names in the database remain truncated but the function calls are not, and so they don't match. Also some functions use casting commands that no longer work in 7.3 and these functions must be recreated.

To upgrade an OpenACS site from PostGreSQL 7.2 to 7.3, first upgrade the kernel to 4.6.3. Then, dump the database, run the upgrade script /var/lib/aolserver/$OPENACS_SERVICE_NAME/bin/pg_7.2to7.3_upgrade_helper.pl on the dump file, and reply the dump. See Forum OpenACS Q&A: PG 7.2->7.3 upgrade gotcha?. Example:

  1. Back up the database as per PostgreSQL.

  2. Run the upgrade script on the backup file.

    [root root]# su - $OPENACS_SERVICE_NAME
    
              [$OPENACS_SERVICE_NAME $OPENACS_SERVICE_NAME]# cd /var/lib/aolserver/$OPENACS_SERVICE_NAME/bin
              [$OPENACS_SERVICE_NAME bin]$ ./pg_7.2to7.3_upgrade_helper.pl  ../database-backup/nightly.dmp  ../database-backup/upgrade-7.3.dmp  /var/lib/aolserver/$OPENACS_SERVICE_NAME
    
              ==================================================================
              looking for function acs_object__check_object_ancest in oacs
              grep result: /var/lib/aolserver/aufrecht-dev/packages/acs-kernel/sql/postgresql/acs-objects-create.sql:create function acs_object__check_object_ancestors (integer,integer,integer)
    
              replacing acs_object__check_object_ancest with acs_object__check_object_ancestors
    
              (many lines omitted)
              [$OPENACS_SERVICE_NAME bin]$
              
  3. Use perl to replace timestamp with timestamptz in the dump file. See example perl code in step two in /contrib/misc/upgrade_4.6_to_5.0.sh

  4. Create a new user for PostgreSQL 7.3.x, as per the Postgres installation guide. Keep in mind that your installation location is different, and your startup script (/etc/init.d/postgres73 should be named differently. You might even need to edit that file to make the paths correct). You'll also need to add export PGPORT=5434 to the .bashrc and/or .bash_profile for the postgres73 user.

  5. Install PostgreSQL 7.3.x. Note that you PostgreSQL must listen on a different port in order to work correctly, so you'll need to edit the configuration file (/usr/local/pgsql73/data/postgresql.conf) and change the port (to 5433, say). create a second postgres user to differentiate between the two postgres installs. When you do ./configure, you'll need to include --prefix=$HOME to ensure that it is installed in the postgres73 user's home directory.

  6. Change the path in $OPENACS_SERVICE_NAME's .bashrc or .bash_profile (or both) files to reflect the new postgres73 user directory. Also add in the PGPORT.

  7. Restore the database from dump as per the recovery instructions.

Install Full Text Search

Created by Gustaf Neumann, last modified by Gustaf Neumann 17 Feb 2008, at 07:08 AM

By Joel Aufrecht and Malte Sussdorff

OpenACS docs are written by the named authors, and may be edited by OpenACS documentation staff.

If you want full text search, and you are running PostgreSQL, install this module to support FTS. Do this step after you have installed both PostgreSQL and AOLserver. You will need the openfts tarball in /var/tmp.

  1. Install Tsearch. This is a PostgreSQL module that OpenFTS requires.

    [root root]# su - postgres
    [postgres pgsql]$ cd /usr/local/src/postgresql-7.3.4/contrib/tsearch/
    [postgres tsearch]$ make
    sed 's,MODULE_PATHNAME,$libdir/tsearch,g' tsearch.sql.in >tsearch.sql
    /usr/bin/flex  -8 -Ptsearch_yy -o'parser.c' parser.l(many lines omitted)
    rm -f libtsearch.so
    ln -s libtsearch.so.0.0 libtsearch.so
    [postgres tsearch]$ make install
    mkdir /usr/local/pgsql/share/contrib
    mkdir /usr/local/pgsql/doc/contrib
    (2 lines omitted)
    /bin/sh ../../config/install-sh -c -m 755 libtsearch.so.0.0 /usr/local/pgsql/lib/tsearch.so
    [postgres tsearch]$ exit
    logout
    
    [root root]#
    su - postgres
    cd /usr/local/src/postgresql-7.3.4/contrib/tsearch
    make
    make install
    exit
    
  2. Unpack the OpenFTS tarball and compile and install the driver.

    [root root]# cd /usr/local/src
    [root src]# tar xzf /var/tmp/Search-OpenFTS-tcl-0.3.2.tar.gz
    [root src]# cd /usr/local/src/Search-OpenFTS-tcl-0.3.2/
    [root Search-OpenFTS-tcl-0.3.2]# ./configure --with-aolserver-src=/usr/local/src/aolserver/aolserver --with-tcl=/usr/lib/
    checking prefix... /usr/local
    checking for gcc... gcc
    (many lines omitted)
    configure: creating ./config.status
    config.status: creating Makefile.global
    [root Search-OpenFTS-tcl-0.3.2]# make
    (cd parser; make all)
    make[1]: Entering directory `/usr/local/src/Search-OpenFTS-tcl-0.3.2/parser'
    (many lines omitted)
    packages provided were {Lingua::Stem::Snowball 0.3.2}
    processed fts_base_snowball.tcl
    [root Search-OpenFTS-tcl-0.3.2]# cd aolserver
    [root aolserver]# make
    gcc -c -fPIC  -DPACKAGE=\"OPENFTS\" -DVERSION=\"0.3.2\" -DHAVE_UNISTD_H=1 -DSTDC_HEADERS=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STR
    (many lines omitted)
    n_stem.o italian_stem.o norwegian_stem.o portuguese_stem.o russian_stem.o nsfts.o  -o nsfts.so
    [root aolserver]# cp nsfts.so /usr/local/aolserver/bin/
    [root aolserver]#
    cd /usr/local/src
    tar xzf /var/tmp/Search-OpenFTS-tcl-0.3.2.tar.gz
    cd /usr/local/src/Search-OpenFTS-tcl-0.3.2/
    ./configure --with-aolserver-src=/usr/local/src/aolserver/aolserver --with-tcl=/usr/lib/
    make
    cd aolserver
    make
    cp nsfts.so /usr/local/aolserver/bin
    
    
  3. Build some supplemental modules.

    [root aolserver]# cd /usr/local/src/Search-OpenFTS-tcl-0.3.2
    [root Search-OpenFTS-tcl-0.3.2]# cp -r pgsql_contrib_openfts /usr/local/src/postgresql-7.3.4/contrib
    [root Search-OpenFTS-tcl-0.3.2]# cd /usr/local/src/postgresql-7.3.4/contrib/pgsql_contrib_openfts
    [root pgsql_contrib_openfts]# make
    sed 's,MODULE_PATHNAME,$libdir/openfts,g' openfts.sql.in >openfts.sql
    gcc -O2 -Wall -Wmissing-prototypes -Wmissing-declarations -fpic -I. -I../../src/include   -c -o openfts.o openfts.c
    gcc -shared -o openfts.so openfts.o
    rm openfts.o
    [root pgsql_contrib_openfts]# su postgres
    [postgres pgsql_contrib_openfts]$ make install
    /bin/sh ../../config/install-sh -c -m 644 openfts.sql /usr/local/pgsql/share/contrib
    /bin/sh ../../config/install-sh -c -m 755 openfts.so /usr/local/pgsql/lib
    /bin/sh ../../config/install-sh -c -m 644 ./README.openfts /usr/local/pgsql/doc/contrib
    [postgres pgsql_contrib_openfts]$ exit
    [root pgsql_contrib_openfts]#
    cd /usr/local/src/Search-OpenFTS-tcl-0.3.2
    cp -r pgsql_contrib_openfts /usr/local/src/postgresql-7.3.4/contrib
    cd /usr/local/src/postgresql-7.3.4/contrib/pgsql_contrib_openfts
    make
    su postgres
    make install
    exit
    

If you are installing Full Text Search, add required packages to the new database. (In order for full text search to work, you must also install the PostgreSQL OpenFTS module and prerequisites.)

[$OPENACS_SERVICE_NAME $OPENACS_SERVICE_NAME]$ /usr/local/pgsql/bin/psql $OPENACS_SERVICE_NAME -f /usr/local/src/postgresql-7.3.4/contrib/tsearch/tsearch.sql
BEGIN
CREATE
(many lines omitted)
INSERT 0 1
COMMIT
[$OPENACS_SERVICE_NAME $OPENACS_SERVICE_NAME]$ /usr/local/pgsql/bin/psql $OPENACS_SERVICE_NAME -f /usr/local/src/postgresql-7.3.4/contrib/pgsql_contrib_openfts/openfts.sql
CREATE
CREATE
[$OPENACS_SERVICE_NAME $OPENACS_SERVICE_NAME]$
/usr/local/pgsql/bin/psql $OPENACS_SERVICE_NAME -f /usr/local/src/postgresql-7.3.4/contrib/tsearch/tsearch.sql
/usr/local/pgsql/bin/psql $OPENACS_SERVICE_NAME -f /usr/local/src/postgresql-7.3.4/contrib/pgsql_contrib_openfts/openfts.sql

Note

If you get the error ERROR: could not access file "$libdir/tsearch": no such file or directory It is probably because PostgreSQL's libdir configuration variable points to a diffent directory than where tsearch is. You can find out where PostgreSQL expects to find tsearch via

pg_config --pkglibdir

If you have installed OpenFTS, you can enable it for this service. Uncomment this line from config.tcl. (To uncomment a line in a tcl file, remove the # at the beginning of the line.)

#ns_param   nsfts           ${bindir}/nsfts.so
  1. Click Admin on the top of the default home page. If prompted, log in with the account and password you entered during install.

  2. Click on the Install software link.

  3. Click on the Install new service link.

  4. Click on the Install link next to OpenFTS Driver.

  5. Restart the service.

    [$OPENACS_SERVICE_NAME $OPENACS_SERVICE_NAME]$ svc -t /service/$OPENACS_SERVICE_NAME
    
    [$OPENACS_SERVICE_NAME $OPENACS_SERVICE_NAME]$
  6. Wait a minute, then browse back to the home page.

  7. Click on Admin on the top of the screen.

  8. Click on Main Site Administration in the "Subsite Administration" section.

  9. Click on Site Map in the "Advanced Features" section.

  10. Mount the OpenFTS Full Text Search Engine in the site map.

    1. Click the new sub folder link on the "/" line, the first line under Main Site:/.

    2. Type openfts and click New.

    3. On the new openfts line, click the mount link.

    4. Click OpenFTS Driver.

    5. On the openfts line, click set parameters.

    6. Change openfts_tcl_src_path to /usr/local/src/Search-OpenFTS-tcl-0.3.2/ and click Set Parameters

  11. Mount the Search interface in the site map.

    1. Click the new sub folder link on the Main Site line.

    2. Type search and click New.

    3. Click the new application link on the search line.

    4. Type search where it says untitled, choose search from the drop-down list, and click New.

  12. Restart the service.

    [$OPENACS_SERVICE_NAME $OPENACS_SERVICE_NAME]$ svc -t /service/$OPENACS_SERVICE_NAME
    
    [$OPENACS_SERVICE_NAME $OPENACS_SERVICE_NAME]$
  13. Wait a minute, then click on Main Site at the top of the page.

  14. Initialize the OpenFTS Engine. This creates a set of tables in the database to support FTS.

    Near the bottom of the page, click on the OpenFTS Driver link. Click on Administration. Click on Initialize OpenFTS Engine. Click Initialize OpenFTS Engine.

  15. Add the FTS Engine service contract

    1. Click on the DevAdmin.

    2. Click on the Service Contract link.

    3. On the FtsEngineDriver line, click Install.

  16. Restart the service.

    [$OPENACS_SERVICE_NAME $OPENACS_SERVICE_NAME]$ svc -t /service/$OPENACS_SERVICE_NAME
    
    [$OPENACS_SERVICE_NAME $OPENACS_SERVICE_NAME]$

Enabling Full Text Search in packages at the moment is not trivial. It involves a couple of steps, which I will illustrate taking lars-blogger as an example package

  1. Install the package.

    1. Click Admin on the top of the default home page. If prompted, log in with the account and password you entered during install.

    2. Click on the Install software link.

    3. Click on the Install new application link.

    4. Click on the Install link next to Weblogger.

    5. Install all required packages as well (always say okay until you shall restart the server)

  2. Load the service contracts datamodell and enable the service contract

    [$OPENACS_SERVICE_NAME $OPENACS_SERVICE_NAME]$ cd packages/lars-blogger/sql/postgresql
    [$OPENACS_SERVICE_NAME postgresql]$ psql $OPENACS_SERVICE_NAME -f lars-blogger-sc-create.sql

    Note: Usually this script is called package_name-sc-create.sql

  3. Restart the service.

    [$OPENACS_SERVICE_NAME postgresql]$ svc -t /service/$OPENACS_SERVICE_NAME
    
                    [$OPENACS_SERVICE_NAME postgresl]$

If you are lucky, Full Text Search is enabled now, if not consult http://openacs.org/forums/message-view?message_id=154759. This link also contains some hints on how to make sure it is enabled.

Package Manager Design

Created by Gustaf Neumann, last modified by Gustaf Neumann 17 Feb 2008, at 07:08 AM

By Bryan Quinn

OpenACS docs are written by the named authors, and may be edited by OpenACS documentation staff.

In general terms, a package is a unit of software that serves a single well-defined purpose. That purpose may be to provide a service directly to one or more classes of end-user, (e.g., discussion forums and file storage for community members, user profiling tools for the site publisher), or it may be to act as a building block for other packages (e.g., an application programming interface (API) for storing and querying access control rules, or an API for scheduling email alerts). Thus, packages fall into one of two categories:

An installation of the OpenACS includes the OpenACS Kernel, some services that extend the kernel's functionality, and some applications intended for end-users. Packages function as individual pieces of subsites. A subsite can contain multiple application and service instances that provide the end-user with capabilities and content customized to the particular subsite.

This architecture supports the growth of collaborative commerce. For example, Jane User starts a forum focusing on the merits of View Cameras by creating an instance of the Forum application for her personal subsite on an OpenACS Installation. Jack User discovers Jane's forum and includes a link to it in his subsite. As interest in Jane's forum grows, she creates a subsite specializing in providing information about View cameras. This subsite now includes several package instances beyond Forum; it could potentially include its own Ecommerce capabilities (ala Yahoo! Shopping). This could include a knowledge management application that allows users to spread expertise about view cameras and a portal application that links to reliable camera models and resellers. Any subsite enabled package that is added to the OpenACS installation through APM is another potential package instance that can become part of Jane's View Camera subsite.

The APM provides an architecture for packaging software, making instances of that software available to subsites, specifying configuration parameters for each instance, and managing the creation and release of new packages.

Prior to ACS 3.3, all packages were lumped together into one monolithic distribution without explicit boundaries; the only way to ascertain what comprised a given package was to look at the top of the corresponding documentation page, where, by convention, the package developer would specify where to find:

  • the data model

  • the Tcl procedures

  • the user-accessible pages

  • the administration pages

Experience has shown us that this lack of explicit boundaries causes a number of maintainability problems for pre-3.3 installations:

  1. Package interfaces were not guaranteed to be stable in any formal way, so a change in the interface of one package would often break dependent packages (which we would only discover through manual regression testing). In this context, any of the following could constitute an interface change:

    • renaming a file or directory that appears in a URL

    • changing what form variables are expected as input by a page

    • changing a procedural abstraction, e.g., a PL/SQL or Java stored procedure or a Tcl procedure

    • changing a functional abstraction, e.g., a database view or a PL/SQL or Java stored function

    • changing the data model

    This last point is especially important. In most cases, changing the data model should not affect dependent packages. Rather, the package interface should provide a level of abstraction above the data model (as well as the rest of the package implementation). Then, users of the package can take advantage of implementation improvements that don't affect the interface (e.g., faster performance from intelligent denormalization of the data model), without having to worry that code outside the package will now break.

  2. A typical ACS-backed site only uses a few of the modules included in the distribution, yet there was no well-understood way to pick only what you needed when installing the ACS, or even to uninstall what you didn't need, post-installation. Unwanted code had to be removed manually.

  3. Releasing a new version of the ACS was complicated, owing again to the monolithic nature of the software. Since we released everything in the ACS together, all threads of ACS development had to converge on a single deadline, after which we would undertake a focused QA effort whose scale increased in direct proportion to the expansion of the ACS codebase.

  4. There was no standard way for developers outside of ArsDigita to extend the ACS with their own packages. Along the same lines, ArsDigita programmers working on client projects had no standard way to keep custom development cleanly separated from ACS code. Consequently, upgrading an already installed ACS was an error-prone and time-consuming process.

Consistent use of the APM format and tools will go a long way toward solving the maintainability problems listed above. Moreover, APM is the substrate that will enable us to establish a central package repository, where developers will be able publish their packages for other OpenACS users to download and install.

For a simple illustration of the difference between ACS without APM (pre-3.3) and ACS with APM (3.3 and beyond), consider a hypothetical ACS installation that uses only two of the thirty-odd modules available circa ACS 3.2 (say, bboard and e-commerce):

APM itself is part of a package, the OpenACS Kernel, an OpenACS service that is the only mandatory component of an OpenACS installation.

The OpenACS is a platform for web-based application software, and any software platform has the potential to develop problems like those described above. Fortunately, there are many precedents for systematic solutions, including:

Borrowing from all of the above, OpenACS 3.3 introduces its own package management system, the OpenACS Package Manager (APM), which consists of:

  • a standard format for APM packages (also called "OpenACS packages"), including:

    • version numbering, independent of any other package and the OpenACS as a whole

    • specification of the package interface

    • specification of dependencies on other packages (if any)

    • attribution (who wrote it) and ownership (who maintains it)

  • web-based tools for package management:

    • obtaining packages from a remote distribution point

    • installing packages, if and only if:

      1. all prerequisite packages are installed

      2. no conflicts will be created by the installation

    • configuring packages (obsoleting the monolithic OpenACS configuration file)

    • upgrading packages, without clobbering local modifications

    • uninstalling unwanted packages

  • a registry of installed packages, database-backed and integrated with filesystem-based version control

  • web-based tools for package development:

    • creating new packages locally

    • releasing new versions of locally-created packages

The design chosen for APM was meant to satisfy the following constraints:

  • The process of authoring a package must be as simple as possible.

  • Strict conventions must be established that provide a set of canonical locations and names for files and patterns, for OpenACS application development.

  • The processes of installing, upgrading, and using packages must be straightforward and accessible through a web-based UI.

  • Package instances must be able to have subsite-specific content available at an easily configurable URL.

All of these requirements were met, but at the cost of development simplicity. As Packages demonstrates, a set of strict directory conventions are required in order for a package to use APM. This contrasts with the apparent simplicity available to developers of the OpenACS 3.3 system. However, while the system has become more complex for developers to build packages, this complexity is easily managed and is compensated for by additional capabilities.

For example, to make a new application available to the system, a developer must:

  1. Create the necessary files to support the data model, Tcl API, and UI pages.

  2. Put the files in the correct locations for APM to be aware of them.

  3. Use APM to create a new package and enable it.

  4. Use the Site Map facility to create an instance of the package, mount it on an appropriate URL, and set parameters for that particular instance.

While this is complex, especially to a new OpenACS developer, the documentation walks the developer through each of these steps. Moreover, from following these steps, the package can be subsite specific, available to subsites across the system, and be available for distribution to other OpenACS installations without doing a monolithic upgrade or reinstall.

The APM is composed of systems for accomplishing a set of package-related tasks. Each of these tasks comprise a feature area that has an API, data model, and a UI:

  • Authoring a Package

  • Maintaining Multiple Versions of a Package

  • Creating Instances of the Package

  • Specifying Configuration Parameters for each Instance

Authoring a Package

Full instructions on how to prepare an OpenACS package are available in Packages. The API here can be invoked manually by a package's data model creation script, but need not to be used. This API is part of the APM PL/SQL package.

-- Informs the APM that this application is available for use.
procedure register_application (
    package_key         in apm_package_types.package_key%TYPE,
    pretty_name         in apm_package_types.pretty_name%TYPE,
    pretty_plural       in apm_package_types.pretty_plural%TYPE,
    package_uri         in apm_package_types.package_uri%TYPE,
    singleton_p         in apm_package_types.singleton_p%TYPE
                                default 'f',
    spec_file_path      in apm_package_types.spec_file_path%TYPE
                                default null,
    spec_file_mtime     in apm_package_types.spec_file_mtime%TYPE
                                default null
);

The procedure above registers an OpenACS application in the APM. It creates a new OpenACS object and stores information about the package, such as its name, in the APM data model. There is an analogous procedure for OpenACS services, called apm.register_service.

To remove an application from the system, there are the calls apm.unregister_application and apm.unregister_service.

-- Remove the application from the system.
procedure unregister_application (
    package_key     in apm_package_types.package_key%TYPE,
    -- Delete all objects associated with this application.
    cascade_p       in char default 'f'
);

Use the cascade_p only if you want to completely remove the package from the OpenACS.

In order to determine if a particular package exists in the system, use the register_p predicate. It returns 1 if the specified package_key exists in the system, 0 otherwise.

function register_p (
    package_key     in apm_package_types.package_key%TYPE
) return integer;

Maintaining Multiple Versions of a Package

While the package authoring API provides a means for registering a package, some information about a package is version dependent. For example, between versions, the owner of a package, its vendor, its URI, and its dependency information may change. The API for package versions allows this information to be specified. All of these APIs are part of the apm_package_version PL/SQL package.

To create a new package version, use the apm_package_version.new constructor function.

function new (
    version_id          in apm_package_versions.version_id%TYPE
                default null,
    package_key         in apm_package_versions.package_key%TYPE,
    version_name        in apm_package_versions.version_name%TYPE
                                default null,
    version_uri         in apm_package_versions.version_uri%TYPE,
    summary         in apm_package_versions.summary%TYPE,
    description_format      in apm_package_versions.description_format%TYPE,
    description         in apm_package_versions.description%TYPE,
    release_date        in apm_package_versions.release_date%TYPE,
    vendor          in apm_package_versions.vendor%TYPE,
    vendor_uri          in apm_package_versions.vendor_uri%TYPE,
    installed_p         in apm_package_versions.installed_p%TYPE
                                default 'f',
    data_model_loaded_p     in apm_package_versions.data_model_loaded_p%TYPE
                        default 'f'
) return apm_package_versions.version_id%TYPE;

In order to use this function, an existing package_key must be specified. The version_name parameter must follow a strict convention:

  1. A major version number

  2. at least one minor version number. Although any number of minor version numbers may be included, three minor version numbers is sufficient and is the convention of software developers.

  3. One of the following:

    • The letter d, indicating a development-only version

    • The letter a, indicating an alpha release

    • The letter b, indicating a beta release

    • No letter at all, indicating a final production release

In addition, the letters d, a, and b may be followed by another integer, indicating a version within the release.

For those who like regular expressions:

version_number := ^[0-9]+((\.[0-9]+)+((d|a|b|)[0-9]?)?)$

So the following is a valid progression for version numbers:

0.9d, 0.9d1, 0.9a1, 0.9b1, 0.9b2, 0.9, 1.0, 1.0.1, 1.1b1, 1.1

To delete a given version of a package, use the apm_package_version.delete procedure:

procedure delete (
    package_id      in apm_packages.package_id%TYPE
);

After creating a version, it is possible to edit the information associated with it using apm_package_version.edit.

function edit (
      new_version_id        in apm_package_versions.version_id%TYPE
                default null,
      version_id        in apm_package_versions.version_id%TYPE,
      version_name      in apm_package_versions.version_name%TYPE
                default null,
      version_uri       in apm_package_versions.version_uri%TYPE,
      summary           in apm_package_versions.summary%TYPE,
      description_format    in apm_package_versions.description_format%TYPE,
      description       in apm_package_versions.description%TYPE,
      release_date      in apm_package_versions.release_date%TYPE,
      vendor            in apm_package_versions.vendor%TYPE,
      vendor_uri        in apm_package_versions.vendor_uri%TYPE,
      installed_p       in apm_package_versions.installed_p%TYPE
                default 'f',
      data_model_loaded_p   in apm_package_versions.data_model_loaded_p%TYPE
                default 'f'
) return apm_package_versions.version_id%TYPE;

Versions can be enabled or disabled. Enabling a version instructs APM to source the package's libraries on startup and to make the package available to the OpenACS.

procedure enable (
    version_id          in apm_package_versions.version_id%TYPE
);

procedure disable (
    version_id          in apm_package_versions.version_id%TYPE
);

Files associated with a version can be added and removed. The path is relative to the package-root which is acs-server-root/packages/package-key.

-- Add a file to the indicated version.
function add_file(
    file_id             in apm_package_files.file_id%TYPE
                        default null,
    version_id in       apm_package_versions.version_id%TYPE,
    path                in apm_package_files.path%TYPE,
    file_type           in apm_package_file_types.file_type_key%TYPE
) return apm_package_files.file_id%TYPE;

-- Remove a file from the indicated version.
procedure remove_file(
    version_id          in apm_package_versions.version_id%TYPE,
    path                in apm_package_files.path%TYPE
);

Package versions need to indicate that they provide interfaces for other software. An interface is an API that other packages can access and utilize. Interfaces are identified as a URI and a version name, that comply with the specification of a version name for package URIs.

-- Add an interface provided by this version.
function add_interface(
    interface_id        in apm_package_dependencies.dependency_id%TYPE
                    default null,
    version_id          in apm_package_versions.version_id%TYPE,
    interface_uri       in apm_package_dependencies.service_uri%TYPE,
    interface_version       in apm_package_dependencies.service_version%TYPE
) return apm_package_dependencies.dependency_id%TYPE;

procedure remove_interface(
    interface_id        in apm_package_dependencies.dependency_id%TYPE,
    version_id          in apm_package_versions.version_id%TYPE
);

procedure remove_interface(
    interface_uri       in apm_package_dependencies.service_uri%TYPE,
    interface_version       in apm_package_dependencies.service_version%TYPE,
    version_id          in apm_package_versions.version_id%TYPE
);

The primary use of interfaces is for other packages to specify required interfaces, known as dependencies. A package cannot be correctly installed unless all of its dependencies have been satisfied.

-- Add a requirement for this version.  A requirement is some interface that this
-- version depends on.
function add_dependency(
    requirement_id      in apm_package_dependencies.dependency_id%TYPE
                    default null,
    version_id          in apm_package_versions.version_id%TYPE,
    requirement_uri     in apm_package_dependencies.service_uri%TYPE,
    requirement_version     in apm_package_dependencies.service_version%TYPE
) return apm_package_dependencies.dependency_id%TYPE;

procedure remove_dependency(
    requirement_id      in apm_package_dependencies.dependency_id%TYPE,
    version_id          in apm_package_versions.version_id%TYPE
);

procedure remove_dependency(
    requirement_uri     in apm_package_dependencies.service_uri%TYPE,
    requirement_version     in apm_package_dependencies.service_version%TYPE,
    version_id          in apm_package_versions.version_id%TYPE
);

As new versions of packages are created, it is necessary to compare their version names. These two functions assist in that task.

-- Given a version_name (e.g. 3.2a), return
-- something that can be lexicographically sorted.
function sortable_version_name (
    version_name        in apm_package_versions.version_name%TYPE
) return varchar;

-- Given two version names, return 1 if one > two, -1 if two > one, 0 otherwise.
-- Deprecate?
function compare(
    version_name_one        in apm_package_versions.version_name%TYPE,
    version_name_two        in apm_package_versions.version_name%TYPE
) return integer;

Creating Instances of a Package

Once a package is registered in the system, it is possible to create instances of it. Each instance can maintain its own content and parameters.

create or replace package apm_application
as

function new (
    application_id  in acs_objects.object_id%TYPE default null,
    instance_name   in apm_packages.instance_name%TYPE
            default null,
    package_key     in apm_package_types.package_key%TYPE,
    object_type     in acs_objects.object_type%TYPE
               default 'apm_application',
    creation_date   in acs_objects.creation_date%TYPE default sysdate,
    creation_user   in acs_objects.creation_user%TYPE default null,
    creation_ip     in acs_objects.creation_ip%TYPE default null,
    context_id      in acs_objects.context_id%TYPE default null
) return acs_objects.object_id%TYPE;

procedure delete (
    application_id      in acs_objects.object_id%TYPE
);
end apm_application;

Just creating a package instance is not sufficient for it to be served from the web server. A corresponding site node must be created for it. As an example, here is how the OpenACS API Documentation service makes itself available on the OpenACS main site:

declare
    api_doc_id integer;
begin
    api_doc_id := apm_service.new (
      instance_name => 'OpenACS API Browser',
      package_key => 'acs-api-browser',
      context_id => main_site_id
    );

    apm_package.enable(api_doc_id);

    api_doc_id := site_node.new (
      parent_id => site_node.node_id('/'),
      name => 'api-doc',
      directory_p => 't',
      pattern_p => 't',
      object_id => api_doc_id
    );

    commit;
end;
/
show errors


Specifying Configuration Parameters for each Instance

A parameter is a setting that can be changed on a package instance basis. Parameters are registered on each package_key, and the values are associated with each instance. Parameters can have default values and can be of type 'string' or 'number.' There is support with this API for setting a number of minimum and maximum values for each parameter, but for most instances, the minimum and maximum should be 1. It is useful to allow or require multiple values for packages that need to store multiple pieces of information under one parameter. Default values are automatically set when instances are created, but can be changed for each instance.

All of the functions below are in the APM PL/SQL package.

-- Indicate to APM that a parameter is available to the system.
function register_parameter (
    parameter_id        in apm_parameters.parameter_id%TYPE
                default null,
    parameter_name      in apm_parameters.parameter_name%TYPE,
    description         in apm_parameters.description%TYPE
                default null,
    package_key         in apm_parameters.package_key%TYPE,
    datatype            in apm_parameters.datatype%TYPE
                default 'string',
    default_value       in apm_parameters.default_value%TYPE
                default null,
    section_name        in apm_parameters.section_name%TYPE
                default null,
    min_n_values        in apm_parameters.min_n_values%TYPE
                default 1,
    max_n_values        in apm_parameters.max_n_values%TYPE
                default 1
) return apm_parameters.parameter_id%TYPE;

function update_parameter (
    parameter_id        in apm_parameters.parameter_id%TYPE,
    parameter_name      in apm_parameters.parameter_name%TYPE,
    description         in apm_parameters.description%TYPE
                default null,
    package_key         in apm_parameters.package_key%TYPE,
    datatype            in apm_parameters.datatype%TYPE
                default 'string',
    default_value       in apm_parameters.default_value%TYPE
                default null,
    section_name        in apm_parameters.section_name%TYPE
                default null,
    min_n_values        in apm_parameters.min_n_values%TYPE
                default 1,
    max_n_values        in apm_parameters.max_n_values%TYPE
                default 1
) return apm_parameters.parameter_name%TYPE;

-- Remove any uses of this parameter.
procedure unregister_parameter (
    parameter_id        in apm_parameters.parameter_id%TYPE
                default null
);

The following functions are used to associate values with parameters and instances:

-- Return the value of this parameter for a specific package and parameter.
function get_value (
    parameter_id        in apm_parameter_values.parameter_id%TYPE,
    package_id          in apm_packages.package_id%TYPE
) return apm_parameter_values.attr_value%TYPE;

function get_value (
    package_id          in apm_packages.package_id%TYPE,
    parameter_name      in apm_parameters.parameter_name%TYPE
) return apm_parameter_values.attr_value%TYPE;

-- Sets a value for a parameter for a package instance.
procedure set_value (
    parameter_id        in apm_parameter_values.parameter_id%TYPE,
    package_id          in apm_packages.package_id%TYPE,
    attr_value          in apm_parameter_values.attr_value%TYPE
);

procedure set_value (
    package_id          in apm_packages.package_id%TYPE,
    parameter_name      in apm_parameters.parameter_name%TYPE,
    attr_value          in apm_parameter_values.attr_value%TYPE
);

The central piece of the data model is the apm_package_types table where each package is registered. When a new application or service is installed on an OpenACS instance, a corresponding row in this table is inserted with information about the type of package, e.g. if the forum package is installed on your OpenACS server, a row in apm_package_types will be created, noting that it's an application package type.

The apm_packages table is used to contain information about the instances of packages currently created in the system. The package_key column references the apm_package_types table to ensure that no package instance can be created for a type that does not exist.

The apm_package_versions table contains information specific to a particular version of a package. Several tables reference this one to provide further information about the particular version:

  • apm_package_owners Stores information about the owners of a particular version of a package.

  • apm_package_files Stores information about the files that are part of a version.

  • apm_package_dependencies Stores information about what interfaces the package provides and requires.

Parameter information is maintained through two tables:

  • apm_parameters This table contains the definition of each of the parameters for a package.

  • apm_parameter_values This table holds all of the values of parameters for specific package instances.

A number of views are available for obtaining information about packages registered in the APM.

  • apm_package_version_info Provides information about all of the versions in the system with information available from the apm_package_types table.

  • apm_enabled_package_versions A view (subset) of the above table with only enabled versions.

  • apm_file_info Provides a public interface for querying file information.

The APM's user interface is part of the OpenACS Administration Service. The UI is the primary point of contact with APM by developers and administrators. It is part of OpenACS Administration, because only the site-wide administrator should be able to access it. Thus in order to develop a package, the developer must be granted site-wide administration.

APM has two parameters for configuring how it interacts with the UNIX filesystem, accessible via the Site Map admin page. These parameters need not be changed under most circumstances, but may need to be tweaked for Windows compatibility.

  • GzipExecutableDirectory This directory points to where the gunzip program can be found for uncompressing gzip archives. This is needed for the installation of .apm files which are simply gziped tarballs. Default is /usr/local/bin

  • InfoFilePermissionsMode This sets the default UNIX permissions used when creating files using the APM. Default is 775.

APM has been in production since OpenACS 3.3, and as of version 4.0 offers a stable set of features. One major feature planned is integration with the OpenACS Package Repository for automatic dependency satisfaction. When a user tries to install a package that depends on other packages, the APM will contact the package repository, determine what packages depend on it, and offer the user a chance to download and install them all. This improvement offers value to end users by facilitating the extension of their OpenACS systems.

Architecturally, minor improvements to the data model and the specification file are planned to increase modularity. The current implementation puts all package specification information in a single file. This approach has certain advantages, such as centralization, but splitting this information into several files allows for flexible extensions to the APM architecture over time.

APM packages currently lack provisions to verify security information. There are plans to add MD5 time stamps and PGP signatures to packages to enable secure authentication of packages. These steps are necessary for APM to be usable as a scalable method to distribute packages on multiple repositories worldwide.

Another anticipated change is to split the APM UI into separate systems for authoring, maintaining, and installing packages. The current UI presents all of this functionality in one interface and it can be confusing from a usability perspective.

  • System creator: Bryan Quinn, Jon Salz, Michael Yoon, Lars Pind, Todd Nightingale.

  • System owner: Bryan Quinn

  • Documentation author: Bryan Quinn, building from earlier versions by Jon Salz, Michael Yoon, and Lars Pind.

Document Revision # Action Taken, Notes When? By Whom?
0.1 Creation 9/25/2000 Bryan Quinn
0.8 Ready for QA 9/29/2000 Bryan Quinn
0.9 Edited for ACS 4 Beta release 10/02/2000 Kai Wu
1.0 Edited for OpenACS 4.5 Beta release 03/02/2002 Roberto Mello

OpenACS Data Models and the Object System

Created by Gustaf Neumann, last modified by Gustaf Neumann 17 Feb 2008, at 07:08 AM

By Pete Su

OpenACS docs are written by the named authors, and may be edited by OpenACS documentation staff.

Developing data models in OpenACS 5.2.3rc1 is much like developing data models for OpenACS 3, save for the implementation. As usual, you need to examine how to model the information that the application must store and manipulate, and define a suitable set of SQL tables. In our Notes application, we have to be able to keep track of who entered a particular note, when they did it, and the actual text of the notes that users have entered. A simple data model might look like this:

create table notes (
    note_id           integer primary key,
    owner_id          integer references users(user_id),
    creation_user     references(user_id) not null,
    creation_date     date not null,
    last_modified     date not null,
    title             varchar(255) not null,
    body              varchar(1024)
)

We've omitted constraint names for the purpose of clarity.

Thinking further ahead, we can imagine doing any of the following things with Notes as well:

  • Define access control policies on notes.

  • Attach user comments on notes.

  • Allow users to define custom fields to store on their notes.

  • Automatically generate input forms or output displays for notes.

  • Allow other applications to use notes in ways we don't know of yet.

In OpenACS, the key to enabling these types of services on your application data is to take advantage of the Object System. The first question, then, is "Just what are objects, and what do you use them for anyway?". The short answer: objects are anything represented in the application's data model that will need to be managed by any central service in OpenACS, or that may be reusable in the context of future applications. Every object in the system is represented using a row in the acs_objects table. This table defines all the standard attributes that are stored on every object, including its system-wide unique ID, object type, and some generic auditing columns.

To make use of the object system, you as the application developer have to write your data model in a way that is slightly more complex than in the ACS 3.x days. What you get for this extra work includes:

  • The Permissions System lets you track who is allowed to do what to the rows in an application table, and gives you an easy way to enforce this from Tcl.

  • Every object has an attribute called context_id that provides a way to trivially specify both the default permissions for an object, and the intended "scope" of an object. Just set the context_id to the controlling object and forget about it.

  • And most importantly, any future object-level service - from a general-comments replacement to personalized ranking - will become available to your application "for free."

Using ACS objects is straightforward: all that's required are a few extra steps in the design of your application data model.

In order to hook our Notes application into the object system, we make some calls to use our notes table as the basis for a new object type. Object types are analogous to classes in programming languages such as C++ and Java. In Java, a class defines a set of attributes that store data and a set of methods that run code. In OpenACS, we use one or more database tables to store the data attributes, and we define a stored procedure package to hold procedures to define the programming interface to the data model.

The object type itself is described using data in the acs_object_types and acs_attributes tables, which play a role similar to the data dictionary in Oracle. As in Java, object types can inherit attributes from a parent type, so the type system forms a hierarchy. Unlike Java, Oracle does not support this inheritance transparently, so we have to make sure we add our own bookkeeping code to keep everything consistent. Below you'll find the code needed to describe a new object type called notes in your system.

Fire up your text editor and open the ROOT/packages/notes/sql/oracle/notes-create.sql (ROOT/packages/notes/sql/postgresql/notes-create.sql for the PG version) file created when we created the package. Then, do the following:

First, add an entry to the acs_object_types table with the following PL/SQL call:

begin
  acs_object_type.create_type (
    supertype     => 'acs_object',
    object_type   => 'note',
    pretty_name   => 'Note',
    pretty_plural => 'Notes',
    table_name    => 'NOTES',
    id_column     => 'NOTE_ID'
  );
end;
/
show errors;

This PL/SQL call tells the system that we would like to use the table NOTES as the basis for a new object type called note. This type is a subtype of the acs_object type, which means that we want to inherit all of the basic attributes of all ACS objects. As mentioned, it will take some work on our part to make this happen, since Oracle can't do it automatically. In general, most basic applications will define types that are simple subtypes of acs_object.

Add entries to the acs_attributes table to describe the data attributes of the new type. This data can eventually be used to do things like automatically generate user interfaces to manipulate the notes table, though that functionality isn't yet available.

declare
 attr_id acs_attributes.attribute_id%TYPE;
begin
  attr_id := acs_attribute.create_attribute (
    object_type    => 'note',
    attribute_name => 'TITLE',
    pretty_name    => 'Title',
    pretty_plural  => 'Titles',
    datatype       => 'string'
  );

  attr_id := acs_attribute.create_attribute (
    object_type    => 'note',
    attribute_name => 'BODY',
    pretty_name    => 'Body',
    pretty_plural  => 'Bodies',
    datatype       => 'string'
  );
end;
/
show errors;

We can stop here and not bother to register the usual OpenACS 3.x attributes of creation_user, creation_date and last_modified, since the object type acs_object already defines these attributes. Again, because the new type note is a subtype of acs_object, it will inherit these attributes, so there is no need for us to define them.

The next thing we do is make a small modification to the data model to reflect the fact that each row in the notes table represents something that is not only an object of type note, but also an acs_object. The new table definition looks like this:

create table notes (
    note_id    integer references acs_objects(object_id) primary key,
    owner_id   integer references users(user_id),
    title      varchar(255) not null,
    body       varchar(1024)
)

The usual creation_date and modified_date columns are absent since they already exist in acs_objects. Also, note the constraint we have added to reference the acs_objects table, which makes clear that since note is a subtype of acs_object, every row in the notes table must have a corresponding row in the acs_objects table. This is the fundamental means by which we model inheritance; it guarantees that any services that use the acs_objects table to find objects will transparently find any objects that are instances of any subtype of acs_objects.

The next step is to define a PL/SQL package for your new type, and write some basic procedures to create and delete objects. Here is a package definition for our new type:

create or replace package note
as
  function new (
    note_id             in notes.note_id%TYPE default null,
    owner_id            in notes.owner_id%TYPE default null,
    title               in notes.title%TYPE,
    body                in notes.body%TYPE,
    object_type         in acs_object_types.object_type%TYPE default 'note',
    creation_date       in acs_objects.creation_date%TYPE
                           default sysdate,
    creation_user       in acs_objects.creation_user%TYPE
                           default null,
    creation_ip         in acs_objects.creation_ip%TYPE default null,
    context_id          in acs_objects.context_id%TYPE default null
  ) return notes.note_id%TYPE;

  procedure delete (
    note_id      in notes.note_id%TYPE
  );
end note;
/
show errors

You might be wondering what all the extra parameters are to these calls, since we haven't mentioned them before. These parameters are needed to fill out information that will be stored about the object that's not stored directly in the table you defined. The OpenACS Object System defines these attributes on the type acs_object since all objects should have these attributes. Internally, there are tables that store this information for you. Most of the data is pretty self-explanatory and reflects attributes that existed in the earlier OpenACS 3.x data models, with the exception of the context_id attribute.

The context_id attribute stores the ID of an object that represents the default security domain to which the object belongs. It is used by the permissions system in this way: if no permissions are explicitly attached to the object, then the object inherits its permissions from the context. For example, if I had told you how to use the permissions system to specify that an object OBJ was "read only", then any other object that used OBJ as its context would also be "read only" by default. We'll talk about this more later.

The PL/SQL package body contains the implementations of the procedures defined above. The only subtle thing going on here is that we must use acs_object.new to insert a row into acs_objects, before inserting a row into the notes. Similarly, when we delete a row from note, we have to be sure to delete the corresponding acs_object row.

create or replace package body note
as

  function new (
    note_id             in notes.note_id%TYPE default null,
    owner_id            in notes.owner_id%TYPE default null,
    title               in notes.title%TYPE,
    body                in notes.body%TYPE,
    object_type         in acs_object_types.object_type%TYPE default 'note',
    creation_date       in acs_objects.creation_date%TYPE
                           default sysdate,
    creation_user       in acs_objects.creation_user%TYPE
                           default null,
    creation_ip         in acs_objects.creation_ip%TYPE default null,
    context_id          in acs_objects.context_id%TYPE default null
  ) return notes.note_id%TYPE
  is
    v_note_id integer;
  begin
    v_note_id := acs_object.new (
      object_id     => note_id,
      object_type   => object_type,
      creation_date => creation_date,
      creation_user => creation_user,
      creation_ip   => creation_ip,
      context_id    => context_id
    );

    insert into notes
     (note_id, owner_id, title, body)
    values
     (v_note_id, owner_id, title, body);

     return v_note_id;
  end new;

  procedure delete (
    note_id      in notes.note_id%TYPE
  )
  is
  begin
    delete from notes
    where note_id = note.delete.note_id;

    acs_object.del(note_id);
  end delete;

end note;
/
show errors;

That's pretty much it! As long as you use the note.new function to create notes, and the note.delete function to delete them, you'll be assured that the relationship each note has with its corresponding acs_object is preserved.

The last thing to do is to make a file ROOT/packages/notes/sql/notes-drop.sql so it's easy to drop the data model when, say, you're testing:

begin
  acs_object_type.drop_type ('note');
end;
/
show errors

drop package note;
drop table notes;

While it is hard to give general design advice without knowing anything about a particular application, you should follow the following rule of thumb when deciding when to hook part of your data model to the object system:

Anything in your data model that needs to be available to general OpenACS services such as user comments, permissions, and so on should be a subtype of acs_object. In addition, if you want your data model to take advantage of attributes that exist in some object type that is a subtype of acs_object, then you should use the object system.

For example, for most applications, you will want to use objects to represent the data in your application that is user visible and thus requires access control. But other internal tables, views, mapping tables and so on probably don't need to be objects. As before, this kind of design decision is mostly made on an application-by-application basis, but this is a good baseline from which to start.

In this section we cover some overall guidelines for designing data models that are meant to be integrated with the OpenACS object system.

There are two basic rules you should follow when designing OpenACS 5.2.3rc1 data models:

  1. Never utilize fields in the acs_objects table in application specific ways. That is, never assign any application-specific semantics to this data. In the notes application, we use the creation_date and last_modified fields, but this is OK since we do not assign any application-specific meaning to these fields.

  2. In particular, never assign any application specific semantics to the context_id attribute of an object. This field is used for a very specific purpose by the permissions system, and using this field in any other way whatsoever is guaranteed to make your application act strangely.

    As we'll see later, the Notes example will point each note object's context_id to the package instance in which the note was created. The idea will be that in a real site, the administrator would create one package instance for every separate set of Notes (say, one per user). The instance would "own" all of the notes that it created, and the administrator would be able to use the package instance as the basis for access control, which is convenient.

The reason behind these two rules is pretty straightforward: First, the OpenACS Object system itself is meant to be a generic and reusable tool for any application to use for basic services. Second, in order for this to work, the various parts of the OpenACS Objects data model must be interpreted in the same way by all applications that use the data model. Therefore, assigning any application-specific semantics to any part of the core data model is a bad thing to do, because then the semantics of the data model are no longer independent of the application. This would make it impossible to build the generic tools that the data model is trying to support.

Another less important reason for these two rules is to not introduce any joins against the acs_objects table in SQL queries in your application that you do not absolutely need.

In the Notes example, the result of applying these rules is that we are careful to define our own attribute for owner_id rather than overloading creation_user from the objects table. But, since we will probably use creation_date and so on for their intended purposes, we don't bother to define our own attributes to store that data again. This will entail joins with acs_objects but that's OK because it makes the overall data model cleaner. The real lesson is that deciding exactly how and when to use inherited attributes is fairly straightforward, but requires a good amount of thought at design time even for simple applications.

Hooking into the OpenACS 5.2.3rc1 object system brings the application developer numerous benefits, and doing it involves only four easy steps:

  • Describe the a new object type to the system. Most new application types will be subtypes of the built-in type acs_object.

  • Define a table to store application object data.

  • Define a PL/SQL package to store procedures related to the new type. You have to define at least a function called new to create new application objects and a procedure called delete to delete them.

  • Define a package body that contains the implementations of the PL/SQL procedures defined above.

  • Try not to write queries in your application that join against acs_objects. This means you should never use the fields in acs_objects for application-specific purposes. This is especially true for the context_id field.

Admin Pages

Created by Gustaf Neumann, last modified by Gustaf Neumann 17 Feb 2008, at 07:08 AM

There are at least two flavors of admin user interface:

  • Admins use same pages as all other users, except that they are offered admin links and buttons where appropriate. For example, if admins have privilege to bulk-delete items you could provide checkboxes next to every item seen on a list and the Delete Selected button on the bottom of the list.

  • Dedicated admin pages. If you want admins to have access to data that users aren't interested in or aren't allowed to see you will need dedicated admin pages. The conventional place to put those dedicated admin pages is in the /var/lib/aolserver/$OPENACS_SERVICE_NAME/packages/myfirstpackage/www/admin directory.

    [$OPENACS_SERVICE_NAME www]$ mkdir admin
    
    [$OPENACS_SERVICE_NAME www]$ cd admin
    

    Even if your application doesn't need any admin pages of its own you will usually need at least one simple page with a bunch of links to existing administration UI such as Category Management or standard Parameters UI. Adding the link to Category Management is described in the section on categories. The listing below adds a link to the Parameters UI of our package.

    [$OPENACS_SERVICE_NAME admin]$ vi index.adp
    
    <master>
    <property name="title">@title;noquote@</property>
    <property name="context">@context;noquote@</property>
    
    <ul class="action-links">
      <li><a href="@parameters_url@" title="Set parameters" class="action_link">Set parameters</a></li>
    </ul>
    
    [$OPENACS_SERVICE_NAME admin]$ vi index.tcl
    
    ad_page_contract {} {
    } -properties {
        context_bar
    }
    
    set package_id [ad_conn package_id]
    
    permission::require_permission  -object_id $package_id  -privilege admin]
    
    set context [list]
    
    set title "Administration"
    
    set parameters_url [export_vars -base "/shared/parameters" {
      package_id { return_url [ad_return_url] }
    }]
    
    

    Now that you have the first admin page it would be nice to have a link to it somewhere in the system so that admins don't have to type in the /admin every time they need to reach it. You could put a static link to the toplevel index.adp but that might be distracting for people who are not admins. Besides, some people consider it impolite to first offer a link and then display a nasty "You don't have permission to access this page" message.

    In order to display the link to the admin page only to users that have admin privileges add the following code near the top of /var/lib/aolserver/$OPENACS_SERVICE_NAME/packages/myfirstpackage/www/admin/index.tcl:

    set package_id [ad_conn package_id]
    
    set admin_p [permission::permission_p -object_id $package_id  -privilege admin -party_id [ad_conn untrusted_user_id]]
    
    if { $admin_p } {
        set admin_url "admin"
        set admin_title Administration
    }
    

    In /var/lib/aolserver/$OPENACS_SERVICE_NAME/packages/myfirstpackage/www/admin/index.adp put:

    <if @admin_p@ ne nil>
      <a href="@admin_url@">@admin_title@</a>
    </if>
    

Next Page