private void link(final Map<String, Element> elementInstances,
                      final Map<Element, PipelineElementType> elementTypeMap,
                      final Map<String, Set<String>> linkSets,
                      final SteppingController controller,
                      final Element parentElement,
                      final String parentElementId,
                      final int controllerSplitDepth) {
        // Get the child elements of the supplied 'from' element id that we want
        // to work with.
        final List<Element> childElements = getChildElements(parentElementId, elementInstances, elementTypeMap,
                linkSets, controller);

        // Loop over the child elements and link them to the parent.
        for (final Element childElement : childElements) {
            final String elementId = childElement.getElementId();
            final PipelineElementType elementType = elementTypeMap.get(childElement);

            Fragment fragment = new Fragment(childElement);

            // If we are in stepping mode then insert a recorder before the
            // element and one after. Once this is done add the recorders to an
            // element monitor and register it with the stepping controller.
            // Finally insert a record detector if appropriate for the current
            // element to control stepping.
            if (controller != null) {
                if (parentElement instanceof OutputRecorder) {
                    // If the parent element is already an output recorder used
                    // to replace a destination then reuse the same output recorder
                    // for any downstream elements.
                    fragment = new Fragment(parentElement);
                    addMonitor(elementId, elementType, childElement, fragment, controller);

                } else {
                    fragment = insertRecorder(elementId, elementType, fragment, true, controller,
                            controllerSplitDepth);
                    fragment = insertRecorder(elementId, elementType, fragment, false, controller,
                            controllerSplitDepth);
                    addMonitor(elementId, elementType, childElement, fragment, controller);
                    fragment = insertRecordDetector(elementType, fragment, true, controller);
                    fragment = insertRecordDetector(elementType, fragment, false, controller);
                }
            }

            // Continue to link the children of this child.
            link(elementInstances,
                    elementTypeMap,
                    linkSets,
                    controller,
                    fragment.getOut(),
                    elementId,
                    controllerSplitDepth);

            // Now set the target of the parent element to be the 'wrapped'
            // child to complete the link.
            if (parentElement != null && parentElement != fragment.getIn()) {
                if (parentElement instanceof final HasTargets hasTargets) {
                    if (childElement instanceof Target) {
                        if (fragment.getIn() instanceof final Target target) {
                            hasTargets.addTarget(target);
                        }
                    } else {
                        throw new PipelineFactoryException("Attempt to link to an element that is not a target: "
                                                           + parentElement.getElementId() + " > " + elementId);
                    }
                } else {
                    throw new PipelineFactoryException(
                            "Attempt to link from an element that cannot target another element: "
                            + parentElement.getElementId());
                }
            }
        }
    }
Link element instances together to form the pipeline.
stroom-pipeline/src/main/java/stroom/pipeline/factory/PipelineFactory.java