Permission is granted to make and distribute verbatim copies of this manual provided that the copyright notice and this permission notice are preserved on all copies.
This manual describes how to develop applications to run with the Naked Objects framework, from simple prototypes to enterprise systems.
You can download Naked Objects via www.nakedobjects.org, or direct from SourceForge, under an open source licence.
This edition of the manual is intended for use with version 4 of the Naked Objects framework, which works with Java version 1.5 or higher.
Please note that the Hibernate plugin is not available in version 4.0, although it should be available in a subsequent release. Therefore, the sections of this manual that talk about Hibernate are not relevant at this time.
This part of the documentation aims at getting you started with Naked Objects in a short space of time. It cover basic installation, building applications, running them and then an overview of how they actually work. The next part of the manual then goes into each of these areas in more detail.
Before we launch into using and developing Naked Objects you need to have the Naked Objects binary distribuion installed. This is covered in this section if you have not already installed it. There are other ways of building and running Naked Objects, but these are covered in the next part of this manual.
To build and run Naked Objects applications for this tutorial you need Java version 1.5
Download from http://java.sun.com/
or above and Ant 1.7Download from http://ant.apache.org/
installed on your machine.The latest version of the Naked Objects framework can be downloaded from the downloads page of the Naked Objects Sourceforge project
See http://sourceforge.net/projects/nakedobjects/
as shown below. For this tutorial you should download the binary release (suffixed with '-for-ant' ) for immediate use. (There are three other versions available, suffixed '-for-maven' '-source', and '-libs-only-with-dependencies'. You can ignore these for now. TODO: replace screenshot below with that for NOF 4.0.
Extract the downloaded file's content into a suitable directory (a root directory is created upon extraction so there is no need to create a directory in which to install everything). As an example here is the compressed tar file being extracted
$ tar xzf nakedobjects-4.0-for-ant.tar.gz
Once expanded you should have the following directories and files:
/nakedobjects-4.0
/demos
/docs
/examples
/lib
LICENSE.TXT
/resources
Change into the Naked Objects directory before continuing the tutorial
$ cd nakedobjects-4.0
The Naked Objects distribution comes with a demonstration application for expenses processing i.e. for creating, submitting and approving expense claims. The application has only limited functionality - a real expenses processing application would require many more features - but what it has is realistic in terms of implementation.
There are two ways in which you can run the demonstration: as a standalone executable, or building it and running it from within a development environment. This chapter covers the former approach and looks at how a Naked Objects application is used, while the next chapter looks at building and modifying the example allowing you to browse and modify the source code.
The files for running the demo can be found in the demo/expenses directory, while the source code and build files are in the examples/expenses directory.
The executable version of the Expenses demo application exists within the demos directory of the Naked Objects distribution.
Run the batch file ExpensesDND.bat file either by double-clicking on the icon (in Windows) or using the following commands on the command line. In Windows the commands are
> cd demos\expenses > ExpensesDND.bat
while on the Mac or when using Unix/Linux they are:
$ cd demos/expenses $ ./ExpensesDND.sh
After seeing the Naked Objects splash screen, you'll be presented with a Login screen:
Log in as 'sven' and a password of 'pass'. The application will then open like this:
When an application is started an application window is opened and the user's services are displayed on the left hand side. These icons typically give the user access to the domain objects held and used by the system, and provide a way for to create new instances. Any other object that subsequently appears on the screen represents one of those domain objects.
TODO replace with up-to-date shot using current example
The service and objects can be manipulated as follows:
To run the expenses demo with the HTML user interface, run the batch file ExpensesHTML.bat either by double-clicking on the icon (in Windows) or using the following commands on the command line. In Windows the commands are:
> cd demos\expenses > ExpensesHTML.bat
while on the Mac or when using Unix/Linux they are:
$ cd demos/expenses $ ./ExpensesHTML.sh
Allowing a few seconds for the application to start up, you then need to launch a browser and point it to http://localhost:8080/logon.app and you'll be presented with a Login screen.
Log in as 'sven' and a password of 'pass'. This will direct you to the start page of the application, a welcome page is displayed and links to the application services are shown across the top. In the same ways as for DND these links give the user access to the objects held and used by the system and provide a way to create new objects. Any other object that subsequently appears on the screen represents one of those objects.
For example if the claims link is clicked on within the expenses demo the claims service page is shown with the resource actions available in a menu on the left hand side of the screen.
Now that you have used an application created for Naked Objects we will now look at how to develop one of your own. This chapter explains how to build, run and modify a prototype.
The examples directory contains a couple of examples. Here we will use the expenses one. After going to the directory we can use Ant to compile and run the code.
$ cd example/expenses
$ ant
Buildfile: build.xml
compile:
[mkdir] Created dir: /home/rcm/tmp/install/nakedobjects-4.0/examples/expenses/build
[mkdir] Created dir: /home/rcm/tmp/install/nakedobjects-4.0/examples/expenses/build/classes
[javac] Compiling 59 source files to /home/rcm/tmp/install/nakedobjects-4.0/examples/expenses/build/classes
[javac] Note: Some input files use or override a deprecated API.
[javac] Note: Recompile with -Xlint:deprecation for details.
[javac] Note: Some input files use unchecked or unsafe operations.
[javac] Note: Recompile with -Xlint:unchecked for details.
dnd:
(Remember under Windows paths need to use backslashes instead of slashes.)
Naked Objects creates the user interface for an application directly from the definitions of the domain model. In this section we will look at that relationship in more detail, with reference to the Expenses Processing example application supplied as part of the download. As we showed in the previous section, any domain model written for Naked Objects may be run with any of the viewers - there is no specific coding required, and the domain model has no knowledge of which viewer is being used. However, each viewer will have different gestures or mechanisms for providing the same functionality. To illustrate this, we will show the same objects being accessed through both the DND and the HTML viewers, side by side.
The application code for the Expenses Processing example, like any Naked Objects application, consists of two things: domain objects and services. The domain objects form the lion's share of that code, so we'll look at how those work first.
The code for examples we will be looking at can be found in the directory examples/expenses/expenses-dom/src in the downloaded files.
The domain objects are the entities - the nouns - that represent the application domain: employee, claim, expense item, project code, currency, and so forth. In the course of using the application, a user will view and manipulate many instances of these domain objects. To understand how Naked Objects handles domain objects, we'll start by looking at an Employee object:
Every object presented in the user interface will have a corresponding Java class in the domain model - in this case it is org.nakedobjects.example.expenses.employee.Employee. Below we can see the code for the Employee object, as presented in Eclipse, with the object's list of methods presented on the left hand side.
The first thing to note is that the type of the object as shown in the user views is derived directly from the class name in Java. The framework inserts spaces before capital letters, so that the class TemporaryEmployee would be presented to the user as 'Temporary Employee'. However we will see later that the name may be over-ridden where necessary, for example if we want the name to include punctuation or other characters not allowed in Java class names. (Note that there is a separate mechanism for dealing with internationalisation).
Secondly, we can see that Employee extends AbstractDomainObject - a class provided within the Naked Objects application library. This is not a requirement: your domain objects may be Plain Old Java Objects (POJOs) - they do not need to extend any class in the framework. However, extending from AbstractDomainObject will save us having to write a few lines of code in each case, as we'll see later.
Note also that in the body of the object we use 'code folding' (the plug-in used here is Coffee Bytes) to break the object's code into regions, each typically containing one or more related methods that together fulfill a high-level responsibility of the object. This is just a coding convention, not a requirement.
In both of the user views of an Employee we can see a field called 'Name'. Within the Employee class there is a Name region of code, expanded here:
// {{ Name
private String name;
@MemberOrder(sequence="1")
@Disabled
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
// }}
The Name region contains a simple property, of type String, defined by a getName and a setName method. This is sufficient information to allow the viewers to display a field containing a textual value. Note that if the property is to be persisted, then it will need both a get and a set method - per the standard JavaBeans convention. If you wish to display a field that is derived automatically from other information in the object, and do not require this to be persisted, then a get alone will suffice.
As with the name of the object, the field name is derived automatically from the name of the property - though we'll see later that this may be over-ridden if needed.
The getName has been marked up with two Java annotations, both defined in the Naked Objects application library. Annotations allow the programmer to enrich the information available to the framework. On properties, any Naked Objects annotations are always associated with the get method. However, annotations are not mandatory - you can write a simple Naked Objects application without using any annotations at all.
By default, any property with both a get and set method will be editable by the user. @Disabled tells the framework that this particular property may never be altered by the user (though it may be altered programmatically). Later we'll see how to make a property modifiable on certain conditions.
@MemberOrder(sequence="1") tells the framework that this property should be the first field displayed in any view of the Employee - irrespective of where it is defined within the code. This ordering information has been observed by both the viewers.
The next region of the code contains another String property, called UserName:
// {{ UserName field
private String userName;
@Hidden
public String getUserName() {
return userName;
}
public void setUserName(final String variable) {
this.userName = variable;
}
// }}
Note that getUserName has been marked up with @Hidden. This tells the framework that this property should never be shown in user views of the object (check this against the two user views above). Later on we'll see how it is possible to hide a property in certain circumstances.
Next we'll look at the EmailAddress region:
// {{ EmailAddress
private String emailAddress;
@MemberOrder(sequence = "2")
@Optional
@RegEx(validation = "(\\w+\\.)*\\w+@(\\w+\\.)+[A-Za-z]+")
public String getEmailAddress() {
return this.emailAddress;
}
public void setEmailAddress(final String emailAddress) {
this.emailAddress = emailAddress;
}
public void modifyEmailAddress(final String emailAddress) {
getRecordActionService().recordFieldChange(this, "Email Address", getEmailAddress(), emailAddress);
setEmailAddress(emailAddress);
}
public void clearEmailAddress() {
getRecordActionService().recordFieldChange(this, "Email Address", getEmailAddress(), "EMPTY");
setEmailAddress(null);
}
public boolean hideEmailAddress() {
return !employeeIsCurrentUser();
}
private boolean employeeIsCurrentUser() {
return getUserFinder().currentUserAsObject() == this;
}
// }}
As well as @MemberOrder, this property is marked up with @Optional and @RegEx annotations. By default, all properties are taken to be mandatory - if the user creates or edits an object then they will be required to specify the contents of each field. @Optional overrides this default behaviour - indicating here that the object may be saved without an email address.
@RegEx is applicable only to String properties. In this case the annotation specifies a Regular Expression that will be used to validate any value that the user types into the field. In a conventional architecture, this functionality would typically be found in the user interface code. The Naked Objects argument is that this functionality should apply to any user interface that might want to change the property, so its proper place is in the object. @RegEx may also be used to reformat a String that has been entered by the user.
The two screens below show how two different viewers make use of the functionality in different ways. In both cases the user has typed in a value that does not match the RegEx specification (they have typed in an email address that contains a space), so the new value has not been accepted or saved.
In addition to getEmailAddress and setEmailAddress, there are modifyEmailAddress, clearEmailAddress and hideEmailAddress methods. Naked Objects recognises the modify, clear and hide prefixes (and a few others that we shall see later) as specifying additional functionality relating to the EmailAddress property.
If a property has a corresponding modify<propertyName> method, then whenever the user modifies the field, this will be called rather than the set. In this case the modify method uses the RecordActionService to record the details of the change, and then calls setEmailAddress to change the value. The reason for adopting this pattern, rather than including the functionality in the set itself, is that the set will be called by the object store each time the object is retrieved. So we use a modify method where we want to do something (such as add to a total) only when the user changes a field.
clearEmailAddress is called, in a similar manner, if the user clears the contents of the field. Again, it is optional - added where we want to perform some logic only when the user clears the property. On the UserName field we saw that @Hidden hides a property from the user permanently. We may, however, want to hide fields under certain circumstances. The visibility of all classes, properties and methods may be controlled via conventional authorization techniques, based on the user's role(s). In rarer cases, we want to control visibility at an instance level. In this case, for privacy reasons we do not want the email address to be visible, except to that person. This is what the hideEmailAddress()method is doing. If the method returns true, the field will be hidden from the user.
Next we will look at the NormalApprover region:
// {{ NormalApprover
private Employee normalApprover;
@MemberOrder(sequence="4")
public Employee getNormalApprover() {
return this.normalApprover;
}
public void setNormalApprover(final Employee normalAuthoriser) {
this.normalApprover = normalAuthoriser;
}
public void modifyNormalApprover(final Employee normalAuthoriser) {
getRecordActionService().recordFieldChange(this, "Normal Approver", getNormalApprover(), normalApprover);
setNormalApprover(normalAuthoriser);
}
public void clearNormalApprover() {
getRecordActionService().recordFieldChange(this, "Normal Approver", getNormalApprover(), "EMPTY");
setNormalApprover(null);
}
public String validateNormalApprover(Employee newApprover) {
return newApprover == this ? CANT_BE_APPROVER_FOR_OWN_CLAIMS: null;
}
public String disableNormalApprover() {
return employeeIsCurrentUser() ? null: NOT_MODIFIABLE;
}
public static final String NOT_MODIFIABLE = "Not modifiable by current user";
public static final String CANT_BE_APPROVER_FOR_OWN_CLAIMS = "Can't be the approver for your own claims";
// }}
The NormalApprover property takes an object of type Employee. Assuming that this field is not disabled, the user may specify an Employee object for this field. Naked Objects will prevent the user from trying to associate the wrong type of object with this field. This is illustrated in the two screens below:
In the left-hand screen (DND) we can see the user dropping an Employee object into the empty field, and the field is flashing green to indicate that this will succeed. If the user attempted to drop another type of object into the empty field, then the field would flash red, and the drop would not update the field. A successful drop will call the set method, or, if a modify<propertyName> method is provided (as it is here), it will call that instead. Note that on the DND viewer, if a field already contains an object, then this may be cleared by right-clicking on that object and selecting 'Clear Association'. This will set the property to null. If there is a clear<propertyName> field (as there is in this example) then that will be called rather than the set method. Alternatively a new reference can be dropped on to the field's label, which combines both the clearing and the subsequent setting of the field.
In the HTML viewer (right-hand screen) drag and drop is not possible. In a reference field such as this one, the user will be given a drop-down list of objects of the appropriate type (i.e. Employees here) that the user has recently viewed. If the required Employee object is not in that list then the user may go and find that object (e.g. from the Employees tab) and then return to the context - this time the newly viewed Employee will have been added to the list automatically. (Note: This is a generic capability provided by the HTML viewer. In other contexts, the programmer may want to specify an explicit list of objects to appear in a drop-down list. This would be achieved by means of a choices<propertyName> method).
The validateNormalApprover method enforces any rules concerning the specific instances of Employee that may be associated with this field. In this particular example, it prevents the user from specifying an Employee as their own approver. Note that this method returns a String. If the specific Employee instance being passed into the method is acceptable, the method should return null; if unacceptable then the method should return a String message that will be made available to the user to advise them why the action will not succeed. (On the DND this appears at the bottom of the screen.)
The disableNormalApprover method prevents the user from modifying the field in certain circumstances. In this example the method enforces the rule that only the Employee themselves may change this field. Like the validate method, it returns a null if the user may modify the field (subject to the validate rules), or returns a String message if they may not. (Note that this method, along with hide (seen earlier) allow for 'instance-based authorization'. Most applications can manage with 'class-based authorization' - in which the classes, properties and actions made available to a user are based on their roles. Class-based authorization in Naked Objects is administered externally to the application and does not require any coding within the domain objects.)
In the next screen we will look at the title region of the Employee object.
// {{ Title
public String title() {
return getName();
}
// }}
The title method specifies the title for the object - which, on both the DND and HTML viewers appears next to the icon. The title is there to help the user identify objects. Naked Objects also provides an easy mechanism to retrieve objects from the object store by their title. Other methods of finding/searching may require repository methods to be written. If no title method is specified, Naked Objects will use the object's toString method as a title. Titles are usually based on one or more of the persisted properties - in this case on the Name. When constructing a title from multiple elements, the Naked Objects application library provides a helper object: TitleBuffer.
The screen below shows the action menu for the Taxi object, as rendered by the two different user interfaces:
By default, any public instance methods on an object, included inherited public methods, will be rendered as a user-action. The exceptions to this rule are:
private, protected, and static methods are ignored by Naked Objects.
For example, the action 'Copy From' on the Taxi object, is derived from this method on the AbstractExpenseItem class (from which Taxi inherits):
@MemberOrder(sequence="5")
public void copyFrom(final ExpenseItem otherItem) {
if (belongsToSameClaim(otherItem)) {
if (dateIncurred == null) {
modifyDateIncurred(otherItem.getDateIncurred());
}
} else if (getClass().isInstance(otherItem)) {
copyAllSameClassFields(otherItem);
}
}
Again, we can see that the method has been marked up with @MemberOrder, which will govern the relative location of this action on the action menu.
Because the copyFrom method takes a parameter, when the user invokes the corresponding menu action they will be presented with a dialog, wherein each of the parameters may be specified. This is shown below on the two user interfaces:
Editing a dialog is similar to editing an object: though there are differences in the way they are rendered (for example a dialog has an 'OK' button in both the DND and HTML user interfaces). Parameters that take value types (such as String or Date) are rendered as fields that the user can type into. Where a parameter is a domain object class or interface, as in this case with ExpenseItem, then the user must specify an object of that type. In the DND user interface, the user may drag and drop an object into the parameter field. In the HTML user interface, the user is automatically presented with a drop-down list of objects of that type that they have recently viewed. If the desired object doesn't appear, they may go and find the object (by navigating from another object, or using a find method on one of the start points) and then return to the dialog, where the recently-located object should now appear on the list.
Adjacent to the copyFrom method on AbstractExpenseItem we can also find the following two methods:
public String disableCopyFrom() {
return disabledIfLocked();
}
public String validateCopyFrom(final ExpenseItem otherItem) {
if (belongsToSameClaim(otherItem) || (getClass().equals(otherItem.getClass()))) {
return null;
}
return COPY_WARN;
}
private final static String COPY_WARN = "Cannot copy";
disableCopyFrom and validateCopyFrom are other examples of recognised methods (see Link). They work in a similar manner to the disable<propertyName> and validate<propertyName> methods that we have previously seen - in this case disabling the action under certain conditions, and validating the parameters of the action. For both the user-interfaces shown, disabling the action will result in it being greyed-out on the menu. If the entered set of parameters does not pass the validity test, this will be brought to the user's attention when they attempt to execute the action (e.g. by hitting the OK button), along with an explanatory message.
By default, the user will be required to specify each of the parameters within the dialog. The programmer may, however, use the @Optional annotation in-line (i.e. immediately before any parameter in the method signature) to specify that that parameter may be left empty.
As stated previously all the application code consists either of domain objects or services, with the former typically representing the lions share of the code. Now we'll look at the services.
Services perform two roles in a Naked Objects application. First, they provide a place to put functionality that cannot be placed on an instance of a domain object, of which the two most obvious examples are:
To fulfill these requirements we could create two separate services, called, say, CustomerFinder and CustomerFactory. Or we could create a single service called, say, Customers, which has methods to cover both requirements. There's no hard-and-fast rule about how services should be partitioned.
The second role that services perform within a Naked Objects application is to bridge domains. The following are examples of what we mean by bridging domains:
In this section we'll look at how services are defined, and in the next section at how they are used.
Services are implemented as Java classes, as are domain objects, but they are handled differently by the framework.
It is good practice to define services as Java interfaces. That way it is possible for the implementation of the service to change over time, without affecting any of the objects that use the service. During development it is often useful to develop a simple 'mock' implementation of a service that can be used either for prototyping or testing purposes; this can then be replaced with a proper implementation as development progresses towards deployment. For example, within the Expenses Processing application, the following Java interface defines a service for sending an email:
package org.nakedobjects.example.expenses.services;
public interface EmailSender {
void sendTextEmail(final String toEmailAddress, final String text);
}
This service definition has just one method, but it could easily have more, such as methods that take a List of recipient addresses, or that can accommodate file attachments. JavaMailSender is an implementation of that service:
public class JavaMailSender extends AbstractService implements EmailSender {
private static final String SMTP_HOST_NAME = "localhost";
private static final String SMTP_AUTH_USER = "expenses@donotreply.org";
private static final String SMTP_AUTH_PWD = "";
private static final boolean authenticate = false;
private class SMTPAuthenticator extends javax.mail.Authenticator {
public PasswordAuthentication getPasswordAuthentication() {
final String username = SMTP_AUTH_USER;
final String password = SMTP_AUTH_PWD;
return new PasswordAuthentication(username, password);
}
}
public void sendTextEmail(final String toEmailAddress, final String text) {
try {
final Properties properties = new Properties();
properties.put("mail.smtp.host", SMTP_HOST_NAME);
properties.put("mail.smtp.auth", authenticate ? "true" : "false");
final Authenticator authenticator = authenticate ? new SMTPAuthenticator() : null;
final Session session = Session.getDefaultInstance(properties, authenticator);
final Message message = new MimeMessage(session);
final InternetAddress fromAddress = new InternetAddress(SMTP_AUTH_USER);
final InternetAddress toAddress = new InternetAddress(toEmailAddress);
message.setFrom(fromAddress);
message.setRecipient(Message.RecipientType.TO, toAddress);
message.setSubject("Expenses notification");
message.setContent(text, "text/plain");
Transport.send(message);
} catch (AddressException e) {
throw new ApplicationException("Invalid email address", e);
} catch (MessagingException e) {
throw new ApplicationException("Problem sending email", e);
}
}
}
We can see that this service performs a technical bridging role: it bridges between the object domain and an external SMTP server.
Since there could be multiple implementations of any one service within our code base, Naked Objects needs to be informed of which services it is to reference when running an application. This is done within the properties files. For example, the nakedobjects.properties file, which may be found within the expenses.app.client\config directory, contains the property specification:
nakedobjects.services.prefix=org.nakedobjects.example.expenses nakedobjects.services=services.JavaMailSender
This specifies that the class JavaMailSender is to be referenced as a service within the application. You will find a list of other services being referenced there also. Many of those services are 'repositories', and though there is no technical difference between a repository and any other kind of service, repositories play such an important role in Naked Objects applications, that they are worth exploring in more detail.
Naked Objects handles the basic object lifecycle (create, read, update, delete) automatically - there is no need to define your own methods for saving or updating objects, or for retrieving an object that you have a reference to. These mechanisms work the same way irrespective of what technology you are using to persist the objects - such as via Hibernate, natively to a relational database, or via the 'XML Object Store'.
Naked Objects even provides some simple mechanisms for searching for persisted objects - that also operate the same way, irrespective of the object store. However, a business application will also need more complex search queries that, for reasons of efficiency, will need to be written specifically for the type of object store you are working with.
Best practice in application design suggests that such queries should be implemented on 'Repository' classes, rather than within the domain classes directly. That way if you change the persistent object store, you can just create a new implementation of the affected Repositories, without having to change any domain classes. Naked Objects supports this concept. Within the Expenses application you will find the following three repository definitions:
org.nakedobjects.example.expenses.claims.ClaimRepository org.nakedobjects.example.expenses.employee.EmployeeRepository; org.nakedobjects.example.expenses.recordedAction.impl.RecordedActionRepository;
In each case the repository is defined as a Java interface, anticipating the possibility of different implementations. We'll look at the ClaimRepository definition:
public interface ClaimRepository {
final static int MAX_CLAIMS = 20;
final static int MAX_ITEMS = 10;
List<Claim> findClaims(final Employee employee, final ClaimStatus status, final String description);
List<Claim> findRecentClaims(final Employee employee);
boolean descriptionIsUniqueForClaimant(final Employee employee, final String initialDescription);
List<ExpenseItem> findExpenseItemsLike(final ExpenseItem item);
List<Claim> findClaimsAwaitingApprovalBy(Employee approver);
ClaimStatus findClaimStatus(String title);
ExpenseItemStatus findExpenseItemStatus(String title);
}
This interface defines some seven method signatures for retrieving Claims and ExpenseItems. Note that there is no hard rule about the scope of a single Repository - we could have decided to separate this into a ClaimRepository and an ExpenseItemRepository if that offered us some advantage.
The example application contains two concrete implementations of ClaimRepository:
org.nakedobjects.example.expenses.services.inmemory.ClaimRepositoryInMemory org.nakedobjects.example.expenses.services.hibernate.ClaimRepositoryHibernate
The first of these is intended for use with a standalone prototype - with a relatively small number of object instances, all held in memory. So the finder methods can be written 'naively' - to enumerate through all the objects in a class and find the match(es). The following is its implementation of the findClaimsAwaitingApprovalBy method:
public List<Claim> findClaimsAwaitingApprovalBy( final Employee approver ) {
return allMatches(
Claim.class,
new Filter() {
public boolean accept(final Object obj) {
Claim claim = (Claim) obj;
return claim.getStatus().isSubmitted() && claim.getApprover() == approver;
}
});
}
This delegates to an allMatches method, inherited from AbstractFactoryAndRepository, and use a Filter object (created in-line) to compare to each instance of Claim held in memory. Such methods are very simple to write and debug (because they can invoke methods on the objects being searched, such as isSubmitted here), but they would not operate efficiently for large numbers of objects.
ClaimRepositoryHibernate is written to work with the Hibernate Object Store and can work efficiently at large scale. Here is its the findClaimsAwaitingApprovalBy method:
public List<Claim> findClaimsAwaitingApprovalBy( final Employee approver ) {
final Criteria criteria = hibernateHelper.createCriteria(Claim.class);
criteria.
add(Restrictions.eq("approver", approver)).
createCriteria("status").
add(Restrictions.eq("titleString", ClaimStatus.SUBMITTED));
return hibernateHelper.findByCriteria(criteria, Claim.class);
}
This implementation uses a Criteria object, a class provided by the Hibernate framework.
Both ClaimRepositoryInMemory and ClaimRepositoryHibernate inherit from ClaimRepositoryAbstract, which inherits from AbstractFactoryAndRepository and also implements the ClaimRepository interface. This pattern is not a requirement - the implementations do not need to inherit from any framework class, they can just implement the required Repository interface natively. However the advantage of this pattern is that some simple query methods can be written generically, as shown in these two examples:
public List<ExpenseItem> findExpenseItemsOfType(final Employee employee, final ExpenseType type) {
final List<Claim> claims = findClaims(employee, null, null);
final List<ExpenseItem> items = new ArrayList<ExpenseItem>();
for (final Claim claim : claims) {
ExpenseItem pattern = (ExpenseItem) newTransientInstance((Class) type.correspondingClass());
pattern.setClaim(claim);
List list = (List) uniqueMatch((Class) type.correspondingClass(), pattern, EXCLUDING_SUBCLASSES);
items.addAll(list);
}
return items;
}
public ClaimStatus findClaimStatus(String title) {
return uniqueMatch(ClaimStatus.class, title, EXCLUDING_SUBCLASSES);
}
These two query methods both delegate to uniqueMatch, inherited from AbstractFactoryAndRepository, but different, overloaded, versions of that method. findExpenseItemsOfType invokes uniqueMatch with a pattern - an instance of ExpenseItem that has been set up with the fields where a match is required. findClaimStatus invokes uniqueMatch with a String representing the title of the object required. The implementation of both of these forms of query is delegated to the object store, in a manner that is transparent to the application programmer. So, if the nature of the query can be represented in the form of a find by title, or a find by pattern, then it is advantageous to use these methods on AbstractFactoryAndRepository. Otherwise you can write specialised methods on the respective repository implementations.
As with all services, we need to inform the framework of the existence and intent of these implementations, via the properties files. Within nakedobjects.properties you will find:
nakedobjects.services=services.inmemory.ClaimRepositoryInMemory
and within persistor_hibernate.properties you will find:
nakedobjects.services = services.hibernate.ClaimRepositoryHibernate
persistor_hibernate.properties is only referenced if the application is run with the Hibernate Object Store, in which case the framework will recognise that ClaimRepositoryHibernate is intended to replace ClaimRepositoryInMemory as the implementation to use.
A Factory is just the name we give to a kind of service that specialises in the creation of new objects, of one or more kinds. It is not necessary to use a Factory in order to create objects within Naked Objects: we may invoke the methods newTransientInstance from within a method on a domain object or within any service.
However, if there is a need to create a type of object from several different places in the application, and there are common steps involved, then it is good practice to delegate this to a Factory. Within Naked Objects a Factory is just another service, it doesn't have any special status. For example, within the Expenses application, new Claims and new ExpenseItems are created via the ClaimFactory. However, new RecordedActions are created in the RecordedActionService. Note that ClaimFactory is specified as a class rather than an interface, because we have no particular reason to anticipate different implementations of the factory.
Services are used within Naked Objects in three ways:
We'll look at these three in turn.
Objects may need access to services, such as repositories for finding related objects, or for calling functionality from outside the domain model. Naked Objects uses the 'dependency injection' model. Each object merely needs to provide a set method for each type of service that it requires. For example, within the Employee object there is a code region labelled Injected Services:
// {{ Injected Services
// {{ Injected: RecordActionService
private RecordActionService recordActionService;
protected RecordActionService getRecordActionService() {
return this.recordActionService;
}
public void setRecordActionService(final RecordActionService recordActionService) {
this.recordActionService = recordActionService;
}
// }}
// {{ Injected: UserFinder
private UserFinder userFinder;
protected UserFinder getUserFinder() {
return this.userFinder;
}
public void setUserFinder(final UserFinder userFinder) {
this.userFinder = userFinder;
}
// }}
// }}
In this case, the Employee object has specified that it requires two services to be injected: a RecordActionService and a UserFinder. Whenever an instance of Employee is created, or retrieved from the object store, Naked Objects will inject the implementation that it knows about (as specified in properties) for each type of service required. Note that, unlike the other properties we have looked at, get methods may be protected, because the property is not displayed. (Strictly speaking a get is often not needed here - as the injected service may be accessed via the variable - but it is considered to be good practice.)
From within the object we can then call any of the methods defined for those types of service. For example, we can see that the hideEmailAddress method makes a call (via employeeIsCurrentUser) to the UserFinder service:
public boolean hideEmailAddress() {
return !employeeIsCurrentUser();
}
private boolean employeeIsCurrentUser() {
return getUserFinder().currentUserAsObject() == this;
}
Services may be made available directly to the user. On the DND user interface these appear as the large icons on the desktop; on the HTML user interface (that is, as styled by the default CSS) these appear as the tabs across the top of the screen. Which services are made available to a particular user are defined in 'perspectives' within a user profile. Within the Fixture project the class ExplorationUserProfileFixture defines the perspectives for various defined prototype users:
public class ExplorationUserProfileFixture extends UserProfileFixture {
@Override
protected void installProfiles() {
...
Profile svenProfile = newUserProfile();
Perspective claimsPerspective = svenProfile.newPerspective("Claims");
claimsPerspective.addToServices(Claims.class);
claimsPerspective.addToServices(Employees.class);
saveForUser("sven", svenProfile);
...
}
}
The above example specifies that the user 'sven' is to be given a perspective called 'Claims', which gives him direct access to two services: ClaimStartPoints and EmployeeStartPoints. If we look at the second of those, we can see that it defines two actions: findEmployeeByName and me:
@Named("Employees")
public class EmployeeStartPoints extends AbstractService {
// {{ Title & ID
// {{ Injected Services
@MemberOrder(sequence = "2")
public List<Employee> findEmployeeByName(@Named("Name (or start of Name)")
final String name) {
List<Employee> results = employeeRepository.findEmployeeByName(name);
if (results.isEmpty()) {
warnUser("No employees found matching name: " + name);
return null;
}
return results;
}
@Executed(Executed.Where.LOCALLY)
public Employee me() {
Employee me = employeeRepository.me();
if (me == null) {
warnUser("No Employee representing current user");
}
return me;
}
}
Both of these methods delegate to methods on the EmployeeRepository, which has been injected (services may be injected into other services, just as into domain objects). Note that it is not necessary to define specific services to be provided directly to the user - we could provide the user with direct access to the Repositories, Factories or other services specified within the application. Creating dedicated user-oriented service definitions just helps us to separate the concerns. Calling them 'Start Points' is also just a convention.
The screens below show the action menu on the Claim object, as rendered by the two different user interfaces:
This menu has a sub-menu, entitled 'Recorded Actions', containing, in this case, a single method 'All Recorded Actions'. Sub-menus in Naked Objects are 'contributed' by services; the actions in the sub-menus are described as 'contributed actions'. In this case the actions are contributed the service RecordedActionContributedActions:
@Named("Recorded Actions")
public class RecordedActionContributedActions extends AbstractService {
// {{ Injected Services
public List<RecordedAction> allRecordedActions(RecordedActionContext context) {
return recordedActionRepository.allRecordedActions(context);
}
}
The method allRecordedActions takes a RecordedActionContext as a parameter, and will return all the RecordedActions associated with that object. Note that RecordedActionContext is an interface that defines no methods - it is purely a type definition:
public interface RecordedActionContext {
}
However, this interface is implemented by two classes: Employee and Claim. The net result of this is that the action 'All Recorded Actions' will be contributed to each instance of Employee and of Claim. By default, this would appear in a sub-menu named after the service on which the method was defined (i.e. 'Recorded Action Contributed Actions'), but in this case we have used the @Named annotation to override this and render the service name, and hence the sub-menu name, simply as 'Recorded Actions'.
We can also see that this method delegates its execution to the RecordedActionRepository, which has been injected as a service. You are not required to follow this pattern, or this naming convention. In fact, if the allRecordedActions method on RecordedActionRepository was not @Hidden, then it would have been contributed automatically - without the need for defining RecordedActionContributedActions. We have defined the latter purely to help convey intent and manage our code base.
The rule is that any method defined on any service that the user is authorised to access (see Link) and is not hidden, will be contributed to any object of a type that features as any of the parameters to that method.
This is a very powerful feature of Naked Objects, but it is one that takes a bit of getting used to. In some respects it is a little bit like Aspect Oriented Programming (AOP), in that it allows an object effectively to inherit capabilities from several different sources. However, this all takes place at run-time, not at compile time.
In a more complex application, it might well be that a domain object might have several contributed sub-menus, each containing several methods. Designing an application this way allows us to keep the model well partitioned. In this very simple example, it has allowed us to keep the part of the model concerned with recording actions very separate from the other parts of the model.
Fixtures are used to set up objects within the code based, principally for use within prototyping and or testing. Naked Objects provides specific support for using fixtures. The following code shows a fixture class that sets up one claim:
public class SvenClaim1NewStatus extends AbstractClaimFixture {
public static Employee SVEN;
public static Employee DICK;
public static Claim SVEN_CLAIM_1;
@Override
public void install() {
SVEN = EmployeeFixture.SVEN;
DICK = EmployeeFixture.DICK;
SVEN_CLAIM_1 =createNewClaim(SVEN, DICK, "28th Mar - Sales call, London", ProjectCodeFixture.CODE1, new Date(2007,4,3));
Date mar28th = new Date(2007,3,28);
addTaxi(SVEN_CLAIM_1, mar28th, null, 8.50, "Euston", "Mayfair", false);
addMeal(SVEN_CLAIM_1, mar28th, "Lunch with client", 31.90);
addTaxi(SVEN_CLAIM_1, mar28th, null, 11.00, "Mayfair", "City", false);
}
}
This inherits from AbstractClaimFixture, which provides the helper methods such as createNewClaim, and which inherits in turn from AbstractFixture, a class in the Naked Objects application library. However, there is no need to follow this pattern: a fixture may be any class that has an install method.
Fixtures may be composite, as we can see in this example:
public class SvenClaims_All extends AbstractClaimFixture {
public SvenClaims_All() {
addFixture(new SvenClaim1NewStatus());
addFixture(new SvenClaim2Submitted());
addFixture(new SvenClaim5New());
addFixture(new SvenClaim3Returned());
addFixture(new SvenClaim4Approved());
}
public void install() {}
}
This fixture has had five other fixtures added to it. The install method is empty: Naked Objects will automatically call install on each of the fixtures that has been added to this composite fixture. This pattern makes it easy to manage large fixtures, and multiple sets of (potentially overlapping) fixtures, both for prototyping and for testing.
As with services, Naked Objects needs to be instructed which fixtures it should use when running an application. This may be done in the properties files, for example:
nakedobjects.fixtures.prefix=org.nakedobjects.example.expenses.fixtures nakedobjects.fixtures=ExplorationPerspectiveFixture, RefdataFixture, EmployeeFixture, SvenClaims_All
Note that this also specifies the ExplorationPerspectiveFixture, which we looked at earlier.
Fixtures may also be specified as a command line parameter (see Link) when launching the application from the command line; composite fixtures are especially handy in this circumstance.
Like all Java frameworks Naked Objects is essentially a set of classes and other resources that you run your code with. There are a number of ways of developing with Naked Objects: downloading a traditional distribution and installing it on your machine; or using Maven to create and manage your Naked Object projects. Which you choose is a matter of preference and familiarity.
To develop or run Naked Objects applications you need Java version 1.5 or above installed on your machine.
To build and run the examples you will also need either Ant or Maven installed, both available from Apache.
To develop applications you will need a suitable development environment. If you have the choice, we recommend using Eclipse, because there are some specific plug-ins available for Eclipse to facilitate developing with Naked Objects. However, Naked Objects may be used with any IDE, or even just the JDK.
The next two sections look at downloading and installing Naked Objects.
The latest version of the Naked Objects framework can be downloaded from the downloads page of the Naked Objects Sourceforge project as shown below. You may download either the Ant release (suffixed with '-for-ant' ), the Maven release (suffixed with '-for-maven'), the full source release (suffixed with '-source' ) if you wish to build the framework yourself, or simply a jar file that bundles in all the dependencies but has no documentation or other resources.
TODO: update the following screenshot
Extract the downloaded file's content into a suitable directory (a root directory is created upon extraction so there is no need to create a directory in which to install everything).
The binary release for Ant provides the following directories and files:-
For more details on developing using Ant, see Link.
The binary release for Maven contains supporting resources (including icons, examples and this documentation). It does not include the Naked Objects libraries themselves however, because Maven itself downloads them from the Maven central repository.
The distribution contains following directories and files:-
Because the Maven distribution does not include the libraries, there is no immediately runnable demo. However as indicated above the examples are still there and can be built using Maven (or imported into an Eclipse or other IDE and built there).
For more details on developing using Maven, see Link.
The source release contains a series of directories, which are Eclipse projects, for the different subcomponents of Naked Objects, the libs and examples. This source can be used, with the templates in the build directories, to recreate the distributions made publicly available.
To install all the components into your local repository, and generate the distribution files, first build the parent project POM and then build the framework itself:
$ cd nakedobjects-4.0 $ cd pom $ mvn clean install $ cd .. $ mvn clean install -P all
Any missing resources that maven complains about are can be installed into the repository by specifying the specific jar file from the lib directory when following the instructions that Maven gives you.
Using the source release Maven will build the same files that you would download if you had downloaded a binary version. The distribution files (as detailed for the binary release above) are to be found within the directory distribution/tarball/target as shown below.
distribution/ |-- docexamples |-- documentation |-- pom.xml |-- tarball | |-- pom.xml | |-- src | `-- target | |-- archive-tmp | |-- maven-archiver | |-- nakedobjects-4.0-beta-3-SNAPSHOT-for-ant.tar.gz | |-- nakedobjects-4.0-beta-3-SNAPSHOT-for-ant.zip | |-- nakedobjects-4.0-beta-3-SNAPSHOT-for-maven.tar.gz | |-- nakedobjects-4.0-beta-3-SNAPSHOT-for-maven.zip | |-- nakedobjects-4.0-beta-3-SNAPSHOT-source.zip | `-- nakedobjects-4.0-beta-3-SNAPSHOT-libs-only-with-dependencies.jar `-- target
With the core libraries and plugins build, the final step is to install the archetype so that new projects can be created from the local archetype, rather then the publicly available one.
$ cd archetypes/application $ mvn clean install
A final option is to use the jar of jars. This is a single jar file that contains all the other jars. This allows you simplify your projects by simply having that one jar on your classpath. However, this leaves you to set up your projects from scratch.
This release is often useful later, however, when you have created an application and wish to distribute your own way.
This section defines a set of conventions for writing domain objects, that are together known as the 'Naked Objects (Java) Programming Model'.
Following these conventions does not tie your domain objects to the Naked Objects Framework or any other framework: the resulting domain objects may be run within any framework or platform that supports POJOs (Plain Old Java Objects). A few of the conventions do make use of Interfaces or Annotations that are necessarily defined somewhere - in this case they are in the Naked Objects AppLib. However, the AppLib is not itself dependent upon the Naked Objects Framework (and in theory another framework implementation could support the same applib).
The conventions of the programming model are best described as 'intentional' - they convey an intention as to how domain objects, their properties and behaviours, are to be made available to users. The specific way in which those intentions are interpreted or implemented will depend upon the framework, and/or the particular components or options selected within that framework.
To pick a single example, marking up a domain class with the annotation @Bounded is an indication that the class is intended to have only a small number of instances and that the set does not change very often - such as the class Country. This is an indication to any interested framework that the whole set of instances might be offered to the user in a convenient form such as a drop-down list. The programming convention has not been defined as @DropDownList because the user interface might not support drop-down-lists - but it might provide a capability to select from an @Bounded class by typing the initial letters of the desired instance.
The first set of conventions are concerned with the capabilities or behaviour of an object as a whole.
A title is used to identify an object to the user in the user interface. For example, a Customer's title might be the organization's customer reference, or perhaps (more informally) their first and last names. By default, the framework will use the object's toString() method as the title. However, if a title() method (returning a String) is present, then that is used instead, thus:
public String toString()
or
public String title()
The reason for providing the option to use a title method is in case the programmer needs to make use of the toString method for other purposes, such as for debugging.
By default, the framework will look for an image in the images directory (which must be on the classpath) that has the same name as the object class. So for an object of type Customer, it will look for Customer.gif or Customer.png. If it finds no such file, then it will work up the inheritance hierarchy to see if there is an icon matching the name of any of the super-classes, and use that instead. If no matching icon is found then the framework will look for an image called default in the images directory, and if this has not been specified, then the framework will use its own default image for an icon.
We strongly recommend that you adopt 'camel case' as the convention for icon file names: if you have a class called OrderLine, then call the icon OrderLine.gif or OrderLine.png (JPEG suffixes OrderLine.jpg or OrderLine.jpeg will also be recognized but generally GIF or PNG are to be preferred). Actually, the framework will also recognise orderline.gif, but some operating systems and deployment environments are case sensitive, so it is good practice to adopt an unambiguous convention.
The programmer may choose to specify, manually, which icon to use, by specifying an iconName method:
public String iconName() {
return "Person";
}
This makes it easy for more than one class to use the same icon, without having to duplicate the image file.
The iconName method may also be used to specify an individual icon for each instance. For example, an instance of Product could use a photograph of the product as an icon, using:
public String iconName() {
return getProductName() + "-photograph";
}
or to vary the icon according to the status of the object:
public String iconName() {
return "Order-" + getStatus();
}
By default, the name (or type) of an object, as displayed to the user will be the class name. However, if an @Named annotation is included, then this will override the default name. This might be used to include punctuation or other characters that may not be used within a class name.
By default the framework will create a plural version of the object name by adding an 's' to singular name, or a 'ies' to names ending 'y'. For irregular nouns or other special case, the @Plural annotation may be used to specify the plural form of the name explicitly.
(Note that there is an entirely separate mechanism for dealing with Internationalisation, which is described elsewhere).
The programmer may optionally also provide a @DescribedAs annotations, containing a brief description of the object's purpose, from a user perspective. The framework will make this available to the user in a form appropriate to the user interface style - for example as 'balloon' help.
Use the @NotPersistable annotation.
Use the @Immutable annotation.
Use the @Bounded annotation. A common way of describing this is that the whole (limited) set of instances may be rendered to the user as a drop down list - but the actual interpretation will depend upon the form of the user interface.
Use the @Hidden annotation.
These conventions are concerned with the creation, retrieval, updating and deletion of objects.
When you create any domain object within your application code, it is important that the framework is made aware of the existence of this new object - in order that it may be persisted to the object store, and in order that any services that the new object needs are injected into it. Just specifying new Customer(), for example, will create a Customer object, but that object will not be known to the framework. However, since we do not want to tie our domain objects to a particular framework, we use the idea of a 'container' to intermediate. The application library provides an interface:
org.nakedobject.applib.DomainObjectContainer which in turn defines the following methods for managing domain objects:
<T> T newTransientInstance(final Class<T> ofClass) returns a new instance of the specified class, that is transient (unsaved). This may subsequently saved either by the user invoking the Save action (that will automatically be rendered on the object view) or programmatically by calling void makePersistent(Object transientObject)
<T> T newPersistentInstance(final Class<T> ofClass) creates a new object already persisted.
boolean isPersistent()checks whether an object has already been persisted. (Useful in controlling visibility or availability of properties or actions).
void remove(Object object) deletes a persistent object from the object store.
Any framework that recognises the Naked Objects Programming Model will provide an implementation of DomainObjectContainer and will take responsibility to inject a reference to that Container into any domain object that needs it.
A domain object specifies that it needs to have a reference to the Container injected into by including the following code:
private DomainObjectContainer container;
protected DomainObjectContainer getContainer() {
return this.container;
}
public final void setContainer(final DomainObjectContainer container) {
this.container = container;
}
Creating or deleting objects is then done by invoking those methods on the Container. For example the following code would then create a new Customer object within another method:
Customer newCust = (Customer) getContainer().newTransientInstance(Customer.class);
newCust.setName("Charlie");
getContainer().persist(newCust);
If you are able to make your domain object inherit from org.nakedobjects.applib.AbstractDomainObject then you have direct access to those methods, so the code would become:
Customer newCust = (Customer) newTransientInstance(Customer.class);
newCust.setName("Charlie");
persist(newCust);
These methods are actually provided by the org.nakedobjects.applib.AbstractContainedObject and so are also available on org.nakedobjects.applib.AbstractService (and, hence, on org.nakedobjects.applib.AbstractFactoryAndRepository) for creating objects within a service.
It is possible to create a transient object within another transient object. When the framework persists any transient object, it will automatically persist any other transient object referenced by that object. However, if any of these transient objects are to be exposed to the user (while in their transient state), then you need to write your code very carefully - anticipating the fact that the user could elect to save any of the transient objects at any point - which could cause the graph of related objects to be persisted in an invalid state.
The recommended approach is, if possible, to mark these supplementary classes as not persistable by the user (see Link), or not to permit the user to create a new transient object that is a child of an existing transient object, but, rather, to require the user to save the parent object first.
The following is a list of methods that correspond to various events in the life-cycle of a domain object. If a domain object implements any of these methods (they are all optional) then the framework will call that method whenever the corresponding event occurs. For example, persisted is called after an object has been persisted. One reason for implementing the persisted method might be to set up a reverse association that we do not want to be set up until the new object has been persisted.
public void created() will be called by the framework at logical creation of the object
public void loading() will be called by the framework when a persistent object is about to be loaded into memory
public void loaded() will be called by the framework when a persistent object has just been loaded into memory
public void persisting() will be called by the framework just before a transient object is first persisted. (For backwards compatibility saving() is also supported).
public void persisted() will be called by the framework just after a transient object is first persisted. (For backwards compatibility saved() is also supported).
public void updating() will be called by the framework after any property on a persistent object has been changed and just before this change is persisted
public void updated() will be called by the framework just after a changed property on a persistent object has been persisted
public void removing() will be called by the framework when a persistent object is just about to be deleted from the persistent object store. (For backwards compatibility deleting() is also supported).
public void removed() will be called by the framework when a persistent object has just been deleted from the persistent object store. (For backwards compatibility deleted() is also supported).
The following conventions are concerned with specifying the properties of an object, and the way in which users can interact with those properties.
Properties are specified using the JavaBean conventions, recognizing a standard accessor/mutator pair (get and set). The syntax is:
public <property type> get<PropertyName>() public void set<PropertyName>(<property type> param)
where <property type> is a primitive, a value object or an entity object.
Properties may reference either a value or another domain object. Values include Java primitives, and JDK classes with value semantics (java.lang.Strings and java.util.Dates). A property referencing another domain object is sometimes called an association.
For example, the following example contains both a value (String) property and a reference (Organisation) property:
public class Customer {
private String firstName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
private Organisation organisation;
public Organisation getOrganisation() {
return organisation;
}
public void setOrganisation(Organisation organisation) {
this.organisation = organisation;
}
...
}
Preventing the user from modifying a property value is known as 'disabling' the property.
Use the @Disabled annotation.
A disable method can be used to disable a particular instance's member under certain conditions. The syntax is:
public String disable<PropertyName>() A non-null return value indicates the reason why the property cannot be modified. The framework is responsible for providing this feedback to the user. For example:
public class OrderLine {
public String getQuantity() { ... }
public void setQuantity(int quantity) { ... }
public String disableFirstName() {
if (this.hasBeenSubmitted()) {
return "Cannot alter any quantity after Order has been submitted";
}
return null;
}
}
Generally it is not good practice to embed knowledge of roles and/or users into the domain classes. This is the responsibility of the framework or platform and should be specified and administered externally to the domain model.
However, in rare circumstances it might be necessary or pragmatic to implement access control within the domain model using the inherited getUser() method:
For example:
import org.nakedobjects.applib.UserMemento;
public class Employee {
public BigDecimal getSalary() { ... }
public void setSalary(BigDecimal salary) { ... }
public String validateSalary(BigDecimal salary) {
return salary.doubleValue() >= 30000 &&
!getUser().hasRole("MODIFY_SALARY")?
"Need MODIFY_SALARY role to increase salary above 30000": null;
}
}
By default, when a new transient (unsaved) object is presented to the user, values must be specified for all properties before the object may be saved. To specify that a particular property is optional, use the @Optional annotation.
Use the @MaxLength, @TypicalLength and @MultiLine annotations.
A validate method is used to check that a new value for a property is valid. If the proffered value is deemed to be invalid then the property will not be changed. A non-null return String indicates the reason why the member cannot be modified/action be invoked; the framework is responsible for providing this feedback to the user.
The syntax is:
public String validate<PropertyName>(<property type> param)
For example:
public class Exam {
public int getMark() { ... }
public void setMark(int mark) { ... }
public validateMark(int mark) {
return !(mark >= 0 && mark <= 30)?
"Mark must be in range 0 to 30"
:null;
}
}
For properties that accept a text input string, such as String and Date, there are convenient mechanisms to validate and/or normalise the values typed in.
For Date and number values the @Mask annotation may be used.
For String properties the @RegEx annotation may be used.
The simplest way to provide the user with a set of choices for a property (possibly rendered as a drop-down list, for example) is to ensure that the type used by the property is marked up as @Bounded. However, this is not always appropriate. For example you might wish to provide the user with the choice of all the Addresses known for that Customer, with the most recently-used address as the default.
The syntax for specifying a default value is:
public <property type> default<PropertyName>()
And for specifying a list of choices is:
public <array or collection of property type> choices<PropertyName>()
The full code for our example above is:
public class Order {
public Address getShippingAddress() { ... }
public void setShippingAddress() { ... }
public Address defaultShippingAddress() {
return getCustomer().normalAddress();
}
public List<Address> choicesShippingAddress() {
return getCustomer().allActiveAddresses();
}
}
Initial values for properties may be set up programmatically within the created() method on the object. (See Object: Life Cycle).
If you want to invoke functionality whenever a property is changed by the user, then you should create a modify <propertyName> and include the functionality within that. For example:
public int getAmount() {}
public void setAmount(int amount) {}
public void modifyAmount(int amount) {
setAmount(amount);
addToTotal(amount);
}
The reason for the modifyAmount method is that it would not be a good idea to include the addToTotal call within the setAmount method , because that method may be called by the persistence mechanism when an object is retrieved from storage.
You may optionally also specify a clear<PropertyName> which works the same way as modify modify <propertyName> but is called when the property is cleared by the user (i.e. the current value replaced by nothing).
These methods may also be used for setting up bidirectional relationships (using the 'mutual registration pattern'). For example:
public class Employee {
private Department department;
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
public void modifyDepartment(Department department) {
setDepartment(department);
department.addToStaff(this);
}
public void clearDepartment(Department department) {
setDepartment(null);
department.removeFromStaff(this);
}
...
}
Use the @MemberOrder annotation.
By default the framework will use the property name itself to label the property on the user interface. If you wish to over-ride this, use the @Named annotation on the property.
Use the @DescribedAs annotation on the property itself.
The framework will take responsibility to make this description available to the user, for example in the form of a 'balloon help'.
Use the @Hidden annotation.
A hide method can be used to indicate that a particular instance's member should be hidden (rendered invisible to the user) under certain conditions. The syntax is:
public boolean hide<PropertyName>() A true return value indicates that the property is hidden. For example:
public class Order {
public String getShippingInstructions() { ... }
public void setShippingInstructions(String shippingInstructions) { ... }
public boolean hideShippingInstructions() {
return hasShipped();
}
}
Important: see the comments under 'Disabling a property for specific users or roles'.
Session controls provide a way for the class to hide properties from specific users, or users not in specific roles. The syntax is: public boolean hide<PropertyName>(Session session) For example:
public class Employee {
public BigDecimal getSalary() { ... }
public void setSalary(BigDecimal salary) { ... }
public boolean hideSalary(UserMemento user, BigDecimal salary) {
return !user.hasRole("VIEW_SALARY");
}
}
If there is no mutator (set) method then the field is not only unmodifiable but will also not be persisted. This approach is by design and also happens to be compatible with Java Persistence Api (JPA) semantics. This may be used to derive a property from other information available to the object, for example:
public class Employee {
public Department getDepartment() { ... }
public void setDepartment(Department department) { ... }
public void modifyDepartment(Department department) { ... }
public void clearDepartment(Department department) { ... }
// this is the derived property
public Employee getManager() {
if (getDepartment() == null) { return null; }
return getDepartment().getManager();
}
...
}
If you need to have a get and set method for the property but do not wish to have that property persisted, use the @NotPersisted annotation.
An 'action' is a method that we expect the user to be able to invoke via the user interface, though it may also be invoked programmatically within the object model. The following conventions are used to determine when and how methods are made available to the user as actions.
By default, any public instance method that you add to a class will be treated as a user action, unless it represents a property, collection, or another reserved method defined in this manual.
If you have a method that you don't want to be made available as a user-action you should either make it protected or private or use the @Hidden annotation. Note also that static methods are ignored: such functionality should reside in a service, such as a Repository or Factory.
We refer to all methods that are intended to be invoked by users as 'action methods'.
The syntax is:
public void <actionName>([<value or entity type> param]...)
or
public <return type> <actionName>([<value or entity type> param]...)
When a method returns a reference the viewer will attempt to display that object. If the return value is null then nothing is displayed.
If an action method takes parameters, the viewing mechanism will automatically require the user to specify these parameters (for example, in the form of a dialog box) - and ensure that they are of the correct type.
Unlike with properties, the framework cannot pick up the names of parameters that you use within the domain code. By default parameters will be labelled only with the type of the object required (e.g. 'String:' or 'Customer:) If you want a parameter to have a different name (such as 'First Name', 'Last Name') then that parameter should be marked up with an @Named annotation - very often taking the same form as the parameter name used in the code. (This is one of the few forms of duplication that we cannot eliminate; the parameter name is not available in the class' bytecode and so cannot be inferred automatically. Alternatively though, you could create a user-defined value type, using @Value).
Similarly, any parameter may be given a short user-description using the @DescribedAs annotation. The framework takes responsibility to make this available to the user.
By default, the framework assumes that when an action method is to be invoked, all the parameters are mandatory. You may over-ride this behaviour by marking up one or more of the paramaters with the @Optional annotation.
When an action is about to be invoked, then default values can be provided for any or all of its parameters.
There are two different ways to specify parameters; either per parameter, or for all parameters. The per-parameter form is simpler and probably preferred; the syntax is:
public <parameter type> defaultN<ActionName>()
where N indicates the 0-based parameter number. For example:
public class Customer {
public Order placeOrder(
Product product,
@Named("Quantity")
int quantity) {
...
}
public Product default0PlaceOrder() {
return productMostRecentlyOrderedBy(this.getCustomer());
}
}
The syntax for specifying all the parameter default values in one go is:
public Object[] default<ActionName>([<parameter type> param]...)
returning an array (which must have one element per parameter in the action method signature) of corresponding default values. For example:
public class Customer {
public Order placeOrder(
Product product,
@Named("Quantity")
int quantity) {y
...
}
public Object[] defaultPlaceOrder(
Product product,
int quantity) {
return new Object[] {productMostRecentlyOrderedBy(this.getCustomer()), 1};
}
}
The programmer may provide a set of choices for the value of any or all of the parameters of an action. These will be made available to the user - for example as a drop-down list.
As for defaults, there are two different ways to specify parameters; either per parameter, or for all parameters. The per-parameter form is simpler and probably preferred; the syntax is:
public List<parameter type> choicesN<ActionName>()
where N indicates the 0-based parameter number. For example:
public class Order {
public Order placeOrder(
Product product,
@Named("Quantity")
int quantity) {
...
}
public List<Product> choices0PlaceOrder() {
return lastFiveProductsOrderedBy(this.getCustomer());
}
}
The alternative mechanism is to supply all the parameter choices in one go:
public Object[] choices<ActionName>([<parameter type> param]...)
returning an array, which must have one element per parameter in the method signature. Each element of the array should itself either be an array or a list, containing the set of values representing the choices for that parameter, or null if there are no choices to be specified for that parameter.
For example:
public class Order {
public Order placeOrder(
Product product,
@Named("Quantity")
int quantity) {
...
}
public Object[] choicesPlaceOrder(
Product product,
int quantity) {
return new Object[] {lastFiveProductsOrderedBy(this.getCustomer()).toArray(), null};
}
}
(Note that if the type of the property is annotated with @Bounded, then it is not necessary to specify the choices for that parameter, as the user will automatically be offered the full set to choose from.)
Use the @MaxLength, @TypicalLength, @MultiLine, @Mask or @RegEx annotations.
A validate method is used to check that the set of arguments used by an action method is valid. If the arguments are invalid then the framework will not invoke the action. A non-null return String indicates the reason why the member cannot be modified/action be invoked, and the viewing mechanism will display this feedback to the user.
The syntax is:
public String validate<ActionName>([<parameter type> param]...)
For example:
public class Customer {
public Order placeOrder(Product p, int quantity) { ... }
public String validatePlaceOrder(Product p, int quantity) {
if (p.isOutOfStock()) { return "Product is out of stock"; }
if (quantity <= 0) { return "Quantity must be a positive value"; }
return null;
}
There may be circumstances in which we do not want the user to be able to initiate the action at all - for example because that action is not appropriate to the current state of the object on which the action resides. Such rules are enforced by means of a disable method.
The syntax is:
public String disable<ActionName>([<parameter type> param]...)
A non-null return String indicates the reason why the action may not be invoked. The framework takes responsibility to provide this feedback to the user. For example:
public class Customer {
public Order placeOrder(Product p, int quantity) { ... }
public String disablePlaceOrder(Product p, int quantity) {
return isBlackListed()?
"Blacklisted customers cannot place orders"
:null;
}
It is also possible to permanently disable an action using the @Disabled annotation. One possible reason for doing this might be during prototyping - to indicate to the user that an action is planned, but has not yet been implemented.
See 'Properties: Disabling a property for specific users or roles'. The same technique can be applied to actions. However, the caveats apply.
Use the @MemberOrder annotation.
Use the @Hidden annotation. (This is generally used where a public method on an object is not intended to be a user action).
A hide method can be used to indicate that an action should be hidden under certain conditions. The syntax is:
public boolean hide<ActionName>([<parameter type> param]...)
A true return value indicates that the action should not be shown. For example:
public class Order {
public void applyDiscount(int percentage) { ... }
public boolean hideApplyDiscount() {
return isWholesaleOrder();
}
}
See 'Properties: Hiding a property for specific users or roles'. The same technique can be applied to actions. However, the caveats apply.
Sometimes, within an action it is necessary or desirable to pass a message to the user, for example to inform them of the results of their action ('5 payments have been issued') or that the action was not successful ('No Customer found with name John Smith'). DomainObjectContainer defines two methods for this purpose:
void informUser(String message) void warnUser(String message)
These two methods provide different ways to send a message to the user, representing increasing levels of severity. How each of these messages is rendered visible to the user is determined by the framework.
These two methods are also available on AbstractDomainObject as a shortcut.
A collection is a list of references to several entity objects that have a common type.
A collection is recognized via an accessor/mutator method pair (get and set) for any type of collection provided by the programming language. The syntax is:
public <collection type> get<CollectionName>() private void set<CollectionName>(<collection type> param)
It is recommended that the collections be specified using generics (for example: List<Customer> ). That way the framework will be able to display the collection based on that type definition. (Generics are also required by some persistence mechanisms). For example the viewer might display the collection as a table, with the columns defined by the visible properties of that type. The viewer will then automatically prevent the user from adding objects not of that type. If generics are not used then the type may be inferred from the addTo / removeFrom methods, if specified (see below).
For example:
public class Employee { ... }
public class Department {
private List<Employee> employees = new ArrayList<Employee>();
public List <Employee> getEmployees() {
return employees;
}
private void setEmployees(List<Employee> employees) {
this.employees = employees;
}
...
}
A collection may have a corresponding addTo and/or removeFrom method. This is equivalent to the modify and clear methods for properties. The syntax is:
public void addTo<CollectionName>(<entity type> param) public void removeFrom<CollectionName>(<entity type> param)
where <entity type> is the same type as the generic collection type. For example:
public class Employee { ... }
public class Department {
private List<Employee> employees = new ArrayList<Employee>();
public List <Employee> getEmployees() {
return employees;
}
private void setEmployees(List<Employee> employees) {
this.employees = employees;
}
public void addToEmployees(Employee employee) {
employees.add(employee);
}
public void removeFromEmployees(Employee employee) {
employees.remove(employee);
}
...
}
The addTo / removeFrom and modify / clear methods are particularly useful in setting up bidirectional relationships using the 'mutual registration pattern'; for a write-up of this pattern, see http://www.two-sdg.demon.co.uk/curbralan/papers/MutualRegistration.pdf.
Preventing the user from adding to or removing from a collection is known as 'disabling' the collection.
Use the @Disabled annotation.
A disable method can be used to disable a particular instance's collection under certain conditions: The syntax is:
public String disable<CollectionName>() For example:
public class Department {
public List<Employee> getEmployees() { ... }
public void addToEmployees(Employee employee) { ... }
public void removeFromEmployees(Employee employee) { ... }
public void disableEmployees() {
return isClosed()? "This department is closed" : null;
}
}
See Properties: Disabling a property for specific users or roles'. The same technique can be applied to collections, with the same caveats applying.
A validate method is used to check that an object to be added or removed from a collection is valid. A non-null return String indicates the reason why the object cannot be added/removed, and the viewing mechanism will display this to the user. The syntax is:
public String validateAddTo<CollectionName>(<property type> param) public String validateRemoveFrom<CollectionName>(<property type> param)
For example:
public class Department {
private List<Employee> employees = new ArrayList<Employee>();
public List<Employee> getEmployees() { ... }
private void setEmployees(List<Employee> employees) { ... }
public String validateAddToEmployee(Employee employee) {
return employee.isRetired()?
"Cannot add retired employees to department"
:null;
}
Use the @MemberOrder annotation.
Use the @Named and/or @DescribedAs annotations.
See 'Properties: How to hide a property'. The same approaches work for collections.
Collections can be derived, in the same way as properties. These are not persisted, but are represented as read only collections. For example:
public class Department {
// Standard collection
List<Employee> employees = new ArrayList<Employee>();
public List<Employee> getEmployees() { ... }
private void setEmployees(List<Employee>) { ... }
void addToEmployees(final Employee employee) { ... }
void removeFromEmployees(final Employee employee) { ... }
// Derived collection
public List<Employee> getTerminatedEmployees() {
List<Employee> terminatedEmployees = new ArrayListt<Employee>();
for(Employee e: employees) {
if (e.isTerminated()) {
addTo(terminatedEmployees, e);
}
}
return terminatedEmployees;
}
}
The Naked Objects application library provides a class
org.nakedobjects.applib.AbstractFactoryAndRepository
that makes it easy to write a Repository and/or Factory to work entirely in memory. This is very useful during prototyping, as you can quickly get your system up and running without having to deal with a database. Refer to the JavaDoc documentation for that class for a full list of methods available. However, the following describes some of the most commonly-used methods:
<T> T newTransientInstance(final Class<T> ofClass) returns a new instance of the specified class, that is transient (unsaved). This may subsequently be saved either by the user invoking the Save action (that will automatically be rendered on the object view) or programmatically by calling . . .
void makePersistent(Object transientObject)
<T> T newPersistentInstance(final Class<T> ofClass) creates a new object already persisted.
void disposeInstance(Object persistentObject)
<T> List<T> allInstances(final Class<T> cls, final boolean includeSubclasses) these may be iterated through for a specific match.
<T> T firstMatch(final Class<T> cls, final String title, final boolean includeSubclasses)looks for an object with the title specified.
There are a number of useful matching and filtering methods in AbstractFactoryAndRepository.
The @MustSatisfy annotation is an alternative to using imperative validation, allowing validation rules to be captured in an (implementation of a) org.nakedobjects.applib.spec.Specification.
For example:
public class DomainObjectWithMustSatisfyAnnotations extends AbstractDomainObject {
private String lastName;
@MustSatisfy(SpecificationRequiresFirstLetterToBeUpperCase.class)
public String getLastName() {
resolve(lastName);
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
objectChanged();
}
public void changeLastName(
@MustSatisfy(SpecificationRequiresFirstLetterToBeUpperCase.class)
String lastName
) {
setLastName(lastName);
}
}
TODO: @MustSatisfy may not work for action parameters; see http://development.nakedobjects.org/trac/ticket/669.
In addition to the built-in value types it is also possible to define user-defined value types. This is typically done using the @Value annotation.
The @Value annotation is used to provide an implementation of the ValueSemanticsProvider interface. In turn this provides objects that allow the framework to interact with the value, in particular for parsing (parsing strings into values and display values as strings), and for encoding/decoding (for serialization for client/server and used by some persistors).
For more details, explore the built-in types within the applib, for example org.nakedobjects.applib.value.Money.
@Value(semanticsProviderName = "org.nakedobjects.metamodel.value.MoneyValueSemanticsProvider")
public class Money extends Magnitude {
...
}
where MoneyValueSemanticsProvider is the implementation of ValueSemanticsProvider described above.
Using value types generally removes the need for using @MustSatisfy annotation; the rules can instead move down into a validate method on the value type itself.
TODO: need to beef up this discussion. Also, note that it is possible to register value types using nakedobjects.properties rather than the @Value annotation; this is particularly useful for third-party value types.
In previous versions of the framework it was necessary to call the inherited resolve() method within every property or collection's getter, and objectChanged() within every property's setter and every collection's addTo or removeFrom.
These methods still exist in AbstractDomainObject, but no longer need to be called explicitly. Instead Naked Objects 4.0 uses bytecode enhancement (using either cglib or javassist) to automatically call these methods. This bytecode enhancement can be disabled in nakedobjects.properties file; if it is then the methods must be called manually as they were in Naked Objects 3.0.
Once an application is written it must, like all Java programs, be compiled before it can be run. As you want to run your application within the framework there are dependencies that need to be satisfied to allow this. Naked Object promote two ways of doing this, using Ant and Maven. This section looks at how to use Ant to build a Naked Objects application, while the next section shows how to do the same thing with Maven.
How you organise your project and its dependencies is a matter of personal preference, but for this section we will assume the following directory structure, where nakedobjects-4.0 is the unzipped distribution and project is the directory containing the application you are working on.
nakedobjects-4.0/
lib/
resources/
:
:
project/
src/
xat/
resources/
config/
build.xml
:
:
The source code for the domain objects is contained in the src directory and the tests are in xat. Resources to be made available via the class path (rather than loaded via the file system) should be added to the project/resources directory, while configuration files should be placed in config. The file build.xml should be copied across from the nakedobjects-4.0/resources directory.
To build a Naked Objects application you need to have access to its needed libraries as well as your source code. The libraries are all contained in the lib directory within the distribution download. The following extract from a build.xml file
The complete file can be found in the resource directory
shows how you can use Ant to compile a Naked Objects application. The path element is used to link in all the Jar files that are provided by the distribution, while the compile target uses javac to compile your Java code found in the source directory. Make sure you change the lib.dir property to point to the lib directory within the binary download and the src.dir property to your source directory. The src directory will normally contain DOM classes, fixtures and application-framework integration code (such as repositories and services).<property name="lib.dir" value="../nakedobjects-3.0/lib/" />
<property name="build.dir" value="./build" />
<property name="classes.dir" value="./build/classes" />
<property name="src.dir" value="./src/" />
<property name="xat.dir" value="./xat" />
<property name="compile.target" value="1.5" />
<property name="source.target" value="1.5" />
<fileset dir="${lib.dir}" id="libs.set">
<include name="**/*.jar" />
</fileset>
<path id="libs.path">
<fileset refid="libs.set" />
</path>
<target name="compile" description="Compile example">
<mkdir dir="${build.dir}" />
<mkdir dir="${classes.dir}" />
<javac destdir="${classes.dir}" target="${compile.target}" source="${source.target}">
<src path="${src.dir}" />
<src path="${xat.dir}" />
<classpath refid="libs.path" />
</javac>
</target>
Running Ant with the compile target will compile your code, producing the following output indicating that the compilation was successful. The newly created build directory will contain the class files.
$ ant compile
Buildfile: build.xml
compile:
[mkdir] Created dir: /home/rcm/workspace/project/build
[javac] Compiling 17 source files to /home/rcm/workspace/project/build
[javac] Note: Some input files use unchecked or unsafe operations.
[javac] Note: Recompile with -Xlint:unchecked for details.
BUILD SUCCESSFUL
Total time: 2 seconds
A fully runnable distribution and zip file can be created using the build file. Ensure the resource.dir property points the resource directory in the distribution.
<property name="resource.dir" value="../nakedobjects-4.0/resources" />
<target name="dist" depends="compile" description="Collects together files for distribution">
<copy todir="${build.dir}/lib">
<fileset refid="libs.set" />
</copy>
<copy todir="${build.dir}">
<fileset dir=".">
<include name="config/*" />
<include name="images/*" />
</fileset>
<fileset dir="${resource.dir}">
<include name="web/*" />
<include name="run.*"/>
<include name="lcp.bat"/>
</fileset>
</copy>
<chmod file="${build.dir}/nakedobjects.sh" perm="ugo+x"/>
<mkdir dir="${dist.dir}" />
<zip destfile="dist/no-application.zip" >
<zipfileset dir="${build.dir}" prefix="no-application" />
</zip>
</target>
Running ant with the dist target will create the distribution, producing the following output.
$ ant dist
Buildfile: build.xml
compile:
dist:
[copy] Copying 45 files to /home/rcm/workspace/project/build/lib
[copy] Copying 7 files to /home/rcm/workspace/project/build
[zip] Building zip: /home/rcm/workspace/project/dist/no-application.zip
BUILD SUCCESSFUL
Total time: 6 seconds
After this has completed the build directory will look like the following, and the dist directory will contain a zipped file of the same contents.
build/ classes/ config/ images/ lib/ resources/ web/ lcp.bat nakedobjects.bat nakedobjects.sh
Using Maven archetype plugin we can generate a new Naked Objects application very quickly. These are multi-moduled projects with separate sections for the domain code, fixtures, web interfaces and so on.
One option is to create the archetype from the commandline. Note that if you are using Eclipse and have installed the m2eclipse plugin then it provides a dialog to create the project from an archetype; this is discussed below.
TODO: update the listing below, it is out of date. The archetype to use is called 'org.nakedobjects:application-archetype'.[rcm@localhost ~]$ mvn archetype:generate -DarchetypeCatalog=http://nakedobjects.org [INFO] Scanning for projects... [INFO] Searching repository for plugin with prefix: 'archetype'. [INFO] ------------------------------------------------------------------------ [INFO] Building Maven Default Project [INFO] task-segment: [archetype:generate] (aggregator-style) [INFO] ------------------------------------------------------------------------ [INFO] Preparing archetype:generate [INFO] No goals needed for project - skipping [INFO] Setting property: classpath.resource.loader.class => 'org.codehaus.plexus.velocity.ContextClassLoaderResourceLoader'. [INFO] Setting property: velocimacro.messages.on => 'false'. [INFO] Setting property: resource.loader => 'classpath'. [INFO] Setting property: resource.manager.logwhenfound => 'false'. [INFO] [archetype:generate] [INFO] Generating project in Interactive mode [INFO] No archetype defined. Using maven-archetype-quickstart (org.apache.maven.archetypes:maven-archetype-quickstart:1.0) Choose archetype: 1: local -> nakedobjects-application (Naked Objects Application (Archetype)) 2: local -> nakedobjects-icons (Naked Objects Icons (Archetype)) 3: local -> htmlviewer-war (Naked Objects WAR (Archetype)) 4: local -> hibernate-support (Hibernate Support (Archetype)) 5: local -> remoting-support (Naked Objects Remoting Support (Archetype)) Choose a number: (1/2/3/4/5): 1
To create a project from an archetype you must specify a groupId and artifactId, a version and a Java package.
After choosing the archetype number and pressing enter you are prompted for the project's details. The groupId is an identifier representing your company/group; ours would typically be "org.nakedobjects" for the domain followed by a name for the group of products. The artifactId identifies the projects that we are creating. The version indicates how mature the project is, and should be left as the default, e.g. 1.0-SNAPSHOT. Finally, the package is the base package name used for all Java files. After these have been entered you are prompted to confirm by entering Y, as shown below.
Be wary of using invalid names. The groupId should be alphanumeric with dots to separate the words. The artifactId should also be alphanumeric, with '-' (hyphens) to separate the words. The package name should be a valid Java package name, i.e. should have no spaces or dashes. Maven does little to check these things.
Define value for groupId: : org.example Define value for artifactId: : expenses Define value for version: 1.0-SNAPSHOT: : Define value for package: : org.example.expenses Confirm properties configuration: groupId: org.example artifactId: expenses version: 1.0-SNAPSHOT package: org.example.expenses Y: : y [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------ [INFO] Total time: 1 minute 13 seconds [INFO] Finished at: Thu Oct 02 20:42:50 BST 2008 [INFO] Final Memory: 13M/247M [INFO] ------------------------------------------------------------------------
This will create a new directory with the name of the artifactId. So in this example this is expenses as shown below.
expenses
|-- pom.xml
|-- dom
| |-- pom.xml
| `-- src
|-- commandline
| |-- config
| |-- ide
| |-- pom.xml
| `-- src
|-- fixture
| |-- pom.xml
| `-- src
|-- service
| |-- pom.xml
| `-- src
`-- webapp
|-- pom.xml
`-- src
Once an archetype is created it can be built using Maven and run. Build using the install goal as shown here.
[rcm@localhost expenses]$ mvn clean install
[INFO] Scanning for projects...
[INFO] Reactor build order:
[INFO] Naked Object Application
[INFO] Domain Model
[INFO] Services
[INFO] Fixtures
[INFO] Exploration
[INFO] ------------------------------------------------------------------------
[INFO] Building Naked Object Application
[INFO] task-segment: [clean, install]
[INFO] ------------------------------------------------------------------------
[INFO] [clean:clean]
[INFO] [site:attach-descriptor]
[INFO] [install:install]
[INFO] Installing /home/rcm/tmp/bearingpoint/expenses/pom.xml to /home/rcm/.m2/repository/org/example/expenses/1.0-SNAPSHOT/expenses-1.0-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] Building Domain Model
[INFO] task-segment: [clean, install]
[INFO] ------------------------------------------------------------------------
[INFO] [clean:clean]
[INFO] Deleting directory /home/rcm/tmp/bearingpoint/expenses/dom/target
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:compile]
[INFO] Compiling 7 source files to /home/rcm/tmp/bearingpoint/expenses/dom/target/classes
[INFO] [resources:testResources]
[INFO] Using default encoding to copy filtered resources.
:
:
[INFO] [resources:testResources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:testCompile]
[INFO] No sources to compile
[INFO] [surefire:test]
[INFO] No tests to run.
[INFO] [jar:jar]
[INFO] Building jar: /home/rcm/tmp/bearingpoint/expenses/exploration/target/expenses.jar
[INFO] [assembly:attached {execution: default}]
[INFO] Reading assembly descriptor: src/main/assembly/descriptor.xml
[INFO] Processing DependencySet (output=lib)
[INFO] Copying files to /home/rcm/tmp/bearingpoint/expenses/exploration/target/expenses-exploration-1.0-SNAPSHOT-prototype.dir
[WARNING] Assembly file: /home/rcm/tmp/bearingpoint/expenses/exploration/target/expenses-exploration-1.0-SNAPSHOT-prototype.dir is not a regular file (it may be a directory). It cannot be attached to the project build for installation or deployment.
[INFO] [install:install]
[INFO] Installing /home/rcm/tmp/bearingpoint/expenses/exploration/target/expenses.jar to /home/rcm/.m2/repository/org/example/expenses-exploration/1.0-SNAPSHOT/expenses-exploration-1.0-SNAPSHOT.jar
[INFO]
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] ------------------------------------------------------------------------
[INFO] Naked Object Application .............................. SUCCESS [6.087s]
[INFO] Domain Model .......................................... SUCCESS [8.711s]
[INFO] Services .............................................. SUCCESS [2.432s]
[INFO] Fixtures .............................................. SUCCESS [2.283s]
[INFO] Commandline ........................................... SUCCESS [10.774s]
[INFO] ------------------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 31 seconds
[INFO] Finished at: Tue Mar 03 11:59:50 GMT 2009
[INFO] Final Memory: 40M/495M
[INFO] ------------------------------------------------------------------------
[rcm@localhost expenses]$
Once the project is built the results can be found in the exploration/target directory, which contains both a zipped distribution (in this example expenses-exploration-1.0-SNAPSHOT-prototype.zip) and an expanded distribution (again for this example, expenses-exploration-1.0-SNAPSHOT). Using the contained script files the application can be run from the command line.
exploration
|-- config
|-- ide
|-- pom.xml
|-- src
`-- target
|-- archive-tmp
|-- classes
|-- expenses-exploration-1.0-SNAPSHOT-prototype.dir
| `-- expenses-exploration-1.0-SNAPSHOT
| |-- config
| |-- images
| |-- expenses.jar
| |-- nakedobjects.bat
| |-- nakedobjects.sh
| |-- lib
|-- expenses-exploration-1.0-SNAPSHOT-prototype.zip
|-- expenses.jar
`-- maven-archiver
There are two approaches for using Eclipse in development.
The recommended approach is to use the Eclipse's Maven plug-in, m2eclipse, obtainable from http://m2eclipse.sonatype.org/. This then allows the Maven projects to be imported directly using File > Import > Maven Projects.
TODO: would be good to have some screenshots of using m2eclipse to import projects here.In fact, you can use m2eclipse to create the project from the archetype in a single go:
TODO: would be good to have a screenshot of using m2eclipse to generate project using archetype here.
Alternatively you can use Maven's Eclipse plug-in to generate .project and .classpath files. These can then be imported into Eclipse using File > Import > Existing Projects. The remainder of this section discusses this alternative approach; to reiterate the m2eclipse approach is more straightforward and generally preferred.
[rcm@localhost ~]$ cd expenses/ [rcm@localhost expenses]$ mvn eclipse:eclipse [INFO] Scanning for projects... [INFO] Reactor build order: [INFO] Naked Object Application [INFO] Domain Model [INFO] Fixtures [INFO] Services [INFO] Exploration [INFO] Hibernate Object Store [INFO] Web App [INFO] Client Distribution [INFO] Server Distribution [INFO] Searching repository for plugin with prefix: 'eclipse'. [INFO] ------------------------------------------------------------------------ [INFO] Building Naked Object Application [INFO] task-segment: [eclipse:eclipse] [INFO] ------------------------------------------------------------------------ [INFO] Preparing eclipse:eclipse : : : [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------ [INFO] Total time: 1 minute 22 seconds [INFO] Finished at: Thu Oct 02 20:44:41 BST 2008 [INFO] Final Memory: 32M/247M [INFO] ------------------------------------------------------------------------
This downloads all the required libraries and creates .project and .classpath files for Eclipse to use. After this is complete we can start up Eclipse and import the projects.
[rcm@localhost expenses]$ eclipse -data .
The workspace starts off empty as this is a new project. First we need to import the modules using the File/Import... menu. On the first page of this dialog select the Existing Project into Workspace option.
After selecting the same directory that we are running in, then you select all the modules in the list as Eclipse projects to import.
All the libraries are referenced using a the variable M2_REPO, so this needs to be set up in the Classpath variables in the main preferences. In the Classpath Variable tab press the New... button.
Enter the name M2_REPO and find the path to .m2/repository.
Now to run the application select Run/Run Configuration... on the commandline project and browse for the class org.nakedobjects.runtime.NakedObjects. With that selected press the Run button.
After a short while the login dialog should appear. Logon with a valid user name and password, like sven and pass.
When the application has finished starting up you should the services on the DnD user interface.
A Naked Objects application is commonly developed in two stages. First it is developed as standalone prototype, running on a single machine, with no security, and with all the objects in memory. Then it is converted into a deployable application i.e. running on a server with multiple clients, with a persistence layer (most commonly in the form of the Hibernate Object Store) and with full authentication and authorization. (Note that these two phases do not have to be sequential, you might prefer to develop your system feature-by-feature right through to a deployable version.) In this section we show you how to run Naked Objects as prototype; in the next section we show you how to deploy the same domain object model as the real application.
Running a Naked Objects application generally requires some configuration files and images. Resources are typically loaded from the working directory, while specific resources can be loaded from the class path. Assuming that we will be running from the development directory the following structure is typical of a Naked Objects application.
project/
src/
xat/
config/
nakedobjects.properties
passwords
images/
build.xml
:
Naked Objects loads in one or more properties files during startup. The properties files must contain at least a list of services that the application uses and commonly there is also a list of fixtures that are to be run at startup. In addition to the core properties there may be also other properties that are used by other components. The file nakedobjects.properties is always loaded up, while other properties files relating to specific components are only loaded up if the component they relate to is to be is used. For example when using the DND viewer the system will also load in the file viewer_dnd.properties, and when using the hibernate object store the file persistor_hibernate.properties is loaded. While nakedobjects.properies must exist for the framework to start up properly, all other properties files are optional.
The example nakedobjects.properties file below shows a number of service classes being listed along with a generic repository for accessing Location objects. The prefix is prepended to each class name before loading. The second set of properties lists the fixture classes to load and instantiate.
nakedobjects.services.prefix=org.nakedobjects.example.ecs nakedobjects.services=repository#Location,resources.CustomerRepository, \ CustomerFactory, BookingFactory, resources.PaymentMethodFactory, \ resources.PhoneNumberFactory, LocationFactory nakedobjects.fixtures.prefix=org.nakedobjects.example.ecs.fixtures nakedobjects.fixtures=CitiesFixture, BookingsFixture, ContextFixture
By default the authentication mechanism is a username/password challenge with the details stored in a password file called passwords. This file is a simple list of user names and their corresponding passwords separated by a colon. The file below shows two users with badly chosen passwords.
jbrown:pass tsmith:pass
The drag and drop and web interfaces both display icons as part of the views for each object. Images for these icons, and for other uses, are sought in the images directory. The web interface currently only works with GIF images, while the DnD interface will work with GIF, PNG and JPEG images. The following shows images for the main objects in the ECS example.
images
Booking.gif
City.gif
CreditCard.gif
Customer.gif
Location.gif
Telephone.gif
Running a naked objects application is done via the Naked Objects boot loader, which is the class NakedObjects. Without any arguments Naked Objects will be started in prototype mode with the Drag and Drop viewer. The example below shows the Ant target (from the build template file) that will start up Naked Objects in exploration mode. The classpath is simply a list of all the library jars (as compiled by the path element) and the classes compiled by the compile target show previously.
<property name="bootclass" value="org.nakedobjects.runtime.NakedObjects"/>
<path id="libs.external">
<fileset dir="${lib.dir}">
<include name="**/*.jar" />
</fileset>
</path>
<target name="exploration-dnd" depends="compile"
description="Run exploration class using drag-and-drop graphical viewer">
<java fork="yes" classname="${bootclass}">
<classpath path="${classes.dir}" />
<classpath refid="libs.external" />
<arg line="--type exploration --viewer dnd"/>
</java>
</target>
Run
$ ant exploration-dnd Buildfile: build.xml
By changing the command line parameters you can change the way that Naked Objects starts up. This can include using different: viewers, such as web browser, command line and drag and drop; persistors, such as Hibernate, XML, and in-memory; and modes, such as standalone, client and server. Details on all the options are listed in the reference section.
TODO: need to discuss the alternative org.nakedobjects.webserver.WebServer bootloader that can be used to run the webapp defined by the webapp project's web.xml descriptor.
Assuming that you have developed your domain model and tested it as a standalone prototype (previous section), this section describes the process of deploying the domain object model as an enterprise application.
$ nakedobjects.sh --type server --viewer xstream-sockets --persistor xml
This command runs Naked Objects as a socket server. The viewer command line option specifies that the server will listen for requests using basic TCP/IP plain sockets with the XStream marshalling mechanism. The persistor command line option indicates to use XML object store as the persistence mechanism.
$ nakedobjects.sh --type client --viewer dnd --connector xstream-sockets
This commands starts up Naked Objects as a client, using the DND viewer. The connector command line option indicates to use the XStream marshalling mechanism over basic TCP/IP sockets. As we are now running a multi-user system more than one client can be started up.
To allow the client to connect to a remote server the address property needs to be specified. For simplicity this can be added the nakedobjects.properties file, but it may alternatively be placed in a separate file called transport_sockets.properties.
nakedobjects.transport.sockets.host = server.mycompanyname.com
$ nakedobjects.sh --type server --viewer encoding-http --persistor xml
This command runs Naked Objects as a server. The viewer command line parameter specifies that the server will listen using http using the encoding marshalling mechanism. The persistor property XML object store as the persistence mechanism - a very simple persistence mechanism useful for initial prototyping. (In fact, the XML object store is the default persistence mechanism, so this property may be omitted from the command).
Alternatively the remoting servlet EncodingOverHttpRemotingServlet can be added to the web.xml in the webapp project and the Naked Objects run as a web app, see below.
$ nakedobjects.sh --type client --viewer dnd --connector encoding-http
This commands starts up Naked Objects as a client, using the DND viewer. The connector command line option indicates to use submit requests using http and with the encoding marshalling mechanism. Again, as we are now running a multi-user system more than one client can be started up.
To allow the client to connect to a remote server the address property needs to be specified. For simplicity this can be added the nakedobjects.properties file, but it may alternatively be placed in a separate file called transport_http.properties.
nakedobjects.transport.http.url = http://server.mycompanyname.com:8080/remoting.svc
Naked Objects provides three different ways to run as a webapp:
$ nakedobjects.sh --type server --viewer html --persistor xml 2007-08-09 12:37:13.671::INFO: Logging to STDERR via org.mortbay.log.StdErrLog 2007-08-09 12:37:13.801::INFO: jetty-6.0.2 2007-08-09 12:37:13.954::INFO: Started SocketConnector @ 0.0.0.0:8080
This command runs the Naked Objects with the HTML viewer, allowing multiple clients to access it via a browser. As for the standalone version the users need to access the URL http://server/logon.app to access the log on page.
The next mechanism uses the org.nakedobjects.webserver.WebServer bootstrap to run Naked Objects. This loads up whatever is in the webapp project's web.xml file.
TODO: we don't have a webserver.sh script to show this in action; we probably should.
Ordinarily the web.xml will be configured to run the same HTML viewer, so the end result will be the same. However, if necessary the remoting servlet can also be configured
The final mechanism is to use Maven to package up the webapp project as a WAR file, packaging up whatever is in the web.xml file. As above, ordinarily the web.xml will be configured to run the same HTML viewer, so the end result will be the same. However, if necessary the remoting servlet can also be configured
Packaging up is done using:
$ cd webapp $ mvn clean package
This should result in a WAR file in target directory. This can be deployed to an existing servlet containerd
Perspectives allow the set of services available to a user (eg as icons in the DnD viewer) to be customized for that user. Since these services represent the "start points" for the user to interact with the domain model, they in a sense define an application on a per-user basis.
The perspectives are stored in user-profiles, which are a persistence mechanism independent of the object store. (The intent is for user profiles to store additional information for use by viewers, for example allowing a user to reskin an application or change colors or fonts. As of NOF 4.0 the functionality provided is still limited, however).
The key here is that if a user logs in and no perspective exists for that user then one will be created for them. The new perspective will be a copy of the 'template' perspective, or, if none was defined, a perspective containing all the known services. To create a template perspective add a perspective fixture that calls saveAsDefault(), rather than saveForUser() for a named user, as shown below.
public class PerspectivesFixture extends UserProfileFixture {
@Override
protected void installProfiles() {
Profile profile = newUserProfile();
Perspective perspective = profile.newPerspective("ECS");
perspective.addToServices(LocationFactory.class);
perspective.addToServices(CustomerRepository.class);
perspective.addToServices(PaymentMethodFactory.class);
saveAsDefault(profile);
}
}
With that set up, when a new user now logs in they will see three service icons on the screen for locations, customers and payment methods. On the drag and drop user interface the user can add and remove services from their perspective. To remove a service icon right-click on the grey border and select the close option. To add a service select the Services... option from the application menu (accessed by right-clicking on the application background) and drag the required service onto the desktop. Unfortunately there is no similar mechanism available on the HTML user interface so you will need to modify the created perspectives via the DND UI.
TODO: this material seems to repeat a lot of what is in Link; should be merged and simplified.
Starting Naked Objects requires a user name and password . (The exception is when running in exploration mode, which uses default values). The simplest way to specify the list of user names and passwords is in a passwords file.
Create a file called passwords, and place into the config directory as the following example shows.
config/ passwords
The following example shows how user names (sven and dick) and corresponding passwords (passwd1 and passwd2) should be specified in the password file.
sven:passwd1 dick:passwd2
Authorization allows access to actions and properties to be controlled by configuration. Authorization is entirely optional and is disabled by default - in which case every user is given access to all actions and properties. A prerequisite for authorization is for users to have roles. These should be added to the password file as follows.
sven:passwd1:ordinary_user dick:passwd2:special_user|admin
Sven has the role 'ordinary_user', dick has the two roles 'special_user' and 'admin'.
Naked Objects needs to be told which actions and properties are authorised for which roles. The simplest way to do this is in a file i.e. allow in the config directory.
config/
password
allow
Each entry in the allow file specifies either a class, or a particular action or property within a class, together with the roles that are allowed to access it. E.g.
example.dom.PhoneNumber#Number:ordinary_user|special_user example.dom.Contact#Phones:ordinary_user|special_user example.dom.Contact#FullName:ordinary_user|special_user org.nakedobjects.nof.core.service.SimpleRepository#FindByTitle(java.lang.String):ordinary_user|special_user example.dom.PhoneNumber#Type:ordinary_user|special_user example.dom.Contact#KnownAs:ordinary_user|special_user org.nakedobjects.nof.core.service.SimpleRepository#NewPersistentInstance():ordinary_user|special_user example.dom.Contact#CreatePhone():special_user org.nakedobjects.nof.core.service.SimpleRepository#NewTransientInstance():ordinary_user|special_user org.nakedobjects.nof.core.service.SimpleRepository#AllInstances():ordinary_user|special_user
The above file allows 'example.dom.Contact#CreatePhone()' only for users with role 'special_user' all other actions and properties are allowed for roles 'ordinary_user' and 'special_user'. A different, terser implementation of this would be to use separate allow and disallow files, or example, the allow file:
example.dom.PhoneNumber:ordinary_user|special_user example.dom.Contact:ordinary_user|special_user org.nakedobjects.nof.core.service.SimpleRepository:ordinary_user|special_user
and the disallow file:
example.dom.Contact#CreatePhone():ordinary_user
For more details, see the section on Managing Security.
No configuration is necessary for the simplest (file-based) authentication mechanism. For authorization the reflector needs to be decorated to look up the permissions every time the framework needs information about the visibility of fields and menus. To include the decorator add the following to a properties file.
nakedobjects.reflector.facet-decorators=file-security
If a disallow file is to be used, this needs to be specified explicitly (the allow file will be picked up automatically):
nakedobjects.security.blacklist.file=config/disallow
All the text that appears to the user comes from the domain object model. Any literal text used within the code may be externalised, and localised using the standard Java mechanism of resource bundles. However as all field and menu names are derived from the method names the framework provides a mechanism to adapt these names using resource bundles as well.
Create a file for each required language, e.g., i18n_en_GB.properties for English in Great Britain, and place them into the project such that they will be included in the class path. For the project structures we have already seen the files could be added to the resources directory, as the following example shows.
resources/ i18n_en_GB.properties i18n_de_DE.properties i18n_fr_FR.properties
The following example is part of one of those translation file and shows how property and action names and descriptions may be specified.
example.dom.Contact.property.Phone.name=Téléphone example.dom.Contact.action.NewPhone.name=Nouveau téléphone example.dom.Contact.property.FullName.description=Le nom complet du client
For more details on defining localisation files, Link.
The reflector needs to be decorated with a facet decorator to look up the translated names every time the framework needs information about the fields and menus. To include the decorator add the following to a properties file.
nakedobjects.reflector.facet-decorators=resource-i18n
If the application is being accessed via the web (html) viewer, then the server will need to perform the localisation, so the above property should be included in one of nakedobjects.properties read by the server; if the application is being accessed by a client (e.g. the DND viewer) then the localisation will be performed within the client, and the property should be specified in one of the properties files read by the client e.g. facet-decorator_i18n.properties.
The locale will be picked up automatically from the machine on which the code is executing. You may override this by specifying the locale in a properties file.
Naked Objects supports authentication and role-based authorization. Within the framework the user is represented by a Session object. From the session object you can get the name of the user and their roles.
The session is used within the distribution mechanism and the security manager. The security manager uses the session to determine if a user can access or manipulate an object member. The distribution mechanism passes the session to the server so the server knows which user is accessing it.
An Authoriser processes an AuthorizationRequest and if granted generates a Session object to represent the user.
To explicitly use file-based authentication set the following property. (Note, however, that if this property is absent, the framework will anyway assume file-based authentication as the default).
nakedobjects.authentication=file
The location and name of the passwords file may be specified explicitly - if not it defaults to the passwords file, assumed to be in the config directory (or under WEB-INF if running as a web application).
nakedobjects.authentication.file.location= passwords
The passwords file contains users, passwords and any roles, as follows.
<user>:<password>:<role>|<role>|...
For example, user 'sven', with password 'pass' and roles 'role1' and 'role2' would be
sven:pass:role1|role2
To authenticate through LDAP set
nakedobjects.authentication=ldap
LDAP needs a URL for the server and a 'dn' of the root of the users on the server. For example,
nakedobjects.authentication.ldap.dn= dc=nakedobjects, dc=org
nakedobjects.authentication.ldap.server=ldap://localhost:10389
The logon password check will be on uid='username', <dn> - For example, attempting to logon with user 'sven' with the above settings will check against the server on localhost, port = 10389, uid=sven, dc=nakedobjects, dc=org, using the password in the standard userpassword attribute in that entry. Once logged in, roles will be picked up from cn='rolename' where objectclass =organizationalRole in child entries of the user entry.
To authenticate through a database set
nakedobjects.authentication=database
Database authentication is dependent upon Hibernate. Authentication tables should exist in the database configured in the hibernate.cfg.xml settings. By default this will check against a set of user and role database tables described as follows.
CREATE TABLE user (id INTEGER NOT NULL , username VARCHAR(255) NOT NULL,password VARCHAR(255) NOT NULL, PRIMARY KEY (id)); CREATE TABLE role (id INTEGER NOT NULL , rolename VARCHAR(255) NOT NULL, PRIMARY KEY (id)) ; CREATE TABLE user_role (id INTEGER NOT NULL , user INTEGER NOT NULL,role INTEGER NOT NULL, PRIMARY KEY (id));
The password should be encrypted using the 'MD5' algorithm. (Example code from sun ).
private String generateHash(String key) {
MessageDigest md = MessageDigest.getInstance("MD5");
md.reset();
md.update(key.getBytes());
byte[] bytes = md.digest();
// buffer to write the md5 hash to
StringBuffer buff = new StringBuffer();
for (int l=0;l< bytes.length;l++) {
String hx = Integer.toHexString(0xFF & bytes[l]);
// make sure the hex string is correct if 1 character
if(hx.length() == 1) buff.append("0");
buff.append(hx);
}
return buff.toString();}
As with authentication, authorization may be executed via a file, LDAP, or database. If no authorization mechanism is specified, all domain calls, methods and properties will be available to all users.
To enable authorization by file including the facet and specifying the authorization implementation in the settings
nakedobjects.reflector.facets.include=\
org.nakedobjects.runtime.authorization.standard.AuthorizationFacetFactoryImpl
nakedobjects.authorization=file
With file authorization the current user's role will be authorised against a white and (optionally) a black list for each method possibly available to them. The files are identified by properties, e.g.
nakedobjects.authorization.file.whitelist=allow nakedobjects.authorization.file.blacklist=disallow
The white list file is mandatory, if there is no property it defaults to 'allow' (picked up from the config directory or from WEB-INF if running as a webapp). The black list file is optional. The presence of the property indicates it is being used. If the white list file default is being used then 'disallow' is suggested as a setting for the black list.
Each file contains a signature to match against and a list of roles. A match in the white list file permits the method and match in the black list forbids it. The black list overrides the white list. Matching occurs at three levels class, method (or property) and parameters. Property matches are to the bean property name e.g. 'phone' for getPhone and setPhone.
Formatted as follows:
If only class is to be matched: <fully qualified class> :role1|role2|..
Class and method: <fully qualified class>#<method>:role1|role2|..
Class and property: <fully qualified class>#<property>:role1|role2|..
Parameters where method takes no parameters: <fully qualified class>#<method>():role1|role2|...
Parameters where method takes one parameter: <fully qualified class>#<method>(fully qualified parameter class):role1|role2|...
Parameters where method takes two or more:
<fully qualified class>#<method>(fully qualified parameter class1, fully qualified parameter class2, ...):role1|role2|...
Each line in the file is a separate match.
So, for example, in the white list file
com.nakedobjectsgroup.expenses.services.hibernate.ClaimRepositoryHibernate:role1
will permit all actions/properties on com.nakedobjectsgroup.expenses.services.hibernate.ClaimRepositoryHibernate for role1
com.nakedobjectsgroup.expenses.services.hibernate.
ClaimantRepositoryHibernate#findClaimantByName:role1|role2
will permit all overloaded methods named findClaimantByName on com.nakedobjectsgroup.expenses.services.hibernate.ClaimantRepositoryHibernate for role1 and role2 and
com.nakedobjectsgroup.expenses.services.hibernate.ClaimantRepositoryHibernate#findClaimantByIdentifier(java.lang.String):role3
will permit findClaimantByIdentifier with parameter java.lang.String on com.nakedobjectsgroup.expenses.services.hibernate.ClaimantRepositoryHibernate for role3. In each case an identical entry in the black list file will instead make the action or property unavailable. The combination of white and black list allows some economy in the settings. For example, if all but one method of a large class is to be allowed, that could be configured with one entry per method in the white list file or, better, a single entry for the class in the white list and a single entry for the unavailable method in the black list.
To enable authorization by LDAP server include facet for authorization and specifying the ldap implementation for authorization.
nakedobjects.reflector.facets.include=\
org.nakedobjects.runtime.authorization.standard.AuthorizationFacetFactoryImpl
nakedobjects.authorization=ldap
With LDAP authorization the current user's role will be authorised against an entry in the LDAP server for each method possibly available to them. The URL of the server is obtained from the same property as for authentication.
The authorization entries should exist under a DN configured in the property file. For example.
nakedobjects.authorization.ldap.application.dn= cn=expenses, dc=apps, dc=nakedobjects, dc=org
This will be checked anonymously.
Entries to be matched against should exist under this dn. Expected configuration is a hierarchy of class, method and parameters. The cn of each entry is expected to be the fully qualified class name, method or parameter list ('()' for an empty parameter list). If an entry has a 'uniquemember' attribute equal to 'role' then that entry and all sub-entries are authorised.
For example
DN: cn=com.nakedobjectsgroup.expenses.services.hibernate.ClaimRepositoryHibernate, cn=expenses, dc=apps, dc=nakedobjects, dc=org
with uniquemember = role1 will authorise all members of com.nakedobjectsgroup.expenses.services.hibernate.ClaimRepositoryHibernate for users with role1.
DN: cn=(com.nakedobjectsgroup.expenses.claimant.Claimant, java.lang.String),
cn=createNewClaim, cn=com.nakedobjectsgroup.expenses.services.hibernate.ClaimRepositoryHibernate, cn=expenses, dc=apps, dc=nakedobjects, dc=org
with uniquemember = role1 will authorise the createNewClaim(com.nakedobjectsgroup.expenses.claimant.Claimant, String) member of com.nakedobjectsgroup.expenses.services.hibernate.ClaimRepositoryHibernate for users with role1.
To enable authorization through a database table specify:
nakedobjects.reflector.facets.include=\
org.nakedobjects.runtime.authorization.standard.AuthorizationFacetFactoryImpl
nakedobjects.authorization=database
In the same way as database authentication, database authorization is dependent upon hibernate. The authorization table should exist in the database configured in the hibernate.cfg.xml settings. By default this will check against a permissions table described as follows.
CREATE TABLE permissions (id INTEGER NOT NULL, role INTEGER NOT NULL,permission VARCHAR(255) NOT NULL, flags INTEGER, PRIMARY KEY (id));
The functionality is the same as for white list file matching described above, where the permission field in the table contains the signature to match.
There is an optional capability to authorise the visibility and usability of fields independently. Thus a field may be visible to certain roles but not editable. In order to configure this against a authorization a flag must be added as follows.
Add a "-rw" suffix to any role in the allow or disallow files. "-rw" will match (and hence allow in the allow file, disallow in the disallow file) edit authorization requests.
Populate the flags column in the permissions table with the value 0 (for read/write) or 1 (for read only).
Add a flags attribute to the entry with the role. If it's set to 'rw' editing will be allowed, any other value (typically 'ro') will disallow editing.
In each case absence of the flag will mean that visibility and usability and are always the same.
As a tool for helping to configure the initial security settings there is a 'learn' property.
nakedobjects.authorization.learn=true
When this is present and set, all methods will be authorised for all roles. However as each method is accessed through the user interface authorization will be configured for that method and the current role. Configuration will be saved in either the file (saved on shutdown), database table or LDAP security settings depending upon the reflector setup.
The settings can then be modified to conform to the required security settings.
Naked Objects creates an adapter for every domain object. There are three categories of objects that are recognised: value, reference and collection. Value and collection objects are dealt with on a class by class basis, while all reference objects are dealt with in the same way where the properties are seen as fields, and public methods as actions. Listed below are all the recognised types.
The following are built-in types. In addition to these any type either annotated using @Value, or registered as a value type using nakedobjects.properties, will also be treated as a value type.
Note that java.util.Map is not supported as a collection type.
The Java 1.5 reflector allows the domain modeller to define behaviourally complete domain objects, provide maximum feedback to the user, and control any interaction that may take place (rather than place that code in each and every user interface and back-end). This sections lists the explicit methods that are recognised by the standard reflector. All methods must be public to be recognised.
Methods that do not get matched with any of the listed rules are deemed to be action methods that we expect the user to invoke via the user interface. These are public methods, of the following format
public void <actionName>([<property type> param]...) public <type> <actionName>([<property type> param]...)
that are not used for fields, titles, or for controlling other methods. Essentially they are the methods left over from generating all other aspects of the class.
When such a method returns a reference the framework will attempt to display that object. If an action method returns null then nothing will be displayed.
public void addTo<PropertyName>(<collection element type> param)
Used in conjunction with a collection, the addTo method is called by the framework when the user adds an element to the collection. By providing this method in conjunction with a non-generic collection it will be limited to a specific type (rather than being used to store references of type Object). This behaviour is similar to that of the modify method for properties.
public <property type>[] choices<PropertyName>([<property type> param]) public List<property type> choices<PropertyName>([<property type> param])
Used in conjunction with a property, the choices method provides the user with a set of options for setting a property. This method is called when the user interface makes the options available, hence its return value can be based on the current state of the object. Note - this does not limit the state of the field, see the validate methods for a way to do that.
On parameters on an actionEither:
public Object[] choicesN<actionName>() public List<parameter type> choicesN<parameterNumber><actionName>()
where N is the 0-based parameter number.
Or:
public Object[] choices<actionName>([<parameter type> param]...) public List<parameter type> choices<parameterNumber><actionName>([<parameter type> param])
Complements an action method, or a specific paramter within an action.
The choices method provides a set of options for the parameters of an action. This method is called when the user interface prompts for the parameters from the user, so the options cannot depend on the state of any of the other parameters. Note - this does not limit the state of the parameter, see the validate methods for a way to do that.
For each parameter the array should contain either null, an array, or a collection.
public void clear<PropertyName>()
Used in conjunction with a property, the clear method is called when the user (rather than the framework) clears a reference field, or blanks (so there is no entry) a value field.
See also Link
public void created()
Life cycle method called by framework when an object is first created. This is the instance's logical creation. This method will not be called when the object is recreated during persistence or remoting.
public <parameter type> default<actionName>([<parameter type> param]...)
Complements a get/set method for a reference or value property.
Provides the default reference or value for a property to be set to when an object is first created, or the user asks for a property to be reset.
For parameters on an actionEither:
public Object[] defaultN<actionName>() public List<parameter type> defaultN<parameterNumber><actionName>()
where N is the 0-based parameter number.
Or:
public Object[] default<actionName>([<parameter type> param]...) public List<parameter type> default<parameterNumber><actionName>([<parameter type> param])
Complements an action method, or a specific parameter on an action method.
Provides the defaults that should be used for the parameters expected by an action method. The values should be provided as an array with the same number of elements as the number of parameters. Any parameter that does not have a default should have its corresponding element in the array set to null.
Deprecated but still supported; replaced by removed()
public void deleted()
Life cycle method called by framework when an object has just been removed from the persistent store. At this point the object will exist in memory, but no longer exist in the persistent store.
Deprecated but still supported; replaced by removing()
public void deleting()
Life cycle method called by framework when an object is just about to be removed from the persistent store. At this point the object still exists in the persistent store.
public String disable<PropertyName>([<property type> param]) public String disable<actionName>([<parameter type> param]...)
Complements a get/set method for a field, or an action method.
The disable dynamically controls whether a field is editable, or an action can be initiated. If a String object is returned the field or action is disabled and the string is made visible to user to inform them why it is disabled. If the method returns null then field or action remains enabled.
public <property type> get<PropertyName>()
A standard JavaBean accessor/mutator pair will be recognised as a field for a value or reference. Values include Java primitives, Strings and Dates, and also the value types provided by the Naked Objects application library (see the section on Link).
If there is no mutator method then the field is flagged as being derived and will not be persisted or editable.
Collection propertyA collection is picked up via an accessor method with the following signature. Java arrays, Vectors and Lists (including generic lists) are the only types that are recognised at the moment.
public <collection type> get<PropertyName>()
public String getId()
The getId method is used by service objects to provide an identity for a particular service type. This should be the same for all implementations of a specific service allowing them to be swapped for one another.
public boolean hide<PropertyName>([<property type> param]) public boolean hide<actionName>([<parameter type> param]...)
Complements a get/set method for a field, or an action method.
The hide method allows a property or action to be dynamically hidden from the user. This is typically used for security reasons, such as hiding a field once it is set up. Returning true makes the property or action invisible.
public String iconName()
The iconName is used to over-ride the framework's default mechanism for determining the icon to display based on the class name. The name returned by this method should be simply a name and not a filepath: it should not have an file extension. The framework will look for an acceptable type of image using this name, deriving possible filenames from it. See the section on Link for more information.
public void loaded()
Life cycle method called by framework when an object has just been loaded in from the persistent store. At this point the object has it's correct persisted state.
public void loading()
Life cycle method called by framework when an object is just about to be loaded from the persistent store. At this point the object exists in memory but has not had its state restored.
public void modify<PropertyName>(<property type> param)
Complements a property.
The modify method is called when the user (rather than the framework) sets a reference or value field. This is typically used to initialise an association (where an association is combination of references, such as a back link), or to trigger other behaviours such as adding the new value to a total.
See also Link.
Replaces saved()
public void persisted()
Life cycle method called by framework when a transient object has just been added to the persistent store. At this point the object exists in the persistent store.
Replaces saving()
public void persisting()
Life cycle method called by framework when a transient object is just about to be added to the persistent store. At this point the object exists only in memory and not in the persistent store.
Replaces deleted()
public void removed()
Life cycle method called by framework when an object has just been removed from the persistent store. At this point the object will exist in memory, but no longer exist in the persistent store.
Replaces deleting()
public void removing()
Life cycle method called by framework when an object is just about to be removed from the persistent store. At this point the object still exists in the persistent store.
public void removeFrom<PropertyName>(<property type> param)
Complements a collection.
The removeFrom method is called by the framework when the user removes an element from the collection. By providing this method in conjunction with a non-generic collection interactions with that collection become limited to a specific type (rather than being used to store references of type Object). This signature is similar to the clear method for properties.
Deprecated but still supported; replaced by persisted()
public void saved()
Life cycle method called by framework when a transient object has just been added to the persistent store. At this point the object exists in the persistent store..
Deprecated but still supported; replaced by persisting()
public void saving()
Life cycle method called by framework when a transient object is just about to be added to the persistent store. At this point the object exists only in memory and not in the persistent store.
public void set<PropertyName>(<property type> param)
Complements a get method.
The set method is called when the property is changed, either by the user or the framework. As the framework calls the same method when recreating the object from persistent data it is important to avoid doing any work in the set method other than setting up the property variable. A typical set method would be:
private String name;
public void setName(String name) {
this.name = name;
}
public String toString()
The toString method is used to get a title for the object. If the toString method cannot serve as the title, because it is being used for alternative purposes (such as debugging) then you may define a title method instead (below).
public String title()
If a title method is defined then the framework will use this in preference to the toString method for displaying the object's title.
public void updated()
Life cycle method called by framework when a modified persistent object has just been saved to the persistent store. At this point the object's state in the persistent store will be in its new state.
public void updating()
Life cycle method called by framework when a persistent object has just been modified and is about to be saved to the persistent store. At this point the object's state in the persistent store will still be in its pre-modified state.
public String validate<PropertyName>(<property type> param)
Complements a get/set method for a value or reference property.
This validate method is used to check the value/reference that is to be used to set a property. Using such a mechanism the programmer can guarantee that a field never becomes invalid. If a String object is returned the value/reference is consider to be invalid and the field will not be set. The string itself will then be made visible to the user to inform them why it is invalid. If the method returns null then property will be set, that is, its set or modify method will be called with the same parameter.
Ensure collection can have an element added or removedpublic String validateAddTo<PropertyName>(<property type> param) public String validateRemoveFrom<PropertyName>(<property type> param)
Complements a get method for a collection property.
This validate method is used to check that the specified object can be added to or removed from a collection. Using such a mechanism the programmer can guarantee that the collection never becomes invalid. If a String object is returned then adding/removing the specified object is not valid and the collection will not be changed. The string itself will then be made visible to the user to inform them why changing the collection with the object would be invalid. If the method returns null then the object will be added to/removed from the collection, that is, from the collection object directly if there is no addTo/removeFrom method, or via a call to the addTo/removeFrom method, with the same parameter, if they do exist.
Ensure it is valid to invoke an actionpublic String validate<actionName>([<parameter type> param]...)
Complements an action method.
This validate method is used to check that the actions parameters are all valid and that the action method can be safely invoked, that is, if it is invoked that it will not fail. If a String object is returned then the action should not be invoked. The string itself will then be made visible to the user to inform them why the method cannot be invoked. If the method returns null then the action method will be invoked with the same set of parameters.
Ensure object is in a valid state to be savedA validate() method may be added to provided validation at object level - prior to making an object persistent.
public String validate()
This is particularly useful for validating fields in relation to each other, as in the following example:
public class Booking {
private Date fromDate;
public Date getFromDate() {...}
public void setFromDate(Date d) {...}
private Date toDate;
public Date getToDate() {...}
public void setToDate(Date d) {...}
public String validate() {
if (fromDate > toDate) {
return "From Date cannot be after To Date";
}
return null;
}
...
}
This will prevent the user from saving a transient Booking where the From Date falls after the To Date. Note that in this example, the two date properties could also have their own individual validate methods - for example in order to test that each date was after today.
At the time of writing, the validate method is called only when the object is first saved, not when it is subsequently updated. For validation of subsequent updates, it is necessary to build the validation logic into the individual property validation methods, though these could delegate to a common validate method.
This section defines the set of annotations that are recognised by the Naked Objects Java Programming Model.
Note: The recommended mechanism for specifying the order in which actions are listed to the user is @MemberOrder (see below). @ActionOrder provides an alternative mechanism, in which the order is specified in one place in the class, with the added advantage (currently) that you can easily specify groupings (which may be rendered by the viewer as sub-menus). However, @ActionOrder is more 'brittle' to change: if you change the name of an existing action you will need to ensure that the corresponding name within the @ActionOrder annotation is also changed.
The syntax is: @ActionOrder("<comma separated list of action names>") (the action names are not case sensitive).
For example:
@ActionOrder("PlaceNewOrder, CheckCredit")
public class Customer {
public Order placeNewOrder() {}
public CreditRating checkCredit() {}
...
}
Actions can be grouped together by surrounding the group with brackets, and prefixing the group with name and colon. This information may be used by the viewing mechanism to render actions into sub-menus. For example:
@ActionOrder("(Account Management: PlaceOrder, CheckCredit), (Personal Details: ChangeOfAddress, AddEmail)")
public class Customer {
public Order placeNewOrder() {}
public CreditRating checkCredit() {}
public void changeOfAddress() {}
public void addEmail(String emailAddress) {}
...
}
For immutable objects where there is a bounded set of instances, the @Bounded annotation can be used. For example:
@Bounded
public class County {
// members and actions here
}
The number of instances is expected to be small enough that all instance can be held in memory. The viewer will use this information to render all the instances of this class in a drop-down list or equivalent. (Note: Although this is not enforced, @Bounded is intended for use on final classes. Its behaviour when used on interfaces, or classes with sub-classes is not specified).
The @Debug annotation marks an action method as available in debug mode only, and so will not normally be displayed by the user interface.
The @DescribedAs annotation is used to provide a short description of something that features on the user interface. How this description is used will depend upon the viewing mechanism - but it may be thought of as being like a 'tool tip'. Descriptions may be provided for objects, members (properties, collections and actions), and for individual parameters within an action method. @DescribedAs therefore works in a very similar manner to @Named.
Providing a description for an objectTo provide a description for an object, use the @DescribedAs annotation immediately before the declaration of that object class. For example:
@DescribedAs("A Customer who may have originally become known to us via " +
"the marketing system or who may have contacted us directly.")
public class ProspectiveSale {
...
}
Any member (property, collection or action) may provide a description. To specify this description, use the @DescribedAs annotation immediately before the declaration of that member. For example:
public class Customer {
@DescribedAs("The name that the customer has indicated that they wish to be " +
"addressed as (e.g. Johnny rather than Jonathan)")
public String getFirstName() { ... }
}
To provide a description for an individual action parameter, use the @DescribedAs annotation in-line i.e. immediately before the parameter declaration. For example:
public class Customer {
public Order placeOrder(
Product product,
@Named("Quantity")
@DescribedAs("The quantity of the product being ordered")
int quantity) {
Order order = new Order();
order.modifyCustomer(this);
order.modifyProduct(product);
order.setQuantity(quantity);
return order;
}
...
}
The @Disabled annotation means that the member cannot be used in any instance of the class. When applied to the property it means that the user may not modify the value of that property (though it may still be modified programmatically). When applied to an action method, it means that the user cannot invoke that method. For example:
public class Customer {
@Disabled
public void assessCreditWorthiness() { ... }
@Disabled
public int getInitialCreditRating(){ ... }
public void setInitialCreditRating(int initialCreditRating) { ... }
}
Note that if an action is marked as @Disabled, it will be shown on the user interface but cannot ever be invoked. One possible reason to do this is during prototyping, to indicate an action that is still to be developed. If a method is intended for programmatic use, but not intended ever to be invoked directly by a user, then it should be marked as @Hidden instead.
This annotation can also take a single parameter indicating when it is to be hidden, for example the following code would disable the action until the object has been saved.
public class Customer {
@Disabled(When.UNTIL_PERSISTED)
public void assessCreditWorthiness() { ... }
}
The acceptable values for the parameter are: When.ALWAYS, When.NEVER, When.ONCE_PERSISTED and When.UNTIL_PERSISTED. By default the annotated property or action is always disabled i.e. it is implicitly When.ALWAYS.
The @Executed annotation overrides the default location where a method is executed.
Forcing a method to be executed on the clientThe @Executed(Where.LOCALLY) annotation marks an action method so that it executes on the client, rather than being forwarded to the server for execution. This is useful for methods that invoke a service that must be run client-side, for example spawning off a separate process (such as a web browser or Acrobat Reader).
Forcing a method to be executed on the serverThe @Executed(Where.REMOTELY) annotation marks an action method so that it executes on the server, even though it would normally be executed on the client (as methods for transient objects are). This is useful for methods that although based on transient objects need access to persistent objects.
The @Exploration annotation marks an action method as available in exploration mode only, and therefore not intended for use in the production system
Note: The recommended mechanism for specifying the order in which fields are listed to the user is @MemberOrder (see below). @FieldOrder provides an alternative mechanism, in which the order is specified in one place in the class. However, @FieldOrder is more 'brittle' to change: if you change the name of an existing property you will need to ensure that the corresponding name within the @FieldOrder annotation is also changed.
The syntax is: @FieldOrder("<comma separated list of field names>") (the field names are not case sensitive.).
For example:
@FieldOrder("Name, Address, DateOfBirth, RecentOrders")
public class Customer {
public Date getDateOfBirth() {...}
public List<Order> getRecentOrders() {...}
public String getAddress() {...}
public String getName() {...}
...
}
The @Hidden annotation indicates that the member (property, collection or action) to which it is applied should never be visible to the user. For example:
public class Customer {
private int internalId;
@Hidden
public int getInternalId() {
return internalId;
}
@Hidden
public void updateStatus() { ... }
}
This annotation can also take a single parameter indicating when it is to be hidden, for example the following code would show the Id until the object has been saved, and then would hide it.
public class Customer {
private int internalId;
@Hidden(When.ONCE_PERSISTED)
public int getInternalId() {
return internalId;
}
}
The acceptable values for the parameter are: When.ALWAYS, When.NEVER, When.ONCE_PERSISTED and When.UNTIL_PERSISTED. By default the annotated property or action is always hidden i.e. it is implicitly When.ALWAYS.
The @Immutable annotation may be applied to a class. The framework does not allow the state of such objects to be changed through the UI, and it should be considered a programmer error to do so programmatically. The ObjectStorePersistor, as used to run the in-memory and Hibernate object stores will actually fail if the programmer tries to change an object in a way that cause the persistor to try and save it. For example the following class would prevent the user from changing the object even when transient:
@Immutable
public class Country {
...
}
This annotation can also take a single parameter indicating when it is to become immutable, for example the following code would allow the user to create an email object and set it up, and then prevent any changes once it has been saved.
@Immutable(When.ONCE_PERSISTED)
public class Email {
...
}
The acceptable values for the parameter are: When.ALWAYS, When.NEVER, When.ONCE_PERSISTED and When.UNTIL_PERSISTED. By default the annotated property or action is always immutable i.e. it is implicitly When.ALWAYS.
The @Mask annotation may be applied to any property, or to any parameter within an action method, that allows the user to type in text as input. The mask serves to validate, and potentially to normalise, the format of the input. The characters that can be used are based on Swing's MaskFormatter, and also Java's SimpleDateFormat. When applying a mask to a value property, the annotation should be applied to the 'getter'. For example:
public class Email {
private String telNo;
@Mask("(NNN)NNN-NNNN")
public String getTelephoneNumber() {...}
public void setTelephoneNumber(String telNo) {...}
...
}
When applying a mask to a value parameter within an action method, the annotation should be applied 'in-line' before that parameter). For example:
public void newContact(
@Named("Contact Name")
String contactName,
@Named("Telephone Number")
@Mask("(NNN)NNN-NNNN")
String telNo) {}
The @MaxLength annotation indicates the maximum number of characters that the user may enter into a String property, or a String parameter in an action. (It is ignored if applied to a property or parameter of any other type.) For example:
public class Customer {
@MaxLength(30)
public String getFirstName() { ... }
public void setFirstName(String firstName) { ... }
...
}
If the model is being persisted on a relational database then @MaxLength should be specified for all String properties and action parameters.
@MemberOrder is the recommended mechanism for specifying the order in which fields and/or actions are presented to the user. (@ActionOrder and @FieldOrder provide alternative mechanisms).
@MemberOrder is specified at the individual member level, on a 'relative' basis. The syntax is:
@MemberOrder(sequence = "<relative order>")
where <relative order> may be any string. The actual sequence is determined by comparing all the values of the sequence specifier string, using the standard String comparator.
The simplest convention is to use numbers - 1, 2, 3 - though it is a better idea to leave gaps in the numbers - 10, 20, 30 perhaps - such that a new member may be added without having to edit existing numbers. A useful alternative is to adopt the 'dot-decimal' notation - 1, 1.1, 1.2, 2, 3, 5.1.1, 5.2.2, 5.2, 5.3 - which allows for an indefinite amount of future insertion. For example:
Public Class Customer {
@MemberOrder(sequence="2.1")
Public String getAddress() {...}
Public void setAddress(value as String) {...}
@MemberOrder(sequence="1.1")
Public String getFirstName() {...}
Public void setFirstName(value as String) {...}
@MemberOrder(sequence="1.2")
Public String getLastName() {...}
Public void setLastName(value as String) {...}
@MemberOrder(sequence="3")
Public Date getDateOfBirth() {...}
Public void setDateOfBirth(value as Date) {...}
...
}
If a member does not have a specified order then it will be placed after those that are specified. Two members may have the same sequence specifier, but in such a case the relative ordering of those members will be indeterminate.
This approach is especially useful when dealing with inheritance hierarchies, as it allows sub-classes to specify where their additional members should be placed in relation to those inherited from the super-class.
Note that certain styles of user interface will lay out an object's properties and its collections separately, in which case the relative member order of properties and collections will be evaluated separately. However, since other styles of user interface may interleave properties and collections, it is safer to assume the latter.
The @MultiLine annotation provides information about the carriage returns in a String property or action parameter. The annotation indicates that:
- the String property or parameter may contain carriage returns, and
- (optionally) the typical number of such carriage returns, and
- (optionally) that the text should be wrapped (the default is that text is not wrapped).
Currently the preventWrapping functionality is not fully implemented.
This may be used by the viewing mechanism to render the property as a multi-line textbox (or text-editor when changes are permitted), with appropriate wrapping and/or scrollbars. The syntax is:
@MultiLine([numberOfLines=<typicalNumberOfCRs>] [,preventWrapping=<false|true>])
For example:
public class BugReport {
@MultiLine(numberOfLines=10)
public String getStepsToReproduce() { ... }
public void setStepsToReproduce(String stepsToReproduce) { ... }
...
}
Here the stepsToReproduce may be displayed in a text area of 10 rows, with no wrapping. A horizontal scrollbar may appear if the number of characters on any given row exceeds the width. Another example:
public class Email {
@MultiLine(numberOfLines=20, preventWrapping=false)
public String getBody() { ... }
public void setBody(String body) { ... }
...
}
Here the body should be displayed in a text area of 20 rows, with wrapping. If this attribute is combined with the <TypicalLength>, then the expected width of the text area in the user interface will be determined by the value of the typical length divided by the number of specified lines. For example:
public class Email {
@MultiLine(numberOfLines=20, preventWrapping=false)
@TypicalLength(800)
public String getBody() { ... }
public void setBody(String body) { ... }
...
}
Here the body will (likely be) displayed in a text area of 20 rows, with 40 columns.
The @MustSatisfy annotation allows validation to be applied to properties using an (implementation of a) org.nakedobjects.applib.spec.Specification object.
For example:
public class Customer {
@MustSatisfy(StartWithCapitalLetterSpecification.class)
public String getFirstName() { ... }
...
}
The Specification is consulted during validation, being passed the proposed value.
The @Named annotation is used when you want to specify the way something is named on the user interface i.e. when you do not want to use the name generated automatically by the system. It can be applied to objects, members (properties, collections, and actions) and to parameters within an action method.
Generally speaking it is better to rename the property, collection or action. The only common case where @Named is common is to rename parameters for built-in value types. Even here though a custom value type can be defined using @Value so that the value type is used as the parameter name. @Named may also be used if the name needs punctuation or other symbols in the name presented to the user.
Specifying the name of an objectBy default the name of an object is derived, reflectively from the class name. To specify a different name for an object, use the @Named annotation in front of the class declaration. For example:
@Named("Customer")
public class CustomerImpl implements Customer{
...
}
See also: @Plural.
Specifying the name of a memberBy default, the name of a member (a property, collection or action) presented to the user is derived, reflectively, from the name of the member defined in the program code. To specify a different name use the @Named annotation immediately before the member declaration. For example:
public class Customer {
@Named("Given Name")
public String getFirstName() { ... }
@Named("Family Name")
public String getSurname() { ... }
public CreditRating getCreditRating() { ... }
}
Note that the framework provides a separate and more powerful mechanism for internationalisation.
Specifying the name for an action parameterThe most common usage of @Named will be to specify names for the parameters of an action: because, by default, the user interface will use the type of the parameter as the name. (This is because the parameter name declared in the code for the action method cannot be picked up reflectively.) To specify the name of a parameter, the @Named annotation is applied 'in-line' (i.e. preceding the individual parameter declaration). For example:
public class Customer {
public Order placeOrder(
Product product,
@Named("Quantity")
int quantity) {
Order order = new Order();
order.modifyCustomer(this);
order.modifyProduct(product);
order.setQuantity(quantity);
return order;
}
...
}
This annotation indicates that transient instances of this class may be created but may not be persisted. The framework will not provide the user with an option to 'save' the object, and attempting to persist such an object programmatically would be an error. For example:
@NotPersistable
public class InputForm {
// members and actions here
}
This annotation can also take a single parameter indicating whether it is only the user that cannot persist the object, for example the following code would prevent the user from saving the object (via the viewer) but still allow the program to persist the object. By default the annotated object is effectively transient, e.g. it is implicitly By.USER_OR_PROGRAM.
@NotPersistable(By.USER)
public class InputForm {
...
}
This indicates that the property is not to be persisted. Note that in many cases the same thing could be achieved by providing the property with a 'getter' but no 'setter'. For example:
Check that this is acceptable for Hibernatepublic class Order {
private Order previousOrder;
@NotPersisted
public Order getPreviousOrder() {...}
public void setPreviousOrder(Order previousOrder) {...}
// rest of code
}
By default, the system assumes that all properties of an object are required, and therefore will not let the user save a new object unless a value has been specified for each property. Similarly, by default, the system assumes that all parameters in an action are required and will not let the user execute that action unless values have been specified for each parameter. To indicate that either a property, or an action parameter, is optional, use the @Optional annotation.
Making a property optionalTo indicate that a property is optional (i.e. that the user may save the object without necessarily specifying a value for this property), use the @Optional annotation immediately before the declaration of that property. For example:
public class Order {
public Product getProduct() { ... }
public java.util.Date getShipDate() { ... }
public void setShipDate(Date java.util.shipDate) { ... }
@Optional
public String getComments() { ... }
public void setComments(String comments) { ... }
}
Here the product and shipDate properties are both required, but the comments property is optional.
Making an action parameter optionalTo indicate that an action may be invoked without having to specify a value for a particular parameter, the @Optional annotation should be used in-line i.e. immediately before the declaration of that parameter. For example:
public class Customer {
public Order placeOrder(
Product product,
@Named("Quantity") int quantity,
@Optional @Named("Special Instructions") String instr) {
....
}
...
}
Note that the @Optionalannotation has no meaning for a primitive property (or parameter) such as int - because primitives will always return a default value (e.g. zero). If optionality is required, then use the corresponding wrapper class (e.g. java.lang.Integer).
Where Naked Objects displays a collection of several objects it may use the plural form of the object type in the title. By default the plural name will be created by adding an 's' to the end of the singular name (whether that is the class name or another name specified using @Named). Where the single name ends in 'y' then the default plural name will end in 'ies' - for example a collection of Country objects will be titled 'Countries'. Where these conventions do not work, the programmer may specify the plural form of the name using @Plural. For example:
@Plural("Children")
public class Child {
// members and actions here
}
The @RegEx annotation may be applied to any property, or to any parameter within an action method, that is a value type (i.e. that allows the user to type in text as input). It serves both to validate and potentially to normalise the format of the input. @Regex is therefore similar in use to @Mask but provides more flexibility. The syntax is:
@RegEx(validation = <regEx string>, format = <regEx string>, caseSensitive = <true|false>)
The first parameter is required; the format defaults to 'no formatting'; caseSensitive defaults to false. When applying Regex to a value property, the annotation should be applied to the 'getter'. For example:
private String email;
@RegEx(validation = "(\\w+\\.)*\\w+@(\\w+\\.)+[A-Za-z]+")
public String getEmail() {}
public void setEmail(String email) {}
When applying a RegEx expression to a value parameter within an action method, the annotation should precede that parameter:
public void newContact(
@Named("Contact Name")
String contactName,
@Named("Email")
@RegEx(validation = "(\\w+\\.)*\\w+@(\\w+\\.)+[A-Za-z]+")
String email) {}
The @TypeOf annotation is used to specify the type of elements in a collection, when it is not possible to use generics - for example when invoking an external method that does not use generics.
@TypeOf(Customer.class)
public List allNewCustomers() {
return CustomerDatabase.allNewCustomers();
}
The @TypicalLength annotation indicates the typical length of a String property or String parameter in an action. This may be used by the viewing mechanism to determine the space that should be given to that property or parameter in the appropriate view. For example:
public class Customer {
@MaxLength(30)
@TypicalLength(20)
public String getFirstName() { ... }
public void setFirstName(String firstName) { ... }
}
If the typical length is the same as the <MaxLength> then there is no need to specify <TypicalLength> as well. If the value specified is zero or negative then it will be ignored.
The @Value annotation indicates that a class should be treated as a value type rather than as a reference (or entity) type. It does this providing an implementation of a org.nakedobjects.applib.adapters.ValueSemanticsProvider.
For example:
@Value(semanticsProviderClass=ComplexNumberValueSemanticsProvider.class)
public class ComplexNumber {
...
}
The ValueSemanticsProvider allows the framework to interact with the value, parsing strings and displaying as text, and encoding/decoding (for serialization).
TODO: we should have a chapter on the methods of the DomainObjectContainer.
Services, which are automatically injected into any domain object that needs them, are just ordinary domain objects but with an getId() identity method. The method recognised is:
public String getId()
This identity method is currently used only by perspectives, when registering services. (Strictly speaking the getId() method is optional; if not present then the fully qualified class name is used as the Id).
All the other methods are processed as for domain objects, as described in a previous section.
To simplify the process of writing service objects the following abstract classes are available from within the application library:
In addition there are classes within the framework that assist building services that need to integrate with the framework.
Services are registered with the Framework at startup via the properties file, using the services properties. The services property itself dictates which service classes are to be instantiated at startup, for example:
nakedobjects.services = bom.BookingFactory, bom.CustomerFactoryAndRepository, \ bom.LocationFactoryAndRepository
The prefix property allows you to specify the common package once and, hence omit them from the classes in the list. The following set of properties is therefore equivalent to the previous example.
nakedobjects.services.prefix = bom nakedobjects.services = BookingFactory, CustomerFactoryAndRepository, \ LocationFactoryAndRepository
As well as services defined programmatically, it is possible to use a repository provided by the framework that will, for a configured object type, provide methods to:
To register such a service prefix the class name with 'repository#', for example:
nakedobjects.services = repository#bom.Booking
TODO
TODO
Fixtures are simple classes that are used to set up a naked objects system. The code for a fixture should be placed in the install method, which is run when the system installs the fixtures. Fixtures are only installed if the NakedObjectPersistor flags that it is uninitialised via its isInitialized method. For the in memory object store this will be every time it is started, and for other object stores will only be when they detect they have no persistent data.
Fixtures are registered with the Framework at startup via the properties file, using the fixtures properties. The fixtures property itself dictates which fixture classes are to be instantiated at startup, for example:
nakedobjects.fixtures = fixture.BookingsFixture, fixture.PerspectivesFixture
The prefix property allows you specify the common package name once and, hence omit them from the classes in the list. The following set of properties is therefore equivalent to the previous example.
nakedobjects.fixtures.prefix = fixture nakedobjects.fixtures = BookingsFixture, PerspectivesFixture
A bootstrapper takes care of loading up the Naked Objects framework and application, initialising it, and running it. When it starts up it reads in a list of components and attempts to load in each one. Any component that can be loaded is then available to be used and can then be specified on the command line if required. Requesting a component that cannot be loaded will result in an error. To resolve such an error simply ensure that the component's Jar file is available on the class path. All the components supplied with the framework are listed in the file installer-registry.properties which is part of the runtime-4.0.jar file.
During start-up Naked Objects loads in a number of configuration files. The main file, nakedobjects.properties, is always loaded and must be present for the framework to start up. For each of the various types of persistor and viewer, there is a separate configuration file. For example, for the following command line parameters
-v dnd -r xml
specifies that the viewer is to be the drag and drop (dnd) interface, and the persistor is to be the 'xml object store'. As well as loading those components, the framework will look for configuration files named viewer.properties, viewer_dnd.properties, persistor.xml and persistor_xml.properties and will load them if found.
The bootstrapper is the class org.nakedobjects.runtime.NakedObjects and takes the following options on the command line. (Note that in each case there is an abbreviated, and a full, version of the option).
There is also an alternative bootstrapper, org.nakedobjects.webserver.WebServer.
-c <config file> --config <config file>
Loads in the specified configuration file, in addition to nakedobjects.properties and the properties files relating to the installed component.
-s --nosplash
Prevents the splash screen from being displayed during start up.
-h --help
Prints out the options that are available. This is dynamic so that only the components that can be used are listed.
The amount of logging detail can be controlled from the command line. If no flag is specified only warnings and errors are output. All logging level relate to the Log4J levels, which is the logging mechanism used by Naked Objects.
-quiet
Quiet reduces logging to show errors only.
-verbose
Increases logging to show information about the system as it runs.
-debug
Increases logging to show all logged entries. At this level so much is logged that it will likely slow down the system if used when outputting log details to the console or other slow devices.
-t <mode> --type <mode>
The framework is started up in one of several different deployment modes, as listed below:
exploration - Exploration mode is for developers to explore and test their code. The framework always uses an in-memory persistor and runs the fixtures at startup to ensure a known state every time the system is started. The user is not prompted to log in, but is automatically logged in as user 'exploration'. The logged in user can be changed on the fly using an option with the user interface; this simply changes the user and does not require an explicit login action. Also, exploration methods defined in the DOM are also available to the user to do things that a user would not normally be allowed to do. These are used expressly for testing the system. Please note it is an error to specify a persistor type in exploration mode.
prototype - Prototype mode is for demonstrating the system in realistic fashion. The user is always prompted to log in at start up, and can log out and log in again without losing the state of the objects. This allows a user to demonstrate exactly how a system would work.
single-user - Single user mode runs the system for a single user with object persistence.
client - Client mode provides multiple users access to a server. With this mode selected the connection option must also be specified.
server-exploration - Server mode, but all clients will automatically be logged in as the 'exploration' user (or as LoginFixture is present).
server-prototype - Server mode, but if a LoginFixture is present then all clients will automatically be logged in as this user.
server - Server mode runs Naked Objects as a server for multiple clients. With this mode selected the connection option must also be specified.
If no mode is specified, the framework will start up in prototype mode.
-D property=value
Using this flag we can pass in properties from the command line that would otherwise need to specified in a configuration file. This is normally used to override a property, or to temporarily specify one.
-p <password> --password <password>
Ignored if type is prototype or not used with the user option below.
Uses the specified password when logging the user in with the username specified with --user.
-r <persistor> --persistor <persistor>
Ignored if type is client.
The persistor option allows you to choose how the objects created by the system will be persisted so they are still available the next time you run the system. The following options are available with the current distribution, and if none is specified then the in-memory persistor will be used for exploration mode and xml persistor will be used for other non-client modes by default.
in-memory - A simple non-persisting mechanism that will only hold the persisted object while the virtual machine is running. This is used for testing and demonstrating.
xml - A simple file based mechanism that encodes each object's data in an XML file. Alongside the data files for the objects are instance lists listing all the files for each type of object.
<class name> - By specifying a class name the boot loader will load up that class as the persistor.
-x <connector> --connector <connector>
Ignored if mode is not a client. If the type is a client then a connection mechanism can be specified. Connector mechanisms available are:-
encoding-sockets - Uses the encoding mechanism (of value types) to serialize/deserialize objects, over TCP/IP sockets.
encoding-http - Uses the encoding mechanism (of value types) to serialize/deserialize objects, over HTTP.
serializing-sockets - Uses Java serialization (of value types) to serialize/deserialize objects, over TCP/IP sockets.
xstream-sockets - Uses the Xstream library to serialize/deserialize objects to/from XML for simple stream communications, over TCP/IP sockets.
-u <user name> --user <user name>
Ignored if type is prototype.
Uses the specified name when logging the user in. If the password is also specified (see above) then an attempt to log in using the supplied user name and password is made; authentication failure at this point will exit the program.
-v <viewer> --viewer <viewer>
The viewer option allows you to choose the user interface that is used to access your Naked Objects application. The following options are available with the current distribution, and if none is specified then the drag and drop view will be used by default.
dnd - Drag and Drop viewer
html - Web browser viewer - accessed via standard web browser such as FireFox, Internet Explorer or Opera
encoding-sockets - Uses the encoding mechanism (of value types) to serialize/deserialize objects, over TCP/IP sockets.
encoding-http - Uses the encoding mechanism (of value types) to serialize/deserialize objects, over HTTP.
serializing-sockets - Uses Java serialization (of value types) to serialize/deserialize objects, over TCP/IP sockets.
xstream-sockets - Uses the Xstream library to serialize/deserialize objects to/from XML for simple stream communications, over TCP/IP sockets.
<class name> - By specifying a class name the boot loader will load up that class as the viewer
All images must be held in a directory called images in the working directory, or on the classpath.
Images for objects are picked up by class name in the form of <ClassName>.<extension> and will be case sensitive on Unix/Linux, but case insensitive on Windows.
The names of the images can be overriden by the reflector, which can pass back a name of an image that is found from the domain object. This provides a mechanism for objects to be shown with images that they themselves specify.
Extensions that are currently supported are .gif, .png, .jpg and .jpeg.
ClassName is either the short form or the fully qualified name of the class, and case can be important depending on the platform.
When loading images based on class, if no image is found for the current class using the above variations then the process is repeated using the name (both short and fully qualified) of the class's immediate superclass. This process repeats until there are no more superclasses.
There are a number of generic images that are used by the framework, and that can be changed to create a different look. These are:-
The names given to the download files indicate what the product is, which platform it runs on and its version. The version indicates the stage of development and how stable the system should be. The file name format is as follows:
nakedobjects-[version]-[type].[file-type]
For example:-
nakedobjects-4.0.1-for-maven.zip nakedobjects-4.0-for-ant.tar.gz nakedobjects-4.0-libs-only-with-dependencies.jar nakedobjects-4.1-source.zip
The parts of the name are:
The configuration file (nakedobjects.properties) needs to specify what resources are used by the application and what fixtures to load. These details are common to all the modes that the NOF can be run in. Additional properties can be also specified for use in specific modes, such as database connection details when using the SQL object store.
The configuration file nakedobjects.properties is always picked up. In addition other properties files will be picked up, if present, based on the type, viewer, persistor and connection command line switches with the filename matching the option. E.g.
-r (or --persistor) hibernate will cause persistor.properties and persistor_hibernate.properties to be used
-t (or --type) client will cause client.properties to be used
-v (or --viewer) dnd will cause viewer.properties and viewer_dnd.properties to be used
-x (or -- connector) xstream-sockets will cause transport.properties, transport_sockets.properties, protocol.properties and protocol_xstream.properties to be loaded; because of the way that connector is implemented it will also load persistor.properties and persistor_xstream-sockets.properties too.
In addition a particular file can be selected with the -c switch. For example
-c mysettings.properties
Each properties file can contain lists of services and fixtures as well as any specific settings (e.g. Hibernate setting). Services are defined using the nakedobjects.services property, fixtures by the nakedobjects.fixtures property.
As a shortcut you can specify the prefix for all classes listed in the services property using services.prefix and the fixtures property using fixtures.prefix.
# uncomment to hide splash
#nakedobjects.nosplash
nakedobjects.locale=en_GB
nakedobjects.services.prefix = org.nakedobjects.example.expenses
nakedobjects.services = resources.ExpenseTypeFactory \
, resources.naive.NaiveClaimRepository\
, resources.naive.NaiveEmployeeRepository\
, resources.naive.NaiveExpenseTypeRepository\
, resources.naive.NaiveItemRepository
nakedobjects.fixtures.prefix=org.nakedobjects.example.expenses.fixture
nakedobjects.fixtures=ExpenseTypes, Employees, ExampleClaim, Context
Other settings that can be set include
See the relevant section for details of settings specific to persistence, web viewer etc.
The logging.properties file allows control over the logging settings. The NOF uses log4j for logging so please refer to Apache log4j documentation.
The format for all bytes can be set, replacing the default format derived from the system, using the following property to specify a mask:
nakedobjects.value.format.byte=####
The mask is used to set up a java.text.DecimalFormat formatting object so details of the mask format can be found in the Java documentation.
This setting can be overriden for a specific field using the @Mask annotation.
The format for all dates can be set, replacing the default format derived from the system, using the following property to specify one of long, medium, short, isolong, isoshort or a mask:
nakedobjects.value.format.date=dd/MM/yy
When a mask is specified it is used to set up a java.text.SimpleDateFormat formatting object so details of the mask format can be found in the Java documentation.
This setting can be overriden for a specific field using the @Mask annotation.
The format for all date/time values can be set, replacing the default format derived from the system, using the following property to specify one of long, medium, short, isolong, isoshort or a mask:
nakedobjects.value.format.datetime=dd/MM/yy
When a mask is specified it is used to set up a java.text.SimpleDateFormat formatting object so details of the mask format can be found in the Java documentation.
This setting can be overriden for a specific field using the @Mask annotation.
The format for Bigdecimal values can be set, replacing the default format derived from the system, using the following property to specify a mask:
nakedobjects.value.format.decimal=####
The mask is used to set up a java.text.DecimalFormat formatting object so details of the mask format can be found in the Java documentation.
This setting can be overriden for a specific field using the @Mask annotation.
The format for all double values can be set, replacing the default format derived from the system, using the following property to specify a mask:
nakedobjects.value.format.double=####
The mask is used to set up a java.text.DecimalFormat formatting object so details of the mask format can be found in the Java documentation.
This setting can be overriden for a specific field using the @Mask annotation.
The format for all float values can be set, replacing the default format derived from the system, using the following property to specify a mask:
nakedobjects.value.format.float=####
The mask is used to set up a java.text.DecimalFormat formatting object so details of the mask format can be found in the Java documentation.
This setting can be overriden for a specific field using the @Mask annotation.
The format for all integers (including BigInteger) can be set, replacing the default format derived from the system, using the following property to specify a mask:
nakedobjects.value.format.int=####
The mask is used to set up a java.text.DecimalFormat formatting object so details of the mask format can be found in the Java documentation.
This setting can be overriden for a specific field using the @Mask annotation.
The format for all long values can be set, replacing the default format derived from the system, using the following property to specify a mask:
nakedobjects.value.format.long=####
The mask is used to set up a java.text.DecimalFormat formatting object so details of the mask format can be found in the Java documentation.
This setting can be overriden for a specific field using the @Mask annotation.
The format for all short values can be set, replacing the default format derived from the system, using the following property to specify a mask:
nakedobjects.value.format.short=####
The mask is used to set up a java.text.DecimalFormat formatting object so details of the mask format can be found in the Java documentation.
This setting can be overriden for a specific field using the @Mask annotation.
The format for all time values can be set, replacing the default format derived from the system, using the following property to specify one of long, medium, short, isolong, isoshort or a mask:
nakedobjects.value.format.time=ddMMyyyy hhmm
When a mask is specified it is used to set up a java.text.SimpleDateFormat formatting object so details of the mask format can be found in the Java documentation.
This setting can be overriden for a specific field using the @Mask annotation.
The format for time stamp values can be set, replacing the default format derived from the system, using the following property to specify one of long, medium, short, isolong, isoshort or a mask:
nakedobjects.value.format.timestamp=hh:mm
When a mask is specified it is used to set up a java.text.SimpleDateFormat formatting object so details of the mask format can be found in the Java documentation.
This setting can be overriden for a specific field using the @Mask annotation.
The reflector facet-decorators property specifies a list of FacetDecoratorInstaller objects that should be installed and registered with the reflector. These decorators decorate specific facets of the metamodel, allowing it change or modify their behaviour. This allows us to add internationalization, help look up and other features. Although transaction management is also achieved by decorating the reflector this is done automatically and does not need to be specified using this property. The following example adds a single decorator that provide internationalization via resource files.
nakedobjects.reflector.facet-decorators=resource-i18n
The list of users that can be switched between during exploration can be listed, separated by commas, with the following property. If no users are specified the default user "exploration" will be used and switching between users will not be possible.
nakedobjects.exploration.users=sven, dick, bob
To disable the showing of exploration menu items set the following property to false (by default exploration options are shown).
nakedobjects.exploration.show=false
Translated names, descriptions and help text for a specific language should be held in a file named in the following format:
i18n_<language code>_<country code>.properties
The language and country codes must reflect the translated language are ISO standards and are detailed in the Locale section.
Theses files must be on the root of the class path, otherwise they cannot be loaded.
Each property can have a line for each of the name, description and help text. The format for each is shown below.
<fully qualified class name>.property.<property name>.name=<translated name> <fully qualified class name>.property.<property name>.description=<translated description> <fully qualified class name>.property.<property name>.help=<translated help>
The class name must be fully qualified and the property name is the short name provided by the reflector. All the values are case sensitive. The following shows the translated name and description being specified for the property fullName, which is defined by the getFullName method.
example.dom.Contact.property.fullName.name=Nom example.dom.Contact.property.fullName.description=Le nom complet du client
TODO: presumably i18n is supported for collections, too?
Actions are specified in a similar fashion to properties but with the keyword action instead of property. Parameters within an action can also be translated by inserting parameter and a number before the keyword. Each parameter must be numbered to show its position, starting from one (1), eg parameter1, parameter2 etc.
<fully qualified class name>.action.<action name>.name=<translated name> <fully qualified class name>.action.<action name>.description=<translated description> <fully qualified class name>.action.<action name>.help=<translated help> <fully qualified class name>.action.<action name>.parameter<index>.name=<translated name> <fully qualified class name>.action.<action name>.parameter<index>.description=<translated description> <fully qualified class name>.action.<action name>.parameter<index>.help=<translated help>
The class name must be fully qualified and the action name is the short name provided by the reflector. All the values are case sensitive. The following shows the translated action name and description, and two parameter names being specified for the action createPhone, which is defined by the createPhone method.
example.dom.Contact.action.createPhone.name=Nouveau téléphone example.dom.Contact.action.createPhone.parameter1.name=Indicatif de zone example.dom.Contact.action.createPhone.parameter2.name=Nombre example.dom.Contact.action.createPhone.description=Créez un nouveau téléphone et ajoutez-le au contact actuel
The look of the viewing mechanism can be changed by using different fonts, colours and icons. All viewer properties for the drag and drop user interface have a common root of nakedobjects.viewer.dnd and for the HTML interface nakedobjects.viewer.html.
The size and location of the application within the windowing systems can be specified using the initial.size and initial.location properties. If not specified the size defaults to nearly full screen and location to near the top-left corner.
nakedobjects.viewer.dnd.initial.size = 800 x 600 nakedobjects.viewer.dnd.initial.location = 100, 200
The width of the resource icons (as shown on the desktop) can be specified using the large-icon-size property. If not specified it will default to 34 pixels.
nakedobjects.viewer.dnd.large-icon-size = 48
The amount of space allocated for the resize border on a text field can be set using the field-resize-border property. Unless set this will default to 5 pixels.
nakedobjects.viewer.dnd.field-resize-border = 3
The amount of space allocated for the resize border on a tree view can be set using the tree-resize-border property. Unless set this will default to 5 pixels.
nakedobjects.viewer.dnd.tree-resize-border = 3
To turn off exploration menus set the show-exploration property to off.
nakedobjects.viewer.dnd.show-exploration = off
The whole viewer is double buffered by default, but this can be turned off by setting the double-buffer property to off.
nakedobjects.viewer.dnd.double-buffer = off
A background logo can be added to the workspace using the logo-background properties. The image sub-property indicates that a logo should be displayed and what image to use. The size and location are then optional.
nakedobjects.viewer.dnd.logo-background.image = background.jpg nakedobjects.viewer.dnd.logo-background.size = 400 x 300 nakedobjects.viewer.dnd.logo-background.location = 100, 200
Images are normally loaded directly from the Java resources (accessed via the class path) or as files within the images directory. If necessary the loading of files can be suspended so they are only loaded from resources. This is done via the load-images-from-files property.
nakedobjects.viewer.dnd.load-images-from-files = false
nakedobjects.viewer.dnd.hpadding = 5 nakedobjects.viewer.dnd.vpadding = 5
The default image is the one used if the image file/resource that should be used cannot be found. This simply ensures that an image is available for all icons. The default image can be changed using the default-image property.
nakedobjects.viewer.dnd.default-image = square.png
The directory in which the images are to be found can be specified via the image-directory property.
nakedobjects.viewer.dnd.image-directory = graphics
The views used to render objects and collection are created by view specifications. These specifications are installed at start up and can be added to. The specification.view property lists the specifications to load.
nakedobjects.viewer.dnd.specification.view = org.nakedobjects.viewer.skylark.basic.FormSpecification, \
org.nakedobjects.viewer.skylark.basic.ListSpecification
The viewer has a set of default set of views that it loads up, these include specifications for forms, lists, tree browsers and tables. To disable this default set, normally so you can explicitly set up the specification list as above, use the specification.defaults property with the false value.
nakedobjects.viewer.dnd.specification.defaults =off
Specific field and subview types can be also specified, so they no longer use the built-in default. These all have the same property name root of nakedobjects.viewer.dnd.specification and are:-
The following example causes all logical properties to be shown using the standard text field rather than the default check box.
nakedobjects.viewer.dnd.specification.field.checkbox = \
org.nakedobjects.viewer.skylark.basic.TextFieldSpecification
Fonts can be specified using the following font property names:-
A particular font can be specified for an area by specifying
org.nakedobjects.viewer.dnd.font.<area>=<font>
Where the <area> is the name and the <font> is specified according the Font.decode() specification (see java.awt.Font). This essentially is a concatenation of the name, style and size as below:
<logical font name>-<style>-<point size>
In addition to the font the amount of space between lines of text can also be specified.
org.nakedobjects.viewer.dnd.spacing.<area>=<pixels>
The colour properties are prefixed with nakedobjects.viewer.dnd.color and take a numerical value. The most useful form is in hexadecimal where the six digits are grouped into twos to represent the red, green and blue components. The following example specifies the colours green, red and black respectively.
nakedobjects.viewer.dnd.color.normal=0x0000FF nakedobjects.viewer.dnd.color.label=0xFF0000 nakedobjects.viewer.dnd.color.text.edit=0xFFFFFF
The basic colour scheme is made up of a palette of eight colours: three primary, three secondary and black and white. These form the basis for many of the other colour properties. The colour property names are:-
The backgrounds of the application, the windows and the menus can be set with the following properties:-
In addition to the setting of a colour for all window backgrounds you can also set the colour for a specific view type.
nakedobjects.viewer.dnd.color.background.window.List=0x00ffff
The colours of the text on the menus can be specified with the following properties:-
The state of the views can be reflected by its colour. The following colour properties can be specified (only the identified property is based on the core colour scheme, all others are unique by default):-
There are a number of properties relating to text for text fields. These are the colour of text when editing it, the cursor's colour, the colour of the highlight when selecting text, and the colour of text when it has focus for editing but has not been changed yet.
The single character used to represent an entered character in a password field can be specified with the echo property, for example to change it from the default * to a hash specify the following:
nakedobjects.viewer.dnd.echo=#
The html viewer has only two properties
nakedobjects.viewer.html.debug=true
Which enables debugging on the web controller.
nakedobjects.viewer.html.encoding=UTF-8
Which enables the character set encoding used by the HTTPServletRequest. This is ISO-8859-1 by default.
The embedded web server (implemented by Jetty) has only one properties
nakedobjects.embedded-web-server.port=8080
Which specifies the port to use when prototyping.
The locale that the system works in can be set by the following property:
nakedobjects.locale = en_GB
The locale is specified in two parts separated by an underscore.
The first part is the language and is a valid ISO Language Code. These codes are the lower-case, two-letter codes as defined by ISO-639. You can find a full list of these codes at a number of sites, such as: http://www.loc.gov/standards/iso639-2/php/English_list.php The second part is the country and is a valid ISO Country Code. These codes are the upper-case, two-letter codes as defined by ISO-3166. You can find a full list of these codes at a number of sites, such as: http://www.iso.ch/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/list-en1.html
If no underscore is used then the setting is deemed to be the language.
If no locale is specified the system uses the locale specified by the operating system.
The time zone that the system works in can be set by the following property:
nakedobjects.timezone = Europe/London
The time zone is normally a geographic area and a location separated by a slash (/), such as America/New_York, Europe/Paris or Australia/Perth. These codes can be looked up online on sites such as ConvertIt.com and Date and Time Gateway.
Alternatively the time zone can be specified in relative format of the form GMT-8:00 where the relative number of hours and minutes can be specified.