This text describes the key concepts within ztm.topicmaps. It tries to be useful to people working on the ztm.topicmaps-codebase or extending its functionality in other packages.
It only covers the architecture of the core engine itself. To get an overview of the engine architecture in the context of writing applications using the larger ZTM-framework please see the top level documentation.
Please make yourself acquainted with the basics of ISO Topic Maps before continuing with this document.
Familiarity with Zope’s Component Architecture (ZCA) is not required but strongly recommended. Baiju M’s A Comprehensive Guide to Zope Component Architecture is a nice introduction.
Note
If you have any questions, feel something is missing or find something that seems to be unclear or even wrong, please let us know on the ztm@ztmproject.org mailinglist or drop by the #ztm channel on freenode
The TMDM consists of a topic map, topics, topic names, name variants, occurrences, roles and associations. It is designed for information interchange. The standard contains an informative UML-drawing:
There is a class for each of these information items. They self-coordinate to keep references and counts up to date.
Topics, the topic map itself and all information items are persistent objects in ZODB. References between persistent objects are built using strong references (as opposed to weak references, a feature provided by Python).
ztm.topicmaps is the third version of ZTM Topic Maps. While we’ve tried to make it useful for a wider set of tasks than previous versions, we have kept the focus on content management rather than being a more generic Topic Maps engine.
The primary architectural artifact of this design decision has been that topics are first class objects that very much stand on their own.
Practiaclly this means that all topics can be traversed through the built-in object file system. This is a tree-structure.
The topic map exists as a persisten graph on top of the object file system. Permissions are calculated using the object file system, while relationships are typically defined through the topic map graph.
All topic map constructs are registered with the topic map and given a serial that is unique within the topic map. The serial is simply an integer.
In addition we maintain unique ids for different scopes and templates.
The engine specifies 7 core interfaces that maps to key topic map constructs[1].
- ITopicMap
- ITopic
- IName
- IVariant
- IOccurrence
- IAssociation
- IRole
Topic maps automatically registers with the local site managers and there can be only one topic map per local component registry.
While we consider topic maps to be content belonging to the content namespace, they offer many indexes and other services. Topic Maps are therefore automatically register with the local component registry (site manager) as an ztm.topicmaps.ITopicMap interface. A topic map can always be retrieved like this:
>>> import ztm.topicmaps
>>> import zope.component
>>> topicmap = ztm.topicmaps.topicmap.TopicMap()
There is an event that registers a topic map with its closest component registry when it is added in Zope’s Object File System (OFS). This is not triggered here so we need to set it manually.
>>> cr = zope.component.getSiteManager()
>>> cr.registerUtility(topicmap, ztm.topicmaps.ITopicMap)
>>> cr.getUtility(ztm.topicmaps.ITopicMap) #doctest: +ELLIPSIS
<TopicMap at ...>
For this reason we only allow one topic map per component registry. To create multiple topic maps in a database just add a component registry to a folder and create it inside there. The active component registry is set during traversal. Topics created inside the same folder will get the new topic map.
Topic Maps try to model knowledge structures with topics and allow users to connect information items to these.
While all topics are of equal importance to the topic map model, in most systems some topics are more important than others. Particularly topics that represent the type of other information items.
Applications virtually always want to tie different behaviour or appearance to these topics. This is accomplished by marking them with other interfaces.
All topics that act as a type for other topics are marked with the ITopicType interface:
>>> topictype = topicmap.createTopic()
>>> instance = topicmap.createTopic()
>>> topictype.addInstance(instance)
>>> ztm.topicmaps.interfaces.ITopicType.providedBy(topictype)
True
The same is true for topics that type other properties:
>>> nametype = topicmap.createTopic()
>>> instance.createName('value', type=nametype)
>>> ztm.topicmaps.interfaces.INameType.providedBy(nametype)
True
Note
While building your ontology you often need to mark a topic as a type before it has any actual instances of its own. You typically do this by adding one of the IPossibleType interfaces.
See also
ztm.interfacemanager allows one to add domain specialized marker interfaces to topics with a particular subject identifier, to instances of topics with a particular subject identifier or to topics playing a role of a topic with a particular identifier.
We use the object publishing infrastructure of Zope, including views and templates, to create applications.
Each item in a topic maps as a canonical address, represented by its place in the object file system. This allows us to calculate permissions.
Each topic typically has one or more pages/resources. The default view typically belongs to the application built on top.
A view is a multi-adapter that adapts an object and the request to a view. This means that the entire publishing process can be handled by the component architecture. (ztm.publishing provides a way to specify views in a browser for non-developers.)
Views can be bound to interfaces. This means that custom marker interfaces is used to decide which views a topic get.
The core engine provide a lot of standard indexes that can be retrieved through the API. This is things like lists of instances.
You can also plug indexes into the search machinery. These indexes are called every time an item it monitors is created, updated or changed.
These pluggable indexes are managed by the index drawer on each topic map. Each index is persistent and applies only to the topic map it is inside.
You can define indexes that are only interested in names, variants, occurrences, roles and/or associations. These are virtually always mapped to topics.
We have introduced the concept of a mandatory index. That is, an index that is always applied against a search. Typically this is used to provide security filters.
You search against the indexes registered in the indexdrawer.
ztm.topicmaps mostly use hard-coded callbacks internally, but we provide many synchronous events to let other packages plug into the Topic Maps engine. This is built on top of the triumvirate of zope.event, zope.interfaces, and zope.component, and is a standard Zope feature.
Security is pluggable, but we use the default zope.security and zope.securitypolicy. Please read up on these modules to get an understanding of how they work.
We use the standard Zope 3 policy which work something like this:
- The topic being published is wrapped in a kind of spacesuit
- where all attribute accesses check if the user has permission to access the attribute or method
- and whatever is returned is then wrapped in its own security spacesuit to ensure that security is always enforced.
- Permissions required to access a particular method is granted to roles, groups and occasionally directy to principals.
ztm.topicmaps declares many different permissions to guard different parts of a topic. You can find them in the permissions.zcml. Most of them should be pretty self-explanatory and they are used in configure.zcml.
Principals are essentially users that can have roles or be member of groups.
Roles or groups can be granted for subsets of the database, meaning that a user can be a manager in one location and just a member elsewhere.
In the unit-tests you can disable the security by not having an interaction.
Other levels of the stack provide workflow and publishing.