2010-12-13

Funambol GroupDAV Release

There has been a new release of the Funambol GroupDAV connector.
This release fixes some major bugs as a result of changes earlier this year. Some changes have been made to support newer clients, such as the renaming of sync sources to the Funambol defaults. SIF support has now been dropped
The README is available on the Bionicmessage wiki. For download and more information see Bionicmessage.net.

2010-12-08

Manually Adding an ACL To An Object

In OpenGroupware the ACLs applied to an object are stored in the "object_acl" table.  If, for example, I want to add the list, view, read, write, and administer privileges for the team 11,530 for object 1,6829,810 the correct SQL to execute is:
INSERT INTO object_acl
  (object_acl_id, sort_key, action, object_id, auth_id, permissions)
VALUES (nextval('key_generator'), 0, 'allowed', 16829810, 11530, 'lvrwa')
The important points are:
  1. Use the "key_generator" sequence to assign the "object_acl_id" value.  This is the object id of the ACL itself;  all object ids are assigned from the key_generator sequence.
  2. The value of "sort_key" is always 0.  This value isn't actually used for anything.
  3. The value of "action" must be either "allowed" or "denied".  In most cases "allowed" is what you want in order to grant access.
  4. "object_id" is the object id of the object to which the ACL is applied in contrast to "auth_id" is the context to which the privileges, specified in "permissions", are either granted [if "action" is "allowed"] or revoked [if "action" is "denied"].  The value of "auth_id" should be the object id of an account or a team.
  5. The permissions string is always lower case.  Permission flags are documented in WMOGAG.
The ACLs in "object_acl" are the primary access control mechanism for all entities excepting Projects and Appointments.

2010-07-21

Testing vCard Operations

vCard data is an essential component of groupware / collaboration operations;  from sending an electric business card as a e-mail attachment, to synchronizing your mobile device or desktop PIM - vCard is everywhere.  But a vCard is a complicated structure and there is an enormous variation in the details (and quality) of the various implementations.  Some applications will keep fields they don't recognize while others will discard them.  Some applications will drop attributes of fields and/or add their own - and of course expect whatever the server is to keep track of them.  This means that being able to debug exactly what happens to a vCard is a critical system administration task.  To facilitate this OpenGroupware Coils provides two command line utilities:
  • coils-get-vcard
  • coils-parse-vcard
Both are quite straight-forward.  coils-get-vcard simply produces the vCard for a specified Contact;  this is the same vCard that will be sent to any client requesting that Contact as a vCard via HTTP.
$ coils-get-vcard --objectid=10100
BEGIN:VCARD
VERSION:3.0
UID:coils://Contact/10100
ADR;X-COILS-ADDRESS-TYPE=location;TYPE=other:;Bang the dums Incorporated;9999 Mo
 nroe Ave NW;Grand Rapids;MI;49505;USA
ADR;X-COILS-ADDRESS-TYPE=mailing;TYPE=work,pref:;Bang the dums Incorporated;9999
  Monroe Ave NW;Grand Rapids;MI;49505;USA
....
The coils-parse-vcard performs the opposite action - it accepts a vCard file and produces the Omphalos representation of the attributes derived from the vCard.  This reveals how OpenGroupware Coils will respond to a vCard that is PUT from a client.

$ coils-parse-vcard --file=10100-2.vcf
{'_ADDRESSES': [{'city': u'Grand Rapids',
                 'country': u'USA',
                 'name1': u'Bang The Drums Incorporated',
                 'postalCode': u'49599',
                 'state': u'MI',
                 'street': u'9999 Monroe Ave NW',
                 'type': u'location'},
                {'city': u'Grand Rapids',
...

Optionally the --update option to coils-parse-vcard will cause the utility [using an administrative context] to create or update the related Contact in the OpenGroupware Coils instance.  So coils-parse-vcard can double both as a debugging tool and a data import tool.

2010-07-20

Replacing Images With mod_rewrite

OpenGroupware Legacy's WebUI has an extensive themeing system... but sometimes you just want to replace a couple images. Doing so is possible using Apache's mod_write module; and you don't have to touch any of the files installed by the OpenGroupware packages. The key to using mod_rewrite to 'overload' image references is that mod_rewrite supports the chaining of conditions [via RewriteCond] so rewriting rules can only apply to certain requests.

Adding:
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} \.(jpg|jpeg|gif|png)$
RewriteCond /srv/www/htdocs/override/%{REQUEST_FILENAME} -f
RewriteRule ^(.+) /override/$1 [L]
</IfModule>
<Directory /srv/www/htdocs/override>
Order allow,deny
Allow from all
</Directory>

- to the vhost entry of your OpenGroupware Legacy instance allows you to overload any reference to a request ending in jpg, jpeg, gif or png. But this only happens if a file of the same name exists in our overload directory. So if you don't create an eponymously named file then nothing changes,

So a request for -
https://tor.example.com/OpenGroupware11.woa/WebServerResources/English.lproj/homepage2.gif
- gets rewritten to -
https://tor.example.com/override/OpenGroupware11.woa/WebServerResources/English.lproj/homepage2.gif
- if and only if a file with a path and name of -
/srv/www/htdocs/override/OpenGroupware11.woa/WebServerResources/English.lproj/homepage2.gif
- exists. Otherwise the file is delivered from the usual place via the Alias directive in your ogo-webui.conf file included into your Apache configuration:
Alias /OpenGroupware11.woa/WebServerResources/ /usr/local/share/opengroupware.org-1.1/www/
<Directory local="" opengroupware.org-1.1="" share="" usr="" www="">
Order allow,deny
Allow from all </directory>

Making zOGI XML-RPC API calls from Jython

Using zOGI from CPython is simple - you just use the "xmlrpc" module.  However, that module relies on native [unmanaged] code so it doesn't work in Jython.  In order to make XML-RPC calls from Jython you need to use a Java XML-RPC assembly: for example, the Apache XML-RPC assembly (previously known as "Helma") from the Apache foundation.  Here is an example of making zOGI XML-RPC API calls from Jython:

import org.apache.xmlrpc.client as xmlrpc
from java.util import Vector
from java.util import Hashtable
import java.net

client = xmlrpc.XmlRpcClient()
config = xmlrpc.XmlRpcClientConfigImpl()
config.setServerURL(java.net.URL('http://***********/zidestore/so/***********/'))
config.basicUserName = '***********'
config.basicPassword = '***********'
client.setConfig(config)

qualifier = Hashtable()
qualifier['key'] = 'firstname'
qualifier['conjunction'] = 'AND'
qualifier['expression'] = 'LIKE'
qualifier['value'] = 'A%'
criteria = Vector()
criteria.add(qualifier)

params = Vector()
params.add('Contact')
params.add(criteria)
params.add(65535)

results = client.execute("zogi.searchForObjects", params)
for result in results:
    print 'ObjectId:%s %s, %s' % (
        result['objectId'], 
        result['lastName'],
        result['firstName'])

2010-07-19

EOGlobalIDs And Primary Keys

What is a primary key?
Every document stored within OpenGroupware has a numeric id that is unique. In fact, almost every entry concerning anything in OpenGroupware has one of these ids. This id namespace is flat; that is: ids do not overlap between document types; if there is a person with an id of 99450 then there is no enterprise, file, appointment, or anything else with the id of 99450. When anything is created it is assigned one of these unique numbers, called the primary key (often abbreviated pkey or pk. And that is the id of that document for its entire lifetime.

One note of clarification: by "document" we don't mean "file". An enterprise, person, job, project, file, appointment, or resource are all documents. You could call them objects too, but that gets even more confusing. :)  Sometimes, especially in OpenGroupware Coils, they are referred to as entities;  all these are names of the same things.

What is an EOGlobalID / EOKeyGlobalID?
Serialized an EOKeyGlobalID looks something like
<0x0x82cb23c[EOKeyGlobalID]: Date 28260>
Yea, that was helpful wasn't it? But look at it... there is the string "Date" and a number of "28260". If you guessed that this refers to the date [appointment] document with a pkey [primary key] of 28260 then you are correct. Internally EOKeyGlogalIDs (often abbreviated as gid / gids) are what OpenGroupware uses, on the database level, in order to retrieve and store data. OGo Legacy / SOPE uses a database abstraction layer called GDL, apparently very much like the GDL from GNU-Step. You can think of the EOKeyGlobalID as a "handle" for the data related to a given document.

The EOKeyGlobalID object has the following accessors:
- (NSString *)entityName;
- (unsigned int)keyCount;
- (id *)keyValues;
- (NSArray *)keyValuesArray;

The entityName accessor will provide the type of document that the key refers like. The keyValuesArray provides access to the payload which is the pkey [primary key] of the object. Since the value of keyValuesArray is an NSArray and you almost certainly want just the single value which it contains you'd use code like:
[[key keyValuesArray] objectAtIndex: 0]
which in the above example would provide you with the value "28260" (as an NSNumber object).  Of course it is possible to have a composite primary key, which explains why keyValuesArray is an array, but this doesn't happen in OpenGroupware.

How to turn a pkey into an EOKeyGlobalId?
It is a very frequent case where you have a pkey or an NSArray of pkeys for which you need to marshal the corrsponding document objects. Marshalling an object requires having the EOKeyGlobalId (handle). Fortunately the OpenGroupware framework provides a very simple means to generate an EOKeyGlobalId from a pkey; this is via the typeManager object.
The first requirement for getting handles from typeManager is that your pkey or array of pkeys must be NSNumber objects. You cannot use NSString objects. If what you have are strings then use the NSString class's intValue method to produce NSNumber objects:
[NSNumber numberWithInt:[_arg intValue]]
where _arg is your NSString object. Of course the contents of _arg have to actually be numeric. Then you can invoke the typeManager object like:
gid = [[[self commandContext] typeManager] globalIDForPrimaryKey:_arg]
where _arg is the NSNumber object containing your pkey and you get an instance of the EOKeyGlobalId required to retrieve the specified document from the database.

2010-07-14

Keeping an OIE Route After Completion

Once an OpenGroupware Integration Engine route has successfully completed it is automatically garbage collected.  This prevents the system from filling up with messages and version information.  But if an external application, like ogo-curses-putter, wants to retrieve the output of the route... it's gone!  To facilitate this use-case the garbage collection can be suppressed per-route by setting the {http://www.opengroupware.us/oie}preserveAfterCompletion property of the route entity with a value of "YES".  If this property value exists the processes derived from that route will not be garbage collected.  The simplest way to set the property is to retrieve the route via "route::get" and use your context's property manager:

from coils.core import *
ctx = AdministrativeContext()
r = ctx.run_command('route::get', name='MTAStockOrder_TEST')
ctx.property_manager.set_property(r, 'http://www.opengroupware.us/oie', 'preserveAfterCompletion', 'YES')
ctx.commit() 
 
The above will set the value of {http://www.opengroupware.us/oie}preserveAfterCompletion to "YES" for the route named "MTAStockOrder_TEST". The property manager will update the value of the property if such a property already exists, or it will create a new property with the specified value.