SOPE uses a construct called a "SoClass" to make up classes of common attributes and methods. It's similiar to a Python class and quite a bit different to an Objective-C class. To understand SoClass'es it is good to understand Python classes.
In Python a class doesn't distinguish between instance variables and methods. A class simply contains a number of "slots" which can store either variable values or method objects. A method is actually a usual object in Python, the only difference is, that method objects implement the special '__call__' method.Take a look at a message call in Python:
myObject.myMethod(myArg=1)This is actually a two stage process:
method = myObject.myMethod method(myArg=1)First, the method object is looked up (using __getattr__) in the object, then it is activated using the () "operator". In detail it really looks like:
method = myObject.__getattr__("myMethod") method.__call__(myArg=1)
This is a (more or less) unique thing Zope makes use of, methods in Python doesn't need to be implemented in Python, but can be *any* object which implements the __call__ special method.
More exactly, we can treat an WOComponent (or DTML method in Zope) as a method which is callable and returns some value as a result.
Back to SoClasses ...
So since Objective-C classes are a bit to low level, we invented a separate class system called SoClasses. SoClasses can contain "class variables" of any object type and they can contain method-objects (any Objective-C object which implements the SOPE 'call' special method).
Of course you can easily wrap an Objective-C class into a SoClass given the reflective capabilities of Objective-C.Getting the SoClass of an object:
SoClass *soClass = [myObject soClass];Getting the SoClass wrapper of an Objective-C class:
SoClass *soClass = [WOComponent soClass];Very simple. The default implementation for getting the SoClass of an Objective-C object is actually using the Objective-C class as the base:
SoClass *soClass = [[myObject class] soClass];
This way any Objective-C object automatically has an associated SoClass
Mapping Objective-C classes to SoClasses
So the major difference between an Objective-C class and a SoClass is, that in SoClasses methods are "real" objects which in ObjC methods are plain C functions.Objective-C:
IMP method = [WOComponent methodForSelector:@selector(doIt)]; method(self, @selector(doIt), @"hello");SoClass:
id method = [[WOComponent soClass] lookupName:@"doIt"]; [method call:@"hello", nil];
When we mirror the Objective-C class into a SoClass, we need to morph the C functions bound to selectors to method objects bound to names. Of course the obvious solution is to use NSInvocation objects. So when we call -lookupName: on a SoClass that mirrors an Objective-C class, it actually creates an NSInvocation objects for the selector.TODO: write text (and code ;-) on mapping key parameters to index-parameters.
Creating Non-ObjC SoClasses
A major point in having a separate class hierarchy independend of Objective-C is the possibility of creating SoClasses that are not implemented in ObjC at all. In Zope this is called as "ZClass" - a class which looks like a real Python class, but is actually implemented as a Zope folder with DTML methods as it's methods.Consider a SOPE folder based SoClass which is setup like this:
MySoClass/ CallWebService.py ProcessConfig.pl ParseXML.class ViewResult.woxThe MySoClass would be a "component" that is composed of methods in four different languages !
Unfortunatly, as so many things, the huge potential of this can't be easily explained ... Consider some more types of methods:
- LDAP-queries (perform a parameterized LDAP query and return result-set)
- scripts of any kind
A method is just something which takes parameters and returns a result. How it is implemented, is the task of the method's class, not SOPE.
SOPE Folders vs SOPE Classes
In SOPE methods cannot only be used in the context of classes, that is, method are not bound to classes, but to the client object (actually the same is true in Objective-C, but rarely used in that way because it has no syntactic support). You can place SOPE methods of any kind into any SOPE container, be it a SoClass or a SOPE OFS folder.
So why do we add "classes" ?
SoClasses are intended to group methods for reuse. The methods contained in a SOPE folder can only be called on the folder itself, the methods of a SoClass are available in any object which is an instance of that SoClass.
In practice you will often start out implementing methods in a folder and later, if the methods seem to be useful in several places, you group them into classes for reuse.
For example, you could create a "Person" SoClass which has several methods, like
The "lookupInLDAP" method could be implemented as an LDAP query-method which returns the LDAP record for a person object given. "view" could be a WOComponent "method" which shows the person object in a webbrowser. etc.
Like Objective-C classes, SoClasses support categories. Categories are a way to add new methods to a class which is already loaded into the runtime. This is very useful since products can extend classes with methods without the requirement to tweak the original implementations. You don't even need to have the source code of the original class to add methods.
A nice example of this is the SMI.sxp product, which extends several common classes with a management interface comparable to the ZMI (Zope Management Interface).
A method in the SOPE context is an ordinary objects which is bound to a context object, takes parameters, has a method to trigger a call and returns a result. This "abstraction" covers a very broad class of objects since a lot of processing is done that way. In special it covers scripts of all kinds, it covers SQL or LDAP queries and it covers "usual" Objective-C methods. It's a neat way to abstract several "processing" entities into a single call convention.
For example, consider a method which is implemented in SQL. Our object is a record in the "person" table and we want to write a method which retrieves all it's addresses stored in a 1:n relation:
SELECT * FROM address WHERE person_id=<self.primaryKey> AND type="<arg>"
In SOPE we will have an SoSQLMethod which will perform this SQL "template":
[[myPerson lookupName:@"QueryAddress"] call:@"billing"];
The SoSQLMethod will get the primary key from the "context object" of the method (self), it will take a list of parameters and will return a list of new objects. It looks to the outside like any other method and can be called by the SOPE framework using any "adaptor" available (XML-RPC, HTTP, WebDAV, ...)
Instead of being tied to a particular language for implementation, the implementation is encapsulated in an Objective-C class (actually SoClasses) which can use any mechanism available to do it's processing.
Some more SoMethod implementations:
- SoJavaMethod, SoMonoMethod
All of these method classes can be added dynamically to the SOPE runtime by providing proper product bundles. The method class will be found in the similiar way like any class, by resolving the file extension or MIME-TYPE into a SoClass object.
Eg if SOPE discovers an object called "callIt.pl" it will activate that object using the SoPerlScript class, which will pass in parameters as global Perl variables, run the script and return the result to SOPE for further processing.
An advantage of having a separate class hierarchy and object graph is that we gain a bit of "implicit" security. It's certainly not intendend that *any* object, method or variable can be accessed from the web. In SOPE only things that are mapped to SoClasses are accessible through the web providing some implicit security.
Of course we still have the SOPE security system which can protect any object. This is also used in Zope to protect anything from being accessed. But still SoClasses have an advantage since only SoClass lookups need to be run through security checks.
In short: the separation between "system" (Objective-C) classes and the SOPE (web) classes makes it a bit easier to implement proper security.TODO: write more on everything, find better examples ...