Skip navigation links

Package org.frankframework.frankdoc.model

This package contains a set of model classes that is used by DocWriterNew to generate the XML configuration schema used by Frank developers.

See: Description

Package org.frankframework.frankdoc.model Description

This package contains a set of model classes that is used by DocWriterNew to generate the XML configuration schema used by Frank developers. Please note that DocWriterNew is presently not used; this class is under development.

The model

The following diagram introduces the Java classes of the model:

Image FrankDocModel.jpg can not be shown

Class FrankElement models a Java class of the Frank!Framework that can be accessed from a Frank config. An example is FrankElement nl.nn.adapterframework.parameters.Parameter, which you can reference in a Frank config with the tag <Param>. The modeled Java class can have a superclass, which is expressed by the link named "parent".

A tag in a Frank config can contain other tags. A <Receiver> can for example contain <DirectQuerySender> or DirectQueryErrorSender. These two tags reference the same Java class, namely nl.nn.adapterframework.jdbc.DirectQuerySender, but the first tag uses it as a Sender while the second tag uses it as an ErrorSender. The model expresses a set of allowed child tags by relating a containing FrankElement to an ElementRole. An ElementRole has a property roleName to express the role and references an ElementType to define what child FrankElement objects can appear. Each ElementType has one or more FrankElement objects as members.

There are two flavors of ElementType objects. Some ElementType objects model a Java interface. In this case, the members are the FrankElement objects that model the Java classes that implement the Java interface. Please note that not every Java interface that appears in the Java source code is modeled by an ElementType object. An ElementType object appears only for Java interfaces that are relevant for nesting tags in a Frank config. Some ElementType objects model Java interfaces that have an inheritance relation. This inheritance is expressed in the model using the "highest common interface" relation. This relation is needed for some corner cases of generating the XML schema file.

Other ElementType objects just model a single Java class, which is expressed by a reference to the corresponding FrankElement as the only member.

There is no direct relation between FrankElement an ElementRole, because the relation between a parent tag and a child tag requires some additional information. DocWriterNew requires information on how often a child tag can appear, whether usage of the child tag is deprecated, and some other information. This additional information is included in class ConfigChild, which also references the FrankElement of the parent tag and the ElementRole for a set of allowed child tags.

Tags in Frank configs can have attributes, which are modeled by class FrankAttribute. Attributes have a type that is modeled by AttributeType (not in diagram). String attributes can have their values restricted by a Java enum type. In that case, the list of allowed values is stored in a AttributeEnum, which can be shared by multiple FrankAttribute. The tag in which the attribute occurs is modeled by its FrankElement, see relation "attribute of". The documentation of an attribute may appear in a Java class that differs from the attribute owning Java class (the IbisDocRef Java annotation). This is expressed by the relation "described by".

FrankElement only hold the attributes and config children that are declared by the modeled Java class. Attributes or config children owned by inheritance are not modeled directly as children. FrankElement has methods to browse inherited children, which need a callback object of type CumulativeChildHandler. This functionality works the same for attributes and config children. Therefore, FrankAttribute and ConfigChild have a common superclass ElementChild. That class also parses IbisDoc annotations, which is done the same way for config children and attributes.

The model is not only used to generate the XML Schema file (ibisdoc.xsd). It is also used to generate a website with reference documentation. That website documents all tags (FrankElement objects) that can be used. The FrankElement objects are grouped by the implemented Java interface (interface-implementing ElementType objects). There is an additional group "Other" for all FrankElement that belong to a non-interface-based ElementType (e.g. nl.nn.adapterframework.core.PipeForward). These table-of-contents (TOC) groups are modeled by model class FrankDocGroup.

Class FrankDocModel holds the entire model (not shown in the diagram). Two model classes have not been explained yet: ConfigChildSet and ElementRoleSet. These have been introduced to avoid conflicts in the XML schema file. These conflicts are explained in the next section.

Conflicts

Element names from FrankElement

A FrankElement models a Java class that Frank developers reference using an XML tag. Method FrankElement.getXsdElementName(ElementRole) calculates the name of this XML element. Remember that XML elements in Frank configs not only express the referenced Java class but also the role in which this Java class is used. This is why the method has an ElementRole argument.

FrankElement-s in FrankDocGroup "Other", like <Forward> and <Param>, are named in a special way. They usually belong to a single ElementRole. Their name is based on the syntax 1 name of that ElementRole.

This way of naming introduces potential conflicts when deprecated FrankElement-s are included. These potential conflicts are explained in the following subsections, with the solutions applied to prevent them. There are also some other potential conflicts. These are explained as well.

ElementRole member conflicts

A Pipeline can have pipes as children, which appears in the Java code as a config child setter: nl.nn.adapterframework.core.PipeLine.addPipe(nl.nn.adapterframework.core.IPipe). The resulting config child has an ElementRole that we can express as (IPipe, pipe), IPipe representing the ElementType and "pipe" is the syntax 1 name. The members of this element role include the following: nl.nn.adapterframework.pipes.FixedResult and nl.nn.adapterframework.pipes.FixedResultPipe. Method FrankElement.getXsdElementName(ElementRole) gives them the same name, which is FixedResultPipe.

To avoid this type of conflicts, element role member conflicts, we do the following for each ElementRole. We collect all members (FrankElement) and group them by the XML element name that these members get for the role. When an XML element name is shared by multiple FrankElement-s, we assume that the conflict has arisen because the core team intended to rename the Java class behind the element name. The old Java class is needed for backwards compatibility, but it has been deprecated to support resolving the conflict. This assumption allows us to omit the deprecated FrankElement-s. We emit a warning in case the conflict remains unresolved.

The generic element option

The XML schema files we produce are needed by the Frank!Framework to parse configurations. To make this possible, our XML schemas need to support syntax 1, which means syntax like <Listener className="nl.nn....">. This element is allowed as a child of each XML element that is allowed already to contain a particular listener as child, for example as a child of <Receiver>. We say that <Listener> is the generic element option of <Receiver>.

Our XML schemas should specify which FrankElement-s are allowed as children of a generic element option. These FrankElement-s do not have to be written out explicitly, because we can reference XML schema groups that are based on ElementRole-s, the member children. The member children of an ElementRole are found by collecting all config children of all members, and taking the ElementRole of each ConfigChild. The generic element option is defined by adding a group reference for each relevant member child, the set of relevant member children depending on the XML schema version being created.

In some cases, multiple ElementRole-s have to share a generic element option. The example is nl.nn.adapterframework.batch.StreamTransformerPipe. It has config children with the followin ElementRole-s:

If all of these would be used to define the generic element option <Child>, the XML schema would be invalid. If the parser would encounter <Child>, it would not know which definition to apply.

This potential conflict is resolved by merging config children. When a FrankElement has cumulative config children sharing a syntax 1 name, then these config children are merged. This check is done by method FrankElement.hasOrInheritsPluralConfigChildren(java.util.function.Predicate, java.util.function.Predicate), which takes Predicate arguments that express the XML schema version being created. Config children sharing a common syntax 1 name for the XML schema version being created are called "plural config children".

To be able to handle plural config children, we introduce model entity ConfigChildSet. All cumulative config children of a FrankElement are grouped by syntax 1 name, and each group results in a ConfigChildSet. We use ConfigChildSet-s instead of ConfigChild-s to fill XSD element groups, resulting in common code for plural and non-plural config children.

Conflicts by ElementType interface inheritance

Listeners are Frank elements that receive incoming messages. A Java class within the Frank!Framework is a listener when it implements nl.nn.adapterframework.core.IListener. There is a potential conflict because there are Java interfaces that inherit from nl.nn.adapterframework.core.IListenernl.nn.adapterframework.pipes.PostboxRetrieverPipe with ElementRole (IPostboxListener, listener). We also have nl.nn.adapterframework.pipes.SenderPipe with ElementRole (ICorrelatedPullingListener, listener). These two pipes are members of role (IPipe, pipe). There is a generic element option <Pipe> that can have an element <Listener>. But if both listener ElementRole-s (IPostboxListener, listener) and (ICorrelatedPullingListener, listener) would be used to define the <Listener>, a conflict would result. If the parser would encounter <Listener>, it would not know which of the two definitions to use.

This conflict is resolved by promoting the two ElementRole-s to (IListener, listener). Every ElementRole is used only once and thus this promotion resolves the conflict. We call it the "highest common interface", which is calculated by method ElementRole.getHighestCommonInterface().

Member conflicts with element name of generic element option

We have an ElementRole (IErrorMessageFormatter, errorMessageFormatter) which has FrankElement nl.nn.adapterframework.errormessageformatters.ErrorMessageFormatter as member. Element groups including a generic element option <ErrorMessageFormatter> cannot also define XML element <ErrorMessageFormatter> to reference Java class nl.nn.adapterframework.errormessageformatters.ErrorMessageFormatter. This conflict with the element name of the generic element option is found by method ElementRole.getDefaultElementOptionConflict() by that XML element.

Member conflicts in shared generic element option

Members of a generic element options shared by plural config children can introduce a conflict. An example occurs in FrankElement nl.nn.adapterframework.batch.AbstractRecordHandler. It has the following ElementRole-s: These are non-interface-based ElementRole-s, so these would both define XML element <Child>. This is not possible. Syntax 2 does not provide the option to reference Java class InputfieldsPart or OutputfieldsPart in role "child". This is not a big issue, because they are available in roles "inputFields" and "outputFields". Furthermore, they can still be expressed in syntax 1: <Child className="...InputfieldsPart">.

The config child setters for (InputfieldsPart, child) and (OutputfieldsPart, child) are deprecated. One could wonder why we do not try to resolve the conflict by omitting deprecated config children. In theory, a FF! version could have existed in which the config child setter for (InputfieldsPart, child) was not deprecated. Then that version could define <Child> to reference Java class InputfieldsPart in role "child". We do not want this, however, because it threats backward compatibility. We would have old Frank configs including <Child>, but they would be incorrect now because of the conflict.

To detect member conflicts in shared generic element options, the model has entity ElementRoleSet. Each ConfigChildSet is linked to an ElementRoleSet that has the element roles of the config children of the ConfigChildSet as members. When multiple ConfigChildSet-s reference the same set of ElementRole-s, then they reference a common ElementRoleSet object. Each ElementRole knows the ElementRoleSet-s it belongs to. ElementRole uses this information to omit members that would conflict with a shared generic element option. Class ElementRoleSet is not public, because ElementRole takes care of calling ElementRoleSet.

Generic element option recursion

This subsection describes a conflict that does not occur in version 7.6-SNAPSHOT of the Frank!Framework. It is covered by the test input classes in package org.frankframework.frankdoc.testtarget.exotic. There is a class Master with ElementRole (IMember, part). This role has no FrankElement-members with plural config children, but there is still a conflict. All child members of this role should be used to find child XML elements of <Master><Part>.... We have FrankElement-s Member1 and Member2 that have config children with the following ElementRole-s: (Child1, child) and (Child2, child). These are not interface-based, but we cannot define element <Master><Part><Child> to reference them both. We would have two conflicting definitions, because it is not clear whether this XML element would reference to Child1 or Child2.

We solve this issue by omitting syntax 2 elements for Java classes Child1 and Child2. These classes are only available through syntax 1: <Child className="...">. This is implemented in two places. First, we introduce more ElementRole objects. For each ConfigChildSet-based ElementRoleSet, we introduce new ElementRoleSet-s by calculating the member children and grouping them by syntax 1 name. We do this recursively. The recursion will stop because the recursion will find sets of element roles that correspond to existing ElementRoleSet objects.

Omitting syntax 2 elements for these conflicts is also done in DocWriterNew. The example shows that the contents of a generic element option does not necessarily correspond to a ConfigChildSet. To fill a generic element option, we need to get a Set of ElementRole holding the roles that are selected for the XML version being created. Then we have to calculate member children recursively from these element role sets. The model provides static method ConfigChildSet.getMemberChildren(java.util.List, java.util.function.Predicate, java.util.function.Predicate, java.util.function.Predicate) to support this. We cannot use ElementRoleSet for this in DocWriterNew, because DocWriterNew has to filter roles for the chosen version of the XML schema. Class ElementRoleSet works without filtering role. That class has to find conflicting FrankElement, the conflicts not being affected by the version of the XML schema being created.

Skip navigation links

Copyright © 2022 Ibissource.org. All rights reserved.