# Creating Extensible Controls

You can implement your control so that it can be customized at design time. In other words, its interface isn't defined (or isn't completely defined) until it's actually being used. To do this, you implement your control so that it generates a Java control extension (JCX) file when the user adds it to a design.

## Extensible Controls Overview

A control is extensible when it generates or uses a JCX file. A JCX file is an extension of your control. To create a JCX at design time, when the user adds the control to an application, you write code that tells the IDE what the contents of the JCX should be. You can specify whether the generated interface is editable by the user or if, as with the EJB control (whose interface is defined by the EJB it represents), the interface may not be changed.

Here are a few other characteristics of controls extended with a JCX file:

- The JCX is an interface that extends your control's interface. The JCX can define new methods and callbacks, but has no implementation code.
- A control that generates a JCX does so by implementing a "control wizard" (extending ControlWizardSimple or ControlWizard) that is used by the IDE when the user is inserting a new instance of the control. The wizard collects the information needed to generate the JCX, then passes the actual JCX code to the IDE so that the file can be generated.
- When your control supports extension through a JCX, your JCS must implement the Extensible interface, including its invoke method. Your control can define a mixed interface that includes both predefined members (whose implementation is in the JCS file) and late-defined members. Late-defined members are those added when the user creates the control instance, or added by the user later. Calls to late-defined methods in a JCX are delegated to your implementation of the invoke method. There, you handle the specifics of the method call, including its parameter values and property attribute values.
- You can use the ControlContext interface's sendEvent method to invoke a specific callback declared in the generated JCX file.

As described in Creating Dialogs and Wizards for Inserting Controls, you can generate a JCX file by extending one of the control wizard classes. Your implementation receives the name that the user specified for the JCX, along with the package name. You then implement the getExtensionFileContent method to return a String containing the JCX file content.

Note: Before implementing an insert wizard, you may find it useful to create one or more JCX files by hand for debugging purposes, giving them interface definitions and annotations that your control will support. Once you have a stable shape for potential JCX files, and code that handles that shape, you can write code that generates the file, as well as user interface code (if needed) to prompt for required properties.

Whether the user has generated a new JCX file or is using an existing one, a kind of long-distance relationship exists between your core JCS code and the code contained in the JCX. You use the Java control API to manage communications between your core implementation code and the extension.

## Validating Control Name and Method Signatures

As described in Controlling Appearance and Handling Actions in Design View, you can enable users to customize the interface represented by the JCX file. If you allow methods or callbacks to be added, removed, or edited, you may also want to validate those changes. The Java control API provides two interfaces you can implement to receive notification when the control's user is attempting an edit, and to validate their changes.

- ValidateMethod — Implement this interface to have an opportunity to validate control methods as the user edits them. WebLogic Workshop calls your validateDuringCompile method implementation, passing the method's name, return type, and parameters; your implementation can validate each of these against rules you define. In addition, your implementation receives a Map object in which you can store information current at the time of validation. For example, you might want to store what you discovered about the method's parameters, get and store information about its annotations, and so on. For more information, see the next bullet point about the ValidateControl interface. Your implementation should return null if the method is valid, or return an array of objects that implement the Issue interface if it is not.
- ValidateControl — Implement this interface in order to validate the control as a whole. For example, imagine that your control isn't useful unless it has three methods with particular parameters, and each method has a specific, different annotation (such as "start", "continue" and "finish"). As the user is editing the control, adding those methods and annotating them, you can use your implementation of the ValidateMethod.validateDuringCompile method to store method-specific values in the map. When ValidateControl.validateDuringCompile is called at the end of the control's validation cycle (when the method's start parameter is false), you can retrieve the map object (context parameter), extract the accumulated values, and validate based on them.

Note that the Map object WebLogic Workshop passes to the validateDuringCompile methods exposed by these interfaces is the same instance across validation calls for a given control the user is editing. You can use the object to accumulate information and validate based on it.

Note: These methods are called for each "compilation event" and the map is the same instance for that compilation event but will be empty for the next compilation. A compilation event occurs every time the compiler checks the document's validity — such as when it's opened, for each edit, during a build, and so on. In other words: any time the compiler checks for errors to present squiggles in Source View, that's a compilation event.

Note: The two validator interfaces described here are very similar to the ValidateAttribute interface described in Creating Dialogs and Wizards for Inserting Controls. See that topic for more details.

## Implementing the invoke Method

If your control supports customization (meaning that it creates a JCX file), it must implement the Extensible interface. Your implementation of this interface's one method, invoke, is the way your core code (in the JCS file) will receive calls to methods defined in the JCX.

Note: You should handle calls to predefined methods just as you would in a control that can not be extended. For example, for control property attributes whose values are needed by such a method, you should retrieve those attribute values in that method's code.

### Handling Calls to JCX Methods

The invoke method has two parameters — one for the name of the JCX method that has been called and another for its arguments. Needless to say, your invoke implementation should be prepared to handle all of the valid method names and parameters that the user might add, and to throw an exception (ideally, ControlException) if what you receive is invalid. In other words, if your control allows the user to add multiple methods with multiple numbers of parameters in a range of types, then your invoke implementation will need to test for the possible options.

If a JCX method's logic depends on the value of a property attribute set for the method, you must get the property attribute's value within the context of the invoke method execution, as noted below.

Note: To avoid a case in which a user adds a JCX method that's inappropriate for your control, you should provide a validator class, as described above.

#### Getting the Value of Attributes Set on a JCX Method

You can use the ControlContext interface's getMethodAttributes method to retrieve property attribute values set on a particular JCX method (note that users will not be able to set properties on predefined methods because they won't be defined in the JCX file). You should be sure to retrieve these in the context of a call to invoke, or to a predefined method.

The ControlContext interface provides several other methods you can use to retrieve other bits of information specific to the current context, such as the current method being called. You can get a specific property attribute set on the control as a whole, a specific method argument, even a Class object representing the current control or callback interface.

### Calling JCX Callbacks

Just as calls to JCX methods are delegated to your invoke method implementation, so you invoke JCX callbacks through a kind of delegation.

Note: Your control may define a callback in its core code (meaning, the JAVA file that contains your control's interface) or in a JCX that extends the control. The only circumstances under which a callback may be defined in both places is when the JCX definition extends the core callback.

To execute a JCX-defined callback from your core JCS code, you can use one of three ControlContext methods:

sendEvent — This is the most direct way to invoke a JCX callback, and is something of a counterpart to the invoke method. This method has two parameters: the name of the callback you want to execute and an array containing its arguments.

scheduleEvent — Use this method to specify that a callback should execute at a particular time. In addition to the callback name and its argument, you also pass as parameters a long specifying the time at which the callback should be invoked. A fourth parameter is a boolean to indicate that (if true) an exception should not be thrown if the conversation containing the callback has finished.

raiseEvent — Use this method when you want to simply forward a callback from your control's code. You call raiseEvent from within a callback handler in your JCS; when you do, the callback is forwarded to a callback of the same name in the JCx. Note that the callback handlers must have the same name; if you call raiseEvent from within a myTimer_onTimeout handler, then the client must have a myTime_onTimeout handler defined in order to receive the callback.

## Putting It All Together

The code in the following simple example is based on the XQuery sample control available with the Control Developers Kit. For the complete working version, see the ControlDevKit sample application; there, look for the jcxCreate folder of the ControlFeatures project.

This example shows a few pieces of a control that is extensible. Its wizard class prompts the user for the name of a JCX to create, the generates the JCX code; its editor support class tells the IDE whether the JCX may be customized by the user; and its JCS code implements the Extensible interface to handle calls to methods defined in the JCX.

### Control Wizard Example

This control wizard class doesn't present a user interface because this control does not require that certain property attribute values be set up front. Instead, it main role is to receive the interface name and package name for the JCX file the user is trying to create, then use those values in constructing the content of the JCX for passing to the IDE.

    package jcxCreate.ide;

    import java.text.MessageFormat;
    import com.bea.ide.control.ControlWizardSimple;
    import javax.swing.JComponent;

    /*
     * Represents an insert dialog for the XQuery control. While
     * this dialog doesn't provide any special user interface, it
     * does provide the text that the IDE should insert into
     * newly created JCX files.
     */
    public class XQueryWizard extends ControlWizardSimple
    {
        private boolean m_createExtension = false;

        String m_jcxPackage = null;
        String m_jcxName = null;

        /*
         * Tell the IDE how to configure the insert dialog. This dialog
         * should provide an option to use an existing JCX file, or to
         * create a new one.
         */
        public int getConfigurationInfo()
        {
            return CONFIG_CREATE_EXTENSION_FILE |
                CONFIG_INSERT_INSTANCE;
        }

        /*
         * Provide a place for the IDE to tell this code whether the
         * user has asked to create a new JCX or not.
         */
        public void setConfiguration(int config)
        {
            m_createExtension =
                ((config & ControlWizardSimple.CONFIG_CREATE_EXTENSION_FILE) != 0);
        }

        /*
         * Tell the IDE what to use for custom insert dialog user interface.
         * None is needed here.
         */
        public JComponent getComponent()
        {
            return null;
        }

        /*
         * Provide a place for code that should execute when the user clicks the
         * Create button on the insert dialog.
         */
        public boolean onFinish()
        {
            if (super.onFinish() == false){
                return false;
            } else {
                return true;
            }
        }

        /*
         * Provides a way for the IDE to pass in the package name
         * that should be specified at the top of a new JCX file.
         */
        public void setPackage(String packageName)
        {
            m_jcxPackage = packageName;
        }

        /*
         * Provides a way for the IDE to pass in the name of the
         * interface defined in a new JCX file.
         */
        public void setName(String name)
        {
            m_jcxName = name;
        }

        /*
         * Provides a place for the IDE to retrieve the content that it
         * should insert into newly created JCX files. Here, the package name
         * and interface name passed in by the IDE are inserted into a
         * template using the MessageFormat class.
         */
        public String getExtensionFileContent(){
            // did they ask for an extension?
            String jcxTemplate = this.getTemplate();

            String jcxContent =
                MessageFormat.format(jcxTemplate, new Object[] {m_jcxPackage, m_jcxName});

            return jcxContent;
        }

        /*
         * Returns a template for text to be inserted into a new JCX file.
         */
        private String getTemplate(){
            String template =
                "package {0}; \n\n " +
                "import com.bea.control.*; \n " +
                "import com.bea.xml.XmlCursor; \n " +
                "import jcxCreate.XQuery; \n\n " +

                "public interface {1} extends XQuery, com.bea.control.ControlExtension \n " +
                "'{' \n \n" +
                "    /* \n" +
                "     * A version number for this JCX. You would increment this to ensure \n" +
                "     * that conversations for instances of earlier versions were invalid. \n" +
                "     */ \n" +
                "    static final long serialVersionUID = 1L; \n \n" +

                "    /* Replace the following method with your own. Be sure \n" +
                "     * that your method returns an XmlCursor and that its \n" +
                "     * parameter is a File object to contain the XML against \n" +
                "     * which to execute the XQuery expression. \n" +
                "     */ \n \n" +

                "    /** \n" +
                "     * @jc:query expression=\"$input/purchase-order/line-item[price <= 20.00]\" \n" +
                "     */ \n" +
                "     XmlCursor selectLineItem(String filePath); \n \n" +

                "'}' \n ";
            return template;
        }

    }

### Editor Support Example

For the purposes of this example, the key point of interest in this editor support class is the fact that it returns Boolean.TRUE when asked by the IDE whether the user may edit or add JCX methods — and Boolean.FALSE for editing callbacks.

This editor support class also specifies what to do when the user asks to edit a method property. Be sure to see the working sample application for the simple user interface code provided.

    package jcxCreate.ide;

    import com.bea.ide.control.ControlAttribute;
    import com.bea.ide.control.EditorSupport;
    import com.bea.ide.control.ControlMethod;
    import com.bea.ide.control.ControlBehaviorContext;
    import com.bea.ide.control.DefaultEditorSupport;

    /*
     * Represents IDE support for XQuery controls added to a project.
     * An "editor support" class specifies various "behaviors" within the IDE.
     * These include what to do when the user double-clicks on control methods,
     * when the user attempts to add or edit methods and callbacks, and what icon
     * to display on a method.
     */
    public class XQueryEditorSupport extends DefaultEditorSupport
    {
        /*
         * Provides a way for WebLogic Workshop to retrieve the behavior associated
         * with specified actions in the IDE. Each action has a corresponding
         * constant in the EditorSupport interface, which is implemented by the
         * DefaultEditorSupport class this class extends. A ControlBehaviorContext
         * argument specifies whether the context of the action is a method, attribute,
         * interface, instance, and so on.
         */
        public Object getBehavior(String behavior, ControlBehaviorContext context)
        {
            /*
             * If the user asks to edit a JCX method, return true.
             */
            if (behavior.equals(EditorSupport.BEHAVIOR_EDIT_METHOD))
            {
                return Boolean.TRUE;
            }
            /*
             * If the user asks to edit a callback, return false. XQuery JCX files do
             * not support callbacks.
             */
            else if (behavior.equals(EditorSupport.BEHAVIOR_EDIT_CALLBACK))
            {
                return Boolean.FALSE;
            }
           /*
             * If the user clicks an edit ... in the Property Editor, and if the attribute
             * corresponding to the ... is expression, return an instance of the expression
             * edit dialog box.
             */
            else if (behavior.equals(EditorSupport.BEHAVIOR_ATTRIBUTE_EDITOR))
            {
                if (context instanceof ControlAttribute && ((ControlAttribute)context).getName().equals("expression"))
                {
                    return new QueryExpressionEditorSimple(((ControlAttribute)context).getValue());
                }
            }
            return super.getBehavior(behavior, context);
        }
    }

### JCS Example

Here's the core code for this control. Note that it implements the Extensible interface. The control is designed to be customized with methods that execute XQuery expressions (similar to the way that the Database control executes SQL expressions); the invoke method receives calls to JCX methods, grabbing the expression text as a property attribute of the method that has been called.

    package jcxCreate;

    import com.bea.control.*;
    import com.bea.xml.XmlCursor;
    import com.bea.xml.XmlObject;
    import java.lang.reflect.Method;
    import java.lang.reflect.InvocationTargetException;
    import java.io.File;
    import java.io.InputStream;
    import java.io.FileInputStream;

    /**
     * The XQuery control illustrates how to create a control
     * that generates a JCX file. A JCX file enables the control's
     * user to customize the control's interface.
     *
     * This control also includes a dialog for editing an attribute
     * containing the XQuery expression. Each method of the control's
     * extended interface is annotation with this attribute.
     *
     * @jcs:control-tags file="XQuery-tags.xml"
     * @jcs:jc-jar label="XQuery"
     *      insert-wizard-class="jcxCreate.ide.XQueryWizard"
     *      version="0.8"
     *      icon-16="/images/xml.gif"
     *      palette-priority="5"
     *      group-name="Feature Sample Controls"
     *      description="Extensible control that creates a jcx and runs an XQuery."
     *      @editor-info:code-gen control-interface="true"
      */
    public class XQueryImpl implements XQuery, com.bea.control.Extensible, com.bea.control.ControlSource
    {
        /**
         * @common:context
         */
        com.bea.control.ControlContext context;

        /*
         * A String to hold the XQuery expression specified by the control's
         * user.
         */
        String m_expression = null;

        /*
         * Constants representing annotation and attribute names. The
         * jc:query annotation will be written into the JCX file for
         * each XQuery expression the control's user associates with a
         * control method.
         */
    	public static final String TAG_XQUERY = "jc:query";
    	public static final String ATTR_EXPRESSION = "expression";

        /*
         * The invoke method is implemented from the Extensible interface.
         * It is a key aspect of any control that provides a customizable
         * interface. When a method on a control's JCX file is called, that
         * call is delegated to the invoke method. The name and arguments for
         * the JCX method called are passed to invoke as arguments. Within
         * the invoke method, you extract and process the method and arguments,
         * returning a value if appropriate.
         *
         * A JCX file for the XQuery control would contain methods whose argument
         * is an object containing the XML on which to execute the query. An
         * annotation on that method would specify the XQuery expression to use.
         */
        public Object invoke(Method method, Object[] args) throws Throwable {
            XmlCursor resultCursor;
            try{
                 // Create an instance of the class designed to handle the XQuery.
                XQueryUtil queryUtil = new XQueryUtil();

                // Retrieve the XQuery expression from the annotation in the JCX.
                m_expression = context.getMethodAttribute(TAG_XQUERY, ATTR_EXPRESSION);

                // Get the XML Stream named by the first parameter.
                // First look to see if it is a resource in the jar that will be created from this project.
                // Any non-source files in this project will be moved to the jar by default
                String filePath = new String().valueOf(args[0]);
                InputStream iXml= null;
                if (filePath.substring(0,1).equals("/"))
                    iXml= this.getClass().getClassLoader().getResourceAsStream(filePath);
                    // try as a file
                if (iXml==null)
                {
                    File f=new File(filePath);
                    if (f.exists())
                        iXml = new FileInputStream(f);
                    else
                    {
                        throw new ControlException("file not found at " + f.getAbsolutePath());
                    }
                }
                // Load the XML using the XMLBeans API.
                XmlObject instanceDoc = XmlObject.Factory.parse(iXml);

                /*
                 * Pass the value to the XQuery utility class, and return the results
                 * in an XMLBeans XmlCursor object.
                 */
                resultCursor = queryUtil.runXQueryExpression(instanceDoc, m_expression);
                return resultCursor;
            } catch (InvocationTargetException ite) {
                throw new ControlException("Error while executing the query: " + ite.getMessage(), ite);
            } catch (Exception e) {
                throw new ControlException("XQueryControl: Exception while executing expression.", e);
            }
        }

        /*
         * Implement onException for any unhandled exceptions.
         */
        public void context_onException(Exception e, String methodName, Object [] args){
            throw new ControlException("XQuery control exception in " + methodName + ": ", e);
        }
    }

Related Topics

None.