| First steps |
|
Elexis developer resources© 2010 by G. Weirich Table of contents
PrefaceElexis is originally a physician’s practice management and electronic medical record (EMR) software. It is build on the Eclipse RCP and therefore extensible in many ways. The kernel is relatively small and contains some simple CRM elements. Specific behaviour and capabilities are introduced with plug-ins. Elexis is distributed under the Eclipse Public License and thus can freely be extended or modifed. This article will help you in creating plugins for Elexis. This document is written using the Textile markup language. More about it can be found in our readme. Getting startedPrerequisitesWe assume that you have good knowledge of the Java programming language and at least a basic knowledge of the Eclipse environment. Beside this you’ll need:
Clone the repositoryPlease follow the instructions in mercurial-how2. We recommend cloning the elexis-base repository only to begin. Create a run configurationIn Eclipse, choose Window..Preferences..General..Workspace. Set the Text-Encoding to ‚UTF-8’. In Eclipse, choose Run-Run configurations. Right click on „Eclipse Application” and select „New”. Enter an appropriate name for the project. You should see something like here: In „Program to Run” select „Run a Product” and find ch.elexis.ElexisProduct in the Combobox. Then, go to the tab „Arguments”: If you do not change anything, elexis will run in the default locale of your operating system. If you want to try a different locale and language, you can add an -nl parameter in the upper box „Program arguments”. The lower box „VM arguments” can be left empty for the beginning. Proceed to the tab "Plug-Ins"
Then you should be able to start elexis with „Run”. Programming guidelinesYou are very welcome to provide any type of plugin for elexis. Please adhere to a few rules to avoid conflicts between different plugins. 1) Name of your pluginYou may name your plugin whatever you like but we recommend that you prefix it with your name or the name of your organization. The prefix elexis- must not be used. this is reserved for our own plugins. We recommend, that you chose a name dervived from the Plugin ID (as described below). 2) Plugin ID of your puginEvery plugin must have its unique ID. It is common use that such an id consists of an inversely written url of the provider. So, all Plugins of elexis.ch have a plugin ID starting with ch.elexis. We recommend, that you use a similar naming scheme. In any case, the prefix ch.elexis must not be used. 3) Packages of your pluginAgain, it is common use to name the packages of a Java programm after its originating url. Therefore, all elexis classes reside in packages prefixed with ch.elexis. We recommend that you use a similar scheme. In any case, the prefix ch.elexis must not be used. 4) Name of your TablesTo avoid name clashes between the tables of different Plugins, it is mandatory that you prefix the names of the tables your plugin creates (if any) with the pluginID. For sql syntax reasons, every dot in the name must be replaced by an underscore. Thus the plugin ch.elexis.something will create a table like CH_ELEXIS_SOMETHING_TABLE1 5) Coding conventionsAdhering to our style guide is easy in eclipse: 6) DocumentationIt is recommended to document your work. Besides of the javadoc, we recommend that you include at least a ‚readme.txt’ or ‚whatisit.textile’ file in the root of your plugin that describes, what it does and what it depends on. If you want to give your users a more elaborate documentation, it should be included in a folder called doc at the root of your plugin, the main file named after the plugin. Our continuos integration tool provides you with the latest Javadoc. If you want to check whether the Javadocs for your changes are okay, go to BuildElexis/rsc/build, call ant javadoc and preview your changes here. For newer documentation, we use the simple Markup-Format ‚Textile’. Eclipse’s Mylyn plugin can manage these files out of the box and conversion to HTML and FOP/PDF is easy. Besides this, LaTeX is still supported. If you use other formats than Textile or LaTeX, your documentation can not be generated automatically during build. Hence if your plugin is called my-cool-plugin, there should be at least one file called my-cool-plugin.tex or my-cool-plugin.textile in a folder with the name ‚doc’. If so, documentation can be generated automatically. More information on the wikitext editor and the textile format can be found here: More information on TeX/LaTeX can be found here: 7) Testing and writing JUnit test casesTo guarantee that your non-UI, non trivial stuff is working, write UnitTests. UI-tests and ideas/experience in how to make them work are welcome. At the moment we put all JUnit-tests into a separate eclipse-project TestElexis. This has the advantage, that the dependencies for JUnit, etc. don’t get mixed up with the sources. We migthl switch to the TPTP. But its learning curve is a little bit steeper than with JUnit. The setup demands also some more thinking. TPTP has its main advantages if you want to parameterize your testcases, set up datapools, run tests on various targets (e.g. inside your eclipse, on a remote computer with a different OS). Write your JUnit tests using the checklist:
As with the Javadoc you will find the results of the last build. To run your tests, please have a look at the Ants Junit Task, as you have to install ant and make the junit.jar available to it. E.g. by copying it from the eclipse/plugins into the lib directory, where you installed ant. Go to BuildElexis/rsc/build, call „ant junit” and you should find the results under elexis-developer-resources/junit. 8) Becoming a committerIf you feel that you’d like to offer your work to the community, we are happy to invite you as a commiter. Being a committer gives you write access to the elexis repository, but gives you also responsabilities:
If you do not want to take this responsability, but still would like to offer your work, please send it via e-mail, so we can integrate it in the repository for you. ConceptsPersistence and Database AccessElexis uses an abstraction layer to separate java’s object orientated programming style from the needs of the underlying relational database. Furthermore, the abstraction layer hides differences between database brands and thus allows the elexis programmer to focus on his/her programming model without having to think about persistence. Unlike similar abstraction layers, the elexis model does not rely on translation files or annotations. Instead, programmers are enforced to design their persistable classes in a specific way: If one wants to create a new persistable class, the following steps are needed:
We recommend to use the classes ch.elexis.developer.resources.model.SampleDataType and SampleDataTypeFactory in the plugin elexis-developer-resources as a guideline on how to write such a database model. Access privilegesElexis has a hierarchical privilege system that is organized in roles and users. The meaning of a privilege is defined by the implementor. We organize the access rights as a hierarchical system of ACE, similar to a file system path. The right „foo” might supersede a right „bar” which, in turn overlays the right „baz”. This dependency is written as „foo/bar/baz”. The grant of a right includes all rights contained therein. So if one has the right „foo” he or she implicitely ha also the rights „bar” and „baz”.Each privilege can be granted to a single user or to a role. Any user with the role admin implicitely has all privileges. A user who has the right „ACE_USERS” can grant or revoke rights to users and groups. This can be done in the preference page „Gruppen und Rechte/Zugriffssteuerung”. A Plugin that wants to protect one ore more of its resources with ACE’s should do the following.
A Plugin can grant one or more access rights to a single user by calling Hub.acl.grant(user,strings... ) or to a role by calling Hub.acl.grant(role,strings...). Privileges are revoked by calling the respective Hub.acl.revoke – methods. Note that privelegs are stored in the elexis database and therefore are effective for all work stations. A view should define by itself the necessary rights to display its content, and should check if the loggend in user has the necessary rights before displaying its contents. You can use ch.elexis.action.RestrictecAction to implement an Action that honors an ACE. The sample code of the plugin elexis-developer-resources contains examples for use of the ACLSystem (see ACLContributor.java and SampleView.java) ConfigurationThere are four configuration areas in elexis. Programmers should take care to use the correkt place for configuration data:
Warning A plugin must not make any assumptions on how and where configuration data are stored, but always use te API Hub.xxxCfg#get() and Hub.xxxCfg#set() and so on. Event Dispatcher systemIn elexis, a view cannot rely on being part of a specific window layout. Instead, a view can be linked to any of an arbitrary number of perspectives, can be hidden or visible or can be closed. Therefore a view must not make any assumptions on other views being available at any given moment. This aticle describes, how a view can be informed on user actions in other views anyhow. The ch.elexis.actions.ElexisEventDispatcher class is the central point for exchange of informations on user selections. ElexisEventDispatcher is a singleton that can be accessed with ElexisEventDispatcher.getInstance(). Views that receive user actions must inform the system by calling ElexisEventDispatcher.fire(ElexisEvent ee) - methods. Classes that want to be informed on user actions in other views must register one or more ElexisEventListeners on ElexisEventDispatcher. Classes that want to know which instance of a specific class was selected last, can call the ElexisEventDispatcher.getSelected(Class c)-method. Note: To optimize performance, we recommend following rules:
This ensures that listeners are only called if their view is visible to the user at the moment of the selection. The sample code of the elexis-developer-resources plugins contains extensive examples on using the ElexisEventDispatcher. KeybindingsThis section describes how to declare keybindings using the Eclipse framework. Let’s consider an action „renameAction”, which is used to rename some object, e. g. a file name. You can find this example in the plugin „elexis-externe-dokumente”. Generally, you need to define a command for each action you want to assign keybindings to. You can use existing actions already defined by Eclipse, like e. g. „org.eclipse.ui.edit.rename”, or you can define your own actions in the plugin editor. Each command has a unique id. The pre-defined action for renaming has the id „org.eclipse.ui.edit.rename”. To use this command, you must first assign it to the action, and then register it an action handler for it. renameAction = new Action() {
...
}
...
renameAction.setActionDefinitionId(GlobalActions.RENAME_COMMAND);
GlobalActions.registerActionHandler(this, renameAction);
This should be all required to do for a pre-defined command. If you want to define your own key bindings, you require to define a context, a command and a binding. Let’s consider an keybinding to export data to the clipboard. The key binding should be „CTRL-E”. First, declare a new context in plugin.xml: <extension point="org.eclipse.ui.contexts">
<context id="org.iatrix.view.context"
name="%iatrix.view.context.name"
parentId="org.eclipse.ui.contexts.window">
</context>
</extension>
Next, declare a command (including a category, if required): <extension point="org.eclipse.ui.commands">
<category
id="org.iatrix.commands.category"
name="%iatrix.commands.category">
</category>
<command
categoryId="org.iatrix.commands.category"
id="org.iatrix.commands.export_clipboard"
name="%iatrix.commands.export_clipboard">
</command>
</extension>
Then declare a binding <extension point="org.eclipse.ui.bindings">
<key
commandId="org.iatrix.commands.export_clipboard"
contextId="org.iatrix.view.context"
schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"
sequence="M1+E">
</key>
</extension>
Now, the new command is known, including the context where it should be valid, and the key binding to be used. In the view, where you want to use the command, you first need to declare the context: Declare the context id and the command id as constants: private static final String VIEW_CONTEXT_ID = "org.iatrix.view.context"; //$NON-NLS-1$ private static final String EXPORT_CLIPBOARD_COMMAND = "org.iatrix.commands.export_clipboard"; //$NON-NLS-1$ At the end of your createPartControl() method, call „activateContext()”, which may defined as: /**
* Activate a context that this view uses. It will be tied to this view
* activation events and will be removed when the view is disposed.
* Copied from org.eclipse.ui.examples.contributions.InfoView.java
*/
private void activateContext() {
IContextService contextService = (IContextService) getSite()
.getService(IContextService.class);
contextService.activateContext(VIEW_CONTEXT_ID);
}
Whenever the Iatrix view is active, the context „org.iatrix.view.context” will be active, and the key bindings for it will be valid. Then, assign the command to the action as in the previous example: exportToClipboardAction = new Action("Export (Zwischenablage)") {
...
}
...
exportToClipboardAction.setActionDefinitionId(EXPORT_CLIPBOARD_COMMAND);
GlobalActions.registerActionHandler(this, exportToClipboardAction);
Further documentation: http://help.eclipse.org/help33/topic/org.eclipse.platform.doc.isv/guide/wrkAdv_keyBindings.htm Drag&DropEclipse has drag&drop support by itself. Elexis offers a simplification of this technique, optimized for PersistentObject’s. A data type must only override Drag sourceA PersistentObjectDragSource can be attached on any control or StructuredViewer. This happens automatically on creation of such a PersistentObjectDragSource with one of its constructors:
In the first case, the ISelectionRenderer must produce a java.util.List Drop targetAn org.eclipse.swt.widgets.Control can become a drop target by attaching a PersistentObjectDropTarget to it. This is achieved by just creating such an Object via one of its constructors
CodeSelectorHandlerSometimes, it is more convenient to select Objects to send to a Control via double click or with a key press instead of drag&drop. The problem is, that a double-click or a keystroke does not contain any information on the destination the user thinks of. This is addressed by the CodeSelectorHandler concept. When a selection view is openend (e. g. Leistungen or Diagnosis), the view doesn’t know about its caller. Actually, it’s not really opened, but just brought to the foreground and focused (maybe it’s first loaded if it hasn’t yet been used so far). The CodeSelectorHandler system allows a caller to announce that it wants to receive an object. When the caller has registered itself with CodeSelectorHandler.getInstance().setCodeSelectorTarget(), and the user selects an object in the selection view, the caller is informed about the selection and can handle it as if it would have been dropped After registration as a CodeSelectorTarget, the caller should visually mark itself so that the user knows where selected objects will end up. If the selection operation has finished, the caller will be informed and can remove the visual mark. To avoid confusion, the selection operation is finished as soon as the selection view looses the focus. The caller can decide if the selection operation should should finish after one object has been selected, or if it should be possible to select multiple objects in one operation. See ch.elexis.actions.CodeSelectorHandler for more information. System independent universal object identifiers: XIDObjects used in an EMR System frequently do have many identifiers before they first come to our user’s practice. A Patient might be identified by a Social Security Number, by a Health Insurance Card Number, by a Passport Number, an AHV Number and many more. This article will discuss how to handle such different identifiers. The task of identifying an object uniquely becomes more important as we want to share objects between different EMR systems. We might see a patient that was treated by a collegue earlier and want to receive the EMR data from this collegue. Or we want to receive laboratory values or other findings from different sources and ye be able to identify such items unambiguously. To deal with such problems while exchanging EMR data, Elexis introduces the concept of a XID (eXternal Identifier). A XID is an identifier that consists of one or more pairs of domain/id values. A Domain is a String describing the classification system. An ID is a String that describes the given object within this classification system uniquely. In Elexis, the following API for handling XID’s exists: Xid.localRegisterXIDDomain(String domain, int quality) The name for the domain is any alphanumeric string that must not contain the characters # and ; XID encourages the following rule: Use a string that is guaranteed under your control. We recommend to use an URL you own as prefix for your XID-Domains., e.g. www.elexis.ch/xids/myownXidDomain. If you do not want to use such a String, you should register your XID-Domain with www.xid.ch to avoid name conflicts. Please note that this method will register your domain only locally for the database where it was issued. The registration will, however, persist between program launches and workstations accessing the same elexis database. PersistentObject#getXID(String domain) PersistentObject#getXID() PersistentObject#addXID(String Domain, String id, boolean updateIfExists) Xid.findXid(String domain, String id) Xid.findXID(PersistentObject ob, String domain) Xid.findObject(String domain, String id) Internationalization (il18n)In eclipse, designing your UI for different languages, is quite easy, if you think about this topic from the beginning!
Elexis will use the messages_xx.properties and plugin_xx.properties of the locale that matches best the operating system, or the -nl parameter. If no matching locale is found, the default (messages.properties and plugin.properties) will be used. Creating a pluginElexis plugins are basically eclipse 3.5 plugins and therefore OSGi-Bundles. You’ll find a lot of literature on these topics on the web, Recommended reading includes Ed Burnette’s Rich client tutorial, and Dave Springgay’s Eclipse Views tutorial along with other articles found on the eclipse website. There are also some books on programming OSGi Bundles and Eclipse plugins. You should have a basic knowledge on programming eclipse before proceeding. The sample pluginTo provide a convenient starting point, the Plugin elexis-developer-resources is deliberately simple designed and well documented. You might want to use this to start learning about plugin development for elexis. In this section, we’ll describe that plugin detailed. The following files belong to the plugin elexis-developer-resources:
Testing and integrationSince April 2010 we are starting to use Hudson (http://hudson-ci.org) as a CI (Continuos Integration) tool. It’s output can be found at http://ngiger.dyndns.org/hudson. It will probably take months if not several years to achieve a good coverage of the whole elexis code base. An interesting strategy for developing good code is Test Driven Development (TDD), where you write first tests, which will fail. Afterwards you fill in the code to make them pass. Writing tests allows you to specify use cases first. At the moment there is very little tested on each checking, as setting up an automated build process is quite a lot of work. At the moment it only generates the documentation. Further steps will include:
Setup of the testing environmentThe Elexis CI-environment consists of several VMs (virtual machine). They run inside an ArchivistaVM (AMD QuadCore, 8GB RAM, 1 TB HD).
Further ideasThe following ideas are not ordered in any way.
|
First steps 


