Software Architecture

From DoKSwiki

Table of contents

Technologies

DoKS is build using only open source Java frameworks:

  • Struts: MVC (Model View Controller) web application framework
  • Hibernate: object/relational data persistence framework
  • Aspectwerkz: AOP (aspect oriented programming) framework
  • Velocity Template Engine : HTML templates and code generation
  • Lucene: search engine
  • OAIcat: OAI-pmh implementation
  • BeanShell: scripting engine

A basic knowledge of all these frameworks is necessary to understand the internals of DoKS.

The core

The DoKS core can be roughly devided into 8 modules (and resp. java packages):

  • record management: doks.record
  • folder management: doks.folder
  • file storage management (file sets): doks.storage
  • user management: doks.user
  • scripting: doks.scripting
  • access control: doks.accessControl
  • import/export: doks.importexport
  • search engine: doks.search

Each of these modules can be accessed through its facade (facade design pattern). Because these facades are the major classes to be used in scripts, they are packaged all together in the doks.facade package. This makes the javadoc pages for this package the starting point for understanding DoKS and for writing scripts in DoKS.

Each of these modules contains at least following classes:

  • object: the class that modells the business object (ex. doks.record.Record)
  • DTO: the data transfer object (DTO design pattern) to send data forth and back to the clients (a web client or a script) (ex. doks.record.RecordDTO)
  • manager: manages the life cycle of the objects (create, read, update, delete) and dilivers additional services like searching objects etc.(ex. doks.record.RecordManager)
  • facade interface: the interface that is visible to clients
  • facade implementation: an implementation of the facade interface that mainly takes care of transaction demarcation and delegates requests to the manager. There are two types of facade implementations:
    • stand alone implementation: after obtaining a Hibernate session and starting a new transaction or joining an existing transaction, it merely delegates requests to the manager
    • container managed implementation (stateless session bean): uses container managed transactions. Currently there are no container managed implementations yet.

doks.record

Records are the heart of DoKS. They are used to hold descriptive metadata and/or files (binary files, text files...). The Record class itself has only a description field, creationDate and lastModificationDate. All other fields are defined in subclasses of Record.

The RecordDTO class is the base class for all data transfer objects. This means that a client can ask a RecordDTO at the RecordFacade, modify it and ask the RecordFacade to save it.

RecordDTO also has some functions to retrieve the actual number of fields, to retrieve the fieldnames, to iterate over all fields etc.

For each structure (defined in structures.xml), two classes are generated in the structure package:

  • structure.StructureName: a subclass of doks.record.Record that holds all the fields defined for the structure. Hibernate generates one table in the database for each such class. Ex. structures.FAQ, structures.ETD...
  • structure.StructureNameDTO: a subclass of doks.record.RecordDTO that serves as data transfer object. Ex. structures.FAQDDTO, structures.ETDDTO...

doks.folder

Folders form a single rooted hierarchy like a Unix file system. Each folder is uniquelly defined by its path. The path of the root folder is /

There are two predefined folders: /Home and /Workplaces. The reason behind this is that there are typically a large number of work places that we do not wan't to display together with 'regular' folders.

Folders can be sub folders of multiple other folders. In this cases they appear as shortcuts in their parent folders, except for the 'real' parent folder (the one that appears in the path).

The main classes are:

  • doks.folder.Folder: folder objects
  • doks.folder.FolderDTO: data transfer objects
  • doks.folder.FolderManager: the manager class

doks.storage

The storage module takes care of the file storage. This involves three classes (and their DTO's):

  • doks.storage.FileSet: a set of FileWrappers that can be attached to a Record (if defined in the Record's structure)
  • doks.storage.FileWrapper: a wrapper class for the actual files on the file system. All files of a FileSet are saved in the same directory with the directory name= id of FileSet and the file name= id of FileWrapper i.e. fileSetId/fileWrapperId. The FileWrapper keeps track of the actual file name (provided by the user) and of the FileFormat. File names must be unique within a FileSet.
  • doks.storage.FileFormat: FileFormats identify the file type (MIME type). They and are used for long term archiving and to set the correct MIME type when a file is downloaded.

The StorageManager manages the FileSets, FileWrappers and FileFormats.

doks.user

In this package you find the User and UserGroup classes, together with their DTO's and the UserManager. Users and UserGroups have in DoKS a similar meaning as in Unix in that they act as owner or group for SecuredObjects (the base class for Records, Folders, Scripts, FileSets, Users and UserGroups).

doks.scripting

A script can be either a Java class that implements doks.scripting.Executable or a BeanShell script that is stored in the database. Both are wrapped in a doks.scripting.Script object.

The ExecutableImpl class is a utility class that implements the Executable interface. It can serve as base class for your own scripts.

Scripts can be executed explicitly by a call to the ScriptingFacade, or they can be executed implicitly by the RecordFacade:

  • doks.facade.RecordFacade.createRecordDTO(String structureName) will execute the script named structureName_init if it exists
  • doks.facade.RecordFacade.createRecord(String folderId, RecordDTO recordDto) will execute the script named structureName_create if it exists
  • doks.facade.RecordFacade.updateRecord(RecordDTO dto) will execute the script named structureName_update if it exists
  • doks.facade.RecordFacade.deleteRecord(String id) will execute the script named structureName_delete if it exists

A java.util.Map is used to provide a script with parameters. The script can use the same Map to send results back to the caller.

doks.accessControl

The AccessControlFacade is used to login/log out and to change owner, group and permissions for SecuredObjects. Besides the AccessControlManager, there are two sub packages in the doks.accessControl package:

  • doks.accessControl.login: this package contains the JAAS login modules (implementations of javax.security.auth.spi.LoginModule). Currently there are two login modules that cheque the users password via an LDAP server (LdapLoginModule and LdapSearchLoginModule). Both extend BaseLoginModule. The BaseLoginModule can be used as base class for new login modules (f.e. login module that contacts an IMAP server to cheque the password). New login modules have to be configured in doks_home/conf/auth.conf
  • doks.accessControl.permissions: this package contains the CheckPermissionsAspect class. The functions in this class cheque read, write...permissions and filter lists of records so that they contain only records that user may see. These functions are post-compiled into the different managers with Aspectwerkz (see doks_home/conf/aspectwerkz.xml).

doks.importexport

The ImportExport facade can be used to export records, folders, fileformats, scripts, users and user groups to XML and import them back into DoKS.

Exporting is straightforward: the data and XML tags are directly written into a file.

Importing data is handled by a SAX parser and has to be done in two steps:

  • the XML is parsed and the objects (records etc.) are instantiated. This is done by the InstantiatingHandler
  • the XML is parsed again but now references between objects are restored (links between records, owner and group, parent folders...). This cannot be done in step one because the referenced object (the owner etc.) may not have been instantiated yet. This is done by the InstantiatingHandler

doks.search

DoKS uses the Lucene search engine to index everything.

doks.framework

The doks.framework package contains the base classes and the classes that handle transaction management.

  • doks.framework.BaseManager: the base class for all manager classes
  • factory classes: classes to instantiate the facades
  • doks.framework.ManagedObject(DTO): base class for everything that is stored in the database; holds a unique id.
  • doks.framework.SecuredObject(DTO): extends ManagedObject(DTO) and serves as base class for everything that has an owner, group and permissions
  • doks.framework.TransactionAspect:contains the function that creates a new Hibernate Session, starts a transaction (or joins an existing transaction) an stores it in a ThreadLocal where the facade implementations can find it. This function is postcompied into all facade implementation by AspectWerkz (AOP)

Web interface

All the classes dealing with the web interface are located in the doks.web package. The subpackages typically contain an action (Struts action) class that extends doks.web.BaseDispatchAction and a form class (Struts form) that extends doks.web.BaseForm.

Displaying a record

When a user requests a record, following steps are executed:

  • Struts maps the request parameters to the RecordForm and calls the appropriate method on a GetRecordAction object
  • The GetRecordAction object fetches the requested RecordDTO from the RecordFacade to put it in the request scope.
  • Struts loads the appropriate Velocity macro (web/record/View.vm in this case) that can display the RecordDTO

Displaying folders, users, etc. happens in a similar way.

Saving a record

When a user wants to save a record, following steps are executed:

  • Struts maps the request parameters to the RecordForm.
    • Because Struts only knows how to populate Strings (and some basic types), the RecordForm contains a RecordFO (form object). A RecordFO is similar to a RecordDTO, but the fields are all Strings (or primitive types). A class that extends RecordFO is generated for each structure. This means that we also need a class that extends RecordForm for each structure.
    • All these different forms need to be declared in Struts configuration file (struts-config.xml). This is achieved by generating two files (structureActions.xml and structureFormBeans.xml) that are included in struts-config.xml as external XML entities.
  • The EditRecordAction object copies all the fields from the RecordFO to the RecordDTO. and then saves it via the RecordFacade.
  • The EditRecordAction object saves the updated RecordDTO via the RecordFacade.

Saving folders, users, etc. happens in a similar way.

doks.web.RequestProcessor

DoKS provides its own request processor to Struts. This request processor filters out URL's of the form /do/files/file_set_id/file_name. In this way relative paths of files in a file set can be maintained.This is important for HTML files that refer to images in a separate directory (the file set can f.e. contain one file index.htm and a file img/image1.jpg, when the HTML file contains <img src="img/image1.jpg"> this image will be found).

Code generation

This section gives a summary of all the code (and other files) that are generated by DoKS.

Generation is done with Velocity macros that can be found in the doks.codegen package.

  • structure.vm: generates a class that extends doks.record.Record for each structure
  • datatransferobject.vm: generates a class that extends doks.record.RecordDTO for each structure
  • formobject.vm: generates a class that extends doks.web.record.RecordFO for each structure
  • structureForm.vm: generates a class that extends doksweb..record.RecordForm for each structure
  • structureActions.vm: generates one XML file with an <action> element for each structure
  • structureFormBeans.vm: generates one XML file with a <form-bean> element for each structure