Pierre Tutorials

Creating a Pierre Service


The General Approach


You should begin by writing a Java class which implements pierre.db.DataRepository. You can save yourself some work by using pierre.db.AbstractDataRepository, an abstract class that already has some methods that have been filled in.

Before you begin writing your own repository, make sure the following things are installed:

You should be able to type ant at the command line and have it work on a build file. To run the tutorial, you should install MySQL and move the data files into the area the program uses to hold its databases.

The process of creating a Pierre search and browse service is described in the following steps:

Step 1: Developing a Domain Model This involves writing an XML schema that describes the domain concepts you are working with. For this step, please refer to the Pedro data modeller documentation at: pedrodownload.man.ac.uk.

Step 2: Add in Controlled Vocabaulary Services You may want to allow users to mark up query forms using terms that come from one or more different ontologies. The way you associate ontology services with form fields in Pierre is identical to the way you do this in Pedro. Therefore, please again refer to the data modeller documentation at: pedrodownload.man.ac.uk

Step 3: Create a Specification for a Search and Browse Service The next step is to load the Pierre Configuration tool with the model you've created. You can then proceed to create a service description based on aspects of the schema. The documentation for this is in a separate tutorial that should be included with your distribution.

In this step, the service designer will develop a specification that can be used with a dummy repository. This repository class will simply return junk data for all the query results fields. The point of doing this is to allow the end-users to get a good idea for what they should expect in the finished service.

Step 4: Implement a Data Repository This will involve writing a Java class that implements the pierre.db.DataRepository class. We recommend that you make your class extend pierre.db.AbstractDataRepository because this class already has some of the methods filled in. More information about implementing a repository is covered in later sections.

Step 5: Jar the Code for the Data Repository You should create a jar file to hold all the class files you've created for the data repository class. This includes the Java class that implements pierre.db.DataRepository and all the classes it refers to.

Step 6: Make the Service work with a Live Repository The service designer should use the Pierre Configuration Tool to change the service so it works with the live repository. This involves specifying the fully qualified path of the class which implements pierre.db.DataRepository. It also involves specifying the jar file created in Step 4 as well as any other jar files needed to make the repository work. For example, the tutorial requires a file called mm.jar, which contains the database driver classes needed to interact with an SQL database.

Step 7: Preview the live service to users Take the opportunity to determine whether the live service meets the needs of the users. If it doesn't, then either the service designer needs to modify the specification or the repository designer has to change some code.

You can also review the service by using the configuration tool to automatically generate a full functional specification. You can print this out and hand it to people for their feedback.

Step 8: Autogenerate Applications When the users, service designer and repository designer are happy with the way a service works, the service designer can cause the configuration tool to simultaneously generate multiple forms of deployment.

Autogenerating the services requires you to specify three things:

The configuration tool will let you know that it has created an ant file. In the directory where you started the configuration tool, type "ant". You should then see "ant" install all your files in the destination directory.

Look in your installation directory. You should now be able to exercise the various deployments. You should also see a folder with the name of the web application you specified earlier. Take this folder and move it to the tomcat/webapps folder. You should then start tomcat and type something like: http://localhost:8080/



An Overview of the Tutorial Code Packages


You should now look at the source code for the tutorial. It is organised into packages, which have these themes:

The major class to consider is the tutorial.db.PatientDataRepository class. It implements the pierre.db.DataRepository interface and delegates funcationality to the other classes. "db" contains classes for creating, populating and accessing the SQL database. tutorial.db.PatientDatabaseOperations is responsible for executing SQL queries and storing the results in data container classes belonging to the tutorial.model package.

The classes in tutorial.model are all designed the same way. Each model class:

Model objects are passed to specialised reports that are part of the tutorial.reports section. Many of the reports simply extend pierre.reports.TupleReport. TupleReport is a report that is able to render pierre.reports.Tuple objects in a variety of ways.



Creating an Implementation of pierre.db.DataRepository


This section is explained through an examination of the tutorial data repository. You should begin by creating a Java class that implements pierre.db.DataRepository. You can save yourself some work if you simply have your class extends pierre.db.AbstractDataRepository. The following headings explain how you should go about implementing the methods in the DataRepository interface.

The first method you should implement is setParameters(...), which is where the system will pass parameters that were specified in the Database panel of the Pierre configuration tool. These would be things like server, port, and webApplicationName. You can add others if you like. At the end of this method, you should call some sort of initialisation method. This is where you may want to instantiate things such as a connection manager for managing muliple users who connect with a database.

setParameters(Parameter[] parameters)

You will probably want to have a "for" loop that goes through the list of parameters and looks for parameters such as "server", "port" and "webApplicationName". You may want to concatenate these together to get the starting part of hyperlinks. You may also want to return this fragment under getServerInformation().

You will have to include pedro.util.Parameter. Parameter has two useful methods getName() and getValue().

You should probably initialise most of your code when this method is called. You can create a routine such as "init()" and call it after you've cycled through the parameter list to obtain values you need to make your implementation work.

getServerInformation()

This method is used by the deployments to help construct hyperlinks. For tomcat applications, it should look something like:

SecurityService getSecurityService()

This is a method that returns a security service to the Pierre deployments. The pierre deployments use this service to help determine whether the user can:

When null is returned for this method, Pierre uses pierre.security.DummySecurityService instead. This service allows users to see all fields and access all features.

authenticateUser(User user)

This helps determine whether "user" is a legitimate user. The pierre deployment forms only make login requests if the SecurityService getSecurityService() method returns a non-null value.

In AbstractDataRepository, "authenticateUser(User user) always returns true. You may wish to override this method with code to lookup a user in some registry. You can extract the userID and password from a pierre.security.User object through the getID() and getPassword() methods respectively.

getDataSetSummaries(String[] browseAttributes, user)

This method returns data container objects, each of which represents a top level view of a data set. It is used by the browse features of the deployments. In the tutorial, we have decided that the top level information view of any patient data set should describe the patient demographics.

getExistingValues(String recordClassName, String fieldName, String schemaContext, User user)

This will be a branching statement that delegates to other code which extracts a collection of unique repository values for some given record class - field name pair. In this case recordClassName will be the name of a record structure in the XML schema and fieldName will be one of its fields.

The schemaContext object is really only useful for XML-based repository implementations. It is to help specify where in a document a particular record class might be found. For example, the record class "Organism" could be found under "Experiment" records or in the path "Experiment/Sample/Organism". This feature is not relevant to relational database implementations.

Implementing Execute Methods

The DataRepository has three execute methods. One is used to manage responses for simple and advanced searches; another is used to manage responses for Expert Search; and the third is to manage responses for queries that are submitted as information links that have been embedded in reports.

All of the execute calls have parameters for pierre.system.DeploymentForm and pierre.securityUser. DeploymentForm describes what form of deployment called the method. It could be a web application, a standalone GUI application, a text-based menu-driven application, a command-line-service or a library. You may want to use this parameter value to help make the most appropriate kinds of reports that consider the nature of the display. User indicates the person requesting the action to be done. This can be used to help constrain the way execute behaves. Both DeploymentForm and User can also be used to help make usage statistics of the repository.

Simple&Advanced Search execute

Report execute(pierre.model.QueryFeature queryFeature,
              pierre.system.DeploymentForm deploymentForm,
              pierre.security.User user)


You should begin by using queryFeature.getName() to obtain the name of the feature. You can use this to make branching statements that delegate to other classes which handle a specific query. If the queryFeature was created by an advanced search feature in one of the deployments, the name of the feature will be "Advanced Search". However, you should really try to obtain this by using pierre.system.BrowserServiceResources. You may have a code snippet like:

String advancedQueryFeatureTitle
    = BrowserServiceResources.getMessage("general.advancedSearch.title");

String queryFeatureName = queryFeature.getName()
if (queryFeatureName.equals(advancedQueryFeatureTitle)) {
        //delegate to something that does advanced search
}
else if (queryFeatureName.equals("Query By Proteins") == true) {

}
else if (queryFeatureName.equals("Query By Visit") == true) {

}
else ...

If the name of the feature is the advancedQueryFeatureTitle, then you should defer to a code snippet, which does the advanced search. Otherwise, these queries were created by simple search activities. You should delegate the task of a given query to a specialised class you design. For example, the tutorial uses QueryByPatientDetails, QueryByPrescribedDrugs, QueryByVisit. These classes are all very similar to one another and are explained more in the section on "Interpretting Query Features".

In the tutorial, the body of the method is enclosed by a try...catch statement. If any error occurs, then the execute(..) method returns an instance of pierre.report.ErrorReport. If the feature is not recognised in the branch statements, then pierre.report.UnderConstructionReport is returned. Both of these reports are not required but they are convenient to use.

Expert Search Execute

public Report execute(String dataBase,
                      String query,
                      DeploymentForm deploymentForm,
                      User user);


The two parameters to deal with here are "dataBase" and "query". Database can be used to help you determine which data resources you should apply the query to. The query will be written in the native query language understood by the technologies used to manage your data (eg SQL, XPath, etc.)

Pierre does no error checking on the query string. However, you can see how to process errors in the tutorial example. If the query has mistakes in it, then the call to "executeQuery" will trigger an exception and be caught in the catch statement. Here, you can create an error report and add the exception as an error to be described to the user.

Link Execute

public Report execute(pierre.reports.LinkObject linkObject
                      pierre.system.DeploymentForm deploymentForm,
                      pierre.user.User user);


This execute method gets called when a user selects an internal link that is embedded in some report. The internal link is a request for additional information. You can use linkObject.getParameterValue("") to determine what items exist.

I would recommend that when you create your links, you remain consistent with certain parameters. For example, all the internal links used in the tutorial will have a parameter called "action" that can have values such as: "showVisits", "showDiagnoses", etc. This is used to help delegate the task of executing the link to some specialised piece of code. For more information on Links, see the section on building reports.