Document Properties
Kbid
2D5406
Last Modified
13-Oct-2021
Added to KB
06-Dec-2013
Public Access
Everyone
Status
Online
Doc Type
Concepts
Product
  • ICM 7.10
  • ICM 11
Concept - Pipeline Nodes

Introduction

Basically, pipelines may be considered as directed graphs whereas pipeline nodes represent the vertices and transitions act as edges. The pipeline nodes can be divided in two categories: The ones provided by the pipeline framework itself and pipelets which are extensions of the pipeline framework and have to extend from a particular framework class. The former embody the basic programming constructs to model program flows such as loops, conditional statements etc. and, as such, make up the core of pipelines as a programming language. The latter are meant as access points to business functionality and, thus, bring the business aspect into pipelines. A combination of  instances of both node types and the transitions between them within a pipeline represent a step within a business interaction. At pipeline level, pipelets represent basic units of business funcionality. A pipelet implementation itself should encode as less business functionality as possible but re-use already existing functional blocks. Ideally, these functional blocks are business objects but programming artifacts from lower levels such as POs and their managers may be found there as well.

Disadvantages of Pipelets

Over the years a few drawbacks of pipelets have become visible. Pipelets can only act as nodes within pipelines but not as start or end nodes. They may only have one input connector and one output connector apart from an optional error connector whereas the latter cannot have its own set of parameters. As a result, functional blocks that would have better been encapsulated in one pipeline node, have been spread over several pipelets and have lead to rather blown up pipelines. From a business view, it is often hard to get the gist of such pipelines whilst their maintenance and further development often turns out to be rather tedious and complex. That way the original intention of the pipeline concept has become compromised and the pipeline programming language has often been abused as a rather low-level programming language.

Benefits of Pipeline Nodes

Pipeline Nodes are a means to bring pipelines back on track by allowing a developer to introduce their own nodes. Pipeline nodes are not a new concept but, up to now, with pipelets a developer has only been allowed to contribute to one type of them. Now, a developer can extend the pipeline framework with nodes that can take up any position in a pipeline and may have more than one input or output connectors whereas each connector may have its own parameter set.

The pipeline node programming model is all about leaving the developer as much freedom as possible and only relies on a few Java annotations. There is no explicit meta model as with pipelets or an abstract class that needs to be extended.

Pipeline Nodes

A class meant to be a pipeline node implementation needs to be annotated with  c.i.b.pipeline.capi.annotation.PipelineNode:

import com.intershop.beehive.pipeline.capi.annotation.PipelineNode

@PipelineNode
public class SomePipelineNode
{
	...
}

In case the pipeline node shall have a name other than the simple class name of the implementing class, the annotation needs to be augmented with a name attribute:

@PipelineNode(name="SomePipelineNodeName")
public class SomePipelineNode
{
	...
}

The name of a pipeline node is used by the pipeline engine as key when the node is registered on server startup and by the pipeline editor in Intershop Studio. It has to be unique within the pipeline node set the node is part of which is actually its enclosing cartridge.

The annotation @PipelineNode has two additional attributes:

  1. transactional:boolean: This attribute indicates if a pipeline node is transactional, i.e., if it has to be carried within a transaction. Its default value is  false.
  2. type:c.i.b.p.capi.annotation.PipelineNode.Types: This attribute determines whether a node implemenation is a start node (StartNode), end node ( EndNode) or a normal node (Node), i.e., a node within the pipeline structure. Its default value is  Node.

There are certain criteria a class implementing a pipeline node has to fulfill:

  • It must not inherit from another class.
  • The class must be public.
  • It does not need a constructor. In case it provides one, the constructor must either be parameterless or obtain its parameter values via dependency injection. The constructor must be public.
  • As with pipelets, a pipeline node class must be stateless since a single instance may be used by several parallel requests.

In most cases pipeline node classes probably do not need constructors. Instance variables holding references to managers and the like can be initialized via dependency injection.

Additional Attributes for Pipeline Start Nodes (since 7.6)

Starting with Intershop 7.6 additional attributes were introduced to configure start nodes. The following attributes are supported:

  • The attribute CallMode represents the visibility of a start node. These values are possible:
    • PRIVATE: The start node is not reachable for HTTP or Web Adapter requests.

    • PUBLIC: The start node is reachable for HTTP requests.

    • INCLUDE: The start node is reachable for Web Adapter include requests only.

  • The attribute Strict

    • Set the value to false, if a non-strict start node is necessary.

    • If not set the start node's attribute strict is true by default.

@PipelineNode(name = CallbackAfterCheckoutStartNode.NAME, type = Types.StartNode, attributes = { 
                @Attribute(name = StartNodeTypeConstants.ATTRIBUTE_NAME_CALL_MODE, value = StartNodeTypeConstants.PUBLIC),
                @Attribute(name = StartNodeTypeConstants.ATTRIBUTE_NAME_STRICT, value = "False")})
public class CallbackAfterCheckoutStartNode
{
	...
}

Input and Output Connectors

A pipeline node may have more than one input. The counterpart to inputs or input connectors at Java level are simple methods. To declare a method as pipeline node input it has to be annotated with c.i.b.pipeline.capi.annotation.PipelineNodeInput:

@PipelineNode
public class SomePipelineNode
{
	@PipelineNodeInput
	public Output execute(Input input)
	{
		...
	}
}

The input connector appears in the pipeline editor with the same name as its associated input method. The developer may choose another name by providing a value for the name attribute of the annotation:

@PipelineNodeInput(name="in")
public Output execute(Input input)
{
	...
}

Output connectors are represented by object fields. The respective annotation here is  c.i.b.pipeline.capi.annnotation.PipelineNodeOutput. Again, the developer may explicitly define the name of the output connector in the pipeline editor by supplying the name attribute, otherwise a fallback to the field name applies.

@PipelineNode
public class SomePipelineNode
{
	@PipelineNodeOutput(name="Next")
	Next next;
	@PipelineNodeOutput(name="Error")
	Error error;
	...
}

If input methods return a value, it is expected to be one of the object fields marked as pipeline node output. As each of these fields is assocciated with an output connector, the pipeline run-time can detect which output a pipeline node instance returns when a pipeline is carried out.

@PipelineNode
public class SomePipelineNode
{
	@PipelineNodeOutput
	Next next;
	@PipelineNodeOutput
	Error error;
	@PipelineNodeInput
	public Object execute(Input input)
	{
		...
		if (<condition>)
		{
			return next;
		}
		else
		{
			return error;
		} 
	}
}

If a pipeline node is not supposed to have any output connectors, like a stop node, the input methods do not need to return a value; they can either return void as return type or just return  null.

When a pipeline node has more than one input, the several inputs should adhere to a logical concept embodied by the node. This concept can most likely be considered as a state machine with one main input connected to the initial state and the other inputs leading from the initial state to subsequent states or from subsequent states back to the initial state. The already existing loop node is a good example for such a state machine. It has one main input and a second input returning back from the logic within the loop to the initial state.


The following code snippet roughly depicts how the basic code structure of the loop node would look like if it would be implemented with the new pipeline node concept:

@PipelineNode
public class LoopNode
{
    @PipelineNodeOutput
    private Body body;
 
    @PipelineNodeOutput
    private Output output;
 
    @PipelineNodeInput
    public Object loop(Loop loop)
    {
		...
    }
 
    @PipelineNodeInput
    public Object next(Next next)
    {
		...
    }
}

Parameter Interfaces

From a pipeline node developer point of view, Parameter Interfaces are the means to declare the parameters of pipeline node connectors. First of all, these interfaces are plain Java interfaces that are interpreted by the pipeline run-time to determine which parameters have to be passed on from the node execution environment into the node and which parameters the node returns. The interfaces are introspected according to the JavaBeans Specification, whereas the properties therein are the same as the parameters of a parameter interface. The following interface

public interface Input
{
	String getName();
	boolean isAvailable();
}

is equivalent to the parameter set  {Name, Available}.

This interface:

public interface Output
{
	void setProductBO(ProductBO product);
	void setCatalogBO(CatalogBO catalog);
}

is equivalent to {ProductBO, CatalogBO}.

To provide a pipeline node input with parameters the respective input method needs to declare exactly one argument whose type must be an interface. The pipeline run-time then ensures that the method is served with an object matching the interface and holding the required parameter values when it is invoked. In case a pipeline node input does not need any parameters, the respective input method may indicate this fact by either having no argument or by specifying one argument whose type is an empty interface.

Pipeline node outputs, i.e., their respective object fields, must have interfaces as types. If an output does not have any parameters, the respective interface has to be empty. Again, the pipeline run-time is responsible to properly initialize the object fields representing node outputs. A node implementation must never change the value of these fields! Similar to the fixture of test cases, the node output object fields will be newly-initialized each time an input method is executed. Although they are object fields, their values can be regarded as local in the scope of the method invocation, i.e., even if the belong to the same node instance, their accessor methods may return different values per method run.

To declare a parameter as optional the respective method has to be annotated with  javax.annotation.Nullable. Input parameters are defined by accessor methods and output parameters by mutator methods. Both types can be mixed within one interface:

public interface IO
{
	ProductBO getProductBO();
	void setProductBO(ProductBO product);
	@Nullable
	CatalogBO getCatalogBO();
	@Nullable
	void setCatalogBO(CatalogBO catalog);
}

IO can be used as interface for input method arguments and node output object fields.

Coming back to the loop node example of the previous section, here is a state diagram with named connectors as well as their parameters:

and here the full implemenation of the node showing the usage of parameter interfaces:

@PipelineNode
public class LoopNode
{
    @PipelineNodeOutput
	private Body body;

    @PipelineNodeOutput
    private Output output;

    @PipelineNodeInput
    public Object loop(Loop loop)
    {
		Iterable<?> i = loop.getIterable();
		return iterate(i.iterator());
    }

    @PipelineNodeInput
    public Object next(Next next)
    {
        return iterate(next.getIterator());
    }

    private Object iterate(Iterator<?> i)
    {
        if (!i.hasNext()) return output;
	
		body.setEntry(i.next());
		body.setIterator(i);
		return body;
    }

	interface Loop
	{
		Iterable<?> getIterable();
	}

	interface Next
	{
		Iterator<?> getIterator();
	}

	interface Body
	{
		void setIterator(Iterator<?> i);
		void setEntry(Object entry);
	}

	interface Output
	{
		// empty
	}
 }

Configuration Parameters

To add a configuration parameter to a pipeline node implemenation one has to add an object field to the node class and annotate that field with c.i.b.pipeline.capi.annotation.PipelineNodeParameter. As for the parameter name, the same pattern as with the node names, input names and output names applies here, i.e., to have a name other than the field name one must add a value for the name attribute of the annotation. If a parameter is optional, needs to be indicated with javax.annoation.Nullable. Configuration parameter can be simply served with default value by adding default values to their fields in the node implementation class. Multiple configuration parameters are declared by using  java.util.List as type of the respective fields. Here, the value of the list type parameter is used as type of the configuration parameter. The following code snippet gives an overview of pipeline node configuration parameters:

@PipelineNode
public class SomePipelineNode
{
	enum TestEnum { A, B, C; }

    @PipelineNodeParameter
    String stringParameter;
    @PipelineNodeParameter @Nullable
    String optionalStringParameter;
    @PipelineNodeParameter
    String stringParameterWithDefaultValue = "Default Value";
    @PipelineNodeParameter
    boolean booleanParameter;
    @PipelineNodeParameter
    int intParameter;
    @PipelineNodeParameter
    Boolean booleanWrapperParameter;
 	@PipelineNodeParameter
	Integer intWrapperParameter;
    @PipelineNodeParameter   
    TestEnum enumParameter;
    @PipelineNodeParameter
    TestEnum enumParameterWithDefaultValue = TestEnum.C;
    @PipelineNodeParameter @Nullable
    TestEnum optionalEnumParameter;
    @PipelineNodeParameter
    Class<?> classParameter;
    @PipelineNodeParameter @Nullable
    Class<?> optionalClassParameter;
    @PipelineNodeParameter
    List<String> multipleStringParameter;
    @PipelineNodeParameter
    List<String> multipleStringParameterWithDefaultValue = Arrays.asList("a", "b", "c");
	...
 }

Here are the currently supported configuration parameter types:

  • java.lang.String
  • java.lang.Byte
  • java.lang.Character
  • java.lang.Short
  • java.lang.Integer
  • java.lang.Long
  • java.lang.Float
  • java.lang.Double
  • All sub types of java.lang.Enum<E extends Enum<E>>
  • All sub types of  java.lang.Class<?>
  • All sub types of c.i.b.pipeline.capi.PipelineElementReference
  • Lists of the aforementioned types. The list type itself must be java.util.List

Pipeline Element References

Configuration parameters of type PipelineElementReference come into play when a pipeline node needs to refer to other code artifacts somehow involved in the pipeline execution. Typical examples are ISML templates, webforms, content page types or pipeline themselves.

A pipeline element reference holds a reference string that works as qualified name of the targeted pipeline element and is sufficient to look up the actual element at run-time and development time, i.e., implementations of PipelineElementReference are expected to return the pipeline element via getReferencedElement() without further explicit information. This does not prevent that an implementation of  PipelineElementReference.getReferencedElement() resorts to certain run-time variables available in its execution environment such as the application context. The qualified name of a pipeline consist of the pipeline name and the start node name, e.g., ViewPage-Start. The qualified name of a content page type is made up of the cartridge name followed by the name of the content model and the model element itself, e.g., app_sf_webshop:HomePage.pagelet2-Page.

The only implemenation of PipelineElementReference right now is  PageEntryPointDefinitionReference in the cartridge sld_pmc. It is used by the node  RenderSystemPage located in the same cartridge.  RenderSystemPage renders system pages. The target system page can be selected in Intershop Studio when one embeds the node in a pipeline:


Parameter Maps

There is no general interface for pipeline elements, but most of them probably need to be parameterized to be carried out successfully, i.e., they have a parameter interface that must be served with values in their execution environment. To make their parameter interface known to pipeline nodes, their types have to implement ParameterizedPipelineElement. This interface provides a means for pipeline elements to explicitly express their requirements regarding their execution contexts in terms of objects of type c.i.b.pipeline.capi.definition.ParameterDefinition.

When a pipeline node is executed that in turn somehow invokes another pipeline element, the node must convey the parameter values from its execution environment to the pipeline element. To do so, at first, the node needs a configuration parameter referring to the pipeline element. The type of this configuration parameter must be a sub type of  PipelineElementReference. The type argument of this reference type must be a sub type of  ParameterizedPipelineElement. The only way to pass on the pipeline element parameter values to the pipeline node is via one of its input connectors. Therefore, the respective input interface has to provide an accessor method with java.util.Map<String,Object> as return type. Additionally, that method needs to be annotated with c.i.b.pipeline.capi.annotation.DefinedBy whereas the parameter element of the annotation must refer to the configuration parameter holding the pipeline element reference by its name. At run-time, the returned map will contain an entry for each  ParameterDefinition element returned by the method getParameters when invoked at the pipeline element referred to by the configuration parameter as long as the parameter definition declares the parameter as mandatory and there exists a respective value in the pipeline node execution environment. Otherwise, there will be no entry if the parameter is optional or an exception is thrown if the parameter is mandatory.

The following code example roughly outlines the just described concept using the example of a ficticious pipeline start node class whose instances may be referred to and carried out by pipeline nodes:

public class StartNode 
    implements ParameterizedPipelineElement
{
    public Collection<? extends ParameterDefinition> getParameters() {...}
	public void execute(Map<String,Object> parameters) {...}
    ...
}
 
public class StartNodeReference implements PipelineElementReference<StartNode>
{
    public StartNode getReferencedElement() {...}
    ...
} 
 
@PipelineNode
public class CallNode
{
    @PipelineNodeParameter(name="StartNode")
    private StartNodeReference startNode;
 
    @PipelineNodeInput
    public Output execute(Input input)
    {
        startNode.getReferencedElement().execute(input.getStartNodeParameters());
        ...
    }
 
    interface Input
    {
        @DefinedBy(parameter="StartNode")
        Map<String,Object> getStartNodeParameters(); 
    }
 
    interface Output
    {
        ...
    }
}
Disclaimer
The information provided in the Knowledge Base may not be applicable to all systems and situations. Intershop Communications will not be liable to any party for any direct or indirect damages resulting from the use of the Customer Support section of the Intershop Corporate Web site, including, without limitation, any lost profits, business interruption, loss of programs or other data on your information handling system.
Home
Knowledge Base
Product Releases
Log on to continue
This Knowledge Base document is reserved for registered customers.
Log on with your Intershop Entra ID to continue.
Write an email to supportadmin@intershop.de if you experience login issues,
or if you want to register as customer.