The example plugin (including sources) is available as the Maven artifact
com.subshell.sophora.teletext:example-teletext-plugin. It is designed to work with an empty Sophora repository after the basic node types have been imported and configuration has been applied as described in the node types and configuration section.
Hence, the example plugin relies on the example story node type
The following sections provide information about the various aspects of plugin development.
The following Maven dependency is required when developing a plugin using the plugin API:
<dependency> <groupId>com.subshell.sophora.teletext</groupId> <artifactId>teletext-plugin-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency>
The Teletext Generator needs to know which Java packages to include in the initial package scan on startup to enable the plugin.
These packages have to be specified in the
pluginPackages configuration key in the
Every plugin must provide a central Spring component implementing the
com.subshell.sophora.teletext.api.TeletextPlugin interface. The Teletext Generator will pick up this component upon startup and perform the registration of customizations.
You can verify that your plugin has been picked up, by looking at the log messages of the Teletext Generator during startup of the application.
The corresponding class in the example plugin is
To represent Sophora documents as Java objects, Spring Data Sophora (maven:
com.subshell.sophora:spring-data-sophora) is used. Customers may declare their own model classes to represent sophora teletext documents with customer specific properties and/or child nodes.
The model class must extend
com.subshell.sophora.teletext.api.model.TeletextDocumentEntity for Sophora documents and
com.subshell.sophora.teletext.api.model.index.DocumentReference for their reference node types and must be annotated with an appropriate Spring Data Sophora annotation specifying the corresponding Sophora node type like
Upon startup of the Teletext Generator, Spring Data Sophora will perform a package scan to pick up custom models and register them. Whenever a Java object is to be instantiated by Spring Data Sophora for a Sophora document, the most specific Java class found in the class path is picked and a new object of this class is populated with the data from the Sophora document.
You can verify which model classes are being used for which Sophora node types, by looking at the log messages of the teletext generator during startup of the application.
When custom node types, properties or child nodes are added by the plugin developer, please be advised that the
sophora-teletext* Sophora namespace is reserved for core teletext features provided by subshell.
The example plugin defines a custom model class
com.subshell.sophora.teletext.example.MyStory for the node type
Renderes are responsible for rendering the "body" part of a teletext document (the lines between header and footer) and for providing variables used in headers and footers.
Renderers have to implement the
com.subshell.sophora.teletext.api.generation.TeletextRenderer interface and must be registered via the central plugin component.
The example plugin provides a custom renderer
com.subshell.sophora.teletext.example.MyStoryRenderer for documents of type
A renderer may signal that it is not responsible for rendering a given model by returning
BodyRenderingResult.notRendered(). That way, other renderers will be asked to render the model until a result is produced or none of the renderers yield a rendered result.
Index pages contain references to other teletext documents, layout elements and provide means of grouping references together.
Of course you can write an individual renderer for index pages if you wish, but if you prefer to keep (most of) the behaviour of the default renderer but have to customize only the rendering of individual elements on an index page you can provide a custom class implementing the
The example plugin provides a custom index element factory
For any model of an
IndexElement whose rendering you'd like to customize, you need to return a corresponding
RenderableIndexElement that implements the rendering logic.
For the rendering of index elements that you don't want to customize, delegate to the default mechanisms by returning
The transfer service is responsible for writing teletext content to files and/or uploading it to external services.
The default implementation of a transfer service will write
.etp files for each slot to the local file system and upload them via (s)ftp to remote servers.
To define a custom transfer service, implement the
com.subshell.sophora.teletext.api.transfer.ITransferService interface and register the implementation through the central plugin component.
If you want to customize only some aspects of the default implementation of the transfer service, the default transfer service (
com.subshell.sophora.teletext.api.transfer.IDefaultTransferService) can be accessed by using spring dependency injection.
This is demonstrated in the example pugin in the
The teletext generator provides a preview of the teletext content for saved but not necessarily published sophora documents.
The preview is availabe as an HTTP endpoint. The path for the default preview is /preview.
To provide a custom preview, create a Spring web controller class annotated with
@Controller and set the controller's URL as a preview url in Sophora. An
com.subshell.sophora.teletext.api.generation.preview.IPreviewGenerator can be auto-wired into the controller instance to re-use the core preview implementation.
The example plugin defines a custom controller in the
The Teletext Generator will call the various plugin extension points with readily instantiated Java objects representing Sophora documents.
If you need to query the Sophora repository directly for documents, you need to make use of the Spring Data Sophora Repository mechanism to do as follows.
Define an interface extending
com.subshell.sophora.spring.data.repository.SophoraDocumentRepository. Spring will then supply an implementation of this interface that can be auto-wired into your code.
The example plugin includes the
com.subshell.sophora.teletext.example.MyStoryRepository interface to demonstrate this.
The Teletext Plugin API provides means to easily partition the teletext content of a Sophora document onto a number of teletext subpages. The following helper classes are instantiated with builders that enable you to control the behavior of the page partitioning on a high level while the actual work gets done by the API.
PagePartitioner helps partitioning a page into subpages. It reads headline and body from the given decorator objects and distributes the resulting lines across pages of the given size.
builder(HeadlineDecorator, BodyDecorator) method to create an object of this class. The builder can be configured with the following parameters:
headlineDecorator- Required: provides the headlines for each page. See below for details.
bodyDecorator- Required: provides the body lines for each page. See below for details.
linesPerPage- The number of lines which are available for a page body on each page.
fillUnusedLines- indicates whether unused lines at the end of each page should be filled with blank lines. Defaults to
Once the partitioner is built, all that is left to do is to call
partition() to receive a readily partitioned
BodyRenderingResult containing a number of page bodies with the specified content and potential notifications that arose during the generation that need to be passed on to the user.
HeadlineDecorator reads headlines from a fixed size text field, decorates them with colors, an optional counter, appends a blank line or any combination of these.
Use the builder(
FixedSizeTextField) method to create a new object of this class. The individual builder methods will have the following effect:
entity- Required: the sophora entity this headline will be built for. Used for associating any notifications that are generated during the process.
textField- Required: the fixed size text field to be used to get the headlines from.
defaultColor- Default color for not explicitly colored elements of the headline. Defaults to cyan.
headlineOnlyOnFirstPage- Set to true if only the first page will get a headline. Defaults to false.
withCounter- Set to true if a counter should be added if the total pages is > 1 (and the headline will be displayed on more than only the first page). Defaults to false.
counterColor- Color to be used for coloring an optional counter. Defaults to white.
additionalBlankLines- Number of blank lines to be added at the end of the headline section. Defaults to 0.
Pass the resulting
HeadlineDecorator into the builder of a
PagePartitioner (see above).
BodyDecorator reads body lines from a fixed size text field and assigns a default text color for the purpose of feeding the lines to a
builder(FixedSizeTextField, Color) method to create a new object of this class. The builder can be configured with the following parameters:
textField- Required: the fixed size text field to be used to get the body lines from.
color- the default color for not explicitly colored parts of the body lines.
Pass the resulting
BodyDecorator into the builder of a
PagePartitioner (see above).
IndexLinesFormatter is a helper for creating reference lines in index pages. Those are lines that usually consist of a short bit of text, followed by a number of dots to fill up the space and finally, a slot number. For example:
Title of referenced page ......... 123 The text is provided as a
FixedSizeTextField and the slot number as an integer. Obtain an instance of
IndexLinesFormatter by calling one of its static of methods.
The formatter offers methods to customize the colors of the resulting lines. Also, if your original text already contains more than one line, you may use the method
shiftUnderLines to control the indent of any line following the first one, provided the indented lines still fit the teletext page. The result could look something like this, if we assume an indentation of
2: A slightly longer title of a referenced page ................ 123
TeletextDocumentReferenceCollector is a helper for extracting all reference model objects of a certain type from an Index. This is useful because an Index might contain a rather complex tree structure of elements, only few of which are actually reference model objects. Traversing through this tree manually can be troublesome, so you might want to use this helper instead.
To use it, you first have to instantiate a
TeletextDocumentReferenceCollector by either specifying the desired subtype of
TeletextDocumentReference to only extract model objects of this specific type and its subtypes or without a parameter, in which case all objects of the type
TeletextDocumentReference or any of its subtypes will be extracted from the index element tree structure.
Secondly, you have to apply the instantiated collector to an Index by calling the index's
accept(TeletextDocumentReferenceVisitor) method. Please note that the
TeletextDocumentReferenceCollector is stateful and can therefore only be applied to an
Once applied to an index, call the collector's
getCollectedElements() method to receive your result.
In case you design your own
IndexElement that is a container for other elements (e.g. a bundling group), make sure you override the
accept(TeletextDocumentReferenceVisitor) method and let every contained element accept the visitor recursively, otherwise the
TeletextDocumentReferenceCollector (and any other
TeletextDocumentReferenceVisitor for that matter) cannot traverse the subtree underneath your custom
The markup in
FixedSizeTextFields may contain text links to other Sophora documents and therefore to other teletext content. The
TextLinkReplacer helps you substituting link texts according to the following rules:
- * If the referenced document has a slot number assigned, every occurrence of the letters
XXXwithin the link text will be substituted for the (first) slot number that is occupied by the document.
- * If the referenced document does not have a slot number assigned, the entire link text is substituted for a string of the same length consisting of space characters only.
TextLinkReplacer is a Spring component and has to be obtained through Spring dependency injection.
replaceTextLinksInFixedSizeTextField(FixedSizeTextField, SlotAllocations, SophoraDocumentEntity) method to receive a copy of the given
FixedSizeTextField in which all text links have been substituted according to the above mentioned rules and the given
SophoraDocumentEntity is only needed to associate any notifications that arose in the process with.
As an example, consider the following fixed size text:
This is a text. For further infor- mation please refer to XXX. And here is some more text.
If the entire second sentence constituted a link to a document that occupies slot 123, the result of the substitution would be this:
This is a text. For further infor- mation please refer to 123. And here is some more text.
However, if the referenced document did not have a slot number assigned, the result would be as follows:
This is a text. And here is some more text.
On top of rendering the body lines of any given model object,
TeletextRenderers may provide a variable assignment that will be applied to variable placeholders in the teletext template. See the section on templates in "Node Types and configuration" for further details on variables in general.
There are two ways to provide your own variable assignment from within your custom
TeletextRenderer. You can simply add them to the
TeletextPageBodys in the
BodyRenderingResult produced by your
render(TeletextDocumentEntity, int, SlotAllocations) method or you may implement/override the method
additionalVariables(TeletextDocumentEntity, BodyRenderingResult, SlotAllocations) to provide a VariableAssignment separately from the body rendering routine.
The latter is particularly useful if you want to decouple body rendering and variable assignment or if you don't want to implement body rendering alltogether (and effectively leave it to default renderers). For example, you might find that the default body rendering for stories works just fine for your use case but you just want to provide a few extra variables used in your teletext template.
Please note that variables may have different values assigned for different subpages. The variable assignments in the list returned from
additionalVariables(…) are applied to the subpages in order.
When it comes to stories, some variable assignments will always be provided by default. Those are:
- header1: Contains the text content of the field
- header2: Contains the text content of the field
- header1and2: Contains the text content of the two previous fields concatenated with a control character for cyan text color in between.
- footer1: Contains the text content of the field
- footer2: Contains the text content of the field
- footer1and2: Contains the text content of the two previous fields concatenated.
- footerWithNextHeadline: If the two footer fields are filled, this will contain the same as
footer1and2. Otherwise it will simply contain the text > mehr as long as there are further subpages or
<next headline>..................<slot>on the last subpage, provided the document occupying the next higher slot has sophora-
teletext:headlineset. In that case
<next headline>is filled with that text and
<slot>will be the next higher slot number so that the total length of that line is 40 characters. If that next document has no
sophora-teletext:headlineset, the variable will only contain
<slot>being the next higher slot number.
- footerWithSubpageCounter: If the two footer fields are filled, this will contain the same as
footer1and2. Otherwise it will contain the subpage index and the total subpage count in brackets, centered in the line and
> mehrat the end, provided there are more subpages. For example:
(1/4) > mehron the first of four subpages or
(4/4)on the last subpage.
For index pages, the variable footer1 is defined.
- footer1: Contains the text content of the field
sophora-teletext:indexPageFootlines. Each entry of this multi-string field is used for the corresponding teletext sub page. If there are more sub pages than field entries, the last entry will be used for the remaining sub pages.