Custom Code
From jVantage
jVantage provides entry points where custom programming code can be executed from within your applications. There are essentially three points in the page generation process flow where your custom code can be executed. They are:
- After a user request is received, but before jVantage generates the requested page.
- After jVantage generates a given page.
- When an input form is submitted (form validation).
There is one additional place where custom code can be executed, and that is if you choose to create a custom menu renderer. During page generation, jVantage collects all of the information that will ultimately be displayed on the user interface and inserts it into a Velocity context instance. One of the last things the jVantage does prior to returning the rendered page is convert the menu information (which may have been modified via custom code) into a rendered format so that it displays correctly on the user interface. By creating a custom renderer you can replace jVantage menus with a look and behavior of your own. For more information on this see Creating a Custom Menu Renderer.
Contents |
How to Know Which Custom Code Integration Method to Use
The following sections will help you understand when and why to choose one integration path over another. The jVantage process flow is actually quite intuitive as you can see from the activity diagram below. Despite the rather busy look of this diagram at first glance, tracing the execution path through the diagram reveals that is actually quite simple. It shows jVantage kernel behavior only as it relates to custom code entry points.
Deciding which of the three methods to use for integrating your custom code into your jVantage application is very simple.
Pre-Page Generation
If you need to generate one or more user interface pages entirely from custom code and you do not need jVantage to generate any information for that page, you should opt for pre-page generation. This means that jVantage will receive user requests and pass them directly to your custom code in the form of PageContext instances. Your custom code can insert arbitrary information into the PageContext, which jVantage will then make available on the user interface. Data is inserted into the PageContext in label = value form.
The following code sample shows some of the operations supported directly by the PageContext object.
Code Sample
/*
* TestSnapInBean.java
*/
package com.jvantage.snapin.ejb;
import com.jvantage.common.PageContext;
import com.jvantage.common.exceptions.SnapInException;
import com.jvantage.common.snapin.SnapInResponse;
/**
* This class demonstrates how to add custom programming code to jVantage applications.
*/
public class TestSnapInBean implements javax.ejb.SessionBean {
public SnapInResponse execute(PageContext pageContext) throws SnapInException {
// Get a SnapInResponse instance from the pageContext.
SnapInResponse siResponse = pageContext.getSnapInResponse();
try {
//======================================================================//
// Report errors to users on the user interface.
//======================================================================//
// Causes an error message box to display on the user interface.
siResponse.addUserErrorMessage("This is my test error message");
// Causes an informational message box to display on the user interface.
// If and error has also been added (siResponse.addUserErrorMessage(...)),
// then the informational message(s) will be displayed in the same box
// as the errors.
siResponse.addUserInfoMessage("This is my test info message");
//======================================================================//
// Add custom content to the user interface using label/value pairs.
//======================================================================//
// Adds a value to the Velocity context, making it available on the
// user interface. To access the value, simply add the variable ${MyTestTag}
// to the HTML (or XML) template. The value 'This is my test tag value.' will
// then be inserted into the user interface in its place.
siResponse.setTagValue("MyTestTag", "This is my test tag value. Set from the NetBeans tutorial.");
//======================================================================//
// Override entity attributes for display purposes.
//======================================================================//
// If there is an attribute called firstName associated with the entity being
// displayed in the current context, this will override its display label.
siResponse.setAttributeDisplayLabel("firstName", "My First Name");
// If there is an attribute called firstName associated with the entity being
// displayed in the current context, this will override its value. Note that
// this only overrides the value that is going to be displayed to the user, it
// does not change the persisted value.
siResponse.setAttributeValue("firstName", "George");
//======================================================================//
// Send email messages
//======================================================================//
// Add zero or more email messages to the reponse and jVantage will automatically
// send them. There is no need to bind to, or know anything about the details of
// the email subsystem.
EmailMessage emailMessage = new EmailMessage();
emailMessage.addToAddress("email.recipient@someorganization.org");
emailMessage.setSubject("Test Email from jVantage SnapIn Tutorial");
emailMessage.setContent("This is my test content.");
// Add an attachment.
EmailAttachment emailAttachment = new EmailAttachment();
emailAttachment.setDescription("Test attachment.");
emailAttachment.setName("AttachmentName");
emailAttachment.setPath("C:/jvantage/test.file.name");
// Set the email disposition as follows:
//
// emailAttachment.setDisposition(EmailAttachment.ATTACHMENT);
//
// - or -
//
// emailAttachment.setDisposition(EmailAttachment.INLINE);
//
// The default is EmailAttachment.ATTACHMENT, so there is no need
// to set it explicitly here.
// Add the attachment to the EmailMessage.
emailMessage.addAttachment(emailAttachment);
// Add the emailMessage to the response. jVantage will handle the rest.
siResponse.addEmailMessage(emailMessage);
//======================================================================//
// Log
//======================================================================//
// Writes the argument String to the SnapIn log as an error.
siResponse.logError("This is a snapIn error log entry.");
// Writes the argument String to the SnapIn log as a warning.
siResponse.logWarn("This is a snapIn warn log entry.");
// Writes the argument String to the SnapIn log as an information entry.
siResponse.logInfo("This is a snapIn info log entry. Set from the NetBeans tutorial.");
// Writes the argument String to the SnapIn log as a debug message.
siResponse.logDebug("This is a snapIn debug log entry. Set from the NetBeans tutorial.");
} catch (Exception e) {
throw new SnapInException("Exception", e);
}
// Return the response.
return siResponse;
}
public void ejbActivate() throws javax.ejb.EJBException, java.rmi.RemoteException { }
public void ejbCreate() { }
public void ejbPassivate() throws javax.ejb.EJBException, java.rmi.RemoteException { }
public void ejbRemove() throws javax.ejb.EJBException, java.rmi.RemoteException { }
public void setSessionContext(javax.ejb.SessionContext sessionContext) throws javax.ejb.EJBException, java.rmi.RemoteException { }
}
What is Happening
Stepping through the code, we can see the following things happening:
- The execute method is called with a PageContext argument. The PageContext instance is always non-null.
- A SnapInResponse object instance is created by calling the pageContext.getSnapInResponse().
- The user error message, "This is my test error message," is inserted into the response. When jVantage renders the page, this message will be displayed in a warning box that is typically at the top of the page.
- The user informational message "This is my test info message," is inserted into the response. When jVantage renders the page, this message will be displayed in an informational box that is typically at the top of the page. If both an error message and an informational message are inserted into the response, jVantage will render both message types within an error box. If only an informational message is inserted (there are no error messages), then the message will be displayed in an informational box.
- setTagValue() is a simple but powerful way of inserting values into the current context, and make it available on the user interface. Given the code above, embedding ${MyTestTag} into the user interface template (typically HTML) will cause the value This is my test tag value. to be displayed on the rendered page in place of the tag itself.
- The setAttribute...() methods apply to the post page generation context, referred to the section below for an explanation of these method calls.
- Email messages can be sent from within SnapIn code by adding them to the response. If a returned SnapInReponse contains email messages they are sent by the jVantage kernel automatically.
- although it is possible to access the logging subsystem directly, for convenience methods are provided to allow log entries to be inserted in the log files with virtually no overhead were extra work on the part of the developer. Inserting a string values directly into the SnapInResponse using any of the four methods, logError(), logWarn(), logInfo() and logDebug(), will cause them to be inserted into the long files when the response is returned to jVantage. Following is an excerpt from the SnapIn log which shows the from the code of the log entries resulting from the code above.
- SnapIn.log 2006/12/31 12:07:51 INFO : Preparing to execute method [execute] on SnapIn with reference name [ejb/TestSnapInBean] using PageContext. 2006/12/31 12:07:51 INFO : JNDI Name [ejb/TestSnapInBean] resolved to EJB Home class [$Proxy372]. 2006/12/31 12:07:51 DEBUG: JNDI name refers to class type [com.jvantage.snapin.ejb.TestSnapInRemoteHome]. 2006/12/31 12:07:51 ERROR: This is a snapIn error log entry. 2006/12/31 12:07:51 WARN : This is a snapIn warn log entry. 2006/12/31 12:07:51 INFO : This is a snapIn info log entry. 2006/12/31 12:07:51 DEBUG: This is a snapIn debug log entry.
Finally, the SnapInResponse is returned.
A couple other things that should be pointed out about the code sample above. First of all, the method name can be anything you desire. Many web development frameworks impose naming restrictions on custom code, whether they be form validator classes or business logic components, meaning that in order to call out to custom code you must override a specific interface and more specifically, override predetermined methods on them. This is another of the many behavioral differences between development frameworks and jVantage, which we do not consider to be a framework. This means that your custom components can expose more than one method if you wish such that if you have, for instance, an EJB that modifies customer bank accounts you can expose a method that returns a balance and one that adds and subtracts values from account, then execute them by defining a trigger for each method. jVantage places no requirements on custom code, although it is preferred (but not required) that your methods throw a SnapInException if an unhandled error occurs. If a SnapInException is received by jVantage when it attempts to execute custom code, it will attempt to show the root cause of that exception.
Another thing that should be mentioned is that pageContext.getSnapInResponse() can be called multiple times without losing changes to the SnapInResponse instance. The first time this method is called, a new instance is created. On subsequent calls, a reference to the same instance is returned. To override this behavior and forced jVantage to create a new instance of the object, you may call pageContext.getNewSnapInResponse(). This will cause any existing SnapInResponse already associated with the PageContext to be destroyed and replaced with a new instance. However, it is typically not necessary to call the getNewSnapInResponse() method at all, since getSnapInReponse() always returns a non-null, and developers will almost always want to continue working with the same instance. Given this, the above code could be written as follows:
public class TestSnapInBean implements javax.ejb.SessionBean {
public SnapInResponse execute(PageContext pageContext) throws SnapInException {
try {
pageContext.getSnapInResponse().addUserErrorMessage("This is my test error message");
pageContext.getSnapInResponse().addUserInfoMessage("This is my test info message");
pageContext.getSnapInResponse().setTagValue("MyTestTag", "This is my test tag value.");
...
Although there is no benefit in using the SnapInResponse in this way within a single method, it can be handy if multiple methods within your code require access to the response. Simply pass the PageContext instance among those methods and each one can then access and modify the response as needed.
Post-Page Generation
Post-page generation is largely the same as pre-page generation except that jVantage has already completed constructing the user interface state prior to calling your code. In this case, when jVantage calls your custom method and passes a PageContext, that instance contains all of the information that jVantage intends to display on the user interface. Since jVantage has already completed this work you can write code that simply modifies the data as needed.
In the code sample above there are two methods that specifically apply to this context, they are snapInResponse.setAttributeDisplayLabel() and snapInResponse.setAttributeValue(). These methods are only operational on post-page calls. Rather than inserting arbitrary data into the Velocity Context so that it can be accessed using a variable name as the snapInResponse.setTagValue() does, the setAttribute...() methods modify the values associated with the existing entity that is about to be displayed. If the attribute past to either of these methods does not exist on the current entity, the values are ignored and no warnings or errors are created.
A good practice for ensuring that references to specific entity attributes are correct is to use the Generate Database Constants utility to build a class that contains the attributes for all the entities in your application. By generating and leveraging this constants class, you achieve compile time validation of your references, which greatly reduces possible current and future programming mistakes.
To generate the Database Constants class, go to the application view within the jVantage development environment and select Tasks | Database Constants from the Tasks menu.
Executing this menu option causes the following code to be produced, which can then be imported into your custom library to make your code safer in terms of maintenance. As your applications evolve over time you can regenerate this class and not only use new attributes as you add them, but also continuously verify and validate those already in use. If a change to the application results in the removal of a previously existing attribute it will be discovered at compile time, thus eliminating the possibility that it will go undetected into your production environment.
/*
* DatabaseConstants_ProjectManager.java
*
* Generated: 07/30/2006 03:21:19.233
*/
package com.projectmanager.common
//=================================================================//
//=================================================================//
// Database Constants
//=================================================================//
//=================================================================//
public class DatabaseConstants_ProjectManager
{
//=================================================================//
// Application ProjectManager tables.
//=================================================================//
// PM_ASSOC table
public static final String TableName_PM_ASSOC = "PM_ASSOC";
public static final long Table_PM_ASSOC_IDValue = 9110;
public static final String TableFieldName_PM_ASSOC_ID = "ID";
public static final String TableFieldName_PM_ASSOC_NAME = "name";
public static final String TableFieldName_PM_ASSOC_FIRSTNAME = "firstName";
public static final String TableFieldName_PM_ASSOC_LASTNAME = "lastName";
public static final String TableFieldName_PM_ASSOC_DESCRIPTION = "description";
public static final String TableFieldName_PM_ASSOC_PHONEW = "phoneW";
public static final String TableFieldName_PM_ASSOC_PHOTO = "photo";
public static final String TableFieldName_PM_ASSOC_DEPT = "dept";
public static final String TableFieldName_PM_ASSOC_CREATEDBY = "createdBy";
public static final String TableFieldName_PM_ASSOC_DATECREATED = "dateCreated";
public static final String TableFieldName_PM_ASSOC_LASTMODIFIEDBY = "lastModifiedBy";
public static final String TableFieldName_PM_ASSOC_LASTMODIFIEDDATE = "lastModifiedDate";
public static final int TableFieldCount_PM_ASSOC = 12;
public static final String TableFields_PM_ASSOC[] =
{
TableFieldName_PM_ASSOC_ID,
TableFieldName_PM_ASSOC_NAME,
TableFieldName_PM_ASSOC_FIRSTNAME,
TableFieldName_PM_ASSOC_LASTNAME,
TableFieldName_PM_ASSOC_DESCRIPTION,
TableFieldName_PM_ASSOC_PHONEW,
TableFieldName_PM_ASSOC_PHOTO,
TableFieldName_PM_ASSOC_DEPT,
TableFieldName_PM_ASSOC_CREATEDBY,
TableFieldName_PM_ASSOC_DATECREATED,
TableFieldName_PM_ASSOC_LASTMODIFIEDBY,
TableFieldName_PM_ASSOC_LASTMODIFIEDDATE
};
// PM_COMPANY table
public static final String TableName_PM_COMPANY = "PM_COMPANY";
public static final long Table_PM_COMPANY_IDValue = 9111;
public static final String TableFieldName_PM_COMPANY_ID = "ID";
public static final String TableFieldName_PM_COMPANY_NAME = "name";
public static final String TableFieldName_PM_COMPANY_DESCRIPTION = "description";
public static final String TableFieldName_PM_COMPANY_ADDRESS1 = "address1";
public static final String TableFieldName_PM_COMPANY_CITY = "city";
public static final String TableFieldName_PM_COMPANY_STATE = "state";
public static final String TableFieldName_PM_COMPANY_ZIP = "zip";
public static final String TableFieldName_PM_COMPANY_PHONE = "phone";
public static final String TableFieldName_PM_COMPANY_PHOTO = "photo";
public static final String TableFieldName_PM_COMPANY_CREATEDBY = "createdBy";
public static final String TableFieldName_PM_COMPANY_DATECREATED = "dateCreated";
public static final String TableFieldName_PM_COMPANY_LASTMODIFIEDBY = "lastModifiedBy";
public static final String TableFieldName_PM_COMPANY_LASTMODIFIEDDATE = "lastModifiedDate";
public static final int TableFieldCount_PM_COMPANY = 13;
public static final String TableFields_PM_COMPANY[] =
{
TableFieldName_PM_COMPANY_ID,
TableFieldName_PM_COMPANY_NAME,
TableFieldName_PM_COMPANY_DESCRIPTION,
TableFieldName_PM_COMPANY_ADDRESS1,
TableFieldName_PM_COMPANY_CITY,
TableFieldName_PM_COMPANY_STATE,
TableFieldName_PM_COMPANY_ZIP,
TableFieldName_PM_COMPANY_PHONE,
TableFieldName_PM_COMPANY_PHOTO,
TableFieldName_PM_COMPANY_CREATEDBY,
TableFieldName_PM_COMPANY_DATECREATED,
TableFieldName_PM_COMPANY_LASTMODIFIEDBY,
TableFieldName_PM_COMPANY_LASTMODIFIEDDATE
};
...
}
This means that you can now modify the sample code above to look like this:
import com.projectmanager.common.DatabaseConstants_ProjectManager;
...
public SnapInResponse execute(PageContext pageContext) throws SnapInException {
// Get a SnapInResponse instance from the pageContext.
SnapInResponse siResponse = pageContext.getSnapInResponse();
try {
siResponse.setAttributeDisplayLabel(TableFieldName_PM_ASSOC_FIRST_NAME, "My First Name");
siResponse.setAttributeValue(TableFieldName_PM_ASSOC_FIRST_NAME, "George");
...
You now have compile-time validation for all entity references - a little extra work, but well worth it. This mechanism has been invaluable in enforcing the stability and ease of development of jVantage itself, helping establish and maintain jVantage code from one version to the next.
Form Validation
All of the behavior we have discussed regarding executing custom code in either the pre or post page generation modes also applies to form validation, and all of the SnapInResponse capabilities applicable to either of those contexts are also available to your form validation code. The main difference between validation code and other custom code is where in the process flow the code gets executed. Logically, form validation occurs at the end of the page generation cycle (although, internally it is somewhat intertwined with the mainline logic -- still, where and how form validation is handled internally does not impact your experience as a developer).
The primary behavioral difference between form validation and other custom code is the handling of errors. In the code sample above, adding a user error message simply results in that message being displayed to the user when the page is rendered. In a form validator, if an error is inserted into the response instance jVantage will view that as a failed validation. When this happens the input form is redrawn and the errors are displayed instruct the user of how to correct them. All of the input that the user has entered will be preserved on the redrawn form.
jVantage will interpolate its own errors with any custom errors generated from within your code. Each Data Dictionary type is associated with a string of error text that jVantage will use as validation error text for the field anywhere it is used throughout your applications. If a user-entered phone number fails to validate against any of the phone number dictionary type's regular expressions, the error message text the developer attached to the phone type will be read in and displayed to the user. Furthermore, variables can be inserted into the message text and jVantage will substitute them with contextual data. For instance, a dictionary type could have an associated error message such as:
${FieldInputValue} is not a valid entry for this field.
If the user entered the value foobar, the error message the user will see is:
foobar is not a valid entry for this field.
Getting jVantage to Call Custom Code - Defining a Trigger
The following trigger causes jVantage to execute the above code when an associate (PM_ASSOC) is modified.
More detailed guidance on creating triggers can be found on the NetBeans tutorial.
Returning Complex or Composite Types from Custom Code
Since jVantage utilizes the Apache Velocity template engine for rendering (merging) user interface pages, it is possible to insert complex objects into the velocity context and then access that information in a structured way using the Velocity Template Language. Velocity has a powerful set of constructs that allow composite types to be accessed similarly to the Object Graph Navigation Language (OGNL) used heavily by the Apache Tapestry framework. Furthermore, Velocity even allows direct navigation and iteration over maps, lists and even XML documents. This capability should not be underappreciated, and brings yet another dimension of flexibility and power to developers. It is well worth the time to explore the Velocity tool and learn about the many new avenues of development capabilities it brings to the table.



