仪表板 API
仪表板Wiki 页面类似,但 Wiki 页面主要包含静态内容,而仪表板主要包含由小组件提供的动态内容。
仪表板小组件与 Wiki 插件功能等效,优势如下:
提供可用小组件目录可供选择。
提供小组件参数编辑器。
小组件开发人员指南
本部分对开发人员指南进行了概述。
快速入门指南
要查看工作小组件项目,请先查阅小组件开发和部署快速入门指南
自定义小组件实现
Codebeamer 8.0 中引入的小组件框架旨在简化新小组件的开发,同时为小组件开发人员提供尽可能多的自由发挥空间。以下 5 个类用于定义系统中的小组件:
1. Widget 接口的实现。
2. WidgetInformation 接口的实现。
3. Renderer 接口的实现,一个用于小组件内容,一个用于编辑器内容。(强烈建议)。
4. WidgetFactory 接口的实现。
请知悉,实现示例使用 Spring 依赖注入机制。必须使用 @Component 来注释 WidgetWidgetInformationWidgetFactory 实现,否则小组件框架不会选取自定义小组件。以下各部分介绍如何通过注释代码示例来实现这些接口。
基础知识
WidgetInformation 接口
我们先以 WidgetInformation 接口为例。此类将在小组件目录中表示小组件。(因此,此类会直接影响在“小组件浏览器”弹出窗口中显示的内容。)

package com.intland.codebeamer.dashboard.component.widgets.wiki
import org.springframework.stereotype.Component
import com.intland.codebeamer.dashboard.component.common.interfaces.WidgetInformation
import com.intland.codebeamer.dashboard.component.widgets.common.WidgetCategory
// Annotation required to make it available in the system.
@Component
public class WikiMarkupWidgetInformation implements WidgetInformation {
// Defines the category, which affects on which tab in Widget popup it appears.
//Possible values: ALL, CHART, PROJECT, AGILE, TEST, PERSONAL_CONTENT, OTHER
@Override
public String getCategory() {
return WidgetCategory.OTHER.getName()
}
// Custom icon for the Widget.
@Override
public String getImagePreviewUrl() {
return "/images/newskin/dashboard/thumbnails/icon_wiki_markup.png"
}
// Knowledgebase url on codebeamer.com, reserved for built-in widgets.
@Override
public String getKnowledgeBaseUrl() {
return ""
}
// Vendor information.
@Override
public String getVendor() {
return "Intland"
}
// Key in ApplicationResources.properties, which defines the short name of the Widget.
@Override
public String getName() {
return "dashboard.wiki.markup.widget.name"
}
// Key in ApplicationProperties.properties, which contains the description for the Widget.
@Override
public String getShortDescription() {
return "dashboard.wiki.markup.widget.short.description"
}
// Reserved for future use.
@Override
public WikiMarkupWidgetFactory getFactory() {
return null
}
// Type name of the related Widget class.
@Override
public String getType() {
return WikiMarkupWidget.class.getCanonicalName()
}
}
小组件接口
此接口的实现是任何小组件的核心。它表示框架持久层中的小组件,还包含用于呈现内容和编辑器的逻辑。建议不要直接实现,而是从 AbstractWidget 类进行继承,因为该类已实现了常见功能。我们先来看一下接口:

package com.intland.codebeamer.dashboard.component.common.interfaces
import com.intland.codebeamer.dashboard.component.common.RenderingContext
public interface LayoutComponent {
// Unique identifier of the component
String getId()
// Renders the component as HTML fragment (not a standalone HTML page)
String renderToHtml(RenderingContext renderingContext)
}

package com.intland.codebeamer.dashboard.component.common.interfaces
import java.util.Map
import com.intland.codebeamer.dashboard.component.common.RenderingContext
import com.intland.codebeamer.dashboard.component.serializer.JacksonSerializableObject
import com.intland.codebeamer.dashboard.component.widgets.common.attribute.WidgetAttribute
public interface Widget extends LayoutComponent, JacksonSerializableObject {
// Renders the editor for the Widget as HTML fragment (not a standalone HTML page)
String renderEditorToHtml(RenderingContext renderingContext)
// Attributes of the widget. These are typed values, which might represent just a simple text or more complex entities like a list of projects.
Map<String, WidgetAttribute getAttributes()
// Setter for the title of the Widget.
void setTitle(String title)
// Reserved for future use.
void setFullWidth(boolean isFullWidth)
// Reserved for future use.
boolean isFullWidth()
// Sets the color of the Widget header.
void setColor(String color)
// Reserved for future use.
void setHeaderHidden(boolean headerHidden)
// Returns a key from ApplicationResources.properties, which defines the default title for the Widget.
String getDefaultTitleKey()
}
接下来是 AbstractWidget 类:

package com.intland.codebeamer.dashboard.component.widgets.common
import java.util.Map
import org.apache.commons.lang3.Validate
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.annotation.JsonPropertyOrder
import com.intland.codebeamer.dashboard.component.common.interfaces.Widget
import com.intland.codebeamer.dashboard.component.widgets.common.attribute.WidgetAttribute
// Important to have these annotations to ensure that the persistance layer saves the Widget properly
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonPropertyOrder(alphabetic = true)
public abstract class AbstractWidget implements Widget {
protected static final String NULL_MESSAGE_FORMAT = "The value %s must not be null."
// Identifier of the Widget
protected final String id
// CURRENT attributes of the widget
protected final Map<String, WidgetAttribute attributes
// Title of the widget
protected String title
// Reserved for future use
protected boolean isFullWidth
// Color of the header
protected String color
// Reserved for future use.
protected boolean headerHidden
public AbstractWidget(String id, Map<String, WidgetAttribute attributes) {
Validate.notBlank(id, NULL_MESSAGE_FORMAT, "id")
Validate.notNull(attributes, NULL_MESSAGE_FORMAT, "attributes")
this.id = id
this.attributes = attributes
}
// Getters and setter omitted.
}
最后看一下真实示例:

package com.intland.codebeamer.dashboard.component.widgets.wiki
import java.util.HashMap
import java.util.Map
import org.apache.commons.lang3.Validate
import com.fasterxml.jackson.annotation.JacksonInject
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.annotation.JsonPropertyOrder
import com.intland.codebeamer.dashboard.component.common.RenderingContext
import com.intland.codebeamer.dashboard.component.common.interfaces.Renderer
import com.intland.codebeamer.dashboard.component.widgets.common.AbstractWidget
import com.intland.codebeamer.dashboard.component.widgets.common.attribute.TextAttribute
import com.intland.codebeamer.dashboard.component.widgets.common.attribute.WidgetAttribute

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonPropertyOrder(alphabetic=true)
public class WikiMarkupWidget extends AbstractWidget {
public static final String MARKUP_ATTRIBUTE_NAME = "markup"
public static final WidgetAttribute<String DEFAULT_MARKUP_ATTRIBUTE = new TextAttribute("", true, false)
// Preferred method to define default attributes
public static Map<String, WidgetAttribute getDescriptor() {
final Map<String, WidgetAttribute result = new HashMap<String, WidgetAttribute()
result.put(MARKUP_ATTRIBUTE_NAME, DEFAULT_MARKUP_ATTRIBUTE)
return result
}
// Renderer instance, which can create the content of the Widget
private final Renderer<WikiMarkupWidget htmlRenderer
// Renderer instance, which can create the editor form for the Widget
private final Renderer<WikiMarkupWidget editorRenderer
// Annotated constructor used by the persistance layer. Please don"t forget to add these annotations!"
public WikiMarkupWidget(@JsonProperty("id") final String id, @JsonProperty("attributes") final Map<String, WidgetAttribute attributes,
@JacksonInject("wikiMarkupWidgetHtmlRenderer") final Renderer<WikiMarkupWidget htmlRenderer,
@JacksonInject("wikiMarkupWidgetEditorRenderer") final Renderer<WikiMarkupWidget editorRenderer) {
super(id, attributes)
Validate.notNull(htmlRenderer, NULL_MESSAGE_FORMAT, "htmlRenderer")
Validate.notNull(editorRenderer, NULL_MESSAGE_FORMAT, "editorRenderer")
this.htmlRenderer = htmlRenderer
this.editorRenderer = editorRenderer
}
@Override
public String getTypeName() {
return WikiMarkupWidget.class.getCanonicalName()
}
// Recommended way to render the content of the Widget.
@Override
public String renderToHtml(final RenderingContext renderingContext) {
return htmlRenderer.render(renderingContext, this)
}
// Recommended way to render the editor for of the Widget.
@Override
public String renderEditorToHtml(final RenderingContext renderingContext) {
return editorRenderer.render(renderingContext, this)
}
@Override
public String getDefaultTitleKey() {
return "dashboard.wiki.markup.widget.name"
}
}
本示例演示如何从头开始实现 Widget。但是,建议扩展 AbstractRendererWidget 类,该类已实现此模式并包含重要的错误处理代码。
支持类
WidgetAttribute 子类型
此通用类的子类型表示不同的数据类型,以用作小组件的参数。所有内置子类型都具有各自的呈现器,结合使用一些其他支持类即可自动生成每个小组件的编辑器表单。有关此方案的信息,请参阅以下部分。不过现在,我们来看一下该类本身:

package com.intland.codebeamer.dashboard.component.widgets.common.attribute
import java.util.Map
import org.apache.commons.lang3.Validate
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.annotation.JsonPropertyOrder
import com.fasterxml.jackson.annotation.JsonTypeInfo
// Important annotations, which fine the persistance mechanism.
@JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.PROPERTY, property="typeName")
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonPropertyOrder(alphabetic=true)
public abstract class WidgetAttribute<T {
protected static final String NULL_MESSAGE_FORMAT = "The value %s must not be null."
// CURRRENT value of the attribute.
@JsonTypeInfo(use=JsonTypeInfo.Id.CLASS, include=JsonTypeInfo.As.WRAPPER_OBJECT, property="valueTypeName")
T value
// Shows whether this attribute has to be filled on the editor form.
protected final boolean required
// Shows whether this attribute has to be validated using additional validation logic.
protected final boolean validationRequired
public WidgetAttribute(final T value, final boolean required, final boolean validationRequired) {
Validate.notNull(value, NULL_MESSAGE_FORMAT, "value")
this.value = value
this.required = required
this.validationRequired = validationRequired
}
// Getters and setters ommited.
// Important method, all atrributes has to provide a way to parse values from a String representation.
public abstract WidgetAttribute<T convert(final String newValue)
// If the attribute is affected by export / import mechanism (like using project ids), then the identifiers will be mapped by calling this method during the import.
public void replaceIdsAfterImport(Map<Integer, Map<Integer, Integer ids) {
}
protected void setValue(T value) {
this.value = value
}
}
简单子类型:
类名称
值类型
BooleanAttribute
布尔型
StringAttribute
字符串
TextAttribute
字符串,但具有富文本编辑器
IntegerAttribute
整数
IntegerListAttribute
整数列表
StringListAttribute
字符串列表
FixedChoiceWidgetAttribute
可以与枚举一起用作类型参数,包含来自该枚举的单个值。
MultiChoiceWidgetAttribute
与 FixedChoiceWidgetAttribute 类似,但可以包含多个值。
表示 Codebeamer 实体的子类型:
类名称
值类型
ProjectListAttribute
项目标识符列表
ProjectAttribute
单一项目标识符
ReleaseListAttribute
版本标识符列表
ReleaseAttribute
单一版本标识符
TeamListAttribute
团队标识符列表
TeamAttribute
单一团队标识符
TrackerListAttribute
跟踪器标识符列表
TrackerAttribute
单一跟踪器标识符
TrackerTypeListAttribute
TrackerType 标识符列表
TypedTrackerListAttribute
类似于 TrackerListAttribute,但仅允许给定跟踪器类型
UserListAttribute
用户标识符列表
UserAttribute
单个用户标识符
Renderer 接口和实现
在小组件框架中,首选通过 Renderer 实例提供内容。如最后一个示例代码所示,其中允许编写简单明了的 Widget 类,方法是将呈现小组件内容和编辑器表单的潜在复杂任务转移到独立类。

import com.intland.codebeamer.dashboard.component.common.RenderingContext
public interface Renderer<T {
String render(RenderingContext renderingContext, T object)
}
从根本上说,render 方法会根据当前 RenderingContext 和给定对象创建 String 值。按照设计,接口不会强制使用任何技术解决方案来创建对象的 String 表示。开发人员可根据自己的首选项或规范使用基于模板的呈现或编程呈现。
HTML 内容呈现器存在一个限制,即自定义代码必须封装在 DIV 元素中,该元素包含小组件的标识符,如以下 Velocity 模板所示:

<div id="${id}">
${html}
</div>
现在,继续执行具体的实现:

package com.intland.codebeamer.dashboard.component.widgets.wiki
// Imports removed
@Component
@Qualifier("wikiMarkupWidgetHtmlRenderer")
public class WikiMarkupWidgetHtmlRenderer implements Renderer<WikiMarkupWidget {
private static final String TEMPLATE = "wikimarkup-widget.vm"
@Autowired
private WikiPageManager wikiPageManager
@Autowired
private WikiMarkupProcessor wikiMarkupProcessor
@Override
public String render(final RenderingContext renderingContext, final WikiMarkupWidget object) {
// Initialization and argument checks (ommitted)
// Use the arguments to retrieve the information required to render the wiki markup to html.
final String id = object.getId()
final Map<String, WidgetAttribute attributes = object.getAttributes()
final WidgetAttribute wikiMarkupAttribute = attributes.get(WikiMarkupWidget.MARKUP_ATTRIBUTE_NAME)
final String markup = wikiMarkupAttribute.getValue() != null ? (String) wikiMarkupAttribute.getValue() : ""
// Transforming Wiki markup to HTML (omitted)
// Add data to Velocity context
velocityContext.put("id", id)
velocityContext.put("html", html)
final TemplateRenderer templateRenderer = TemplateRenderer.getInstance()
// Render Velocity template
return templateRenderer.renderTemplateOnPath(TEMPLATE, velocityContext, new Parameters(renderingContext.getRequest().getLocale(), false))
}
// Helper methods (omitted)
}
通过使用支持类即可简化编辑器表单内容的呈现。这些类会根据小组件的属性和标准呈现器来呈现编辑器,这会为每个属性创建合适的表单元素。(所有内置 WidgetAttribute 类型都具有特定的呈现器。)进行编写时,我们准备了一个基于 Velocity 模板的实现,因此,在这种类型的呈现器实现中,必须使用 Velocity。

package com.intland.codebeamer.dashboard.component.widgets.wiki
import org.apache.commons.lang3.Validate
import org.apache.velocity.VelocityContext
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Component
import com.intland.codebeamer.dashboard.component.common.RenderingContext
import com.intland.codebeamer.dashboard.component.common.interfaces.Renderer
import com.intland.codebeamer.dashboard.component.widgets.common.ModelPopulator
import com.intland.codebeamer.utils.TemplateRenderer
import com.intland.codebeamer.utils.TemplateRenderer.Parameters

@Component
@Qualifier("wikiMarkupWidgetEditorRenderer")
public class WikiMarkupWidgetEditorRenderer implements Renderer<WikiMarkupWidget {
private static final String NULL_MESSAGE_FORMAT = "The value %s must not be null."
private static final String EDITOR_TEMPLATE = "wikimarkup-widget-editor.vm"
// This is the required helper class, which will generate the editor form elements
private final ModelPopulator<VelocityContext modelPopulator
private final TemplateRenderer templateRenderer
@Autowired
public WikiMarkupWidgetEditorRenderer(final ModelPopulator<VelocityContext modelPopulator, final TemplateRenderer templateRenderer) {
Validate.notNull(modelPopulator, NULL_MESSAGE_FORMAT, "modelPopulator")
Validate.notNull(templateRenderer, NULL_MESSAGE_FORMAT, "templateRenderer")
this.modelPopulator = modelPopulator
this.templateRenderer = templateRenderer
}

@Override
public String render(final RenderingContext renderingContext, final WikiMarkupWidget wikiMarkupWidget) {
Validate.notNull(renderingContext, NULL_MESSAGE_FORMAT, "renderingContext")
Validate.notNull(wikiMarkupWidget, NULL_MESSAGE_FORMAT, "wikiMarkupWidget")
// Create the Velocity context
final VelocityContext velocityContext = new VelocityContext()
// Let the populator create the right HTML fragments for each attribute
modelPopulator.populateAttributes(renderingContext, wikiMarkupWidget, WikiMarkupWidget.getDescriptor(), velocityContext)
// Return the rendererd template
return templateRenderer.renderTemplateOnPath(EDITOR_TEMPLATE, velocityContext, new Parameters(renderingContext.getRequest().getLocale(), false))
}
}
在这里您只需了解一个约定。ModelPopulator 使用由小组件描述符定义的名称将每个表单元素添加至 VelocityContext。(在本例中,为 WikiMarkupWidget.getDescriptor()) 因此,模板如下所示:

${markup}
<br/>
注意:尽管生成了表单元素,但仍可通过此模板自行添加内容!
RenderingContext 实现
RenderingContext 实例包含数据,这些数据在呈现 HTML 内容时可能会很有用。

// Imports omitted
public class RenderingContext {
// The current user, who is viewing or editing the dashboard.
private final UserDto user
// The current request instance.
private final HttpServletRequest request
// WikiPageDto representing the actual Dashboard.
private final WikiPageDto dashboard
// Utility class, which can be used to translate keys from ApplicationResources.properties to text
private final LocalizedTextRetriever localizedTextRetriever
// Constructor, getters omitted
}
WidgetFactory 接口
小组件框架中的另一个中心部分,此接口的实现用于在从持久层加载仪表板以及创建或编辑小组件时创建小组件的新实例。

package com.intland.codebeamer.dashboard.component.serializer
import com.fasterxml.jackson.databind.InjectableValues
public interface JacksonObjectDeserializeContext<T {
// Class of the Widget
Class<T getType()
// Full class name of the Widget
String getTypeName()
// All the properties, which must be injected through dependncy injection into Widgets. (See the Jackson annotation on the example Widget constructor.)
InjectableValues getInjectableValues()
}

package com.intland.codebeamer.dashboard.component.common.interfaces
import com.intland.codebeamer.dashboard.component.serializer.JacksonObjectDeserializeContext
// Marker interface
public interface LayoutComponentFactory<T extends LayoutComponent extends JacksonObjectDeserializeContext<T {
}

package com.intland.codebeamer.dashboard.component.common.interfaces
import java.util.Map
public interface WidgetFactory<T extends Widget extends LayoutComponentFactory<T {
// Create an instance with the given attributes
T createInstance(String id, Map<String, String attributes)
// Create an instance with the given attributes, run validations
T createInstance(String id, Map<String, String attributes, boolean validate)
// Create an instance with default attribute values
T createInstance()
}
最终的实现示例如下所示:

package com.intland.codebeamer.dashboard.component.widgets.wiki
import java.util.Map
import java.util.UUID
import org.apache.commons.lang3.Validate
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Component
import com.fasterxml.jackson.databind.InjectableValues
import com.intland.codebeamer.dashboard.component.common.interfaces.Renderer
import com.intland.codebeamer.dashboard.component.common.interfaces.WidgetFactory
import com.intland.codebeamer.dashboard.component.widgets.common.attribute.WidgetAttribute
import com.intland.codebeamer.dashboard.component.widgets.common.attribute.WidgetAttributeMapper

@Component
public class WikiMarkupWidgetFactory implements WidgetFactory<WikiMarkupWidget {
private static final String NULL_MESSAGE_FORMAT = "The value %s must not be null."
// The HMTL renderer
private final Renderer<WikiMarkupWidget htmlRenderer
// The editor form renderer
private final Renderer<WikiMarkupWidget editorRenderer
private final WidgetAttributeMapper widgetAttributeMapper
// Qualifiers are important here!
@Autowired
public WikiMarkupWidgetFactory(@Qualifier("wikiMarkupWidgetHtmlRenderer") final Renderer<WikiMarkupWidget htmlRenderer,
@Qualifier("wikiMarkupWidgetEditorRenderer") final Renderer<WikiMarkupWidget editorRenderer,
final WidgetAttributeMapper widgetAttributeMapper) {
Validate.notNull(htmlRenderer, NULL_MESSAGE_FORMAT, "htmlRenderer")
Validate.notNull(editorRenderer, NULL_MESSAGE_FORMAT, "editorRenderer")
Validate.notNull(widgetAttributeMapper, NULL_MESSAGE_FORMAT, "widgetAttributeMapper")
this.htmlRenderer = htmlRenderer
this.editorRenderer = editorRenderer
this.widgetAttributeMapper = widgetAttributeMapper
}
@Override
public WikiMarkupWidget createInstance(String id, Map<String, String attributes) {
return createInstance(id, attributes, true)
}
@Override
public WikiMarkupWidget createInstance(String id, Map<String, String attributes, boolean validate) {
Validate.notBlank(id, NULL_MESSAGE_FORMAT, "id")
Validate.notNull(attributes, NULL_MESSAGE_FORMAT, "attributes")
final Map<String, WidgetAttribute widgetAttributes = widgetAttributeMapper.map(attributes, WikiMarkupWidget.getDescriptor(), validate)
return new WikiMarkupWidget(id, widgetAttributes, htmlRenderer, editorRenderer)
}

// The Widget's default attributes are accessed via a static method'
@Override
public WikiMarkupWidget createInstance() {
return new WikiMarkupWidget(UUID.randomUUID().toString(), WikiMarkupWidget.getDescriptor(), htmlRenderer, editorRenderer)
}
@Override
public Class<WikiMarkupWidget getType() {
return WikiMarkupWidget.class
}

@Override
public String getTypeName() {
return WikiMarkupWidget.class.getCanonicalName()
}
// Add all the constructor attributes of the Widget class, which needs to be injected via dependency injection. Please make sure that the value's name here matches the Jackson annotation on the Widget itself! ("wikiMarkupWidgetHtmlRenderer" for example)'
@Override
public InjectableValues getInjectableValues() {
final InjectableValues inject = new InjectableValues.Std()
.addValue("wikiMarkupWidgetHtmlRenderer", htmlRenderer)
.addValue("wikiMarkupWidgetEditorRenderer",editorRenderer)
return inject
}
}
将现有 Wiki 插件封装为小组件
如果已经编写了一些 Wiki 插件,那么就可以轻松将其复用为小组件。基于插件的小组件和独立小组件之间没有太大的区别,您需要创建完全相同的类。由于您要将 Wiki 插件用于 HTML 表示,因此您需要将小组件的实际参数作为自变量传递至插件,并调用其 execute 方法以生成 HTML。该框架提供了 Renderer 实现,从而简化了此过程。AbstractDefaultWidgetHtmlRenderer 定义了几个重要方法:

import java.util.Collections
import java.util.HashMap
import java.util.Map
import org.springframework.beans.factory.ObjectFactory
import org.springframework.beans.factory.annotation.Autowired
import com.intland.codebeamer.dashboard.component.common.interfaces.Widget
import com.intland.codebeamer.dashboard.component.widgets.common.DefaultWidgetHtmlRenderer
import com.intland.codebeamer.dashboard.component.widgets.common.attribute.WidgetAttribute
import com.intland.codebeamer.dashboard.component.widgets.provider.WidgetAttributeValueProvider
import com.intland.codebeamer.wiki.plugins.base.AbstractCodeBeamerWikiPlugin

public abstract class AbstractDefaultWidgetHtmlRenderer<U extends Widget, Z extends AbstractCodeBeamerWikiPlugin extends DefaultWidgetHtmlRenderer<U, Z{
@Autowired
public AbstractDefaultWidgetHtmlRenderer(ObjectFactory<Z factory) {
super(factory)
}
protected Map<String, String createPluginParameters(Map<String, WidgetAttribute attributes) {
final Map<String, String parameters = new HashMap<String, String()
// Generates the parameters of the plugin using the WidgetAttributeValueProviders, code omitted

createAdditionalParameters(attributes, parameters)
return parameters
}
// You can add any parameter here, which is not generated by a WidgetAttributeValueProvider instance
protected void createAdditionalParameters(final Map<String, WidgetAttribute attributes, final Map<String, String parameters) {
}
// Returns the WidgetAttributes defined in the Widget itself
protected abstract WidgetAttributeWrapper[] getWidgetAttributes()
// A list of WidgetAttributeValueProvider instances. These can map a WidgetAttribute to a Wiki plugin parameter.
protected Map<WidgetAttributeWrapper, WidgetAttributeValueProvider getValueProviders() {
return Collections.emptyMap()
}
}
WidgetAttributeValueProvider 实例根据 Wiki 插件接口将 WidgetAttributes 转换为 String 参数。

import java.util.Map
import com.intland.codebeamer.dashboard.component.widgets.common.WidgetAttributeWrapper
import com.intland.codebeamer.dashboard.component.widgets.common.attribute.WidgetAttribute
public interface WidgetAttributeValueProvider {
String obtainValue(WidgetAttributeWrapper attribute, Map<String, WidgetAttribute attributes)
}
转换 Integer 就是一个简单的示例:

import com.intland.codebeamer.dashboard.component.widgets.common.attribute.WidgetAttribute

public abstract class AbstractIntegerValueProvider extends AbstractWidgetAttributeValueProvider<WidgetAttribute<Integer {
@Override
protected String convertWidgetAttributeToString(final WidgetAttribute<Integer widgetAttribute) {
return widgetAttribute.getValue().toString()
}
}
AbstractWidgetAttributeValueProvider 类具有 obtainValue 方法的默认实现,因此,子类只需定义 convertWidgetAttributeToStringgetWidgetAttributeKey 方法。后者需要返回 Wiki 插件参数的名称,如以下类所示:

import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Component

@Qualifier("widthIntegerValueProvider")
@Component
public class WidthIntegerValueProvider extends AbstractIntegerValueProvider {
public static final String WIDTH = "width"
@Override
public String getWidgetAttributeKey() {
return WIDTH
}
}
现在,可轻松将插件呈现封装在自定义 Renderer 中:

package com.intland.codebeamer.dashboard.component.widgets.project
import java.util.HashMap
import java.util.Map
import org.apache.commons.lang3.Validate
import org.springframework.beans.factory.ObjectFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Component
import com.intland.codebeamer.dashboard.component.widgets.common.AbstractDefaultWidgetHtmlRenderer
import com.intland.codebeamer.dashboard.component.widgets.common.WidgetAttributeWrapper
import com.intland.codebeamer.dashboard.component.widgets.provider.WidgetAttributeValueProvider
import com.intland.codebeamer.wiki.plugins.releaseganttchart.ReleaseGanttChartPlugin

@Component
@Qualifier("releaseGanttChartWidgetHtmlRenderer")
public class ReleaseGanttChartWidgetHtmlRenderer extends AbstractDefaultWidgetHtmlRenderer<ReleaseGanttChartWidget, ReleaseGanttChartPlugin {
private static final String NULL_MESSAGE_FORMAT = "The value %s must not be null."
private final WidgetAttributeValueProvider releaseListAttributeValueProvider
private final WidgetAttributeValueProvider heightAttributeValueProvider
@Autowired
public ReleaseGanttChartWidgetHtmlRenderer(ObjectFactory<ReleaseGanttChartPlugin objectFactory,
@Qualifier("ganttReleaseListAttributeValueProvider") final WidgetAttributeValueProvider releaseListAttributeValueProvider,
@Qualifier("ganttHeightAttributeProvider") final WidgetAttributeValueProvider heightAttributeValueProvider) {
super(objectFactory)
Validate.notNull(releaseListAttributeValueProvider, NULL_MESSAGE_FORMAT, "releaseListAttributeValueProvider")
Validate.notNull(heightAttributeValueProvider, NULL_MESSAGE_FORMAT, "heightAttributeValueProvider")
this.releaseListAttributeValueProvider = releaseListAttributeValueProvider
this.heightAttributeValueProvider = heightAttributeValueProvider
}

@Override
protected WidgetAttributeWrapper[] getWidgetAttributes() {
return ReleaseGanttChartWidget.Attribute.values()
}
@Override
protected Map<WidgetAttributeWrapper, WidgetAttributeValueProvider getValueProviders() {
final Map<WidgetAttributeWrapper, WidgetAttributeValueProvider valueProviders =
new HashMap<WidgetAttributeWrapper, WidgetAttributeValueProvider()
valueProviders.put(ReleaseGanttChartWidget.Attribute.RELEASE_IDS, releaseListAttributeValueProvider)
valueProviders.put(ReleaseGanttChartWidget.Attribute.HEIGHT, heightAttributeValueProvider)
return valueProviders
}
}
这对您有帮助吗?