C H A P T E R  4

Developing Model Components

This section assumes that you have already read Develop Your First Component found in Chapter 2, Developing Components.


Model Components

The obvious Model components are the extensible Model components. Extensible Model components are custom implementations of the Model class which are intended for specialization by application developers. The specialization by application developers will usually consist of application developers adding schema information to their application specific Models. The Sun ONE Application Framework Component Library contains a number of extensible Model components, such as QueryModelBase, WebServiceModel, SessionModel, ObjectAdapterModel, and CustomModel.

ModelComponentInfo

The ModelComponentInfo interface allows component authors to define additional metadata that is applicable to all Model components.

ExecutingModelComponentInfo

The ExecutingModelComponentInfo interface allows component authors to define additional metadata that is applicable to all Model components whose component class implements the com.iplanet.jato.model.ExecutingModel interface.

Is it possible to create a non-extensible Model component?

The answer is yes. In fact, whenever application developers create a new Model via the Model wizard, they are in fact extending an extensible Model component and creating an application-specific Model. This new application specific Model is by definition a non-extensible component. Whenever an application developer attempts to fill out a property of type ModelReference, the IDE toolset will invoke a component browser that allows the application developer to choose from a set of existing non-extensible Models. For instance when a application developers specify a DisplayField's Model Reference property, the IDE toolset presents them with a browser that allows them to select a Model.

Is it possible to create one of these non-extensible Models and add it to a library so that it can be distributed? Again, the answer is yes. See the section Developing and Distributing Non-Extensible Model, Command and ContainerView Components below.


Developing a Non-Extensible Model Component

A non-extensible Model component is a concrete Model that has been created within the IDE from an extensible Model component. It is no different from an application specific Model, except that is distributed in a JAR file and can be incorporated into multiple applications. The distribution technique is common for non-extensible Models, ContainerViews, and Commands. See the section Developing and Distributing Non-Extensible Model, Command and ContainerView Components


Developing an Extensible Model Component

This section describes how to create a new extensible Model component that acts as an adapter to an arbitrary XML document. The adapter pattern is one of the patterns which Sun ONE Application Framework Models are well suited to implement. In this example, the Model component will allow Sun ONE Application Framework Views to access arbitrary XML document data in a Sun ONE Application Framework consistent way. View developers will not need to know anything about the XML internals, or any XML specific APIs. Instead, the View developers will interact with the XML document Model as they would any other Sun ONE Application Framework Model. This hightlights one of the key aspects of Sun ONE Application Framework Model design. Sun ONE Application Framework Models are intended primarily to serve as application resources which are used by Views. For more on the relationship between Sun ONE Application Framework Views and Models see the Sun ONE Application Framework Developer's Guide.

Designing a new extensible Model is generally a non-trivial undertaking. The following example is sophisticated, yet concise enough for this guide. As with any Model, alternative designs are possible. As with any example, further refinement is encouraged for a production quality version. The objective of this section is to familiarize yourself with the mechanics of Sun ONE Application Framework extensible Model implementation.

This example introduces several additional Sun ONE Application Framework component model topics, as follows:

Key XML Document Model Design Points

This Model will not be a business delegate. Some models ARE both adapters and business delegates. For example, the Sun ONE Application Framework standard component library's JDBC SQL Query Model is both an adapter and a business delegate because it is responsible for communicating with the enterprise tier.

The XML Document Model will not be responsible for the lifecycle of the XML Document. It will assume that the application has managed to acquire the XML document. The Model does not care how the application acquires the XML document. The Model will rely on the application to place the XML document within a well defined location. The Model will access the XML document from that location, as needed.

There are several benefits to this design decision beyond just making the Model's job simpler. For one, this approach will allow > 1 XML Document Model access to the same XML Document. During the testing of the component you will see how this Document-Model cardinality will benefit application developers.

Another benefit is that it allows application developers to seamlessly leverage non-Sun ONE Application Framework infrastructure code that they might already have written to manage the document lifecycle.

This Model will limit its ambition to serving as a read-only Model. This means that the Model will support retrieval and display of XML document data, but it will not facilitate modification of document data. The implementation of full XML document update support is beyond the scope of this document. Furthermore, it is perfectly justifiable for a Model to limit its ambition to a well defined feature set, as long as the Model documentation makes it clear what is, and what is not supported. Application developers will then limit the use of the Model according to its documented usage.

Your XML Document Model component should support the following design-time functionality:

Your XML Document Model component should support the following run-time functionality:



Note - The implementation shown below will take shortcuts in the interest of brevity. The sample code contains some comments which point out areas where run time optimizations are possible, but would require more complex code beyond the scope of this exercise.



To meet these requirements, you will design and implement the following classes:

Additionally, you will implement a custom Java template which the IDE toolset will use as the basis for application specific sub-types of our XMLDocumentModel.

Finally, you will edit the mycomponents complib.xml to add the new component to the Sun ONE Application Framework component library.

Create the ModelFieldDescriptor Class

The Sun ONE Application Framework component model provides extensible Model component authors with the opportunity to specify an arbitrary implementation of the com.iplanet.jato.model.ModelFieldDescriptor interface. This is a very minimal interface. Each implementation of ModelFieldDescriptor must also be a JavaBean. Model component authors should design a ModelFieldDescriptor as a bean that can be configured by application developers to define a model field at design-time. Component authors, therefore have tremendous freedom to design model fields which can expose all the design-time configuration opportunity they want, as long as it can be expressed as a JavaBean.

In the example, your model field design-time configuration needs are trivial. The application developer needs to be able to configure each model field with an XPath expression.

1. In any Java editor, create the class mycomponents.XMLDocumentModelFieldDescriptor.

2. Implement the basic com.iplanet.jato.model.ModelFieldDescriptor interface.

3. Add a get and set method for the property XPath.

4. Add a get and set method for the property FieldClass.

This is an optional property. If populated, at run-time the Model will coerce the raw value retrieved with the XPath expression into the type specified by the FieldClass property.

After these steps, mycomponents/XMLDocumentModelFieldDescriptor.java should look as follows:

package mycomponents;

 

import java.io.*;

import java.util.*;

import com.iplanet.jato.model.*;

 

/**

*

*

*

*/

public class XMLDocumentModelFieldDescriptor extends Object

implements ModelFieldDescriptor, Serializable

{

 

public XMLDocumentModelFieldDescriptor()

{

super();

}

 

public String getName()

{

return name;

}

 

public void setName(String name)

{

this.name = name;

}

 

public String getXPath()

{

return xpath;

}

 

public void setXPath(String xpath)

{

this.xpath = xpath;

}

 

public Class getFieldClass()

{

return fieldClass;

}

 

public void setFieldClass(Class fieldClass)

{

this.fieldClass = fieldClass;

}

 

private String xpath;

private String name;

private Class fieldClass; // DO NOT change this init to a default value

}


Create the Sun ONE Application Framework Component Class

1. In any Java editor, create the class mycomponents.XMLDocumentModel.

2. Make XMLDocumentModel extend com.iplanet.jato.view.DatasetModelBase.

3. Make XMLDocumentModel implement com.iplanet.jato.view.MultiDatasetModel.

4. Implement the appropriate constructor for the component type.

All Model components must implement a no-arg constructor.

5. Add a get and set method for the property named "DocumentScope".

6. Add a get and set method for the property named "DocumentScopeAttributeName".

7. Add a get and set method for the property named "CurrentDatasetName".

This property will get a more user friendly display name "Base Dataset Path", but that work will be done in the XMLDocumentModelComponentInfo.

8. Implement the remaining methods that are required to fulfill your component specific requirements.

After these steps, mycomponents/XMLDocumentModel.java should look as follows:

package mycomponents;

import java.util.*;

import com.iplanet.jato.*;

import com.iplanet.jato.model.*;

import com.iplanet.jato.model.custom.*;

import com.iplanet.jato.util.*;

import org.w3c.dom.*;

import org.w3c.dom.traversal.*;

import org.apache.xpath.XPathAPI;

import javax.xml.transform.*;

import javax.servlet.jsp.PageContext;

 

 

/**

*

* @author component-author

*/

public class XMLDocumentModel extends DatasetModelBase implements MultiDatasetModel

{

 

public XMLDocumentModel()

{

super();

}

 

 

////////////////////////////////////////////////////////////////////////////

// Properties

////////////////////////////////////////////////////////////////////////////

 

public String getCurrentDatasetName()

{

// Add some defensive logic to ensure a valid currentDatasetName

if(currentDatasetName == null || currentDatasetName.trim().equals(""))

currentDatasetName = "/";

return currentDatasetName;

}

 

public void setCurrentDatasetName(String datasetName)

{

this.currentDatasetName = datasetName;

}

 

public int getDocumentScope()

{

return documentScope;

}

 

 

public void setDocumentScope(int documentScope)

{

this.documentScope = documentScope;

}

 

public String getDocumentScopeAttributeName()

{

return documentScopeAttr;

}

 

 

public void setDocumentScopeAttributeName(String name)

{

this.documentScopeAttr = name;

}

 

public void setDocument(Document value)

{

doc = value;

}

 

public Document getDocument()

{

if(doc == null) {

// Use the scope and attribute name to find the document

// The assumption is that the application logic has placed doc

// in the appropriate scope.

RequestContext rc = RequestManager.getRequestContext();

String attr = getDocumentScopeAttributeName();

switch (getDocumentScope())

{

case PageContext.REQUEST_SCOPE:

doc = (Document)

rc.getRequest().getAttribute(attr);

break;

case PageContext.APPLICATION_SCOPE:

doc = (Document)

rc.getServletContext().getAttribute(attr);

break;

case PageContext.SESSION_SCOPE:

doc = (Document)

rc.getRequest().getSession().getAttribute(attr);

break;

default:

throw new IllegalArgumentException(

"DocumentScope is set to an invalid value " +

getDocumentScope());

}

 

if(DEBUG)

System.out.println("XMLDocumentModel.getModel doc is " +

(doc==null?"null":"not null"));

}

return doc;

}

 

////////////////////////////////////////////////////////////////////////////

// Model Interface Methods

////////////////////////////////////////////////////////////////////////////

 

public Object getValue(String name)

{

Node node=null;

try

{

node=getNode(name);

}

catch (Exception e)

{

throw new ModelValueException("Exception getting value for "+

"field \""+name+"\"",e);

}

 

if (node==null)

return null;

 

Object result=null;

if (isTextNode(node) || isAttributeNode(node))

{

result=node.getNodeValue();

 

XMLDocumentModelFieldDescriptor descriptor=(XMLDocumentModelFieldDescriptor)

getFieldGroup().getFieldDescriptor(name);

if (descriptor.getFieldClass()!=null)

result=TypeConverter.asType(descriptor.getFieldClass(),result);

}

else

{

// Return the node as is and let the caller figure out what to

// do with it--this could've been what they actually wanted

result=node;

}

 

return result;

}

 

 

public Object[] getValues(String name)

{

NodeList nodes=null;

try

{

nodes=getNodes(name);

}

catch (Exception e)

{

throw new ModelValueException("Exception getting values for "+

"field \""+name+"\"",e);

}

 

if (nodes==null)

return new Object[0];

 

Object[] result=null;

try

{

List resultList=new LinkedList();

for (int i=0; i<nodes.getLength(); i++)

{

Node node=nodes.item(i);

if (isTextNode(node) || isAttributeNode(node))

{

Object data=node.getNodeValue();

 

XMLDocumentModelFieldDescriptor descriptor=(XMLDocumentModelFieldDescriptor)

getFieldGroup().getFieldDescriptor(name);

if (descriptor.getFieldClass()!=null)

{

data=TypeConverter.asType(descriptor.getFieldClass(),

data);

}

 

resultList.add(data);

}

else

{

// Return the node as is and let the caller figure out what

// to do with it--this could've been what they actually

// wanted

resultList.add(node);

}

}

 

result=resultList.toArray();

}

catch (Exception e)

{

throw new ModelValueException("Exception getting values "+

"for field \""+name+"\"",e);

}

 

return result;

}

 

 

 

public void setValue(String name, Object value)

{

// Ignore

}

 

public void setValues(String name, Object[] value)

{

// Ignore

}

 

////////////////////////////////////////////////////////////////////////////

// DatasetModel Interface Methods

////////////////////////////////////////////////////////////////////////////

 

protected NodeList getCurrentDatasetNodeList()

throws ModelControlException

{

if (nodeList!=null)

return nodeList;

 

if (getDocument()==null)

{

throw new ModelControlException(

"No XML document has been provided");

}

 

try

{

// Note: instead of XPathAPI, we can use CachedXPathAPI to improve

// the efficiency of this call. This requires some additional

// complexity not useful in this example, however.

// Also, we could potentially move away from use Apache-specific

// code by using the org.w3c.dom.xpath package, as long as the

// XML parser supported DOM Level 3.

nodeList=XPathAPI.selectNodeList(getDocument(),

getCurrentDatasetName());

}

catch (TransformerException e)

{

throw new ModelControlException("Exception getting NodeList for "+

"dataset \""+getCurrentDatasetName()+"\"");

}

 

return nodeList;

}

 

 

public int getLocationOffset()

{

return 0;

}

 

 

public int getLocation()

throws ModelControlException

{

Integer index=(Integer)datasetContexts.get(getCurrentDatasetName());

if (index==null)

{

// Call just to check for NodeList validity

getCurrentDatasetNodeList();

return -1;

}

 

return index.intValue();

}

 

 

public void setLocation(int value)

throws ModelControlException

{

int maxLength=getCurrentDatasetNodeList().getLength();

if (value>=maxLength || value<-1)

{

throw new ModelControlException("Location index out of "+

"range (max value = "+(maxLength-1)+")");

}

 

datasetContexts.put(getCurrentDatasetName(),new Integer(value));

}

 

 

public int getSize()

throws ModelControlException

{

return getCurrentDatasetNodeList().getLength();

}

 

 

public void setSize(int value)

throws ModelControlException

{

throw new ModelControlException("Unsupported operation; "+

"model size cannot be set");

}

 

protected boolean ensureValidDataPosition()

throws ModelControlException

{

if (getSize()==0)

return false; // No data to retrieve

else

if (getLocation()==-1)

{

// If we're currently before the first item, we need to move

// to the first item to retrieve some data

if (!first())

throw new ModelControlException("Could not move to first item");

}

 

return true;

}

 

//////////////////////////////////////////////////////////

// XML Node methods

////////////////////////////////////////////////////////////////////////////

 

public Node getNode(String fieldName)

throws ModelControlException, TransformerException

{

if (!ensureValidDataPosition())

return null;

 

Node contextNode=getCurrentDatasetNodeList().item(getLocation());

 

// Note: instead of XPathAPI, we can use CachedXPathAPI to improve

// the efficiency of this call. This requires some additional

// complexity not useful in this example, however.

// Also, we could potentially move away from use Apache-specific

// code by using the org.w3c.dom.xpath package, as long as the

// XML parser supported DOM Level 3.

Node n = XPathAPI.selectSingleNode(contextNode,getFieldXPath(fieldName));

if(DEBUG) {

if(n == null)

System.out.println("Warning: getNode found no node at[" +

getFieldXPath(fieldName) + "]");

}

return n;

}

 

public NodeList getNodes(String fieldName)

throws ModelControlException, TransformerException

{

if (!ensureValidDataPosition())

return null;

 

Node contextNode=getCurrentDatasetNodeList().item(getLocation());

 

// Note: instead of XPathAPI, we can use CachedXPathAPI to improve

// the efficiency of this call. This requires some additional

// complexity not useful in this example, however.

// Also, we could potentially move away from use Apache-specific

// code by using the org.w3c.dom.xpath package, as long as the

// XML parser supported DOM Level 3.

NodeList nl = XPathAPI.selectNodeList(contextNode,getFieldXPath(fieldName));

if(DEBUG) {

if(nl == null)

System.out.println("Warning: getNodes found no nodes at[" +

getFieldXPath(fieldName) + "]");

}

return nl;

}

 

 

public static boolean isTextNode(Node node)

{

if (node==null)

return false;

return (node instanceof CharacterData);

}

 

public static boolean isAttributeNode(Node node)

{

if (node==null)

return false;

return node.getNodeType()==Node.ATTRIBUTE_NODE;

}

 

////////////////////////////////////////////////////////////////////////////

// Helper method

////////////////////////////////////////////////////////////////////////////

public String getFieldXPath(String fieldName)

{

XMLDocumentModelFieldDescriptor descriptor=(XMLDocumentModelFieldDescriptor)

getFieldGroup().getFieldDescriptor(fieldName);

return descriptor.getXPath();

}

////////////////////////////////////////////////////////////////////////////

// Instance variables

////////////////////////////////////////////////////////////////////////////

 

private int documentScope = PageContext.REQUEST_SCOPE; // request scope by default

private String documentScopeAttr = "testDoc";

private String currentDatasetName;

private Document doc;

 

private NodeList nodeList;

private Map datasetContexts=new HashMap();

private String datasetName;

 

private static final boolean DEBUG = true;

}


Create the Extensible Component's Java Template

Extensible components serve as base classes for application defined entities. Therefore, the Sun ONE Application Framework component model provides extensible component authors the opportunity to provide a custom Java template. The IDE toolset will, subsequently, use the component supplied template to create the application specific sub-type. Component authors can utilize the custom template to enhance the application developer's experience. Component authors may prepare the component specific Java template with a set of template tokens defined in com.iplanet.jato.component.ExtensibleComponentInfo. For token details see ExtensibleComponent API.

Component authors may also utilize any arbitrary Java constructs within the Java template (for example, import statements, methods, variables, interface declarations, and so on). Minimally, the custom template will ensure that the new Java class extends from the extensible component class. Component authors may also use the template as a means of communicating to the developer documentation inline in the source so as to provide "recommended steps" or conditions or boundaries to keep in mind while specializing.

In this example, the template will be kept minimal.

In any text editor create the template mycomponents.resources.XMLDocumentModel_java.template.

The template contents should look as follows:



Note - The tokens follow a __TOKEN__ pattern.



package __PACKAGE__;

 

import java.io.*;

 

import java.util.*;

import javax.servlet.*;

import javax.servlet.http.*;

import com.iplanet.jato.*;

import com.iplanet.jato.model.*;

import com.iplanet.jato.util.*;

import mycomponents.*;

 

 

/**

*

*

* @author

*/

public class __CLASS_NAME__ extends XMLDocumentModel

{

/**

* Default constructor

*

*/

public __CLASS_NAME__()

{

super();

}

}


Create the ComponentInfo Class

The ComponentInfo class defines the design-time metadata that the IDE toolset requires to incorporate the component. In this example, you will extend an existing ComponentInfo and in true OO style, simply augment it. You could, of course, choose to implement the ComponentInfo interface from scratch, but that would be unproductive in this case.

In this example, you are going beyond the functionality revealed in the earlier component examples. Below, you are going to take advantage of a new metadata opportunity provided by the ExtensibleModelComponentInfo interface, the opportunity to describe an arbitrary Model Field type.

1. Create the class mycomponents.XMLDocumentModelComponentInfo.

2. Make XMLDocumentModelComponentInfo extend com.iplanet.jato.model.ExtensibleModelComponentInfo.

3. Implement the no-arg constructor.

4. Implement the getComponentDescriptor() method to provide the basic design-time description of the component.

5. Implement the getConfigPropertyDescriptors() method to identify which properties you want to expose in the IDE.

Note the use of default values within the ConfigPropertyDescriptor declarations.

6. Implement the getPrimaryTemplateAsStream() method to return a Java template file which you want the IDE toolset to use as the starting point for new classes derived from this extensible component.

7. Implement the getModelFieldGroupDescriptors() method to provide a design-time description of the model field type required by the Model.

Do not get confused by the extra level of indirectness suggested by ModelFieldGroupDescriptor on top of ModelFieldDescriptor. The ModelFieldDescriptor is the vital feature for you to focus on. The ModelFieldGroupDescriptor is an advanced optional feature. Suffice to say that most Sun ONE Application Framework Model components can simply make use of the standard com.iplanet.jato.model.ModelFieldGroup.

After these steps, mycomponents/XMLDocumentModelComponentInfo.java should look las follows:



Note - In this sample code, String values have been embedded directly for ease of demonstration. Utilize resource bundles if you anticipate the need to localize your display strings.



package mycomponents;

 

import java.util.*;

import java.io.*;

import com.iplanet.jato.component.*;

import com.iplanet.jato.model.*;

 

/**

*

*

*/

public class XMLDocumentModelComponentInfo extends ExtensibleModelComponentInfo

{

 

public XMLDocumentModelComponentInfo()

{

super();

}

 

public ComponentDescriptor getComponentDescriptor()

{

// identify the component class

ComponentDescriptor result=new ComponentDescriptor(

"mycomponents.XMLDocumentModel");

 

// The name will be used to determine a name for the component instance

result.setName("XMLDocumentModel");

 

// The display name will be used to show the component in a chooser

result.setDisplayName("XML Document Model");

 

// The description will be the tool tip text for the component

result.setShortDescription("A simple demonstration of a new model component");

 

return result;

 

}

 

 

public String getPrimaryTemplateEncoding()

{

/* Production version would be resource bundle driven, like this:

return getResourceString(getClass(),

"PROP_XMLDocumentModel_SOURCE_TEMPLATE_ENCODING", "ascii");

*/

 

return "ascii";

}

 

public InputStream getPrimaryTemplateAsStream()

{

/* Production version would be resource bundle driven, like this:

return XMLDocumentModelComponentInfo.class.getClassLoader().

getResourceAsStream(

getResourceString(getClass(),

"RES_XMLDocumentModelComponentInfo_SOURCE_TEMPLATE",""));

*/

 

return XMLDocumentModelComponentInfo.class.getResourceAsStream(

"/mycomponents/resources/XMLDocumentModel_java.template");

}

 

public ConfigPropertyDescriptor[] getConfigPropertyDescriptors()

{

if (configPropertyDescriptors!=null)

return configPropertyDescriptors;

 

configPropertyDescriptors=super.getConfigPropertyDescriptors();

List descriptors=new LinkedList(Arrays.asList(configPropertyDescriptors));

 

ConfigPropertyDescriptor descriptor = null;

descriptor=new ConfigPropertyDescriptor(

"documentScope",Integer.TYPE);

descriptor.setDisplayName("Document Scope");

descriptor.setHidden(false);

descriptor.setExpert(false);

descriptor.setDefaultValue(new Integer(

javax.servlet.jsp.PageContext.REQUEST_SCOPE));

descriptors.add(descriptor);

 

descriptor=new ConfigPropertyDescriptor(

"documentScopeAttributeName",String.class);

descriptor.setDisplayName("Document Scope Attribute Name");

descriptor.setHidden(false);

descriptor.setExpert(false);

descriptor.setDefaultValue("");

descriptors.add(descriptor);

 

descriptor=new ConfigPropertyDescriptor(

"currentDatasetName",String.class);

descriptor.setDisplayName("Base Dataset Path");

descriptor.setHidden(false);

descriptor.setExpert(false);

descriptor.setDefaultValue("");

descriptors.add(descriptor);

 

// Create/return the array

configPropertyDescriptors = (ConfigPropertyDescriptor[])

descriptors.toArray(

new ConfigPropertyDescriptor[descriptors.size()]);

return configPropertyDescriptors;

}

 

public ModelFieldGroupDescriptor[] getModelFieldGroupDescriptors()

{

if(null != modelFieldGroupDescriptors)

return modelFieldGroupDescriptors;

List descriptors=new ArrayList();

ModelFieldGroupDescriptor descriptor=null;

 

descriptor = new ModelFieldGroupDescriptor(

"Fields",

ModelFieldGroup.class,

new ConfigPropertyDescriptor[0],

XMLDocumentModelFieldDescriptor.class,

"addFieldDescriptor",

"setFieldGroup");

 

descriptor.setFieldBaseName("field");

descriptor.setFieldTypeDisplayName("Field");

descriptor.setGroupDisplayName("Fields");

descriptor.setFieldPropertyEditorClass(null);

descriptors.add(descriptor);

modelFieldGroupDescriptors = (ModelFieldGroupDescriptor[])

descriptors.toArray(

new ModelFieldGroupDescriptor[descriptors.size()]);

return modelFieldGroupDescriptors;

}

 

private ModelFieldGroupDescriptor[] modelFieldGroupDescriptors;

private ConfigPropertyDescriptor[] configPropertyDescriptors;

 

}


Augment the Component Library Manifest

The component manifest has already been created in the earlier example. Now you will add additional information.

Note that you will add additional types of information not seen in the prior example.

The Sun ONE Application Framework library manifest must be named complib.xml. Within the JAR file, the Sun ONE Application Framework library manifest must be placed in the /COMP-INF directory.

1. Create/Open the file COMP-INF/complib.xml.

2. Add an extensible component element to declare the XMLDocumentModel component.

After these steps, the COMP-INF/complib.xml file should look as follows:



Note - For clarity, only the significant delta to the prior version of this file shown earlier is shown here.



<?xml version="1.0" encoding="UTF-8"?>

<component-library>

<tool-info>

<tool-version>2.1.0</tool-version>

</tool-info>

<library-name>mycomponents</library-name>

<display-name>My First Component Library</display-name>

 

...

<extensible-component>

<component-class>mycomponents.XMLDocumentModel</component-class>

<component-info-class>mycomponents.XMLDocumentModelComponentInfo</component-info-class>

</extensible-component>

...

</component-library>


Recreate the Component Library JAR File

Jar up the component classes as you did in the first example, so that they can be ready for distribution as a library.

1. The name of the JAR file is arbitrary.

In this case, name it "mycomponents.jar".

2. You can omit the Java source files from the JAR.

3. You should include in the JAR any necessary ancillary resources, like icon images, or resource bundles.

In this case, there are none.

In this case, you are now including several new classes and a Java template file.

4. The mycomponents.jar internal structure should look as follows:

mycomponents/resources/SecureViewBean_java.template

mycomponents/resources/XMLDocumentModel_java.template

mycomponents/MissingTokensEvent.class

mycomponents/MyTextField.class

mycomponents/MyTextFieldComponentInfo.class

mycomponents/SecureViewBean.class

mycomponents/SecureViewBeanComponentInfo.class

mycomponents/TypeValidator.class

mycomponents/ValidatingDisplayField.class

mycomponents/ValidatingTextFieldComponentInfo.class

mycomponents/ValidatingTextFieldTag.class

mycomponents/Validator.class

mycomponents/XMLDocumentModel.class

mycomponents/XMLDocumentModelComponentInfo.class

mycomponents/XMLDocumentModelFieldDescriptor.class

mycomponents/mycomplib.tld

COMP-INF/complib.xml


Test the New Component

1. Deploy the new version of the library into your previously created test application.

Important Sun ONE Studio note: The Sun ONE Studio will not let you delete or copy over a JAR file that is currently mounted unless this is done via the IDE using ANT tasks which share the same VM as the IDE and share the file locks. You should shutting down the Sun ONE Studio whenever you need to replace one of the JAR files that is currently mounted. If you are trying to test the new version of component library in a project that is already opened inside the Sun ONE Studio, first shut down the Sun ONE Studio. Once the Sun ONE Studio has released its hold on the old copy of the library JAR file, you can copy the new version of the JAR file over the old version. After successfully deploying the new version of the library, you can re-open the application in Sun ONE Studio.

2. Create a new Model object.

If you have not done this before, complete the Sun ONE Application Framework Tutorial.

The new Model wizard should now look as follows:



Note - Depending upon the version of Sun ONE Application Framework, you might not see all of the models that are shown below. The important point is that you see the entry for "XML Document Model".



This figure shows the Select Model Type panel. 

3. Select the "XML Document Model" from the component list and complete the wizard.

Take the default settings and let the wizard create "XMLDocumentModel1" for you.

After the wizard completes, you can see that the IDE toolset has created a new class based on the component supplied template.

4. To test your mode fully, create a second XML Document Model.

Your application should now contain two XML Document Models (XMLDocumentModel1 and XMLDocumentModel2).

Note the Base Dataset Name, Document Scope, and Document Scope Attribute Name properties.

This figure shows the two created XML Document Models: XMLDocumentModel1 and XMLDocumentModel2. 

Note above that the Document Scope property value is the raw integer 2. This is because XMLDocumentModelComponentInfo declared the DocumentScope property thus. The type is Integer.Type, and the default value is javax.servlet.jsp.PageContext.REQUEST_SCOPE. The net effect in the IDE will use the default Integer property editor which will express the raw integer value, in this case 2.

descriptor=new ConfigPropertyDescriptor(

"documentScope",Integer.TYPE);

descriptor.setDisplayName("Document Scope");

descriptor.setHidden(false);

descriptor.setExpert(false);

descriptor.setDefaultValue(new Integer(

javax.servlet.jsp.PageContext.REQUEST_SCOPE));

descriptors.add(descriptor);


You would be correct in thinking that this is a poor user interface since most developers will not know that 2 corresponds to request scope. Therefore, as a follow up exercise, you will see later how to substitute a more user friendly property editor in place of the default Integer property editor.

To test your new Model component, you need a suitable XML document.

A test case will be contrived by placing an arbitrary XML file on disk, and at run time the test application will read the document from disk and place it into the request scope.

Your XML Document Model component does not care where the XML document comes from. In the real world, the XML document will probably be dynamically fetched by the application from the enterprise tier. That is of no concern to your XML Document Model.

a. In any text editor, copy the following XML into a file named "author.xml".

b. Place author.xml in the same application module directory as XMLDocumentModel1 and XMLDocumentModel2.

The code you will enter below will assume that it is in the same directory as your test Models. This is purely a convention of this exercise.

<?xml version="1.0"?>

<author>

<name first="Charles" last="Dickens"/>

<details birth="1812" death="1870"/>

<works>

<book title="Great Expectations" publisher="Penguin USA " pages="544"/>

<book title="Nicholas Nickleby" publisher="Penguin USA " pages="816"/>

<book title="A Tale of Two Cities" publisher="Signet Classic" pages="371"/>

<book title="Hard Times" publisher="Bantam Classic" pages="280"/>

<book title="Oliver Twist" publisher="Tor Books" pages="496"/>

<book title="David Copperfield " publisher="Penguin USA " pages="912"/>

<book title="A Christmas Carol" publisher="Bantam Classics" pages="102"/>

<book title="Our Mutual Friend" publisher="Indypublish.Com" pages="472"/>

<book title="Bleak House" publisher="Penguin USA " pages="1036"/>

<book title="The Pickwick Papers " publisher="Penguin USA " pages="848"/>

<book title="The Haunted House" publisher="Hesperus Press" pages="128"/>

<book title="Little Dorrit" publisher="Indypublish.Com" pages="460"/>

<book title="Barnaby Rudge" publisher="Viking Press" pages="766"/>

<book title="The Mystery of Edwin Drood" publisher="Penguin USA" pages="432"/>

<book title="Sketches by Boz" publisher="Penguin USA" pages="635"/>

</works>

</author>


c. Review author.xml for a moment.

Notice that for a single author, there are many book entries. Now is the time to point out, that you will utilize the two models (XMLDocumentModel1 and XMLDocumentModel2) to access different parts of the same XML document. You will configure XMLDocumentModel1 to access the scalar author information, name, and details. You will configure XMLDocumentModel2 to access the non-scalar collection of books.

This approach was chosen when the XMLDocumentModel was designed because it simplifies both the implementation of the Model component, and simplifies the usage of the component within an application.

5. Configure XMLDocumentModel1 to access the scalar author information.

a. Select the XMLDocumentModel1 node.

b. Edit its Document Scope Attribute Name property.

Set the value to "authorDocument".

c. Leave the Document Scope and the Base Dataset Path properties unchanged.

This figure shows the Properties of XMLDocumentModel1. 

d. Expand the XMLDocumentModel1 node so that you can see its Fields sub-node.

e. Select the Fields sub-node.

f. Right-click, and select the pop up menu's Add Field ... action.

This will automatically add a field with a default name.

In this case the default name will be "field1".

g. Repeat the previous step to create additional fields "field2", "field3", and "field4".

For the purposes of this exercise you will leave the names unchanged.

In a real application, the Model developer would probably change the field names to make them more descriptive of their role.

h. Select the field1 node.

Select the Model Field Properties tab on its property sheet.

i. Edit field1's XPath property.

Set the value to the XPath expression "/author/name/@first".

This figure shows the XMLDocumentModel1 Field1 properties. 

j. Repeat the previous step, and adjust the XPath property for the remaining three fields as follows:

6. Configure XMLDocumentModel2 to access the "books" dataset.

a. Select the XMLDocumentModel2 node.

b. Edit its Document Scope Attribute Name property.

Set the value to "authorDocument".

c. Edit its Base Dataset Path property.

Set the value to the XPath expression "/author/works/book".

That is an XPath expression that will address the collection of book entries (for example, a Sun ONE Application Framework dataset).

d. Leave the Document Scope property unchanged.

This figure shows the XMLDocumentModel2 node. 

e. Expand the XMLDocumentModel2 node so that you can see its Fields sub-node.

f. Select the Fields sub-node.

g. Right-click, and select the pop up menu's Add Field ... action to add "field1", "field2", and "field3".

h. Edit each field's XPath property to XPath expressions relative to the value of the Base Dataset Path property set above.

This figure shows the XMLDocumentModel2 field nodes. 

The models have now been configured. Now you need to create some Views to use the Models, and also provide some application logic to read the author.xml document from disk and store it in the request scope attribute "authorDocument".

First, create the Views.

Advisory: Read this next step fully before attempting, since if you follow the instructions correctly, you can save some time and effort by utilizing the full capability of the "New View" wizard. It is perfectly acceptable to create a ViewBean that will exercise the XMLDocumentModel1 without following the steps detailed below, and you can create the ViewBean according to your preferred style. But, for newcomers to the Sun ONE Application Framework, the following steps are, hopefully, the most concise.

7. Create the AuthorPage

a. Invoke the "New View" wizard.

This figure shows the Select View Type panel. 

b. Take the default values in the Associate JSP panel, and press Next.

c. In the Model Associations panel (below), expand the Current Application Components node until you find "XMLDocumentModel1".

That will cause the "XMLDocumentModel1' to appear in the Currently chosen models section of the panel.

This figure shows the Model Associations panel. 

d. In the Bind Display Fields panel (below), select all four of the Model fields that are available, and press the Add Fields button.

That will cause four entries to appear in the Bound Fields section of the panel.

e. Press Finish.

This figure shows the Bind Display Fields panel. 

After completing the wizard in the manner described above, you should find that the AuthorPage node looks like this.

The individual child display fields should be properly bound to the corresponding XMLDocumentModel1 fields.

This figure shows the AuthorPage node. 

8. Open AuthorPage.java, and add the following code to the constructor.

This code will read the "author.xml" document from disk and store it in the request scope attribute named "authorDocument", which is where the XMLDocumentModel1 expects to find it. The choice of placing this code here in the AuthorPage constructor is simply an arbitrary test stratagem. As stated before, the XMLDocumentModel does not care how or when the XML document is placed into the agreed upon attribute, as long as it is there when the Model is accessed. Note the extra import statements at the top. Also, note the getResourceAsStream method's parameter must take a parameter which reflects the name of your test application (for example, getResourceAsStream("/testmycomplib/main/author.xml").

import org.w3c.dom.*;

import org.xml.sax.InputSource;

import javax.xml.parsers.DocumentBuilderFactory;

 

/**

*

*

*/

public class AuthorPage extends BasicViewBean

{

/**

* Default constructor

*

*/

public AuthorPage()

{

super();

try {

InputSource in = new InputSource(AuthorPage.class.

getResourceAsStream("/testmycomplib/main/author.xml"));

DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance();

dfactory.setNamespaceAware(true);

Document doc = dfactory.newDocumentBuilder().parse(in);

doc.normalize(); // Make sure text in the document is in normal form

RequestManager.getRequest().setAttribute("authorDocument", doc);

System.out.println("Author XML Document has been put into request");

}

catch(Exception e) {

System.out.println("Exception trying to load author.xml" + e);

}

}


9. Compile all the classes in the application and test run the AuthorPage.

Author should appear in the browser as follows:

This figure shows the author in the browser. 

10. Create a TiledView.

Now you want to test XMLDocumentModel2 and its dataset capability.

For this you will need to create a TiledView. Essentially, duplicate the steps taken in creating AuthorPage, but select a "Basic TiledView" instead of a "Basic ViewBean" and associate it with XMLDocumentModel2 instead of XMLDocumentModel1.

Here are the detailed steps:

a. Invoke the "New View" wizard.

b. In the Select View Type panel (below) select the "Basic Tiled View" component.

c. Explicitly set the Name to be "Books".

d. Press Next.

This figure shows the Select View Type panel. 

That will cause the "XMLDocumentModel2' to appear in the Currently chosen models section of the panel.

This figure shows the Model Associations panel. 

That will cause three entries to appear in the Bound Fields section of the panel.

This figure shows the Bind Display Fields panel. 

This figure shows the Books TiledView node. 

11. The TiledView requires one additional configuration step that you have not seen before in this guide.

You need to configure the :Book TiledView's Primary Model Reference property to reference "XMLDocumentModel2". If you do not configure this property, the test run of this TiledView will show no data. That is a common Sun ONE Application Framework application developer error.

Edit the Primary Model Reference property (below), and from its drop down list select the "xmldocumentModel2" value.

This figure shows the Primary Model Reference property. 

12. Before you can test run the Books TiledView, you must add it to a ViewBean. To make this example more interesting, add the Books TiledView as a child of the AuthorPage. This is a good example of Sun ONE Application Framework's hierarchical view support.

Add an instance of Books TiledView to AuthorPage. Again, you can achieve this by selecting Books TiledView from either the Component Palette or the Component Browser, just as you did earlier with the various text fields and button components. Except this time, the component will be located in the Current Application Components section of either the Component Palette or Component Browser.

This figure shows the Component Palette.This figure shows the Books node in the Component Browser. 

This figure shows the Component Palette.This figure shows the Books node in the Component Browser. 

 

After adding Books as a child view, the AuthorPage node should look as follows:

This figure shows the AuthorPage node. 

13. You can now format the JSP associated with the AuthorPage to suit your taste.

By default when you add a container view child to a ViewBean, the IDE toolset will add the container view child and its children's tags to the ViewBean's JSP(s). The application developer may use the Synchronize to View action on the JSP node to batch remove tags for any child container view children. Take this opportunity to also add some basic formatting to the JSP so it renders more neatly.

a. Select and expand the AuthorPage's JSPs node.

b. Select the actual AuthorPage.jsp node.

c. Double-click the AuthorPage.jsp node to open the JSP file in the editor so you can edit it.

d. Add some basic formatting (for example, <p> and <br> ) to the AuthorPage.jsp so that you will get some separation between rows of Book data.

When done, the AuthorPage.jsp should look something like the following:

<%@page contentType="text/html; charset=ISO-8859-1" info="AuthorPage" language="java"%>

<%@taglib uri="/WEB-INF/jato.tld" prefix="jato"%>

 

<jato:useViewBean className="testmycomplib.main.AuthorPage">

 

<html>

<head>

<title></title>

</head>

<body>

 

<jato:form name="Author" method="post">

 

<jato:text name="field1"/>

<jato:text name="field2"/>

<jato:text name="field3"/>

<jato:text name="field4"/>

<p>

<jato:tiledView name="books1">

<br>

<jato:text name="field1"/>

<jato:text name="field2"/>

<jato:text name="field3"/>

</jato:tiledView></jato:form>

 

</body>

</html>

 

</jato:useViewBean>


14. Test run AuthorPage again.

Make sure you select the ExecutePage (Redeploy) action, not the Execute Page action. Otherwise you will not see the effects of the recent changes.

The contents of AuthorPage.jsp should show up in the browser (because the user had accumulated the required tokens).

This figure shows the contents of the AuthorPage.jsp in the browser. 

Ship It?

Not yet. Provide a custom editor for that "Document Scope" property.

Recall that the XMLDocumentModel's Document Scope property is currently vulnerable to user error, because it exposes a raw int value for direct editing. What you really need is a custom editor that presents the application developer with a fool-proof drop down list containing only valid choices. You would normally spend a little time and develop a custom property editor. The details of that are beyond the scope of this document, but can be found in any basic JavaBean reference.