Interface IPluginActionNodeHandler<TData extends BaseActionProps>

  • Type Parameters:
    TData - Type of the node's properties model, which must extend BaseActionProps. The properties themselves are stored as JSON and serialized to / deserialized from this type automatically. You should make sure this type can be serialized / deserialized via JSON. This means you should only basic field types such as number, booleans, or strings, or nested types. Furthermore, If you wish, you can also use the type JSONObject to skip serialization / deserialization and work with the raw JSON data.
    All Superinterfaces:
    IBaseActionNode<TData>, IBeanValidatingElement<TData,​WorkflowNode>, IBeanValidatingNode<TData>, ICustomParametersUpdateable, IDefaultClientHandlerNode<TData>, IElementHandler<TData,​WorkflowNode>, IExecutingLikeActionNode<TData>, IExecutionResultDescriptor, IFCPlugin, INamedUiElement, INameProviding, INodeHandler<TData>, IPluginGenericCustomGUI<IPluginWorkflowNodeBean>, IPluginWorkflowNode, IResourceBundleLocator, ISingleBaseActionNodePrototype<TData>, ISingleElementPrototype<TData,​WorkflowNode>, ISingleNodePrototype<TData>, ITransferable, IWorkflowElementTypeProviding, IWorkflowNodeFlowAnalyzer<TData>, IWorkflowNodeTypeProviding, Serializable
    All Known Implementing Classes:
    APluginActionNodeHandler

    public interface IPluginActionNodeHandler<TData extends BaseActionProps>
    extends IPluginWorkflowNode, INodeHandler<TData>, IBaseActionNode<TData>, IExecutingLikeActionNode<TData>, IDefaultClientHandlerNode<TData>, IBeanValidatingNode<TData>, ISingleBaseActionNodePrototype<TData>
    Mixin meant for IPluginWorkflowNode plugins that only wish to provide a workflow action that executes some business logic. The INodeHandler offers many methods that are irrelevant for this use case - this mixin implements most methods with the appropriate defaults.

    By default, you only need to implement execute(INodeExecutionParams) and getName(), as well as getPropertiesViewXhtmlName() for the UI. Make sure that the getName() is unique among all plugins. This name is also used as the getType() of the workflow node.

    The XHTML page with the UI for editing the action's property must be placed in the directory getPropertiesViewXhtmlPath(), with the file name getPropertiesViewXhtmlName(). You can also add properties files in getResourceBundlePath() with localized messages for the UI.

    Assume the getType() is MyActionPlugin. Then, if you do not override the defaults:

    • For each language you want to support, add a properties file in /src/main/resources/WEB-INF/properties/i18n_LOCALECODE.properties. The LOCALECODE is en for English, de for German, fr for French etc. These properties are then available in the XHTML page via the variable msg, see IElementHandler.getPropertiesViewXhtml()
    • Add an entry in the properties file for the keys MyActionPlugin.name, MyActionPlugin.desc, MyActionPlugin.label to customize the name of the plugin, the description of the plugin, and the name of the workflow action.

    You can also override most methods of this mixin to customize the action plugin. The following lists a few common use cases:

    To use a localized name or description
    Add an entry to your getResourceBundle(Locale). For the name of the plugin as it appears in the backend plugin configuration menu, use getI18nKeyDisplayName(). For the description that also appears in the backend plugin configuration menu, use getI18nKeyDescription(). For the label of the action as it appears in the workflow designer, use getI18nKeyActionLabel().
    To update the properties model for new versions of the plugin
    First make sure the MANIFEST.MF of your plugin contains the appropriate version information, see getVersion(). Then, override ICustomParametersUpdateable.updateCustomParams(de.xima.fc.interfaces.IUpdateCustomParametersParams). If you are using the SemVer (semantic version) format (i.e. your version looks like x.y.z), you may want to use the default methods provided by the mixin ISemverUpdating.
    To return a value form the exec method and have it displayed in the workflow designer
    Override getSuccessValueDescriptor(IValueDescriptorFactory) (which defines which values the exec method may return, and also contain a human-readable description for each) and adjust the return value of execute(INodeExecutionParams) accordingly.
    To provide pre-configured actions to the drawer panel to the left of the workflow designer
    Override INodeHandler.getNodePrototypes(de.xima.fc.interfaces.workflow.params.IGetNodePrototypesParams), optionally adding the list of prototypes (which contains just one item) returned by the super method implementation.
    To display the action in a different category in the workflow designer
    Override ISingleElementPrototype.getSubCategory(IGetElementPrototypesParams)
    To use a custom UI
    Create an XHTML page in getPropertiesViewXhtmlPath(); and override getPropertiesViewXhtmlName() with the name of the XHTML page. For example, if you name the XHTML page myCustomUi and do not override the XHTML path, add the file /src/main/resources/WEB-INF/ui/myCustomUi.xhtml. If you need a custom bean as a controller, also override getMainPluginBeanClass(). See IElementHandler.getPropertiesViewXhtml() for more details on the XHTML page.
    To use more than one bean
    Override getUnmanagedBeans(), but make sure to include getMainPluginBeanClass(). The additional beans can be used in XHTML pages, or referenced via Inject.
    To customize the serialization / deserialization process of the properties model
    Use the JSONField annotation on the fields of your properties model class, and/or override IElementHandler.getFastJsonConverter()

    Examples

    A simple skeletal implementation for an action that sends an HTTP post request might look like as follows. Note that you replace APluginActionNodeHandler with this interface IPluginActionNodeHandler if you need a custom super class (you only need to implement the getter getPluginInitializeData()).

     package com.example;
     
     import java.net.MalformedURLException;
     import java.net.URL;
     
     import de.xima.fc.exceptions.AbstractAbruptCompletionException;
     import de.xima.fc.interfaces.workflow.execution.INormalCompletionResult;
     import de.xima.fc.interfaces.workflow.params.INodeExecutionParams;
     import de.xima.fc.plugin.escalation.MyPostRequestPlugin.EMyPostRequestError;
     import de.xima.fc.plugin.escalation.MyPostRequestPlugin2.MyPostRequestProps;
     import de.xima.fc.workflow.mixin.APluginActionNodeHandler;
     import de.xima.fc.workflow.taglib.model.BaseActionProps;
     
     @SuppressWarnings("serial")
     public class MyPostRequestPlugin extends APluginActionNodeHandler<MyPostRequestProps> {
       public static class MyPostRequestProps extends BaseActionProps {
         private String url;
     
         public String getUrl() {
           return url;
         }
     
         public void setUrl(String url) {
           this.url = url;
         }
       }
       
       public Class<EMyPostRequestError> getErrorCodeClass() {
         return EMyPostRequestError.class;
       }
     
       @Override
       public String getName() {
         return "My Awesome Post Plugin";
       }
     
       @Override
       public INormalCompletionResult execute(INodeExecutionParams<MyPostRequestProps params) throws AbstractAbruptCompletionException {
         try {
           URL url = new URL(params.getData().getUrl());
           // todo: implement sending an HTTP post request
           // ...
         }
         catch (MalformedURLException e) {
           // Do not catch unexpected "Exception" - this is handled by the system.
           // Only catch exceptions you anticipate may occur.
           throw params.throwingException().cause(e).build();
         }
         return params.normalResult().build();
       }
     }
     

    A more advanced version with custom return values for the success and error case might look like this:

     package com.example;
     
     import java.net.MalformedURLException;
     import java.net.URL;
     import java.util.Map;
     
     import org.apache.commons.lang3.tuple.Pair;
     
     import de.xima.fc.exceptions.AbstractAbruptCompletionException;
     import de.xima.fc.interfaces.workflow.execution.INormalCompletionResult;
     import de.xima.fc.interfaces.workflow.params.INodeExecutionParams;
     import de.xima.fc.interfaces.workflow.value.IRecordValueDescriptor;
     import de.xima.fc.interfaces.workflow.value.IUnionValueDescriptor;
     import de.xima.fc.interfaces.workflow.value.IValueBuilder;
     import de.xima.fc.interfaces.workflow.value.IValueDescriptorFactory;
     import de.xima.fc.plugin.escalation.MyPostRequestPlugin.EMyPostRequestError;
     import de.xima.fc.plugin.escalation.MyPostRequestPlugin.MyPostRequestProps;
     import de.xima.fc.workflow.mixin.APluginActionNodeHandler;
     import de.xima.fc.workflow.taglib.model.BaseActionProps;
     
     @SuppressWarnings("serial")
     public class MyPostRequestPlugin extends APluginActionNodeHandler<MyPostRequestProps> {
       public static enum EMyPostRequestError {
         MALFORMED_URL,
       }
     
       public static class MyPostRequestProps extends BaseActionProps {
         private String url;
     
         public String getUrl() {
           return url;
         }
     
         public void setUrl(String url) {
           this.url = url;
         }
       }
     
       @Override
       public String getName() {
         return "My New Action Plugin";
       }
     
       @Override
       public INormalCompletionResult execute(INodeExecutionParams<MyPostRequestProps> params) throws AbstractAbruptCompletionException {
         MyPostRequestProps props = params.getData();
         try {
           // do something awesome
           Pair<Integer, String> result = sendPostRequest(props.getUrl());
           return params.normalResult().success(f -> f.asRecord() //
               .property("responseCode", result.getLeft()) //
               .property("responseMessage", result.getRight())) //
               .build();
         }
         catch (MalformedURLException e) {
           throw params.throwingException().error(EMyPostRequestError.MALFORMED_URL.name(), Map.class, b -> b.asRecord() //
               .property("message", e.getMessage()) //
               .property("invalidUrl", props.getUrl()) //
               .build() //
           ).build();
         }
         // Do not catch unexpected "Exception" - this is handled by the system.
         // Only catch exceptions you anticipate may occur.
       }
     
       private Pair<Integer, String> sendPostRequest(String urlString) throws MalformedURLException {
         URL url = new URL(urlString);
         // todo: implement sending an HTTP post request
         return Pair.of(404, "Not Found");
       }
     
       public Class<EMyPostRequestError> getErrorCodeClass() {
         return EMyPostRequestError.class;
       }
    
       @Override
       public IUnionValueDescriptor<String> getErrorValueDescriptor(IValueDescriptorFactory factory) {
         return factory.unionStringBuilder() //
             // When GENERAL error occurs, a string with the error message is returned
             .addAndUseAsDefault(EMyPostRequestError.GENERAL.name(), f -> f.string()) //
             // When the malformed URL error occurs, an JSON object with two properties is returned
             .add(EMyPostRequestError.MALFORMED_URL.name(), f -> f.recordBuilder() //
                 .requiredProperty("message", v -> v.string()) //
                 .requiredProperty("invalidUrl", v -> v.string()) //
                 .build()) //
             .build();
       }
     
       @Override
       public IRecordValueDescriptor getSuccessValueDescriptor(IValueDescriptorFactory factory) {
         return factory.recordBuilder() //
             .requiredProperty("responseCode", f -> f.integer(200)) //
             .requiredProperty("responseMessage", f -> f.string()) //
             .build();
       }
     }
     
     
    Since:
    7.0.0
    Author:
    XIMA MEDIA GmbH